]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1848] skeleton Postgres CB implementation added
authorTomek Mrugalski <tomek@isc.org>
Wed, 30 Jun 2021 18:52:52 +0000 (20:52 +0200)
committerTomek Mrugalski <tomek@isc.org>
Wed, 17 Nov 2021 14:35:18 +0000 (15:35 +0100)
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc [new file with mode: 0644]
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h [new file with mode: 0644]

diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc
new file mode 100644 (file)
index 0000000..70ed23f
--- /dev/null
@@ -0,0 +1,1072 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include "pgsql_cb_impl.h"
+#include <asiolink/io_address.h>
+#include <config_backend/constants.h>
+#include <pgsql/pgsql_exchange.h>
+#include <dhcp/option_space.h>
+#include <util/buffer.h>
+#include <cstdint>
+#include <utility>
+
+using namespace isc::asiolink;
+using namespace isc::cb;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr();
+
+PgSqlConfigBackendImpl::
+ScopedAuditRevision::ScopedAuditRevision(PgSqlConfigBackendImpl* impl,
+                                         const int index,
+                                         const ServerSelector& server_selector,
+                                         const std::string& log_message,
+                                         bool cascade_transaction)
+    : impl_(impl) {
+    impl_->createAuditRevision(index, server_selector,
+                               boost::posix_time::microsec_clock::local_time(),
+                               log_message,
+                               cascade_transaction);
+}
+
+PgSqlConfigBackendImpl::
+ScopedAuditRevision::~ScopedAuditRevision() {
+    impl_->clearAuditRevision();
+}
+
+PgSqlConfigBackendImpl::
+PgSqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters,
+                       const DbCallback db_reconnect_callback)
+    : conn_(parameters,
+            IOServiceAccessorPtr(new IOServiceAccessor(PgSqlConfigBackendImpl::getIOService)),
+            db_reconnect_callback), timer_name_(""),
+      audit_revision_created_(false), parameters_(parameters) {
+    // Test schema version first.
+    std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
+                                               PG_SCHEMA_VERSION_MINOR);
+    std::pair<uint32_t, uint32_t> db_version =
+        PgSqlConnection::getVersion(parameters);
+    if (code_version != db_version) {
+        isc_throw(DbOpenError, "Postgres schema version mismatch: need version: "
+                  << code_version.first << "." << code_version.second
+                  << " found version:  " << db_version.first << "."
+                  << db_version.second);
+    }
+
+    // Open the database.
+    conn_.openDatabase();
+}
+
+PgSqlConfigBackendImpl::~PgSqlConfigBackendImpl() {
+    /// nothing to do there. The conn_ connection will be deleted and its dtor
+    /// will take care of releasing the compiled statements and similar.
+}
+
+#if 0
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createBinding(const Triplet<uint32_t>& triplet) {
+    PsqlBindArrayPtr bind(new PsqlBindArray());
+
+    if (triplet.unspecified()) {
+        bind->addNull();
+    } else {
+        bind->add<uint32_t>(triplet.get());
+    }
+    return (bind);
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createMinBinding(const Triplet<uint32_t>& triplet) {
+    PsqlBindArrayPtr bind(new PsqlBindArray());
+    if (triplet.unspecified() || (triplet.getMin() == triplet.get())) {
+        bind->addNull();
+    } else {
+        bind->add<uint32_t>(triplet.getMin());
+    }
+    return (bind);
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createMaxBinding(const Triplet<uint32_t>& triplet) {
+    PsqlBindArrayPtr bind(new PsqlBindArray());
+    if (triplet.unspecified() || (triplet.getMax() == triplet.get())) {
+        bind->addNull();
+    } else {
+        bind->add<uint32_t>(triplet.getMax());
+    }
+    return (bind);
+}
+#endif
+
+/* Triplet<uint32_t>
+PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& binding) {
+    if (!binding) {
+        isc_throw(Unexpected, "Postgres configuration backend internal error: "
+                  "binding pointer is NULL when creating a triplet value");
+    }
+
+    if (binding->empty()) {
+        return (Triplet<uint32_t>());
+    }
+
+    return (Triplet<uint32_t>(binding->getInteger<uint32_t>()));
+} */
+
+/* Triplet<uint32_t>
+PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& def_binding,
+                                      const PsqlBindArrayPtr& min_binding,
+                                      const PsqlBindArrayPtr& max_binding) {
+    if (!def_binding || !min_binding || !max_binding) {
+        isc_throw(Unexpected, "Postgres configuration backend internal error: "
+                  "binding pointer is NULL when creating a triplet value");
+    }
+
+    // This code assumes the database was filled using the API, e.g. it
+    // is not possible (so not handled) to have only the min_binding not NULL.
+    if (def_binding->empty()) {
+        return (Triplet<uint32_t>());
+    }
+
+    uint32_t value = def_binding->getInteger<uint32_t>();
+    uint32_t min_value = value;
+    if (!min_binding->empty()) {
+        min_value = min_binding->getInteger<uint32_t>();
+    }
+    uint32_t max_value = value;
+    if (!max_binding->empty()) {
+        max_value = max_binding->getInteger<uint32_t>();
+    }
+
+    return (Triplet<uint32_t>(min_value, value, max_value));
+} */
+
+void
+PgSqlConfigBackendImpl::createAuditRevision(const int index,
+                                            const ServerSelector& server_selector,
+                                            const boost::posix_time::ptime& audit_ts,
+                                            const std::string& log_message,
+                                            const bool cascade_transaction) {
+    // Do not touch existing audit revision in case of the cascade update.
+    if (audit_revision_created_) {
+        return;
+    }
+
+    /// @todo The audit trail is not really well prepared to handle multiple server
+    /// tags or no server tags. Therefore, if the server selector appears to be
+    /// pointing to multiple servers, no servers or any server we simply associate the
+    /// audit revision with all servers. The only case when we create a dedicated
+    /// audit entry is when there is a single server tag, i.e. "all" or explicit
+    /// server name. In fact, these are the most common two cases.
+    std::string tag = ServerTag::ALL;
+    auto tags = server_selector.getTags();
+    if (tags.size() == 1) {
+        tag = tags.begin()->get();
+    }
+
+#if 0
+    PsqlBindArray in_bindings = {
+        PsqlBindArray::createTimestamp(audit_ts),
+        PsqlBindArray::createString(tag),
+        PsqlBindArray::createString(log_message),
+        PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(cascade_transaction))
+    };
+    conn_.insertQuery(index, in_bindings);
+    audit_revision_created_ = true;
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::clearAuditRevision() {
+    audit_revision_created_ = false;
+}
+
+void
+PgSqlConfigBackendImpl::getRecentAuditEntries(const int index,
+                                              const db::ServerSelector& server_selector,
+                                              const boost::posix_time::ptime& modification_time,
+                                              const uint64_t& modification_id,
+                                              AuditEntryCollection& audit_entries) {
+    isc_throw(NotImplemented, "");
+}
+
+uint64_t
+PgSqlConfigBackendImpl::deleteFromTable(const int index,
+                                        const ServerSelector& server_selector,
+                                        const std::string& operation) {
+    // When deleting multiple objects we must not use ANY server.
+    if (server_selector.amAny()) {
+        isc_throw(InvalidOperation, "deleting multiple objects for ANY server is not"
+                  " supported");
+    }
+
+    PsqlBindArray in_bindings;
+    return (deleteFromTable(index, server_selector, operation, in_bindings));
+}
+
+void
+PgSqlConfigBackendImpl::getGlobalParameters(const int index,
+                                            const PsqlBindArray& in_bindings,
+                                            StampedValueCollection& parameters) {
+#if 0
+    // The following parameters from the dhcp[46]_global_parameter table are
+    // returned:
+    // - id
+    // - name - parameter name
+    // - value - parameter value
+    // - modification_ts - modification timestamp.
+    PsqlBindArray out_bindings = {
+        PsqlBindArray::createInteger<uint64_t>(), // id
+        PsqlBindArray::createString(GLOBAL_PARAMETER_NAME_BUF_LENGTH), // name
+        PsqlBindArray::createString(GLOBAL_PARAMETER_VALUE_BUF_LENGTH), // value
+        PsqlBindArray::createInteger<uint8_t>(), // parameter_type
+        PsqlBindArray::createTimestamp(), // modification_ts
+        PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag
+    };
+
+    StampedValuePtr last_param;
+
+    StampedValueCollection local_parameters;
+
+    conn_.selectQuery(index, in_bindings, out_bindings,
+                      [&last_param, &local_parameters]
+                      (PsqlBindArray& out_bindings) {
+
+        uint64_t id = out_bindings[0]->getInteger<uint64_t>();
+
+        // If we're starting or if this is new parameter being processed...
+        if (!last_param || (last_param->getId() != id)) {
+
+            // parameter name
+            std::string name = out_bindings[1]->getString();
+
+            if (!name.empty()) {
+                last_param = StampedValue::create(name,
+                                                  out_bindings[2]->getString(),
+                                                  static_cast<Element::types>
+                                                  (out_bindings[3]->getInteger<uint8_t>()));
+
+                // id
+                last_param->setId(id);
+
+                // modification_ts
+                last_param->setModificationTime(out_bindings[4]->getTimestamp());
+
+                // server_tag
+                ServerTag last_param_server_tag(out_bindings[5]->getString());
+                last_param->setServerTag(last_param_server_tag.get());
+                // If we're fetching parameters for a given server (explicit server
+                // tag is provided), it takes precedence over the same parameter
+                // specified for all servers. Therefore, we check if the given
+                // parameter already exists and belongs to 'all'.
+                auto& index = local_parameters.get<StampedValueNameIndexTag>();
+                auto existing = index.find(name);
+                if (existing != index.end()) {
+                    // This parameter was already fetched. Let's check if we should
+                    // replace it or not.
+                    if (!last_param_server_tag.amAll() && (*existing)->hasAllServerTag()) {
+                        // Replace parameter specified for 'all' with the one associated
+                        // with the particular server tag.
+                        local_parameters.replace(existing, last_param);
+                        return;
+                    }
+
+                }
+
+                // If there is no such parameter yet or the existing parameter
+                // belongs to a different server and the inserted parameter is
+                // not for all servers.
+                if ((existing == index.end()) ||
+                    (!(*existing)->hasServerTag(last_param_server_tag) &&
+                     !last_param_server_tag.amAll())) {
+                    local_parameters.insert(last_param);
+                }
+            }
+        }
+    });
+
+    parameters.insert(local_parameters.begin(), local_parameters.end());
+#endif
+}
+
+OptionDefinitionPtr
+PgSqlConfigBackendImpl::getOptionDef(const int index,
+                                     const ServerSelector& server_selector,
+                                     const uint16_t code,
+                                     const std::string& space) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    auto tag = getServerTag(server_selector, "fetching option definition");
+
+    OptionDefContainer option_defs;
+#if 0
+    PsqlBindArray in_bindings = {
+        PsqlBindArray::createString(tag),
+        PsqlBindArray::createInteger<uint16_t>(code),
+        PsqlBindArray::createString(space)
+    };
+    getOptionDefs(index, in_bindings, option_defs);
+#endif
+    return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin());
+}
+
+void
+PgSqlConfigBackendImpl::getAllOptionDefs(const int index,
+                     const ServerSelector& server_selector,
+                     OptionDefContainer& option_defs) {
+    auto tags = server_selector.getTags();
+#if 0
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings = {
+            PsqlBindArray::createString(tag.get())
+        };
+        getOptionDefs(index, in_bindings, option_defs);
+    }
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::getModifiedOptionDefs(const int index,
+                                              const ServerSelector& server_selector,
+                                              const boost::posix_time::ptime& modification_time,
+                                              OptionDefContainer& option_defs) {
+    auto tags = server_selector.getTags();
+#if 0
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings = {
+            PsqlBindArray::createString(tag.get()),
+            PsqlBindArray::createTimestamp(modification_time)
+        };
+        getOptionDefs(index, in_bindings, option_defs);
+    }
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::getOptionDefs(const int index,
+                                      const PsqlBindArray& in_bindings,
+                                      OptionDefContainer& option_defs) {
+    // Create output bindings. The order must match that in the prepared
+    // statement.
+#if 0
+    PsqlBindArray out_bindings = {
+        PsqlBindArray::createInteger<uint64_t>(), // id
+        PsqlBindArray::createInteger<uint16_t>(), // code
+        PsqlBindArray::createString(OPTION_NAME_BUF_LENGTH), // name
+        PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH), // space
+        PsqlBindArray::createInteger<uint8_t>(), // type
+        PsqlBindArray::createTimestamp(), // modification_ts
+        PsqlBindArray::createInteger<uint8_t>(), // array
+        PsqlBindArray::createString(OPTION_ENCAPSULATE_BUF_LENGTH), // encapsulate
+        PsqlBindArray::createString(OPTION_RECORD_TYPES_BUF_LENGTH), // record_types
+        PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH), // user_context
+        PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag
+    };
+#endif
+
+    uint64_t last_def_id = 0;
+
+    OptionDefContainer local_option_defs;
+
+#if 0
+    // Run select query.
+    conn_.selectQuery(index, in_bindings, out_bindings,
+                      [&local_option_defs, &last_def_id]
+                      (PsqlBindArray& out_bindings) {
+        // Get pointer to last fetched option definition.
+        OptionDefinitionPtr last_def;
+        if (!local_option_defs.empty()) {
+            last_def = *local_option_defs.rbegin();
+        }
+
+        // See if the last fetched definition is the one for which we now got
+        // the row of data. If not, it means that we need to create new option
+        // definition.
+        if ((last_def_id == 0) ||
+            (last_def_id != out_bindings[0]->getInteger<uint64_t>())) {
+
+            last_def_id = out_bindings[0]->getInteger<uint64_t>();
+
+            // Check array type, because depending on this value we have to use
+            // different constructor.
+            bool array_type = static_cast<bool>(out_bindings[6]->getInteger<uint8_t>());
+            if (array_type) {
+                // Create array option.
+                last_def = OptionDefinition::create(out_bindings[2]->getString(),
+                                                    out_bindings[1]->getInteger<uint16_t>(),
+                                                    out_bindings[3]->getString(),
+                                                    static_cast<OptionDataType>
+                                                    (out_bindings[4]->getInteger<uint8_t>()),
+                                                    array_type);
+            } else {
+                // Create non-array option.
+                last_def = OptionDefinition::create(out_bindings[2]->getString(),
+                                                    out_bindings[1]->getInteger<uint16_t>(),
+                                                    out_bindings[3]->getString(),
+                                                    static_cast<OptionDataType>
+                                                    (out_bindings[4]->getInteger<uint8_t>()),
+                                                    out_bindings[7]->getStringOrDefault("").c_str());
+            }
+
+            // id
+            last_def->setId(last_def_id);
+
+            // record_types
+            ElementPtr record_types_element = out_bindings[8]->getJSON();
+            if (record_types_element) {
+                if (record_types_element->getType() != Element::list) {
+                    isc_throw(BadValue, "invalid record_types value "
+                              << out_bindings[8]->getString());
+                }
+                // This element must contain a list of integers specifying
+                // types of the record fields.
+                for (auto i = 0; i < record_types_element->size(); ++i) {
+                    auto type_element = record_types_element->get(i);
+                    if (type_element->getType() != Element::integer) {
+                        isc_throw(BadValue, "record type values must be integers");
+                    }
+                    last_def->addRecordField(static_cast<OptionDataType>
+                                             (type_element->intValue()));
+                }
+            }
+
+            // Update modification time.
+            last_def->setModificationTime(out_bindings[5]->getTimestamp());
+
+            // server_tag
+            ServerTag last_def_server_tag(out_bindings[10]->getString());
+            last_def->setServerTag(last_def_server_tag.get());
+
+            // If we're fetching option definitions for a given server
+            // (explicit server tag is provided), it takes precedence over
+            // the same option definition specified for all servers.
+            // Therefore, we check if the given option already exists and
+            // belongs to 'all'.
+            auto& index = local_option_defs.get<1>();
+            auto existing_it_pair = index.equal_range(last_def->getCode());
+            auto existing_it = existing_it_pair.first;
+            bool found = false;
+            for ( ; existing_it != existing_it_pair.second; ++existing_it) {
+                if ((*existing_it)->getOptionSpaceName() == last_def->getOptionSpaceName()) {
+                    found = true;
+                    // This option definition was already fetched. Let's check
+                    // if we should replace it or not.
+                    if (!last_def_server_tag.amAll() && (*existing_it)->hasAllServerTag()) {
+                        index.replace(existing_it, last_def);
+                        return;
+                    }
+                    break;
+                }
+            }
+
+            // If there is no such option definition yet or the existing option
+            // definition belongs to a different server and the inserted option
+            // definition is not for all servers.
+            if (!found ||
+                (!(*existing_it)->hasServerTag(last_def_server_tag) &&
+                 !last_def_server_tag.amAll())) {
+                static_cast<void>(local_option_defs.push_back(last_def));
+            }
+        }
+    });
+
+    // Append the option definition fetched by this function into the container
+    // supplied by the caller. The container supplied by the caller may already
+    // hold some option definitions fetched for other server tags.
+    option_defs.insert(option_defs.end(), local_option_defs.begin(),
+                       local_option_defs.end());
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_selector,
+                                              const OptionDefinitionPtr& option_def,
+                                              const std::string& /*space*/,
+                                              const int& /*get_option_def_code_space*/,
+                                              const int& insert_option_def,
+                                              const int& update_option_def,
+                                              const int& create_audit_revision,
+                                              const int& insert_option_def_server) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    auto tag = getServerTag(server_selector, "creating or updating option definition");
+
+    ElementPtr record_types = Element::createList();
+    for (auto field : option_def->getRecordFields()) {
+        record_types->add(Element::create(static_cast<int>(field)));
+    }
+#if 0
+    PsqlBindArrayPtr record_types_binding = record_types->empty() ?
+        PsqlBindArray::createNull() : PsqlBindArray::createString(record_types->str());
+
+    PsqlBindArray in_bindings = {
+        PsqlBindArray::createInteger<uint16_t>(option_def->getCode()),
+        PsqlBindArray::createString(option_def->getName()),
+        PsqlBindArray::createString(option_def->getOptionSpaceName()),
+        PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getType())),
+        PsqlBindArray::createTimestamp(option_def->getModificationTime()),
+        PsqlBindArray::createBool(option_def->getArrayType()),
+        PsqlBindArray::createString(option_def->getEncapsulatedSpace()),
+        record_types_binding,
+        createInputContextBinding(option_def),
+        PsqlBindArray::createString(tag),
+        PsqlBindArray::createInteger<uint16_t>(option_def->getCode()),
+        PsqlBindArray::createString(option_def->getOptionSpaceName())
+    };
+
+    PgSqlTransaction transaction(conn_);
+
+    // Create scoped audit revision. As long as this instance exists
+    // no new audit revisions are created in any subsequent calls.
+    ScopedAuditRevision audit_revision(this,
+                                       create_audit_revision,
+                                       server_selector,
+                                       "option definition set",
+                                       true);
+
+    if (conn_.updateDeleteQuery(update_option_def, in_bindings) == 0) {
+        // Remove the bindings used only during the update.
+        in_bindings.resize(in_bindings.size() - 3);
+        conn_.insertQuery(insert_option_def, in_bindings);
+
+        // Fetch unique identifier of the inserted option definition and use it
+        // as input to the next query.
+        uint64_t id = pgsql_insert_id(conn_.pgsql_);
+
+        // Insert associations of the option definition with servers.
+        attachElementToServers(insert_option_def_server,
+                               server_selector,
+                               PsqlBindArray::createInteger<uint64_t>(id),
+                               PsqlBindArray::createTimestamp(option_def->getModificationTime()));
+    }
+
+    transaction.commit();
+#endif
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+                                  const Option::Universe& universe,
+                                  const ServerSelector& server_selector,
+                                  const uint16_t code,
+                                  const std::string& space) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    auto tag = getServerTag(server_selector, "fetching global option");
+
+#if 0
+    OptionContainer options;
+    PsqlBindArray in_bindings;
+    in_bindings.push_back(PsqlBindArray::createString(tag));
+    if (universe == Option::V4) {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+    } else {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+    }
+    in_bindings.push_back(PsqlBindArray::createString(space));
+    getOptions(index, in_bindings, universe, options);
+    return (options.empty() ? OptionDescriptorPtr() :
+            OptionDescriptor::create(*options.begin()));
+#endif
+}
+
+OptionContainer
+PgSqlConfigBackendImpl::getAllOptions(const int index,
+                                      const Option::Universe& universe,
+                                      const ServerSelector& server_selector) {
+    OptionContainer options;
+#if 0
+    auto tags = server_selector.getTags();
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings = {
+            PsqlBindArray::createString(tag.get())
+        };
+        getOptions(index, in_bindings, universe, options);
+    }
+#endif
+
+    return (options);
+}
+
+OptionContainer
+PgSqlConfigBackendImpl::getModifiedOptions(const int index,
+                                           const Option::Universe& universe,
+                                           const ServerSelector& server_selector,
+                                           const boost::posix_time::ptime& modification_time) {
+    OptionContainer options;
+
+    auto tags = server_selector.getTags();
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings;
+#if 0
+        PsqlBindArray in_bindings = {
+            PsqlBindArray::createString(tag.get()),
+            PsqlBindArray::createTimestamp(modification_time)
+        };
+#endif
+        getOptions(index, in_bindings, universe, options);
+    }
+
+    return (options);
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+                                  const Option::Universe& universe,
+                                  const ServerSelector& server_selector,
+                                  const SubnetID& subnet_id,
+                                  const uint16_t code,
+                                  const std::string& space) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    auto tag = getServerTag(server_selector, "fetching subnet level option");
+
+    OptionContainer options;
+    PsqlBindArray in_bindings;
+#if 0
+    in_bindings.push_back(PsqlBindArray::createString(tag));
+    uint32_t id = static_cast<uint32_t>(subnet_id);
+    in_bindings.push_back(PsqlBindArray::createInteger<uint32_t>(id));
+    if (universe == Option::V4) {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+    } else {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+    }
+    in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+    getOptions(index, in_bindings, universe, options);
+    return (options.empty() ? OptionDescriptorPtr() :
+            OptionDescriptor::create(*options.begin()));
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+                                  const ServerSelector& server_selector,
+                                  const Lease::Type& pool_type,
+                                  const uint64_t pool_id,
+                                  const uint16_t code,
+                                  const std::string& space) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    std::string msg = "fetching ";
+    if (pool_type == Lease::TYPE_PD) {
+        msg += "prefix delegation";
+    } else {
+        msg += "address";
+    }
+    msg += " pool level option";
+    auto tag = getServerTag(server_selector, msg);
+
+    Option::Universe universe = Option::V4;
+    OptionContainer options;
+    PsqlBindArray in_bindings;
+#if 0
+    in_bindings.push_back(PsqlBindArray::createString(tag));
+    in_bindings.push_back(PsqlBindArray::createInteger<uint64_t>(pool_id));
+    if (pool_type == Lease::TYPE_V4) {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+    } else {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+        universe = Option::V6;
+    }
+    in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+    getOptions(index, in_bindings, universe, options);
+    return (options.empty() ? OptionDescriptorPtr() :
+            OptionDescriptor::create(*options.begin()));
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+                                  const Option::Universe& universe,
+                                  const ServerSelector& server_selector,
+                                  const std::string& shared_network_name,
+                                  const uint16_t code,
+                                  const std::string& space) {
+
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "managing configuration for no particular server"
+                  " (unassigned) is unsupported at the moment");
+    }
+
+    auto tag = getServerTag(server_selector, "fetching shared network level option");
+
+    OptionContainer options;
+    PsqlBindArray in_bindings;
+#if 0
+    in_bindings.push_back(PsqlBindArray::createString(tag));
+    in_bindings.push_back(PsqlBindArray::createString(shared_network_name));
+    if (universe == Option::V4) {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+    } else {
+        in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+    }
+    in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+    getOptions(index, in_bindings, universe, options);
+    return (options.empty() ? OptionDescriptorPtr() :
+            OptionDescriptor::create(*options.begin()));
+}
+
+void
+PgSqlConfigBackendImpl::getOptions(const int index,
+                                   const db::PsqlBindArray& in_bindings,
+                                   const Option::Universe& universe,
+                                   OptionContainer& options) {
+#if 0
+    // Create output bindings. The order must match that in the prepared
+    // statement.
+    PsqlBindArray out_bindings;
+    // option_id
+    out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+    // code
+    if (universe == Option::V4) {
+        out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+    } else {
+        out_bindings.push_back(PsqlBindArray::createInteger<uint16_t>());
+    }
+    // value
+    out_bindings.push_back(PsqlBindArray::createBlob(OPTION_VALUE_BUF_LENGTH));
+    // forma\tted_value
+    out_bindings.push_back(PsqlBindArray::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH));
+    // space
+    out_bindings.push_back(PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH));
+    // persistent
+    out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+    // dhcp[46]_subnet_id
+    out_bindings.push_back(PsqlBindArray::createInteger<uint32_t>());
+    // scope_id
+    out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+    // user_context
+    out_bindings.push_back(PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH));
+    // shared_network_name
+    out_bindings.push_back(PsqlBindArray::createString(SHARED_NETWORK_NAME_BUF_LENGTH));
+    // pool_id
+    out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+    // modification_ts
+    out_bindings.push_back(PsqlBindArray::createTimestamp());
+    // server_tag
+    out_bindings.push_back(PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH));
+    // pd_pool_id
+    if (universe == Option::V6) {
+        out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+    }
+
+    uint64_t last_option_id = 0;
+
+    OptionContainer local_options;
+
+    conn_.selectQuery(index, in_bindings, out_bindings,
+                      [this, universe, &local_options, &last_option_id]
+                      (PsqlBindArray& out_bindings) {
+        // Parse option.
+        if (!out_bindings[0]->amNull() &&
+            ((last_option_id == 0) ||
+             (last_option_id < out_bindings[0]->getInteger<uint64_t>()))) {
+            last_option_id = out_bindings[0]->getInteger<uint64_t>();
+
+            OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin());
+            if (desc) {
+                // server_tag for the global option
+                ServerTag last_option_server_tag(out_bindings[12]->getString());
+                desc->setServerTag(last_option_server_tag.get());
+
+                // If we're fetching options for a given server (explicit server
+                // tag is provided), it takes precedence over the same option
+                // specified for all servers. Therefore, we check if the given
+                // option already exists and belongs to 'all'.
+                auto& index = local_options.get<1>();
+                auto existing_it_pair = index.equal_range(desc->option_->getType());
+                auto existing_it = existing_it_pair.first;
+                bool found = false;
+                for ( ; existing_it != existing_it_pair.second; ++existing_it) {
+                    if (existing_it->space_name_ == desc->space_name_) {
+                        found = true;
+                        // This option was already fetched. Let's check if we should
+                        // replace it or not.
+                        if (!last_option_server_tag.amAll() && existing_it->hasAllServerTag()) {
+                            index.replace(existing_it, *desc);
+                            return;
+                        }
+                        break;
+                    }
+                }
+
+                // If there is no such global option yet or the existing option
+                // belongs to a different server and the inserted option is not
+                // for all servers.
+                if (!found ||
+                    (!existing_it->hasServerTag(last_option_server_tag) &&
+                     !last_option_server_tag.amAll())) {
+                    static_cast<void>(local_options.push_back(*desc));
+                }
+            }
+        }
+    });
+
+    // Append the options fetched by this function into the container supplied
+    // by the caller. The container supplied by the caller may already hold
+    // some options fetched for other server tags.
+    options.insert(options.end(), local_options.begin(), local_options.end());
+#endif
+}
+
+#if 0
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::processOptionRow(const Option::Universe& universe,
+                                         PsqlBindArray::iterator first_binding) {
+    // Some of the options have standard or custom definitions.
+    // Depending whether the option has a definition or not a different
+    // C++ class may be used to represent the option. Therefore, the
+    // first thing to do is to see if there is a definition for our
+    // parsed option. The option code and space is needed for it.
+    std::string space = (*(first_binding + 4))->getString();
+    uint16_t code;
+    if (universe == Option::V4) {
+        code = (*(first_binding + 1))->getInteger<uint8_t>();
+    } else {
+        code = (*(first_binding + 1))->getInteger<uint16_t>();
+    }
+
+
+    // Get formatted value if available.
+    std::string formatted_value = (*(first_binding + 3))->getStringOrDefault("");
+
+    OptionPtr option = Option::create(universe, code);
+
+    // If we don't have a formatted value, check for a blob. Add it to the
+    // option if it exists.
+    if (formatted_value.empty()) {
+        std::vector<uint8_t> blob;
+        if (!(*(first_binding + 2))->amNull()) {
+            blob = (*(first_binding + 2))->getBlob();
+        }
+        option->setData(blob.begin(), blob.end());
+    }
+
+    // Check if the option is persistent.
+    bool persistent = static_cast<bool>((*(first_binding + 5))->getIntegerOrDefault<uint8_t>(0));
+
+    // Create option descriptor which encapsulates our option and adds
+    // additional information, i.e. whether the option is persistent,
+    // its option space and timestamp.
+    OptionDescriptorPtr desc = OptionDescriptor::create(option, persistent, formatted_value);
+    desc->space_name_ = space;
+    desc->setModificationTime((*(first_binding + 11))->getTimestamp());
+
+    // Set database id for the option.
+    if (!(*first_binding)->amNull()) {
+        desc->setId((*first_binding)->getInteger<uint64_t>());
+    }
+
+    return (desc);
+}
+
+void
+PgSqlConfigBackendImpl::attachElementToServers(const int index,
+                                               const ServerSelector& server_selector,
+                                               const PsqlBindArrayPtr& first_binding,
+                                               const PsqlBindArrayPtr& in_bindings...) {
+    // Create the vector from the parameter pack.
+    PsqlBindArray in_server_bindings = { first_binding, in_bindings };
+    for (auto tag : server_selector.getTags()) {
+        in_server_bindings.push_back(PsqlBindArray::createString(tag.get()));
+        // Handles the case where the server does not exists.
+        try {
+            conn_.insertQuery(index, in_server_bindings);
+        } catch (const NullKeyError&) {
+            // The message should give the tag value.
+            isc_throw(NullKeyError,
+                      "server '" << tag.get() << "' does not exist");
+        }
+        in_server_bindings.pop_back();
+    }
+}
+#endif
+
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createInputRelayBinding(const NetworkPtr& network) {
+    ElementPtr relay_element = Element::createList();
+    const auto& addresses = network->getRelayAddresses();
+    if (!addresses.empty()) {
+        for (const auto& address : addresses) {
+            relay_element->add(Element::create(address.toText()));
+        }
+    }
+
+#if 0
+    return (relay_element->empty() ? PsqlBindArray::createNull() :
+            PsqlBindArray::condCreateString(relay_element->str()));
+#endif
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createOptionValueBinding(const OptionDescriptorPtr& option) {
+
+    PsqlBindArrayPtr p(new PsqlBindArray());
+    OptionPtr opt = option->option_;
+    if (option->formatted_value_.empty() && (opt->len() > opt->getHeaderLen())) {
+        OutputBuffer buf(opt->len());
+        opt->pack(buf);
+        const char* buf_ptr = static_cast<const char*>(buf.getData());
+        std::vector<uint8_t> blob(buf_ptr + opt->getHeaderLen(),
+                                  buf_ptr + buf.getLength());
+
+        // return (PsqlBindArray::createBlob(blob.begin(), blob.end()));
+    }
+
+    // return (PsqlBindArray::createNull());
+    return (p);
+}
+
+ServerPtr
+PgSqlConfigBackendImpl::getServer(const int index, const ServerTag& server_tag) {
+    ServerCollection servers;
+    PsqlBindArray in_bindings; /* = {
+        PsqlBindArray::createString(server_tag.get())
+    }; */
+    getServers(index, in_bindings, servers);
+
+    return (servers.empty() ? ServerPtr() : *servers.begin());
+}
+
+void
+PgSqlConfigBackendImpl::getAllServers(const int index, db::ServerCollection& servers) {
+    PsqlBindArray in_bindings;
+    getServers(index, in_bindings, servers);
+}
+
+void
+PgSqlConfigBackendImpl::getServers(const int index,
+                                   const PsqlBindArray& in_bindings,
+                                   ServerCollection& servers) {
+    PsqlBindArray out_bindings; /*= {
+        PsqlBindArray::createInteger<uint64_t>(),
+        PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH),
+        PsqlBindArray::createString(SERVER_DESCRIPTION_BUF_LENGTH),
+        PsqlBindArray::createTimestamp()
+    }; */
+#if 0
+    conn_.selectQuery(index, in_bindings, out_bindings,
+                      [&servers](PsqlBindArray& out_bindings) {
+
+        ServerPtr last_server;
+        uint64_t id = out_bindings[0]->getInteger<uint64_t>();
+        if (!last_server || (last_server->getId() != id)) {
+
+            // Set description if it is non-null.
+            auto desc = (out_bindings[2]->amNull() ? "" : out_bindings[2]->getString());
+            last_server = Server::create(ServerTag(out_bindings[1]->getString()),
+                                         desc);
+
+            // id
+            last_server->setId(id);
+
+            // modification_ts
+            last_server->setModificationTime(out_bindings[3]->getTimestamp());
+
+            // New server fetched. Let's store it.
+            servers.insert(last_server);
+        }
+    });
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::createUpdateServer(const int& create_audit_revision,
+                                           const int& create_index,
+                                           const int& update_index,
+                                           const ServerPtr& server) {
+    // The server tag 'all' is reserved.
+    if (server->getServerTag().amAll()) {
+        isc_throw(InvalidOperation, "'all' is a name reserved for the server tag which"
+                  " associates the configuration elements with all servers connecting"
+                  " to the database and a server with this name may not be created");
+    }
+
+    // Create scoped audit revision. As long as this instance exists
+    // no new audit revisions are created in any subsequent calls.
+    ScopedAuditRevision audit_revision(this,
+                                       create_audit_revision,
+                                       ServerSelector::ALL(),
+                                       "server set",
+                                       true);
+
+    PgSqlTransaction transaction(conn_);
+
+    PsqlBindArray in_bindings; /* = {
+        PsqlBindArray::createString(server->getServerTagAsText()),
+        PsqlBindArray::createString(server->getDescription()),
+        PsqlBindArray::createTimestamp(server->getModificationTime())
+    };
+
+    try {
+        conn_.insertQuery(create_index, in_bindings);
+
+    } catch (const DuplicateEntry&) {
+        in_bindings.push_back(PsqlBindArray::createString(server->getServerTagAsText()));
+        conn_.updateDeleteQuery(update_index, in_bindings);
+    }*/
+
+    transaction.commit();
+}
+
+std::string
+PgSqlConfigBackendImpl::getType() const {
+    return ("mysql");
+}
+
+std::string
+PgSqlConfigBackendImpl::getHost() const {
+    std::string host = "localhost";
+    try {
+        host = conn_.getParameter("host");
+    } catch (...) {
+        // No host parameter. Return localhost as a default.
+    }
+    return (host);
+}
+
+uint16_t
+PgSqlConfigBackendImpl::getPort() const {
+    try {
+        std::string sport = conn_.getParameter("port");
+        return (boost::lexical_cast<uint16_t>(sport));
+
+    } catch (...) {
+        // No port parameter or parameter invalid.
+    }
+    return (0);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h
new file mode 100644 (file)
index 0000000..aad7b57
--- /dev/null
@@ -0,0 +1,617 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PGSQL_CONFIG_BACKEND_IMPL_H
+#define PGSQL_CONFIG_BACKEND_IMPL_H
+
+#include <cc/stamped_value.h>
+#include <database/audit_entry.h>
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/subnet_id.h>
+#include <exceptions/exceptions.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for PostgreSQL Config Backend implementations.
+///
+/// This class contains common methods for manipulating data in the
+/// Postgres database, used by all servers.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+class PgSqlConfigBackendImpl {
+protected:
+
+    /// @brief RAII object used to protect against creating multiple
+    /// audit revisions during cascade configuration updates.
+    ///
+    /// Audit revision is created per a single database transaction.
+    /// It includes log message associated with the configuration
+    /// change. Single command sent over the control API should
+    /// result in a single audit revision entry in the database.
+    /// A single configuration update often consists of multiple
+    /// insertions, updates and/or deletes in the database. For
+    /// example, a subnet contains pools and DHCP options which are
+    /// inserted to their respective tables. We refer to such update
+    /// as a cascade update. Cascade update should result in a single
+    /// audit revision and an audit entry for a subnet, rather than
+    /// multiple audit revisions and audit entries for the subnet,
+    /// pools and child DHCP options.
+    ///
+    /// Creating an instance of the @c ScopedAuditRevision guards
+    /// against creation of multiple audit revisions when child
+    /// objects are inserted or updated in the database. When the
+    /// instance of this object goes out of scope the new audit
+    /// revisions can be created. The caller must ensure that
+    /// the instance of this object exists throughout the whole
+    /// transaction with the database.
+    class ScopedAuditRevision {
+    public:
+
+        /// @brief Constructor.
+        ///
+        /// Creates new audit revision and sets the flag in the
+        /// Postgres CB implementation object which prevents new audit
+        /// revisions to be created while this instance exists.
+        ///
+        /// @param impl pointer to the Postgres CB implementation.
+        /// @param index index of the query to set session variables
+        /// used for creation of the audit revision and the audit
+        /// entries.
+        /// @param server_selector Server selector.
+        /// @param log_message log message associated with the audit
+        /// revision to be inserted into the database.
+        /// @param cascade_transaction boolean flag indicating if
+        /// we're performing cascade transaction. If set to true,
+        /// the audit entries for the child objects (e.g. DHCP
+        /// options) won't be created.
+        ScopedAuditRevision(PgSqlConfigBackendImpl* impl,
+                            const int index,
+                            const db::ServerSelector& server_selector,
+                            const std::string& log_message,
+                            bool cascade_transaction);
+
+        /// @brief Destructor.
+        ///
+        /// Clears the flag which is blocking creation of the new
+        /// audit revisions.
+        ~ScopedAuditRevision();
+
+    private:
+
+        /// @brief Pointer to the Postgres CB implementation.
+        PgSqlConfigBackendImpl* impl_;
+    };
+
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    /// concerned with the database.
+    /// @param db_reconnect_callback The connection recovery callback.
+    explicit PgSqlConfigBackendImpl(const db::DatabaseConnection::ParameterMap& parameters,
+                                    const db::DbCallback db_reconnect_callback);
+
+    /// @brief Destructor.
+    virtual ~PgSqlConfigBackendImpl();
+
+    /// @todo: implement condCreateInteger(const util::Optional<T>& value)
+#if 0
+    static db::PsqlBindArrayPtr createBinding(const Triplet<uint32_t>& triplet);
+    static db::PsqlBindArrayPtr createMaxBinding(const Triplet<uint32_t>& triplet);
+    static db::PsqlBindArrayPtr createMinBinding(const Triplet<uint32_t>& triplet);
+    static Triplet<uint32_t> createTriplet(const db::PsqlBindArrayPtr& binding);
+    static Triplet<uint32_t> createTriplet(const db::PsqlBindArrayPtr& def_binding,
+        const db::PsqlBindArrayPtr& min_binding, const db::PsqlBindArrayPtr& max_binding);
+#endif
+    std::string getServerTag(const db::ServerSelector& server_selector,
+                            const std::string& operation );
+
+    /// @brief Returns server tags associated with the particular selector
+    /// as text.
+    ///
+    /// This method is useful for logging purposes.
+    std::string getServerTagsAsText(const db::ServerSelector& server_selector) const {
+        std::ostringstream s;
+        auto server_tags = server_selector.getTags();
+        for (auto tag : server_tags) {
+            if (s.tellp() != 0) {
+                s << ", ";
+            }
+            s << tag.get();
+        }
+
+        return (s.str());
+    }
+
+
+
+    /// @brief Invokes the corresponding stored procedure in MySQL.
+    ///
+    /// The @c createAuditRevision stored procedure creates new audit
+    /// revision and initializes several session variables to be used when
+    /// the audit entries will be created for the inserted, updated or
+    /// deleted configuration elements.
+    ///
+    /// @param index query index.
+    /// @param server_selector Server selector.
+    /// @param audit_ts Timestamp to be associated with the audit
+    /// revision.
+    /// @param log_message log message to be used for the audit revision.
+    /// @param cascade_transaction Boolean value indicating whether the
+    /// configuration modification is performed as part of the owning
+    /// element modification, e.g. subnet is modified resulting in
+    /// modification of the DHCP options it owns. In that case only the
+    /// audit entry for the owning element should be created.
+    void createAuditRevision(const int index,
+                             const db::ServerSelector& server_selector,
+                             const boost::posix_time::ptime& audit_ts,
+                             const std::string& log_message,
+                             const bool cascade_transaction);
+
+    /// @brief Clears the flag blocking creation of the new audit revisions.
+    ///
+    /// This is used by the @c ScopedAuditRevision object.
+    void clearAuditRevision();
+
+    /// @brief Sends query to the database to retrieve most recent audit entries.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @param modification_time Timestamp being a lower limit for the returned
+    /// result set, i.e. entries later than specified time are returned.
+    /// @param modification_id Identifier being a lower limit for the returned
+    /// result set, used when two (or more) revisions have the same
+    /// modification_time.
+    /// @param [out] audit_entries Reference to the container where fetched audit
+    /// entries will be inserted.
+    void getRecentAuditEntries(const int index,
+                               const db::ServerSelector& server_selector,
+                               const boost::posix_time::ptime& modification_time,
+                               const uint64_t& modification_id,
+                               db::AuditEntryCollection& audit_entries);
+
+    /// @todo: implement uint64_t deleteFromTable(const int index, ...)
+
+    /// @brief Sends query to delete rows from a table.
+    ///
+    /// @param index Index of the statement to be executed.
+    /// @param server_selector Server selector.
+    /// @param operation Operation which results in calling this function. This is
+    /// used for error reporting purposes.
+    /// @return Number of deleted rows.
+    uint64_t deleteFromTable(const int index,
+                             const db::ServerSelector& server_selector,
+                             const std::string& operation);
+
+    uint64_t deleteFromTable(const int index,
+                             const db::ServerSelector& server_selector,
+                             const std::string& operation,
+                             db::PsqlBindArray& bindings);
+
+    /// @brief Sends query to retrieve multiple global parameters.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param in_bindings Input bindings specifying selection criteria. The
+    /// size of the bindings collection must match the number of placeholders
+    /// in the prepared statement. The input bindings collection must be empty
+    /// if the query contains no WHERE clause.
+    /// @param [out] parameters Reference to the container where fetched parameters
+    /// will be inserted.
+    void getGlobalParameters(const int index,
+                             const db::PsqlBindArray& in_bindings,
+                             data::StampedValueCollection& parameters);
+
+    /// @brief Sends query to retrieve single option definition by code and
+    /// option space.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option definition or NULL if such
+    /// option definition doesn't exist.
+    OptionDefinitionPtr getOptionDef(const int index,
+                                     const db::ServerSelector& server_selector,
+                                     const uint16_t code,
+                                     const std::string& space);
+
+    /// @brief Sends query to retrieve all option definitions.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @param [out] option_defs Reference to the container where option
+    /// definitions are to be stored.
+    void getAllOptionDefs(const int index,
+                          const db::ServerSelector& server_selector,
+                          OptionDefContainer& option_defs);
+
+    /// @brief Sends query to retrieve option definitions with modification
+    /// time later than specified timestamp.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @param modification_time Lower bound subnet modification time.
+    /// @param [out] option_defs Reference to the container where option
+    /// definitions are to be stored.
+    void getModifiedOptionDefs(const int index,
+                               const db::ServerSelector& server_selector,
+                               const boost::posix_time::ptime& modification_time,
+                               OptionDefContainer& option_defs);
+
+    /// @brief Sends query to the database to retrieve multiple option
+    /// definitions.
+    ///
+    /// Query should order option definitions by id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param in_bindings Input bindings specifying selection criteria. The
+    /// size of the bindings collection must match the number of placeholders
+    /// in the prepared statement. The input bindings collection must be empty
+    /// if the query contains no WHERE clause.
+    /// @param [out] option_defs Reference to the container where fetched
+    /// option definitions will be inserted.
+    void
+    getOptionDefs(const int index,
+                  const db::PsqlBindArray& in_bindings,
+                  OptionDefContainer& option_defs);
+
+    /// @brief Creates or updates an option definition.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option_def Option definition to be added or updated.
+    /// @param space Default option space
+    /// @param get_option_def_code_space Statement getting option
+    /// definition by code and space.
+    /// @param insert_option_def Statement inserting option definition.
+    /// @param update_option_def Statement updating option definition.
+    /// @param create_audit_revision Statement creating audit revision.
+    /// @param insert_option_def_server Statement associating option
+    /// definition with a server.
+    /// @throw NotImplemented if server selector is "unassigned".
+    void createUpdateOptionDef(const db::ServerSelector& server_selector,
+                               const OptionDefinitionPtr& option_def,
+                               const std::string& space,
+                               const int& get_option_def_code_space,
+                               const int& insert_option_def,
+                               const int& update_option_def,
+                               const int& create_audit_revision,
+                               const int& insert_option_def_server);
+
+    /// @brief Sends query to retrieve single global option by code and
+    /// option space.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param server_selector Server selector.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option or NULL if such option
+    /// doesn't exist.
+    OptionDescriptorPtr
+    getOption(const int index, const Option::Universe& universe,
+              const db::ServerSelector& server_selector, const uint16_t code,
+              const std::string& space);
+
+    /// @brief Sends query to retrieve all global options.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param server_selector Server selector.
+    /// @return Container holding returned options.
+    OptionContainer
+    getAllOptions(const int index, const Option::Universe& universe,
+                  const db::ServerSelector& server_selector);
+
+    /// @brief Sends query to retrieve global options with modification
+    /// time later than specified timestamp.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param server_selector Server selector.
+    /// @return Container holding returned options.
+    OptionContainer
+    getModifiedOptions(const int index, const Option::Universe& universe,
+                       const db::ServerSelector& server_selector,
+                       const boost::posix_time::ptime& modification_time);
+
+    /// @brief Sends query to retrieve single option by code and option space
+    /// for a given subnet id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param server_selector Server selector.
+    /// @param subnet_id Subnet identifier.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option descriptor or NULL if such
+    /// option doesn't exist.
+    OptionDescriptorPtr getOption(const int index,
+                                  const Option::Universe& universe,
+                                  const db::ServerSelector& server_selector,
+                                  const dhcp::SubnetID& subnet_id,
+                                  const uint16_t code,
+                                  const std::string& space);
+
+    /// @brief Sends query to retrieve single option by code and option space
+    /// for a given address or prefix delegation (v6) pool id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @param pool_type Pool type (Lease::TYPE_V4, TYPE_NA or TYPE_PD).
+    /// @param pool_id Pool identifier in the database.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option descriptor or NULL if such
+    /// option doesn't exist.
+    OptionDescriptorPtr getOption(const int index,
+                                  const db::ServerSelector& server_selector,
+                                  const dhcp::Lease::Type& pool_type,
+                                  const uint64_t pool_id,
+                                  const uint16_t code,
+                                  const std::string& space);
+
+    /// @brief Sends query to retrieve single option by code and option space
+    /// for a given shared network.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Shared network name.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option descriptor or NULL if such
+    /// option doesn't exist.
+    OptionDescriptorPtr getOption(const int index,
+                                  const Option::Universe& universe,
+                                  const db::ServerSelector& server_selector,
+                                  const std::string& shared_network_name,
+                                  const uint16_t code,
+                                  const std::string& space);
+
+    /// @brief Sends query to the database to retrieve multiple options.
+    ///
+    /// Query should order by option_id.
+    ///
+    /// @note The universe is reused to switch between DHCPv4 and DHCPv6
+    /// option layouts.
+    /// @param family Address family (either AF_INET or AF_INET6).
+    /// @param index Index of the query to be used.
+    /// @param in_bindings Input bindings specifying selection criteria. The
+    /// size of the bindings collection must match the number of placeholders
+    /// in the prepared statement. The input bindings collection must be empty
+    /// if the query contains no WHERE clause.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param [out] options Reference to the container where fetched options
+    /// will be inserted.
+    void getOptions(const int index,
+                    const db::PsqlBindArray& in_bindings,
+                    const Option::Universe& universe,
+                    OptionContainer& options);
+
+    /// @todo implement OptionDescriptorPtr processOptionRow(const Option::Universe& universe, ...)
+
+    /// @todo implement void attachElementToServers(const int index, ...)
+
+    /// @todo implement
+    db::PsqlBindArrayPtr createInputRelayBinding(const NetworkPtr& network);
+
+    /// @todo implement template<typename T> db::MySqlBindingPtr createInputRequiredClassesBinding(const T& object)
+
+    /// @todo implement db::MySqlBindingPtr createInputContextBinding(const T& config_element) {
+
+    /// @todo implement
+    db::PsqlBindArrayPtr createOptionValueBinding(const OptionDescriptorPtr& option);
+
+    /// @brief Retrieves a server.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_tag Server tag of the server to be retrieved.
+    /// @return Pointer to the @c Server object representing the server or
+    /// null if such server doesn't exist.
+    db::ServerPtr getServer(const int index, const data::ServerTag& server_tag);
+
+    /// @brief Retrieves all servers.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param [out] servers Reference to the container where fetched servers
+    /// will be inserted.
+    void getAllServers(const int index, db::ServerCollection& servers);
+
+    /// @todo implement
+    /// @brief Sends query to retrieve servers.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param bindings Reference to the MySQL input bindings.
+    /// @param [out] servers Reference to the container where fetched servers
+    /// will be inserted.
+    void getServers(const int index,
+                    const db::PsqlBindArray& bindings,
+                    db::ServerCollection& servers);
+
+    /// @brief Creates or updates a server.
+    ///
+    /// This method attempts to insert a new server into the database using
+    /// the query identified by @c create_index. If the insertion fails because
+    /// the server with the given tag already exists in the database, the
+    /// existing server is updated using the query identified by the
+    /// @c update_index.
+    ///
+    /// @param create_audit_revision Index of the query inserting audit
+    /// revision.
+    /// @param create_index Index of the INSERT query to be used.
+    /// @param update_index Index of the UPDATE query to be used.
+    /// @param server Pointer to the server to be inserted or updated.
+    /// @throw InvalidOperation when trying to create a duplicate or
+    /// update the logical server 'all'.
+    void createUpdateServer(const int& create_audit_revision,
+                            const int& create_index,
+                            const int& update_index,
+                            const db::ServerPtr& server);
+
+    /// @todo implement template<typename T, typename... R> void multipleUpdateDeleteQueries(T first_index, R... other_indexes)
+
+    /// @brief Removes configuration elements from the index which don't match
+    /// the specified server selector.
+    ///
+    /// This is a generic function which removes configuration elements which
+    /// don't match the specified selector. In order to fetch all server tags
+    /// for the returned configuration element, the query must not limit the
+    /// results to the given server tag. Instead, it must post process the
+    /// result to eliminate those configuration elements for which the desired
+    /// server tag wasn't found.
+    ///
+    /// If the server selector is set to ANY, this method is no-op.
+    ///
+    /// @tparam CollectionIndex Type of the collection to be processed.
+    /// @param server_selector Server selector.
+    /// @param index Reference to the index holding the returned configuration
+    /// elements to be processed.
+    template<typename CollectionIndex>
+    void tossNonMatchingElements(const db::ServerSelector& server_selector,
+                                 CollectionIndex& index) {
+        // Don't filter the matching server tags if the server selector is
+        // set to ANY.
+        if (server_selector.amAny()) {
+            return;
+        }
+
+        // Go over the collection of elements.
+        for (auto elem = index.begin(); elem != index.end(); ) {
+
+            // If we're asking for shared networks matching all servers,
+            // we have to make sure that the fetched element has "all"
+            // server tag.
+            if (server_selector.amAll()) {
+                if (!(*elem)->hasAllServerTag()) {
+                    // It doesn't so let's remove it.
+                    elem = index.erase(elem);
+                    continue;
+                }
+
+            } else if (server_selector.amUnassigned()) {
+                // Returned element has server tags but we expect that the
+                // elements are unassigned.
+                if (!(*elem)->getServerTags().empty()) {
+                    elem = index.erase(elem);
+                    continue;
+                }
+
+            } else {
+                // Server selector contains explicit server tags, so
+                // let's see if the returned elements includes any of
+                // them.
+                auto tags = server_selector.getTags();
+                bool tag_found = false;
+                for (auto tag : tags) {
+                    if ((*elem)->hasServerTag(tag) ||
+                        (*elem)->hasAllServerTag()) {
+                        tag_found = true;
+                        break;
+                    }
+                }
+                if (!tag_found) {
+                    // Tag not matching, so toss the element.
+                    elem = index.erase(elem);
+                    continue;
+                }
+            }
+
+            // Go to the next element if we didn't toss the current one.
+            // Otherwise, the erase() function should have already taken
+            // us to the next one.
+            ++elem;
+        }
+    }
+
+    /// @brief Returns backend type in the textual format.
+    ///
+    /// @return "pgsql".
+    std::string getType() const;
+
+    /// @brief Returns backend host.
+    ///
+    /// This is used by the @c BaseConfigBackendPool to select backend
+    /// when @c BackendSelector is specified.
+    ///
+    /// @return host on which the database is located.
+    std::string getHost() const;
+
+    /// @brief Returns backend port number.
+    ///
+    /// This is used by the @c BaseConfigBackendPool to select backend
+    /// when @c BackendSelector is specified.
+    ///
+    /// @return Port number on which database service is available.
+    uint16_t getPort() const;
+
+    /// @brief Return backend parameters
+    ///
+    /// Returns the backend parameters
+    ///
+    /// @return Parameters of the backend.
+    const isc::db::DatabaseConnection::ParameterMap& getParameters() {
+        return (parameters_);
+    }
+
+    /// @brief Sets IO service to be used by the Postgres config backend.
+    ///
+    /// @param IOService object, used for all ASIO operations.
+    static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
+        io_service_ = io_service;
+    }
+
+    /// @brief Returns pointer to the IO service.
+    static isc::asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Represents connection to the Postgres database.
+    db::PgSqlConnection conn_;
+
+protected:
+
+    /// @brief Timer name used to register database reconnect timer.
+    std::string timer_name_;
+
+private:
+
+    /// @brief Boolean flag indicating if audit revision has been created
+    /// using @c ScopedAuditRevision object.
+    bool audit_revision_created_;
+
+    /// @brief Connection parameters
+    isc::db::DatabaseConnection::ParameterMap parameters_;
+
+    /// The IOService object, used for all ASIO operations.
+    static isc::asiolink::IOServicePtr io_service_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif