]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#95] All Queries - CRUD for servers
authorThomas Markwalder <tmark@isc.org>
Tue, 11 Jan 2022 15:44:41 +0000 (10:44 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Jan 2022 17:04:10 +0000 (12:04 -0500)
src/hooks/dhcp/pgsql_cb/Makefile.am
    New files:
        pgsql_query_macros.h pgsql_cb_dhcp4.cc pgsql_cb_dhcp4.h

src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.*
    Initial implementation - includes CRUD for servers

src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.*
    PgSqlConfigBackendImpl::getStatement()
    PgSqlConfigBackendImpl::selectQuery()
    PgSqlConfigBackendImpl::insertQuery()
    PgSqlConfigBackendImpl::updateDeleteQuery()
    PgSqlConfigBackendImpl::createAuditRevision()
    PgSqlConfigBackendImpl::getRecentAuditEntries()
    PgSqlConfigBackendImpl::deleteFromTable()
    PgSqlConfigBackendImpl::getLastInsertId()
    PgSqlConfigBackendImpl::createInputRelayBinding()
    PgSqlConfigBackendImpl::createOptionValueBinding()
    PgSqlConfigBackendImpl::getServer()
    PgSqlConfigBackendImpl::getServers()
    PgSqlConfigBackendImpl::createUpdateServer()
    PgSqlConfigBackendImpl::attachElementToServers()
    PgSqlConfigBackendImpl::addRelayBinding()
    PgSqlConfigBackendImpl::addOptionValueBinding()
    PgSqlConfigBackendImpl::addRequiredClassesBinding()
    PgSqlConfigBackendImpl::addOptionValueBinding()

src/hooks/dhcp/pgsql_cb/pgsql_cb_messages.mes
    New messages

src/hooks/dhcp/pgsql_cb/tests/Makefile.am
    New file:pgsql_cb_dhcp4_unittest.cc

src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc
    New file

src/lib/config_backend/base_config_backend.h
src/lib/config_backend/tests/config_backend_mgr_unittest.cc
src/lib/database/backend_selector.h
src/lib/database/backend_selector.cc
src/lib/database/tests/backend_selector_unittest.cc
    "pgsql" -> "postgresql"
    BackendSelector::Type::PGSQL -> BackendSelector::Type::POSTGRESQL

src/lib/pgsql/pgsql_connection.cc
    Added NULL_KEY
    PgSqlConnection::checkStatementError() - throws DuplicateEntry and NullKeyError

src/lib/pgsql/pgsql_connection.h
    Added NULL_KEY
    Upped PGSQL_MAX_PARAMETERS_IN_QUERY to 128;

src/lib/pgsql/pgsql_exchange.*
    PsqlBindArray::popBack() - new function
    Cleaned up AddOptional functions
    PsqlBindArray::add(const ConstElementPtr& value) - new

23 files changed:
src/hooks/dhcp/pgsql_cb/Makefile.am
src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.cc [new file with mode: 0644]
src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.h [new file with mode: 0644]
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h
src/hooks/dhcp/pgsql_cb/pgsql_cb_messages.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_messages.h
src/hooks/dhcp/pgsql_cb/pgsql_cb_messages.mes
src/hooks/dhcp/pgsql_cb/pgsql_macros.h [deleted file]
src/hooks/dhcp/pgsql_cb/pgsql_query_macros_dhcp.h [new file with mode: 0644]
src/hooks/dhcp/pgsql_cb/tests/Makefile.am
src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc [new file with mode: 0644]
src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_impl_unittest.cc
src/lib/config_backend/base_config_backend.h
src/lib/config_backend/tests/config_backend_mgr_unittest.cc
src/lib/database/backend_selector.cc
src/lib/database/backend_selector.h
src/lib/database/tests/backend_selector_unittest.cc
src/lib/pgsql/pgsql_connection.cc
src/lib/pgsql/pgsql_connection.h
src/lib/pgsql/pgsql_exchange.cc
src/lib/pgsql/pgsql_exchange.h
src/lib/pgsql/tests/pgsql_exchange_unittest.cc

index 9ff5dd06b7708df1fad9b113fc6b967caeccc1a0..45835456761e606be057906a67005f6af3b1ffc9 100644 (file)
@@ -24,7 +24,8 @@ libpgsqlcb_la_SOURCES  = pgsql_cb_callouts.cc
 libpgsqlcb_la_SOURCES += pgsql_cb_impl.cc pgsql_cb_impl.h
 libpgsqlcb_la_SOURCES += pgsql_cb_messages.cc pgsql_cb_messages.h
 libpgsqlcb_la_SOURCES += pgsql_cb_log.cc pgsql_cb_log.h
-libpgsqlcb_la_SOURCES += pgsql_macros.h
+libpgsqlcb_la_SOURCES += pgsql_query_macros.h
+libpgsqlcb_la_SOURCES += pgsql_cb_dhcp4.cc pgsql_cb_dhcp4.h
 libpgsqlcb_la_SOURCES += version.cc
 
 libpgsqlcb_la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.cc b/src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.cc
new file mode 100644 (file)
index 0000000..bfb7740
--- /dev/null
@@ -0,0 +1,3366 @@
+// Copyright (C) 2021-2022 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_dhcp4.h>
+#include <pgsql_cb_impl.h>
+#include <pgsql_query_macros_dhcp.h>
+#include <cc/data.h>
+#include <config_backend/constants.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcp/classify.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <util/buffer.h>
+#include <util/boost_time_utils.h>
+#include <util/multi_threading_mgr.h>
+#include <pgsql/pgsql_connection.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <array>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+using namespace isc::cb;
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::log;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the PgSQL Configuration Backend.
+class PgSqlConfigBackendDHCPv4Impl : public PgSqlConfigBackendImpl {
+public:
+
+    /// @brief Statement tags.
+    ///
+    /// The contents of the enum are indexes into the list of SQL statements.
+    /// It is assumed that the order is such that the indices of statements
+    /// reading the database are less than those of statements modifying the
+    /// database.
+    enum StatementIndex {
+        CREATE_AUDIT_REVISION,
+        CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
+        GET_GLOBAL_PARAMETER4,
+        GET_ALL_GLOBAL_PARAMETERS4,
+        GET_MODIFIED_GLOBAL_PARAMETERS4,
+        GET_SUBNET4_ID_NO_TAG,
+        GET_SUBNET4_ID_ANY,
+        GET_SUBNET4_ID_UNASSIGNED,
+        GET_SUBNET4_PREFIX_NO_TAG,
+        GET_SUBNET4_PREFIX_ANY,
+        GET_SUBNET4_PREFIX_UNASSIGNED,
+        GET_ALL_SUBNETS4,
+        GET_ALL_SUBNETS4_UNASSIGNED,
+        GET_MODIFIED_SUBNETS4,
+        GET_MODIFIED_SUBNETS4_UNASSIGNED,
+        GET_SHARED_NETWORK_SUBNETS4,
+        GET_POOL4_RANGE,
+        GET_POOL4_RANGE_ANY,
+        GET_SHARED_NETWORK4_NAME_NO_TAG,
+        GET_SHARED_NETWORK4_NAME_ANY,
+        GET_SHARED_NETWORK4_NAME_UNASSIGNED,
+        GET_ALL_SHARED_NETWORKS4,
+        GET_ALL_SHARED_NETWORKS4_UNASSIGNED,
+        GET_MODIFIED_SHARED_NETWORKS4,
+        GET_MODIFIED_SHARED_NETWORKS4_UNASSIGNED,
+        GET_OPTION_DEF4_CODE_SPACE,
+        GET_ALL_OPTION_DEFS4,
+        GET_MODIFIED_OPTION_DEFS4,
+        GET_OPTION4_CODE_SPACE,
+        GET_ALL_OPTIONS4,
+        GET_MODIFIED_OPTIONS4,
+        GET_OPTION4_SUBNET_ID_CODE_SPACE,
+        GET_OPTION4_POOL_ID_CODE_SPACE,
+        GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
+        GET_CLIENT_CLASS4_NAME,
+        GET_ALL_CLIENT_CLASSES4,
+        GET_ALL_CLIENT_CLASSES4_UNASSIGNED,
+        GET_MODIFIED_CLIENT_CLASSES4,
+        GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED,
+        GET_AUDIT_ENTRIES4_TIME,
+        GET_SERVER4,
+        GET_ALL_SERVERS4,
+        INSERT_GLOBAL_PARAMETER4,
+        INSERT_GLOBAL_PARAMETER4_SERVER,
+        INSERT_SUBNET4,
+        INSERT_SUBNET4_SERVER,
+        INSERT_POOL4,
+        INSERT_SHARED_NETWORK4,
+        INSERT_SHARED_NETWORK4_SERVER,
+        INSERT_OPTION_DEF4,
+        INSERT_OPTION_DEF4_CLIENT_CLASS,
+        INSERT_OPTION_DEF4_SERVER,
+        INSERT_OPTION4,
+        INSERT_OPTION4_SERVER,
+        INSERT_CLIENT_CLASS4,
+        INSERT_CLIENT_CLASS4_SERVER,
+        INSERT_CLIENT_CLASS4_DEPENDENCY,
+        INSERT_SERVER4,
+        UPDATE_GLOBAL_PARAMETER4,
+        UPDATE_SUBNET4,
+        UPDATE_SHARED_NETWORK4,
+        UPDATE_OPTION_DEF4,
+        UPDATE_OPTION_DEF4_CLIENT_CLASS,
+        UPDATE_OPTION4,
+        UPDATE_OPTION4_SUBNET_ID,
+        UPDATE_OPTION4_POOL_ID,
+        UPDATE_OPTION4_SHARED_NETWORK,
+        UPDATE_OPTION4_CLIENT_CLASS,
+        UPDATE_CLIENT_CLASS4,
+        UPDATE_CLIENT_CLASS4_SAME_POSITION,
+        UPDATE_SERVER4,
+        DELETE_GLOBAL_PARAMETER4,
+        DELETE_ALL_GLOBAL_PARAMETERS4,
+        DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED,
+        DELETE_SUBNET4_ID_WITH_TAG,
+        DELETE_SUBNET4_ID_ANY,
+        DELETE_SUBNET4_PREFIX_WITH_TAG,
+        DELETE_SUBNET4_PREFIX_ANY,
+        DELETE_ALL_SUBNETS4,
+        DELETE_ALL_SUBNETS4_UNASSIGNED,
+        DELETE_ALL_SUBNETS4_SHARED_NETWORK_NAME,
+        DELETE_SUBNET4_SERVER,
+        DELETE_POOLS4,
+        DELETE_SHARED_NETWORK4_NAME_WITH_TAG,
+        DELETE_SHARED_NETWORK4_NAME_ANY,
+        DELETE_ALL_SHARED_NETWORKS4,
+        DELETE_ALL_SHARED_NETWORKS4_UNASSIGNED,
+        DELETE_SHARED_NETWORK4_SERVER,
+        DELETE_OPTION_DEF4_CODE_NAME,
+        DELETE_ALL_OPTION_DEFS4,
+        DELETE_ALL_OPTION_DEFS4_UNASSIGNED,
+        DELETE_OPTION_DEFS4_CLIENT_CLASS,
+        DELETE_OPTION4,
+        DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED,
+        DELETE_OPTION4_SUBNET_ID,
+        DELETE_OPTION4_POOL_RANGE,
+        DELETE_OPTION4_SHARED_NETWORK,
+        DELETE_OPTIONS4_SUBNET_ID_PREFIX,
+        DELETE_OPTIONS4_SHARED_NETWORK,
+        DELETE_OPTIONS4_CLIENT_CLASS,
+        DELETE_CLIENT_CLASS4_DEPENDENCY,
+        DELETE_CLIENT_CLASS4_SERVER,
+        DELETE_ALL_CLIENT_CLASSES4,
+        DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED,
+        DELETE_CLIENT_CLASS4,
+        DELETE_CLIENT_CLASS4_ANY,
+        DELETE_SERVER4,
+        DELETE_ALL_SERVERS4,
+        GET_LAST_INSERT_ID4,
+        NUM_STATEMENTS
+    };
+
+    /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    /// concerned with the database.
+    explicit PgSqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap&
+                                          parameters);
+
+    /// @brief Destructor.
+    ~PgSqlConfigBackendDHCPv4Impl();
+
+    /// @brief Fetches the SQL statement for a given statement index.
+    ///
+    /// @param index index of the desired statement.
+    /// @throw BadValue if there is no statement corresponding to
+    /// the index.
+    virtual PgSqlTaggedStatement& getStatement(size_t index) const;
+
+    /// @brief Sends query to retrieve global parameter.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the parameter to be retrieved.
+    ///
+    /// @return Pointer to the retrieved value or null if such parameter
+    /// doesn't exist.
+    StampedValuePtr getGlobalParameter4(const ServerSelector& /* server_selector */,
+                                        const std::string& /* name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update global parameter.
+    ///
+    /// @param server_selector Server selector.
+    /// @param value StampedValue describing the parameter to create/update.
+    void createUpdateGlobalParameter4(const db::ServerSelector& /* server_selector */,
+                                      const StampedValuePtr& /* value */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to the database to retrieve multiple subnets.
+    ///
+    /// Query should order subnets by subnet_id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @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] subnets Reference to the container where fetched subnets
+    /// will be inserted.
+    void getSubnets4(const StatementIndex& /* index */,
+                     const ServerSelector& /* server_selector */,
+                     const PsqlBindArray& /* in_bindings */,
+                     Subnet4Collection& /* subnets */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve single subnet by id.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Pointer to the returned subnet or NULL if such subnet
+    /// doesn't exist.
+    Subnet4Ptr getSubnet4(const ServerSelector& /* server_selector */,
+                          const SubnetID& /* subnet_id */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve single subnet by prefix.
+    ///
+    /// The prefix should be in the following format: "192.0.2.0/24".
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Pointer to the returned subnet or NULL if such subnet
+    /// doesn't exist.
+    Subnet4Ptr getSubnet4(const ServerSelector& /* server_selector */,
+                          const std::string& /* subnet_prefix */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve all subnets.
+    ///
+    /// @param server_selector Server selector.
+    /// @param [out] subnets Reference to the subnet collection structure where
+    /// subnets should be inserted.
+    void getAllSubnets4(const ServerSelector& /* server_selector */,
+                        Subnet4Collection& /* subnets */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve modified subnets.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_ts Lower bound modification timestamp.
+    /// @param [out] subnets Reference to the subnet collection structure where
+    /// subnets should be inserted.
+    void getModifiedSubnets4(const ServerSelector& /* server_selector */,
+                             const boost::posix_time::ptime& /* modification_ts */,
+                             Subnet4Collection& /* subnets */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve all subnets belonging to a shared network.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of the shared network for which the
+    /// subnets should be retrieved.
+    /// @param [out] subnets Reference to the subnet collection structure where
+    /// subnets should be inserted.
+    void getSharedNetworkSubnets4(const ServerSelector& /* server_selector */,
+                                  const std::string& /* shared_network_name */,
+                                  Subnet4Collection& /* subnets */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve multiple pools.
+    ///
+    /// Query should order pools 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] pools Reference to the container where fetched pools
+    /// will be inserted.
+    /// @param [out] pool_ids Identifiers of the pools returned in @c pools
+    /// argument.
+    void getPools(const StatementIndex& /* index */,
+                  const PsqlBindArray& /* in_bindings */,
+                  PoolCollection& /* pools */,
+                  std::vector<uint64_t>& /* pool_ids */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve single pool by address range.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool_start_address Lower bound pool address.
+    /// @param pool_end_address Upper bound pool address.
+    /// @param pool_id Pool identifier for the returned pool.
+    /// @return Pointer to the pool or null if no such pool found.
+    Pool4Ptr getPool4(const ServerSelector& /* server_selector */,
+                      const IOAddress& /* pool_start_address */,
+                      const IOAddress& /* pool_end_address */,
+                      uint64_t& /* pool_id */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update subnet.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet Pointer to the subnet to be inserted or updated.
+    void createUpdateSubnet4(const ServerSelector& /* server_selector */,
+                             const Subnet4Ptr& /* subnet */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Inserts new IPv4 pool to the database.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool Pointer to the pool to be inserted.
+    /// @param subnet Pointer to the subnet that this pool belongs to.
+    void createPool4(const ServerSelector& /* server_selector */, const Pool4Ptr& /* pool */,
+                     const Subnet4Ptr& /* subnet */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends a query to delete data from a table.
+    ///
+    /// If creates a new audit revision for this change if such audit
+    /// revision doesn't exist yet (using ScopedAuditRevision mechanism).
+    ///
+    /// @tparam Args type of the arguments to be passed to one of the existing
+    /// @c deleteFromTable methods.
+    /// @param server_selector server selector.
+    /// @param operation operation which results in calling this function. This is
+    /// used for logging purposes.
+    /// @param log_message log message to be associated with the audit revision.
+    /// @param cascade_delete boolean flag indicating if we're performing
+    /// cascade delete. If set to true, the audit entries for the child
+    /// objects (e.g. DHCPoptions) won't be created.
+    /// @param keys arguments to be passed to one of the existing
+    /// @c deleteFromTable methods.
+    ///
+    /// @return Number of deleted entries.
+    template<typename... Args>
+    uint64_t deleteTransactional(const int index,
+                                 const db::ServerSelector& server_selector,
+                                 const std::string& operation,
+                                 const std::string& log_message,
+                                 const bool cascade_delete,
+                                 Args&&... keys) {
+
+        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,
+                           PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+                           server_selector, log_message, cascade_delete);
+
+        auto count = deleteFromTable(index, server_selector, operation, keys...);
+
+        transaction.commit();
+
+        return (count);
+    }
+
+    /// @brief Sends query to delete subnet by id.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of the subnet to be deleted.
+    /// @return Number of deleted subnets.
+    uint64_t deleteSubnet4(const ServerSelector& /* server_selector */,
+                           const SubnetID& /* subnet_id */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to delete subnet by id.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_prefix Prefix of the subnet to be deleted.
+    /// @return Number of deleted subnets.
+    uint64_t deleteSubnet4(const ServerSelector& /* server_selector */,
+                           const std::string& /* subnet_prefix */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes pools belonging to a subnet from the database.
+    ///
+    /// The query deletes all pools associated with the subnet's
+    /// identifier or prefix.
+    /// @param subnet Pointer to the subnet for which pools should be
+    /// deleted.
+    uint64_t deletePools4(const Subnet4Ptr& /* subnet */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to the database to retrieve multiple shared
+    /// networks.
+    ///
+    /// Query should order shared networks by id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @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] shared_networks Reference to the container where fetched
+    /// shared networks will be inserted.
+    void getSharedNetworks4(const StatementIndex& /* index */,
+                            const ServerSelector& /* server_selector */,
+                            const PsqlBindArray& /* in_bindings */,
+                            SharedNetwork4Collection& /* shared_networks */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve single shared network by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Shared network name.
+    ///
+    /// @return Pointer to the returned shared network or NULL if such shared
+    /// network doesn't exist.
+    SharedNetwork4Ptr getSharedNetwork4(const ServerSelector& /* server_selector */,
+                                        const std::string& /* name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve all shared networks.
+    ///
+    /// @param server_selector Server selector.
+    /// @param [out] shared_networks Reference to the shared networks collection
+    /// structure where shared networks should be inserted.
+    void getAllSharedNetworks4(const ServerSelector& /* server_selector */,
+                               SharedNetwork4Collection& /* shared_networks */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve modified shared networks.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_ts Lower bound modification timestamp.
+    /// @param [out] shared_networks Reference to the shared networks collection
+    /// structure where shared networks should be inserted.
+    void getModifiedSharedNetworks4(const ServerSelector& /* server_selector */,
+                                    const boost::posix_time::ptime& /* modification_ts */,
+                                    SharedNetwork4Collection& /* shared_networks */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update shared network.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet Pointer to the shared network to be inserted or updated.
+    void createUpdateSharedNetwork4(const ServerSelector& /* server_selector */,
+                                    const SharedNetwork4Ptr& /* shared_network */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert DHCP option.
+    ///
+    /// This method expects that the server selector contains exactly one
+    /// server tag.  It is intended to be used within a transaction.
+    ///
+    /// @param server_selector Server selector.
+    /// @param in_bindings Collection of bindings representing an option.
+    /// @param modification_ts option's modification timestamp
+    void insertOption4(const ServerSelector& /* server_selector */,
+                       const PsqlBindArray& /* in_bindings */,
+                       const boost::posix_time::ptime& /* modification_ts */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update global DHCP option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const OptionDescriptorPtr& /* option */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a subnet.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of the subnet the option belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param cascade_update Boolean value indicating whether the update is
+    /// performed as part of the owning element, e.g. subnet.
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const SubnetID& /* subnet_id */,
+                             const OptionDescriptorPtr& /* option */,
+                             const bool /* cascade_update */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a pool.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool_start_address Lower bound address of the pool.
+    /// @param pool_end_address Upper bound address of the pool.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const IOAddress& /* pool_start_address */,
+                             const IOAddress& /* pool_end_address */,
+                             const OptionDescriptorPtr& /* option */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a pool.
+    ///
+    /// @param selector Server selector.
+    /// @param pool_id Identifier of the pool the option belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param cascade_update Boolean value indicating whether the update is
+    /// performed as part of the owning element, e.g. subnet.
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const uint64_t  /* pool_id */,
+                             const OptionDescriptorPtr& /* option */,
+                             const bool /* cascade_update */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a shared network.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network the option
+    /// belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    /// @param cascade_update Boolean value indicating whether the update is
+    /// performed as part of the owning element, e.g. shared network.
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const std::string& /* shared_network_name */,
+                             const OptionDescriptorPtr& /* option */,
+                             const bool /* cascade_update */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a client class.
+    ///
+    /// @param selector Server selector.
+    /// @param client_class Pointer to the client_class the option belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option..
+    void createUpdateOption4(const ServerSelector& /* server_selector */,
+                             const ClientClassDefPtr& /* client_class */,
+                             const OptionDescriptorPtr& /* option */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update option definition.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option_def Pointer to the option definition to be inserted or updated.
+    void createUpdateOptionDef4(const ServerSelector& /* server_selector */,
+                                const OptionDefinitionPtr& /* option_def */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to insert or update option definition
+    /// for a client class.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option_def Pointer to the option definition to be inserted or updated.
+    /// @param client_class Client class name.
+    void createUpdateOptionDef4(const ServerSelector& /* server_selector */,
+                                const OptionDefinitionPtr& /* option_def */,
+                                const std::string& /* client_class_name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to delete option definition by code and
+    /// option space name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param code Option code.
+    /// @param name Option name.
+    /// @return Number of deleted option definitions.
+    uint64_t deleteOptionDef4(const ServerSelector& /* server_selector */,
+                              const uint16_t /* code */,
+                              const std::string& /* space */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to delete option definitions for a client class.
+    ///
+    /// @param server_selector Server selector.
+    /// @param client_class Pointer to the client class for which option
+    /// definitions should be deleted.
+    /// @return Number of deleted option definitions.
+    uint64_t deleteOptionDefs4(const ServerSelector& /* server_selector */,
+                               const ClientClassDefPtr& /* client_class */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes global option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    uint64_t deleteOption4(const ServerSelector& /* server_selector */,
+                           const uint16_t  /* code */,
+                           const std::string&  /* space */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes subnet level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of the subnet to which deleted option
+    /// belongs.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    uint64_t deleteOption4(const ServerSelector& /* server_selector */,
+                           const SubnetID& /* subnet_id */,
+                           const uint16_t /* code */,
+                           const std::string& /* space */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes pool level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool_start_address Lower bound pool address.
+    /// @param pool_end_address  Upper bound pool address.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    uint64_t deleteOption4(const db::ServerSelector& /* server_selector */,
+                           const IOAddress& /* pool_start_address */,
+                           const IOAddress& /* pool_end_address */,
+                           const uint16_t /* code */,
+                           const std::string& /* space */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of the shared network which deleted
+    /// option belongs to
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    uint64_t deleteOption4(const db::ServerSelector& /* server_selector */,
+                           const std::string& /* shared_network_name */,
+                           const uint16_t /* code */,
+                           const std::string& /* space */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes options belonging to a subnet from the database.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet Pointer to the subnet for which options should be
+    /// deleted.
+    /// @return Number of deleted options.
+    uint64_t deleteOptions4(const ServerSelector& /* server_selector */,
+                            const Subnet4Ptr& /* subnet */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes options belonging to a shared network from the database.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet Pointer to the subnet for which options should be
+    /// deleted.
+    /// @return Number of deleted options.
+    uint64_t deleteOptions4(const ServerSelector& /* server_selector */,
+                            const SharedNetwork4Ptr& /* shared_network */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Deletes options belonging to a client class from the database.
+    ///
+    /// @param server_selector Server selector.
+    /// @param client_class Pointer to the client class for which options
+    /// should be deleted.
+    /// @return Number of deleted options.
+    uint64_t deleteOptions4(const ServerSelector& /* server_selector */,
+                            const ClientClassDefPtr& /* client_class */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Common function to retrieve client classes.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param server_selector Server selector.
+    /// @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] client_classes Reference to a container where fetched client
+    /// classes will be inserted.
+    void getClientClasses4(const StatementIndex& /* index */,
+                           const ServerSelector& /* server_selector */,
+                           const PsqlBindArray& /* in_bindings */,
+                           ClientClassDictionary& /* client_classes */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve a client class by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the class to be retrieved.
+    /// @return Pointer to the client class or null if the class is not found.
+    ClientClassDefPtr getClientClass4(const ServerSelector& /* server_selector */,
+                                      const std::string& /* name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve all client classes.
+    ///
+    /// @param server_selector Server selector.
+    /// @param [out] client_classes Reference to the client classes collection
+    /// where retrieved classes will be stored.
+    void getAllClientClasses4(const ServerSelector& /* server_selector */,
+                              ClientClassDictionary& /* client_classes */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Sends query to retrieve modified client classes.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_ts Lower bound modification timestamp.
+    /// @param [out] client_classes Reference to the client classes collection
+    /// where retrieved classes will be stored.
+    void getModifiedClientClasses4(const ServerSelector& /* server_selector */,
+                                   const boost::posix_time::ptime& /* modification_ts */,
+                                   ClientClassDictionary& /* client_classes */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+
+    /// @brief Upserts client class.
+    ///
+    /// @param server_selector Server selector.
+    /// @param client_class Pointer to the upserted client class.
+    /// @param follow_class_name name of the class after which the
+    /// new or updated class should be positioned. An empty value
+    /// causes the class to be appended at the end of the class
+    /// hierarchy.
+    void createUpdateClientClass4(const ServerSelector& /* server_selector */,
+                                  const ClientClassDefPtr& /* client_class */,
+                                  const std::string& /* follow_class_name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Removes client class by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Removed client class name.
+    /// @return Number of deleted client classes.
+    uint64_t deleteClientClass4(const ServerSelector& /* server_selector */,
+                                const std::string& /* name */) {
+        isc_throw(NotImplemented, NOT_IMPL_STR);
+    }
+
+    /// @brief Removes unassigned global parameters, global options and
+    /// option definitions.
+    ///
+    /// This function is called when one or more servers are deleted and
+    /// it is likely that there are some orphaned configuration elements
+    /// left in the database. This method removes those elements.
+    void purgeUnassignedConfig() {
+        multipleUpdateDeleteQueries(DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED,
+                                    DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED,
+                                    DELETE_ALL_OPTION_DEFS4_UNASSIGNED);
+    }
+
+    /// @brief Attempts to delete a server having a given tag.
+    ///
+    /// @param server_tag Tag of the server to be deleted.
+    /// @return Number of deleted servers.
+    /// @throw isc::InvalidOperation when trying to delete the logical
+    /// server 'all'.
+    uint64_t deleteServer4(const data::ServerTag& server_tag) {
+        // It is not allowed to delete 'all' logical server.
+        if (server_tag.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 may not be deleted");
+        }
+
+        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, PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+                           ServerSelector::ALL(), "deleting a server", false);
+
+        // Specify which server should be deleted.
+        PsqlBindArray in_bindings;
+        in_bindings.addTempString(server_tag.get());
+
+        // Attempt to delete the server.
+        auto count = updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::DELETE_SERVER4,
+                                       in_bindings);
+
+        // If we have deleted any servers we have to remove any dangling global
+        // parameters, options and option definitions.
+        if (count > 0) {
+            purgeUnassignedConfig();
+        }
+
+        transaction.commit();
+        return (count);
+    }
+
+    /// @brief Attempts to delete all servers.
+    ///
+    /// This method deletes all servers added by the user. It does not
+    /// delete the logical server 'all'.
+    ///
+    /// @return Number of deleted servers.
+    uint64_t deleteAllServers4() {
+        // Start transaction.
+        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, PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+                           ServerSelector::ALL(), "deleting all servers",
+                           false);
+
+        // No argumens, hence empty input bindings.
+        PsqlBindArray in_bindings;
+
+        // Attempt to delete the servers.
+        auto count = updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SERVERS4,
+                                       in_bindings);
+
+        // If we have deleted any servers we have to remove any dangling global
+        // parameters, options and option definitions.
+        if (count > 0) {
+            purgeUnassignedConfig();
+        }
+
+        // Commit the transaction.
+        transaction.commit();
+        return (count);
+    }
+
+    /// @brief Returns the last sequence value for the given table and
+    /// column name.
+    ///
+    /// @param table name of the table
+    /// @param column name of the sequence column
+    ///
+    /// @return returns the most recently modified value for the given
+    /// sequence
+    uint64_t getLastInsertId4(const std::string& table, const std::string& column) {
+        return (getLastInsertId(PgSqlConfigBackendDHCPv4Impl::GET_LAST_INSERT_ID4,
+                                table, column));
+    }
+
+    /// @brief Attempts to reconnect the server to the config DB backend manager.
+    ///
+    /// This is a self-rescheduling function that attempts to reconnect to the
+    /// server's config DB backends after connectivity to one or more have been
+    /// lost. Upon entry it will attempt to reconnect via
+    /// @ref ConfigBackendDHCPv4Mgr.addBackend.
+    /// If this is successful, DHCP servicing is re-enabled and server returns
+    /// to normal operation.
+    ///
+    /// If reconnection fails and the maximum number of retries has not been
+    /// exhausted, it will schedule a call to itself to occur at the
+    /// configured retry interval. DHCP service remains disabled.
+    ///
+    /// If the maximum number of retries has been exhausted an error is logged
+    /// and the server shuts down.
+    ///
+    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+    /// configured reconnect parameters.
+    /// @return true if connection has been recovered, false otherwise.
+    static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+        MultiThreadingCriticalSection cs;
+
+        // Invoke application layer connection lost callback.
+        if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
+            return (false);
+        }
+
+        bool reopened = false;
+
+        const std::string timer_name = db_reconnect_ctl->timerName();
+
+        // At least one connection was lost.
+        try {
+            auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+            auto config_ctl = srv_cfg->getConfigControlInfo();
+            // Iterate over the configured DBs and instantiate them.
+            for (auto db : config_ctl->getConfigDatabases()) {
+                const std::string& access = db.getAccessString();
+                auto parameters = db.getParameters();
+                if (ConfigBackendDHCPv4Mgr::instance().delBackend(parameters["type"], access, true)) {
+                    ConfigBackendDHCPv4Mgr::instance().addBackend(db.getAccessString());
+                }
+            }
+
+            reopened = true;
+        } catch (const std::exception& ex) {
+            LOG_ERROR(pgsql_cb_logger, PGSQL_CB_RECONNECT_ATTEMPT_FAILED4)
+                    .arg(ex.what());
+        }
+
+        if (reopened) {
+            // Cancel the timer.
+            if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+                TimerMgr::instance()->unregisterTimer(timer_name);
+            }
+
+            // Invoke application layer connection recovered callback.
+            if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
+                return (false);
+            }
+        } else {
+            if (!db_reconnect_ctl->checkRetries()) {
+                // We're out of retries, log it and initiate shutdown.
+                LOG_ERROR(pgsql_cb_logger, PGSQL_CB_RECONNECT_FAILED4)
+                        .arg(db_reconnect_ctl->maxRetries());
+
+                // Cancel the timer.
+                if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+                    TimerMgr::instance()->unregisterTimer(timer_name);
+                }
+
+                // Invoke application layer connection failed callback.
+                DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+
+                return (false);
+            }
+
+            LOG_INFO(pgsql_cb_logger, PGSQL_CB_RECONNECT_ATTEMPT_SCHEDULE4)
+                    .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+                    .arg(db_reconnect_ctl->maxRetries())
+                    .arg(db_reconnect_ctl->retryInterval());
+
+            // Start the timer.
+            if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+                TimerMgr::instance()->registerTimer(timer_name,
+                    std::bind(&PgSqlConfigBackendDHCPv4Impl::dbReconnect, db_reconnect_ctl),
+                              db_reconnect_ctl->retryInterval(),
+                              asiolink::IntervalTimer::ONE_SHOT);
+            }
+            TimerMgr::instance()->setup(timer_name);
+        }
+
+        return (true);
+    }
+};
+
+namespace {
+
+/// @brief Array of tagged statements.
+typedef std::array<PgSqlTaggedStatement, PgSqlConfigBackendDHCPv4Impl::NUM_STATEMENTS>
+TaggedStatementArray;
+
+/// @brief Prepared PgSQL statements used by the backend to insert and
+/// retrieve data from the database. They must be in the same order as
+/// PgSqlConfigBackendDHCPv4Impl::StatementIndex.  The statement is
+/// the corresponding enum name.
+TaggedStatementArray tagged_statements = { {
+    {
+        // PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+        4,
+        {
+            OID_TIMESTAMP,  // 1 audit_ts
+            OID_VARCHAR,    // 2 server_tag
+            OID_TEXT,       // 3 audit_log_message
+            OID_BOOL        // 4 cascade_transaction
+        },
+        "CREATE_AUDIT_REVISION",
+        "select createAuditRevisionDHCP4($1, $2, $3, $4)"
+    },
+
+    // Verify that dependency on KNOWN/UNKNOWN class has not changed.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
+        0, { OID_NONE },
+        "CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE",
+        "select checkDHCPv4ClientClassKnownDependencyChange()"
+    },
+
+    // Select global parameter by name.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_GLOBAL_PARAMETER4,
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR     // 2 name
+        },
+        "GET_GLOBAL_PARAMETER4",
+        PGSQL_GET_GLOBAL_PARAMETER(dhcp4, AND g.name = $2)
+    },
+
+    // Select all global parameters.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_GLOBAL_PARAMETERS4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "GET_ALL_GLOBAL_PARAMETERS4",
+        PGSQL_GET_GLOBAL_PARAMETER(dhcp4)
+    },
+
+    // Select modified global parameters.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_GLOBAL_PARAMETERS4,
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_TIMESTAMP   // 2 modification_ts
+        },
+        "GET_MODIFIED_GLOBAL_PARAMETERS4",
+        PGSQL_GET_GLOBAL_PARAMETER(dhcp4, AND g.modification_ts >= $2)
+    },
+
+    // Select subnet by id.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID_NO_TAG,
+        1,
+        {
+            OID_INT8    // 1 subnet_id
+        },
+        "GET_SUBNET4_ID_NO_TAG",
+        PGSQL_GET_SUBNET4_NO_TAG(WHERE s.subnet_id = $1)
+    },
+
+    // Select subnet by id without specifying server tags.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID_ANY,
+        1,
+        {
+            OID_INT8    // 1 subnet_id
+        },
+        "GET_SUBNET4_ID_ANY",
+         PGSQL_GET_SUBNET4_ANY(WHERE s.subnet_id = $1)
+    },
+
+    // Select unassigned subnet by id.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID_UNASSIGNED,
+        1,
+        {
+            OID_INT8    // 1 subnet_id
+        },
+        "GET_SUBNET4_ID_UNASSIGNED",
+        PGSQL_GET_SUBNET4_UNASSIGNED(AND s.subnet_id = $1)
+    },
+
+    // Select subnet by prefix.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_PREFIX_NO_TAG,
+        1,
+        {
+            OID_VARCHAR // 1 subnet_prefix
+        },
+        "GET_SUBNET4_PREFIX_NO_TAG",
+        PGSQL_GET_SUBNET4_NO_TAG(WHERE s.subnet_prefix = $1)
+    },
+
+    // Select subnet by prefix without specifying server tags.
+    {
+        //PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_PREFIX_ANY,
+        1,
+        {
+            OID_VARCHAR // 1 subnet_prefix
+        },
+        "GET_SUBNET4_PREFIX_ANY",
+        PGSQL_GET_SUBNET4_ANY(WHERE s.subnet_prefix = $1)
+    },
+
+    // Select unassigned subnet by prefix.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SUBNET4_PREFIX_UNASSIGNED,
+        1,
+        {
+            OID_VARCHAR // 1 subnet_prefix
+        },
+        "GET_SUBNET4_PREFIX_UNASSIGNED",
+        PGSQL_GET_SUBNET4_UNASSIGNED(AND s.subnet_prefix = $1)
+    },
+
+    // Select all subnets.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_SUBNETS4,
+        0, { OID_NONE },
+        "GET_ALL_SUBNETS4",
+        PGSQL_GET_SUBNET4_NO_TAG()
+    },
+
+    // Select all unassigned subnets.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_SUBNETS4_UNASSIGNED,
+        0, { OID_NONE },
+        "GET_ALL_SUBNETS4_UNASSIGNED",
+        PGSQL_GET_SUBNET4_UNASSIGNED()
+    },
+
+    // Select subnets having modification time later than X.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_SUBNETS4,
+        1,
+        {
+            OID_TIMESTAMP // 1 modification_ts
+        },
+        "GET_MODIFIED_SUBNETS4",
+        PGSQL_GET_SUBNET4_NO_TAG(WHERE s.modification_ts >= $1)
+    },
+
+    // Select modified and unassigned subnets.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_SUBNETS4_UNASSIGNED,
+        1,
+        {
+            OID_TIMESTAMP // 1 modification_ts
+        },
+        "GET_MODIFIED_SUBNETS4_UNASSIGNED",
+        PGSQL_GET_SUBNET4_UNASSIGNED(AND s.modification_ts >= $1)
+    },
+
+    // Select subnets belonging to a shared network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SHARED_NETWORK_SUBNETS4,
+        1,
+        {
+            OID_VARCHAR // 1 share_network_name
+        },
+        "GET_SHARED_NETWORK_SUBNETS4",
+        PGSQL_GET_SUBNET4_ANY(WHERE s.shared_network_name = $1)
+    },
+
+    // Select pool by address range for a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_POOL4_RANGE,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_TEXT,       // 2 start_address - cast as inet
+            OID_TEXT        // 3 end_address  - cast as inet
+        },
+        "GET_POOL4_RANGE",
+        PGSQL_GET_POOL4_RANGE_WITH_TAG(WHERE (srv.tag = $1 OR srv.id = 1) \
+                                       AND (p.start_address = cast($2 as inet)) \
+                                       AND (p.end_address = cast($3 as inet)))
+    },
+
+    // Select pool by address range for any server
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_POOL4_RANGE_ANY,
+        2,
+        {
+            OID_TEXT,       // 1 start_address - cast as inet
+            OID_TEXT        // 2 end_address  - cast as inet
+        },
+        "GET_POOL4_RANGE_ANY",
+        PGSQL_GET_POOL4_RANGE_NO_TAG(WHERE (p.start_address = cast($1 as inet)) AND \
+                                           (p.end_address = cast($2 as inet)))
+    },
+
+    // Select shared network by name.
+    {
+        //PgSqlConfigBackendDHCPv4Impl::GET_SHARED_NETWORK4_NAME_NO_TAG,
+        1,
+        {
+            OID_VARCHAR // name of network
+        },
+        "GET_SHARED_NETWORK4_NAME_NO_TAG",
+        PGSQL_GET_SHARED_NETWORK4_NO_TAG(WHERE n.name = $1)
+    },
+
+    // Select shared network by name without specifying server tags.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SHARED_NETWORK4_NAME_ANY,
+        1,
+        {
+            OID_VARCHAR // name of network
+        },
+        "GET_SHARED_NETWORK4_NAME_ANY",
+        PGSQL_GET_SHARED_NETWORK4_ANY(WHERE n.name = $1)
+    },
+
+    // Select unassigned shared network by name.
+    {
+        //PgSqlConfigBackendDHCPv4Impl::GET_SHARED_NETWORK4_NAME_UNASSIGNED,
+        1,
+        {
+            OID_VARCHAR // name of network
+        },
+        "GET_SHARED_NETWORK4_NAME_UNASSIGNED",
+        PGSQL_GET_SHARED_NETWORK4_UNASSIGNED(AND n.name = $1)
+    },
+
+    // Select all shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_SHARED_NETWORKS4,
+        0, { OID_NONE },
+        "GET_ALL_SHARED_NETWORKS4",
+        PGSQL_GET_SHARED_NETWORK4_NO_TAG()
+    },
+
+    // Select all unassigned shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_SHARED_NETWORKS4_UNASSIGNED,
+        0, { OID_NONE },
+        "GET_ALL_SHARED_NETWORKS4_UNASSIGNED",
+        PGSQL_GET_SHARED_NETWORK4_UNASSIGNED()
+    },
+
+    // Select modified shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_SHARED_NETWORKS4,
+        1,
+        {
+            OID_TIMESTAMP  // 1 modification_ts
+        },
+        "GET_MODIFIED_SHARED_NETWORKS4",
+        PGSQL_GET_SHARED_NETWORK4_NO_TAG(WHERE n.modification_ts >= $1)
+    },
+
+    // Select modified and unassigned shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_SHARED_NETWORKS4_UNASSIGNED,
+        1,
+        {
+            OID_TIMESTAMP  // 1 modification_ts
+        },
+        "GET_MODIFIED_SHARED_NETWORKS4_UNASSIGNED",
+        PGSQL_GET_SHARED_NETWORK4_UNASSIGNED(AND n.modification_ts >= $1)
+    },
+
+    // Retrieves option definition by code and space.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_OPTION_DEF4_CODE_SPACE,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT2,       // 2 code
+            OID_VARCHAR     // 3 space
+        },
+        "GET_OPTION_DEF4_CODE_SPACE",
+        PGSQL_GET_OPTION_DEF(dhcp4, AND d.code = $2 AND d.space = $3)
+    },
+
+    // Retrieves all option definitions.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "GET_ALL_OPTION_DEFS4",
+        PGSQL_GET_OPTION_DEF(dhcp4)
+    },
+
+    // Retrieves modified option definitions.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
+        // server tag is $1
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_TIMESTAMP   // 2 modification_ts
+        },
+        "GET_MODIFIED_OPTION_DEFS4",
+        PGSQL_GET_OPTION_DEF(dhcp4, AND d.modification_ts >= $2)
+    },
+
+    // Retrieves global option by code and space.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT2,       // 2 code
+            OID_VARCHAR     // 3 space
+        },
+        "GET_OPTION4_CODE_SPACE",
+        PGSQL_GET_OPTION4(AND o.scope_id = 0 AND o.code = $2 AND o.space = $3)
+    },
+
+    // Retrieves all global options.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_OPTIONS4,
+        // server tag is $1
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "GET_ALL_OPTIONS4",
+        PGSQL_GET_OPTION4(AND o.scope_id = 0)
+    },
+
+    // Retrieves modified options.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTIONS4,
+        // server tag is $1
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_TIMESTAMP   // 2 modification_ts
+        },
+        "GET_MODIFIED_OPTIONS4",
+        PGSQL_GET_OPTION4(AND o.scope_id = 0 AND o.modification_ts >= $2)
+    },
+
+    // Retrieves an option for a given subnet, option code and space.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_SUBNET_ID_CODE_SPACE,
+        // server tag is $1
+        4,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT8,       // 2 subnet_id
+            OID_INT2,       // 3 code
+            OID_VARCHAR     // 4 space
+        },
+        "GET_OPTION4_SUBNET_ID_CODE_SPACE",
+        PGSQL_GET_OPTION4(AND o.scope_id = 1 AND o.dhcp4_subnet_id = $2 AND o.code = $3 AND o.space = $4)
+    },
+
+    // Retrieves an option for a given pool, option code and space.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_POOL_ID_CODE_SPACE,
+        // server tag is $1
+        4,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT8,       // 2 pool_id
+            OID_INT2,       // 3 code
+            OID_VARCHAR     // 4 space
+        },
+        "GET_OPTION4_POOL_ID_CODE_SPACE",
+        PGSQL_GET_OPTION4(AND o.scope_id = 5 AND o.pool_id = $2 AND o.code = $3 AND o.space = $4)
+    },
+
+    // Retrieves an option for a given shared network, option code and space.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
+        4,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR,    // 2 shared_network_name
+            OID_INT2,       // 3 code
+            OID_VARCHAR     // 4 space
+        },
+        "GET_OPTION4_SHARED_NETWORK_CODE_SPACE",
+        PGSQL_GET_OPTION4(AND o.scope_id = 4 AND o.shared_network_name = $2 AND o.code = $3 AND o.space = $4)
+    },
+
+    // Select a client class by name.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_CLIENT_CLASS4_NAME,
+        1,
+        {
+            OID_VARCHAR // name of class
+        },
+        "GET_CLIENT_CLASS4_NAME",
+        PGSQL_GET_CLIENT_CLASS4_WITH_TAG(WHERE c.name = $1)
+    },
+
+    // Select all client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4,
+        0, { OID_NONE },
+        "GET_ALL_CLIENT_CLASSES4",
+        PGSQL_GET_CLIENT_CLASS4_WITH_TAG()
+    },
+
+    // Select all unassigned client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4_UNASSIGNED,
+        0, { OID_NONE },
+        "GET_ALL_CLIENT_CLASSES4_UNASSIGNED",
+        PGSQL_GET_CLIENT_CLASS4_UNASSIGNED()
+    },
+
+    // Select modified client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4,
+        1,
+        {
+            OID_TIMESTAMP // 1 modification_ts
+        },
+        "GET_MODIFIED_CLIENT_CLASSES4",
+        PGSQL_GET_CLIENT_CLASS4_WITH_TAG(WHERE c.modification_ts >= $1)
+    },
+
+    // Select modified client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED,
+        1,
+        {
+            OID_TIMESTAMP // 1 modification_ts
+        },
+        "GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED",
+        PGSQL_GET_CLIENT_CLASS4_UNASSIGNED(AND c.modification_ts >= $1)
+    },
+    // Retrieves the most recent audit entries.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_INT8        // 3 revision id
+        },
+        "GET_AUDIT_ENTRIES4_TIME",
+        PGSQL_GET_AUDIT_ENTRIES_TIME(dhcp4)
+    },
+
+    // Retrieves a server by tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_SERVER4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "GET_SERVER4",
+        PGSQL_GET_SERVER(dhcp4)
+    },
+
+    // Retrieves all servers.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_ALL_SERVERS4,
+        0, { OID_NONE },
+        "GET_ALL_SERVERS4",
+        PGSQL_GET_ALL_SERVERS(dhcp4)
+    },
+
+    // Insert global parameter.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4,
+        4,
+        {
+            OID_VARCHAR,    // 1 name
+            OID_TEXT,       // 2 value
+            OID_INT2,       // 3 parameter_type
+            OID_TIMESTAMP   // 4 modification_ts
+        },
+        "INSERT_GLOBAL_PARAMETER4",
+        PGSQL_INSERT_GLOBAL_PARAMETER(dhcp4)
+    },
+
+    // Insert association of the global parameter with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4_SERVER,
+        3,
+        {
+            OID_INT8,       // 1 parameter_id
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_GLOBAL_PARAMETER4_SERVER",
+        PGSQL_INSERT_GLOBAL_PARAMETER_SERVER(dhcp4)
+    },
+
+    // Insert a subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_SUBNET4,
+        36,
+        {
+            OID_INT8,       //  1 subnet_id,
+            OID_VARCHAR,    //  2 subnet_prefix
+            OID_VARCHAR,    //  3 interface_4o6
+            OID_VARCHAR,    //  4 interface_id_4o6
+            OID_VARCHAR,    //  5 subnet_4o6
+            OID_VARCHAR,    //  6 boot_file_name
+            OID_VARCHAR,    //  7 client_class
+            OID_VARCHAR,    //  8 interface
+            OID_BOOL,       //  9 match_client_id
+            OID_TIMESTAMP,  // 10 modification_ts
+            OID_TEXT,       // 11 next_server   ---  cast as INET
+            OID_INT8,       // 12 rebind_timer
+            OID_TEXT,       // 13 relay
+            OID_INT8,       // 14 renew_timer
+            OID_TEXT,       // 15 require_client_classes
+            OID_BOOL,       // 16 reservations_global
+            OID_VARCHAR,    // 17 server_hostname
+            OID_VARCHAR,    // 18 shared_network_name
+            OID_TEXT,       // 19 user_context
+            OID_INT8,       // 20 valid_lifetime
+            OID_INT8,       // 21 min_valid_lifetime
+            OID_INT8,       // 22 max_valid_lifetime
+            OID_BOOL,       // 23 calculate_tee_times
+            OID_TEXT,       // 24 t1_percent --- cast as FLOAT
+            OID_TEXT,       // 25 t2_percent --- cast as FLOAT
+            OID_BOOL,       // 26 authoritative
+            OID_BOOL,       // 27 ddns_send_updates
+            OID_BOOL,       // 28 ddns_override_no_update
+            OID_BOOL,       // 29 ddns_override_client_update
+            OID_INT8,       // 30 ddns_replace_client_name
+            OID_VARCHAR,    // 31 ddns_generated_prefix
+            OID_VARCHAR,    // 32 ddns_qualifying_suffix
+            OID_BOOL,       // 33 reservations_in_subnet
+            OID_BOOL,       // 34 reservations_out_of_pool
+            OID_TEXT,       // 35 cache_threshold -- cast as FLOAT
+            OID_INT8        // 36 cache_max_age"
+        },
+        "INSERT_SUBNET4",
+        "INSERT INTO dhcp4_subnet("
+        "  subnet_id,"
+        "  subnet_prefix,"
+        "  interface_4o6,"
+        "  interface_id_4o6,"
+        "  subnet_4o6,"
+        "  boot_file_name,"
+        "  client_class,"
+        "  interface,"
+        "  match_client_id,"
+        "  modification_ts,"
+        "  next_server,"
+        "  rebind_timer,"
+        "  relay,"
+        "  renew_timer,"
+        "  require_client_classes,"
+        "  reservations_global,"
+        "  server_hostname,"
+        "  shared_network_name,"
+        "  user_context,"
+        "  valid_lifetime,"
+        "  min_valid_lifetime,"
+        "  max_valid_lifetime,"
+        "  calculate_tee_times,"
+        "  t1_percent,"
+        "  t2_percent,"
+        "  authoritative,"
+        "  ddns_send_updates,"
+        "  ddns_override_no_update,"
+        "  ddns_override_client_update,"
+        "  ddns_replace_client_name,"
+        "  ddns_generated_prefix,"
+        "  ddns_qualifying_suffix,"
+        "  reservations_in_subnet,"
+        "  reservations_out_of_pool,"
+        "  cache_threshold,"
+        "  cache_max_age"
+        ") VALUES ("
+            "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, "
+            "cast($11 as inet), $12, $13, $14, $15, $16, $17, $18, cast($19 as json), $20, "
+            "$21, $22, $23, cast($24 as float), cast($25 as float), $26, $27, $28, $29, $30, "
+            "$31, $32, $33, $34, cast($35 as float), $36"
+        ")"
+    },
+
+    // Insert association of the subnet with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_SUBNET4_SERVER,
+        3,
+        {
+            OID_INT8,       // 1 subnet_id
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_SUBNET4_SERVER",
+        PGSQL_INSERT_SUBNET_SERVER(dhcp4)
+    },
+
+    // Insert pool for a subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_POOL4,
+        7,
+        {
+            OID_TEXT,       // 1 start_address - cast as inet
+            OID_TEXT,       // 2 end_address - cast as inet
+            OID_INT8,       // 3 subnet_id
+            OID_VARCHAR,    // 4 client_class
+            OID_TEXT,       // 5 require_client_classes
+            OID_TEXT,       // 6 user_context
+            OID_TIMESTAMP   // 7 modification_ts
+        },
+        "INSERT_POOL4",
+        PGSQL_INSERT_POOL(dhcp4)
+    },
+
+    // Insert a shared network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_SHARED_NETWORK4,
+        31,
+        {
+            OID_VARCHAR,    //  1 name,
+            OID_VARCHAR,    //  2 client_class,
+            OID_VARCHAR,    //  3 interface,
+            OID_BOOL,       //  4 match_client_id,
+            OID_TIMESTAMP,  //  5 modification_ts,
+            OID_INT8,       //  6 rebind_timer,
+            OID_TEXT,       //  7 relay,
+            OID_INT8,       //  8 renew_timer,
+            OID_TEXT,       //  9 require_client_classes,
+            OID_BOOL,       // 10 reservations_global,
+            OID_TEXT,       // 11 user_context,
+            OID_INT8,       // 12 valid_lifetime,
+            OID_INT8,       // 13 min_valid_lifetime,
+            OID_INT8,       // 14 max_valid_lifetime,
+            OID_BOOL,       // 15 calculate_tee_times,
+            OID_TEXT,       // 16 t1_percent - cast as float
+            OID_TEXT,       // 17 t2_percent - cast as float
+            OID_BOOL,       // 18 authoritative,
+            OID_VARCHAR,    // 19 boot_file_name,
+            OID_TEXT,       // 20 next_server - cast as inet
+            OID_VARCHAR,    // 21 server_hostname,
+            OID_BOOL,       // 22 ddns_send_updates,
+            OID_BOOL,       // 23 ddns_override_no_update,
+            OID_BOOL,       // 24 ddns_override_client_update,
+            OID_INT8,       // 25 ddns_replace_client_name,
+            OID_VARCHAR,    // 26 ddns_generated_prefix,
+            OID_VARCHAR,    // 27 ddns_qualifying_suffix,
+            OID_BOOL,       // 28 reservations_in_subnet,
+            OID_BOOL,       // 29 reservations_out_of_pool,
+            OID_TEXT,       // 30 cache_threshold - cast as float
+            OID_INT8        // 31 cache_max_age
+        },
+        "INSERT_SHARED_NETWORK4",
+        "INSERT INTO dhcp4_shared_network("
+        "  name,"
+        "  client_class,"
+        "  interface,"
+        "  match_client_id,"
+        "  modification_ts,"
+        "  rebind_timer,"
+        "  relay,"
+        "  renew_timer,"
+        "  require_client_classes,"
+        "  reservations_global,"
+        "  user_context,"
+        "  valid_lifetime,"
+        "  min_valid_lifetime,"
+        "  max_valid_lifetime,"
+        "  calculate_tee_times,"
+        "  t1_percent,"
+        "  t2_percent,"
+        "  authoritative,"
+        "  boot_file_name,"
+        "  next_server,"
+        "  server_hostname,"
+        "  ddns_send_updates,"
+        "  ddns_override_no_update,"
+        "  ddns_override_client_update,"
+        "  ddns_replace_client_name,"
+        "  ddns_generated_prefix,"
+        "  ddns_qualifying_suffix,"
+        "  reservations_in_subnet,"
+        "  reservations_out_of_pool,"
+        "  cache_threshold,"
+        "  cache_max_age"
+        ") VALUES ("
+            "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+            "cast($11 as json), $12, $13, $14, $15, "
+            "cast($16 as float), cast($17 as float), $18, $19, cast($20 as inet), "
+            "$21, $22, $23, $24, $25, $26, $27, $28, $29, cast($30 as float), $31"
+        ")"
+    },
+
+    // Insert association of the shared network with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_SHARED_NETWORK4_SERVER,
+        3,
+        {
+            OID_VARCHAR,    // 1 shared_network_name
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_SHARED_NETWORK4_SERVER",
+        PGSQL_INSERT_SHARED_NETWORK_SERVER(dhcp4)
+    },
+
+    // Insert option definition.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4,
+        10,
+        {
+            OID_INT2,       //  1 code
+            OID_VARCHAR,    //  2 name
+            OID_VARCHAR,    //  3 space
+            OID_INT2,       //  4 type
+            OID_TIMESTAMP,  //  5 modification_ts
+            OID_BOOL,       //  6 is_array
+            OID_VARCHAR,    //  7 encapsulate
+            OID_VARCHAR,    //  8 record_types
+            OID_VARCHAR,    //  9 user_context
+            OID_INT8        // 10 class_id"  -- column is missing from dhcpX_option_def tables
+        },
+        "INSERT_OPTION_DEF4",
+        PGSQL_INSERT_OPTION_DEF(dhcp4)
+    },
+
+    // Insert option definition for client class.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_CLIENT_CLASS,
+        10,
+        {
+            OID_INT2,       //  1 code
+            OID_VARCHAR,    //  2 name
+            OID_VARCHAR,    //  3 space
+            OID_INT2,       //  4 type
+            OID_TIMESTAMP,  //  5 modification_ts
+            OID_BOOL,       //  6 is_array
+            OID_VARCHAR,    //  7 encapsulate
+            OID_VARCHAR,    //  8 record_types
+            OID_VARCHAR,    //  9 user_context
+            OID_VARCHAR     // 10 class name for where clause
+        },
+        "INSERT_OPTION_DEF4_CLIENT_CLASS",
+        PGSQL_INSERT_OPTION_DEF_CLIENT_CLASS(dhcp4)
+    },
+
+    // Insert association of the option definition with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_SERVER,
+        3,
+        {
+            OID_INT8,       // 1 option_def_id
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_OPTION_DEF4_SERVER",
+        PGSQL_INSERT_OPTION_DEF_SERVER(dhcp4)
+    },
+
+    // Insert subnet specific option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION4,
+        12,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP   // 12 modification_ts
+        },
+        "INSERT_OPTION4",
+        PGSQL_INSERT_OPTION4()
+    },
+
+    // Insert association of the DHCP option with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION4_SERVER,
+        3,
+        {
+            OID_INT8,       // 1 option_id
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_OPTION4_SERVER",
+        PGSQL_INSERT_OPTION_SERVER(dhcp4)
+    },
+
+    // Insert client class.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4,
+        12,
+        {
+            OID_VARCHAR,    //  1 name
+            OID_TEXT,       //  2 test
+            OID_TEXT,       //  3 next_server - cast as inet
+            OID_VARCHAR,    //  4 server_hostname
+            OID_VARCHAR,    //  5 boot_file_name
+            OID_BOOL,       //  6 only_if_required
+            OID_INT8,       //  7 valid_lifetime
+            OID_INT8,       //  8 min_valid_lifetime
+            OID_INT8,       //  9 max_valid_lifetime
+            OID_BOOL,       // 10 depend_on_known_directly
+            OID_VARCHAR,    // 11 follow_class_name
+            OID_TIMESTAMP   // 12 modification_ts
+        },
+        "INSERT_CLIENT_CLASS4",
+        "INSERT INTO dhcp4_client_class("
+        "  name,"
+        "  test,"
+        "  next_server,"
+        "  server_hostname,"
+        "  boot_file_name,"
+        "  only_if_required,"
+        "  valid_lifetime,"
+        "  min_valid_lifetime,"
+        "  max_valid_lifetime,"
+        "  depend_on_known_directly,"
+        "  follow_class_name,"
+        "  modification_ts"
+        ") VALUES ("
+            "$1, $2, cast($3 as inet), $4, $5, $6, $7, $8, $9, $10, $11, $12"
+        ")"
+    },
+
+    // Insert association of a client class with a server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_SERVER,
+        3,
+        {
+            OID_VARCHAR,    // 1 class_name
+            OID_TIMESTAMP,  // 2 modification_ts
+            OID_VARCHAR     // 3 server_tag
+        },
+        "INSERT_CLIENT_CLASS4_SERVER",
+        PGSQL_INSERT_CLIENT_CLASS_SERVER(dhcp4)
+    },
+
+    // Insert client class dependency.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_DEPENDENCY,
+        2,
+        {
+            OID_VARCHAR,    // class name
+            OID_VARCHAR     // dependency class name
+        },
+        "INSERT_CLIENT_CLASS4_DEPENDENCY",
+        PGSQL_INSERT_CLIENT_CLASS_DEPENDENCY(dhcp4)
+    },
+
+    // Insert server with server tag and description.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::INSERT_SERVER4,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR,    // 2 description
+            OID_TIMESTAMP   // 3 modification_ts
+        },
+        "INSERT_SERVER4",
+        PGSQL_INSERT_SERVER(dhcp4)
+    },
+
+    // Update existing global parameter.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_GLOBAL_PARAMETER4,
+        6,
+        {
+            OID_VARCHAR,    // 1 name
+            OID_TEXT,       // 2 value
+            OID_INT2,       // 3 parameter_type
+            OID_TIMESTAMP,  // 4 modification_ts
+            OID_VARCHAR,    // 5 server_tag
+            OID_VARCHAR,    // 6 name (of global to update)
+        },
+        "UPDATE_GLOBAL_PARAMETER4",
+        PGSQL_UPDATE_GLOBAL_PARAMETER(dhcp4)
+    },
+
+    // Update existing subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_SUBNET4,
+        38,
+        {
+            OID_INT8,       //  1 subnet_id,
+            OID_VARCHAR,    //  2 subnet_prefix
+            OID_VARCHAR,    //  3 interface_4o6
+            OID_VARCHAR,    //  4 interface_id_4o6
+            OID_VARCHAR,    //  5 subnet_4o6
+            OID_VARCHAR,    //  6 boot_file_name
+            OID_VARCHAR,    //  7 client_class
+            OID_VARCHAR,    //  8 interface
+            OID_BOOL,       //  9 match_client_id
+            OID_TIMESTAMP,  // 10 modification_ts
+            OID_TEXT,       // 11 next_server   - cast as INET
+            OID_INT8,       // 12 rebind_timer
+            OID_TEXT,       // 13 relay
+            OID_INT8,       // 14 renew_timer
+            OID_TEXT,       // 15 require_client_classes
+            OID_BOOL,       // 16 reservations_global
+            OID_VARCHAR,    // 17 server_hostname
+            OID_VARCHAR,    // 18 shared_network_name
+            OID_TEXT,       // 19 user_context
+            OID_INT8,       // 20 valid_lifetime
+            OID_INT8,       // 21 min_valid_lifetime
+            OID_INT8,       // 22 max_valid_lifetime
+            OID_BOOL,       // 23 calculate_tee_times
+            OID_TEXT,       // 24 t1_percent - cast as FLOAT
+            OID_TEXT,       // 25 t2_percent - cast as FLOAT
+            OID_BOOL,       // 26 authoritative
+            OID_BOOL,       // 27 ddns_send_updates
+            OID_BOOL,       // 28 ddns_override_no_update
+            OID_BOOL,       // 29 ddns_override_client_update
+            OID_INT8,       // 30 ddns_replace_client_name
+            OID_VARCHAR,    // 31 ddns_generated_prefix
+            OID_VARCHAR,    // 32 ddns_qualifying_suffix
+            OID_BOOL,       // 33 reservations_in_subnet
+            OID_BOOL,       // 34 reservations_out_of_pool
+            OID_TEXT,       // 35 cache_threshold - cast as FLOAT
+            OID_INT8,       // 36 cache_max_age"
+            OID_INT8,       // 37 subnet_id (of subnet to update)
+            OID_VARCHAR     // 38 subnet_prefix (of subnet to update)
+        },
+        "UPDATE_SUBNET4,",
+        "UPDATE dhcp4_subnet SET"
+        "  subnet_id = $1,"
+        "  subnet_prefix = $2,"
+        "  interface_4o6 = $3,"
+        "  interface_id_4o6 = $4,"
+        "  subnet_4o6 = $5,"
+        "  boot_file_name = $6,"
+        "  client_class = $7,"
+        "  interface = $8,"
+        "  match_client_id = $9,"
+        "  modification_ts = $10,"
+        "  next_server = cast($11 as inet),"
+        "  rebind_timer = $12,"
+        "  relay = $13,"
+        "  renew_timer = $14,"
+        "  require_client_classes = $15,"
+        "  reservations_global = $16,"
+        "  server_hostname = $17,"
+        "  shared_network_name = $18,"
+        "  user_context = cast($19 as json),"
+        "  valid_lifetime = $20,"
+        "  min_valid_lifetime = $21,"
+        "  max_valid_lifetime = $22,"
+        "  calculate_tee_times = $23,"
+        "  t1_percent = cast($24 as float),"
+        "  t2_percent = cast($25 as float),"
+        "  authoritative = $26,"
+        "  ddns_send_updates = $27,"
+        "  ddns_override_no_update = $28,"
+        "  ddns_override_client_update = $29,"
+        "  ddns_replace_client_name = $30,"
+        "  ddns_generated_prefix = $31,"
+        "  ddns_qualifying_suffix = $32,"
+        "  reservations_in_subnet = $33,"
+        "  reservations_out_of_pool = $34,"
+        "  cache_threshold = cast($35 as float),"
+        "  cache_max_age = $36 "
+        "WHERE subnet_id = $37 OR subnet_prefix = $38"
+    },
+
+    // Update existing shared network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_SHARED_NETWORK4,
+        32,
+        {
+            OID_VARCHAR,    //  1 name,
+            OID_VARCHAR,    //  2 client_class,
+            OID_VARCHAR,    //  3 interface,
+            OID_BOOL,       //  4 match_client_id,
+            OID_TIMESTAMP,  //  5 modification_ts,
+            OID_INT8,       //  6 rebind_timer,
+            OID_TEXT,       //  7 relay,
+            OID_INT8,       //  8 renew_timer,
+            OID_TEXT,       //  9 require_client_classes,
+            OID_BOOL,       // 10 reservations_global,
+            OID_TEXT,       // 11 user_context,
+            OID_INT8,       // 12 valid_lifetime,
+            OID_INT8,       // 13 min_valid_lifetime,
+            OID_INT8,       // 14 max_valid_lifetime,
+            OID_BOOL,       // 15 calculate_tee_times,
+            OID_TEXT,       // 16 t1_percent - cast as float
+            OID_TEXT,       // 17 t2_percent - cast as float
+            OID_BOOL,       // 18 authoritative,
+            OID_VARCHAR,    // 19 boot_file_name,
+            OID_TEXT,       // 20 next_server - cast as inet
+            OID_VARCHAR,    // 21 server_hostname,
+            OID_BOOL,       // 22 ddns_send_updates,
+            OID_BOOL,       // 23 ddns_override_no_update,
+            OID_BOOL,       // 24 ddns_override_client_update,
+            OID_INT8,       // 25 ddns_replace_client_name,
+            OID_VARCHAR,    // 26 ddns_generated_prefix,
+            OID_VARCHAR,    // 27 ddns_qualifying_suffix,
+            OID_BOOL,       // 28 reservations_in_subnet,
+            OID_BOOL,       // 29 reservations_out_of_pool,
+            OID_TEXT,       // 30 cache_threshold - cast as float
+            OID_INT8,       // 31 cache_max_age
+            OID_VARCHAR     // 32 name (of network to update)
+        },
+        "UPDATE_SHARED_NETWORK4",
+        "UPDATE dhcp4_shared_network SET"
+        "  name = $1,"
+        "  client_class = $2,"
+        "  interface = $3,"
+        "  match_client_id = $4,"
+        "  modification_ts = $5,"
+        "  rebind_timer = $6,"
+        "  relay = $7,"
+        "  renew_timer = $8,"
+        "  require_client_classes = $9,"
+        "  reservations_global = $10,"
+        "  user_context = cast($11 as json),"
+        "  valid_lifetime = $12,"
+        "  min_valid_lifetime = $13,"
+        "  max_valid_lifetime = $14,"
+        "  calculate_tee_times = $15,"
+        "  t1_percent = cast($16 as float),"
+        "  t2_percent = cast($17 as float),"
+        "  authoritative = $18,"
+        "  boot_file_name = $19,"
+        "  next_server = cast($20 as inet),"
+        "  server_hostname = $21,"
+        "  ddns_send_updates = $22,"
+        "  ddns_override_no_update = $23,"
+        "  ddns_override_client_update = $24,"
+        "  ddns_replace_client_name = $25,"
+        "  ddns_generated_prefix = $26,"
+        "  ddns_qualifying_suffix = $27,"
+        "  reservations_in_subnet = $28,"
+        "  reservations_out_of_pool = $29,"
+        "  cache_threshold = cast($30 as float),"
+        "  cache_max_age = $31 "
+        "WHERE name = $32"
+    },
+
+    // Update existing option definition.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4,
+        13,
+        {
+            OID_INT2,       //  1 code
+            OID_VARCHAR,    //  2 name
+            OID_VARCHAR,    //  3 space
+            OID_INT2,       //  4 type
+            OID_TIMESTAMP,  //  5 modification_ts
+            OID_BOOL,       //  6 is_array
+            OID_VARCHAR,    //  7 encapsulate
+            OID_VARCHAR,    //  8 record_types
+            OID_TEXT,       //  9 user_context
+            OID_INT2,       // 10 class_id
+            OID_VARCHAR,    // 11 server_tag
+            OID_INT2,       // 12 code (of option to update)
+            OID_VARCHAR,    // 13 space (of option to update)
+        },
+        "UPDATE_OPTION_DEF4",
+        PGSQL_UPDATE_OPTION_DEF(dhcp4)
+    },
+
+    // Update existing option definition.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4_CLIENT_CLASS,
+        13,
+        {
+            OID_INT2,       //  1 code
+            OID_VARCHAR,    //  2 name
+            OID_VARCHAR,    //  3 space
+            OID_INT2,       //  4 type
+            OID_TIMESTAMP,  //  5 modification_ts
+            OID_BOOL,       //  6 is_array
+            OID_VARCHAR,    //  7 encapsulate
+            OID_VARCHAR,    //  8 record_types
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 name (of class option belongs to)
+            OID_VARCHAR,    // 11 server_tag
+            OID_INT2,       // 12 code (of option to update)
+            OID_VARCHAR,    // 13 space (of option to update)
+        },
+        "UPDATE_OPTION_DEF4_CLIENT_CLASS",
+        PGSQL_UPDATE_OPTION_DEF_CLIENT_CLASS(dhcp4)
+    },
+
+    // Update existing global option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4,
+        15,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP,  // 12 modification_ts
+            OID_VARCHAR,    // 13 server_tag
+            OID_INT2,       // 14 code (of option to update)
+            OID_VARCHAR,    // 15 space (of option to update)
+        },
+        "UPDATE_OPTION4",
+        PGSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = $14 AND o.space = $15)
+    },
+
+    // Update existing subnet level option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID,
+        15,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP,  // 12 modification_ts
+            OID_INT8,       // 13 subnet_id (of option to update)
+            OID_INT2,       // 14 code (of option to update)
+            OID_VARCHAR     // 15 space (of option to update)
+        },
+        "UPDATE_OPTION4_SUBNET_ID",
+        PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 1 AND o.dhcp4_subnet_id = $13 AND o.code = $14 AND o.space = $15)
+    },
+
+    // Update existing pool level option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_POOL_ID,
+        15,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP,  // 12 modification_ts
+            OID_INT8,       // 13 pool_id (of option to update)
+            OID_INT2,       // 14 code (of option to update)
+            OID_VARCHAR     // 15 space (of option to update)
+        },
+        "UPDATE_OPTION4_POOL_ID",
+        PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 5 AND o.pool_id = $13 AND o.code = $14 AND o.space = $15)
+    },
+
+    // Update existing shared network level option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SHARED_NETWORK,
+        15,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP,  // 12 modification_ts
+            OID_VARCHAR,    // 13 shared_network_name (of option to update)
+            OID_INT2,       // 14 code (of option to update)
+            OID_VARCHAR     // 15 space (of option to update)
+        },
+        "UPDATE_OPTION4_SHARED_NETWORK",
+        PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = $13 AND o.code = $14 AND o.space = $15)
+    },
+
+    // Update existing client class level option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_CLIENT_CLASS,
+        15,
+        {
+            OID_INT2,       //  1 code
+            OID_BYTEA,      //  2 value
+            OID_TEXT,       //  3 formatted_value
+            OID_VARCHAR,    //  4 space
+            OID_BOOL,       //  5 persistent
+            OID_VARCHAR,    //  6 dhcp_client_class
+            OID_INT8,       //  7 dhcp4_subnet_id
+            OID_INT2,       //  8 scope_id
+            OID_TEXT,       //  9 user_context
+            OID_VARCHAR,    // 10 shared_network_name
+            OID_INT8,       // 11 pool_id
+            OID_TIMESTAMP,  // 12 modification_ts
+            OID_VARCHAR,    // 13 dhcp_client_class (of option to update)
+            OID_INT2,       // 14 code (of option to update)
+            OID_VARCHAR,    // 15 space (of option to update)
+        },
+        "UPDATE_OPTION4_CLIENT_CLASS",
+        PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = $13 AND o.code = $14 AND o.space = $15)
+    },
+
+    // Update existing client class with specifying its position.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4,
+        13,
+        {
+            OID_VARCHAR,    //  1 name
+            OID_TEXT,       //  2 test
+            OID_TEXT,       //  3 next_server - cast as inet
+            OID_VARCHAR,    //  4 server_hostname
+            OID_VARCHAR,    //  5 boot_file_name
+            OID_BOOL,       //  6 only_if_required
+            OID_INT8,       //  7 valid_lifetime
+            OID_INT8,       //  8 min_valid_lifetime
+            OID_INT8,       //  9 max_valid_lifetime
+            OID_BOOL,       // 10 depend_on_known_directly
+            OID_TIMESTAMP,  // 11 modification_ts
+            OID_VARCHAR,    // 12 name (of class to update)
+            OID_VARCHAR     // 13 follow_class_name
+        },
+        "UPDATE_CLIENT_CLASS4",
+        PGSQL_UPDATE_CLIENT_CLASS4("follow_class_name = $13,")
+    },
+
+    // Update existing client class without specifying its position.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4_SAME_POSITION,
+        12,
+        {
+            OID_VARCHAR,    //  1 name
+            OID_TEXT,       //  2 test
+            OID_TEXT,       //  3 next_server - cast as inet
+            OID_VARCHAR,    //  4 server_hostname
+            OID_VARCHAR,    //  5 boot_file_name
+            OID_BOOL,       //  6 only_if_required
+            OID_INT8,       //  7 valid_lifetime
+            OID_INT8,       //  8 min_valid_lifetime
+            OID_INT8,       //  9 max_valid_lifetime
+            OID_BOOL,       // 10 depend_on_known_directly
+            OID_TIMESTAMP,  // 11 modification_ts
+            OID_VARCHAR     // 12 name (of class to update)
+        },
+        "UPDATE_CLIENT_CLASS4_SAME_POSITION",
+        PGSQL_UPDATE_CLIENT_CLASS4("")
+    },
+
+    // Update existing server, e.g. server description.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::UPDATE_SERVER4,
+        4,
+        {
+            OID_VARCHAR,    // 1 tag
+            OID_VARCHAR,    // 2 description
+            OID_TIMESTAMP,  // 3 modification_ts
+            OID_VARCHAR     // 4 tag (of server to update)
+        },
+        "UPDATE_SERVER4",
+        PGSQL_UPDATE_SERVER(dhcp4)
+    },
+
+    // Delete global parameter by name.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_GLOBAL_PARAMETER4,
+        // args: server_tag, name
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR     // 2 name of parameter
+        },
+        "DELETE_GLOBAL_PARAMETER4",
+        PGSQL_DELETE_GLOBAL_PARAMETER(dhcp4, AND g.name = $2)
+    },
+
+    // Delete all global parameters.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_GLOBAL_PARAMETERS4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "DELETE_ALL_GLOBAL_PARAMETERS4",
+        PGSQL_DELETE_GLOBAL_PARAMETER(dhcp4)
+    },
+
+    // Delete all global parameters which are unassigned to any servers.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED,
+        0, { OID_NONE },
+        "DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED",
+        PGSQL_DELETE_GLOBAL_PARAMETER_UNASSIGNED(dhcp4)
+    },
+
+    // Delete subnet by id with specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_ID_WITH_TAG,
+        // args: server_tag, subnet_id
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT8        // 2 subnet_id
+        },
+        "DELETE_SUBNET4_ID_WITH_TAG",
+        PGSQL_DELETE_SUBNET_WITH_TAG(dhcp4, AND s.subnet_id = $2)
+    },
+
+    // Delete subnet by id without specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_ID_ANY,
+        1,
+        {
+            OID_INT8    // 1 subnet_id
+        },
+        "DELETE_SUBNET4_ID_ANY",
+        PGSQL_DELETE_SUBNET_ANY(dhcp4, WHERE s.subnet_id = $1)
+    },
+
+    // Delete subnet by prefix with specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_PREFIX_WITH_TAG,
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR     // 2 subnet_prefix
+        },
+        "DELETE_SUBNET4_PREFIX_WITH_TAG",
+        PGSQL_DELETE_SUBNET_WITH_TAG(dhcp4, AND s.subnet_prefix = $2)
+    },
+
+    // Delete subnet by prefix without specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_PREFIX_ANY,
+        1,
+        {
+            OID_VARCHAR // 1 subnet_prefix
+        },
+        "DELETE_SUBNET4_PREFIX_ANY",
+        PGSQL_DELETE_SUBNET_ANY(dhcp4, WHERE s.subnet_prefix = $1)
+    },
+
+    // Delete all subnets.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "DELETE_ALL_SUBNETS4",
+        PGSQL_DELETE_SUBNET_WITH_TAG(dhcp4)
+    },
+
+    // Delete all unassigned subnets.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4_UNASSIGNED,
+        0, { OID_NONE },
+        "DELETE_ALL_SUBNETS4_UNASSIGNED",
+        PGSQL_DELETE_SUBNET_UNASSIGNED(dhcp4)
+    },
+
+    // Delete all subnets for a shared network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4_SHARED_NETWORK_NAME,
+        1,
+        {
+            OID_VARCHAR // 1 shared_network_name
+        },
+        "DELETE_ALL_SUBNETS4_SHARED_NETWORK_NAME",
+        PGSQL_DELETE_SUBNET_ANY(dhcp4, WHERE s.shared_network_name = $1)
+    },
+
+    // Delete associations of a subnet with server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_SERVER,
+        1,
+        {
+            OID_INT8    // 1 subnet_id
+        },
+        "DELETE_SUBNET4_SERVER",
+        PGSQL_DELETE_SUBNET_SERVER(dhcp4),
+    },
+
+    // Delete pools for a subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_POOLS4,
+        // args: subnet_id, subnet_prefix
+        2,
+        {
+            OID_INT8,   // 1 subnet_id
+            OID_VARCHAR // 2 subnet_prefix
+        },
+        "DELETE_POOLS4",
+        PGSQL_DELETE_POOLS(dhcp4)
+    },
+
+    // Delete shared network by name with specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SHARED_NETWORK4_NAME_WITH_TAG,
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR     // 2 shared_network_name
+        },
+        "DELETE_SHARED_NETWORK4_NAME_WITH_TAG",
+        PGSQL_DELETE_SHARED_NETWORK_WITH_TAG(dhcp4, AND n.name = $2)
+    },
+
+    // Delete shared network by name without specifying server tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SHARED_NETWORK4_NAME_ANY,
+        1,
+        {
+            OID_VARCHAR // 1 shared_network_name
+        },
+        "DELETE_SHARED_NETWORK4_NAME_ANY",
+        PGSQL_DELETE_SHARED_NETWORK_ANY(dhcp4, WHERE n.name = $1)
+    },
+
+    // Delete all shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SHARED_NETWORKS4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "DELETE_ALL_SHARED_NETWORKS4",
+        PGSQL_DELETE_SHARED_NETWORK_WITH_TAG(dhcp4)
+    },
+
+    // Delete all unassigned shared networks.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SHARED_NETWORKS4_UNASSIGNED,
+        0, { OID_NONE },
+        "DELETE_ALL_SHARED_NETWORKS4_UNASSIGNED",
+        PGSQL_DELETE_SHARED_NETWORK_UNASSIGNED(dhcp4)
+    },
+
+    // Delete associations of a shared network with server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SHARED_NETWORK4_SERVER,
+        1,
+        {
+            OID_VARCHAR // 1 shared_network_name
+        },
+        "DELETE_SHARED_NETWORK4_SERVER",
+        PGSQL_DELETE_SHARED_NETWORK_SERVER(dhcp4)
+    },
+
+    // Delete option definition.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION_DEF4_CODE_NAME,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT2,       // 2 code
+            OID_VARCHAR     // 3 space
+        },
+        "DELETE_OPTION_DEF4_CODE_NAME",
+        PGSQL_DELETE_OPTION_DEF(dhcp4, AND code = $2 AND space = $3)
+    },
+
+    // Delete all option definitions.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTION_DEFS4,
+        // args: server_tag
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "DELETE_ALL_OPTION_DEFS4",
+        PGSQL_DELETE_OPTION_DEF(dhcp4)
+    },
+
+
+    // Delete all option definitions which are assigned to no servers.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTION_DEFS4_UNASSIGNED,
+        0, { OID_NONE },
+        "DELETE_ALL_OPTION_DEFS4_UNASSIGNED",
+        PGSQL_DELETE_OPTION_DEF_UNASSIGNED(dhcp4)
+    },
+
+    // Delete client class specific option definitions.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION_DEFS4_CLIENT_CLASS,
+        1,
+        {
+            OID_VARCHAR // 1 class name
+        },
+        "DELETE_OPTION_DEFS4_CLIENT_CLASS",
+        PGSQL_DELETE_OPTION_DEFS_CLIENT_CLASS(dhcp4)
+    },
+
+    // Delete single global option.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4,
+        3,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_INT2,       // 2 code
+            OID_VARCHAR     // 3 space
+        },
+        "DELETE_OPTION4",
+        PGSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0  AND o.code = $2 AND o.space = $3)
+    },
+
+    // Delete all global options which are unassigned to any servers.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED,
+        1,
+        {
+            OID_INT2    // 1 scope_id
+        },
+        "DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED",
+        PGSQL_DELETE_OPTION_UNASSIGNED(dhcp4, AND scope_id = $1)
+    },
+
+    // Delete single option from a subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID,
+        3,
+        {
+            OID_INT8,   // 1 subnet_id
+            OID_INT2,   // 2 code
+            OID_VARCHAR // 3 space
+        },
+        "DELETE_OPTION4_SUBNET_ID",
+        PGSQL_DELETE_OPTION_NO_TAG(dhcp4,
+            WHERE o.scope_id = 1 AND o.dhcp4_subnet_id = $1 AND o.code = $2 AND o.space = $3)
+    },
+
+    // Delete single option from a pool.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_POOL_RANGE,
+        4,
+        {
+            OID_TEXT,   // 1 start_address - cast as inet
+            OID_TEXT,   // 2 start_address - cast as inet
+            OID_INT2,   // 3 code
+            OID_VARCHAR // 4 space
+        },
+        "DELETE_OPTION4_POOL_RANGE",
+        PGSQL_DELETE_OPTION_POOL_RANGE(dhcp4, o.scope_id = 5 AND o.code = $3 AND o.space = $4)
+    },
+
+    // Delete single option from a shared network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SHARED_NETWORK,
+        3,
+        {
+            OID_VARCHAR,    // 1 shared_network_name
+            OID_INT2,       // 2 code
+            OID_VARCHAR     // 3 space
+        },
+        "DELETE_OPTION4_SHARED_NETWORK",
+        PGSQL_DELETE_OPTION_NO_TAG(dhcp4,
+            WHERE o.scope_id = 4 AND o.shared_network_name = $1 AND o.code = $2 AND o.space = $3)
+    },
+
+    // Delete options belonging to a subnet.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_SUBNET_ID_PREFIX,
+        2,
+        {
+            OID_INT8,   // 1 subnet_id
+            OID_VARCHAR // 2 subnet_prefix
+        },
+        "DELETE_OPTIONS4_SUBNET_ID_PREFIX",
+        PGSQL_DELETE_OPTION_SUBNET_ID_PREFIX(dhcp4)
+    },
+
+    // Delete options belonging to a shared_network.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_SHARED_NETWORK,
+        1,
+        {
+            OID_VARCHAR // shared_network_name
+        },
+        "DELETE_OPTIONS4_SHARED_NETWORK",
+        PGSQL_DELETE_OPTION_NO_TAG(dhcp4, WHERE o.scope_id = 4 AND o.shared_network_name = $1)
+    },
+
+    // Delete options belonging to a client class.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_CLIENT_CLASS,
+        1,
+        {
+            OID_VARCHAR // dhcp_client_class
+        },
+        "DELETE_OPTIONS4_CLIENT_CLASS",
+        PGSQL_DELETE_OPTION_NO_TAG(dhcp4, WHERE o.scope_id = 2 AND o.dhcp_client_class = $1)
+    },
+
+    // Delete all dependencies of a client class.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_DEPENDENCY,
+        1,
+        {
+            OID_VARCHAR, // 1 classname
+        },
+        "DELETE_CLIENT_CLASS4_DEPENDENCY",
+        PGSQL_DELETE_CLIENT_CLASS_DEPENDENCY(dhcp4)
+    },
+
+    // Delete associations of a client class with server.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_SERVER,
+        1,
+        {
+            OID_VARCHAR // 1 classname
+        },
+        "DELETE_CLIENT_CLASS4_SERVER",
+        PGSQL_DELETE_CLIENT_CLASS_SERVER(dhcp4),
+    },
+
+    // Delete all client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4,
+        1,
+        {
+            OID_VARCHAR // 1 server_tag
+        },
+        "DELETE_ALL_CLIENT_CLASSES4",
+        PGSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp4)
+    },
+
+    // Delete all unassigned client classes.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED,
+        0, { OID_NONE },
+        "DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED",
+        PGSQL_DELETE_CLIENT_CLASS_UNASSIGNED(dhcp4)
+    },
+
+    // Delete specified client class.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4,
+        2,
+        {
+            OID_VARCHAR,    // 1 server_tag
+            OID_VARCHAR     // 2 name
+        },
+        "DELETE_CLIENT_CLASS4",
+        PGSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp4, AND name = $2)
+    },
+
+    // Delete any client class with a given name.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_ANY,
+        1,
+        {
+            OID_VARCHAR     // 1 name
+        },
+        "DELETE_CLIENT_CLASS4_ANY",
+        PGSQL_DELETE_CLIENT_CLASS_ANY(dhcp4, AND name = $1)
+    },
+
+    // Delete a server by tag.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_SERVER4,
+        1,
+        {
+            OID_VARCHAR // server_tag
+        },
+        "DELETE_SERVER4",
+        PGSQL_DELETE_SERVER(dhcp4)
+    },
+
+    // Deletes all servers except logical server 'all'.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SERVERS4,
+        0, { OID_NONE },
+        "DELETE_ALL_SERVERS4",
+        PGSQL_DELETE_ALL_SERVERS(dhcp4)
+    },
+
+    // Fetches the last sequence id for the given table and column.
+    {
+        // PgSqlConfigBackendDHCPv4Impl::GET_LAST_INSERT_ID4,
+        // args are: table name, sequence column name
+        2, { OID_VARCHAR, OID_VARCHAR },
+        "GET_LAST_INSERT_ID4",
+        "SELECT CURRVAL(PG_GET_SERIAL_SEQUENCE($1, $2))"
+    }
+}
+};
+
+} // end anonymous namespace
+
+PgSqlConfigBackendDHCPv4Impl::PgSqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap& parameters)
+    : PgSqlConfigBackendImpl(parameters, &PgSqlConfigBackendDHCPv4Impl::dbReconnect) {
+    // Prepare query statements. Those are will be only used to retrieve
+    // information from the database, so they can be used even if the
+    // database is read only for the current user.
+    conn_.prepareStatements(tagged_statements.begin(),
+                            tagged_statements.end());
+//                            tagged_statements.begin() + WRITE_STMTS_BEGIN);
+
+    // Create unique timer name per instance.
+    timer_name_ = "PgSqlConfigBackend4[";
+    timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    timer_name_ += "]DbReconnectTimer";
+
+    // Create ReconnectCtl for this connection.
+    conn_.makeReconnectCtl(timer_name_);
+}
+
+PgSqlTaggedStatement&
+PgSqlConfigBackendDHCPv4Impl::getStatement(size_t index) const {
+    if (index >= tagged_statements.size()) {
+        isc_throw(BadValue, "PgSqlConfigBackendDHCPv4Impl::getStatement index: "
+                  << index << ", is invalid");
+    }
+
+    return(tagged_statements[index]);
+}
+
+PgSqlConfigBackendDHCPv4Impl::~PgSqlConfigBackendDHCPv4Impl() {
+}
+
+PgSqlConfigBackendDHCPv4::PgSqlConfigBackendDHCPv4(const DatabaseConnection::ParameterMap& parameters)
+    : impl_(new PgSqlConfigBackendDHCPv4Impl(parameters)), base_impl_(impl_) {
+}
+
+bool
+PgSqlConfigBackendDHCPv4::isUnusable() {
+    return (impl_->conn_.isUnusable());
+}
+
+DatabaseConnection::ParameterMap
+PgSqlConfigBackendDHCPv4::getParameters() const {
+    return (impl_->getParameters());
+}
+
+Subnet4Ptr
+PgSqlConfigBackendDHCPv4::getSubnet4(const ServerSelector& server_selector,
+                                     const std::string& subnet_prefix) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SUBNET4_BY_PREFIX)
+        .arg(subnet_prefix);
+    return (impl_->getSubnet4(server_selector, subnet_prefix));
+}
+
+Subnet4Ptr
+PgSqlConfigBackendDHCPv4::getSubnet4(const ServerSelector& server_selector,
+                                     const SubnetID& subnet_id) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SUBNET4_BY_SUBNET_ID)
+        .arg(subnet_id);
+    return (impl_->getSubnet4(server_selector, subnet_id));
+}
+
+Subnet4Collection
+PgSqlConfigBackendDHCPv4::getAllSubnets4(const ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SUBNETS4);
+    Subnet4Collection subnets;
+    impl_->getAllSubnets4(server_selector, subnets);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SUBNETS4_RESULT)
+        .arg(subnets.size());
+    return (subnets);
+}
+
+Subnet4Collection
+PgSqlConfigBackendDHCPv4::getModifiedSubnets4(const ServerSelector& server_selector,
+                                              const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_SUBNETS4)
+        .arg(util::ptimeToText(modification_time));
+    Subnet4Collection subnets;
+    impl_->getModifiedSubnets4(server_selector, modification_time, subnets);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_SUBNETS4_RESULT)
+        .arg(subnets.size());
+    return (subnets);
+}
+
+Subnet4Collection
+PgSqlConfigBackendDHCPv4::getSharedNetworkSubnets4(const ServerSelector& /* server_selector */,
+                                                   const std::string& shared_network_name) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SHARED_NETWORK_SUBNETS4)
+        .arg(shared_network_name);
+    Subnet4Collection subnets;
+    impl_->getSharedNetworkSubnets4(ServerSelector::ANY(), shared_network_name, subnets);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SHARED_NETWORK_SUBNETS4_RESULT)
+        .arg(subnets.size());
+    return (subnets);
+}
+
+SharedNetwork4Ptr
+PgSqlConfigBackendDHCPv4::getSharedNetwork4(const ServerSelector& server_selector,
+                                            const std::string& name) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SHARED_NETWORK4)
+        .arg(name);
+    return (impl_->getSharedNetwork4(server_selector, name));
+}
+
+SharedNetwork4Collection
+PgSqlConfigBackendDHCPv4::getAllSharedNetworks4(const ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SHARED_NETWORKS4);
+    SharedNetwork4Collection shared_networks;
+    impl_->getAllSharedNetworks4(server_selector, shared_networks);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SHARED_NETWORKS4_RESULT)
+        .arg(shared_networks.size());
+    return (shared_networks);
+}
+
+SharedNetwork4Collection
+PgSqlConfigBackendDHCPv4::getModifiedSharedNetworks4(const ServerSelector& server_selector,
+        const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_SHARED_NETWORKS4)
+        .arg(util::ptimeToText(modification_time));
+    SharedNetwork4Collection shared_networks;
+    impl_->getModifiedSharedNetworks4(server_selector, modification_time, shared_networks);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_SHARED_NETWORKS4_RESULT)
+        .arg(shared_networks.size());
+    return (shared_networks);
+}
+
+OptionDefinitionPtr
+PgSqlConfigBackendDHCPv4::getOptionDef4(const ServerSelector& server_selector,
+                                        const uint16_t code,
+                                        const std::string& space) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_OPTION_DEF4)
+        .arg(code).arg(space);
+    return (impl_->getOptionDef(PgSqlConfigBackendDHCPv4Impl::GET_OPTION_DEF4_CODE_SPACE,
+                                server_selector, code, space));
+}
+
+OptionDefContainer
+PgSqlConfigBackendDHCPv4::getAllOptionDefs4(const ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_OPTION_DEFS4);
+    OptionDefContainer option_defs;
+    impl_->getAllOptionDefs(PgSqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
+                            server_selector, option_defs);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_OPTION_DEFS4_RESULT)
+        .arg(option_defs.size());
+    return (option_defs);
+}
+
+OptionDefContainer
+PgSqlConfigBackendDHCPv4::getModifiedOptionDefs4(const ServerSelector& server_selector,
+        const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_OPTION_DEFS4)
+        .arg(util::ptimeToText(modification_time));
+    OptionDefContainer option_defs;
+    impl_->getModifiedOptionDefs(PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
+                                 server_selector, modification_time, option_defs);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_OPTION_DEFS4_RESULT)
+        .arg(option_defs.size());
+    return (option_defs);
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendDHCPv4::getOption4(const ServerSelector& server_selector,
+                                     const uint16_t code,
+                                     const std::string& space) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_OPTION4)
+        .arg(code).arg(space);
+    return (impl_->getOption(PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE,
+                             Option::V4, server_selector, code, space));
+}
+
+OptionContainer
+PgSqlConfigBackendDHCPv4::getAllOptions4(const ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_OPTIONS4);
+    OptionContainer options = impl_->getAllOptions(PgSqlConfigBackendDHCPv4Impl::GET_ALL_OPTIONS4,
+            Option::V4, server_selector);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_OPTIONS4_RESULT)
+        .arg(options.size());
+    return (options);
+}
+
+OptionContainer
+PgSqlConfigBackendDHCPv4::getModifiedOptions4(const ServerSelector& server_selector,
+        const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_OPTIONS4)
+        .arg(util::ptimeToText(modification_time));
+    OptionContainer options = impl_->getModifiedOptions(PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTIONS4,
+            Option::V4, server_selector, modification_time);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_OPTIONS4_RESULT)
+        .arg(options.size());
+    return (options);
+}
+
+StampedValuePtr
+PgSqlConfigBackendDHCPv4::getGlobalParameter4(const ServerSelector& server_selector,
+                                              const std::string& name) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_GLOBAL_PARAMETER4)
+        .arg(name);
+    return (impl_->getGlobalParameter4(server_selector, name));
+}
+
+StampedValueCollection
+PgSqlConfigBackendDHCPv4::getAllGlobalParameters4(const ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4);
+    StampedValueCollection parameters;
+    auto tags = server_selector.getTags();
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings;
+        in_bindings.addTempString(tag.get());
+        impl_->getGlobalParameters(PgSqlConfigBackendDHCPv4Impl::GET_ALL_GLOBAL_PARAMETERS4,
+                                   in_bindings, parameters);
+    }
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT)
+        .arg(parameters.size());
+    return (parameters);
+}
+
+StampedValueCollection
+PgSqlConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+        const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4)
+        .arg(util::ptimeToText(modification_time));
+    StampedValueCollection parameters;
+    auto tags = server_selector.getTags();
+    for (auto tag : tags) {
+        PsqlBindArray in_bindings;
+        in_bindings.addTempString(tag.get());
+        in_bindings.addTimestamp(modification_time);
+
+        impl_->getGlobalParameters(PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_GLOBAL_PARAMETERS4,
+                                   in_bindings, parameters);
+    }
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT)
+        .arg(parameters.size());
+    return (parameters);
+}
+
+ClientClassDefPtr
+PgSqlConfigBackendDHCPv4::getClientClass4(const db::ServerSelector& server_selector,
+                                          const std::string& name) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_CLIENT_CLASS4)
+        .arg(name);
+    return (impl_->getClientClass4(server_selector, name));
+}
+
+ClientClassDictionary
+PgSqlConfigBackendDHCPv4::getAllClientClasses4(const db::ServerSelector& server_selector) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_CLIENT_CLASSES4);
+    ClientClassDictionary client_classes;
+    impl_->getAllClientClasses4(server_selector, client_classes);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT)
+        .arg(client_classes.getClasses()->size());
+    return (client_classes);
+}
+
+ClientClassDictionary
+PgSqlConfigBackendDHCPv4::getModifiedClientClasses4(const db::ServerSelector& server_selector,
+                                                    const boost::posix_time::ptime& modification_time) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4)
+        .arg(util::ptimeToText(modification_time));
+    ClientClassDictionary client_classes;
+    impl_->getModifiedClientClasses4(server_selector, modification_time, client_classes);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT)
+        .arg(client_classes.getClasses()->size());
+    return (client_classes);
+}
+
+AuditEntryCollection
+PgSqlConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector& server_selector,
+        const boost::posix_time::ptime& modification_time,
+        const uint64_t& modification_id) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_RECENT_AUDIT_ENTRIES4)
+      .arg(util::ptimeToText(modification_time))
+      .arg(modification_id);
+    AuditEntryCollection audit_entries;
+    impl_->getRecentAuditEntries(PgSqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
+                                 server_selector, modification_time,
+                                 modification_id, audit_entries);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_RECENT_AUDIT_ENTRIES4_RESULT)
+        .arg(audit_entries.size());
+    return (audit_entries);
+}
+
+ServerCollection
+PgSqlConfigBackendDHCPv4::getAllServers4() const {
+    ServerCollection servers;
+
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SERVERS4);
+    impl_->getAllServers(PgSqlConfigBackendDHCPv4Impl::GET_ALL_SERVERS4,
+                         servers);
+
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_ALL_SERVERS4_RESULT)
+        .arg(servers.size());
+    return (servers);
+}
+
+ServerPtr
+PgSqlConfigBackendDHCPv4::getServer4(const data::ServerTag& server_tag) const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_SERVER4)
+        .arg(server_tag.get());
+    return (impl_->getServer(PgSqlConfigBackendDHCPv4Impl::GET_SERVER4, server_tag));
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateSubnet4(const ServerSelector& server_selector,
+                                              const Subnet4Ptr& subnet) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_SUBNET4)
+        .arg(subnet);
+    impl_->createUpdateSubnet4(server_selector, subnet);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateSharedNetwork4(const ServerSelector& server_selector,
+                                                     const SharedNetwork4Ptr& shared_network) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_SHARED_NETWORK4)
+        .arg(shared_network->getName());
+    impl_->createUpdateSharedNetwork4(server_selector, shared_network);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateOptionDef4(const ServerSelector& server_selector,
+                                                 const OptionDefinitionPtr& option_def) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_OPTION_DEF4)
+        .arg(option_def->getName()).arg(option_def->getCode());
+    impl_->createUpdateOptionDef4(server_selector, option_def);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& server_selector,
+                                              const OptionDescriptorPtr& option) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_OPTION4);
+    impl_->createUpdateOption4(server_selector, option);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+                                              const std::string& shared_network_name,
+                                              const OptionDescriptorPtr& option) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_SHARED_NETWORK_OPTION4)
+        .arg(shared_network_name);
+    impl_->createUpdateOption4(server_selector, shared_network_name, option, false);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& server_selector,
+                                              const SubnetID& subnet_id,
+                                              const OptionDescriptorPtr& option) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4)
+        .arg(subnet_id);
+    impl_->createUpdateOption4(server_selector, subnet_id, option, false);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& server_selector,
+                                              const asiolink::IOAddress& pool_start_address,
+                                              const asiolink::IOAddress& pool_end_address,
+                                              const OptionDescriptorPtr& option) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_BY_POOL_OPTION4)
+        .arg(pool_start_address.toText()).arg(pool_end_address.toText());
+    impl_->createUpdateOption4(server_selector, pool_start_address, pool_end_address,
+                               option);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateGlobalParameter4(const ServerSelector& server_selector,
+                                                       const StampedValuePtr& value) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4)
+        .arg(value->getName());
+    impl_->createUpdateGlobalParameter4(server_selector, value);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateClientClass4(const db::ServerSelector& server_selector,
+                                                   const ClientClassDefPtr& client_class,
+                                                   const std::string& follow_class_name) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4)
+        .arg(client_class->getName());
+    impl_->createUpdateClientClass4(server_selector, client_class, follow_class_name);
+}
+
+void
+PgSqlConfigBackendDHCPv4::createUpdateServer4(const ServerPtr& server) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_CREATE_UPDATE_SERVER4)
+        .arg(server->getServerTagAsText());
+    impl_->createUpdateServer(PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+                              PgSqlConfigBackendDHCPv4Impl::INSERT_SERVER4,
+                              PgSqlConfigBackendDHCPv4Impl::UPDATE_SERVER4,
+                              server);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteSubnet4(const ServerSelector& server_selector,
+                                        const std::string& subnet_prefix) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_PREFIX_SUBNET4)
+        .arg(subnet_prefix);
+    uint64_t result = impl_->deleteSubnet4(server_selector, subnet_prefix);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_PREFIX_SUBNET4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteSubnet4(const ServerSelector& server_selector,
+                                        const SubnetID& subnet_id) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4)
+        .arg(subnet_id);
+    uint64_t result = impl_->deleteSubnet4(server_selector, subnet_id);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllSubnets4(const ServerSelector& server_selector) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SUBNETS4);
+
+    int index = (server_selector.amUnassigned() ?
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4_UNASSIGNED :
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4);
+    uint64_t result = impl_->deleteTransactional(index, server_selector, "deleting all subnets",
+                                                 "deleted all subnets", true);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SUBNETS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+                                                      const std::string& shared_network_name) {
+    if (!server_selector.amAny()) {
+        isc_throw(InvalidOperation, "deleting all subnets from a shared "
+                  "network requires using ANY server selector");
+    }
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4)
+        .arg(shared_network_name);
+    uint64_t result = impl_->deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SUBNETS4_SHARED_NETWORK_NAME,
+                                                 server_selector,
+                                                 "deleting all subnets for a shared network",
+                                                 "deleted all subnets for a shared network",
+                                                 true, shared_network_name);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteSharedNetwork4(const ServerSelector& server_selector,
+                                               const std::string& name) {
+    /// @todo Using UNASSIGNED selector is allowed by the CB API but we don't have
+    /// dedicated query for this at the moment. The user should use ANY to delete
+    /// the shared network by name.
+    if (server_selector.amUnassigned()) {
+        isc_throw(NotImplemented, "deleting an unassigned shared network requires "
+                  "an explicit server tag or using ANY server. The UNASSIGNED server "
+                  "selector is currently not supported");
+    }
+
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK4)
+        .arg(name);
+
+    int index = (server_selector.amAny() ?
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_SHARED_NETWORK4_NAME_ANY :
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_SHARED_NETWORK4_NAME_WITH_TAG);
+    uint64_t result = impl_->deleteTransactional(index, server_selector,
+                                                 "deleting a shared network",
+                                                 "shared network deleted", true, name);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllSharedNetworks4(const ServerSelector& server_selector) {
+    if (server_selector.amAny()) {
+        isc_throw(InvalidOperation, "deleting all shared networks for ANY server is not"
+                  " supported");
+    }
+
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SHARED_NETWORKS4);
+
+    int index = (server_selector.amUnassigned() ?
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SHARED_NETWORKS4_UNASSIGNED :
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_SHARED_NETWORKS4);
+    uint64_t result = impl_->deleteTransactional(index, server_selector, "deleting all shared networks",
+                                                 "deleted all shared networks", true);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SHARED_NETWORKS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteOptionDef4(const ServerSelector& server_selector,
+                                           const uint16_t code,
+                                           const std::string& space) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION_DEF4)
+        .arg(code).arg(space);
+    uint64_t result = impl_->deleteOptionDef4(server_selector, code, space);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION_DEF4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllOptionDefs4(const ServerSelector& server_selector) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_OPTION_DEFS4);
+    uint64_t result = impl_->deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTION_DEFS4,
+                                                 server_selector, "deleting all option definitions",
+                                                 "deleted all option definitions", true);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_OPTION_DEFS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION4)
+        .arg(code).arg(space);
+    uint64_t result = impl_->deleteOption4(server_selector, code, space);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */,
+                                        const std::string& shared_network_name,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    /// @todo In the future we might use the server selector to make sure that the
+    /// option is only deleted if the pool belongs to a given server. For now, we
+    /// just delete it when there is a match with the parent object.
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_OPTION4)
+        .arg(shared_network_name).arg(code).arg(space);
+    uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), shared_network_name,
+                                           code, space);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_OPTION4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */,
+                                        const SubnetID& subnet_id,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    /// @todo In the future we might use the server selector to make sure that the
+    /// option is only deleted if the pool belongs to a given server. For now, we
+    /// just delete it when there is a match with the parent object.
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_OPTION4)
+        .arg(subnet_id).arg(code).arg(space);
+    uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), subnet_id, code, space);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_OPTION4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */,
+                                        const asiolink::IOAddress& pool_start_address,
+                                        const asiolink::IOAddress& pool_end_address,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    /// @todo In the future we might use the server selector to make sure that the
+    /// option is only deleted if the pool belongs to a given server. For now, we
+    /// just delete it when there is a match with the parent object.
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_POOL_OPTION4)
+        .arg(pool_start_address.toText()).arg(pool_end_address.toText()).arg(code).arg(space);
+    uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), pool_start_address,
+                                           pool_end_address, code, space);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_POOL_OPTION4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteGlobalParameter4(const ServerSelector& server_selector,
+                                                 const std::string& name) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_GLOBAL_PARAMETER4)
+        .arg(name);
+    uint64_t result = impl_->deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_GLOBAL_PARAMETER4,
+                                                 server_selector, "deleting global parameter",
+                                                 "global parameter deleted", false, name);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllGlobalParameters4(const ServerSelector& server_selector) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4);
+    uint64_t result = impl_->deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_GLOBAL_PARAMETERS4,
+                                                 server_selector, "deleting all global parameters",
+                                                 "all global parameters deleted", true);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteClientClass4(const db::ServerSelector& server_selector,
+                                             const std::string& name) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_CLIENT_CLASS4)
+        .arg(name);
+    auto result = impl_->deleteClientClass4(server_selector, name);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllClientClasses4(const db::ServerSelector& server_selector) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4);
+
+    int index = (server_selector.amUnassigned() ?
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED :
+                 PgSqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4);
+    uint64_t result = impl_->deleteTransactional(index, server_selector, "deleting all client classes",
+                                                 "deleted all client classes", true);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteServer4(const ServerTag& server_tag) {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SERVER4)
+        .arg(server_tag.get());
+    uint64_t result = impl_->deleteServer4(server_tag);
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SERVER4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+uint64_t
+PgSqlConfigBackendDHCPv4::deleteAllServers4() {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SERVERS4);
+    uint64_t result = impl_->deleteAllServers4();
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_ALL_SERVERS4_RESULT)
+        .arg(result);
+    return (result);
+}
+
+std::string
+PgSqlConfigBackendDHCPv4::getType() const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_TYPE4);
+    return (impl_->getType());
+}
+
+std::string
+PgSqlConfigBackendDHCPv4::getHost() const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_HOST4);
+    return (impl_->getHost());
+}
+
+uint16_t
+PgSqlConfigBackendDHCPv4::getPort() const {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_PORT4);
+    return (impl_->getPort());
+}
+
+bool
+PgSqlConfigBackendDHCPv4::registerBackendType() {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_REGISTER_BACKEND_TYPE4);
+    return (
+        dhcp::ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("postgresql",
+            [](const db::DatabaseConnection::ParameterMap& params) -> dhcp::ConfigBackendDHCPv4Ptr {
+            return (dhcp::PgSqlConfigBackendDHCPv4Ptr(new dhcp::PgSqlConfigBackendDHCPv4(params)));
+        })
+    );
+}
+
+void
+PgSqlConfigBackendDHCPv4::unregisterBackendType() {
+    LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_UNREGISTER_BACKEND_TYPE4);
+    dhcp::ConfigBackendDHCPv4Mgr::instance().unregisterBackendFactory("postgresql");
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.h b/src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.h
new file mode 100644 (file)
index 0000000..d78698a
--- /dev/null
@@ -0,0 +1,631 @@
+// Copyright (C) 2021-2022 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_DHCP4_H
+#define PGSQL_CONFIG_BACKEND_DHCP4_H
+
+#include <pgsql_cb_impl.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4.h>
+#include <pgsql_cb_log.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+class PgSqlConfigBackendDHCPv4Impl;
+
+/// @brief Implementation of the PgSql Configuration Backend for
+/// Kea DHCPv4 server.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+///
+/// The server selection mechanisms used by this backend generally adhere
+/// to the rules described for @c ConfigBackendDHCPv4, but support for
+/// some of the selectors is not implemented. Whenever this is the case,
+/// the methods throw @c isc::NotImplemented exception.
+class PgSqlConfigBackendDHCPv4 : public ConfigBackendDHCPv4 {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    /// concerned with the database.
+    explicit PgSqlConfigBackendDHCPv4(const db::DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Retrieves a single subnet by subnet_prefix.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_prefix Prefix of the subnet to be retrieved.
+    /// @return Pointer to the retrieved subnet or NULL if not found.
+    virtual Subnet4Ptr
+    getSubnet4(const db::ServerSelector& server_selector,
+               const std::string& subnet_prefix) const;
+
+    /// @brief Retrieves a single subnet by subnet identifier.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of a subnet to be retrieved.
+    /// @return Pointer to the retrieved subnet or NULL if not found.
+    virtual Subnet4Ptr
+    getSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const;
+
+    /// @brief Retrieves all subnets.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Collection of subnets or empty collection if no subnet found.
+    virtual Subnet4Collection
+    getAllSubnets4(const db::ServerSelector& server_selector) const;
+
+    /// @brief Retrieves subnets modified after specified time.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_time Lower bound subnet modification time.
+    /// @return Collection of subnets or empty collection if no subnet found.
+    virtual Subnet4Collection
+    getModifiedSubnets4(const db::ServerSelector& server_selector,
+                        const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves all subnets belonging to a specified shared network.
+    ///
+    /// The server selector is currently ignored by this method. All subnets
+    /// for the given shared network are returned regardless of their
+    /// associations with the servers.
+    ///
+    /// @param server_selector Server selector (currently ignored).
+    /// @param shared_network_name Name of the shared network for which the
+    /// subnets should be retrieved.
+    /// @return Collection of subnets or empty collection if no subnet found.
+    virtual Subnet4Collection
+    getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+                             const std::string& shared_network_name) const;
+
+    /// @brief Retrieves shared network by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the shared network to be retrieved.
+    /// @return Pointer to the shared network or NULL if not found.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual SharedNetwork4Ptr
+    getSharedNetwork4(const db::ServerSelector& server_selector,
+                      const std::string& name) const;
+
+    /// @brief Retrieves all shared networks.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Collection of shared network or empty collection if
+    /// no shared network found.
+    virtual SharedNetwork4Collection
+    getAllSharedNetworks4(const db::ServerSelector& server_selector) const;
+
+    /// @brief Retrieves shared networks modified after specified time.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_time Lower bound shared network modification time.
+    /// @return Collection of shared network or empty collection if
+    /// no shared network found.
+    virtual SharedNetwork4Collection
+    getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+                               const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves single option definition by code and space.
+    ///
+    /// @param server_selector Server selector.
+    /// @param code Code of the option to be retrieved.
+    /// @param space Option space of the option to be retrieved.
+    /// @return Pointer to the option definition or NULL if not found.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual OptionDefinitionPtr
+    getOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+                  const std::string& space) const;
+
+    /// @brief Retrieves all option definitions.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Collection of option definitions or empty collection if
+    /// no option definition found.
+    virtual OptionDefContainer
+    getAllOptionDefs4(const db::ServerSelector& server_selector) const;
+
+    /// @brief Retrieves option definitions modified after specified time.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_time Lower bound option definition modification
+    /// time.
+    /// @return Collection of option definitions or empty collection if
+    /// no option definition found.
+    virtual OptionDefContainer
+    getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+                           const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves single option by code and space.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Pointer to the retrieved option descriptor or null if
+    /// no option was found.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual OptionDescriptorPtr
+    getOption4(const db::ServerSelector& server_selector, const uint16_t code,
+               const std::string& space) const;
+
+    /// @brief Retrieves all global options.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Collection of global options or empty collection if no
+    /// option found.
+    virtual OptionContainer
+    getAllOptions4(const db::ServerSelector& server_selector) const;
+
+    /// @brief Retrieves option modified after specified time.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_time Lower bound option modification time.
+    /// @return Collection of global options or empty collection if no
+    /// option found.
+    virtual OptionContainer
+    getModifiedOptions4(const db::ServerSelector& server_selector,
+                        const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves global parameter value.
+    ///
+    /// Typically, the server selector used for this query should be set to
+    /// ONE. It is possible to use the MULTIPLE server selector but in that
+    /// case only the first found parameter is returned.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the global parameter to be retrieved.
+    /// @return Value of the global parameter.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual data::StampedValuePtr
+    getGlobalParameter4(const db::ServerSelector& server_selector,
+                        const std::string& name) const;
+
+    /// @brief Retrieves all global parameters.
+    ///
+    /// Using the server selector it is possible to fetch the parameters for
+    /// one or more servers. The following list describes what parameters are
+    /// returned depending on the server selector specified:
+    /// - ALL: only common parameters are returned which are associated with
+    ///   the logical server 'all'. No parameters associated with the explicit
+    ///   server tags are returned.
+    ///
+    /// - ONE: parameters used by the particular sever are returned. This includes
+    ///   parameters associated with the particular server (identified by tag)
+    ///   and parameters associated with the logical server 'all' when server
+    ///   specific parameters are not given. For example, if there is a
+    ///   renew-timer specified for 'server1' tag, different value of the
+    ///   renew-timer specified for 'all' servers and a rebind-timer specified
+    ///   for 'all' servers, the caller will receive renew-timer value associated
+    ///   with the server1 and the rebind-timer value associated with all servers,
+    ///   because there is no explicit rebind-timer specified for server1.
+    ///
+    /// - MULTIPLE: parameters used by multiple servers, but those associated
+    ///   with specific server tags take precedence over the values specified for
+    ///   'all' servers. This is similar to the case of ONE server described
+    ///   above. The effect of querying for parameters belonging to multiple
+    ///   servers is the same as issuing multiple queries with ONE server
+    ///   being selected multiple times.
+    ///
+    /// - UNASSIGNED: parameters not associated with any servers.
+    ///
+    ///
+    /// @param server_selector Server selector.
+    virtual data::StampedValueCollection
+    getAllGlobalParameters4(const db::ServerSelector& server_selector) const;
+
+    /// @brief Retrieves global parameters modified after specified time.
+    ///
+    /// @param modification_time Lower bound modification time.
+    /// @return Collection of modified global parameters.
+    virtual data::StampedValueCollection
+    getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+                                 const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves a client class by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Client class name.
+    /// @return Pointer to the retrieved client class.
+    virtual ClientClassDefPtr
+    getClientClass4(const db::ServerSelector& selector, const std::string& name) const;
+
+    /// @brief Retrieves all client classes.
+    ///
+    /// @param selector Server selector.
+    /// @return Collection of client classes.
+    virtual ClientClassDictionary
+    getAllClientClasses4(const db::ServerSelector& selector) const;
+
+    /// @brief Retrieves client classes modified after specified time.
+    ///
+    /// @param selector Server selector.
+    /// @param modification_time Modification time.
+    /// @return Collection of client classes.
+    virtual ClientClassDictionary
+    getModifiedClientClasses4(const db::ServerSelector& selector,
+                              const boost::posix_time::ptime& modification_time) const;
+
+    /// @brief Retrieves the most recent audit entries.
+    ///
+    /// @param 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) entries have the same
+    /// modification_time.
+    /// @return Collection of audit entries.
+    virtual db::AuditEntryCollection
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time,
+                          const uint64_t& modification_id) const;
+
+    /// @brief Retrieves all servers.
+    ///
+    /// This method returns the list of servers excluding the logical server
+    /// 'all'.
+    ///
+    /// @return Collection of servers from the backend.
+    virtual db::ServerCollection
+    getAllServers4() const;
+
+    /// @brief Retrieves a server.
+    ///
+    /// @param server_tag Tag of the server to be retrieved.
+    /// @return Pointer to the server instance or null pointer if no server
+    /// with the particular tag was found.
+    virtual db::ServerPtr
+    getServer4(const data::ServerTag& server_tag) const;
+
+    /// @brief Creates or updates a subnet.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet Subnet to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateSubnet4(const db::ServerSelector& server_selector,
+                        const Subnet4Ptr& subnet);
+
+    /// @brief Creates or updates a shared network.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network Shared network to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+                               const SharedNetwork4Ptr& shared_network);
+
+    /// @brief Creates or updates an option definition.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option_def Option definition to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateOptionDef4(const db::ServerSelector& server_selector,
+                           const OptionDefinitionPtr& option_def);
+
+    /// @brief Creates or updates global option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param option Option to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateOption4(const db::ServerSelector& server_selector,
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates shared network level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of a shared network to which option
+    /// belongs.
+    /// @param option Option to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateOption4(const db::ServerSelector& server_selector,
+                        const std::string& shared_network_name,
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates subnet level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of a subnet to which option belongs.
+    /// @param option Option to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateOption4(const db::ServerSelector& server_selector,
+                        const SubnetID& subnet_id,
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates pool level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool_start_address Lower bound address of the pool to which
+    /// the option belongs.
+    /// @param pool_end_address Upper bound address of the pool to which the
+    /// option belongs.
+    /// @param option Option to be added or updated.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateOption4(const db::ServerSelector& server_selector,
+                        const asiolink::IOAddress& pool_start_address,
+                        const asiolink::IOAddress& pool_end_address,
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates global parameter.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the global parameter.
+    /// @param value Value of the global parameter.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual void
+    createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+                                 const data::StampedValuePtr& value);
+
+    /// @brief Creates or updates DHCPv4 client class.
+    ///
+    /// @param server_selector Server selector.
+    /// @param client_class Client class to be added or updated.
+    /// @param follow_class_name name of the class after which the
+    /// new or updated class should be positioned. An empty value
+    /// causes the class to be appended at the end of the class
+    /// hierarchy.
+    virtual void
+    createUpdateClientClass4(const db::ServerSelector& server_selector,
+                             const ClientClassDefPtr& client_class,
+                             const std::string& follow_class_name);
+
+    /// @brief Creates or updates a server.
+    ///
+    /// @param server Instance of the server to be stored.
+    /// @throw InvalidOperation when trying to create a duplicate or
+    /// update the logical server 'all'.
+    virtual void
+    createUpdateServer4(const db::ServerPtr& server);
+
+    /// @brief Deletes subnet by prefix.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_prefix Prefix of the subnet to be deleted.
+    /// @return Number of deleted subnets.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteSubnet4(const db::ServerSelector& server_selector,
+                  const std::string& subnet_prefix);
+
+    /// @brief Deletes subnet by identifier.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of the subnet to be deleted.
+    /// @return Number of deleted subnets.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id);
+
+    /// @brief Deletes all subnets.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Number of deleted subnets.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteAllSubnets4(const db::ServerSelector& server_selector);
+
+    /// @brief Deletes all subnets belonging to a specified shared network.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of the shared network for which the
+    /// subnets should be deleted.
+    /// @return Number of deleted subnets.
+    virtual uint64_t
+    deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+                                const std::string& shared_network_name);
+
+    /// @brief Deletes shared network by name.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the shared network to be deleted.
+    /// @return Number of deleted shared networks.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteSharedNetwork4(const db::ServerSelector& server_selector,
+                         const std::string& name);
+
+    /// @brief Deletes all shared networks.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Number of deleted shared networks.
+    virtual uint64_t
+    deleteAllSharedNetworks4(const db::ServerSelector& server_selector);
+
+    /// @brief Deletes option definition.
+    ///
+    /// @param server_selector Server selector.
+    /// @param code Code of the option to be deleted.
+    /// @param space Option space of the option to be deleted.
+    /// @return Number of deleted option definitions.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+                     const std::string& space);
+
+    /// @brief Deletes all option definitions.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Number of deleted option definitions.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteAllOptionDefs4(const db::ServerSelector& server_selector);
+
+    /// @brief Deletes global option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param code Code of the option to be deleted.
+    /// @param space Option space of the option to be deleted.
+    /// @return Number of deleted options.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
+                  const std::string& space);
+
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of the shared network which deleted
+    /// option belongs to
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteOption4(const db::ServerSelector& server_selector,
+                  const std::string& shared_network_name,
+                  const uint16_t code,
+                  const std::string& space);
+
+    /// @brief Deletes subnet level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param subnet_id Identifier of the subnet to which deleted option
+    /// belongs.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id,
+                  const uint16_t code, const std::string& space);
+
+    /// @brief Deletes pool level option.
+    ///
+    /// @param server_selector Server selector.
+    /// @param pool_start_address Lower bound address of the pool to which
+    /// deleted option belongs.
+    /// @param pool_end_address Upper bound address of the pool to which the
+    /// deleted option belongs.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    /// @return Number of deleted options.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteOption4(const db::ServerSelector& server_selector,
+                  const asiolink::IOAddress& pool_start_address,
+                  const asiolink::IOAddress& pool_end_address,
+                  const uint16_t code,
+                  const std::string& space);
+
+    /// @brief Deletes global parameter.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the global parameter to be deleted.
+    /// @return Number of deleted global parameters.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteGlobalParameter4(const db::ServerSelector& server_selector,
+                           const std::string& name);
+
+    /// @brief Deletes all global parameters.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Number of deleted global parameters.
+    /// @throw NotImplemented if server selector is "unassigned".
+    virtual uint64_t
+    deleteAllGlobalParameters4(const db::ServerSelector& server_selector);
+
+    /// @brief Deletes DHCPv4 client class.
+    ///
+    /// @param server_selector Server selector.
+    /// @param name Name of the class to be deleted.
+    /// @return Number of deleted client classes.
+    virtual uint64_t
+    deleteClientClass4(const db::ServerSelector& server_selector,
+                       const std::string& name);
+
+    /// @brief Deletes all client classes.
+    ///
+    /// @param server_selector Server selector.
+    /// @return Number of deleted client classes.
+    virtual uint64_t
+    deleteAllClientClasses4(const db::ServerSelector& server_selector);
+
+    /// @brief Deletes a server from the backend.
+    ///
+    /// @param server_tag Tag of the server to be deleted.
+    /// @return Number of deleted servers.
+    /// @throw isc::InvalidOperation when trying to delete the logical
+    /// server 'all'.
+    virtual uint64_t
+    deleteServer4(const data::ServerTag& server_tag);
+
+    /// @brief Deletes all servers from the backend except the logical
+    /// server 'all'.
+    ///
+    /// @return Number of deleted servers.
+    virtual uint64_t
+    deleteAllServers4();
+
+    /// @brief Returns backend type in the textual format.
+    ///
+    /// @return "postgresql".
+    virtual 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.
+    virtual 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.
+    virtual uint16_t getPort() const;
+
+    /// @brief Registers the PgSQL backend factory with backend config manager
+    ///
+    /// This should be called by the hook lib load() function.
+    /// @return True if the factory was registered successfully, false otherwise.
+    static bool registerBackendType();
+
+    /// @brief Unregisters the PgSQL backend factory and discards PgSQL backends
+    ///
+    /// This should be called by the hook lib unload() function.
+    static void unregisterBackendType();
+
+    /// @brief Flag which indicates if the config backend has an unusable
+    /// connection.
+    ///
+    /// @return true if there is at least one unusable connection, false
+    /// otherwise
+    virtual bool isUnusable();
+
+    /// @brief Return backend parameters
+    ///
+    /// Returns the backend parameters
+    ///
+    /// @return Parameters of the backend.
+    isc::db::DatabaseConnection::ParameterMap getParameters() const;
+
+protected:
+
+    /// @brief Pointer to the implementation of the @c PgSqlConfigBackendDHCPv4
+    /// class.
+    boost::shared_ptr<PgSqlConfigBackendDHCPv4Impl> impl_;
+
+    /// @brief Pointer to the base implementation of the backend shared by
+    /// DHCPv4 and DHCPv6 servers.
+    boost::shared_ptr<PgSqlConfigBackendImpl> base_impl_;
+};
+
+/// @brief Pointer to the @c PgSqlConfigBackendDHCPv4 class.
+typedef boost::shared_ptr<PgSqlConfigBackendDHCPv4> PgSqlConfigBackendDHCPv4Ptr;
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // PGSQL_CONFIG_BACKEND_DHCP4_H
index 66145aa7288f9b83902ba9081615d90f5fc11db1..112260010460d4f11bb7501fb8b73b3efd1f30f2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-2022 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
@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <config_backend/constants.h>
 #include <dhcp/option_space.h>
+#include <database/db_exceptions.h>
 #include <pgsql/pgsql_exchange.h>
 #include <util/buffer.h>
 
@@ -28,6 +29,30 @@ namespace dhcp {
 
 isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr();
 
+PgSqlTaggedStatement&
+PgSqlConfigBackendImpl::getStatement(size_t /* index */) const {
+    isc_throw(NotImplemented, "derivations must override this");
+}
+
+void
+PgSqlConfigBackendImpl::selectQuery(size_t index,
+                                    const PsqlBindArray& in_bindings,
+                                    PgSqlConnection::ConsumeResultRowFun process_result_row) {
+    conn_.selectQuery(getStatement(index), in_bindings, process_result_row);
+}
+
+void
+PgSqlConfigBackendImpl::insertQuery(size_t index,
+                                    const PsqlBindArray& in_bindings) {
+    conn_.insertQuery(getStatement(index), in_bindings);
+}
+
+uint64_t
+PgSqlConfigBackendImpl::updateDeleteQuery(size_t index,
+                                    const PsqlBindArray& in_bindings) {
+    return(conn_.updateDeleteQuery(getStatement(index), in_bindings));
+}
+
 PgSqlConfigBackendImpl::ScopedAuditRevision::ScopedAuditRevision(
     PgSqlConfigBackendImpl* impl,
     const int index,
@@ -93,11 +118,11 @@ PgSqlConfigBackendImpl::~PgSqlConfigBackendImpl() {
 }
 
 void
-PgSqlConfigBackendImpl::createAuditRevision(const int /* index */,
+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 */) {
+                                            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;
@@ -115,7 +140,13 @@ PgSqlConfigBackendImpl::createAuditRevision(const int /* index */,
         tag = tags.begin()->get();
     }
 
-    isc_throw(NotImplemented, "todo");
+    PsqlBindArray in_bindings;
+    in_bindings.addTimestamp(audit_ts);
+    in_bindings.add(tag);
+    in_bindings.add(log_message);
+    in_bindings.add(cascade_transaction);
+
+    insertQuery(index, in_bindings);
 }
 
 void
@@ -124,12 +155,59 @@ PgSqlConfigBackendImpl::clearAuditRevision() {
 }
 
 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, "todo");
+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) {
+    auto tags = server_selector.getTags();
+    for (auto tag : tags) {
+        // Create the input parameters.
+        PsqlBindArray in_bindings;
+        in_bindings.addTempString(tag.get());
+        in_bindings.addTimestamp(modification_time);
+        in_bindings.add(modification_id);
+
+        // Execute select.
+        selectQuery(index, in_bindings,
+                    [&audit_entries] (PgSqlResult& r, int row) {
+            // Extract the column values for r[row].
+
+            // Get the object type. Column 0 is the entry ID which
+            // we don't need here.
+            std::string object_type;
+            PgSqlExchange::getColumnValue(r, row, 1, object_type);
+
+            // Get the object ID.
+            uint64_t object_id;
+            PgSqlExchange::getColumnValue(r, row, 2, object_id);
+
+            // Get the modification type.
+            uint8_t mod_typ_int;
+            PgSqlExchange::getColumnValue(r, row, 3, mod_typ_int);
+            AuditEntry::ModificationType mod_type =
+                static_cast<AuditEntry::ModificationType>(mod_typ_int);
+
+            // Get the modification time.
+            boost::posix_time::ptime mod_time;
+            PgSqlExchange::getColumnValue(r, row, 4, mod_time);
+
+            // Get the revision ID.
+            uint64_t revision_id;
+            PgSqlExchange::getColumnValue(r, row, 5, revision_id);
+
+            // Get the revision log message.
+            std::string log_message;
+            PgSqlExchange::getColumnValue(r, row, 6, log_message);
+
+            // Create new audit entry and add it to the collection of received
+            // entries.
+            AuditEntryPtr audit_entry =
+                AuditEntry::create(object_type, object_id, mod_type, mod_time,
+                                   revision_id, log_message);
+            audit_entries.insert(audit_entry);
+        });
+    }
 }
 
 uint64_t
@@ -147,37 +225,49 @@ PgSqlConfigBackendImpl::deleteFromTable(const int index,
 }
 
 uint64_t
-PgSqlConfigBackendImpl::deleteFromTable(const int /* index */,
-                                        const db::ServerSelector& /* server_selector */,
-                                        const std::string& /* operation */,
-                                        db::PsqlBindArray& /* bindings */) {
-    isc_throw(NotImplemented, "todo");
+PgSqlConfigBackendImpl::deleteFromTable(const int index,
+                                        const db::ServerSelector& server_selector,
+                                        const std::string& operation,
+                                        db::PsqlBindArray& in_bindings) {
+    // For ANY server, we use queries that lack server tag, otherwise
+    // we need to insert the server tag as the first input parameter.
+    if (!server_selector.amAny() && !server_selector.amUnassigned()) {
+        std::string tag = getServerTag(server_selector, operation);
+        in_bindings.insert(tag, 0);
+    }
+
+    return (updateDeleteQuery(index, in_bindings));
+}
+
+uint64_t
+PgSqlConfigBackendImpl::getLastInsertId(const int index, const std::string& table,
+                                        const std::string& column) {
+    PsqlBindArray in_bindings;
+    in_bindings.add(table);
+    in_bindings.add(column);
+    uint64_t last_id = 0;
+    conn_.selectQuery(getStatement(index), in_bindings,
+                    [&last_id] (PgSqlResult& r, int row) {
+            // Get the object type. Column 0 is the entry ID which
+            PgSqlExchange::getColumnValue(r, row, 0, last_id);
+        });
+
+    return (last_id);
 }
 
 void
 PgSqlConfigBackendImpl::getGlobalParameters(const int /* index */,
                                             const PsqlBindArray& /* in_bindings */,
                                             StampedValueCollection& /* parameters */) {
-
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 OptionDefinitionPtr
 PgSqlConfigBackendImpl::getOptionDef(const int /* index */,
-                                     const ServerSelector& server_selector,
+                                     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;
-    isc_throw(NotImplemented, "todo");
-    return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin());
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 void
@@ -185,7 +275,7 @@ PgSqlConfigBackendImpl::getAllOptionDefs(const int /* index */,
                                          const ServerSelector& server_selector,
                                          OptionDefContainer& /* option_defs */) {
     auto tags = server_selector.getTags();
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 void
@@ -194,7 +284,7 @@ PgSqlConfigBackendImpl::getModifiedOptionDefs(const int /* index */,
                                               const boost::posix_time::ptime& /* modification_time */,
                                               OptionDefContainer& /* option_defs */) {
     auto tags = server_selector.getTags();
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 void
@@ -203,7 +293,7 @@ PgSqlConfigBackendImpl::getOptionDefs(const int /* index */,
                                       OptionDefContainer& /* option_defs*/ ) {
     // Create output bindings. The order must match that in the prepared
     // statement.
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 void
@@ -227,7 +317,7 @@ PgSqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s
     for (auto field : option_def->getRecordFields()) {
         record_types->add(Element::create(static_cast<int>(field)));
     }
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 OptionDescriptorPtr
@@ -245,7 +335,7 @@ PgSqlConfigBackendImpl::getOption(const int /* index */,
     auto tag = getServerTag(server_selector, "fetching global option");
 
     OptionContainer options;
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
     return (options.empty() ? OptionDescriptorPtr() :
             OptionDescriptor::create(*options.begin()));
 }
@@ -257,7 +347,7 @@ PgSqlConfigBackendImpl::getAllOptions(const int /* index */,
     OptionContainer options;
     auto tags = server_selector.getTags();
 
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 
     return (options);
 }
@@ -274,7 +364,7 @@ PgSqlConfigBackendImpl::getModifiedOptions(const int index,
         PsqlBindArray in_bindings;
 
         /// need to define binding parameters
-        isc_throw(NotImplemented, "todo");
+        isc_throw(NotImplemented, NOT_IMPL_STR);
 
         getOptions(index, in_bindings, universe, options);
     }
@@ -299,7 +389,7 @@ PgSqlConfigBackendImpl::getOption(const int index,
 
     OptionContainer options;
     PsqlBindArray in_bindings;
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 
     getOptions(index, in_bindings, universe, options);
     return (options.empty() ? OptionDescriptorPtr() : OptionDescriptor::create(*options.begin()));
@@ -330,7 +420,7 @@ PgSqlConfigBackendImpl::getOption(const int index,
     Option::Universe universe = Option::V4;
     OptionContainer options;
     PsqlBindArray in_bindings;
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 
     getOptions(index, in_bindings, universe, options);
     return (options.empty() ? OptionDescriptorPtr() : OptionDescriptor::create(*options.begin()));
@@ -353,7 +443,7 @@ PgSqlConfigBackendImpl::getOption(const int index,
 
     OptionContainer options;
     PsqlBindArray in_bindings;
-    isc_throw(NotImplemented, "todo");
+    isc_throw(NotImplemented, NOT_IMPL_STR);
     getOptions(index, in_bindings, universe, options);
     return (options.empty() ? OptionDescriptorPtr() : OptionDescriptor::create(*options.begin()));
 }
@@ -363,46 +453,16 @@ PgSqlConfigBackendImpl::getOptions(const int /* index */,
                                    const db::PsqlBindArray& /* in_bindings */,
                                    const Option::Universe& /* universe */,
                                    OptionContainer& /* options */) {
-    isc_throw(NotImplemented, "todo");
-}
-
-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()));
-        }
-    }
-    isc_throw(NotImplemented, "todo");
-}
-
-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);
+    isc_throw(NotImplemented, NOT_IMPL_STR);
 }
 
 ServerPtr
-PgSqlConfigBackendImpl::getServer(const int index, const ServerTag& /* server_tag */) {
+PgSqlConfigBackendImpl::getServer(const int index, const ServerTag& server_tag) {
     ServerCollection servers;
-    PsqlBindArray in_bindings; /* = {
-        PsqlBindArray::createString(server_tag.get())
-    }; */
-    isc_throw(NotImplemented, "todo");
+
+    // Create input parameter bindings.
+    PsqlBindArray in_bindings;
+    in_bindings.addTempString(server_tag.get());
 
     getServers(index, in_bindings, servers);
 
@@ -416,16 +476,52 @@ PgSqlConfigBackendImpl::getAllServers(const int index, db::ServerCollection& ser
 }
 
 void
-PgSqlConfigBackendImpl::getServers(const int /* index */,
-                                   const PsqlBindArray& /* in_bindings */,
-                                   ServerCollection& /* servers */) {
-    isc_throw(NotImplemented, "todo");
+PgSqlConfigBackendImpl::getServers(const int index,
+                                   const PsqlBindArray& in_bindings,
+                                   ServerCollection& servers) {
+    // Track the last server added to avoid duplicates. This
+    // assumes the rows are ordered by server ID.
+    ServerPtr last_server;
+    selectQuery(index, in_bindings,
+                [&servers, &last_server](PgSqlResult& r, int row) {
+        // Extract the column values for r[row].
+
+        // Get the server ID.
+        uint64_t id;
+        PgSqlExchange::getColumnValue(r, row, 0, id);
+
+        // Get the server tag.
+        std::string tag;
+        PgSqlExchange::getColumnValue(r, row, 1, tag);
+
+        // Get the description.
+        std::string description;
+        PgSqlExchange::getColumnValue(r, row, 2, description);
+
+        // Get the modification time.
+        boost::posix_time::ptime mod_time;
+        PgSqlExchange::getColumnValue(r, row, 3, mod_time);
+
+        if (!last_server || (last_server->getId() != id)) {
+            // Create the server instance.
+            last_server = Server::create(ServerTag(tag), description);
+
+            // id
+            last_server->setId(id);
+
+            // modification_ts
+            last_server->setModificationTime(mod_time);
+
+            // New server fetched. Let's store it.
+            servers.insert(last_server);
+        }
+    });
 }
 
 void
 PgSqlConfigBackendImpl::createUpdateServer(const int& create_audit_revision,
-                                           const int& /* create_index */,
-                                           const int& /* update_index */,
+                                           const int& create_index,
+                                           const int& update_index,
                                            const ServerPtr& server) {
     // The server tag 'all' is reserved.
     if (server->getServerTag().amAll()) {
@@ -435,36 +531,58 @@ PgSqlConfigBackendImpl::createUpdateServer(const int& create_audit_revision,
                   " 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_);
-
+    // Populate the input bindings.
     PsqlBindArray in_bindings;
-    isc_throw(NotImplemented, "todo");
-
-     /* = {
-        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);
-    }*/
+    std::string tag = server->getServerTagAsText();
+    in_bindings.add(tag);
+    in_bindings.addTempString(server->getDescription());
+    in_bindings.addTimestamp(server->getModificationTime());
+
+    bool inserted = false;
+    for (auto attempts = 0; !inserted && attempts < 2; ++attempts) {
+        // Start a new transaction.
+        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, ServerSelector::ALL(),
+                                           "server set", true);
+
+        // On the first attempt we try to insert.
+        if (attempts == 0) {
+            try {
+                // Attempt to insert the server.
+                insertQuery(create_index, in_bindings);
+                inserted = true;
+            } catch (const DuplicateEntry&) {
+                // Server already exists that means our current transaction has
+                // been aborted by PostgreSQL. We need to start over with a new
+                // transaction, but this time we'll do the update.
+                continue;
+            }
+        } else {
+            // Add another instance of tag to the bindings to be used
+            // as the where clause parameter. PostgreSQL uses
+            // numbered placeholders so we could use $1 again, but
+            // doing it this way leaves the SQL more generic.
+            in_bindings.add(tag);
+
+            // Attempt to update the server.
+            if (!updateDeleteQuery(update_index, in_bindings)) {
+                // Possible only if someone deleted it since we tried to insert it,
+                // the query is broken, or the bindings are nonesense.
+                isc_throw(Unexpected, "Update server failed to find server tag: " << tag);
+            }
+        }
 
-    transaction.commit();
+        // Commit the transaction.
+        transaction.commit();
+    }
 }
 
 std::string
 PgSqlConfigBackendImpl::getType() const {
-    return ("pgsql");
+    return ("postgresql");
 }
 
 std::string
@@ -490,5 +608,55 @@ PgSqlConfigBackendImpl::getPort() const {
     return (0);
 }
 
+void
+PgSqlConfigBackendImpl::attachElementToServers(const int index,
+                                               const ServerSelector& server_selector,
+                                               const PsqlBindArray& in_bindings) {
+    // Copy the bindings because we're going to modify them.
+    PsqlBindArray server_bindings = in_bindings;
+    for (auto tag : server_selector.getTags()) {
+        // Add the server tag to end of the bindings.
+        std::string server_tag = tag.get();
+        server_bindings.add(server_tag);
+
+        // Insert the server assocation.
+        insertQuery(index, server_bindings);
+
+        // Remove the prior server tag.
+        server_bindings.popBack();
+    }
+}
+
+void
+PgSqlConfigBackendImpl::addRelayBinding(PsqlBindArray& bindings,
+                                        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()));
+        }
+    }
+
+    bindings.add(relay_element);
+}
+
+void
+PgSqlConfigBackendImpl::addOptionValueBinding(PsqlBindArray& bindings,
+                                              const OptionDescriptorPtr& option) {
+    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());
+        bindings.add(blob);
+    } else {
+        bindings.addNull();
+    }
+}
+
+
 }  // namespace dhcp
 }  // end of namespace isc
index d6bca6a130905d59c1eb9cb6eb886d57a6c26bb1..b04fab1ba3436219e56eaca40fc89fa543e3150d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-2022 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
@@ -28,6 +28,9 @@
 #include <string>
 #include <vector>
 
+// Convenience string for emitting location info NotImplemented exceptions.
+#define NOT_IMPL_STR __FILE__ << ":" << __LINE__  << " - " << __FUNCTION__
+
 namespace isc {
 namespace dhcp {
 
@@ -154,7 +157,7 @@ public:
         return (s.str());
     }
 
-    /// @brief Invokes the corresponding stored procedure in MySQL.
+    /// @brief Invokes the corresponding stored procedure in PgSQL.
     ///
     /// The @c createAuditRevision stored procedure creates new audit
     /// revision and initializes several session variables to be used when
@@ -212,11 +215,67 @@ public:
                              const db::ServerSelector& server_selector,
                              const std::string& operation);
 
+    /// @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 logging purposes.
+    /// @param in_bindings Reference to the PgSQL input bindings. They are modified
+    /// as a result of this function - server tag is inserted into the beginning
+    /// of the bindings collection.
+    /// @return Number of deleted rows.
     uint64_t deleteFromTable(const int index,
                              const db::ServerSelector& server_selector,
                              const std::string& operation,
                              db::PsqlBindArray& bindings);
 
+    /// @brief Sends query to delete rows from a table.
+    ///
+    /// @tparam KeyType Type of the key used as the second binding. The
+    /// server tag is used as first binding.
+    ///
+    /// @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.
+    /// @param key Value to be used as input binding to the delete
+    /// statement. The default value is empty which indicates that the
+    /// key should not be used in the query.
+    /// @return Number of deleted rows.
+    /// @throw InvalidOperation if the server selector is unassigned or
+    /// if there are more than one server tags associated with the
+    /// server selector.
+    template<typename KeyType>
+    uint64_t deleteFromTable(const int index,
+                             const db::ServerSelector& server_selector,
+                             const std::string& operation,
+                             KeyType key) {
+        // When deleting by some key, we must use ANY.
+        if (server_selector.amUnassigned()) {
+            isc_throw(NotImplemented, "deleting an unassigned object requires "
+                      "an explicit server tag or using ANY server. The UNASSIGNED "
+                      "server selector is currently not supported");
+        }
+
+        db::PsqlBindArray in_bindings;
+        in_bindings.add(key);
+        return (deleteFromTable(index, server_selector, operation, in_bindings));
+    }
+
+    /// @brief Returns the last sequence value for the given table and
+    /// column name.
+    ///
+    /// This relies on PostgreSQL's currval() function which will return
+    /// the last value generated for the sequence within the current session.
+    ///
+    /// @param table name of the table
+    /// @param column name of the sequence column
+    /// @return returns the most recently modified value for the given
+    /// sequence
+    uint64_t getLastInsertId(const int index, const std::string& table,
+                             const std::string& column);
+
     /// @brief Sends query to retrieve multiple global parameters.
     ///
     /// @param index Index of the query to be used.
@@ -424,22 +483,60 @@ public:
 
     /// @todo implement OptionDescriptorPtr processOptionRow(const Option::Universe& universe, ...)
 
-    /// @todo implement void attachElementToServers(const int index, ...)
-
-    /// @todo implement
-    /// @note this needs to accept an PsqlBindArrayPtr and should add
-    /// necessary bindings to it.  It cannot create a new array.
-    db::PsqlBindArrayPtr createInputRelayBinding(const NetworkPtr& network);
-
-    /// @todo implement template<typename T> db::MySqlBindingPtr
-    /// createInputRequiredClassesBinding(const T& object)
+    /// @brief Associates a configuration element with multiple servers.
+    ///
+    /// @param index Query index.
+    /// @param server_selector Server selector, perhaps with multiple server tags.
+    /// @param in_bindings Parameter pack holding bindings for the query. The first
+    /// entry must be the primary key of the element to attach. Note that
+    /// the server tag (or server id) must be the last binding in the prepared
+    /// statement. The caller must not include this binding in the parameter pack.
+    void attachElementToServers(const int index,
+                                const db::ServerSelector& server_selector,
+                                const db::PsqlBindArray& in_bindings);
+
+    /// @brief Adds network relays addresses to a bind array.
+    ///
+    /// Creates an Element tree of relay addresses add adds that to the end
+    /// of the given bind array.
+    ///
+    /// @param bindings PsqlBindArray to which the relay addresses should be added.
+    /// @param network Pointer to a shared network or subnet for which binding
+    /// should be created.
+    void addRelayBinding(db::PsqlBindArray& bindings, const NetworkPtr& network);
+
+    /// @brief Adds 'require_client_classes' parameter to a bind array.
+    ///
+    /// Creates an Element tree of required class names and adds that to the end
+    /// of the given bind array.
+    ///
+    /// @tparam T of pointer to objects with getRequiredClasses
+    /// method, e.g. shared network, subnet, pool or prefix delegation pool.
+    /// @param bindings PsqlBindArray to which the classes should be added.
+    /// @param object Pointer to an object with getRequiredClasses method
+    /// @return Pointer to the binding (possibly null binding if there are no
+    /// required classes specified).
+    template<typename T>
+    void addRequiredClassesBinding(db::PsqlBindArray& bindings, const T& object) {
+        // Create JSON list of required classes.
+        data::ElementPtr required_classes_element = data::Element::createList();
+        const auto& required_classes = object->getRequiredClasses();
+        for (auto required_class = required_classes.cbegin();
+             required_class != required_classes.cend();
+             ++required_class) {
+            required_classes_element->add(data::Element::create(*required_class));
+        }
 
-    /// @todo implement db::MySqlBindingPtr createInputContextBinding(const T& config_element) {
+        bindings.add(required_classes_element);
+    }
 
-    /// @todo implement
-    /// @note this needs to accept an PsqlBindArrayPtr and should add
-    /// necessary bindings to it.  It cannot create a new array.
-    db::PsqlBindArrayPtr createOptionValueBinding(const OptionDescriptorPtr& option);
+    /// @brief Adds an option value to a bind array.
+    ///
+    /// @param bindings PsqlBindArray to which the option value should be added.
+    /// @param option Option descriptor holding the option who's value should
+    /// added.
+    void addOptionValueBinding(db::PsqlBindArray& bindings,
+                               const OptionDescriptorPtr& option);
 
     /// @brief Retrieves a server.
     ///
@@ -460,7 +557,7 @@ public:
     /// @brief Sends query to retrieve servers.
     ///
     /// @param index Index of the query to be used.
-    /// @param bindings Reference to the MySQL input bindings.
+    /// @param bindings Reference to the PgSQL input bindings.
     /// @param [out] servers Reference to the container where fetched servers
     /// will be inserted.
     void
@@ -486,8 +583,27 @@ public:
                             const int& update_index,
                             const db::ServerPtr& server);
 
-    /// @todo implement template<typename T, typename... R> void multipleUpdateDeleteQueries(T
-    /// first_index, R... other_indexes)
+    /// @brief Executes multiple update and/or delete queries with no input
+    /// bindings.
+    ///
+    /// This is a convenience function which takes multiple query indexes as
+    /// arguments and for each index executes an update or delete query.
+    /// One of the applications of this function is to remove dangling
+    /// configuration elements after the server associated with these elements
+    /// have been deleted.
+    ///
+    /// @tparam T type of the indexes, e.g. @c PgSqlConfigBackendDHCPv4Impl::StatementIndex.
+    /// @tparam R parameter pack holding indexes of type @c T.
+    /// @param first_index first index.
+    /// @param other_indexes remaining indexes.
+    template<typename T, typename... R>
+    void multipleUpdateDeleteQueries(T first_index, R... other_indexes) {
+        std::vector<T> indexes({ first_index, other_indexes... });
+        db::PsqlBindArray in_bindings;
+        for (auto index : indexes) {
+            updateDeleteQuery(index, in_bindings);
+        }
+    }
 
     /// @brief Removes configuration elements from the index which don't match
     /// the specified server selector.
@@ -603,6 +719,66 @@ public:
         return (io_service_);
     }
 
+    /// @brief Fetches the SQL statement for a given statement index.
+    ///
+    /// Derivations must override the implementation. The reference
+    /// returned should be non-volatile over the entire lifetime
+    /// of the derivation instance.
+    ///
+    /// @param index index of the desired statement.
+    /// @throw NotImplemented, always
+    virtual db::PgSqlTaggedStatement& getStatement(size_t index) const;
+
+    /// @brief Executes SELECT using the prepared statement specified
+    /// by the given index.
+    ///
+    /// The @c index must refer to an existing prepared statement
+    /// associated with the connection. The @c in_bindings size must match
+    /// the number of placeholders in the prepared statement.
+    ///
+    /// This method executes prepared statement using provided input bindings and
+    /// calls @c process_result_row function for each returned row. The
+    /// @c process_result function is implemented by the caller and should
+    /// gather and store each returned row in an external data structure prior.
+    ///
+    /// @param statement reference to the precompiled tagged statement to execute
+    /// @param in_bindings input bindings holding values to substitue placeholders
+    /// in the query.
+    /// @param process_result_row Pointer to the function to be invoked for each
+    /// retrieved row. This function consumes the retrieved data from the
+    /// result set.
+    void selectQuery(size_t index, const db::PsqlBindArray& in_bindings,
+                     db::PgSqlConnection::ConsumeResultRowFun process_result_row);
+
+    /// @brief Executes INSERT using the prepared statement specified
+    /// by the given index.
+    ///
+    /// The @c index must refer to an existing prepared statement
+    /// associated with the connection. The @c in_bindings size must match
+    /// the number of placeholders in the prepared statement.
+    ///
+    /// This method executes prepared statement using provided bindings to
+    /// insert data into the database.
+    ///
+    /// @param statement reference to the precompiled tagged statement to execute
+    /// @param in_bindings input bindings holding values to substitue placeholders
+    /// in the query.
+    void insertQuery(size_t index, const db::PsqlBindArray& in_bindings);
+
+    /// @brief Executes UPDATE or DELETE using the prepared statement
+    /// specified by the given index, and returns the number of affected rows.
+    ///
+    /// The @c index must refer to an existing prepared statement
+    /// associated with the connection. The @c in_bindings size must match
+    /// the number of placeholders in the prepared statement.
+    ///
+    /// @param statement reference to the precompiled tagged statement to execute
+    /// @param in_bindings Input bindings holding values to substitute placeholders
+    /// in the query.
+    ///
+    /// @return Number of affected rows.
+    uint64_t updateDeleteQuery(size_t index, const db::PsqlBindArray& in_bindings);
+
     /// @brief Represents connection to the Postgres database.
     db::PgSqlConnection conn_;
 
index 8f80623f0d73ec17715f0886562a42d72183fd76..2baed5c5e5077e1f98ec404d1a350c99b9206abc 100644 (file)
@@ -12,6 +12,8 @@ extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_POOL_OPTION6 = "PGSQL
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6 = "PGSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4 = "PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6 = "PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6";
+extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4 = "PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4";
+extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS6 = "PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS6";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4 = "PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6 = "PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_OPTION4 = "PGSQL_CB_CREATE_UPDATE_OPTION4";
@@ -27,6 +29,10 @@ extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SHARED_NETWORK_OPTION6 =
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SUBNET4 = "PGSQL_CB_CREATE_UPDATE_SUBNET4";
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SUBNET6 = "PGSQL_CB_CREATE_UPDATE_SUBNET6";
 extern const isc::log::MessageID PGSQL_CB_DEINIT_OK = "PGSQL_CB_DEINIT_OK";
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4 = "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4";
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT = "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT";
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6 = "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6";
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT = "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4 = "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4";
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT = "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6 = "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6";
@@ -65,6 +71,10 @@ extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4 = "PGSQL_C
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT = "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6 = "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6";
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT = "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT";
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS4 = "PGSQL_CB_DELETE_CLIENT_CLASS4";
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT = "PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT";
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS6 = "PGSQL_CB_DELETE_CLIENT_CLASS6";
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS6_RESULT = "PGSQL_CB_DELETE_CLIENT_CLASS6_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER4 = "PGSQL_CB_DELETE_GLOBAL_PARAMETER4";
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT = "PGSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER6 = "PGSQL_CB_DELETE_GLOBAL_PARAMETER6";
@@ -93,6 +103,10 @@ extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4 = "PGSQ
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT = "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6 = "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6";
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT = "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT";
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES4 = "PGSQL_CB_GET_ALL_CLIENT_CLASSES4";
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT = "PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT";
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES6 = "PGSQL_CB_GET_ALL_CLIENT_CLASSES6";
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT = "PGSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT";
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4 = "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4";
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT = "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS6 = "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS6";
@@ -117,10 +131,16 @@ extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS4 = "PGSQL_CB_GET_ALL_S
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS4_RESULT = "PGSQL_CB_GET_ALL_SUBNETS4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS6 = "PGSQL_CB_GET_ALL_SUBNETS6";
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS6_RESULT = "PGSQL_CB_GET_ALL_SUBNETS6_RESULT";
+extern const isc::log::MessageID PGSQL_CB_GET_CLIENT_CLASS4 = "PGSQL_CB_GET_CLIENT_CLASS4";
+extern const isc::log::MessageID PGSQL_CB_GET_CLIENT_CLASS6 = "PGSQL_CB_GET_CLIENT_CLASS6";
 extern const isc::log::MessageID PGSQL_CB_GET_GLOBAL_PARAMETER4 = "PGSQL_CB_GET_GLOBAL_PARAMETER4";
 extern const isc::log::MessageID PGSQL_CB_GET_GLOBAL_PARAMETER6 = "PGSQL_CB_GET_GLOBAL_PARAMETER6";
 extern const isc::log::MessageID PGSQL_CB_GET_HOST4 = "PGSQL_CB_GET_HOST4";
 extern const isc::log::MessageID PGSQL_CB_GET_HOST6 = "PGSQL_CB_GET_HOST6";
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4 = "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4";
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT = "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT";
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6 = "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6";
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT = "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT";
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4 = "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4";
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT = "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT";
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6 = "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6";
@@ -190,6 +210,8 @@ const char* values[] = {
     "PGSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6", "create or update option prefix: %1 prefix len: %2",
     "PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4", "create or update option by subnet id: %1",
     "PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6", "create or update option by subnet id: %1",
+    "PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4", "create or update client class: %1",
+    "PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS6", "create or update client class: %1",
     "PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4", "create or update global parameter: %1",
     "PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6", "create or update global parameter: %1",
     "PGSQL_CB_CREATE_UPDATE_OPTION4", "create or update option",
@@ -205,6 +227,10 @@ const char* values[] = {
     "PGSQL_CB_CREATE_UPDATE_SUBNET4", "create or update subnet: %1",
     "PGSQL_CB_CREATE_UPDATE_SUBNET6", "create or update subnet: %1",
     "PGSQL_CB_DEINIT_OK", "unloading Postgres CB hooks library successful",
+    "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4", "delete all client classes",
+    "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT", "deleted: %1 entries",
+    "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6", "delete all client classes",
+    "PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4", "delete all global parameters",
     "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6", "delete all global parameters",
@@ -243,6 +269,10 @@ const char* values[] = {
     "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6", "delete subnet by subnet id: %1",
     "PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT", "deleted: %1 entries",
+    "PGSQL_CB_DELETE_CLIENT_CLASS4", "delete client class: %1",
+    "PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT", "deleted: %1 entries",
+    "PGSQL_CB_DELETE_CLIENT_CLASS6", "delete client class: %1",
+    "PGSQL_CB_DELETE_CLIENT_CLASS6_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_GLOBAL_PARAMETER4", "delete global parameter: %1",
     "PGSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_GLOBAL_PARAMETER6", "delete global parameter: %1",
@@ -271,6 +301,10 @@ const char* values[] = {
     "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT", "deleted: %1 entries",
     "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6", "delete shared network: %1 subnets",
     "PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT", "deleted: %1 entries",
+    "PGSQL_CB_GET_ALL_CLIENT_CLASSES4", "retrieving all client classes",
+    "PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT", "retrieving: %1 elements",
+    "PGSQL_CB_GET_ALL_CLIENT_CLASSES6", "retrieving all client classes",
+    "PGSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT", "retrieving: %1 elements",
     "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4", "retrieving all global parameters",
     "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT", "retrieving: %1 elements",
     "PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS6", "retrieving all global parameters",
@@ -295,10 +329,16 @@ const char* values[] = {
     "PGSQL_CB_GET_ALL_SUBNETS4_RESULT", "retrieving: %1 elements",
     "PGSQL_CB_GET_ALL_SUBNETS6", "retrieving all subnets",
     "PGSQL_CB_GET_ALL_SUBNETS6_RESULT", "retrieving: %1 elements",
+    "PGSQL_CB_GET_CLIENT_CLASS4", "retrieving client class: %1",
+    "PGSQL_CB_GET_CLIENT_CLASS6", "retrieving client class: %1",
     "PGSQL_CB_GET_GLOBAL_PARAMETER4", "retrieving global parameter: %1",
     "PGSQL_CB_GET_GLOBAL_PARAMETER6", "retrieving global parameter: %1",
     "PGSQL_CB_GET_HOST4", "get host",
     "PGSQL_CB_GET_HOST6", "get host",
+    "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4", "retrieving modified client classes from: %1",
+    "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT", "retrieving: %1 elements",
+    "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6", "retrieving modified client classes from: %1",
+    "PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT", "retrieving: %1 elements",
     "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4", "retrieving modified global parameters from: %1",
     "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT", "retrieving: %1 elements",
     "PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6", "retrieving modified global parameters from: %1",
index 57e3f744a5bfc6c19998bc170c585a299ba594d4..57a41d30cc9bb05c8a41a068e97a4fe02b0e3116 100644 (file)
@@ -13,6 +13,8 @@ extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_POOL_OPTION6;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6;
+extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4;
+extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS6;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_OPTION4;
@@ -28,6 +30,10 @@ extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SHARED_NETWORK_OPTION6;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SUBNET4;
 extern const isc::log::MessageID PGSQL_CB_CREATE_UPDATE_SUBNET6;
 extern const isc::log::MessageID PGSQL_CB_DEINIT_OK;
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4;
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT;
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6;
+extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4;
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6;
@@ -66,6 +72,10 @@ extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4;
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6;
 extern const isc::log::MessageID PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT;
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS4;
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT;
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS6;
+extern const isc::log::MessageID PGSQL_CB_DELETE_CLIENT_CLASS6_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER4;
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_GLOBAL_PARAMETER6;
@@ -94,6 +104,10 @@ extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4;
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6;
 extern const isc::log::MessageID PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT;
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES4;
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT;
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES6;
+extern const isc::log::MessageID PGSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS6;
@@ -118,10 +132,16 @@ extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS4;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS6;
 extern const isc::log::MessageID PGSQL_CB_GET_ALL_SUBNETS6_RESULT;
+extern const isc::log::MessageID PGSQL_CB_GET_CLIENT_CLASS4;
+extern const isc::log::MessageID PGSQL_CB_GET_CLIENT_CLASS6;
 extern const isc::log::MessageID PGSQL_CB_GET_GLOBAL_PARAMETER4;
 extern const isc::log::MessageID PGSQL_CB_GET_GLOBAL_PARAMETER6;
 extern const isc::log::MessageID PGSQL_CB_GET_HOST4;
 extern const isc::log::MessageID PGSQL_CB_GET_HOST6;
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4;
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT;
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6;
+extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT;
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4;
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT;
 extern const isc::log::MessageID PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6;
index 26bbdfef297eb013c30c1793f9f214fecde3308e..8c10c122ffa309b11ca3233f9428d01224edf142 100644 (file)
@@ -17,6 +17,12 @@ Debug message issued when triggered an action to create or update option by subn
 % PGSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6 create or update option by subnet id: %1
 Debug message issued when triggered an action to create or update option by subnet id
 
+% PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS4 create or update client class: %1
+Debug message issued when triggered an action to create or update client class
+
+% PGSQL_CB_CREATE_UPDATE_CLIENT_CLASS6 create or update client class: %1
+Debug message issued when triggered an action to create or update client class
+
 % PGSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4 create or update global parameter: %1
 Debug message issued when triggered an action to create or update global parameter
 
@@ -65,6 +71,18 @@ Debug message issued when triggered an action to create or update subnet
 This informational message indicates that the Postgres Configuration Backend hooks
 library has been unloaded successfully.
 
+% PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4 delete all client classes
+Debug message issued when triggered an action to delete all client classes
+
+% PGSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete all client classes
+
+% PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6 delete all client classes
+Debug message issued when triggered an action to delete all client classes
+
+% PGSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete all client classes
+
 % PGSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4 delete all global parameters
 Debug message issued when triggered an action to delete all global parameters
 
@@ -179,6 +197,18 @@ Debug message issued when triggered an action to delete subnet by subnet id
 % PGSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT deleted: %1 entries
 Debug message indicating the result of an action to delete subnet by subnet id
 
+% PGSQL_CB_DELETE_CLIENT_CLASS4 delete client class: %1
+Debug message issued when triggered an action to delete client class
+
+% PGSQL_CB_DELETE_CLIENT_CLASS4_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete client class
+
+% PGSQL_CB_DELETE_CLIENT_CLASS6 delete client class: %1
+Debug message issued when triggered an action to delete client class
+
+% PGSQL_CB_DELETE_CLIENT_CLASS6_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete client class
+
 % PGSQL_CB_DELETE_GLOBAL_PARAMETER4 delete global parameter: %1
 Debug message issued when triggered an action to delete global parameter
 
@@ -263,6 +293,18 @@ Debug message issued when triggered an action to delete shared network subnets
 % PGSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT deleted: %1 entries
 Debug message indicating the result of an action to delete shared network subnets
 
+% PGSQL_CB_GET_ALL_CLIENT_CLASSES4 retrieving all client classes
+Debug message issued when triggered an action to retrieve all client classes
+
+% PGSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve all client classes
+
+% PGSQL_CB_GET_ALL_CLIENT_CLASSES6 retrieving all client classes
+Debug message issued when triggered an action to retrieve all client classes
+
+% PGSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve all client classes
+
 % PGSQL_CB_GET_ALL_GLOBAL_PARAMETERS4 retrieving all global parameters
 Debug message issued when triggered an action to retrieve all global parameters
 
@@ -339,6 +381,12 @@ Debug message issued when triggered an action to retrieve all subnets
 % PGSQL_CB_GET_ALL_SUBNETS6_RESULT retrieving: %1 elements
 Debug message indicating the result of an action to retrieve all subnets
 
+% PGSQL_CB_GET_CLIENT_CLASS4 retrieving client class: %1
+Debug message issued when triggered an action to retrieve a client class
+
+% PGSQL_CB_GET_CLIENT_CLASS6 retrieving client class: %1
+Debug message issued when triggered an action to retrieve a client class
+
 % PGSQL_CB_GET_GLOBAL_PARAMETER4 retrieving global parameter: %1
 Debug message issued when triggered an action to retrieve global parameter
 
@@ -351,6 +399,18 @@ Debug message issued when triggered an action to retrieve host
 % PGSQL_CB_GET_HOST6 get host
 Debug message issued when triggered an action to retrieve host
 
+% PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4 retrieving modified client classes from: %1
+Debug message issued when triggered an action to retrieve modified client classes from specified time
+
+% PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve modified client classes from specified time
+
+% PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6 retrieving modified client classes from: %1
+Debug message issued when triggered an action to retrieve modified client classes from specified time
+
+% PGSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve modified client classes from specified time
+
 % PGSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4 retrieving modified global parameters from: %1
 Debug message issued when triggered an action to retrieve modified global parameters from specified time
 
diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_macros.h b/src/hooks/dhcp/pgsql_cb/pgsql_macros.h
deleted file mode 100644 (file)
index 05f61ac..0000000
+++ /dev/null
@@ -1,1009 +0,0 @@
-// 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_QUERY_MACROS_DHCP_H
-#define PGSQL_QUERY_MACROS_DHCP_H
-
-/// @file pgsql_macros.h
-/// Collection of common macros defining PostgreSQL prepared statements
-/// used to manage Kea DHCP configuration in the database.
-///
-/// Some of the macros are DHCPv4 specific, other are DHCPv6 specific.
-/// Some macros are common for both DHCP server types. The first
-/// parameter @c table_prefix should be set to @c dhcp4 or @c dhcp6,
-/// depending which DHCP server type it relates to. Provided value
-/// is used as a prefix for PgSQL table names. For example, if the
-/// prefix is set to @c dhcp4, the table name referred to in the
-/// query may be dhcp4_subnet etc. The second argument in the variadic
-/// macro is a part of the WHERE clause in the PgSQL query. The fixed
-/// part of the WHERE clause is included in the macro.
-
-/// @todo Update queries to also fetch server tags to associate
-/// returned configuration elements with particular servers.
-
-namespace isc {
-namespace dhcp {
-
-namespace {
-
-#ifndef PGSQL_GET_GLOBAL_PARAMETER
-#define PGSQL_GET_GLOBAL_PARAMETER(table_prefix, ...)            \
-    "SELECT"                                                     \
-    "  g.id,"                                                    \
-    "  g.name,"                                                  \
-    "  g.value,"                                                 \
-    "  g.parameter_type,"                                        \
-    "  g.modification_ts,"                                       \
-    "  s.tag "                                                   \
-    "FROM " #table_prefix "_global_parameter AS g "              \
-    "INNER JOIN " #table_prefix "_global_parameter_server AS a " \
-    "  ON g.id = a.parameter_id "                                \
-    "INNER JOIN " #table_prefix "_server AS s "                  \
-    "  ON (a.server_id = s.id) "                                 \
-    "WHERE (s.tag = ? OR s.id = 1) " #__VA_ARGS__ " ORDER BY g.id, s.id"
-
-#endif
-
-#ifndef PGSQL_GET_SUBNET4
-#define PGSQL_GET_SUBNET4_COMMON(server_join, ...)                         \
-    "SELECT"                                                               \
-    "  s.subnet_id,"                                                       \
-    "  s.subnet_prefix,"                                                   \
-    "  s.4o6_interface,"                                                   \
-    "  s.4o6_interface_id,"                                                \
-    "  s.4o6_subnet,"                                                      \
-    "  s.boot_file_name,"                                                  \
-    "  s.client_class,"                                                    \
-    "  s.interface,"                                                       \
-    "  s.match_client_id,"                                                 \
-    "  s.modification_ts,"                                                 \
-    "  s.next_server,"                                                     \
-    "  s.rebind_timer,"                                                    \
-    "  s.relay,"                                                           \
-    "  s.renew_timer,"                                                     \
-    "  s.require_client_classes,"                                          \
-    "  s.reservations_global,"                                             \
-    "  s.server_hostname,"                                                 \
-    "  s.shared_network_name,"                                             \
-    "  s.user_context,"                                                    \
-    "  s.valid_lifetime,"                                                  \
-    "  p.id,"                                                              \
-    "  p.start_address,"                                                   \
-    "  p.end_address,"                                                     \
-    "  p.subnet_id,"                                                       \
-    "  p.modification_ts,"                                                 \
-    "  x.option_id,"                                                       \
-    "  x.code,"                                                            \
-    "  x.value,"                                                           \
-    "  x.formatted_value,"                                                 \
-    "  x.space,"                                                           \
-    "  x.persistent,"                                                      \
-    "  x.dhcp4_subnet_id,"                                                 \
-    "  x.scope_id,"                                                        \
-    "  x.user_context,"                                                    \
-    "  x.shared_network_name,"                                             \
-    "  x.pool_id,"                                                         \
-    "  x.modification_ts,"                                                 \
-    "  o.option_id,"                                                       \
-    "  o.code,"                                                            \
-    "  o.value,"                                                           \
-    "  o.formatted_value,"                                                 \
-    "  o.space,"                                                           \
-    "  o.persistent,"                                                      \
-    "  o.dhcp4_subnet_id,"                                                 \
-    "  o.scope_id,"                                                        \
-    "  o.user_context,"                                                    \
-    "  o.shared_network_name,"                                             \
-    "  o.pool_id,"                                                         \
-    "  o.modification_ts,"                                                 \
-    "  s.calculate_tee_times,"                                             \
-    "  s.t1_percent,"                                                      \
-    "  s.t2_percent,"                                                      \
-    "  s.authoritative,"                                                   \
-    "  s.min_valid_lifetime,"                                              \
-    "  s.max_valid_lifetime,"                                              \
-    "  p.client_class,"                                                    \
-    "  p.require_client_classes,"                                          \
-    "  p.user_context,"                                                    \
-    "  s.ddns_send_updates,"                                               \
-    "  s.ddns_override_no_update,"                                         \
-    "  s.ddns_override_client_update,"                                     \
-    "  s.ddns_replace_client_name,"                                        \
-    "  s.ddns_generated_prefix,"                                           \
-    "  s.ddns_qualifying_suffix,"                                          \
-    "  s.reservations_in_subnet,"                                          \
-    "  s.reservations_out_of_pool,"                                        \
-    "  s.cache_threshold,"                                                 \
-    "  s.cache_max_age,"                                                   \
-    "  srv.tag "                                                           \
-    "FROM dhcp4_subnet AS s " server_join                                  \
-    "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id "              \
-    "LEFT JOIN dhcp4_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " \
-    "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = "    \
-    "o.dhcp4_subnet_id " #__VA_ARGS__ " ORDER BY s.subnet_id, p.id, x.option_id, o.option_id"
-
-#define PGSQL_GET_SUBNET4_NO_TAG(...)                               \
-    PGSQL_GET_SUBNET4_COMMON("INNER JOIN dhcp4_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "      \
-                             "INNER JOIN dhcp4_server AS srv "      \
-                             "  ON (a.server_id = srv.id) ",        \
-                             __VA_ARGS__)
-
-#define PGSQL_GET_SUBNET4_ANY(...)                                 \
-    PGSQL_GET_SUBNET4_COMMON("LEFT JOIN dhcp4_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "     \
-                             "LEFT JOIN dhcp4_server AS srv "      \
-                             "  ON a.server_id = srv.id ",         \
-                             __VA_ARGS__)
-
-#define PGSQL_GET_SUBNET4_UNASSIGNED(...)                          \
-    PGSQL_GET_SUBNET4_COMMON("LEFT JOIN dhcp4_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "     \
-                             "LEFT JOIN dhcp4_server AS srv "      \
-                             "  ON a.server_id = srv.id ",         \
-                             WHERE a.subnet_id IS NULL __VA_ARGS__)
-
-#endif
-
-#ifndef PGSQL_GET_SUBNET6
-#define PGSQL_GET_SUBNET6_COMMON(server_join, ...)                            \
-    "SELECT"                                                                  \
-    "  s.subnet_id,"                                                          \
-    "  s.subnet_prefix,"                                                      \
-    "  s.client_class,"                                                       \
-    "  s.interface,"                                                          \
-    "  s.modification_ts,"                                                    \
-    "  s.preferred_lifetime,"                                                 \
-    "  s.rapid_commit,"                                                       \
-    "  s.rebind_timer,"                                                       \
-    "  s.relay,"                                                              \
-    "  s.renew_timer,"                                                        \
-    "  s.require_client_classes,"                                             \
-    "  s.reservations_global,"                                                \
-    "  s.shared_network_name,"                                                \
-    "  s.user_context,"                                                       \
-    "  s.valid_lifetime,"                                                     \
-    "  p.id,"                                                                 \
-    "  p.start_address,"                                                      \
-    "  p.end_address,"                                                        \
-    "  p.subnet_id,"                                                          \
-    "  p.modification_ts,"                                                    \
-    "  d.id,"                                                                 \
-    "  d.prefix,"                                                             \
-    "  d.prefix_length,"                                                      \
-    "  d.delegated_prefix_length,"                                            \
-    "  d.subnet_id,"                                                          \
-    "  d.modification_ts,"                                                    \
-    "  x.option_id,"                                                          \
-    "  x.code,"                                                               \
-    "  x.value,"                                                              \
-    "  x.formatted_value,"                                                    \
-    "  x.space,"                                                              \
-    "  x.persistent,"                                                         \
-    "  x.dhcp6_subnet_id,"                                                    \
-    "  x.scope_id,"                                                           \
-    "  x.user_context,"                                                       \
-    "  x.shared_network_name,"                                                \
-    "  x.pool_id,"                                                            \
-    "  x.modification_ts,"                                                    \
-    "  x.pd_pool_id,"                                                         \
-    "  y.option_id,"                                                          \
-    "  y.code,"                                                               \
-    "  y.value,"                                                              \
-    "  y.formatted_value,"                                                    \
-    "  y.space,"                                                              \
-    "  y.persistent,"                                                         \
-    "  y.dhcp6_subnet_id,"                                                    \
-    "  y.scope_id,"                                                           \
-    "  y.user_context,"                                                       \
-    "  y.shared_network_name,"                                                \
-    "  y.pool_id,"                                                            \
-    "  y.modification_ts,"                                                    \
-    "  y.pd_pool_id,"                                                         \
-    "  o.option_id,"                                                          \
-    "  o.code,"                                                               \
-    "  o.value,"                                                              \
-    "  o.formatted_value,"                                                    \
-    "  o.space,"                                                              \
-    "  o.persistent,"                                                         \
-    "  o.dhcp6_subnet_id,"                                                    \
-    "  o.scope_id,"                                                           \
-    "  o.user_context,"                                                       \
-    "  o.shared_network_name,"                                                \
-    "  o.pool_id,"                                                            \
-    "  o.modification_ts,"                                                    \
-    "  o.pd_pool_id, "                                                        \
-    "  s.calculate_tee_times,"                                                \
-    "  s.t1_percent,"                                                         \
-    "  s.t2_percent,"                                                         \
-    "  s.interface_id,"                                                       \
-    "  s.min_preferred_lifetime,"                                             \
-    "  s.max_preferred_lifetime,"                                             \
-    "  s.min_valid_lifetime,"                                                 \
-    "  s.max_valid_lifetime,"                                                 \
-    "  p.client_class,"                                                       \
-    "  p.require_client_classes,"                                             \
-    "  p.user_context,"                                                       \
-    "  d.excluded_prefix,"                                                    \
-    "  d.excluded_prefix_length,"                                             \
-    "  d.client_class,"                                                       \
-    "  d.require_client_classes,"                                             \
-    "  d.user_context,"                                                       \
-    "  s.ddns_send_updates,"                                                  \
-    "  s.ddns_override_no_update,"                                            \
-    "  s.ddns_override_client_update,"                                        \
-    "  s.ddns_replace_client_name,"                                           \
-    "  s.ddns_generated_prefix,"                                              \
-    "  s.ddns_qualifying_suffix,"                                             \
-    "  s.reservations_in_subnet,"                                             \
-    "  s.reservations_out_of_pool,"                                           \
-    "  s.cache_threshold,"                                                    \
-    "  s.cache_max_age,"                                                      \
-    "  srv.tag "                                                              \
-    "FROM dhcp6_subnet AS s " server_join                                     \
-    "LEFT JOIN dhcp6_pool AS p ON s.subnet_id = p.subnet_id "                 \
-    "LEFT JOIN dhcp6_pd_pool AS d ON s.subnet_id = d.subnet_id "              \
-    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 5 AND p.id = x.pool_id "    \
-    "LEFT JOIN dhcp6_options AS y ON y.scope_id = 6 AND d.id = y.pd_pool_id " \
-    "LEFT JOIN dhcp6_options AS o ON o.scope_id = 1 AND s.subnet_id = "       \
-    "o.dhcp6_subnet_id " #__VA_ARGS__                                         \
-    " ORDER BY s.subnet_id, p.id, d.id, x.option_id, y.option_id, o.option_id"
-
-#define PGSQL_GET_SUBNET6_NO_TAG(...)                               \
-    PGSQL_GET_SUBNET6_COMMON("INNER JOIN dhcp6_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "      \
-                             "INNER JOIN dhcp6_server AS srv "      \
-                             "  ON (a.server_id = srv.id) ",        \
-                             __VA_ARGS__)
-
-#define PGSQL_GET_SUBNET6_ANY(...)                                 \
-    PGSQL_GET_SUBNET6_COMMON("LEFT JOIN dhcp6_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "     \
-                             "LEFT JOIN dhcp6_server AS srv "      \
-                             "  ON a.server_id = srv.id ",         \
-                             __VA_ARGS__)
-
-#define PGSQL_GET_SUBNET6_UNASSIGNED(...)                          \
-    PGSQL_GET_SUBNET6_COMMON("LEFT JOIN dhcp6_subnet_server AS a " \
-                             "  ON s.subnet_id = a.subnet_id "     \
-                             "LEFT JOIN dhcp6_server AS srv "      \
-                             "  ON a.server_id = srv.id ",         \
-                             WHERE a.subnet_id IS NULL __VA_ARGS__)
-
-#endif
-
-#ifndef PGSQL_GET_POOL4_COMMON
-#define PGSQL_GET_POOL4_COMMON(server_join, ...)                                        \
-    "SELECT"                                                                            \
-    "  p.id,"                                                                           \
-    "  p.start_address,"                                                                \
-    "  p.end_address,"                                                                  \
-    "  p.subnet_id,"                                                                    \
-    "  p.client_class,"                                                                 \
-    "  p.require_client_classes,"                                                       \
-    "  p.user_context,"                                                                 \
-    "  p.modification_ts,"                                                              \
-    "  x.option_id,"                                                                    \
-    "  x.code,"                                                                         \
-    "  x.value,"                                                                        \
-    "  x.formatted_value,"                                                              \
-    "  x.space,"                                                                        \
-    "  x.persistent,"                                                                   \
-    "  x.dhcp4_subnet_id,"                                                              \
-    "  x.scope_id,"                                                                     \
-    "  x.user_context,"                                                                 \
-    "  x.shared_network_name,"                                                          \
-    "  x.pool_id,"                                                                      \
-    "  x.modification_ts "                                                              \
-    "FROM dhcp4_pool AS p " server_join                                                 \
-    "LEFT JOIN dhcp4_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " #__VA_ARGS__ \
-    " ORDER BY p.id, x.option_id"
-
-#define PGSQL_GET_POOL4_RANGE_WITH_TAG(...)                                                    \
-    PGSQL_GET_POOL4_COMMON("INNER JOIN dhcp4_subnet_server AS s ON p.subnet_id = s.subnet_id " \
-                           "INNER JOIN dhcp4_server AS srv "                                   \
-                           " ON (s.server_id = srv.id) OR (s.server_id = 1) ",                 \
-                           __VA_ARGS__)
-
-#define PGSQL_GET_POOL4_RANGE_NO_TAG(...) PGSQL_GET_POOL4_COMMON("", __VA_ARGS__)
-#endif
-
-#ifndef PGSQL_GET_POOL6_COMMON
-#define PGSQL_GET_POOL6_COMMON(server_join, ...)                                        \
-    "SELECT"                                                                            \
-    "  p.id,"                                                                           \
-    "  p.start_address,"                                                                \
-    "  p.end_address,"                                                                  \
-    "  p.subnet_id,"                                                                    \
-    "  p.client_class,"                                                                 \
-    "  p.require_client_classes,"                                                       \
-    "  p.user_context,"                                                                 \
-    "  p.modification_ts,"                                                              \
-    "  x.option_id,"                                                                    \
-    "  x.code,"                                                                         \
-    "  x.value,"                                                                        \
-    "  x.formatted_value,"                                                              \
-    "  x.space,"                                                                        \
-    "  x.persistent,"                                                                   \
-    "  x.dhcp6_subnet_id,"                                                              \
-    "  x.scope_id,"                                                                     \
-    "  x.user_context,"                                                                 \
-    "  x.shared_network_name,"                                                          \
-    "  x.pool_id,"                                                                      \
-    "  x.modification_ts,"                                                              \
-    "  x.pd_pool_id "                                                                   \
-    "FROM dhcp6_pool AS p " server_join                                                 \
-    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " #__VA_ARGS__ \
-    " ORDER BY p.id, x.option_id"
-
-#define PGSQL_GET_POOL6_RANGE_WITH_TAG(...)                                                    \
-    PGSQL_GET_POOL6_COMMON("INNER JOIN dhcp6_subnet_server AS s ON p.subnet_id = s.subnet_id " \
-                           "INNER JOIN dhcp6_server AS srv "                                   \
-                           " ON (s.server_id = srv.id) OR (s.server_id = 1) ",                 \
-                           __VA_ARGS__)
-
-#define PGSQL_GET_POOL6_RANGE_NO_TAG(...) PGSQL_GET_POOL6_COMMON("", __VA_ARGS__)
-#endif
-
-#ifndef PGSQL_GET_PD_POOL_COMMON
-#define PGSQL_GET_PD_POOL_COMMON(server_join, ...)                                         \
-    "SELECT"                                                                               \
-    "  p.id,"                                                                              \
-    "  p.prefix,"                                                                          \
-    "  p.prefix_length,"                                                                   \
-    "  p.delegated_prefix_length,"                                                         \
-    "  p.subnet_id,"                                                                       \
-    "  p.excluded_prefix,"                                                                 \
-    "  p.excluded_prefix_length,"                                                          \
-    "  p.client_class,"                                                                    \
-    "  p.require_client_classes,"                                                          \
-    "  p.user_context,"                                                                    \
-    "  p.modification_ts,"                                                                 \
-    "  x.option_id,"                                                                       \
-    "  x.code,"                                                                            \
-    "  x.value,"                                                                           \
-    "  x.formatted_value,"                                                                 \
-    "  x.space,"                                                                           \
-    "  x.persistent,"                                                                      \
-    "  x.dhcp6_subnet_id,"                                                                 \
-    "  x.scope_id,"                                                                        \
-    "  x.user_context,"                                                                    \
-    "  x.shared_network_name,"                                                             \
-    "  x.pool_id,"                                                                         \
-    "  x.modification_ts,"                                                                 \
-    "  x.pd_pool_id "                                                                      \
-    "FROM dhcp6_pd_pool AS p " server_join                                                 \
-    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 6 AND p.id = x.pd_pool_id " #__VA_ARGS__ \
-    " ORDER BY p.id, x.option_id"
-
-#define PGSQL_GET_PD_POOL_WITH_TAG(...)                                                          \
-    PGSQL_GET_PD_POOL_COMMON("INNER JOIN dhcp6_subnet_server AS s ON p.subnet_id = s.subnet_id " \
-                             "INNER JOIN dhcp6_server AS srv "                                   \
-                             " ON (s.server_id = srv.id) OR (s.server_id = 1) ",                 \
-                             __VA_ARGS__)
-
-#define PGSQL_GET_PD_POOL_NO_TAG(...) PGSQL_GET_PD_POOL_COMMON("", __VA_ARGS__)
-#endif
-
-#ifndef PGSQL_GET_SHARED_NETWORK4
-#define PGSQL_GET_SHARED_NETWORK4_COMMON(server_join, ...)         \
-    "SELECT"                                                       \
-    "  n.id,"                                                      \
-    "  n.name,"                                                    \
-    "  n.client_class,"                                            \
-    "  n.interface,"                                               \
-    "  n.match_client_id,"                                         \
-    "  n.modification_ts,"                                         \
-    "  n.rebind_timer,"                                            \
-    "  n.relay,"                                                   \
-    "  n.renew_timer,"                                             \
-    "  n.require_client_classes,"                                  \
-    "  n.reservations_global,"                                     \
-    "  n.user_context,"                                            \
-    "  n.valid_lifetime,"                                          \
-    "  o.option_id,"                                               \
-    "  o.code,"                                                    \
-    "  o.value,"                                                   \
-    "  o.formatted_value,"                                         \
-    "  o.space,"                                                   \
-    "  o.persistent,"                                              \
-    "  o.dhcp4_subnet_id,"                                         \
-    "  o.scope_id,"                                                \
-    "  o.user_context,"                                            \
-    "  o.shared_network_name,"                                     \
-    "  o.pool_id,"                                                 \
-    "  o.modification_ts,"                                         \
-    "  n.calculate_tee_times,"                                     \
-    "  n.t1_percent,"                                              \
-    "  n.t2_percent,"                                              \
-    "  n.authoritative,"                                           \
-    "  n.boot_file_name,"                                          \
-    "  n.next_server,"                                             \
-    "  n.server_hostname,"                                         \
-    "  n.min_valid_lifetime,"                                      \
-    "  n.max_valid_lifetime,"                                      \
-    "  n.ddns_send_updates,"                                       \
-    "  n.ddns_override_no_update,"                                 \
-    "  n.ddns_override_client_update,"                             \
-    "  n.ddns_replace_client_name,"                                \
-    "  n.ddns_generated_prefix,"                                   \
-    "  n.ddns_qualifying_suffix,"                                  \
-    "  n.reservations_in_subnet,"                                  \
-    "  n.reservations_out_of_pool,"                                \
-    "  n.cache_threshold,"                                         \
-    "  n.cache_max_age,"                                           \
-    "  s.tag "                                                     \
-    "FROM dhcp4_shared_network AS n " server_join                  \
-    "LEFT JOIN dhcp4_options AS o ON o.scope_id = 4 AND n.name = " \
-    "o.shared_network_name " #__VA_ARGS__ " ORDER BY n.id, s.id, o.option_id"
-
-#define PGSQL_GET_SHARED_NETWORK4_NO_TAG(...)                                       \
-    PGSQL_GET_SHARED_NETWORK4_COMMON("INNER JOIN dhcp4_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "             \
-                                     "INNER JOIN dhcp4_server AS s "                \
-                                     "  ON (a.server_id = s.id) ",                  \
-                                     __VA_ARGS__)
-
-#define PGSQL_GET_SHARED_NETWORK4_ANY(...)                                         \
-    PGSQL_GET_SHARED_NETWORK4_COMMON("LEFT JOIN dhcp4_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "            \
-                                     "LEFT JOIN dhcp4_server AS s "                \
-                                     "  ON a.server_id = s.id ",                   \
-                                     __VA_ARGS__)
-
-#define PGSQL_GET_SHARED_NETWORK4_UNASSIGNED(...)                                  \
-    PGSQL_GET_SHARED_NETWORK4_COMMON("LEFT JOIN dhcp4_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "            \
-                                     "LEFT JOIN dhcp4_server AS s "                \
-                                     "  ON a.server_id = s.id ",                   \
-                                     WHERE a.shared_network_id IS NULL __VA_ARGS__)
-
-#endif
-
-#ifndef PGSQL_GET_SHARED_NETWORK6
-#define PGSQL_GET_SHARED_NETWORK6_COMMON(server_join, ...)         \
-    "SELECT"                                                       \
-    "  n.id,"                                                      \
-    "  n.name,"                                                    \
-    "  n.client_class,"                                            \
-    "  n.interface,"                                               \
-    "  n.modification_ts,"                                         \
-    "  n.preferred_lifetime,"                                      \
-    "  n.rapid_commit,"                                            \
-    "  n.rebind_timer,"                                            \
-    "  n.relay,"                                                   \
-    "  n.renew_timer,"                                             \
-    "  n.require_client_classes,"                                  \
-    "  n.reservations_global,"                                     \
-    "  n.user_context,"                                            \
-    "  n.valid_lifetime,"                                          \
-    "  o.option_id,"                                               \
-    "  o.code,"                                                    \
-    "  o.value,"                                                   \
-    "  o.formatted_value,"                                         \
-    "  o.space,"                                                   \
-    "  o.persistent,"                                              \
-    "  o.dhcp6_subnet_id,"                                         \
-    "  o.scope_id,"                                                \
-    "  o.user_context,"                                            \
-    "  o.shared_network_name,"                                     \
-    "  o.pool_id,"                                                 \
-    "  o.modification_ts,"                                         \
-    "  o.pd_pool_id, "                                             \
-    "  n.calculate_tee_times,"                                     \
-    "  n.t1_percent,"                                              \
-    "  n.t2_percent,"                                              \
-    "  n.interface_id,"                                            \
-    "  n.min_preferred_lifetime,"                                  \
-    "  n.max_preferred_lifetime,"                                  \
-    "  n.min_valid_lifetime,"                                      \
-    "  n.max_valid_lifetime,"                                      \
-    "  n.ddns_send_updates,"                                       \
-    "  n.ddns_override_no_update,"                                 \
-    "  n.ddns_override_client_update,"                             \
-    "  n.ddns_replace_client_name,"                                \
-    "  n.ddns_generated_prefix,"                                   \
-    "  n.ddns_qualifying_suffix,"                                  \
-    "  n.reservations_in_subnet,"                                  \
-    "  n.reservations_out_of_pool,"                                \
-    "  n.cache_threshold,"                                         \
-    "  n.cache_max_age,"                                           \
-    "  s.tag "                                                     \
-    "FROM dhcp6_shared_network AS n " server_join                  \
-    "LEFT JOIN dhcp6_options AS o ON o.scope_id = 4 AND n.name = " \
-    "o.shared_network_name " #__VA_ARGS__ " ORDER BY n.id, s.id, o.option_id"
-
-#define PGSQL_GET_SHARED_NETWORK6_NO_TAG(...)                                       \
-    PGSQL_GET_SHARED_NETWORK6_COMMON("INNER JOIN dhcp6_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "             \
-                                     "INNER JOIN dhcp6_server AS s "                \
-                                     "  ON (a.server_id = s.id) ",                  \
-                                     __VA_ARGS__)
-
-#define PGSQL_GET_SHARED_NETWORK6_ANY(...)                                         \
-    PGSQL_GET_SHARED_NETWORK6_COMMON("LEFT JOIN dhcp6_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "            \
-                                     "LEFT JOIN dhcp6_server AS s "                \
-                                     "  ON a.server_id = s.id ",                   \
-                                     __VA_ARGS__)
-
-#define PGSQL_GET_SHARED_NETWORK6_UNASSIGNED(...)                                  \
-    PGSQL_GET_SHARED_NETWORK6_COMMON("LEFT JOIN dhcp6_shared_network_server AS a " \
-                                     "  ON n.id = a.shared_network_id "            \
-                                     "LEFT JOIN dhcp6_server AS s "                \
-                                     "  ON a.server_id = s.id ",                   \
-                                     WHERE a.shared_network_id IS NULL __VA_ARGS__)
-
-#endif
-
-#ifndef PGSQL_GET_OPTION_DEF
-#define PGSQL_GET_OPTION_DEF(table_prefix, ...)           \
-    "SELECT"                                              \
-    "  d.id,"                                             \
-    "  d.code,"                                           \
-    "  d.name,"                                           \
-    "  d.space,"                                          \
-    "  d.type,"                                           \
-    "  d.modification_ts,"                                \
-    "  d.is_array,"                                       \
-    "  d.encapsulate,"                                    \
-    "  d.record_types,"                                   \
-    "  d.user_context,"                                   \
-    "  s.tag "                                            \
-    "FROM " #table_prefix "_option_def AS d "             \
-    "INNER JOIN " #table_prefix "_option_def_server AS a" \
-    "  ON d.id = a.option_def_id "                        \
-    "INNER JOIN " #table_prefix "_server AS s "           \
-    "  ON a.server_id = s.id "                            \
-    "WHERE (s.tag = ? OR s.id = 1) " #__VA_ARGS__ " ORDER BY d.id"
-#endif
-
-#ifndef PGSQL_GET_OPTION_COMMON
-#define PGSQL_GET_OPTION_COMMON(table_prefix, pd_pool_id, ...)   \
-    "SELECT"                                                     \
-    "  o.option_id,"                                             \
-    "  o.code,"                                                  \
-    "  o.value,"                                                 \
-    "  o.formatted_value,"                                       \
-    "  o.space,"                                                 \
-    "  o.persistent,"                                            \
-    "  o." #table_prefix "_subnet_id,"                           \
-    "  o.scope_id,"                                              \
-    "  o.user_context,"                                          \
-    "  o.shared_network_name,"                                   \
-    "  o.pool_id,"                                               \
-    "  o.modification_ts,"                                       \
-    "  s.tag " pd_pool_id "FROM " #table_prefix "_options AS o " \
-    "INNER JOIN " #table_prefix "_options_server AS a"           \
-    "  ON o.option_id = a.option_id "                            \
-    "INNER JOIN " #table_prefix "_server AS s"                   \
-    "  ON a.server_id = s.id "                                   \
-    "WHERE (s.tag = ? OR s.id = 1) " #__VA_ARGS__ " ORDER BY o.option_id, s.id"
-
-#define PGSQL_GET_OPTION4(...) PGSQL_GET_OPTION_COMMON(dhcp4, "", __VA_ARGS__)
-#define PGSQL_GET_OPTION6(...) PGSQL_GET_OPTION_COMMON(dhcp6, ", o.pd_pool_id ", __VA_ARGS__)
-#endif
-
-#ifndef PGSQL_GET_AUDIT_ENTRIES_TIME
-#define PGSQL_GET_AUDIT_ENTRIES_TIME(table_prefix)                            \
-    "SELECT"                                                                  \
-    "  a.id,"                                                                 \
-    "  a.object_type,"                                                        \
-    "  a.object_id,"                                                          \
-    "  a.modification_type,"                                                  \
-    "  r.modification_ts,"                                                    \
-    "  r.id, "                                                                \
-    "  r.log_message "                                                        \
-    "FROM " #table_prefix "_audit AS a "                                      \
-    "INNER JOIN " #table_prefix "_audit_revision AS r "                       \
-    "  ON a.revision_id = r.id "                                              \
-    "INNER JOIN " #table_prefix "_server AS s"                                \
-    "  ON r.server_id = s.id "                                                \
-    "WHERE (s.tag = ? OR s.id = 1) AND ((r.modification_ts, r.id)  > (?, ?))" \
-    " ORDER BY r.modification_ts, r.id"
-#endif
-
-#ifndef PGSQL_GET_SERVERS_COMMON
-#define PGSQL_GET_SERVERS_COMMON(table_prefix, ...) \
-    "SELECT"                                        \
-    "  s.id,"                                       \
-    "  s.tag,"                                      \
-    "  s.description,"                              \
-    "  s.modification_ts "                          \
-    "FROM " #table_prefix "_server AS s "           \
-    "WHERE s.id > 1 " __VA_ARGS__ "ORDER BY s.id"
-#define PGSQL_GET_ALL_SERVERS(table_prefix) PGSQL_GET_SERVERS_COMMON(table_prefix, "")
-#define PGSQL_GET_SERVER(table_prefix)      PGSQL_GET_SERVERS_COMMON(table_prefix, "AND s.tag = ? ")
-#endif
-
-#ifndef PGSQL_INSERT_GLOBAL_PARAMETER
-#define PGSQL_INSERT_GLOBAL_PARAMETER(table_prefix)   \
-    "INSERT INTO " #table_prefix "_global_parameter(" \
-    "  name,"                                         \
-    "  value,"                                        \
-    "  parameter_type,"                               \
-    "  modification_ts"                               \
-    ") VALUES (?, ?, ?, ?)"
-#endif
-
-#ifndef PGSQL_INSERT_GLOBAL_PARAMETER_SERVER
-#define PGSQL_INSERT_GLOBAL_PARAMETER_SERVER(table_prefix)   \
-    "INSERT INTO " #table_prefix "_global_parameter_server(" \
-    "  parameter_id,"                                        \
-    "  modification_ts,"                                     \
-    "  server_id"                                            \
-    ") VALUES (?, ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
-#endif
-
-#ifndef PGSQL_INSERT_SUBNET_SERVER
-#define PGSQL_INSERT_SUBNET_SERVER(table_prefix)   \
-    "INSERT INTO " #table_prefix "_subnet_server(" \
-    "  subnet_id,"                                 \
-    "  modification_ts,"                           \
-    "  server_id"                                  \
-    ") VALUES (?, ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
-#endif
-
-#ifndef PGSQL_INSERT_POOL
-#define PGSQL_INSERT_POOL(table_prefix)   \
-    "INSERT INTO " #table_prefix "_pool(" \
-    "  start_address,"                    \
-    "  end_address,"                      \
-    "  subnet_id,"                        \
-    "  client_class,"                     \
-    "  require_client_classes,"           \
-    "  user_context,"                     \
-    "  modification_ts"                   \
-    ") VALUES (?, ?, ?, ?, ?, ?, ?)"
-#endif
-
-#ifndef PGSQL_INSERT_PD_POOL
-#define PGSQL_INSERT_PD_POOL()   \
-    "INSERT INTO dhcp6_pd_pool(" \
-    "  prefix,"                  \
-    "  prefix_length,"           \
-    "  delegated_prefix_length," \
-    "  subnet_id,"               \
-    "  excluded_prefix,"         \
-    "  excluded_prefix_length,"  \
-    "  client_class,"            \
-    "  require_client_classes,"  \
-    "  user_context,"            \
-    "  modification_ts"          \
-    ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-#endif
-
-#ifndef PGSQL_INSERT_SHARED_NETWORK_SERVER
-#define PGSQL_INSERT_SHARED_NETWORK_SERVER(table_prefix)                       \
-    "INSERT INTO " #table_prefix "_shared_network_server("                     \
-    "  shared_network_id,"                                                     \
-    "  modification_ts,"                                                       \
-    "  server_id"                                                              \
-    ") VALUES ("                                                               \
-    "    (SELECT id FROM " #table_prefix "_shared_network WHERE name = ?), ?," \
-    "    (SELECT id FROM " #table_prefix "_server WHERE tag = ?)"              \
-    ")"
-#endif
-
-#ifndef PGSQL_INSERT_OPTION_DEF
-#define PGSQL_INSERT_OPTION_DEF(table_prefix)    \
-    "INSERT INTO " #table_prefix "_option_def (" \
-    "  code,"                                    \
-    "  name,"                                    \
-    "  space,"                                   \
-    "  type,"                                    \
-    "  modification_ts,"                         \
-    "  is_array,"                                \
-    "  encapsulate,"                             \
-    "  record_types,"                            \
-    "  user_context"                             \
-    ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
-#endif
-
-#ifndef PGSQL_INSERT_OPTION_DEF_SERVER
-#define PGSQL_INSERT_OPTION_DEF_SERVER(table_prefix)   \
-    "INSERT INTO " #table_prefix "_option_def_server(" \
-    "  option_def_id,"                                 \
-    "  modification_ts,"                               \
-    "  server_id"                                      \
-    ") VALUES (?, ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
-#endif
-
-#ifndef PGSQL_INSERT_OPTION_COMMON
-#define PGSQL_INSERT_OPTION_COMMON(table_prefix, pd_pool_id, last) \
-    "INSERT INTO " #table_prefix "_options ("                      \
-    "  code,"                                                      \
-    "  value,"                                                     \
-    "  formatted_value,"                                           \
-    "  space,"                                                     \
-    "  persistent,"                                                \
-    "  dhcp_client_class,"                                         \
-    " " #table_prefix "_subnet_id,"                                \
-    "  scope_id,"                                                  \
-    "  user_context,"                                              \
-    "  shared_network_name,"                                       \
-    "  pool_id,"                                                   \
-    "  modification_ts" pd_pool_id ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?" last ")"
-
-#define PGSQL_INSERT_OPTION4() PGSQL_INSERT_OPTION_COMMON(dhcp4, "", "")
-#define PGSQL_INSERT_OPTION6() PGSQL_INSERT_OPTION_COMMON(dhcp6, ", pd_pool_id ", ", ?")
-#endif
-
-#ifndef PGSQL_INSERT_OPTION_SERVER
-#define PGSQL_INSERT_OPTION_SERVER(table_prefix)     \
-    "INSERT INTO " #table_prefix "_options_server (" \
-    "  option_id,"                                   \
-    "  modification_ts,"                             \
-    "  server_id"                                    \
-    ") VALUES (?, ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
-#endif
-
-#ifndef PGSQL_INSERT_SERVER
-#define PGSQL_INSERT_SERVER(table_prefix)    \
-    "INSERT INTO " #table_prefix "_server (" \
-    "  tag,"                                 \
-    "  description,"                         \
-    "  modification_ts"                      \
-    ") VALUES (?, ?, ?)"
-#endif
-
-#ifndef PGSQL_UPDATE_GLOBAL_PARAMETER
-#define PGSQL_UPDATE_GLOBAL_PARAMETER(table_prefix)             \
-    "UPDATE " #table_prefix "_global_parameter AS g "           \
-    "INNER JOIN " #table_prefix "_global_parameter_server AS a" \
-    "  ON g.id = a.parameter_id "                               \
-    "INNER JOIN " #table_prefix "_server AS s"                  \
-    "  ON a.server_id = s.id "                                  \
-    "SET"                                                       \
-    "  g.name = ?,"                                             \
-    "  g.value = ?,"                                            \
-    "  g.parameter_type = ?,"                                   \
-    "  g.modification_ts = ? "                                  \
-    "WHERE s.tag = ? AND g.name = ?"
-#endif
-
-#ifndef PGSQL_UPDATE_OPTION_DEF
-#define PGSQL_UPDATE_OPTION_DEF(table_prefix)             \
-    "UPDATE " #table_prefix "_option_def AS d "           \
-    "INNER JOIN " #table_prefix "_option_def_server AS a" \
-    "  ON d.id = a.option_def_id "                        \
-    "INNER JOIN " #table_prefix "_server AS s"            \
-    "  ON a.server_id = s.id "                            \
-    "SET"                                                 \
-    "  d.code = ?,"                                       \
-    "  d.name = ?,"                                       \
-    "  d.space = ?,"                                      \
-    "  d.type = ?,"                                       \
-    "  d.modification_ts = ?,"                            \
-    "  d.is_array = ?,"                                   \
-    "  d.encapsulate = ?,"                                \
-    "  d.record_types = ?,"                               \
-    "  d.user_context = ? "                               \
-    "WHERE s.tag = ? AND d.code = ? AND d.space = ?"
-#endif
-
-#ifndef PGSQL_UPDATE_OPTION_COMMON
-#define PGSQL_UPDATE_OPTION_COMMON(table_prefix, server_join, pd_pool_id, ...) \
-    "UPDATE " #table_prefix "_options AS o " server_join "SET"                 \
-    "  o.code = ?,"                                                            \
-    "  o.value = ?,"                                                           \
-    "  o.formatted_value = ?,"                                                 \
-    "  o.space = ?,"                                                           \
-    "  o.persistent = ?,"                                                      \
-    "  o.dhcp_client_class = ?,"                                               \
-    "  o." #table_prefix "_subnet_id = ?,"                                     \
-    "  o.scope_id = ?,"                                                        \
-    "  o.user_context = ?,"                                                    \
-    "  o.shared_network_name = ?,"                                             \
-    "  o.pool_id = ?,"                                                         \
-    "  o.modification_ts = ? " pd_pool_id "WHERE " #__VA_ARGS__
-
-#define PGSQL_UPDATE_OPTION4_WITH_TAG(...) \
-    PGSQL_UPDATE_OPTION_COMMON(dhcp4, \
-    "INNER JOIN dhcp4_options_server AS a" \
-    "  ON o.option_id = a.option_id " \
-    "INNER JOIN dhcp4_server AS s" \
-    "  ON a.server_id = s.id ", \
-    "", s.tag = ? __VA_ARGS__)
-
-#define PGSQL_UPDATE_OPTION4_NO_TAG(...) PGSQL_UPDATE_OPTION_COMMON(dhcp4, "", "", __VA_ARGS__)
-
-#define PGSQL_UPDATE_OPTION6_WITH_TAG(...) \
-    PGSQL_UPDATE_OPTION_COMMON(dhcp6, \
-    "INNER JOIN dhcp6_options_server AS a" \
-    "  ON o.option_id = a.option_id " \
-    "INNER JOIN dhcp6_server AS s" \
-    "  ON a.server_id = s.id ", \
-    ", o.pd_pool_id = ? ", s.tag = ? __VA_ARGS__)
-
-#define PGSQL_UPDATE_OPTION6_NO_TAG(...) \
-    PGSQL_UPDATE_OPTION_COMMON(dhcp6, "", ", o.pd_pool_id = ? ", __VA_ARGS__)
-#endif
-
-#ifndef PGSQL_UPDATE_SERVER
-#define PGSQL_UPDATE_SERVER(table_prefix) \
-    "UPDATE " #table_prefix "_server "    \
-    "SET"                                 \
-    "  tag = ?,"                          \
-    "  description = ?,"                  \
-    "  modification_ts = ? "              \
-    "WHERE tag = ?"
-#endif
-
-#ifndef PGSQL_DELETE_GLOBAL_PARAMETER
-#define PGSQL_DELETE_GLOBAL_PARAMETER(table_prefix, ...)         \
-    "DELETE g FROM " #table_prefix "_global_parameter AS g "     \
-    "INNER JOIN " #table_prefix "_global_parameter_server AS a " \
-    "  ON g.id = a.parameter_id "                                \
-    "INNER JOIN " #table_prefix "_server AS s"                   \
-    "  ON (a.server_id = s.id) "                                 \
-    "WHERE s.tag = ? " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_GLOBAL_PARAMETER_UNASSIGNED
-#define PGSQL_DELETE_GLOBAL_PARAMETER_UNASSIGNED(table_prefix, ...) \
-    "DELETE g FROM " #table_prefix "_global_parameter AS g "        \
-    "LEFT JOIN " #table_prefix "_global_parameter_server AS a "     \
-    "  ON g.id = a.parameter_id "                                   \
-    "WHERE a.parameter_id IS NULL " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_SUBNET
-#define PGSQL_DELETE_SUBNET_COMMON(table_prefix, ...)  \
-    "DELETE s FROM " #table_prefix "_subnet AS s "     \
-    "INNER JOIN " #table_prefix "_subnet_server AS a " \
-    "  ON s.subnet_id = a.subnet_id "                  \
-    "INNER JOIN " #table_prefix "_server AS srv"       \
-    "  ON a.server_id = srv.id " #__VA_ARGS__
-
-#define PGSQL_DELETE_SUBNET_WITH_TAG(table_prefix, ...) \
-    PGSQL_DELETE_SUBNET_COMMON(table_prefix, WHERE srv.tag = ? __VA_ARGS__)
-
-#define PGSQL_DELETE_SUBNET_ANY(table_prefix, ...) \
-    "DELETE s FROM " #table_prefix "_subnet AS s " #__VA_ARGS__
-
-#define PGSQL_DELETE_SUBNET_UNASSIGNED(table_prefix, ...) \
-    "DELETE s FROM " #table_prefix "_subnet AS s "        \
-    "LEFT JOIN " #table_prefix "_subnet_server AS a"      \
-    "  ON s.subnet_id = a.subnet_id "                     \
-    "WHERE a.subnet_id IS NULL " #__VA_ARGS__
-
-#endif
-
-#ifndef PGSQL_DELETE_SUBNET_SERVER
-#define PGSQL_DELETE_SUBNET_SERVER(table_prefix)   \
-    "DELETE FROM " #table_prefix "_subnet_server " \
-    "WHERE subnet_id = ?"
-#endif
-
-#ifndef PGSQL_DELETE_POOLS
-#define PGSQL_DELETE_POOLS(table_prefix)              \
-    "DELETE FROM " #table_prefix "_pool "             \
-    "WHERE subnet_id = ? OR subnet_id = "             \
-    "(SELECT subnet_id FROM " #table_prefix "_subnet" \
-    "    WHERE subnet_prefix = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_PD_POOLS
-#define PGSQL_DELETE_PD_POOLS()           \
-    "DELETE FROM dhcp6_pd_pool "          \
-    "WHERE subnet_id = ? OR subnet_id = " \
-    "(SELECT subnet_id FROM dhcp6_subnet" \
-    "    WHERE subnet_prefix = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_SHARED_NETWORK_COMMON
-#define PGSQL_DELETE_SHARED_NETWORK_COMMON(table_prefix, ...) \
-    "DELETE n FROM " #table_prefix "_shared_network AS n "    \
-    "INNER JOIN " #table_prefix "_shared_network_server AS a" \
-    "  ON n.id = a.shared_network_id "                        \
-    "INNER JOIN " #table_prefix "_server AS s"                \
-    "  ON a.server_id = s.id " #__VA_ARGS__
-
-#define PGSQL_DELETE_SHARED_NETWORK_WITH_TAG(table_prefix, ...) \
-    PGSQL_DELETE_SHARED_NETWORK_COMMON(table_prefix, WHERE s.tag = ? __VA_ARGS__)
-
-#define PGSQL_DELETE_SHARED_NETWORK_ANY(table_prefix, ...) \
-    "DELETE n FROM " #table_prefix "_shared_network AS n " #__VA_ARGS__
-
-#define PGSQL_DELETE_SHARED_NETWORK_UNASSIGNED(table_prefix, ...) \
-    "DELETE n FROM " #table_prefix "_shared_network AS n "        \
-    "LEFT JOIN " #table_prefix "_shared_network_server AS a"      \
-    "  ON n.id = a.shared_network_id "                            \
-    "WHERE a.shared_network_id IS NULL " #__VA_ARGS__
-
-#endif
-
-#ifndef PGSQL_DELETE_SHARED_NETWORK_SERVER
-#define PGSQL_DELETE_SHARED_NETWORK_SERVER(table_prefix)   \
-    "DELETE FROM " #table_prefix "_shared_network_server " \
-    "WHERE shared_network_id = "                           \
-    "(SELECT id FROM " #table_prefix "_shared_network WHERE name = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_DEF
-#define PGSQL_DELETE_OPTION_DEF(table_prefix, ...)        \
-    "DELETE d FROM " #table_prefix "_option_def AS d "    \
-    "INNER JOIN " #table_prefix "_option_def_server AS a" \
-    "  ON d.id = a.option_def_id "                        \
-    "INNER JOIN " #table_prefix "_server AS s"            \
-    "  ON a.server_id = s.id "                            \
-    "WHERE s.tag = ? " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_DEF_UNASSIGNED
-#define PGSQL_DELETE_OPTION_DEF_UNASSIGNED(table_prefix, ...) \
-    "DELETE d FROM " #table_prefix "_option_def AS d "        \
-    "LEFT JOIN " #table_prefix "_option_def_server AS a "     \
-    "  ON d.id = a.option_def_id "                            \
-    "WHERE a.option_def_id IS NULL " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_WITH_TAG
-#define PGSQL_DELETE_OPTION_WITH_TAG(table_prefix, ...) \
-    "DELETE o FROM " #table_prefix "_options AS o "     \
-    "INNER JOIN " #table_prefix "_options_server AS a"  \
-    "  ON o.option_id = a.option_id "                   \
-    "INNER JOIN " #table_prefix "_server AS s"          \
-    "  ON a.server_id = s.id "                          \
-    "WHERE s.tag = ? " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_NO_TAG
-#define PGSQL_DELETE_OPTION_NO_TAG(table_prefix, ...) \
-    "DELETE o FROM " #table_prefix "_options AS o " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_SUBNET_ID_PREFIX
-#define PGSQL_DELETE_OPTION_SUBNET_ID_PREFIX(table_prefix) \
-    "DELETE o FROM " #table_prefix "_options AS o "        \
-    "INNER JOIN " #table_prefix "_subnet AS s "            \
-    "  ON s.subnet_id = o." #table_prefix "_subnet_id "    \
-    "WHERE o.scope_id = 1 AND (s.subnet_id = ? OR s.subnet_prefix = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_UNASSIGNED
-#define PGSQL_DELETE_OPTION_UNASSIGNED(table_prefix, ...) \
-    "DELETE o FROM " #table_prefix "_options AS o "       \
-    "LEFT JOIN " #table_prefix "_options_server AS a "    \
-    "  ON o.option_id = a.option_id "                     \
-    "WHERE a.option_id IS NULL " #__VA_ARGS__
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_POOL_RANGE
-#define PGSQL_DELETE_OPTION_POOL_RANGE(table_prefix, ...) \
-    "DELETE o FROM " #table_prefix "_options AS o "       \
-    "WHERE " #__VA_ARGS__ "  AND o.pool_id = "            \
-    "  (SELECT id FROM " #table_prefix "_pool"            \
-    "   WHERE start_address = ? AND end_address = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_OPTION_PD_POOL
-#define PGSQL_DELETE_OPTION_PD_POOL(...)          \
-    "DELETE o FROM dhcp6_options AS o "           \
-    "WHERE " #__VA_ARGS__ "  AND o.pd_pool_id = " \
-    "  (SELECT id FROM dhcp6_pd_pool"             \
-    "   WHERE prefix = ? AND prefix_length = ?)"
-#endif
-
-#ifndef PGSQL_DELETE_SERVER
-#define PGSQL_DELETE_SERVER(table_prefix)   \
-    "DELETE FROM " #table_prefix "_server " \
-    "WHERE tag = ?"
-#endif
-
-#ifndef PGSQL_DELETE_ALL_SERVERS
-#define PGSQL_DELETE_ALL_SERVERS(table_prefix) \
-    "DELETE FROM " #table_prefix "_server "    \
-    "WHERE id > 1"
-#endif
-
-}  // end of anonymous namespace
-
-}  // namespace dhcp
-}  // end of namespace isc
-
-#endif
diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_query_macros_dhcp.h b/src/hooks/dhcp/pgsql_cb/pgsql_query_macros_dhcp.h
new file mode 100644 (file)
index 0000000..e59fc8e
--- /dev/null
@@ -0,0 +1,1369 @@
+// Copyright (C) 2021-2022 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_QUERY_MACROS_DHCP_H
+#define PGSQL_QUERY_MACROS_DHCP_H
+
+/// @file pgsql_query_macros_dhcp.h
+/// Collection of common macros defining PgSQL prepared statements used
+/// to manage Kea DHCP configuration in the database.
+///
+/// Some of the macros are DHCPv4 specific, other are DHCPv6 specific.
+/// Some macros are common for both DHCP server types. The first
+/// parameter @c table_prefix should be set to @c dhcp4 or @c dhcp6,
+/// depending which DHCP server type it relates to. Provided value
+/// is used as a prefix for PgSQL table names. For example, if the
+/// prefix is set to @c dhcp4, the table name referred to in the
+/// query may be dhcp4_subnet etc. The second argument in the variadic
+/// macro is a part of the WHERE clause in the PgSQL query. The fixed
+/// part of the WHERE clause is included in the macro.
+
+/// @todo Update queries to also fetch server tags to associate
+/// returned configuration elements with particular servers.
+
+namespace isc {
+namespace dhcp {
+
+namespace {
+
+#ifndef PGSQL_GET_GLOBAL_PARAMETER
+#define PGSQL_GET_GLOBAL_PARAMETER(table_prefix, ...) \
+    "SELECT" \
+    "  g.id," \
+    "  g.name," \
+    "  g.value," \
+    "  g.parameter_type," \
+    "  extract(epoch from g.modification_ts)::bigint as modification_ts, " \
+    "  s.tag " \
+    "FROM " #table_prefix "_global_parameter AS g " \
+    "INNER JOIN " #table_prefix "_global_parameter_server AS a " \
+    "  ON g.id = a.parameter_id " \
+    "INNER JOIN " #table_prefix "_server AS s " \
+    "  ON (a.server_id = s.id) " \
+    "WHERE (s.tag = $1 OR s.id = 1) " #__VA_ARGS__ \
+    " ORDER BY g.id, s.id"
+
+#endif
+
+#ifndef PGSQL_GET_SUBNET4
+#define PGSQL_GET_SUBNET4_COMMON(server_join, ...) \
+    "SELECT" \
+    "  s.subnet_id," \
+    "  s.subnet_prefix," \
+    "  s.interface_4o6," \
+    "  s.interface_id_4o6," \
+    "  s.subnet_4o6," \
+    "  s.boot_file_name," \
+    "  s.client_class," \
+    "  s.interface," \
+    "  s.match_client_id," \
+    "  extract(epoch from s.modification_ts)::bigint as modification_ts, " \
+    "  s.next_server," \
+    "  s.rebind_timer," \
+    "  s.relay," \
+    "  s.renew_timer," \
+    "  s.require_client_classes," \
+    "  s.reservations_global," \
+    "  s.server_hostname," \
+    "  s.shared_network_name," \
+    "  s.user_context," \
+    "  s.valid_lifetime," \
+    "  p.id," \
+    "  p.start_address," \
+    "  p.end_address," \
+    "  p.subnet_id," \
+    "  extract(epoch from p.modification_ts)::bigint as modification_ts, " \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp4_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  o.option_id," \
+    "  o.code," \
+    "  o.value," \
+    "  o.formatted_value," \
+    "  o.space," \
+    "  o.persistent," \
+    "  o.dhcp4_subnet_id," \
+    "  o.scope_id," \
+    "  o.user_context," \
+    "  o.shared_network_name," \
+    "  o.pool_id," \
+    "  extract(epoch from o.modification_ts)::bigint as modification_ts, " \
+    "  s.calculate_tee_times," \
+    "  s.t1_percent," \
+    "  s.t2_percent," \
+    "  s.authoritative," \
+    "  s.min_valid_lifetime," \
+    "  s.max_valid_lifetime," \
+    "  p.client_class," \
+    "  p.require_client_classes," \
+    "  p.user_context," \
+    "  s.ddns_send_updates," \
+    "  s.ddns_override_no_update," \
+    "  s.ddns_override_client_update," \
+    "  s.ddns_replace_client_name," \
+    "  s.ddns_generated_prefix," \
+    "  s.ddns_qualifying_suffix," \
+    "  s.reservations_in_subnet," \
+    "  s.reservations_out_of_pool," \
+    "  s.cache_threshold," \
+    "  s.cache_max_age," \
+    "  srv.tag " \
+    "FROM dhcp4_subnet AS s " \
+    server_join \
+    "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id " \
+    "LEFT JOIN dhcp4_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " \
+    "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp4_subnet_id " \
+    #__VA_ARGS__ \
+    " ORDER BY s.subnet_id, p.id, x.option_id, o.option_id"
+
+#define PGSQL_GET_SUBNET4_NO_TAG(...) \
+    PGSQL_GET_SUBNET4_COMMON( \
+    "INNER JOIN dhcp4_subnet_server AS a " \
+    "  ON s.subnet_id = a.subnet_id " \
+    "INNER JOIN dhcp4_server AS srv " \
+    "  ON (a.server_id = srv.id) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SUBNET4_ANY(...) \
+    PGSQL_GET_SUBNET4_COMMON( \
+    "LEFT JOIN dhcp4_subnet_server AS a "\
+    "  ON s.subnet_id = a.subnet_id " \
+    "LEFT JOIN dhcp4_server AS srv " \
+    "  ON a.server_id = srv.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SUBNET4_UNASSIGNED(...) \
+    PGSQL_GET_SUBNET4_COMMON( \
+    "LEFT JOIN dhcp4_subnet_server AS a "\
+    "  ON s.subnet_id = a.subnet_id " \
+    "LEFT JOIN dhcp4_server AS srv " \
+    "  ON a.server_id = srv.id ", \
+    WHERE a.subnet_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_GET_SUBNET6
+#define PGSQL_GET_SUBNET6_COMMON(server_join, ...) \
+    "SELECT" \
+    "  s.subnet_id," \
+    "  s.subnet_prefix," \
+    "  s.client_class," \
+    "  s.interface," \
+    "  extract(epoch from s.modification_ts)::bigint as modification_ts, " \
+    "  s.preferred_lifetime," \
+    "  s.rapid_commit," \
+    "  s.rebind_timer," \
+    "  s.relay," \
+    "  s.renew_timer," \
+    "  s.require_client_classes," \
+    "  s.reservations_global," \
+    "  s.shared_network_name," \
+    "  s.user_context," \
+    "  s.valid_lifetime," \
+    "  p.id," \
+    "  p.start_address," \
+    "  p.end_address," \
+    "  p.subnet_id," \
+    "  extract(epoch from p.modification_ts)::bigint as modification_ts, " \
+    "  d.id," \
+    "  d.prefix," \
+    "  d.prefix_length," \
+    "  d.delegated_prefix_length," \
+    "  d.subnet_id," \
+    "  extract(epoch from d.modification_ts)::bigint as modification_ts, " \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp6_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  x.pd_pool_id," \
+    "  y.option_id," \
+    "  y.code," \
+    "  y.value," \
+    "  y.formatted_value," \
+    "  y.space," \
+    "  y.persistent," \
+    "  y.dhcp6_subnet_id," \
+    "  y.scope_id," \
+    "  y.user_context," \
+    "  y.shared_network_name," \
+    "  y.pool_id," \
+    "  extract(epoch from y.modification_ts)::bigint as modification_ts, " \
+    "  y.pd_pool_id," \
+    "  o.option_id," \
+    "  o.code," \
+    "  o.value," \
+    "  o.formatted_value," \
+    "  o.space," \
+    "  o.persistent," \
+    "  o.dhcp6_subnet_id," \
+    "  o.scope_id," \
+    "  o.user_context," \
+    "  o.shared_network_name," \
+    "  o.pool_id," \
+    "  extract(epoch from o.modification_ts)::bigint as modification_ts, " \
+    "  o.pd_pool_id, " \
+    "  s.calculate_tee_times," \
+    "  s.t1_percent," \
+    "  s.t2_percent," \
+    "  s.interface_id," \
+    "  s.min_preferred_lifetime," \
+    "  s.max_preferred_lifetime," \
+    "  s.min_valid_lifetime," \
+    "  s.max_valid_lifetime," \
+    "  p.client_class," \
+    "  p.require_client_classes," \
+    "  p.user_context," \
+    "  d.excluded_prefix," \
+    "  d.excluded_prefix_length," \
+    "  d.client_class," \
+    "  d.require_client_classes," \
+    "  d.user_context," \
+    "  s.ddns_send_updates," \
+    "  s.ddns_override_no_update," \
+    "  s.ddns_override_client_update," \
+    "  s.ddns_replace_client_name," \
+    "  s.ddns_generated_prefix," \
+    "  s.ddns_qualifying_suffix," \
+    "  s.reservations_in_subnet," \
+    "  s.reservations_out_of_pool," \
+    "  s.cache_threshold," \
+    "  s.cache_max_age," \
+    "  srv.tag " \
+    "FROM dhcp6_subnet AS s " \
+    server_join \
+    "LEFT JOIN dhcp6_pool AS p ON s.subnet_id = p.subnet_id " \
+    "LEFT JOIN dhcp6_pd_pool AS d ON s.subnet_id = d.subnet_id " \
+    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " \
+    "LEFT JOIN dhcp6_options AS y ON y.scope_id = 6 AND d.id = y.pd_pool_id " \
+    "LEFT JOIN dhcp6_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp6_subnet_id " \
+    #__VA_ARGS__                                                        \
+    " ORDER BY s.subnet_id, p.id, d.id, x.option_id, y.option_id, o.option_id"
+
+#define PGSQL_GET_SUBNET6_NO_TAG(...) \
+    PGSQL_GET_SUBNET6_COMMON( \
+    "INNER JOIN dhcp6_subnet_server AS a " \
+    "  ON s.subnet_id = a.subnet_id " \
+    "INNER JOIN dhcp6_server AS srv " \
+    "  ON (a.server_id = srv.id) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SUBNET6_ANY(...) \
+    PGSQL_GET_SUBNET6_COMMON( \
+    "LEFT JOIN dhcp6_subnet_server AS a "\
+    "  ON s.subnet_id = a.subnet_id " \
+    "LEFT JOIN dhcp6_server AS srv " \
+    "  ON a.server_id = srv.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SUBNET6_UNASSIGNED(...) \
+    PGSQL_GET_SUBNET6_COMMON( \
+    "LEFT JOIN dhcp6_subnet_server AS a "\
+    "  ON s.subnet_id = a.subnet_id " \
+    "LEFT JOIN dhcp6_server AS srv " \
+    "  ON a.server_id = srv.id ", \
+    WHERE a.subnet_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_GET_POOL4_COMMON
+#define PGSQL_GET_POOL4_COMMON(server_join, ...) \
+      "SELECT" \
+      "  p.id," \
+      "  p.start_address," \
+      "  p.end_address," \
+      "  p.subnet_id," \
+      "  p.client_class," \
+      "  p.require_client_classes," \
+      "  p.user_context," \
+      "  extract(epoch from p.modification_ts)::bigint as modification_ts, " \
+      "  x.option_id," \
+      "  x.code," \
+      "  x.value," \
+      "  x.formatted_value," \
+      "  x.space," \
+      "  x.persistent," \
+      "  x.dhcp4_subnet_id," \
+      "  x.scope_id," \
+      "  x.user_context," \
+      "  x.shared_network_name," \
+      "  x.pool_id," \
+      "  extract(epoch from x.modification_ts)::bigint as modification_ts " \
+      "FROM dhcp4_pool AS p " \
+      server_join \
+      "LEFT JOIN dhcp4_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " \
+      #__VA_ARGS__ \
+      " ORDER BY p.id, x.option_id"
+
+#define PGSQL_GET_POOL4_RANGE_WITH_TAG(...) \
+    PGSQL_GET_POOL4_COMMON( \
+       "INNER JOIN dhcp4_subnet_server AS s ON p.subnet_id = s.subnet_id " \
+       "INNER JOIN dhcp4_server AS srv " \
+       " ON (s.server_id = srv.id) OR (s.server_id = 1) ", \
+       __VA_ARGS__)
+
+#define PGSQL_GET_POOL4_RANGE_NO_TAG(...) \
+    PGSQL_GET_POOL4_COMMON("", __VA_ARGS__)
+#endif
+
+#ifndef PGSQL_GET_POOL6_COMMON
+#define PGSQL_GET_POOL6_COMMON(server_join, ...) \
+    "SELECT" \
+    "  p.id," \
+    "  p.start_address," \
+    "  p.end_address," \
+    "  p.subnet_id," \
+    "  p.client_class," \
+    "  p.require_client_classes," \
+    "  p.user_context," \
+    "  extract(epoch from p.modification_ts)::bigint as modification_ts, " \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp6_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  x.pd_pool_id " \
+    "FROM dhcp6_pool AS p " \
+    server_join \
+    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " \
+    #__VA_ARGS__ \
+    " ORDER BY p.id, x.option_id"
+
+#define PGSQL_GET_POOL6_RANGE_WITH_TAG(...) \
+    PGSQL_GET_POOL6_COMMON( \
+    "INNER JOIN dhcp6_subnet_server AS s ON p.subnet_id = s.subnet_id " \
+    "INNER JOIN dhcp6_server AS srv " \
+    " ON (s.server_id = srv.id) OR (s.server_id = 1) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_POOL6_RANGE_NO_TAG(...) \
+    PGSQL_GET_POOL6_COMMON("", __VA_ARGS__)
+#endif
+
+#ifndef PGSQL_GET_PD_POOL_COMMON
+#define PGSQL_GET_PD_POOL_COMMON(server_join, ...) \
+    "SELECT" \
+    "  p.id," \
+    "  p.prefix," \
+    "  p.prefix_length," \
+    "  p.delegated_prefix_length," \
+    "  p.subnet_id," \
+    "  p.excluded_prefix," \
+    "  p.excluded_prefix_length," \
+    "  p.client_class," \
+    "  p.require_client_classes," \
+    "  p.user_context," \
+    "  extract(epoch from p.modification_ts)::bigint as modification_ts, " \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp6_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  x.pd_pool_id " \
+    "FROM dhcp6_pd_pool AS p " \
+    server_join \
+    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 6 AND p.id = x.pd_pool_id " \
+    #__VA_ARGS__ \
+    " ORDER BY p.id, x.option_id" \
+
+#define PGSQL_GET_PD_POOL_WITH_TAG(...) \
+    PGSQL_GET_PD_POOL_COMMON( \
+    "INNER JOIN dhcp6_subnet_server AS s ON p.subnet_id = s.subnet_id " \
+    "INNER JOIN dhcp6_server AS srv " \
+    " ON (s.server_id = srv.id) OR (s.server_id = 1) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_PD_POOL_NO_TAG(...) \
+    PGSQL_GET_PD_POOL_COMMON("", __VA_ARGS__)
+#endif
+
+#ifndef PGSQL_GET_SHARED_NETWORK4
+#define PGSQL_GET_SHARED_NETWORK4_COMMON(server_join, ...) \
+    "SELECT" \
+    "  n.id," \
+    "  n.name," \
+    "  n.client_class," \
+    "  n.interface," \
+    "  n.match_client_id," \
+    "  extract(epoch from n.modification_ts)::bigint as modification_ts, " \
+    "  n.rebind_timer," \
+    "  n.relay," \
+    "  n.renew_timer," \
+    "  n.require_client_classes," \
+    "  n.reservations_global," \
+    "  n.user_context," \
+    "  n.valid_lifetime," \
+    "  o.option_id," \
+    "  o.code," \
+    "  o.value," \
+    "  o.formatted_value," \
+    "  o.space," \
+    "  o.persistent," \
+    "  o.dhcp4_subnet_id," \
+    "  o.scope_id," \
+    "  o.user_context," \
+    "  o.shared_network_name," \
+    "  o.pool_id," \
+    "  extract(epoch from o.modification_ts)::bigint as modification_ts, " \
+    "  n.calculate_tee_times," \
+    "  n.t1_percent," \
+    "  n.t2_percent," \
+    "  n.authoritative," \
+    "  n.boot_file_name," \
+    "  n.next_server," \
+    "  n.server_hostname," \
+    "  n.min_valid_lifetime," \
+    "  n.max_valid_lifetime," \
+    "  n.ddns_send_updates," \
+    "  n.ddns_override_no_update," \
+    "  n.ddns_override_client_update," \
+    "  n.ddns_replace_client_name," \
+    "  n.ddns_generated_prefix," \
+    "  n.ddns_qualifying_suffix," \
+    "  n.reservations_in_subnet," \
+    "  n.reservations_out_of_pool," \
+    "  n.cache_threshold," \
+    "  n.cache_max_age," \
+    "  s.tag " \
+    "FROM dhcp4_shared_network AS n " \
+    server_join \
+    "LEFT JOIN dhcp4_options AS o ON o.scope_id = 4 AND n.name = o.shared_network_name " \
+    #__VA_ARGS__ \
+    " ORDER BY n.id, s.id, o.option_id"
+
+#define PGSQL_GET_SHARED_NETWORK4_NO_TAG(...) \
+    PGSQL_GET_SHARED_NETWORK4_COMMON( \
+    "INNER JOIN dhcp4_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "INNER JOIN dhcp4_server AS s " \
+    "  ON (a.server_id = s.id) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SHARED_NETWORK4_ANY(...) \
+    PGSQL_GET_SHARED_NETWORK4_COMMON( \
+    "LEFT JOIN dhcp4_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "LEFT JOIN dhcp4_server AS s " \
+    "  ON a.server_id = s.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SHARED_NETWORK4_UNASSIGNED(...) \
+    PGSQL_GET_SHARED_NETWORK4_COMMON( \
+    "LEFT JOIN dhcp4_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "LEFT JOIN dhcp4_server AS s " \
+    "  ON a.server_id = s.id ", \
+    WHERE a.shared_network_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_GET_SHARED_NETWORK6
+#define PGSQL_GET_SHARED_NETWORK6_COMMON(server_join, ...) \
+    "SELECT" \
+    "  n.id," \
+    "  n.name," \
+    "  n.client_class," \
+    "  n.interface," \
+    "  extract(epoch from n.modification_ts)::bigint as modification_ts, " \
+    "  n.preferred_lifetime," \
+    "  n.rapid_commit," \
+    "  n.rebind_timer," \
+    "  n.relay," \
+    "  n.renew_timer," \
+    "  n.require_client_classes," \
+    "  n.reservations_global," \
+    "  n.user_context," \
+    "  n.valid_lifetime," \
+    "  o.option_id," \
+    "  o.code," \
+    "  o.value," \
+    "  o.formatted_value," \
+    "  o.space," \
+    "  o.persistent," \
+    "  o.dhcp6_subnet_id," \
+    "  o.scope_id," \
+    "  o.user_context," \
+    "  o.shared_network_name," \
+    "  o.pool_id," \
+    "  extract(epoch from o.modification_ts)::bigint as modification_ts, " \
+    "  o.pd_pool_id, " \
+    "  n.calculate_tee_times," \
+    "  n.t1_percent," \
+    "  n.t2_percent," \
+    "  n.interface_id," \
+    "  n.min_preferred_lifetime," \
+    "  n.max_preferred_lifetime," \
+    "  n.min_valid_lifetime," \
+    "  n.max_valid_lifetime," \
+    "  n.ddns_send_updates," \
+    "  n.ddns_override_no_update," \
+    "  n.ddns_override_client_update," \
+    "  n.ddns_replace_client_name," \
+    "  n.ddns_generated_prefix," \
+    "  n.ddns_qualifying_suffix," \
+    "  n.reservations_in_subnet," \
+    "  n.reservations_out_of_pool," \
+    "  n.cache_threshold," \
+    "  n.cache_max_age," \
+    "  s.tag " \
+    "FROM dhcp6_shared_network AS n " \
+    server_join \
+    "LEFT JOIN dhcp6_options AS o ON o.scope_id = 4 AND n.name = o.shared_network_name " \
+    #__VA_ARGS__ \
+    " ORDER BY n.id, s.id, o.option_id"
+
+#define PGSQL_GET_SHARED_NETWORK6_NO_TAG(...) \
+    PGSQL_GET_SHARED_NETWORK6_COMMON( \
+    "INNER JOIN dhcp6_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "INNER JOIN dhcp6_server AS s " \
+    "  ON (a.server_id = s.id) ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SHARED_NETWORK6_ANY(...) \
+    PGSQL_GET_SHARED_NETWORK6_COMMON( \
+    "LEFT JOIN dhcp6_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "LEFT JOIN dhcp6_server AS s " \
+    "  ON a.server_id = s.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_SHARED_NETWORK6_UNASSIGNED(...) \
+    PGSQL_GET_SHARED_NETWORK6_COMMON( \
+    "LEFT JOIN dhcp6_shared_network_server AS a " \
+    "  ON n.id = a.shared_network_id " \
+    "LEFT JOIN dhcp6_server AS s " \
+    "  ON a.server_id = s.id ", \
+    WHERE a.shared_network_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_GET_OPTION_DEF
+#define PGSQL_GET_OPTION_DEF(table_prefix, ...) \
+    "SELECT" \
+    "  d.id," \
+    "  d.code," \
+    "  d.name," \
+    "  d.space," \
+    "  d.type," \
+    "  extract(epoch from d.modification_ts)::bigint as modification_ts, " \
+    "  d.is_array," \
+    "  d.encapsulate," \
+    "  d.record_types," \
+    "  d.user_context," \
+    "  s.tag " \
+    "FROM " #table_prefix "_option_def AS d " \
+    "INNER JOIN " #table_prefix "_option_def_server AS a" \
+    "  ON d.id = a.option_def_id " \
+    "INNER JOIN " #table_prefix "_server AS s " \
+    "  ON a.server_id = s.id " \
+    "WHERE (s.tag = $1 OR s.id = 1) " #__VA_ARGS__ \
+    " ORDER BY d.id"
+#endif
+
+#ifndef PGSQL_GET_OPTION_COMMON
+#define PGSQL_GET_OPTION_COMMON(table_prefix, pd_pool_id, ...) \
+    "SELECT" \
+    "  o.option_id," \
+    "  o.code," \
+    "  o.value," \
+    "  o.formatted_value," \
+    "  o.space," \
+    "  o.persistent," \
+    "  o." #table_prefix "_subnet_id," \
+    "  o.scope_id," \
+    "  o.user_context," \
+    "  o.shared_network_name," \
+    "  o.pool_id," \
+    "  extract(epoch from o.modification_ts)::bigint as modification_ts, " \
+    "  s.tag " \
+    pd_pool_id \
+    "FROM " #table_prefix "_options AS o " \
+    "INNER JOIN " #table_prefix "_options_server AS a" \
+    "  ON o.option_id = a.option_id " \
+    "INNER JOIN " #table_prefix "_server AS s" \
+    "  ON a.server_id = s.id " \
+    "WHERE (s.tag = $1 OR s.id = 1) " #__VA_ARGS__ \
+    " ORDER BY o.option_id, s.id"
+
+#define PGSQL_GET_OPTION4(...) \
+    PGSQL_GET_OPTION_COMMON(dhcp4, "", __VA_ARGS__)
+#define PGSQL_GET_OPTION6(...) \
+    PGSQL_GET_OPTION_COMMON(dhcp6, ", o.pd_pool_id ", __VA_ARGS__)
+#endif
+
+#ifndef PGSQL_GET_AUDIT_ENTRIES_TIME
+#define PGSQL_GET_AUDIT_ENTRIES_TIME(table_prefix) \
+    "SELECT" \
+    "  a.id," \
+    "  a.object_type," \
+    "  a.object_id," \
+    "  a.modification_type," \
+    "  extract(epoch from r.modification_ts)::bigint as modification_ts, " \
+    "  r.id, " \
+    "  r.log_message " \
+    "FROM " #table_prefix "_audit AS a " \
+    "INNER JOIN " #table_prefix "_audit_revision AS r " \
+    "  ON a.revision_id = r.id " \
+    "INNER JOIN " #table_prefix "_server AS s" \
+    "  ON r.server_id = s.id " \
+    "WHERE (s.tag = $1 OR s.id = 1) AND ((r.modification_ts, r.id)  > (cast($2 as timestamp), $3))" \
+    " ORDER BY r.modification_ts, r.id, a.id"
+#endif
+
+#ifndef PGSQL_GET_SERVERS_COMMON
+#define PGSQL_GET_SERVERS_COMMON(table_prefix, ...) \
+    "SELECT" \
+    "  s.id," \
+    "  s.tag," \
+    "  s.description," \
+    "  extract(epoch from s.modification_ts)::bigint as modification_ts " \
+    "FROM " #table_prefix "_server AS s " \
+    "WHERE s.id > 1 " \
+    __VA_ARGS__ \
+    "ORDER BY s.id"
+#define PGSQL_GET_ALL_SERVERS(table_prefix) \
+    PGSQL_GET_SERVERS_COMMON(table_prefix, "")
+#define PGSQL_GET_SERVER(table_prefix) \
+    PGSQL_GET_SERVERS_COMMON(table_prefix, "AND s.tag = $1")
+#endif
+
+#ifndef PGSQL_GET_CLIENT_CLASS4_COMMON
+#define PGSQL_GET_CLIENT_CLASS4_COMMON(server_join, ...) \
+    "SELECT " \
+    "  c.id," \
+    "  c.name," \
+    "  c.test," \
+    "  c.next_server," \
+    "  c.server_hostname," \
+    "  c.boot_file_name," \
+    "  c.only_if_required," \
+    "  c.valid_lifetime," \
+    "  c.min_valid_lifetime," \
+    "  c.max_valid_lifetime," \
+    "  c.depend_on_known_directly," \
+    "  o.depend_on_known_indirectly, " \
+    "  extract(epoch from c.modification_ts)::bigint as modification_ts, " \
+    "  d.id," \
+    "  d.code," \
+    "  d.name," \
+    "  d.space," \
+    "  d.type," \
+    "  extract(epoch from d.modification_ts)::bigint as modification_ts, " \
+    "  d.is_array," \
+    "  d.encapsulate," \
+    "  d.record_types," \
+    "  d.user_context," \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp4_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  s.tag " \
+    "FROM dhcp4_client_class AS c " \
+    "INNER JOIN dhcp4_client_class_order AS o " \
+    "  ON c.id = o.class_id " \
+    server_join \
+    "LEFT JOIN dhcp4_option_def AS d ON c.id = d.class_id " \
+    "LEFT JOIN dhcp4_options AS x ON x.scope_id = 2 AND c.name = x.dhcp_client_class " \
+    #__VA_ARGS__ \
+    "  ORDER BY o.order_index, d.id, x.option_id"
+
+#define PGSQL_GET_CLIENT_CLASS4_WITH_TAG(...) \
+    PGSQL_GET_CLIENT_CLASS4_COMMON( \
+    "INNER JOIN dhcp4_client_class_server AS a " \
+    "  ON c.id = a.class_id " \
+    "INNER JOIN dhcp4_server AS s " \
+    "  ON a.server_id = s.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_CLIENT_CLASS4_UNASSIGNED(...) \
+    PGSQL_GET_CLIENT_CLASS4_COMMON( \
+    "LEFT JOIN dhcp4_client_class_server AS a " \
+    "  ON c.id = a.class_id " \
+    "LEFT JOIN dhcp4_server AS s " \
+    "  ON a.server_id = s.id ", \
+    WHERE a.class_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_GET_CLIENT_CLASS6_COMMON
+#define PGSQL_GET_CLIENT_CLASS6_COMMON(server_join, ...) \
+    "SELECT " \
+    "  c.id," \
+    "  c.name," \
+    "  c.test," \
+    "  c.only_if_required," \
+    "  c.valid_lifetime," \
+    "  c.min_valid_lifetime," \
+    "  c.max_valid_lifetime," \
+    "  c.depend_on_known_directly," \
+    "  o.depend_on_known_indirectly, " \
+    "  extract(epoch from c.modification_ts)::bigint as modification_ts, " \
+    "  d.id," \
+    "  d.code," \
+    "  d.name," \
+    "  d.space," \
+    "  d.type," \
+    "  extract(epoch from d.modification_ts)::bigint as modification_ts, " \
+    "  d.is_array," \
+    "  d.encapsulate," \
+    "  d.record_types," \
+    "  d.user_context," \
+    "  x.option_id," \
+    "  x.code," \
+    "  x.value," \
+    "  x.formatted_value," \
+    "  x.space," \
+    "  x.persistent," \
+    "  x.dhcp6_subnet_id," \
+    "  x.scope_id," \
+    "  x.user_context," \
+    "  x.shared_network_name," \
+    "  x.pool_id," \
+    "  extract(epoch from x.modification_ts)::bigint as modification_ts, " \
+    "  s.tag, " \
+    "  c.preferred_lifetime," \
+    "  c.min_preferred_lifetime, " \
+    "  c.max_preferred_lifetime " \
+    "FROM dhcp6_client_class AS c " \
+    "INNER JOIN dhcp6_client_class_order AS o " \
+    "  ON c.id = o.class_id " \
+    server_join \
+    "LEFT JOIN dhcp6_option_def AS d ON c.id = d.class_id " \
+    "LEFT JOIN dhcp6_options AS x ON x.scope_id = 2 AND c.name = x.dhcp_client_class " \
+    #__VA_ARGS__ \
+    "  ORDER BY o.order_index, d.id, x.option_id"
+
+#define PGSQL_GET_CLIENT_CLASS6_WITH_TAG(...) \
+    PGSQL_GET_CLIENT_CLASS6_COMMON( \
+    "INNER JOIN dhcp6_client_class_server AS a " \
+    "  ON c.id = a.class_id " \
+    "INNER JOIN dhcp6_server AS s " \
+    "  ON a.server_id = s.id ", \
+    __VA_ARGS__)
+
+#define PGSQL_GET_CLIENT_CLASS6_UNASSIGNED(...) \
+    PGSQL_GET_CLIENT_CLASS6_COMMON( \
+    "LEFT JOIN dhcp6_client_class_server AS a " \
+    "  ON c.id = a.class_id " \
+    "LEFT JOIN dhcp6_server AS s " \
+    "  ON a.server_id = s.id ", \
+    WHERE a.class_id IS NULL __VA_ARGS__)
+
+#endif
+
+#ifndef PGSQL_INSERT_GLOBAL_PARAMETER
+#define PGSQL_INSERT_GLOBAL_PARAMETER(table_prefix) \
+    "INSERT INTO " #table_prefix "_global_parameter(" \
+    "  name," \
+    "  value," \
+    "  parameter_type," \
+    "  modification_ts" \
+    ") VALUES ($1, $2, $3, $4)"
+#endif
+
+#ifndef PGSQL_INSERT_GLOBAL_PARAMETER_SERVER
+#define PGSQL_INSERT_GLOBAL_PARAMETER_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_global_parameter_server(" \
+    "  parameter_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES ($1, $2, (SELECT id FROM " #table_prefix "_server WHERE tag = $3))"
+#endif
+
+#ifndef PGSQL_INSERT_SUBNET_SERVER
+#define PGSQL_INSERT_SUBNET_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_subnet_server(" \
+    "  subnet_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES ($1, $2, (SELECT id FROM " #table_prefix "_server WHERE tag = $3))"
+#endif
+
+#ifndef PGSQL_INSERT_POOL
+#define PGSQL_INSERT_POOL(table_prefix) \
+    "INSERT INTO " #table_prefix "_pool(" \
+    "  start_address," \
+    "  end_address," \
+    "  subnet_id," \
+    "  client_class," \
+    "  require_client_classes," \
+    "  user_context," \
+    "  modification_ts" \
+    ") VALUES (cast($1 as inet), cast($2 as inet), $3, $4, $5, cast($6 as json), $7)"
+#endif
+
+#ifndef PGSQL_INSERT_PD_POOL
+#define PGSQL_INSERT_PD_POOL() \
+    "INSERT INTO dhcp6_pd_pool(" \
+    "  prefix," \
+    "  prefix_length," \
+    "  delegated_prefix_length," \
+    "  subnet_id," \
+    "  excluded_prefix," \
+    "  excluded_prefix_length," \
+    "  client_class," \
+    "  require_client_classes," \
+    "  user_context," \
+    "  modification_ts" \
+    ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as json), $10)"
+#endif
+
+#ifndef PGSQL_INSERT_SHARED_NETWORK_SERVER
+#define PGSQL_INSERT_SHARED_NETWORK_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_shared_network_server(" \
+    "  shared_network_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES (" \
+    "    (SELECT id FROM " #table_prefix "_shared_network WHERE name = $1), $2," \
+    "    (SELECT id FROM " #table_prefix "_server WHERE tag = $3)" \
+    ")"
+#endif
+
+#ifndef PGSQL_INSERT_OPTION_DEF
+#define PGSQL_INSERT_OPTION_DEF(table_prefix) \
+    "INSERT INTO " #table_prefix "_option_def (" \
+    "  code," \
+    "  name," \
+    "  space," \
+    "  type," \
+    "  modification_ts," \
+    "  is_array," \
+    "  encapsulate," \
+    "  record_types," \
+    "  user_context," \
+    "  class_id" \
+    ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as json), $10)"
+#endif
+
+#ifndef PGSQL_INSERT_OPTION_DEF_CLIENT_CLASS
+#define PGSQL_INSERT_OPTION_DEF_CLIENT_CLASS(table_prefix) \
+    "INSERT INTO " #table_prefix "_option_def (" \
+    "  code," \
+    "  name," \
+    "  space," \
+    "  type," \
+    "  modification_ts," \
+    "  is_array," \
+    "  encapsulate," \
+    "  record_types," \
+    "  user_context," \
+    "  class_id" \
+    ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as json), " \
+    "          (SELECT id FROM " #table_prefix "_client_class WHERE name = $10))"
+#endif
+
+#ifndef PGSQL_INSERT_OPTION_DEF_SERVER
+#define PGSQL_INSERT_OPTION_DEF_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_option_def_server(" \
+    "  option_def_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES ($1, $2, (SELECT id FROM " #table_prefix "_server WHERE tag = $3))"
+#endif
+
+#ifndef PGSQL_INSERT_OPTION_COMMON
+#define PGSQL_INSERT_OPTION_COMMON(table_prefix, pd_pool_id, last) \
+    "INSERT INTO " #table_prefix "_options (" \
+    "  code," \
+    "  value," \
+    "  formatted_value," \
+    "  space," \
+    "  persistent," \
+    "  dhcp_client_class," \
+    " " #table_prefix "_subnet_id," \
+    "  scope_id," \
+    "  user_context," \
+    "  shared_network_name," \
+    "  pool_id," \
+    "  modification_ts" \
+    pd_pool_id \
+    ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as json), $10, $11, $12" last ")"
+
+#define PGSQL_INSERT_OPTION4() \
+    PGSQL_INSERT_OPTION_COMMON(dhcp4, "", "")
+#define PGSQL_INSERT_OPTION6() \
+    PGSQL_INSERT_OPTION_COMMON(dhcp6, ", pd_pool_id ", ", $13")
+#endif
+
+#ifndef PGSQL_INSERT_OPTION_SERVER
+#define PGSQL_INSERT_OPTION_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_options_server (" \
+    "  option_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES ($1, $2, (SELECT id FROM " #table_prefix "_server WHERE tag = $3))"
+#endif
+
+#ifndef PGSQL_INSERT_CLIENT_CLASS_SERVER
+#define PGSQL_INSERT_CLIENT_CLASS_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_client_class_server (" \
+    "  class_id," \
+    "  modification_ts," \
+    "  server_id" \
+    ") VALUES ((SELECT id FROM " #table_prefix "_client_class WHERE name = $1), $2," \
+    "          (SELECT id FROM " #table_prefix "_server WHERE tag = $3))"
+#endif
+
+#ifndef PGSQL_INSERT_CLIENT_CLASS_DEPENDENCY
+#define PGSQL_INSERT_CLIENT_CLASS_DEPENDENCY(table_prefix) \
+    "INSERT INTO " #table_prefix "_client_class_dependency (" \
+    "  class_id," \
+    "  dependency_id" \
+    ") VALUES ((SELECT id FROM " #table_prefix "_client_class WHERE name = $1), " \
+    "          (SELECT id FROM " #table_prefix "_client_class WHERE name = $2))"
+#endif
+
+#ifndef PGSQL_INSERT_SERVER
+#define PGSQL_INSERT_SERVER(table_prefix) \
+    "INSERT INTO " #table_prefix "_server (" \
+    "  tag," \
+    "  description," \
+    "  modification_ts" \
+    ") VALUES ($1, $2, $3)"
+#endif
+
+#ifndef PGSQL_UPDATE_GLOBAL_PARAMETER
+#define PGSQL_UPDATE_GLOBAL_PARAMETER(table_prefix) \
+    "UPDATE " #table_prefix "_global_parameter AS g " \
+    "SET " \
+    "    name = $1, " \
+    "    value = $2, " \
+    "    parameter_type = $3, " \
+    "    modification_ts = $4 " \
+    "FROM " #table_prefix "_global_parameter_server as a, " \
+    "     " #table_prefix "_server as s " \
+    "WHERE g.id = a.parameter_id and  " \
+    "      a.server_id = s.id and  " \
+    "      s.tag = $5 AND g.name = $6"
+#endif
+
+#ifndef PGSQL_UPDATE_OPTION_DEF
+#define PGSQL_UPDATE_OPTION_DEF(table_prefix) \
+    "UPDATE " #table_prefix "_option_def AS d " \
+    "SET" \
+    "  code = $1," \
+    "  name = $2," \
+    "  space = $3," \
+    "  type = $4," \
+    "  modification_ts = $5," \
+    "  is_array = $6," \
+    "  encapsulate = $7," \
+    "  record_types = $8," \
+    "  user_context = cast($9 as json), " \
+    "  class_id = $10 " \
+    "FROM " #table_prefix "_option_def_server as a, " \
+    "     " #table_prefix "_server as s " \
+    "WHERE d.id = a.option_def_id AND" \
+    "      a.server_id = s.id AND " \
+    "      s.tag = $11 AND d.code = $12 AND d.space = $13 "
+#endif
+
+#ifndef PGSQL_UPDATE_OPTION_DEF_CLIENT_CLASS
+#define PGSQL_UPDATE_OPTION_DEF_CLIENT_CLASS(table_prefix) \
+    "UPDATE " #table_prefix "_option_def AS d " \
+    "SET" \
+    "  code = $1," \
+    "  name = $2," \
+    "  space = $3," \
+    "  type = $4," \
+    "  modification_ts = $5," \
+    "  is_array = $6," \
+    "  encapsulate = $7," \
+    "  record_types = $8," \
+    "  user_context =  cast($9 as json)" \
+    "FROM " #table_prefix "_option_def_server as a, " \
+    "     " #table_prefix "_server as s " \
+    "WHERE d.id = a.option_def_id AND " \
+    "      a.server_id = s.id AND " \
+    "      d.class_id = (SELECT id FROM dhcp4_client_class WHERE name = $10)"
+#endif
+
+
+#ifndef PGSQL_UPDATE_OPTION_NO_TAG
+#define PGSQL_UPDATE_OPTION_NO_TAG(table_prefix, pd_pool_id, ...) \
+    "UPDATE " #table_prefix "_options AS o " \
+    "SET" \
+    "  code = $1," \
+    "  value = $2," \
+    "  formatted_value = $3," \
+    "  space = $4," \
+    "  persistent = $5," \
+    "  dhcp_client_class = $6," \
+    "  " #table_prefix "_subnet_id = $7," \
+    "  scope_id = $8," \
+    "  user_context = cast($9 as json)," \
+    "  shared_network_name = $10," \
+    "  pool_id = $11," \
+    "  modification_ts = $12 " \
+    pd_pool_id \
+    "WHERE " #__VA_ARGS__
+#endif
+
+#define PGSQL_UPDATE_OPTION4_NO_TAG(...) \
+    PGSQL_UPDATE_OPTION_NO_TAG(dhcp4, "",  __VA_ARGS__)
+
+#define PGSQL_UPDATE_OPTION6_NO_TAG(...) \
+    PGSQL_UPDATE_OPTION_NO_TAG(dhcp6, ", pd_pool_id = $13 ", __VA_ARGS__)
+
+#ifndef PGSQL_UPDATE_OPTION_WITH_TAG
+#define PGSQL_UPDATE_OPTION_WITH_TAG(table_prefix, pd_pool_id, ...) \
+    "UPDATE " #table_prefix "_options AS o " \
+    "SET" \
+    "  code = $1," \
+    "  value = $2," \
+    "  formatted_value = $3," \
+    "  space = $4," \
+    "  persistent = $5," \
+    "  dhcp_client_class = $6," \
+    "  " #table_prefix "_subnet_id = $7," \
+    "  scope_id = $8," \
+    "  user_context = cast($9 as json)," \
+    "  shared_network_name = $10," \
+    "  pool_id = $11," \
+    "  modification_ts = $12 " \
+    pd_pool_id \
+    "FROM " #table_prefix "_options_server as a, " \
+    "     " #table_prefix "_server as s " \
+    "WHERE  o.option_id = a.option_id AND " \
+    "       a.server_id = s.id " \
+    #__VA_ARGS__
+#endif
+
+#define PGSQL_UPDATE_OPTION4_WITH_TAG(...) \
+    PGSQL_UPDATE_OPTION_WITH_TAG(dhcp4, "", AND s.tag = $13 __VA_ARGS__)
+
+#define PGSQL_UPDATE_OPTION6_WITH_TAG(...) \
+    PGSQL_UPDATE_OPTION_WITH_TAG(dhcp4, \
+    ", pd_pool_id = $13 ", AND s.tag = $14 __VA_ARGS__)
+
+#ifndef PGSQL_UPDATE_CLIENT_CLASS4
+#define PGSQL_UPDATE_CLIENT_CLASS4(follow_class_name_set) \
+    "UPDATE dhcp4_client_class SET" \
+    "  name = $1," \
+    "  test = $2," \
+    "  next_server = cast($3 as inet)," \
+    "  server_hostname = $4," \
+    "  boot_file_name = $5," \
+    "  only_if_required = $6," \
+    "  valid_lifetime = $7," \
+    "  min_valid_lifetime = $8," \
+    "  max_valid_lifetime = $9," \
+    "  depend_on_known_directly = $10," \
+    follow_class_name_set \
+    "  modification_ts = $11 " \
+    "WHERE name = $12"
+#endif
+
+#ifndef PGSQL_UPDATE_CLIENT_CLASS6
+#define PGSQL_UPDATE_CLIENT_CLASS6(follow_class_name_set) \
+    "UPDATE dhcp6_client_class SET" \
+    "  name = $1," \
+    "  test = $2," \
+    "  only_if_required = $3," \
+    "  valid_lifetime = $4," \
+    "  min_valid_lifetime = $5," \
+    "  max_valid_lifetime = $6," \
+    "  depend_on_known_directly = $7," \
+    follow_class_name_set \
+    "  modification_ts = $8, " \
+    "  preferred_lifetime = $9, " \
+    "  min_preferred_lifetime = $10, " \
+    "  max_preferred_lifetime = $11 " \
+    "WHERE name = $12"
+#endif
+
+#ifndef PGSQL_UPDATE_SERVER
+#define PGSQL_UPDATE_SERVER(table_prefix) \
+    "UPDATE " #table_prefix "_server " \
+    "SET" \
+    "  tag = $1," \
+    "  description = $2," \
+    "  modification_ts = $3 " \
+    "WHERE tag = $4"
+#endif
+
+#ifndef PGSQL_DELETE_GLOBAL_PARAMETER
+#define PGSQL_DELETE_GLOBAL_PARAMETER(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_global_parameter AS g " \
+    "USING " \
+    "   " #table_prefix "_global_parameter_server AS a, " \
+    "   " #table_prefix "_server AS s " \
+    "WHERE " \
+    "   g.id = a.parameter_id AND " \
+    "   a.server_id = s.id AND " \
+    "   s.tag = $1 " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_GLOBAL_PARAMETER_UNASSIGNED
+#define PGSQL_DELETE_GLOBAL_PARAMETER_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_global_parameter AS g " \
+    "WHERE g.id in ( " \
+    "   SELECT g.id FROM " #table_prefix "_global_parameter AS g " \
+    "   LEFT JOIN " #table_prefix "_global_parameter_server AS a ON g.id = a.parameter_id " \
+    "   WHERE a.parameter_id IS NULL)  " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_SUBNET_COMMON
+#define PGSQL_DELETE_SUBNET_COMMON(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_subnet AS s " \
+    "USING " \
+    "   " #table_prefix "_subnet_server AS a, " \
+    "   " #table_prefix "_server AS srv " \
+    "WHERE " \
+    "   s.subnet_id = a.subnet_id AND " \
+    "   a.server_id = srv.id " \
+    #__VA_ARGS__
+#endif
+
+#define PGSQL_DELETE_SUBNET_WITH_TAG(table_prefix, ...) \
+    PGSQL_DELETE_SUBNET_COMMON(table_prefix, AND srv.tag = $1 __VA_ARGS__)
+
+#define PGSQL_DELETE_SUBNET_ANY(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_subnet AS s " \
+    #__VA_ARGS__
+
+#define PGSQL_DELETE_SUBNET_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_subnet AS s " \
+    "WHERE s.subnet_id in ( " \
+    "   SELECT s.subnet_id FROM " #table_prefix "_subnet AS s " \
+    "   LEFT JOIN " #table_prefix "_subnet_server AS a ON s.subnet_id = a.subnet_id " \
+    "   WHERE a.subnet_id IS NULL) " #__VA_ARGS__
+
+#ifndef PGSQL_DELETE_SUBNET_SERVER
+#define PGSQL_DELETE_SUBNET_SERVER(table_prefix) \
+    "DELETE FROM " #table_prefix "_subnet_server " \
+    "WHERE subnet_id = $1"
+#endif
+
+#ifndef PGSQL_DELETE_POOLS
+#define PGSQL_DELETE_POOLS(table_prefix) \
+    "DELETE FROM " #table_prefix "_pool " \
+    "WHERE subnet_id = $1 OR subnet_id = " \
+    "(SELECT subnet_id FROM " #table_prefix "_subnet" \
+    "    WHERE subnet_prefix = $2)"
+#endif
+
+#ifndef PGSQL_DELETE_PD_POOLS
+#define PGSQL_DELETE_PD_POOLS() \
+    "DELETE FROM dhcp6_pd_pool " \
+    "WHERE subnet_id = $1 OR subnet_id = " \
+    "(SELECT subnet_id FROM dhcp6_subnet" \
+    "    WHERE subnet_prefix = $2)"
+#endif
+
+#ifndef PGSQL_DELETE_SHARED_NETWORK_COMMON
+#define PGSQL_DELETE_SHARED_NETWORK_COMMON(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_shared_network AS n " \
+    "USING " \
+    "   " #table_prefix "_shared_network_server AS a, " \
+    "   " #table_prefix "_server AS s " \
+    "WHERE " \
+    "   n.id = a.shared_network_id AND " \
+    "   a.server_id = s.id " \
+    #__VA_ARGS__
+#endif
+
+#define PGSQL_DELETE_SHARED_NETWORK_WITH_TAG(table_prefix, ...) \
+    PGSQL_DELETE_SHARED_NETWORK_COMMON(table_prefix, AND s.tag = $1 __VA_ARGS__)
+
+#define PGSQL_DELETE_SHARED_NETWORK_ANY(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_shared_network AS n " \
+    #__VA_ARGS__
+
+#define PGSQL_DELETE_SHARED_NETWORK_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_shared_network AS n " \
+    "WHERE n.id in ( " \
+    "   SELECT n.id FROM " #table_prefix "_shared_network AS n " \
+    "   LEFT JOIN " #table_prefix "_shared_network_server AS a ON n.id = a.shared_network_id " \
+    "   WHERE a.shared_network_id IS NULL) " \
+    #__VA_ARGS__
+
+#ifndef PGSQL_DELETE_SHARED_NETWORK_SERVER
+#define PGSQL_DELETE_SHARED_NETWORK_SERVER(table_prefix) \
+    "DELETE FROM " #table_prefix "_shared_network_server " \
+    "WHERE shared_network_id = " \
+    "(SELECT id FROM " #table_prefix "_shared_network WHERE name = $1)"
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_DEF
+#define PGSQL_DELETE_OPTION_DEF(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_option_def AS d " \
+    "USING " \
+    "   " #table_prefix "_option_def_server AS a, " \
+    "   " #table_prefix "_server AS s " \
+    "WHERE " \
+    "   d.id = a.option_def_id AND " \
+    "   a.server_id = s.id AND " \
+    "   s.tag = $1 " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_DEF_UNASSIGNED
+#define PGSQL_DELETE_OPTION_DEF_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_option_def AS d " \
+    "WHERE d.id in ( " \
+    "   SELECT d.id FROM " #table_prefix "_option_def AS d " \
+    "   LEFT JOIN " #table_prefix "_option_def_server AS a ON d.id = a.option_def_id " \
+    "   WHERE a.option_def_id IS NULL)  " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_DEFS_CLIENT_CLASS
+#define PGSQL_DELETE_OPTION_DEFS_CLIENT_CLASS(table_prefix) \
+    "DELETE FROM " #table_prefix "_option_def " \
+    "WHERE class_id = (SELECT id FROM " #table_prefix "_client_class WHERE name = $1)"
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_WITH_TAG
+#define PGSQL_DELETE_OPTION_WITH_TAG(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_options AS o " \
+    "USING " \
+    "   " #table_prefix "_options_server AS a, " \
+    "   " #table_prefix "_server AS s " \
+    "WHERE " \
+    "   o.option_id = a.option_id AND " \
+    "   a.server_id = s.id AND " \
+    "   s.tag = $1 " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_NO_TAG
+#define PGSQL_DELETE_OPTION_NO_TAG(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_options AS o " \
+    #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_SUBNET_ID_PREFIX
+#define PGSQL_DELETE_OPTION_SUBNET_ID_PREFIX(table_prefix) \
+    "DELETE FROM " #table_prefix "_options AS o " \
+    "USING " \
+    "   " #table_prefix "_subnet AS s " \
+    "WHERE " \
+    "   s.subnet_id = o." #table_prefix "_subnet_id AND " \
+    "   o.scope_id = 1 AND (s.subnet_id = $1 OR s.subnet_prefix = $2)"
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_UNASSIGNED
+#define PGSQL_DELETE_OPTION_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_options AS o " \
+    "WHERE o.option_id in ( " \
+    "   SELECT o.option_id FROM " #table_prefix "_options AS o " \
+    "   LEFT JOIN " #table_prefix "_options_server AS a ON o.option_id = a.option_id " \
+    "   WHERE a.option_id IS NULL)  " #__VA_ARGS__
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_POOL_RANGE
+#define PGSQL_DELETE_OPTION_POOL_RANGE(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_options AS o " \
+    "WHERE " #__VA_ARGS__ \
+    "  AND o.pool_id = " \
+    "  (SELECT id FROM " #table_prefix "_pool" \
+    "   WHERE start_address = cast($1 as inet) AND end_address = cast($2 as inet))"
+#endif
+
+#ifndef PGSQL_DELETE_OPTION_PD_POOL
+#define PGSQL_DELETE_OPTION_PD_POOL(...) \
+    "DELETE o FROM dhcp6_options AS o " \
+    "WHERE " #__VA_ARGS__ \
+    "  AND o.pd_pool_id = " \
+    "  (SELECT id FROM dhcp6_pd_pool" \
+    "   WHERE prefix = $1 AND prefix_length = $2)"
+#endif
+
+#ifndef PGSQL_DELETE_CLIENT_CLASS_DEPENDENCY
+#define PGSQL_DELETE_CLIENT_CLASS_DEPENDENCY(table_prefix) \
+    "DELETE FROM " #table_prefix "_client_class_dependency " \
+    "WHERE class_id = (SELECT id FROM " #table_prefix "_client_class WHERE name = $1)"
+#endif
+
+#ifndef PGSQL_DELETE_CLIENT_CLASS_SERVER
+#define PGSQL_DELETE_CLIENT_CLASS_SERVER(table_prefix) \
+    "DELETE FROM " #table_prefix "_client_class_server " \
+    "WHERE class_id = " \
+    "(SELECT id FROM " #table_prefix "_client_class WHERE name = $1)"
+#endif
+
+#ifndef PGSQL_DELETE_CLIENT_CLASS
+#define PGSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_client_class AS c " \
+    "USING " \
+    "   " #table_prefix "_client_class_server AS a, " \
+    "   " #table_prefix "_server AS s " \
+    "WHERE " \
+    "  c.id = a.class_id AND " \
+    "  a.server_id = s.id " \
+    #__VA_ARGS__
+
+#define PGSQL_DELETE_CLIENT_CLASS_WITH_TAG(table_prefix, ...) \
+    PGSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, AND s.tag = $1 __VA_ARGS__)
+
+#define PGSQL_DELETE_CLIENT_CLASS_ANY(table_prefix, ...) \
+    PGSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, __VA_ARGS__)
+
+#define PGSQL_DELETE_CLIENT_CLASS_UNASSIGNED(table_prefix, ...) \
+    "DELETE FROM " #table_prefix "_client_class AS c " \
+    "WHERE c.id in (" \
+    "   SELECT c.id FROM " #table_prefix "_client_class AS c " \
+    "   LEFT JOIN " #table_prefix "_client_class_server AS a ON c.id = a.class_id " \
+    "   WHERE a.class_id IS NULL) " #__VA_ARGS__
+
+#endif // PGSQL_DELETE_CLIENT_CLASS
+
+#ifndef PGSQL_DELETE_SERVER
+#define PGSQL_DELETE_SERVER(table_prefix) \
+    "DELETE FROM " #table_prefix "_server " \
+    "WHERE tag = $1"
+#endif
+
+#ifndef PGSQL_DELETE_ALL_SERVERS
+#define PGSQL_DELETE_ALL_SERVERS(table_prefix) \
+    "DELETE FROM " #table_prefix "_server " \
+    "WHERE id > 1"
+#endif
+
+} // end of anonymous namespace
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
index b014bca13d972c6adcd789965a91e0acaa699dfe..843416f3e6eecc3d23df7a388e14535f24f590c5 100644 (file)
@@ -24,9 +24,9 @@ if HAVE_GTEST
 TESTS += pgsql_cb_unittests
 
 pgsql_cb_unittests_SOURCES  = pgsql_cb_impl_unittest.cc
+pgsql_cb_unittests_SOURCES  += pgsql_cb_dhcp4_unittest.cc
 
 # disabled for now, to be added in #95
-#pgsql_cb_unittests_SOURCES  = pgsql_cb_dhcp4_unittest.cc
 #pgsql_cb_unittests_SOURCES += pgsql_cb_dhcp4_mgr_unittest.cc
 
 # disabled for now, to be added in #96
diff --git a/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc
new file mode 100644 (file)
index 0000000..836306f
--- /dev/null
@@ -0,0 +1,1261 @@
+// Copyright (C) 2021-2022 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_dhcp4.h>
+#include <pgsql_cb_impl.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server.h>
+#include <database/testutils/schema.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/pgsql_generic_backend_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/multi_threading_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <map>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Test implementation of the PgSQL configuration backend.
+///
+/// It exposes protected members of the @c PgSqlConfigBackendDHCPv4.
+class TestPgSqlConfigBackendDHCPv4 : public PgSqlConfigBackendDHCPv4 {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    /// concerned with the database.
+    explicit TestPgSqlConfigBackendDHCPv4(const DatabaseConnection::ParameterMap& parameters)
+        : PgSqlConfigBackendDHCPv4(parameters) {
+    }
+
+    using PgSqlConfigBackendDHCPv4::base_impl_;
+
+};
+
+/// @brief Test fixture class for @c PgSqlConfigBackendDHCPv4.
+///
+/// @todo The tests we're providing here only test cases when the
+/// server selector is set to 'ALL' (configuration elements belong to
+/// all servers). Currently we have no API to insert servers into
+/// the database, and therefore we can't test the case when
+/// configuration elements are assigned to particular servers by
+/// server tags. We will have to expand existing tests when
+/// the API is extended allowing for inserting servers to the
+/// database.
+class PgSqlConfigBackendDHCPv4Test : public PgSqlGenericBackendTest {
+public:
+
+    /// @brief Constructor.
+    PgSqlConfigBackendDHCPv4Test()
+        : test_subnets_(), test_networks_(), test_option_defs_(),
+          test_options_(), test_client_classes_(), test_servers_(), timestamps_(),
+          cbptr_(), audit_entries_() {
+        // Ensure we have the proper schema with no transient data.
+        createPgSQLSchema();
+
+        try {
+            // Create PgSQL connection and use it to start the backend.
+            DatabaseConnection::ParameterMap params =
+                DatabaseConnection::parse(validPgSQLConnectionString());
+            cbptr_.reset(new TestPgSqlConfigBackendDHCPv4(params));
+
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test\n"
+                         "*** environment is broken and must be fixed before\n"
+                         "*** the PgSQL tests will run correctly.\n"
+                         "*** The reason for the problem is described in the\n"
+                         "*** accompanying exception output.\n";
+            throw;
+        }
+
+        // Create test data.
+        initTestServers();
+        initTestOptions();
+        initTestSubnets();
+        initTestSharedNetworks();
+        initTestOptionDefs();
+        initTestClientClasses();
+        initTimestamps();
+    }
+
+    /// @brief Destructor.
+    virtual ~PgSqlConfigBackendDHCPv4Test() {
+        cbptr_.reset();
+        // If data wipe enabled, delete transient data otherwise destroy the schema.
+        destroyPgSQLSchema();
+    }
+
+    /// @brief Counts rows in a selected table in PgSQL database.
+    ///
+    /// This method can be used to verify that some configuration elements were
+    /// deleted from a selected table as a result of cascade delete or a trigger.
+    /// For example, deleting a subnet should trigger deletion of its address
+    /// pools and options. By counting the rows on each table we can determine
+    /// whether the deletion took place on all tables for which it was expected.
+    ///
+    /// @param table Table name.
+    /// @return Number of rows in the specified table.
+    size_t countRows(const std::string& table) const {
+        auto p = boost::dynamic_pointer_cast<TestPgSqlConfigBackendDHCPv4>(cbptr_);
+        if (!p) {
+            ADD_FAILURE() << "cbptr_ does not cast to TestPgSqlConfigBackendDHCPv4";
+            return (0);
+        }
+
+        // Reuse the existing connection of the backend.
+        auto impl = boost::dynamic_pointer_cast<PgSqlConfigBackendImpl>(p->base_impl_);
+        auto& conn = impl->conn_;
+
+        return (PgSqlGenericBackendTest::countRows(conn, table));
+    }
+
+    /// @brief Creates several servers used in tests.
+    void initTestServers() {
+        test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1"));
+        test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis"));
+        test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2"));
+        test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3"));
+    }
+
+    /// @brief Creates several subnets used in tests.
+    void initTestSubnets() {
+        // First subnet includes all parameters.
+        std::string interface_id_text = "whale";
+        OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+        ElementPtr user_context = Element::createMap();
+        user_context->set("foo", Element::create("bar"));
+
+        Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40, 60, 1024));
+        subnet->get4o6().setIface4o6("eth0");
+        subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID,
+                                                         interface_id)));
+        subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64);
+        subnet->setFilename("/tmp/filename");
+        subnet->allowClientClass("home");
+        subnet->setIface("eth1");
+        subnet->setMatchClientId(false);
+        subnet->setSiaddr(IOAddress("10.1.2.3"));
+        subnet->setT2(323212);
+        subnet->addRelayAddress(IOAddress("10.2.3.4"));
+        subnet->addRelayAddress(IOAddress("10.5.6.7"));
+        subnet->setT1(1234);
+        subnet->requireClientClass("required-class1");
+        subnet->requireClientClass("required-class2");
+        subnet->setReservationsGlobal(false);
+        subnet->setReservationsInSubnet(false);
+        subnet->setSname("server-hostname");
+        subnet->setContext(user_context);
+        subnet->setValid(555555);
+        subnet->setAuthoritative(true);
+        subnet->setCalculateTeeTimes(true);
+        subnet->setT1Percent(0.345);
+        subnet->setT2Percent(0.444);
+        subnet->setDdnsSendUpdates(false);
+
+        Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"), IOAddress("192.0.2.20")));
+        subnet->addPool(pool1);
+
+        Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"), IOAddress("192.0.2.60")));
+        subnet->addPool(pool2);
+
+        // Add several options to the subnet.
+        subnet->getCfgOption()->add(test_options_[0]->option_,
+                                    test_options_[0]->persistent_,
+                                    test_options_[0]->space_name_);
+
+        subnet->getCfgOption()->add(test_options_[1]->option_,
+                                    test_options_[1]->persistent_,
+                                    test_options_[1]->space_name_);
+
+        subnet->getCfgOption()->add(test_options_[2]->option_,
+                                    test_options_[2]->persistent_,
+                                    test_options_[2]->space_name_);
+
+        test_subnets_.push_back(subnet);
+
+        // Adding another subnet with the same subnet id to test
+        // cases that this second instance can override existing
+        // subnet instance.
+        subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 20, 30, 40, 1024));
+
+        pool1.reset(new Pool4(IOAddress("10.0.0.10"), IOAddress("10.0.0.20")));
+        subnet->addPool(pool1);
+
+        pool1->getCfgOption()->add(test_options_[3]->option_,
+                                   test_options_[3]->persistent_,
+                                   test_options_[3]->space_name_);
+
+        pool1->getCfgOption()->add(test_options_[4]->option_,
+                                   test_options_[4]->persistent_,
+                                   test_options_[4]->space_name_);
+
+        pool2.reset(new Pool4(IOAddress("10.0.0.50"), IOAddress("10.0.0.60")));
+
+        pool2->allowClientClass("work");
+        pool2->requireClientClass("required-class3");
+        pool2->requireClientClass("required-class4");
+        user_context = Element::createMap();
+        user_context->set("bar", Element::create("foo"));
+        pool2->setContext(user_context);
+
+        subnet->addPool(pool2);
+
+        test_subnets_.push_back(subnet);
+
+        subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048));
+        Triplet<uint32_t> null_timer;
+        subnet->setT1(null_timer);
+        subnet->setT2(null_timer);
+        subnet->setValid(null_timer);
+        subnet->setDdnsSendUpdates(true);
+        subnet->setDdnsOverrideNoUpdate(true);
+        subnet->setDdnsOverrideClientUpdate(false);
+        subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+        subnet->setDdnsGeneratedPrefix("myhost");
+        subnet->setDdnsQualifyingSuffix("example.org");
+
+        subnet->getCfgOption()->add(test_options_[0]->option_,
+                                    test_options_[0]->persistent_,
+                                    test_options_[0]->space_name_);
+
+        test_subnets_.push_back(subnet);
+
+        // Add a subnet with all defaults.
+        subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24, Triplet<uint32_t>(),
+                                 Triplet<uint32_t>(), Triplet<uint32_t>(), 4096));
+        test_subnets_.push_back(subnet);
+    }
+
+    /// @brief Creates several subnets used in tests.
+    void initTestSharedNetworks() {
+        ElementPtr user_context = Element::createMap();
+        user_context->set("foo", Element::create("bar"));
+
+        SharedNetwork4Ptr shared_network(new SharedNetwork4("level1"));
+        shared_network->allowClientClass("foo");
+        shared_network->setIface("eth1");
+        shared_network->setMatchClientId(false);
+        shared_network->setT2(323212);
+        shared_network->addRelayAddress(IOAddress("10.2.3.4"));
+        shared_network->addRelayAddress(IOAddress("10.5.6.7"));
+        shared_network->setT1(1234);
+        shared_network->requireClientClass("required-class1");
+        shared_network->requireClientClass("required-class2");
+        shared_network->setReservationsGlobal(false);
+        shared_network->setReservationsInSubnet(false);
+        shared_network->setContext(user_context);
+        shared_network->setValid(5555);
+        shared_network->setCalculateTeeTimes(true);
+        shared_network->setT1Percent(0.345);
+        shared_network->setT2Percent(0.444);
+        shared_network->setSiaddr(IOAddress("192.0.1.2"));
+        shared_network->setSname("frog");
+        shared_network->setFilename("/dev/null");
+        shared_network->setAuthoritative(true);
+        shared_network->setDdnsSendUpdates(false);
+
+        // Add several options to the shared network.
+        shared_network->getCfgOption()->add(test_options_[2]->option_,
+                                            test_options_[2]->persistent_,
+                                            test_options_[2]->space_name_);
+
+        shared_network->getCfgOption()->add(test_options_[3]->option_,
+                                            test_options_[3]->persistent_,
+                                            test_options_[3]->space_name_);
+
+        shared_network->getCfgOption()->add(test_options_[4]->option_,
+                                            test_options_[4]->persistent_,
+                                            test_options_[4]->space_name_);
+
+        test_networks_.push_back(shared_network);
+
+        // Adding another shared network called "level1" to test
+        // cases that this second instance can override existing
+        // "level1" instance.
+        shared_network.reset(new SharedNetwork4("level1"));
+        test_networks_.push_back(shared_network);
+
+        // Add more shared networks.
+        shared_network.reset(new SharedNetwork4("level2"));
+        Triplet<uint32_t> null_timer;
+        shared_network->setT1(null_timer);
+        shared_network->setT2(null_timer);
+        shared_network->setValid(null_timer);
+        shared_network->setDdnsSendUpdates(true);
+        shared_network->setDdnsOverrideNoUpdate(true);
+        shared_network->setDdnsOverrideClientUpdate(false);
+        shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+        shared_network->setDdnsGeneratedPrefix("myhost");
+        shared_network->setDdnsQualifyingSuffix("example.org");
+
+        shared_network->getCfgOption()->add(test_options_[0]->option_,
+                                            test_options_[0]->persistent_,
+                                            test_options_[0]->space_name_);
+        test_networks_.push_back(shared_network);
+
+        shared_network.reset(new SharedNetwork4("level3"));
+        test_networks_.push_back(shared_network);
+    }
+
+    /// @brief Creates several option definitions used in tests.
+    void initTestOptionDefs() {
+        ElementPtr user_context = Element::createMap();
+        user_context->set("foo", Element::create("bar"));
+
+        OptionDefinitionPtr option_def(new OptionDefinition("foo", 234,
+                                                            DHCP4_OPTION_SPACE,
+                                                            "string",
+                                                            "espace"));
+        test_option_defs_.push_back(option_def);
+
+        option_def.reset(new OptionDefinition("bar", 234, DHCP4_OPTION_SPACE,
+                                              "uint32", true));
+        test_option_defs_.push_back(option_def);
+
+        option_def.reset(new OptionDefinition("fish", 235, DHCP4_OPTION_SPACE,
+                                              "record", true));
+        option_def->addRecordField("uint32");
+        option_def->addRecordField("string");
+        test_option_defs_.push_back(option_def);
+
+        option_def.reset(new OptionDefinition("whale", 236, "xyz", "string"));
+        test_option_defs_.push_back(option_def);
+
+        option_def.reset(new OptionDefinition("foobar", 234, DHCP4_OPTION_SPACE,
+                                              "uint64", true));
+        test_option_defs_.push_back(option_def);
+    }
+
+    /// @brief Creates several DHCP options used in tests.
+    void initTestOptions() {
+        ElementPtr user_context = Element::createMap();
+        user_context->set("foo", Element::create("bar"));
+
+        OptionDefSpaceContainer defs;
+
+        OptionDescriptor desc =
+            createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                       true, false, "my-boot-file");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+                                         false, true, 64);
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131),
+        desc.space_name_ = "vendor-encapsulated-options";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createAddressOption<Option4AddrLst>(254, true, true,
+                                                   "192.0.2.3");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createEmptyOption(Option::V4, 1, true);
+        desc.space_name_ = "isc";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createAddressOption<Option4AddrLst>(2, false, true, "10.0.0.5",
+                                                   "10.0.0.3", "10.0.3.4");
+        desc.space_name_ = "isc";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                          true, false, "my-boot-file-2");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                          true, false, "my-boot-file-3");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        // Add definitions for DHCPv4 non-standard options in case we need to
+        // compare subnets, networks and pools in JSON format. In that case,
+        // the @c toElement functions require option definitions to generate the
+        // proper output.
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+                         "vendor-encapsulated-1", 1,
+                         "vendor-encapsulated-options", "uint32")));
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+                         "option-254", 254, DHCP4_OPTION_SPACE,
+                         "ipv4-address", true)));
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", "ipv4-address", true)));
+
+        // Register option definitions.
+        LibDHCP::setRuntimeOptionDefs(defs);
+    }
+
+    /// @brief Creates several client classes used in tests.
+    void initTestClientClasses() {
+        ExpressionPtr match_expr = boost::make_shared<Expression>();
+        CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+        auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+        class1->setRequired(true);
+        class1->setNextServer(IOAddress("1.2.3.4"));
+        class1->setSname("cool");
+        class1->setFilename("epc.cfg");
+        class1->setValid(Triplet<uint32_t>(30, 60, 90));
+        test_client_classes_.push_back(class1);
+
+        auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+        class2->setTest("member('foo')");
+        test_client_classes_.push_back(class2);
+
+        auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+        class3->setTest("member('foo') and member('bar')");
+        test_client_classes_.push_back(class3);
+    }
+
+    /// @brief Initialize posix time values used in tests.
+    void initTimestamps() {
+        // Current time minus 1 hour to make sure it is in the past.
+        timestamps_["today"] = boost::posix_time::second_clock::local_time()
+            - boost::posix_time::hours(1);
+        // One second after today.
+        timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1);
+        // Yesterday.
+        timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+        // One second after yesterday.
+        timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1);
+        // Two days ago.
+        timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
+        // Tomorrow.
+        timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+        // One second after tomorrow.
+        timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1);
+    }
+
+    /// @brief Logs audit entries in the @c audit_entries_ member.
+    ///
+    /// This function is called in case of an error.
+    ///
+    /// @param server_tag Server tag for which the audit entries should be logged.
+    std::string logExistingAuditEntries(const std::string& server_tag) {
+        std::ostringstream s;
+
+        auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>();
+
+        for (auto audit_entry_it = mod_time_idx.begin();
+             audit_entry_it != mod_time_idx.end();
+             ++audit_entry_it) {
+            auto audit_entry = *audit_entry_it;
+            s << audit_entry->getObjectType() << ", "
+              << audit_entry->getObjectId() << ", "
+              << static_cast<int>(audit_entry->getModificationType()) << ", "
+              << audit_entry->getModificationTime() << ", "
+              << audit_entry->getRevisionId() << ", "
+              << audit_entry->getLogMessage()
+              << std::endl;
+        }
+
+        return (s.str());
+    }
+
+    /// @brief Tests that the new audit entry is added.
+    ///
+    /// This method retrieves a collection of the existing audit entries and
+    /// checks that the new one has been added at the end of this collection.
+    /// It then verifies the values of the audit entry against the values
+    /// specified by the caller.
+    ///
+    /// @param exp_object_type Expected object type.
+    /// @param exp_modification_type Expected modification type.
+    /// @param exp_log_message Expected log message.
+    /// @param server_selector Server selector to be used for next query.
+    /// @param new_entries_num Number of the new entries expected to be inserted.
+    /// @param max_tested_entries Maximum number of entries tested.
+    void testNewAuditEntry(const std::string& exp_object_type,
+                           const AuditEntry::ModificationType& exp_modification_type,
+                           const std::string& exp_log_message,
+                           const ServerSelector& server_selector = ServerSelector::ALL(),
+                           const size_t new_entries_num = 1,
+                           const size_t max_tested_entries = 65535) {
+
+        // Get the server tag for which the entries are fetched.
+        std::string tag;
+        if (server_selector.getType() == ServerSelector::Type::ALL) {
+            // Server tag is 'all'.
+            tag = "all";
+
+        } else {
+            auto tags = server_selector.getTags();
+            // This test is not meant to handle multiple server tags all at once.
+            if (tags.size() > 1) {
+                ADD_FAILURE() << "Test error: do not use multiple server tags";
+
+            } else if (tags.size() == 1) {
+                // Get the server tag for which we run the current test.
+                tag = tags.begin()->get();
+            }
+        }
+
+        auto audit_entries_size_save = audit_entries_[tag].size();
+
+        // Audit entries for different server tags are stored in separate
+        // containers.
+        ASSERT_NO_THROW_LOG(audit_entries_[tag] = cbptr_->getRecentAuditEntries(server_selector,
+                                                            timestamps_["two days ago"], 0));
+        ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
+            << logExistingAuditEntries(tag);
+
+        auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
+
+        // Iterate over specified number of entries starting from the most recent
+        // one and check they have correct values.
+        for (auto audit_entry_it = mod_time_idx.rbegin();
+             ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) &&
+              (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries));
+             ++audit_entry_it) {
+            auto audit_entry = *audit_entry_it;
+            EXPECT_EQ(exp_object_type, audit_entry->getObjectType())
+                << logExistingAuditEntries(tag);
+            EXPECT_EQ(exp_modification_type, audit_entry->getModificationType())
+                << logExistingAuditEntries(tag);
+            EXPECT_EQ(exp_log_message, audit_entry->getLogMessage())
+                << logExistingAuditEntries(tag);
+        }
+    }
+
+    /// @brief Holds pointers to subnets used in tests.
+    std::vector<Subnet4Ptr> test_subnets_;
+
+    /// @brief Holds pointers to shared networks used in tests.
+    std::vector<SharedNetwork4Ptr> test_networks_;
+
+    /// @brief Holds pointers to option definitions used in tests.
+    std::vector<OptionDefinitionPtr> test_option_defs_;
+
+    /// @brief Holds pointers to options used in tests.
+    std::vector<OptionDescriptorPtr> test_options_;
+
+    /// @brief Holds pointers to classes used in tests.
+    std::vector<ClientClassDefPtr> test_client_classes_;
+
+    /// @brief Holds pointers to the servers used in tests.
+    std::vector<ServerPtr> test_servers_;
+
+    /// @brief Holds timestamp values used in tests.
+    std::map<std::string, boost::posix_time::ptime> timestamps_;
+
+    /// @brief Holds pointer to the backend.
+    boost::shared_ptr<ConfigBackendDHCPv4> cbptr_;
+
+    /// @brief Holds the most recent audit entries.
+    std::map<std::string, AuditEntryCollection> audit_entries_;
+};
+
+// This test verifies that the expected backend type is returned.
+TEST_F(PgSqlConfigBackendDHCPv4Test, getType) {
+    DatabaseConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["password"] = "keatest";
+    params["user"] = "keatest";
+    ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params)));
+    ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+    EXPECT_EQ("postgresql", cbptr_->getType());
+}
+
+// This test verifies that by default localhost is returned as PgSQL connection
+// host.
+TEST_F(PgSqlConfigBackendDHCPv4Test, getHost) {
+    DatabaseConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["password"] = "keatest";
+    params["user"] = "keatest";
+    ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params)));
+    ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+    EXPECT_EQ("localhost", cbptr_->getHost());
+}
+
+// This test verifies that by default port of 0 is returned as PgSQL connection
+// port.
+TEST_F(PgSqlConfigBackendDHCPv4Test, getPort) {
+    DatabaseConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["password"] = "keatest";
+    params["user"] = "keatest";
+    ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+    ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params)));
+    EXPECT_EQ(0, cbptr_->getPort());
+}
+
+// This test verifies that the server can be added, updated and deleted.
+TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteServer) {
+    // Explicitly set modification time to make sure that the time
+    // returned from the database is correct.
+    test_servers_[0]->setModificationTime(timestamps_["yesterday"]);
+    test_servers_[1]->setModificationTime(timestamps_["today"]);
+
+    // Insert the server1 into the database.
+    ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+    {
+        SCOPED_TRACE("CREATE audit entry for server");
+        testNewAuditEntry("dhcp4_server",
+                          AuditEntry::ModificationType::CREATE,
+                          "server set");
+    }
+
+    // It should not be possible to create a duplicate of the logical
+    // server 'all'.
+    auto all_server = Server::create(ServerTag("all"), "this is logical server all");
+    EXPECT_THROW(cbptr_->createUpdateServer4(all_server), isc::InvalidOperation);
+
+    ServerPtr returned_server;
+
+    // An attempt to fetch the server that hasn't been inserted should return
+    // a null pointer.
+    ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server2")));
+    EXPECT_FALSE(returned_server);
+
+    // Try to fetch the server which we expect to exist.
+    ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+    ASSERT_TRUE(returned_server);
+    EXPECT_EQ("server1", returned_server->getServerTag().get());
+    EXPECT_EQ("this is server 1", returned_server->getDescription());
+    EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime());
+
+    // This call is expected to update the existing server.
+    ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+
+    {
+        SCOPED_TRACE("UPDATE audit entry for server");
+        testNewAuditEntry("dhcp4_server",
+                          AuditEntry::ModificationType::UPDATE,
+                          "server set");
+    }
+
+    // Verify that the server has been updated.
+    ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+    ASSERT_TRUE(returned_server);
+    EXPECT_EQ("server1", returned_server->getServerTag().get());
+    EXPECT_EQ("this is server 1 bis", returned_server->getDescription());
+    EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime());
+
+    uint64_t servers_deleted = 0;
+
+    // Try to delete non-existing server.
+    ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server2")));
+    EXPECT_EQ(0, servers_deleted);
+
+    // Make sure that the server1 wasn't deleted.
+    ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+    EXPECT_TRUE(returned_server);
+
+    // Deleting logical server 'all' is not allowed.
+    EXPECT_THROW(cbptr_->deleteServer4(ServerTag()), isc::InvalidOperation);
+
+    // Delete the existing server.
+    ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server1")));
+    EXPECT_EQ(1, servers_deleted);
+
+    {
+        SCOPED_TRACE("DELETE audit entry for server");
+        testNewAuditEntry("dhcp4_server",
+                          AuditEntry::ModificationType::DELETE,
+                          "deleting a server");
+    }
+
+    // Make sure that the server is gone.
+    ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+    EXPECT_FALSE(returned_server);
+}
+
+// This test verifies that it is possible to retrieve all servers from the
+// database and then delete all of them.
+TEST_F(PgSqlConfigBackendDHCPv4Test, getAndDeleteAllServers) {
+    for (auto i = 1; i < test_servers_.size(); ++i) {
+        ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[i]));
+    }
+
+    ServerCollection servers;
+    ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+    ASSERT_EQ(test_servers_.size() - 1, servers.size());
+
+    // All servers should have been returned.
+    EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1")));
+    EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2")));
+    EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3")));
+
+    // The logical server all should not be returned. We merely return the
+    // user configured servers.
+    EXPECT_FALSE(ServerFetcher::get(servers, ServerTag()));
+
+    // Delete all servers and make sure they are gone.
+    uint64_t deleted_servers = 0;
+    ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers4());
+
+    ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+    EXPECT_TRUE(servers.empty());
+
+    // All servers should be gone.
+    EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1")));
+    EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2")));
+    EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3")));
+
+    // The number of deleted server should be equal to the number of
+    // inserted servers. The logical 'all' server should be excluded.
+    EXPECT_EQ(test_servers_.size() - 1, deleted_servers);
+
+    EXPECT_EQ(1, countRows("dhcp4_server"));
+}
+
+class PgSqlConfigBackendDHCPv4DbLostCallbackTest : public ::testing::Test {
+public:
+    PgSqlConfigBackendDHCPv4DbLostCallbackTest()
+        : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+          db_failed_callback_called_(0),
+          io_service_(boost::make_shared<isc::asiolink::IOService>()) {
+        isc::db::DatabaseConnection::db_lost_callback_ = 0;
+        isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+        isc::db::DatabaseConnection::db_failed_callback_ = 0;
+        isc::dhcp::PgSqlConfigBackendImpl::setIOService(io_service_);
+        isc::dhcp::TimerMgr::instance()->setIOService(io_service_);
+        isc::dhcp::CfgMgr::instance().clear();
+    }
+
+    virtual ~PgSqlConfigBackendDHCPv4DbLostCallbackTest() {
+        isc::db::DatabaseConnection::db_lost_callback_ = 0;
+        isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+        isc::db::DatabaseConnection::db_failed_callback_ = 0;
+        isc::dhcp::PgSqlConfigBackendImpl::setIOService(isc::asiolink::IOServicePtr());
+        isc::dhcp::TimerMgr::instance()->unregisterTimers();
+        isc::dhcp::CfgMgr::instance().clear();
+    }
+
+    /// @brief Prepares the class for a test.
+    ///
+    /// Invoked by gtest prior test entry, we create the
+    /// appropriate schema and create a basic DB manager to
+    /// wipe out any prior instance
+    virtual void SetUp() {
+        // Ensure we have the proper schema with no transient data.
+        createPgSQLSchema();
+        isc::dhcp::CfgMgr::instance().clear();
+        isc::dhcp::PgSqlConfigBackendDHCPv4::registerBackendType();
+    }
+
+    /// @brief Pre-text exit clean up
+    ///
+    /// Invoked by gtest upon test exit, we destroy the schema
+    /// we created.
+    virtual void TearDown() {
+        // If data wipe enabled, delete transient data otherwise destroy the schema
+        destroyPgSQLSchema();
+        isc::dhcp::CfgMgr::instance().clear();
+        isc::dhcp::PgSqlConfigBackendDHCPv4::unregisterBackendType();
+    }
+
+    /// @brief Method which returns the back end specific connection
+    /// string
+    virtual std::string validConnectString() {
+        return (validPgSQLConnectionString());
+    }
+
+    /// @brief Method which returns invalid back end specific connection
+    /// string
+    virtual std::string invalidConnectString() {
+        return (connectionString(PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST,
+                                 VALID_USER, VALID_PASSWORD));
+    }
+
+    /// @brief Verifies open failures do NOT invoke db lost callback
+    ///
+    /// The db lost callback should only be invoked after successfully
+    /// opening the DB and then subsequently losing it. Failing to
+    /// open should be handled directly by the application layer.
+    void testNoCallbackOnOpenFailure();
+
+    /// @brief Verifies the CB manager's behavior if DB connection is lost
+    ///
+    /// This function creates a CB manager with a back end that supports
+    /// connectivity lost callback. It verifies connectivity by issuing a known
+    /// valid query. Next it simulates connectivity lost by identifying and
+    /// closing the socket connection to the CB backend. It then reissues the
+    /// query and verifies that:
+    /// -# The Query throws  DbOperationError (rather than exiting)
+    /// -# The registered DbLostCallback was invoked
+    /// -# The registered DbRecoveredCallback was invoked
+    void testDbLostAndRecoveredCallback();
+
+    /// @brief Verifies the CB manager's behavior if DB connection is lost
+    ///
+    /// This function creates a CB manager with a back end that supports
+    /// connectivity lost callback. It verifies connectivity by issuing a known
+    /// valid query. Next it simulates connectivity lost by identifying and
+    /// closing the socket connection to the CB backend. It then reissues the
+    /// query and verifies that:
+    /// -# The Query throws  DbOperationError (rather than exiting)
+    /// -# The registered DbLostCallback was invoked
+    /// -# The registered DbFailedCallback was invoked
+    void testDbLostAndFailedCallback();
+
+    /// @brief Verifies the CB manager's behavior if DB connection is lost
+    ///
+    /// This function creates a CB manager with a back end that supports
+    /// connectivity lost callback. It verifies connectivity by issuing a known
+    /// valid query. Next it simulates connectivity lost by identifying and
+    /// closing the socket connection to the CB backend. It then reissues the
+    /// query and verifies that:
+    /// -# The Query throws  DbOperationError (rather than exiting)
+    /// -# The registered DbLostCallback was invoked
+    /// -# The registered DbRecoveredCallback was invoked after two reconnect
+    /// attempts (once failing and second triggered by timer)
+    void testDbLostAndRecoveredAfterTimeoutCallback();
+
+    /// @brief Verifies the CB manager's behavior if DB connection is lost
+    ///
+    /// This function creates a CB manager with a back end that supports
+    /// connectivity lost callback. It verifies connectivity by issuing a known
+    /// valid query. Next it simulates connectivity lost by identifying and
+    /// closing the socket connection to the CB backend. It then reissues the
+    /// query and verifies that:
+    /// -# The Query throws  DbOperationError (rather than exiting)
+    /// -# The registered DbLostCallback was invoked
+    /// -# The registered DbFailedCallback was invoked after two reconnect
+    /// attempts (once failing and second triggered by timer)
+    void testDbLostAndFailedAfterTimeoutCallback();
+
+    /// @brief Callback function registered with the CB manager
+    bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) {
+        return (++db_lost_callback_called_);
+    }
+
+    /// @brief Flag used to detect calls to db_lost_callback function
+    uint32_t db_lost_callback_called_;
+
+    /// @brief Callback function registered with the CB manager
+    bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) {
+        return (++db_recovered_callback_called_);
+    }
+
+    /// @brief Flag used to detect calls to db_recovered_callback function
+    uint32_t db_recovered_callback_called_;
+
+    /// @brief Callback function registered with the CB manager
+    bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) {
+        return (++db_failed_callback_called_);
+    }
+
+    /// @brief Flag used to detect calls to db_failed_callback function
+    uint32_t db_failed_callback_called_;
+
+    /// The IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_service_;
+};
+
+void
+PgSqlConfigBackendDHCPv4DbLostCallbackTest::testNoCallbackOnOpenFailure() {
+    isc::db::DatabaseConnection::db_lost_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+    // Set the connectivity recovered callback.
+    isc::db::DatabaseConnection::db_recovered_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+    // Set the connectivity failed callback.
+    isc::db::DatabaseConnection::db_failed_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+    std::string access = invalidConnectString();
+
+    // Connect to the CB backend.
+    ASSERT_THROW(ConfigBackendDHCPv4Mgr::instance().addBackend(access), DbOpenError);
+
+    io_service_->poll();
+
+    EXPECT_EQ(0, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndRecoveredCallback() {
+    // Set the connectivity lost callback.
+    isc::db::DatabaseConnection::db_lost_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+    // Set the connectivity recovered callback.
+    isc::db::DatabaseConnection::db_recovered_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+    // Set the connectivity failed callback.
+    isc::db::DatabaseConnection::db_failed_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+    std::string access = validConnectString();
+
+    ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+    // Find the most recently opened socket. Our SQL client's socket should
+    // be the next one.
+    int last_open_socket = findLastSocketFd();
+
+    // Fill holes.
+    FillFdHoles holes(last_open_socket);
+
+    // Connect to the CB backend.
+    ASSERT_NO_THROW(ConfigBackendDHCPv4Mgr::instance().addBackend(access));
+
+    // Find the SQL client socket.
+    int sql_socket = findLastSocketFd();
+    ASSERT_TRUE(sql_socket > last_open_socket);
+
+    // Verify we can execute a query.  We don't care about the answer.
+    ServerCollection servers;
+    ASSERT_NO_THROW_LOG(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()));
+
+    // Now close the sql socket out from under backend client
+    ASSERT_EQ(0, close(sql_socket));
+
+    // A query should fail with DbConnectionUnusable.
+    ASSERT_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()),
+                 DbConnectionUnusable);
+
+    io_service_->poll();
+
+    // Our lost and recovered connectivity callback should have been invoked.
+    EXPECT_EQ(1, db_lost_callback_called_);
+    EXPECT_EQ(1, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndFailedCallback() {
+    // Set the connectivity lost callback.
+    isc::db::DatabaseConnection::db_lost_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+    // Set the connectivity recovered callback.
+    isc::db::DatabaseConnection::db_recovered_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+    // Set the connectivity failed callback.
+    isc::db::DatabaseConnection::db_failed_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+    std::string access = validConnectString();
+    ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+    // Find the most recently opened socket. Our SQL client's socket should
+    // be the next one.
+    int last_open_socket = findLastSocketFd();
+
+    // Fill holes.
+    FillFdHoles holes(last_open_socket);
+
+    // Connect to the CB backend.
+    ASSERT_NO_THROW(ConfigBackendDHCPv4Mgr::instance().addBackend(access));
+
+    // Find the SQL client socket.
+    int sql_socket = findLastSocketFd();
+    ASSERT_TRUE(sql_socket > last_open_socket);
+
+    // Verify we can execute a query.  We don't care about the answer.
+    ServerCollection servers;
+    ASSERT_NO_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()));
+
+    access = invalidConnectString();
+    CfgMgr::instance().clear();
+    // by adding an invalid access will cause the manager factory to throw
+    // resulting in failure to recreate the manager
+    config_ctl_info.reset(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+    const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+    (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+    // Now close the sql socket out from under backend client
+    ASSERT_EQ(0, close(sql_socket));
+
+    // A query should fail with DbConnectionUnusable.
+    ASSERT_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()),
+                 DbConnectionUnusable);
+
+    io_service_->poll();
+
+    // Our lost and failed connectivity callback should have been invoked.
+    EXPECT_EQ(1, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+    // Set the connectivity lost callback.
+    isc::db::DatabaseConnection::db_lost_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+    // Set the connectivity recovered callback.
+    isc::db::DatabaseConnection::db_recovered_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+    // Set the connectivity failed callback.
+    isc::db::DatabaseConnection::db_failed_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+    std::string access = validConnectString();
+    std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+    access += extra;
+    ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+    // Find the most recently opened socket. Our SQL client's socket should
+    // be the next one.
+    int last_open_socket = findLastSocketFd();
+
+    // Fill holes.
+    FillFdHoles holes(last_open_socket);
+
+    // Connect to the CB backend.
+    ASSERT_NO_THROW(ConfigBackendDHCPv4Mgr::instance().addBackend(access));
+
+    // Find the SQL client socket.
+    int sql_socket = findLastSocketFd();
+    ASSERT_TRUE(sql_socket > last_open_socket);
+
+    // Verify we can execute a query.  We don't care about the answer.
+    ServerCollection servers;
+    ASSERT_NO_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()));
+
+    access = invalidConnectString();
+    access += extra;
+    CfgMgr::instance().clear();
+    // by adding an invalid access will cause the manager factory to throw
+    // resulting in failure to recreate the manager
+    config_ctl_info.reset(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+    const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+    (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+    // Now close the sql socket out from under backend client
+    ASSERT_EQ(0, close(sql_socket));
+
+    // A query should fail with DbConnectionUnusable.
+    ASSERT_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()),
+                 DbConnectionUnusable);
+
+    io_service_->poll();
+
+    // Our lost connectivity callback should have been invoked.
+    EXPECT_EQ(1, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+
+    access = validConnectString();
+    access += extra;
+    CfgMgr::instance().clear();
+    config_ctl_info.reset(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+    sleep(1);
+
+    io_service_->poll();
+
+    // Our lost and recovered connectivity callback should have been invoked.
+    EXPECT_EQ(2, db_lost_callback_called_);
+    EXPECT_EQ(1, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+
+    sleep(1);
+
+    io_service_->poll();
+
+    // No callback should have been invoked.
+    EXPECT_EQ(2, db_lost_callback_called_);
+    EXPECT_EQ(1, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+    // Set the connectivity lost callback.
+    isc::db::DatabaseConnection::db_lost_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+    // Set the connectivity recovered callback.
+    isc::db::DatabaseConnection::db_recovered_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+    // Set the connectivity failed callback.
+    isc::db::DatabaseConnection::db_failed_callback_ =
+        std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+    std::string access = validConnectString();
+    std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+    access += extra;
+    ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+    // Find the most recently opened socket. Our SQL client's socket should
+    // be the next one.
+    int last_open_socket = findLastSocketFd();
+
+    // Fill holes.
+    FillFdHoles holes(last_open_socket);
+
+    // Connect to the CB backend.
+    ASSERT_NO_THROW(ConfigBackendDHCPv4Mgr::instance().addBackend(access));
+
+    // Find the SQL client socket.
+    int sql_socket = findLastSocketFd();
+    ASSERT_TRUE(sql_socket > last_open_socket);
+
+    // Verify we can execute a query.  We don't care about the answer.
+    ServerCollection servers;
+    ASSERT_NO_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()));
+
+    access = invalidConnectString();
+    access += extra;
+    CfgMgr::instance().clear();
+    // by adding an invalid access will cause the manager factory to throw
+    // resulting in failure to recreate the manager
+    config_ctl_info.reset(new ConfigControlInfo());
+    config_ctl_info->addConfigDatabase(access);
+    CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+    const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+    (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+    // Now close the sql socket out from under backend client
+    ASSERT_EQ(0, close(sql_socket));
+
+    // A query should fail with DbConnectionUnusable.
+    ASSERT_THROW(servers = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()),
+                 DbConnectionUnusable);
+
+    io_service_->poll();
+
+    // Our lost connectivity callback should have been invoked.
+    EXPECT_EQ(1, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+
+    sleep(1);
+
+    io_service_->poll();
+
+    // Our lost connectivity callback should have been invoked.
+    EXPECT_EQ(2, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(0, db_failed_callback_called_);
+
+    sleep(1);
+
+    io_service_->poll();
+
+    // Our lost and failed connectivity callback should have been invoked.
+    EXPECT_EQ(3, db_lost_callback_called_);
+    EXPECT_EQ(0, db_recovered_callback_called_);
+    EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testNoCallbackOnOpenFailure) {
+    MultiThreadingTest mt(false);
+    testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) {
+    MultiThreadingTest mt(true);
+    testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndRecoveredCallback) {
+    MultiThreadingTest mt(false);
+    testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndFailedCallback) {
+    MultiThreadingTest mt(false);
+    testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) {
+    MultiThreadingTest mt(false);
+    testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) {
+    MultiThreadingTest mt(false);
+    testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PgSQL is handled correctly.
+TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDbLostAndFailedAfterTimeoutCallback();
+}
+
+}
index 9089c9f17f31d973a770352e92a75851bc00f791..2c4f98aeb373cdcf9d29b0e190da25c03a5e6ef5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-2022 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
@@ -74,7 +74,7 @@ public:
 // Setup() and which will ASSERT on failures.
 TEST_F(PgsqlConfigBackendTest, constructor) {
     //  Is this the right config backend type?
-    EXPECT_EQ("pgsql", cbptr_->getType());
+    EXPECT_EQ("postgresql", cbptr_->getType());
 }
 
 }  // namespace
index 821a035509273bf21161c2bc7d5b10dde9ed6cff..5348b201e4012147ffc5735c5640a459840e96b8 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2022 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
@@ -40,7 +40,7 @@ public:
     /// @brief Returns backend type in the textual format.
     ///
     /// @return Name of the storage for configurations, e.g. "mysql",
-    /// "pgsql" and so forth.
+    /// "postgresql" and so forth.
     virtual std::string getType() const = 0;
 
     /// @brief Returns backend host.
index a635a3e1a6726c5cebcf7acbf02b64c3ec2e341e..144967636102265120d74b683642c283ec8badaa 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2022 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
@@ -158,22 +158,22 @@ typedef boost::shared_ptr<TestConfigBackendImpl1> TestConfigBackendImpl1Ptr;
 /// @brief Second implementation of the test config backend.
 ///
 /// It simulates being a Postgres backend installed on the
-/// "pgsql-host" host and running on port 1234.
+/// "postgresql-host" host and running on port 1234.
 class TestConfigBackendImpl2 : public TestConfigBackend {
 public:
 
     /// @brief Returns backend type.
     ///
-    /// @return "pgsql".
+    /// @return "postgresql".
     virtual std::string getType() const {
-        return (std::string("pgsql"));
+        return (std::string("postgresql"));
     }
 
     /// @brief Returns backend host.
     ///
-    /// @return "pgsql-host".
+    /// @return "postgresql-host".
     virtual std::string getHost() const {
-        return (std::string("pgsql-host"));
+        return (std::string("postgresql-host"));
     }
 
     /// @brief Returns backend port.
@@ -350,20 +350,20 @@ public:
         config_mgr_.addBackend("type=mysql");
     }
 
-    /// @brief Creates example database backend called "pgsql".
+    /// @brief Creates example database backend called "postgresql".
     ///
     /// It uses @c TestConfigBackendImpl2.
     void addTestPgSQLBackend() {
-        config_mgr_.registerBackendFactory("pgsql", [](const DatabaseConnection::ParameterMap&)
+        config_mgr_.registerBackendFactory("postgresql", [](const DatabaseConnection::ParameterMap&)
                                           -> TestConfigBackendPtr {
             return (TestConfigBackendImpl2Ptr(new TestConfigBackendImpl2()));
         });
 
         // Actually create the backends.
-        config_mgr_.addBackend("type=pgsql");
+        config_mgr_.addBackend("type=postgresql");
     }
 
-    /// @brief Creates two example database backends called "mysql" and "pgsql".
+    /// @brief Creates two example database backends called "mysql" and "postgresql".
     ///
     /// It uses @c TestConfigBackendImpl1 and @c TestConfigBackendImpl2.
     void addTestBackends() {
@@ -382,9 +382,9 @@ public:
         // Add two properties into the second backend. Both properties share the
         // name so as we can test retrieving multiple records from the same backend.
         config_mgr_.getPool()->createProperty(std::make_pair("cats", 2),
-                                             BackendSelector(BackendSelector::Type::PGSQL));
+                                             BackendSelector(BackendSelector::Type::POSTGRESQL));
         config_mgr_.getPool()->createProperty(std::make_pair("cats", 4),
-                                             BackendSelector(BackendSelector::Type::PGSQL));
+                                             BackendSelector(BackendSelector::Type::POSTGRESQL));
     }
 
     /// Instance of the test configuration manager.
@@ -445,9 +445,9 @@ TEST_F(ConfigBackendMgrTest, getSingleProperty) {
     EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs"));
     EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats"));
 
-    // No dogs in the pgsql backend and no cats in mysql backend.
+    // No dogs in the postgresql backend and no cats in mysql backend.
     EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs",
-                                                   BackendSelector(BackendSelector::Type::PGSQL)));
+                                                   BackendSelector(BackendSelector::Type::POSTGRESQL)));
     EXPECT_EQ(0, config_mgr_.getPool()->getProperty("cats",
                                                    BackendSelector(BackendSelector::Type::MYSQL)));
 
@@ -456,7 +456,7 @@ TEST_F(ConfigBackendMgrTest, getSingleProperty) {
     EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs",
                                                    BackendSelector(BackendSelector::Type::MYSQL)));
     EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats",
-                                                   BackendSelector(BackendSelector::Type::PGSQL)));
+                                                   BackendSelector(BackendSelector::Type::POSTGRESQL)));
 
     // Also make sure that the variant of getProperty function taking two arguments
     // would return the value.
@@ -490,11 +490,11 @@ TEST_F(ConfigBackendMgrTest, getMultipleProperties) {
                                                      BackendSelector(BackendSelector::Type::MYSQL));
     ASSERT_EQ(1, mysql_list.size());
 
-    // There are two cats entries in pgsql.
-    PropertiesList pgsql_list =
+    // There are two cats entries in postgresql.
+    PropertiesList postgresql_list =
         config_mgr_.getPool()->getProperties("cats",
-                                            BackendSelector(BackendSelector::Type::PGSQL));
-    ASSERT_EQ(2, pgsql_list.size());
+                                            BackendSelector(BackendSelector::Type::POSTGRESQL));
+    ASSERT_EQ(2, postgresql_list.size());
 
     // Try to use the backend that is not present.
     EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
@@ -514,10 +514,10 @@ TEST_F(ConfigBackendMgrTest, getAllProperties) {
         config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::MYSQL));
     ASSERT_EQ(2, mysql_list.size());
 
-    // The pgsql backends also holds two entries.
-    PropertiesList pgsql_list =
-        config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::PGSQL));
-    ASSERT_EQ(2, pgsql_list.size());
+    // The postgresql backends also holds two entries.
+    PropertiesList postgresql_list =
+        config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::POSTGRESQL));
+    ASSERT_EQ(2, postgresql_list.size());
 
     // Try to use the backend that is not present.
     EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
index 3ad8e219c64b4eb82b93079186e980280b5ccd9e..91cac343a66846fadfe21b9a5ac12e22e13f0065 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2022 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
@@ -132,8 +132,8 @@ BackendSelector::stringToBackendType(const std::string& type) {
     if (type == "mysql") {
         return (BackendSelector::Type::MYSQL);
 
-    } else if (type == "pgsql") {
-        return (BackendSelector::Type::PGSQL);
+    } else if (type == "postgresql") {
+        return (BackendSelector::Type::POSTGRESQL);
 
     } else if (type == "cql") {
         return (BackendSelector::Type::CQL);
@@ -148,8 +148,8 @@ BackendSelector::backendTypeToString(const BackendSelector::Type& type) {
     switch (type) {
     case BackendSelector::Type::MYSQL:
         return ("mysql");
-    case BackendSelector::Type::PGSQL:
-        return ("pgsql");
+    case BackendSelector::Type::POSTGRESQL:
+        return ("postgresql");
     case BackendSelector::Type::CQL:
         return ("cql");
     default:
index bc969d68aadabd532528da5f39b40c9969b3befd..cc2db1527b2b3723fdedd09404b617feb8c2a112 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2022 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
@@ -61,7 +61,7 @@ public:
     /// as selection criteria.
     enum class Type {
         MYSQL,
-        PGSQL,
+        POSTGRESQL,
         CQL,
         UNSPEC
     };
index 6061e1bd41deb7624f291d895a5113f635f52561..d30941b069fe0d99d521354b875384e8eb31e26b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2022 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
@@ -152,8 +152,8 @@ TEST(BackendSelectorTest, accessMapPortSpec) {
 TEST(BackendSelectorTest, stringToBackendType) {
     EXPECT_EQ(BackendSelector::Type::MYSQL,
               BackendSelector::stringToBackendType("mysql"));
-    EXPECT_EQ(BackendSelector::Type::PGSQL,
-              BackendSelector::stringToBackendType("pgsql"));
+    EXPECT_EQ(BackendSelector::Type::POSTGRESQL,
+              BackendSelector::stringToBackendType("postgresql"));
     EXPECT_EQ(BackendSelector::Type::CQL,
               BackendSelector::stringToBackendType("cql"));
     EXPECT_THROW(BackendSelector::stringToBackendType("unsupported"),
@@ -164,8 +164,8 @@ TEST(BackendSelectorTest, stringToBackendType) {
 TEST(BackendSelectorTest, backendTypeToString) {
     EXPECT_EQ("mysql",
               BackendSelector::backendTypeToString(BackendSelector::Type::MYSQL));
-    EXPECT_EQ("pgsql",
-              BackendSelector::backendTypeToString(BackendSelector::Type::PGSQL));
+    EXPECT_EQ("postgresql",
+              BackendSelector::backendTypeToString(BackendSelector::Type::POSTGRESQL));
     EXPECT_EQ("cql",
               BackendSelector::backendTypeToString(BackendSelector::Type::CQL));
 }
index efd0d06b0835dba78255ba9cfc09a1ff92bafbbf..e5cd0b635e75c24cfeca0c291b00de2929094758 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2022 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
@@ -6,6 +6,7 @@
 
 #include <config.h>
 
+#include <database/db_exceptions.h>
 #include <database/db_log.h>
 #include <pgsql/pgsql_connection.h>
 
@@ -38,6 +39,7 @@ namespace db {
 const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
 
 const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
+const char PgSqlConnection::NULL_KEY[] = ERRCODE_NOT_NULL_VIOLATION;
 
 bool PgSqlConnection::warned_about_tls = false;
 
@@ -162,7 +164,9 @@ PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
                             statement.nbparams, statement.types));
     if (PQresultStatus(r) != PGRES_COMMAND_OK) {
         isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
-                  << statement.text << ", reason: " << PQerrorMessage(conn_));
+                  << " name: " << statement.name
+                  << ", reason: " << PQerrorMessage(conn_)
+                  << ", text: " << statement.text);
     }
 }
 
@@ -348,6 +352,18 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
                       "fatal database error or connectivity lost");
         }
 
+        // Failure: check for the special case of duplicate entry.
+        if (compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+            isc_throw(DuplicateEntry, "statement: " << statement.name
+                      << ", reason: " << PQerrorMessage(conn_));
+        }
+
+        // Failure: check for the special case of null key violation.
+        if (compareError(r, PgSqlConnection::NULL_KEY)) {
+            isc_throw(NullKeyError, "statement: " << statement.name
+                      << ", reason: " << PQerrorMessage(conn_));
+        }
+
         // Apparently it wasn't fatal, so we throw with a helpful message.
         const char* error_message = PQerrorMessage(conn_);
         isc_throw(DbOperationError, "Statement exec failed for: "
index 1de441f87c3bc801f05b55f74cd8c9574412ffae..2de244e73d41f88c427a562c52322c02085eeb90 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2022 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
@@ -25,7 +25,7 @@ const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
 // @todo This allows us to use an initializer list (since we can't
 // require C++11).  It's unlikely we'd go past this many a single
 // statement.
-const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 32;
+const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 128;
 
 /// @brief Define a PostgreSQL statement.
 ///
@@ -199,6 +199,8 @@ class PgSqlConnection : public db::DatabaseConnection {
 public:
     /// @brief Define the PgSql error state for a duplicate key error.
     static const char DUPLICATE_KEY[];
+    /// @brief Define the PgSql error state for a null foreign key error.
+    static const char NULL_KEY[];
 
     /// @brief Function invoked to process fetched row.
     typedef std::function<void(PgSqlResult&, int)> ConsumeResultRowFun;
index 9cee1d574033ad4265a226eee28165cfb002c731..a16d8e057c10efd27d00a380537fef744cf1dce3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2022 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
@@ -56,7 +56,7 @@ void PsqlBindArray::insert(const char* value, size_t index) {
 }
 
 void PsqlBindArray::insert(const std::string& value, size_t index) {
-    if (index >= values_.size()) {
+    if (index && index >= values_.size()) {
         isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index
                   << ", is larger than the array size: " << values_.size());
     }
@@ -66,6 +66,16 @@ void PsqlBindArray::insert(const std::string& value, size_t index) {
     formats_.insert(formats_.begin() + index, TEXT_FMT);
 }
 
+void PsqlBindArray::popBack() {
+    if (values_.size() == 0) {
+        isc_throw(OutOfRange, "PsqlBindArray::pop_back - array empty");
+    }
+
+    values_.erase(values_.end() - 1);
+    lengths_.erase(lengths_.end() - 1);
+    formats_.erase(formats_.end() - 1);
+}
+
 void PsqlBindArray::add(const std::vector<uint8_t>& data) {
     values_.push_back(reinterpret_cast<const char*>(&(data[0])));
     lengths_.push_back(data.size());
@@ -146,20 +156,11 @@ void PsqlBindArray::addTempString(const std::string& str) {
 }
 
 void
-PsqlBindArray::addOptionalString(const util::Optional<std::string>& value) {
-    if (value.unspecified()) {
-        addNull();
-    } else {
-        add(value);
-    }
-}
-
-void
-PsqlBindArray::addOptionalBool(const util::Optional<bool>& value) {
+PsqlBindArray::addOptional(const util::Optional<std::string>& value) {
     if (value.unspecified()) {
         addNull();
     } else {
-        add(value);
+        addTempString(value);
     }
 }
 
@@ -243,6 +244,18 @@ PsqlBindArray::add(const ElementPtr& value) {
     addTempString(ss.str());
 }
 
+void
+PsqlBindArray::add(const ConstElementPtr& value) {
+    if (!value) {
+        addNull();
+        return;
+    }
+
+    std::ostringstream ss;
+    value->toJSON(ss);
+    addTempString(ss.str());
+}
+
 std::string
 PsqlBindArray::toText() const {
     std::ostringstream stream;
index f45a5391f6f8e557654496da1afc0150d80c31a6..d81474da863dae32c9e255525e639bcf600d73fe 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2022 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
@@ -222,17 +222,25 @@ struct PsqlBindArray {
     /// @brief Inserts a string value to the bind array before the given index
     ///
     /// Inserts a TEXT_FMT value into the bind array before the element
-    /// position given by index, using the given given string as the data source.
+    /// position given by index, using the given given string as the data
+    /// source.
+    ///
     /// The caller is responsible for ensuring that string parameter remains in
     /// scope until the bind array has been discarded.
     ///
     /// @param value char array containing the null-terminated text to add.
-    /// @param index element position before which the string should be inserted.
+    /// @param index element position before which the string should be
+    /// inserted.
     ///
     /// @throw DbOperationError if value is NULL.
     /// @throw OutOfRange if the index is beyond the end of the array.
     void insert(const std::string& value, size_t index);
 
+    /// @brief Removes the last entry in the bind array.
+    ///
+    /// @throw OutOfRange if array is empty.
+    void popBack();
+
     /// @brief Adds a vector of binary data to the bind array.
     ///
     /// Adds a BINARY_FMT value to the end of the bind array using the
@@ -338,21 +346,19 @@ struct PsqlBindArray {
 
     /// @brief Adds an @c Optional string to the bind array.
     ///
-    /// @param value Optional string value to add
-    void addOptionalString(const util::Optional<std::string>& value);
-
-    /// @brief Adds an @c Optional boolean to the bind array.
+    /// Optional strings require adding a temp string to the
+    /// bind array, unlike other types which implicitly do so.
     ///
-    /// @param value Optional boolean value to add
-    void addOptionalBool(const util::Optional<bool>& value);
+    /// @param value Optional string value to add
+    void addOptional(const util::Optional<std::string>& value);
 
-    /// @brief Adds an @c Optional of integer type to the bind array.
+    /// @brief Adds an @c Optional<type> value to the bind array.
     ///
-    /// @tparam T Numeric type corresponding to the binding type, e.g.
-    /// @c uint8_t, @c uint16_t etc.
-    /// @param value Optional integer of type T.
+    /// @tparam T variable type corresponding to the binding type, e.g.
+    /// @c string, bool, uint8_t, @c uint16_t etc.
+    /// @param value Optional of type T.
     template<typename T>
-    void addOptionalInteger(const util::Optional<T>& value) {
+    void addOptional(const util::Optional<T>& value) {
         if (value.unspecified()) {
             addNull();
         } else {
@@ -417,6 +423,15 @@ struct PsqlBindArray {
     /// @throw DbOperationError if value is NULL.
     void add(const data::ElementPtr& value);
 
+    /// @brief Adds a ConstElementPtr to the bind array
+    ///
+    /// Adds a TEXT_FMT value to the end of the bind array containing
+    /// the JSON text output by given ElementPtr::toJSON().
+    ///
+    /// @param value ElementPtr containing Element tree to add.
+    /// @throw DbOperationError if value is NULL.
+    void add(const data::ConstElementPtr& value);
+
     /// @brief Dumps the contents of the array to a string.
     /// @return std::string containing the dump
     std::string toText() const;
index 92d9c77fca6caaa54baf506b23c22c2d0ef1d9a1..2c6939d558fb232575e390819d13dc4b91bbc9a2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2022 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
@@ -171,8 +171,8 @@ TEST(PsqlBindArray, addOptionalString) {
         Optional<std::string> not_empty("whoopee!");
 
         // Add strings to the array.
-        b.addOptionalString(empty);
-        b.addOptionalString(not_empty);
+        b.addOptional(empty);
+        b.addOptional(not_empty);
     }
 
     // We've left bind scope, everything should be intact.
@@ -200,9 +200,9 @@ TEST(PsqlBindArray, addOptionalBool) {
         Optional<bool> am_true(true);
 
         // Add booleans to the array.
-        b.addOptionalBool(empty);
-        b.addOptionalBool(am_false);
-        b.addOptionalBool(am_true);
+        b.addOptional(empty);
+        b.addOptional(am_false);
+        b.addOptional(am_true);
     }
 
     // We've left bind scope, everything should be intact.
@@ -231,8 +231,8 @@ TEST(PsqlBindArray, addOptionalInteger) {
         Optional<uint32_t> not_empty(123);
 
         // Add the integers to the array..
-        b.addOptionalInteger(empty);
-        b.addOptionalInteger(not_empty);
+        b.addOptional(empty);
+        b.addOptional(not_empty);
     }
 
     // We've left bind scope, everything should be intact.
@@ -1046,6 +1046,42 @@ TEST(PsqlBindArray, insertString) {
     EXPECT_EQ(expected, b.toText());
 }
 
+TEST(PsqlBindArray, popBackTest) {
+    PsqlBindArray b;
+
+    // Popping on an empty array should throw.
+    ASSERT_THROW_MSG(b.popBack(), OutOfRange,
+                     "PsqlBindArray::pop_back - array empty");
+
+    // Add five integers.
+    for (int i = 1; i < 6; ++i) {
+        b.add(i);
+    }
+
+    // Verify size.
+    EXPECT_EQ(b.size(), 5);
+
+    // Pop one off.
+    ASSERT_NO_THROW_LOG(b.popBack());
+
+    // Verify size.
+    EXPECT_EQ(b.size(), 4);
+
+    // Pop another one off.
+    ASSERT_NO_THROW_LOG(b.popBack());
+
+    // Verify size.
+    EXPECT_EQ(b.size(), 3);
+
+    // This is what we should have left.
+    std::string expected =
+        "0 : \"1\"\n"
+        "1 : \"2\"\n"
+        "2 : \"3\"\n";
+
+    EXPECT_EQ(expected, b.toText());
+}
+
 /// @brief Verify that we can read and write IPv4 addresses
 /// using INET columns.
 TEST_F(PgSqlBasicsTest, inetTest4) {