]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[4277] Bare bones implementation of PgSqlHostDataSource
authorThomas Markwalder <tmark@isc.org>
Tue, 21 Jun 2016 17:21:40 +0000 (13:21 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 21 Jun 2016 17:21:40 +0000 (13:21 -0400)
src/lib/dhcpsrv
    pgsql_host_data_source.c
    pgsql_host_data_source.h  - new files, preliminary implementation

src/lib/dhcpsrv/Makefile.am
    Added new files pgsql_host_data_source.cc, pgsql_host_data_source.h

src/lib/dhcpsrv/dhcpsrv_messages.mes
    Added log messages DHCPSRV_PGSQL_HOST_DB_GET_VERSION, DHCPSRV_PGSQL_START_TRANSACTION

src/lib/dhcpsrv/pgsql_connection.cc
src/lib/dhcpsrv/pgsql_connection.h
    Added PgSqlTransaction
    Added PgSqlConnection::startTransaction()

src/lib/dhcpsrv/pgsql_exchange.cc
src/lib/dhcpsrv/pgsql_exchange.h
    PsqlBindArray
    - Added storage of conversion strings used for bound values
    - Added add() variants for uint8_t, IOAddress, uint8_t buffer
    - Added templated variant for miscellaneous types

    PgSqlExchange
    - Removed getColumnValue variants for various integers, replaced
    with templated version for miscellaneous types

src/lib/dhcpsrv/pgsql_lease_mgr.cc
    Added todo comment to remember to account for hwaddr columns added to lease6

src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc
    TEST(PsqlBindArray, basicOperation) - new test to exercise bind functions

src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/pgsql_connection.cc
src/lib/dhcpsrv/pgsql_connection.h
src/lib/dhcpsrv/pgsql_exchange.cc
src/lib/dhcpsrv/pgsql_exchange.h
src/lib/dhcpsrv/pgsql_host_data_source.cc [new file with mode: 0644]
src/lib/dhcpsrv/pgsql_host_data_source.h [new file with mode: 0644]
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc

index 186cbaf131eb73a9a63c3450f7fedb9ca773ca75..16c8c6449775d71ac8fb3d78f113046b71ea3e3b 100755 (executable)
@@ -136,6 +136,7 @@ libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
 if HAVE_PGSQL
 libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h
 libkea_dhcpsrv_la_SOURCES += pgsql_exchange.cc pgsql_exchange.h
+libkea_dhcpsrv_la_SOURCES += pgsql_host_data_source.cc pgsql_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
 endif
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
index de97bce0bb29250e08334dd8c09a2c9342822d0a..1604e4771fac570d37f20758b91d39b7d0aa5d29 100644 (file)
@@ -634,6 +634,10 @@ An error message indicating that communication with the MySQL database server
 has been lost.  When this occurs the server exits immediately with a non-zero
 exit code.  This is most likely due to a network issue.
 
+% DHCPSRV_PGSQL_HOST_DB_GET_VERSION obtaining schema version information for the PostgreSQL hosts database
+A debug message issued when the server is about to obtain schema version
+information from the PostgreSQL hosts database.
+
 % DHCPSRV_PGSQL_GET_ADDR4 obtaining IPv4 lease for address %1
 A debug message issued when the server is attempting to obtain an IPv4
 lease from the PostgreSQL database for the specified address.
@@ -696,6 +700,15 @@ connection including database name and username needed to access it
 The code has issued a rollback call.  All outstanding transaction will
 be rolled back and not committed to the database.
 
+% DHCPSRV_PGSQL_START_TRANSACTION starting a new PostgreSQL transaction
+A debug message issued when a new PostgreSQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
 % DHCPSRV_PGSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
 A debug message issued when the server is attempting to update IPv4
 lease from the PostgreSQL database for the specified address.
index b7ef34db2f715e6b87af97f7a5703bb60b947270..0f6f69c8f6cb430a492bf964a574a813a0a8e441 100644 (file)
@@ -35,6 +35,25 @@ const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
 
 const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
 
+PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
+    : conn_(conn), committed_(false) {
+    conn_.startTransaction();
+}
+
+PgSqlTransaction::~PgSqlTransaction() {
+    // Rollback if the PgSqlTransaction::commit wasn't explicitly
+    // called.
+    if (!committed_) {
+        conn_.rollback();
+    }
+}
+
+void
+PgSqlTransaction::commit() {
+    conn_.commit();
+    committed_ = true;
+}
+
 PgSqlConnection::~PgSqlConnection() {
     if (conn_) {
         // Deallocate the prepared queries.
@@ -192,6 +211,18 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
     }
 }
 
+void
+PgSqlConnection::startTransaction() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_PGSQL_START_TRANSACTION);
+    PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const char* error_message = PQerrorMessage(conn_);
+        isc_throw(DbOperationError, "unable to start transaction" 
+                  << error_message);
+    }
+}
+
 void
 PgSqlConnection::commit() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
index 68c1cd7bf7a7f4fb438e95c06aee2a894946f74c..ae3bd946ef0b7c00ab7fefb2f4cfa2c4c08dbabd 100755 (executable)
@@ -53,6 +53,7 @@ const size_t OID_NONE = 0;   // PostgreSQL infers proper type
 const size_t OID_BOOL = 16;
 const size_t OID_BYTEA = 17;
 const size_t OID_INT8 = 20;  // 8 byte int
+const size_t OID_INT4 = 23;  // 4 byte int
 const size_t OID_INT2 = 21;  // 2 byte int
 const size_t OID_TIMESTAMP = 1114;
 const size_t OID_VARCHAR = 1043;
@@ -176,6 +177,62 @@ private:
     PGconn* pgconn_;      ///< Postgresql connection
 };
 
+/// @brief Forward declaration to @ref PgSqlConnection.
+class PgSqlConnection;
+
+/// @brief RAII object representing PostgreSQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref PgSqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref PgSqlTransaction::commit commits changes to the database
+/// and the changes remain in the database when the instance of the
+/// class is destroyed. If the class instance is destroyed before the
+/// @ref PgSqlTransaction::commit is called, the transaction is rolled
+/// back. The rollback on destruction guarantees that partial data is
+/// not stored in the database when there is an error during any
+/// of the operations belonging to a transaction.
+///
+/// The default PostgreSQL backend configuration enables 'autocommit'.
+/// Starting a transaction overrides 'autocommit' setting for this
+/// particular transaction only. It does not affect the global 'autocommit'
+/// setting for the database connection, i.e. all modifications to the
+/// database which don't use transactions will still be auto committed.
+class PgSqlTransaction : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Starts transaction by making a "START TRANSACTION" query.
+    ///
+    /// @param conn PostgreSQL connection to use for the transaction. This
+    /// connection will be later used to commit or rollback changes.
+    ///
+    /// @throw DbOperationError if "START TRANSACTION" query fails.
+    PgSqlTransaction(PgSqlConnection& conn);
+
+    /// @brief Destructor.
+    ///
+    /// Rolls back the transaction if changes haven't been committed.
+    ~PgSqlTransaction();
+
+    /// @brief Commits transaction.
+    void commit();
+
+private:
+
+    /// @brief Holds reference to the PostgreSQL database connection.
+    PgSqlConnection& conn_;
+
+    /// @brief Boolean flag indicating if the transaction has been committed.
+    ///
+    /// This flag is used in the class destructor to assess if the
+    /// transaction should be rolled back.
+    bool committed_;
+};
+
 /// @brief Common PgSql Connector Pool
 ///
 /// This class provides common operations for PgSql database connection
@@ -218,18 +275,23 @@ public:
     /// @throw DbOpenError Error opening the database
     void openDatabase();
 
+    /// @brief Start a transaction
+    ///
+    /// Starts a transaction.
+    ///
+    /// @throw DbOperationError If the transaction start failed.
+    void startTransaction();
+
     /// @brief Commit Transactions
     ///
-    /// Commits all pending database operations. On databases that don't
-    /// support transactions, this is a no-op.
+    /// Commits all pending database operations.
     ///
     /// @throw DbOperationError If the commit failed.
     void commit();
 
     /// @brief Rollback Transactions
     ///
-    /// Rolls back all pending database operations. On databases that don't
-    /// support transactions, this is a no-op.
+    /// Rolls back all pending database operations.
     ///
     /// @throw DbOperationError If the rollback failed.
     void rollback();
index 436af0b0d69c20de837af83b534b723452c8db75..b25bf93cc6afacb4333811b7de6c1376096bb4c6 100644 (file)
@@ -38,10 +38,38 @@ void PsqlBindArray::add(const std::vector<uint8_t>& data) {
     formats_.push_back(BINARY_FMT);
 }
 
+void PsqlBindArray::add(const uint8_t* data, const size_t len) {
+    values_.push_back(reinterpret_cast<const char*>(&(data[0])));
+    lengths_.push_back(len);
+    formats_.push_back(BINARY_FMT);
+}
+
 void PsqlBindArray::add(const bool& value)  {
     add(value ? TRUE_STR : FALSE_STR);
 }
 
+void PsqlBindArray::add(const uint8_t& byte) {
+    // We static_cast to an unsigned int, otherwise lexcial_cast may to
+    // treat byte as a character, which yields "" for unprintable values 
+    bindString(boost::lexical_cast<std::string>
+                              (static_cast<unsigned int>(byte)));
+}
+
+void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
+    if (addr.isV4()) {
+        bindString(boost::lexical_cast<std::string>
+                   (static_cast<uint32_t>(addr)));
+    } else {
+        bindString(addr.toText());
+    }
+}
+
+// eventually this should replace add(std::string)
+void PsqlBindArray::bindString(const std::string& str) {
+    bound_strs_.push_back(StringPtr(new std::string(str)));
+    PsqlBindArray::add((bound_strs_.back())->c_str());
+}
+
 std::string PsqlBindArray::toText() const {
     std::ostringstream stream;
     for (int i = 0; i < values_.size(); ++i) {
@@ -135,32 +163,6 @@ PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
     }
 }
 
-void
-PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
-                              const size_t col, uint32_t &value) const {
-    const char* data = getRawColumnValue(r, row, col);
-    try {
-        value = boost::lexical_cast<uint32_t>(data);
-    } catch (const std::exception& ex) {
-        isc_throw(DbOperationError, "Invalid uint32_t data: " << data
-                  << " for: " << getColumnLabel(col) << " row:" << row
-                  << " : " << ex.what());
-    }
-}
-
-void
-PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
-                              const size_t col, int32_t &value) const {
-    const char* data = getRawColumnValue(r, row, col);
-    try {
-        value = boost::lexical_cast<int32_t>(data);
-    } catch (const std::exception& ex) {
-        isc_throw(DbOperationError, "Invalid int32_t data: " << data
-                  << " for: " << getColumnLabel(col) << " row:" << row
-                  << " : " << ex.what());
-    }
-}
-
 void
 PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
                               const size_t col, uint8_t &value) const {
index 64c264b0178f20e0d00a872651bcbe49c0db8a0e..8a2870abedc07399e081fc1af06664b3e7020660 100644 (file)
@@ -7,10 +7,16 @@
 #ifndef PGSQL_EXCHANGE_H
 #define PGSQL_EXCHANGE_H
 
+#include <asiolink/io_address.h>
 #include <dhcpsrv/pgsql_connection.h>
 
+#include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
 #include <stdint.h>
 #include <vector>
+#include <iostream>
 
 namespace isc {
 namespace dhcp {
@@ -24,6 +30,11 @@ namespace dhcp {
 /// be valid for the duration of the PostgreSQL statement execution.  In other
 /// words populating them with pointers to values that go out of scope before
 /// statement is executed is a bad idea.
+
+/// @brief smart pointer to strings used by PsqlBindArray to ensure scope
+/// of strings supplying exchange values 
+typedef boost::shared_ptr<std::string> StringPtr;
+
 struct PsqlBindArray {
     /// @brief Vector of pointers to the data values.
     std::vector<const char *> values_;
@@ -74,14 +85,27 @@ struct PsqlBindArray {
     /// @param value std::string containing the value to add.
     void add(const std::string& value);
 
-    /// @brief Adds a binary value to the bind array.
+    /// @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
-    /// given vector as the data source.
+    /// given vector as the data source.  NOTE this does not replicate
+    /// the vector, so it must remain in scope until the bind array
+    /// is destroyed.
     ///
     /// @param data vector of binary bytes.
     void add(const std::vector<uint8_t>& data);
 
+    /// @brief Adds a buffer of binary data to the bind array.
+    ///
+    /// Adds a BINARY_FMT value to the end of the bind array using the
+    /// given vector as the data source. NOTE this does not replicate
+    /// the buffer, so it must remain in scope until the bind array
+    /// is destroyed.
+    ///
+    /// @param data buffer of binary data.
+    /// @param len  number of bytes of data in buffer 
+    void add(const uint8_t* data, const size_t len);
+
     /// @brief Adds a boolean value to the bind array.
     ///
     /// Converts the given boolean value to its corresponding to PostgreSQL
@@ -90,11 +114,62 @@ struct PsqlBindArray {
     /// @param value bool value to add.
     void add(const bool& value);
 
+    /// @brief Adds a uint8_t value to the bind array.
+    ///
+    /// Converts the given uint8_t value to its corresponding numeric string
+    /// literal and adds it as a TEXT_FMT value to the bind array.
+    ///
+    /// @param value bool value to add.
+    void add(const uint8_t& byte);
+
+    /// @brief Adds a the given IOAddress value to the bind array.
+    ///
+    /// Converts the IOAddress, based on its protocol family, to the 
+    /// corresponding string literal and adds it as a TEXT_FMT value to 
+    /// the bind array.
+    ///
+    /// @param value bool value to add.
+    void add(const isc::asiolink::IOAddress& addr);
+
+    /// @brief Adds a the given value to the bind array.
+    ///
+    /// Converts the given value its corresponding string literal
+    /// boost::lexical_cast and adds it as a TEXT_FMT value to the bind array.
+    ///
+    /// @param value bool value to add.
+    template<typename T>
+    void add(const T& numeric) {
+        bindString(boost::lexical_cast<std::string>(numeric));
+    }
+
+    /// @brief Binds a the given string to the bind array.
+    ///
+    /// Prior to added the The given string the vector of exchange values,
+    /// it duplicated as a StringPtr and saved internally.  This garauntees
+    /// the string remains in scope until the PsqlBindArray is destroyed,
+    /// without the caller maintaining the string values. 
+    ///
+    /// @param value bool value to add.
+    void bindString(const std::string& str);
+
+    //std::vector<const std::string> getBoundStrs() {
+    std::vector<StringPtr> getBoundStrs() {
+        return (bound_strs_);
+    }
+
     /// @brief Dumps the contents of the array to a string.
     /// @return std::string containing the dump
     std::string toText() const;
+
+private:
+    /// @brief vector of strings which supplied the values
+    std::vector<StringPtr> bound_strs_;
+
 };
 
+/// @brief Defines a smart pointer to PsqlBindArray
+typedef boost::shared_ptr<PsqlBindArray> PsqlBindArrayPtr;
+
 /// @brief Base class for marshalling data to and from PostgreSQL.
 ///
 /// Provides the common functionality to set up binding information between
@@ -179,7 +254,7 @@ public:
     void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
                         bool &value) const;
 
-    /// @brief Fetches an integer text column as a uint32_t.
+    /// @brief Fetches an integer text column as a uint8_t.
     ///
     /// @param r the result set containing the query results
     /// @param row the row number within the result set
@@ -189,21 +264,12 @@ public:
     /// @throw  DbOperationError if the value cannot be fetched or is
     /// invalid.
     void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
-                        uint32_t &value) const;
+                        uint8_t &value) const;
 
-    /// @brief Fetches an integer text column as a int32_t.
+    /// @brief Fetches a text column as the given value type
     ///
-    /// @param r the result set containing the query results
-    /// @param row the row number within the result set
-    /// @param col the column number within the row
-    /// @param[out] value parameter to receive the converted value
-    ///
-    /// @throw  DbOperationError if the value cannot be fetched or is
-    /// invalid.
-    void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
-                        int32_t &value) const;
-
-    /// @brief Fetches an integer text column as a uint8_t.
+    /// Uses boost::lexicalcast to convert the text column value into
+    /// a value of type T. 
     ///
     /// @param r the result set containing the query results
     /// @param row the row number within the result set
@@ -212,8 +278,18 @@ public:
     ///
     /// @throw  DbOperationError if the value cannot be fetched or is
     /// invalid.
+    template<typename T>
     void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
-                        uint8_t &value) const;
+                        T& value) const {
+        const char* data = getRawColumnValue(r, row, col);
+        try {
+            value = boost::lexical_cast<T>(data);
+        } catch (const std::exception& ex) {
+            isc_throw(DbOperationError, "Invalid data: " << data
+                      << " for: " << getColumnLabel(col) << " row:" << row
+                      << " : " << ex.what());
+        }
+    }
 
     /// @brief Converts a column in a row in a result set to a binary bytes
     ///
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc
new file mode 100644 (file)
index 0000000..385f26e
--- /dev/null
@@ -0,0 +1,2220 @@
+// Copyright (C) 2016 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 <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/pgsql_host_data_source.h>
+#include <dhcpsrv/db_exceptions.h>
+#include <util/buffer.h>
+#include <util/optional_value.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/static_assert.hpp>
+
+#include <stdint.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+#if 0
+/// @brief Maximum size of an IPv6 address represented as a text string.
+///
+/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
+/// colon separators.
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
+
+/// @brief Maximum length of classes stored in a dhcp4/6_client_classes
+/// columns.
+const size_t CLIENT_CLASSES_MAX_LEN = 255;
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+/// @brief Maximum length of option value.
+const size_t OPTION_VALUE_MAX_LEN = 4096;
+
+/// @brief Maximum length of option value specified in textual format.
+const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
+
+/// @brief Maximum length of option space name.
+const size_t OPTION_SPACE_MAX_LEN = 128;
+#endif
+
+/// @brief Numeric value representing last supported identifier.
+///
+/// This value is used to validate whether the identifier type stored in
+/// a database is within bounds. of supported identifiers.
+const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
+
+/// @brief Maximum length of DHCP identifier value.
+const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// information from the 'hosts' table.
+///
+/// This class is used to insert and retrieve entries from the 'hosts' table.
+/// The queries used with this class do not retrieve IPv6 reservations or
+/// options associated with a host to minimize impact on performance. Other
+/// classes derived from @ref PgSqlHostExchange should be used to retrieve
+/// information about IPv6 reservations and options.
+class PgSqlHostExchange : public PgSqlExchange {
+private:
+
+    /// @brief Column numbers for each column in the hosts table.
+    /// These are used for both retrieving data and for looking up
+    /// column labels for logging.  Note that their numeric order
+    /// MUST match that of the column order in the hosts table.
+    static const int HOST_ID_COL = 0;
+    static const int DHCP_IDENTIFIER_COL = 1;
+    static const int DHCP_IDENTIFIER_TYPE_COL = 2;
+    static const int DHCP4_SUBNET_ID_COL = 3;
+    static const int DHCP6_SUBNET_ID_COL = 4;
+    static const int IPV4_ADDRESS_COL = 5;
+    static const int HOSTNAME_COL = 6;
+    static const int DHCP4_CLIENT_CLASSES_COL = 7;
+    static const int DHCP6_CLIENT_CLASSES_COL = 8;
+    /// @brief Number of columns returned for SELECT queries send by this class.
+    static const size_t HOST_COLUMNS = 9;
+
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param additional_columns_num This value is set by the derived classes
+    /// to indicate how many additional columns will be returned by SELECT
+    /// queries performed by the derived class. This constructor will allocate
+    /// resources for these columns, e.g. binding table, error indicators.
+    PgSqlHostExchange(const size_t additional_columns_num = 0)
+        : columns_num_(HOST_COLUMNS + additional_columns_num),
+          columns_(columns_num_), host_id_(0) {
+        // Set the column names for use by this class. This only comprises
+        // names used by the PgSqlHostExchange class. Derived classes will
+        // need to set names for the columns they use.  Currenty these are
+        // only used for logging purposes.
+        columns_[HOST_ID_COL] = "host_id";
+        columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
+        columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
+        columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
+        columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
+        columns_[IPV4_ADDRESS_COL] = "ipv4_address";
+        columns_[HOSTNAME_COL] = "hostname";
+        columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
+        columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
+
+        BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+    };
+
+    /// @brief Virtual destructor.
+    virtual ~PgSqlHostExchange() {
+    }
+
+    /// @brief Returns index of the first uninitialized column name.
+    ///
+    /// This method is called by the derived classes to determine which
+    /// column indexes are available for the derived classes within a
+    /// binding array, error array and column names. This method
+    /// determines the first available index by searching the first
+    /// empty value within the columns_ vector. Previously we relied on
+    /// the fixed values set for each class, but this was hard to maintain
+    /// when new columns were added to the SELECT queries. It required
+    /// modifying indexes in all derived classes.
+    ///
+    /// Derived classes must call this method in their constructors and
+    /// use returned value as an index for the first column used by the
+    /// derived class and increment this value for each subsequent column.
+    size_t findAvailColumn() const {
+        std::vector<std::string>::const_iterator empty_column =
+            std::find(columns_.begin(), columns_.end(), std::string());
+        return (std::distance(columns_.begin(), empty_column));
+    }
+
+    /// @todo TKM Not certain this is actually needed ....
+    /// @brief Returns value of host id.
+    ///
+    /// This method is used by derived classes.
+    uint64_t getHostId() const {
+        return (host_id_);
+    };
+
+    /// @brief Populate a bind array from a host
+    ///
+    /// Constructs a PsqlBindArray for sending data stored in a Host object
+    /// to the database.
+    ///
+    /// @param host Host object to be added to the database.
+    ///        None of the fields in the host reservation are modified -
+    ///        the host data is only read.
+    ///
+    /// @return pointer to newly constructed bind_array containing the 
+    /// bound values extracted from host
+    ///
+    /// @throw DbOperationError if bind_array cannot be populated.
+    PsqlBindArrayPtr 
+    createBindForSend(const HostPtr& host) {
+        if (!host) {
+            isc_throw(BadValue, "createBindForSend:: host object is NULL");
+        }
+
+        // Store the host to ensure bound values remain in scope
+        host_ = host;
+
+        // Bind the host data to the array 
+        PsqlBindArrayPtr bind_array(new PsqlBindArray());
+        try {
+            // host_id : is auto_incremented skip it
+
+            // dhcp_identifier : BYTEA NOT NULL
+            bind_array->add(host->getIdentifier());
+
+            // dhcp_identifier_type : SMALLINT NOT NULL
+            bind_array->add(host->getIdentifierType());
+
+            // dhcp4_subnet_id : INT NULL
+            bind_array->add(host->getIPv4SubnetID());
+
+            // dhcp6_subnet_id : INT NULL
+            bind_array->add(host->getIPv6SubnetID());
+
+            // ipv4_address : BIGINT NULL 
+            bind_array->add(host->getIPv4Reservation());
+            // hostname : VARCHAR(255) NULL
+            bind_array->bindString(host->getHostname());
+
+            // dhcp4_client_classes : VARCHAR(255) NULL
+            // Override default separator to not include space after comma.
+            bind_array->bindString(host->getClientClasses4().toText(","));
+
+            // dhcp6_client_classes : VARCHAR(255) NULL
+            bind_array->bindString(host->getClientClasses6().toText(","));
+        } catch (const std::exception& ex) {
+            host_.reset();
+            isc_throw(DbOperationError,
+                      "Could not create bind array from Host: "
+                      << host->getHostname() << ", reason: " << ex.what());
+        }
+
+        return (bind_array);
+    };
+
+    /// @brief Creates a Host object from a given row in a result set.
+    ///
+    /// @param r result set containing one or rows from the hosts table
+    /// @param row row number within the result set from to create the Lease4
+    /// object.
+    ///
+    /// @return HostPtr to the newly created Host object
+    /// @throw DbOperationError if the host cannot be created.
+    HostPtr convertFromDatabase(const PgSqlResult& r, int row) {
+
+        // host_id INT NOT NULL
+        // @todo going to have to deal with uint64_t versus INT etc...
+        HostID host_id;
+        getColumnValue(r, row, HOST_ID_COL, host_id); 
+
+        // dhcp_identifier : BYTEA NOT NULL
+        uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
+        size_t identifier_len;
+        convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
+                             sizeof(identifier_value), identifier_len);
+
+        // dhcp_identifier_type : SMALLINT NOT NULL
+        uint8_t type;
+        getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
+        if (type > MAX_IDENTIFIER_TYPE) {
+            isc_throw(BadValue, "invalid dhcp identifier type returned: "
+                      << static_cast<int>(type));
+        }
+
+        Host::IdentifierType identifier_type =
+            static_cast<Host::IdentifierType>(type);
+
+        // dhcp4_subnet_id : INT NULL
+        uint32_t subnet_id; 
+        getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id); 
+        SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
+
+        // dhcp6_subnet_id : INT NULL
+        getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id); 
+        SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
+
+        // ipv4_address : BIGINT NULL
+        uint32_t addr4; 
+        getColumnValue(r, row, IPV4_ADDRESS_COL, addr4); 
+        isc::asiolink::IOAddress ipv4_reservation(addr4);
+
+        // hostname : VARCHAR(255) NULL
+        std::string hostname;
+        getColumnValue(r, row, HOSTNAME_COL, hostname);
+
+        // dhcp4_client_classes : VARCHAR(255) NULL
+        std::string dhcp4_client_classes;
+        getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
+
+        // dhcp6_client_classes : VARCHAR(255) NULL
+        std::string dhcp6_client_classes;
+        getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
+
+        HostPtr host;
+        try {
+            host.reset(new Host(identifier_value, identifier_len,
+                                identifier_type, dhcp4_subnet_id,
+                                dhcp6_subnet_id, ipv4_reservation, hostname,
+                                dhcp4_client_classes, dhcp6_client_classes)); 
+
+            host->setHostId(host_id);
+        } catch (const isc::Exception& ex) {
+            isc_throw(DbOperationError, "Could not create host: " << ex.what());
+        }
+        
+        return(host);
+    };
+
+protected:
+
+    /// Number of columns returned in queries.
+    size_t columns_num_;
+
+    /// Column names.
+    std::vector<std::string> columns_;
+
+    /// Pointer to Host object holding information to be inserted into
+    /// Hosts table.  This is used to retain scope.
+    HostPtr host_;
+
+private:
+
+    /// Host identifier (primary key in Hosts table).
+    uint64_t host_id_;
+};
+
+#if 0
+
+/// @brief Extends base exchange class with ability to retrieve DHCP options
+/// from the 'dhcp4_options' and 'dhcp6_options' tables.
+///
+/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
+/// along with the host information. It is not used to retrieve IPv6
+/// reservations. The following types of queries are supported:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
+class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
+private:
+
+    /// @brief Number of columns holding DHCPv4  or DHCPv6 option information.
+    static const size_t OPTION_COLUMNS = 6;
+
+    /// @brief Receives DHCPv4 or DHCPv6 options information from the
+    /// dhcp4_options or dhcp6_options tables respectively.
+    ///
+    /// The PgSqlHostWithOptionsExchange class holds two respective instances
+    /// of this class, one for receiving DHCPv4 options, one for receiving
+    /// DHCPv6 options.
+    ///
+    /// The following are the basic functions of this class:
+    /// - bind class members to specific columns in MySQL binding tables,
+    /// - set DHCP options specific column names,
+    /// - create instances of options retrieved from the database.
+    ///
+    /// The reason for isolating those functions in a separate C++ class is
+    /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
+    class OptionProcessor {
+    public:
+
+        /// @brief Constructor.
+        ///
+        /// @param universe V4 or V6. The type of the options' instances
+        /// created by this class depends on this parameter.
+        /// @param start_column Index of the first column to be used by this
+        /// class.
+        OptionProcessor(const Option::Universe& universe,
+                        const size_t start_column)
+        : universe_(universe), start_column_(start_column), option_id_(0),
+          code_(0), value_length_(0), formatted_value_length_(0),
+          space_length_(0), persistent_(false), option_id_null_(MLM_FALSE),
+          code_null_(MLM_FALSE), value_null_(MLM_FALSE),
+          formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE),
+          option_id_index_(start_column), code_index_(start_column_ + 1),
+          value_index_(start_column_ + 2),
+          formatted_value_index_(start_column_ + 3),
+          space_index_(start_column_ + 4),
+          persistent_index_(start_column_ + 5),
+          most_recent_option_id_(0) {
+
+            memset(value_, 0, sizeof(value_));
+            memset(formatted_value_, 0, sizeof(formatted_value_));
+            memset(space_, 0, sizeof(space_));
+        }
+
+        /// @brief Returns identifier of the currently processed option.
+        uint64_t getOptionId() const {
+            if (option_id_null_ == MLM_FALSE) {
+                return (option_id_);
+            }
+            return (0);
+        }
+
+        /// @brief Creates instance of the currently processed option.
+        ///
+        /// This method detects if the currently processed option is a new
+        /// instance. It makes it determination by comparing the identifier
+        /// of the currently processed option, with the most recently processed
+        /// option. If the current value is greater than the id of the recently
+        /// processed option it is assumed that the processed row holds new
+        /// option information. In such case the option instance is created and
+        /// inserted into the configuration passed as argument.
+        ///
+        /// @param cfg Pointer to the configuration object into which new
+        /// option instances should be inserted.
+        void retrieveOption(const CfgOptionPtr& cfg) {
+            // option_id may be NULL if dhcp4_options or dhcp6_options table
+            // doesn't contain any options for the particular host. Also, the
+            // current option id must be greater than id if the most recent
+            // option because options are ordered by option id. Otherwise
+            // we assume that this is already processed option.
+            if ((option_id_null_ == MLM_TRUE) ||
+                (most_recent_option_id_ >= option_id_)) {
+                return;
+            }
+
+            // Remember current option id as the most recent processed one. We
+            // will be comparing it with option ids in subsequent rows.
+            most_recent_option_id_ = option_id_;
+
+            // Convert it to string object for easier comparison.
+            std::string space;
+            if (space_null_ == MLM_FALSE) {
+                // Typically, the string values returned by the database are not
+                // NULL terminated.
+                space_[space_length_] = '\0';
+                space.assign(space_);
+            }
+
+            // Convert formatted_value to string as well.
+            std::string formatted_value;
+            if (formatted_value_null_ == MLM_FALSE) {
+                formatted_value_[formatted_value_length_] = '\0';
+                formatted_value.assign(formatted_value_);
+            }
+
+            // Options are held in a binary or textual format in the database.
+            // This is similar to having an option specified in a server
+            // configuration file. Such option is converted to appropriate C++
+            // class, using option definition. Thus, we need to find the
+            // option definition for this option code and option space.
+
+            // If the option space is a standard DHCPv4 or DHCPv6 option space,
+            // this is most likely a standard option, for which we have a
+            // definition created within libdhcp++.
+            OptionDefinitionPtr def;
+            if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
+                def = LibDHCP::getOptionDef(universe_, code_);
+            }
+
+            // Otherwise, we may check if this an option encapsulated within the
+            // vendor space.
+            if (!def && (space != DHCP4_OPTION_SPACE) &&
+                (space != DHCP6_OPTION_SPACE)) {
+                uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
+                if (vendor_id > 0) {
+                    def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
+                }
+            }
+
+            // In all other cases, we use runtime option definitions, which
+            // should be also registered within the libdhcp++.
+            if (!def) {
+                def = LibDHCP::getRuntimeOptionDef(space, code_);
+            }
+
+            OptionPtr option;
+
+            if (!def) {
+                // If no definition found, we use generic option type.
+                OptionBuffer buf(value_, value_ + value_length_);
+                option.reset(new Option(universe_, code_, buf.begin(),
+                                        buf.end()));
+            } else {
+                // The option value may be specified in textual or binary format
+                // in the database. If formatted_value is empty, the binary
+                // format is used. Depending on the format we use a different
+                // variant of the optionFactory function.
+                if (formatted_value.empty()) {
+                    OptionBuffer buf(value_, value_ + value_length_);
+                    option = def->optionFactory(universe_, code_, buf.begin(),
+                                                buf.end());
+                } else {
+                    // Spit the value specified in comma separated values
+                    // format.
+                    std::vector<std::string> split_vec;
+                    boost::split(split_vec, formatted_value, boost::is_any_of(","));
+                    option = def->optionFactory(universe_, code_, split_vec);
+                }
+            }
+
+            OptionDescriptor desc(option, persistent_, formatted_value);
+            cfg->add(desc, space);
+        }
+
+        /// @brief Specify column names.
+        ///
+        /// @param [out] columns Reference to a vector holding names of option
+        /// specific columns.
+        void setColumnNames(std::vector<std::string>& columns) {
+            columns[option_id_index_] = "option_id";
+            columns[code_index_] = "code";
+            columns[value_index_] = "value";
+            columns[formatted_value_index_] = "formatted_value";
+            columns[space_index_] = "space";
+            columns[persistent_index_] = "persistent";
+        }
+
+        /// @brief Initialize binding table fields for options.
+        ///
+        /// Resets most_recent_option_id_ value to 0.
+        ///
+        /// @param [out] bind Binding table.
+        void setBindFields(std::vector<MYSQL_BIND>& bind) {
+            // This method is called just before making a new query, so we
+            // reset the most_recent_option_id_ to start over with options
+            // processing.
+            most_recent_option_id_ = 0;
+
+            // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
+            bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
+            bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
+            bind[option_id_index_].is_unsigned = MLM_TRUE;
+
+            // code : TINYINT OR SHORT UNSIGNED NOT NULL
+            bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
+            bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
+            bind[code_index_].is_unsigned = MLM_TRUE;
+            bind[code_index_].is_null = &code_null_;
+
+            // value : BLOB NULL
+            value_length_ = sizeof(value_);
+            bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
+            bind[value_index_].buffer = reinterpret_cast<char*>(value_);
+            bind[value_index_].buffer_length = value_length_;
+            bind[value_index_].length = &value_length_;
+            bind[value_index_].is_null = &value_null_;
+
+            // formatted_value : TEXT NULL
+            formatted_value_length_ = sizeof(formatted_value_);
+            bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
+            bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
+            bind[formatted_value_index_].buffer_length = formatted_value_length_;
+            bind[formatted_value_index_].length = &formatted_value_length_;
+            bind[formatted_value_index_].is_null = &formatted_value_null_;
+
+            // space : VARCHAR(128) NULL
+            space_length_ = sizeof(space_);
+            bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
+            bind[space_index_].buffer = reinterpret_cast<char*>(space_);
+            bind[space_index_].buffer_length = space_length_;
+            bind[space_index_].length = &space_length_;
+            bind[space_index_].is_null = &space_null_;
+
+            // persistent : TINYINT(1) NOT NULL DEFAULT 0
+            bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
+            bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
+            bind[persistent_index_].is_unsigned = MLM_TRUE;
+        }
+
+    private:
+
+        /// @brief Universe: V4 or V6.
+        Option::Universe universe_;
+
+        /// @brief Index of first column used by this class.
+        size_t start_column_;
+
+        /// @brief Option id.
+        uint64_t option_id_;
+
+        /// @brief Option code.
+        uint16_t code_;
+
+        /// @brief Buffer holding binary value of an option.
+        uint8_t value_[OPTION_VALUE_MAX_LEN];
+
+        /// @brief Option value length.
+        unsigned long value_length_;
+
+        /// @brief Buffer holding textual value of an option.
+        char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
+
+        /// @brief Formatted option value length.
+        unsigned long formatted_value_length_;
+
+        /// @brief Buffer holding option space name.
+        char space_[OPTION_SPACE_MAX_LEN];
+
+        /// @brief Option space length.
+        unsigned long space_length_;
+
+        /// @brief Flag indicating if option is always sent or only if
+        /// requested.
+        bool persistent_;
+
+        /// @name Boolean values indicating if values of specific columns in
+        /// the database are NULL.
+        //@{
+        /// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
+        my_bool option_id_null_;
+
+        /// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
+        my_bool code_null_;
+
+        /// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
+        my_bool value_null_;
+
+        /// @brief Boolean flag indicating if the DHCPv4 formatted option value
+        /// is NULL.
+        my_bool formatted_value_null_;
+
+        /// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
+        my_bool space_null_;
+
+        //@}
+
+        /// @name Indexes of the specific columns
+        //@{
+        /// @brief Option id
+        size_t option_id_index_;
+
+        /// @brief Code
+        size_t code_index_;
+
+        /// @brief Value
+        size_t value_index_;
+
+        /// @brief Formatted value
+        size_t formatted_value_index_;
+
+        /// @brief Space
+        size_t space_index_;
+
+        /// @brief Persistent
+        size_t persistent_index_;
+        //@}
+
+        /// @brief Option id for last processed row.
+        uint64_t most_recent_option_id_;
+    };
+
+    /// @brief Pointer to the @ref OptionProcessor class.
+    typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
+
+public:
+
+    /// @brief DHCP option types to be fetched from the database.
+    ///
+    /// Supported types are:
+    /// - Only DHCPv4 options,
+    /// - Only DHCPv6 options,
+    /// - Both DHCPv4 and DHCPv6 options.
+    enum FetchedOptions {
+        DHCP4_ONLY,
+        DHCP6_ONLY,
+        DHCP4_AND_DHCP6
+    };
+
+    /// @brief Constructor.
+    ///
+    /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
+    /// be fetched from the database for a host.
+    /// @param additional_columns_num Number of additional columns for which
+    /// resources should be allocated, e.g. binding table, column names etc.
+    /// This parameter should be set to a non zero value by derived classes to
+    /// allocate resources for the columns supported by derived classes.
+    PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
+                                 const size_t additional_columns_num = 0)
+        : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
+                            + additional_columns_num),
+          opt_proc4_(), opt_proc6_() {
+
+        // Create option processor for DHCPv4 options, if required.
+        if ((fetched_options == DHCP4_ONLY) ||
+            (fetched_options == DHCP4_AND_DHCP6)) {
+            opt_proc4_.reset(new OptionProcessor(Option::V4,
+                                                 findAvailColumn()));
+            opt_proc4_->setColumnNames(columns_);
+        }
+
+        // Create option processor for DHCPv6 options, if required.
+        if ((fetched_options == DHCP6_ONLY) ||
+            (fetched_options == DHCP4_AND_DHCP6)) {
+            opt_proc6_.reset(new OptionProcessor(Option::V6,
+                                                 findAvailColumn()));
+            opt_proc6_->setColumnNames(columns_);
+        }
+    }
+
+    /// @brief Processes the current row.
+    ///
+    /// The processed row includes both host information and DHCP option
+    /// information. Because used SELECT query use LEFT JOIN clause, the
+    /// some rows contain duplicated host or options entries. This method
+    /// detects duplicated information and discards such entries.
+    ///
+    /// @param [out] hosts Container holding parsed hosts and options.
+    virtual void processFetchedData(ConstHostCollection& hosts) {
+        // Holds pointer to the previously parsed host.
+        HostPtr most_recent_host;
+        if (!hosts.empty()) {
+            // Const cast is not very elegant way to deal with it, but
+            // there is a good reason to use it here. This method is called
+            // to build a collection of const hosts to be returned to the
+            // caller. If we wanted to use non-const collection we'd need
+            // to copy the whole collection before returning it, which has
+            // performance implications. Alternatively, we could store the
+            // most recently added host in a class member but this would
+            // make the code less readable.
+            most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
+        }
+
+        // If no host has been parsed yet or we're at the row holding next
+        // host, we create a new host object and put it at the end of the
+        // list.
+        if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
+            HostPtr host = retrieveHost();
+            hosts.push_back(host);
+            most_recent_host = host;
+        }
+
+        // Parse DHCPv4 options if required to do so.
+        if (opt_proc4_) {
+            CfgOptionPtr cfg = most_recent_host->getCfgOption4();
+            opt_proc4_->retrieveOption(cfg);
+        }
+
+        // Parse DHCPv6 options if required to do so.
+        if (opt_proc6_) {
+            CfgOptionPtr cfg = most_recent_host->getCfgOption6();
+            opt_proc6_->retrieveOption(cfg);
+        }
+    }
+
+    /// @brief Bind variables for receiving option data.
+    ///
+    /// @return Vector of MYSQL_BIND object representing data to be retrieved.
+    virtual std::vector<MYSQL_BIND> createBindForReceive() {
+        // The following call sets bind_ values between 0 and 8.
+        static_cast<void>(PgSqlHostExchange::createBindForReceive());
+
+        // Bind variables for DHCPv4 options.
+        if (opt_proc4_) {
+            opt_proc4_->setBindFields(bind_);
+        }
+
+        // Bind variables for DHCPv6 options.
+        if (opt_proc6_) {
+            opt_proc6_->setBindFields(bind_);
+        }
+
+        // Add the error flags
+        setErrorIndicators(bind_, error_);
+
+        return (bind_);
+    };
+
+private:
+
+    /// @brief Returns a number of columns required to retrieve option data.
+    ///
+    /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
+    /// DHCPv6 a different number of columns is required in the binding array.
+    /// This method returns the number of required columns, according to the
+    /// value of @c fetched_columns passed in the constructor.
+    ///
+    /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
+    /// both types of options should be retrieved.
+    ///
+    /// @return Number of required columns.
+    static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
+        return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
+                OPTION_COLUMNS);
+    }
+
+    /// @brief Pointer to DHCPv4 options processor.
+    ///
+    /// If this object is NULL, the DHCPv4 options are not fetched.
+    OptionProcessorPtr opt_proc4_;
+
+    /// @brief Pointer to DHCPv6 options processor.
+    ///
+    /// If this object is NULL, the DHCPv6 options are not fetched.
+    OptionProcessorPtr opt_proc6_;
+};
+
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
+///
+/// This class extends the @ref PgSqlHostWithOptionsExchange class with the
+/// mechanisms to retrieve IPv6 reservations. This class is used in sitations
+/// when it is desired to retrieve DHCPv6 specific information about the host
+/// (DHCPv6 options and reservations), or entire information about the host
+/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
+/// queries used with this class:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
+///   LEFT JOIN ipv6_reservations ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
+class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
+private:
+
+    /// @brief Number of columns holding IPv6 reservation information.
+    static const size_t RESERVATION_COLUMNS = 5;
+
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Apart from initializing the base class data structures it also
+    /// initializes values representing IPv6 reservation information.
+    PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
+        : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
+          reservation_id_(0),
+          reserv_type_(0), reserv_type_null_(MLM_FALSE),
+          ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
+          reservation_id_index_(findAvailColumn()),
+          address_index_(reservation_id_index_ + 1),
+          prefix_len_index_(reservation_id_index_ + 2),
+          type_index_(reservation_id_index_ + 3),
+          iaid_index_(reservation_id_index_ + 4),
+          most_recent_reservation_id_(0) {
+
+        memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
+
+        // Provide names of additional columns returned by the queries.
+        columns_[reservation_id_index_] = "reservation_id";
+        columns_[address_index_] = "address";
+        columns_[prefix_len_index_] = "prefix_len";
+        columns_[type_index_] = "type";
+        columns_[iaid_index_] = "dhcp6_iaid";
+    }
+
+    /// @brief Returns last fetched reservation id.
+    ///
+    /// @return Reservation id or 0 if no reservation data is fetched.
+    uint64_t getReservationId() const {
+        if (reserv_type_null_ == MLM_FALSE) {
+            return (reservation_id_);
+        }
+        return (0);
+    };
+
+    /// @brief Creates IPv6 reservation from the data contained in the
+    /// currently processed row.
+    ///
+    /// Called after the MYSQL_BIND array created by createBindForReceive().
+    ///
+    /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
+    IPv6Resrv retrieveReservation() {
+        // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
+        IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
+
+        switch (reserv_type_) {
+        case 0:
+            type = IPv6Resrv::TYPE_NA;
+            break;
+
+        case 2:
+            type = IPv6Resrv::TYPE_PD;
+            break;
+
+        default:
+            isc_throw(BadValue,
+                      "invalid IPv6 reservation type returned: "
+                      << static_cast<int>(reserv_type_)
+                      << ". Only 0 or 2 are allowed.");
+        }
+
+        ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
+        std::string address = ipv6_address_buffer_;
+        IPv6Resrv r(type, IOAddress(address), prefix_len_);
+        return (r);
+    };
+
+    /// @brief Processes one row of data fetched from a database.
+    ///
+    /// The processed data must contain host id, which uniquely identifies a
+    /// host. This method creates a host and inserts it to the hosts collection
+    /// only if the last inserted host has a different host id. This prevents
+    /// adding duplicated hosts to the collection, assuming that processed
+    /// rows are primarily ordered by host id column.
+    ///
+    /// Depending on the value of the @c fetched_options specified in the
+    /// constructor, this method also parses options returned as a result
+    /// of SELECT queries.
+    ///
+    /// For any returned row which contains IPv6 reservation information it
+    /// checks if the reservation is not a duplicate of previously parsed
+    /// reservation and appends the IPv6Resrv object into the host object
+    /// if the parsed row contains new reservation information.
+    ///
+    /// @param [out] hosts Collection of hosts to which a new host created
+    ///        from the processed data should be inserted.
+    virtual void processFetchedData(ConstHostCollection& hosts) {
+
+        // Call parent class to fetch host information and options.
+        PgSqlHostWithOptionsExchange::processFetchedData(hosts);
+
+        if (getReservationId() == 0) {
+            return;
+        }
+
+        if (hosts.empty()) {
+            isc_throw(Unexpected, "no host information while retrieving"
+                      " IPv6 reservation");
+        }
+        HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
+
+        // If we're dealing with a new reservation, let's add it to the
+        // host.
+        if (getReservationId() > most_recent_reservation_id_) {
+            most_recent_reservation_id_ = getReservationId();
+
+            if (most_recent_reservation_id_ > 0) {
+                host->addReservation(retrieveReservation());
+            }
+        }
+    }
+
+    /// @brief Create BIND array to receive Host data with IPv6 reservations.
+    ///
+    /// Creates a MYSQL_BIND array to receive Host data from the database.
+    /// After data is successfully received, @ref processedFetchedData is
+    /// called for each returned row to build collection of @ref Host
+    /// objects with associated IPv6 reservations.
+    ///
+    /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
+    virtual std::vector<MYSQL_BIND> createBindForReceive() {
+        // Reset most recent reservation id value because we're now making
+        // a new SELECT query.
+        most_recent_reservation_id_ = 0;
+
+        // Bind values supported by parent classes.
+        static_cast<void>(PgSqlHostWithOptionsExchange::createBindForReceive());
+
+        // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
+        bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
+        bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
+        bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
+
+        // IPv6 address/prefix VARCHAR(39)
+        ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
+        bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
+        bind_[address_index_].buffer = ipv6_address_buffer_;
+        bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
+        bind_[address_index_].length = &ipv6_address_buffer_len_;
+
+        // prefix_len : TINYINT
+        bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
+        bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
+        bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
+
+        // (reservation) type : TINYINT
+        reserv_type_null_ = MLM_FALSE;
+        bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
+        bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
+        bind_[type_index_].is_unsigned = MLM_TRUE;
+        bind_[type_index_].is_null = &reserv_type_null_;
+
+        // dhcp6_iaid INT UNSIGNED
+        bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
+        bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
+        bind_[iaid_index_].is_unsigned = MLM_TRUE;
+
+        // Add the error flags
+        setErrorIndicators(bind_, error_);
+
+        return (bind_);
+    };
+
+private:
+
+    /// @brief IPv6 reservation id.
+    uint64_t reservation_id_;
+
+    /// @brief IPv6 reservation type.
+    uint8_t reserv_type_;
+
+    /// @brief Boolean flag indicating if reservation type field is null.
+    ///
+    /// This flag is used by the class to determine if the returned row
+    /// contains IPv6 reservation information.
+    my_bool reserv_type_null_;
+
+    /// @brief Buffer holding IPv6 address/prefix in textual format.
+    char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
+
+    /// @brief Length of the textual address representation.
+    unsigned long ipv6_address_buffer_len_;
+
+    /// @brief Length of the prefix (128 for addresses)
+    uint8_t prefix_len_;
+
+    /// @brief IAID.
+    uint8_t iaid_;
+
+    /// @name Indexes of columns holding information about IPv6 reservations.
+    //@{
+    /// @brief Index of reservation_id column.
+    size_t reservation_id_index_;
+
+    /// @brief Index of address column.
+    size_t address_index_;
+
+    /// @brief Index of prefix_len column.
+    size_t prefix_len_index_;
+
+    /// @brief Index of type column.
+    size_t type_index_;
+
+    /// @brief Index of IAID column.
+    size_t iaid_index_;
+
+    //@}
+
+    /// @brief Reservation id for last processed row.
+    uint64_t most_recent_reservation_id_;
+
+};
+
+/// @brief This class is used for storing IPv6 reservations in a MySQL database.
+///
+/// This class is only used to insert IPv6 reservations into the
+/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
+/// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
+/// used instead.
+///
+/// When a new IPv6 reservation is inserted into the database, an appropriate
+/// host must be defined in the hosts table. An attempt to insert IPv6
+/// reservation for non-existing host will result in failure.
+class PgSqlIPv6ReservationExchange {
+private:
+
+    /// @brief Set number of columns for ipv6_reservation table.
+    static const size_t RESRV_COLUMNS = 6;
+
+public:
+
+    /// @brief Constructor
+    ///
+    /// Initialize class members representing a single IPv6 reservation.
+    PgSqlIPv6ReservationExchange()
+        : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
+          iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
+
+        // Reset error table.
+        std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
+
+        // Set the column names (for error messages)
+        columns_[0] = "host_id";
+        columns_[1] = "address";
+        columns_[2] = "prefix_len";
+        columns_[3] = "type";
+        columns_[4] = "dhcp6_iaid";
+        BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
+    }
+
+    /// @brief Create MYSQL_BIND objects for IPv6 Reservation.
+    ///
+    /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
+    /// object to the database.
+    ///
+    /// @param resv An object representing IPv6 reservation which will be
+    ///        sent to the database.
+    ///        None of the fields in the reservation are modified -
+    ///        the reservation data is only read.
+    /// @param id ID of a host owning this reservation
+    ///
+    /// @return Vector of MySQL BIND objects representing the data to be added.
+    std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
+                                              const HostID& id) {
+
+        // Store the values to ensure they remain valid.
+        resv_ = resv;
+        host_id_ = id;
+
+        // Initialize prior to constructing the array of MYSQL_BIND structures.
+        // It sets all fields, including is_null, to zero, so we need to set
+        // is_null only if it should be true. This gives up minor performance
+        // benefit while being safe approach. For improved readability, the
+        // code that explicitly sets is_null is there, but is commented out.
+        memset(bind_, 0, sizeof(bind_));
+
+        // Set up the structures for the various components of the host structure.
+
+        try {
+            // address VARCHAR(39)
+            address_ = resv.getPrefix().toText();
+            address_len_ = address_.length();
+            bind_[0].buffer_type = MYSQL_TYPE_BLOB;
+            bind_[0].buffer = reinterpret_cast<char*>
+                (const_cast<char*>(address_.c_str()));
+            bind_[0].buffer_length = address_len_;
+            bind_[0].length = &address_len_;
+
+            // prefix_len tinyint
+            prefix_len_ = resv.getPrefixLen();
+            bind_[1].buffer_type = MYSQL_TYPE_TINY;
+            bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
+            bind_[1].is_unsigned = MLM_TRUE;
+
+            // type tinyint
+            // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
+            type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
+            bind_[2].buffer_type = MYSQL_TYPE_TINY;
+            bind_[2].buffer = reinterpret_cast<char*>(&type_);
+            bind_[2].is_unsigned = MLM_TRUE;
+
+            // dhcp6_iaid INT UNSIGNED
+            /// @todo: We don't support iaid in the IPv6Resrv yet.
+            iaid_ = 0;
+            bind_[3].buffer_type = MYSQL_TYPE_LONG;
+            bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
+            bind_[3].is_unsigned = MLM_TRUE;
+
+            // host_id INT UNSIGNED NOT NULL
+            bind_[4].buffer_type = MYSQL_TYPE_LONG;
+            bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
+            bind_[4].is_unsigned = MLM_TRUE;
+
+        } catch (const std::exception& ex) {
+            isc_throw(DbOperationError,
+                      "Could not create bind array from IPv6 Reservation: "
+                      << resv_.toText() << ", reason: " << ex.what());
+        }
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        // RESRV_COLUMNS -1 as we do not set reservation_id.
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
+    }
+
+private:
+
+    /// @brief Host unique identifier.
+    uint64_t host_id_;
+
+    /// @brief Address (or prefix).
+    std::string address_;
+
+    /// @brief Length of the textual address representation.
+    unsigned long address_len_;
+
+     /// @brief Length of the prefix (128 for addresses).
+    uint8_t prefix_len_;
+
+    /// @brief Reservation type.
+    uint8_t type_;
+
+    /// @brief IAID.
+    uint8_t iaid_;
+
+    /// @brief Object holding reservation being sent to the database.
+    IPv6Resrv resv_;
+
+    /// @brief Array of MySQL bindings.
+    MYSQL_BIND bind_[RESRV_COLUMNS];
+
+    /// @brief Array of strings holding columns names.
+    std::string columns_[RESRV_COLUMNS];
+
+    /// @brief Array of boolean values indicating if error occurred
+    /// for respective columns.
+    my_bool error_[RESRV_COLUMNS];
+};
+
+/// @brief This class is used for inserting options into a database.
+///
+/// This class supports inserting both DHCPv4 and DHCPv6 options.
+class PgSqlOptionExchange {
+private:
+
+    /// @brief Number of columns in the tables holding options.
+    static const size_t OPTION_COLUMNS = 9;
+
+public:
+
+    /// @brief Constructor.
+    PgSqlOptionExchange()
+        : type_(0), value_len_(0), formatted_value_len_(0), space_(), space_len_(0),
+          persistent_(false), client_class_(), client_class_len_(0),
+          subnet_id_(0), host_id_(0), option_() {
+
+        BOOST_STATIC_ASSERT(8 < OPTION_COLUMNS);
+    }
+
+    /// @brief Creates binding array to insert option data into database.
+    ///
+    /// @return Vector of MYSQL_BIND object representing an option.
+    std::vector<MYSQL_BIND>
+    createBindForSend(const OptionDescriptor& opt_desc,
+                      const std::string& opt_space,
+                      const OptionalValue<SubnetID>& subnet_id,
+                      const HostID& host_id) {
+
+        // Hold pointer to the option to make sure it remains valid until
+        // we complete a query.
+        option_ = opt_desc.option_;
+
+        memset(bind_, 0, sizeof(bind_));
+
+        try {
+            // option_id: INT UNSIGNED NOT NULL
+            // The option_id is auto_incremented, so we need to pass the NULL
+            // value.
+            bind_[0].buffer_type = MYSQL_TYPE_NULL;
+
+            // code: SMALLINT UNSIGNED NOT NULL
+            type_  = option_->getType();
+            bind_[1].buffer_type = MYSQL_TYPE_SHORT;
+            bind_[1].buffer = reinterpret_cast<char*>(&type_);
+            bind_[1].is_unsigned = MLM_TRUE;
+
+            // value: BLOB NULL
+            if (opt_desc.formatted_value_.empty() &&
+                (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
+                // The formatted_value is empty and the option value is
+                // non-empty so we need to prepare on-wire format for the
+                // option and store it in the database as a blob.
+                OutputBuffer buf(opt_desc.option_->len());
+                opt_desc.option_->pack(buf);
+                const char* buf_ptr = static_cast<const char*>(buf.getData());
+                value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
+                              buf_ptr + buf.getLength());
+                value_len_ = value_.size();
+                bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+                bind_[2].buffer = &value_[0];
+                bind_[2].buffer_length = value_len_;
+                bind_[2].length = &value_len_;
+
+            } else {
+                // No value or formatted_value specified. In this case, the
+                // value blob is NULL.
+                value_.clear();
+                bind_[2].buffer_type = MYSQL_TYPE_NULL;
+            }
+
+            // formatted_value: TEXT NULL,
+            if (!opt_desc.formatted_value_.empty()) {
+                formatted_value_len_ = opt_desc.formatted_value_.size();
+                bind_[3].buffer_type = MYSQL_TYPE_STRING;
+                bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
+                bind_[3].buffer_length = formatted_value_len_;
+                bind_[3].length = &formatted_value_len_;
+
+            } else {
+                bind_[3].buffer_type = MYSQL_TYPE_NULL;
+            }
+
+            // space: VARCHAR(128) NULL
+            space_ = opt_space;
+            space_len_ = space_.size();
+            bind_[4].buffer_type = MYSQL_TYPE_STRING;
+            bind_[4].buffer = const_cast<char*>(space_.c_str());
+            bind_[4].buffer_length = space_len_;
+            bind_[4].length = &space_len_;
+
+            // persistent: TINYINT(1) NOT NULL DEFAULT 0
+            persistent_ = opt_desc.persistent_;
+            bind_[5].buffer_type = MYSQL_TYPE_TINY;
+            bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
+            bind_[5].is_unsigned = MLM_TRUE;
+
+            // dhcp_client_class: VARCHAR(128) NULL
+            /// @todo Assign actual value to client class string.
+            client_class_len_ = client_class_.size();
+            bind_[6].buffer_type = MYSQL_TYPE_STRING;
+            bind_[6].buffer = const_cast<char*>(client_class_.c_str());
+            bind_[6].buffer_length = client_class_len_;
+            bind_[6].length = &client_class_len_;
+
+            // dhcp4_subnet_id: INT UNSIGNED NULL
+            if (subnet_id.isSpecified()) {
+                subnet_id_ = subnet_id;
+                bind_[7].buffer_type = MYSQL_TYPE_LONG;
+                bind_[7].buffer = reinterpret_cast<char*>(subnet_id_);
+                bind_[7].is_unsigned = MLM_TRUE;
+
+            } else {
+                bind_[7].buffer_type = MYSQL_TYPE_NULL;
+            }
+
+            // host_id: INT UNSIGNED NOT NULL
+            host_id_ = host_id;
+            bind_[8].buffer_type = MYSQL_TYPE_LONG;
+            bind_[8].buffer = reinterpret_cast<char*>(&host_id_);
+            bind_[8].is_unsigned = MLM_TRUE;
+
+        } catch (const std::exception& ex) {
+            isc_throw(DbOperationError,
+                      "Could not create bind array for inserting DHCP "
+                      "option: " << option_->toText() << ", reason: "
+                      << ex.what());
+        }
+
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
+    }
+
+private:
+
+    /// @brief Option type.
+    uint16_t type_;
+
+    /// @brief Option value as binary.
+    std::vector<uint8_t> value_;
+
+    /// @brief Option value length.
+    size_t value_len_;
+
+    /// @brief Formatted option value length.
+    unsigned long formatted_value_len_;
+
+    /// @brief Option space name.
+    std::string space_;
+
+    /// @brief Option space name length.
+    size_t space_len_;
+
+    /// @brief Boolean flag indicating if the option is always returned to
+    /// a client or only when requested.
+    bool persistent_;
+
+    /// @brief Client classes for the option.
+    std::string client_class_;
+
+    /// @brief Length of the string holding client classes for the option.
+    size_t client_class_len_;
+
+    /// @brief Subnet identifier.
+    uint32_t subnet_id_;
+
+    /// @brief Host identifier.
+    uint32_t host_id_;
+
+    /// @brief Pointer to currently parsed option.
+    OptionPtr option_;
+
+    /// @brief Array of MYSQL_BIND elements representing inserted data.
+    MYSQL_BIND bind_[OPTION_COLUMNS];
+};
+#endif
+
+} // end of anonymous namespace
+
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the @ref PgSqlHostDataSource.
+class PgSqlHostDataSourceImpl {
+public:
+
+    /// @brief Statement Tags
+    ///
+    /// The contents of the enum are indexes into the list of SQL statements
+    enum StatementIndex {
+        INSERT_HOST,            // Insert new host to collection
+#if 0
+        INSERT_V6_RESRV,        // Insert v6 reservation
+        INSERT_V4_OPTION,       // Insert DHCPv4 option
+        INSERT_V6_OPTION,       // Insert DHCPv6 option
+        GET_HOST_DHCPID,        // Gets hosts by host identifier
+#endif
+        GET_HOST_ADDR,          // Gets hosts by IPv4 address
+#if 0
+        GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+        GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+        GET_HOST_SUBID_ADDR,    // Gets host by IPv4 SubnetID and IPv4 address
+        GET_HOST_PREFIX,        // Gets host by IPv6 prefix
+#endif
+        GET_VERSION,            // Obtain version number
+        NUM_STATEMENTS          // Number of statements
+    };
+
+    /// @brief Constructor.
+    ///
+    /// This constructor opens database connection and initializes prepared
+    /// statements used in the queries.
+    PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters);
+
+    /// @brief Destructor.
+    ~PgSqlHostDataSourceImpl();
+
+    /// @brief Executes statements which insert a row into one of the tables.
+    ///
+    /// @param stindex Index of a statement being executed.
+    /// @param bind Vector of MYSQL_BIND objects to be used when making the
+    /// query.
+    /// @param return_last_id flag indicating whether or not the insert
+    /// returns the primary key of from the row inserted via " RETURNING 
+    /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
+    /// clause causes the INSERT to return a result set that should consist
+    /// of a single row with one column, the value of the primary key.
+    /// Defaults to false.
+    ///
+    /// @returns 0 if return_last_id is false, otherwise it returns the
+    /// the value in the result set in the first col of the first row. 
+    ///
+    /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
+    uint64_t addStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
+                          PsqlBindArrayPtr& bind, 
+                          const bool return_last_id = false);
+
+#if 0
+    /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
+    ///
+    /// @param resv IPv6 Reservation to be added
+    /// @param id ID of a host owning this reservation
+    void addResv(const IPv6Resrv& resv, const HostID& id);
+
+    /// @brief Inserts a single DHCP option into the database.
+    ///
+    /// @param stindex Index of a statement being executed.
+    /// @param opt_desc Option descriptor holding information about an option
+    /// to be inserted into the database.
+    /// @param opt_space Option space name.
+    /// @param subnet_id Subnet identifier.
+    /// @param host_id Host identifier.
+    void addOption(const PgSqlHostDataSourceImpl::StatementIndex& stindex,
+                   const OptionDescriptor& opt_desc,
+                   const std::string& opt_space,
+                   const OptionalValue<SubnetID>& subnet_id,
+                   const HostID& host_id);
+
+    /// @brief Inserts multiple options into the database.
+    ///
+    /// @param stindex Index of a statement being executed.
+    /// @param options_cfg An object holding a collection of options to be
+    /// inserted into the database.
+    /// @param host_id Host identifier retrieved using @c mysql_insert_id.
+    void addOptions(const StatementIndex& stindex, const ConstCfgOptionPtr& options_cfg,
+                    const uint64_t host_id);
+#endif
+
+    /// @brief Creates collection of @ref Host objects with associated
+    /// information such as IPv6 reservations and/or DHCP options.
+    ///
+    /// This method performs a query which returns host information from
+    /// the 'hosts' table. The query may also use LEFT JOIN clause to
+    /// retrieve information from other tables, e.g. ipv6_reservations,
+    /// dhcp4_options and dhcp6_options.
+    /// Whether IPv6 reservations and/or options are assigned to the
+    /// @ref Host objects depends on the type of the exchange object.
+    ///
+    /// @param stindex Statement index.
+    /// @param bind Pointer to an array of MySQL bindings.
+    /// @param exchange Pointer to the exchange object used for the
+    /// particular query.
+    /// @param [out] result Reference to the collection of hosts returned.
+    /// @param single A boolean value indicating if a single host is
+    /// expected to be returned, or multiple hosts.
+    void getHostCollection(StatementIndex stindex, PsqlBindArrayPtr bind,
+                           boost::shared_ptr<PgSqlHostExchange> exchange,
+                           ConstHostCollection& result, bool single) const;
+
+    /// @brief Retrieves a host by subnet and client's unique identifier.
+    ///
+    /// This method is used by both PgSqlHostDataSource::get4 and
+    /// PgSqlHOstDataSource::get6 methods.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    /// @param stindex Statement index.
+    /// @param exchange Pointer to the exchange object used for the
+    /// particular query.
+    ///
+    /// @return Pointer to const instance of Host or null pointer if
+    /// no host found.
+    ConstHostPtr getHost(const SubnetID& subnet_id,
+                         const Host::IdentifierType& identifier_type,
+                         const uint8_t* identifier_begin,
+                         const size_t identifier_len,
+                         StatementIndex stindex,
+                         boost::shared_ptr<PgSqlHostExchange> exchange) const;
+
+
+    /// @brief Returns PostgreSQL schema version of the open database
+    ///
+    /// @return Version number stored in the database, as a pair of unsigned
+    ///         integers. "first" is the major version number, "second" the
+    ///         minor number.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        has failed.
+    std::pair<uint32_t, uint32_t> getVersion() const;
+
+#if 1
+    // @todo TKM using plain host exchange for now
+    boost::shared_ptr<PgSqlHostExchange> host_exchange_;
+#else
+    /// @brief Pointer to the object representing an exchange which
+    /// can be used to retrieve hosts and DHCPv4 options.
+    boost::shared_ptr<PgSqlHostWithOptionsExchange> host_exchange_;
+
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
+    boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
+
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
+    /// IPv6 reservations using a single query.
+    boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
+
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to insert new IPv6 reservation.
+    boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
+
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
+    /// or dhcp6_options table.
+    boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
+#endif
+
+    /// @brief MySQL connection
+    PgSqlConnection conn_;
+
+};
+
+/// @brief Prepared MySQL statements used by the backend to insert and
+/// retrieve hosts from the database.
+PgSqlTaggedStatement tagged_statements[] = {
+    // Inserts a host into the 'hosts' table. Returns the inserted host id.
+    {PgSqlHostDataSourceImpl::INSERT_HOST, 
+     { OID_INT8, OID_BYTEA, OID_INT2, 
+       OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
+       OID_VARCHAR, OID_VARCHAR },
+     "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+        "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+        "dhcp4_client_classes, dhcp6_client_classes) "
+     "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING host_id"},
+
+#if 0
+    // Inserts a single IPv6 reservation into 'reservations' table.
+    {PgSqlHostDataSourceImpl::INSERT_V6_RESRV,
+         "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+            "dhcp6_iaid, host_id) "
+         "VALUES (?,?,?,?,?)"},
+
+    // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+    // Using fixed scope_id = 3, which associates an option with host.
+    {PgSqlHostDataSourceImpl::INSERT_V4_OPTION,
+         "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
+            "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
+         " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+    // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+    // Using fixed scope_id = 3, which associates an option with host.
+    {PgSqlHostDataSourceImpl::INSERT_V6_OPTION,
+         "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
+            "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
+         " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+    // Retrieves host information, IPv6 reservations and both DHCPv4 and
+    // DHCPv6 options associated with the host. The LEFT JOIN clause is used
+    // to retrieve information from 4 different tables using a single query.
+    // Hence, this query returns multiple rows for a single host.
+    {PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
+            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
+                "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+                "o4.persistent, "
+                "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+                "o6.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o4 "
+                "ON h.host_id = o4.host_id "
+            "LEFT JOIN dhcp6_options AS o6 "
+                "ON h.host_id = o6.host_id "
+            "LEFT JOIN ipv6_reservations AS r "
+                "ON h.host_id = r.host_id "
+            "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
+            "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
+#endif
+
+    // Retrieves host information along with the DHCPv4 options associated with
+    // it. Left joining the dhcp4_options table results in multiple rows being
+    // returned for the same host. The host is retrieved by IPv4 address.
+    {PgSqlHostDataSourceImpl::GET_HOST_ADDR,
+     { OID_INT8 },
+     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+            "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+            "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+            "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+            "o.persistent "
+      "FROM hosts AS h "
+      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+      "WHERE ipv4_address = ? "
+      "ORDER BY h.host_id, o.option_id"},
+#if 0
+
+    // Retrieves host information and DHCPv4 options using subnet identifier
+    // and client's identifier. Left joining the dhcp4_options table results in
+    // multiple rows being returned for the same host.
+    {PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o "
+                "ON h.host_id = o.host_id "
+            "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
+            "   AND h.dhcp_identifier = ? "
+            "ORDER BY h.host_id, o.option_id"},
+
+    // Retrieves host information, IPv6 reservations and DHCPv6 options
+    // associated with a host. The number of rows returned is a multiplication
+    // of number of IPv6 reservations and DHCPv6 options.
+    {PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
+            "SELECT h.host_id, h.dhcp_identifier, "
+                "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+                "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp6_options AS o "
+                "ON h.host_id = o.host_id "
+            "LEFT JOIN ipv6_reservations AS r "
+                "ON h.host_id = r.host_id "
+            "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
+                "AND h.dhcp_identifier = ? "
+            "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+    // Retrieves host information and DHCPv4 options for the host using subnet
+    // identifier and IPv4 reservation. Left joining the dhcp4_options table
+    // results in multiple rows being returned for the host. The number of
+    // rows depends on the number of options defined for the host.
+    {PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o "
+                "ON h.host_id = o.host_id "
+            "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
+            "ORDER BY h.host_id, o.option_id"},
+
+    // Retrieves host information, IPv6 reservations and DHCPv6 options
+    // associated with a host using prefix and prefix length. This query
+    // returns host information for a single host. However, multiple rows
+    // are returned due to left joining IPv6 reservations and DHCPv6 options.
+    // The number of rows returned is multiplication of number of existing
+    // IPv6 reservations and DHCPv6 options.
+    {PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
+            "SELECT h.host_id, h.dhcp_identifier, "
+                "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+                "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp6_options AS o "
+                "ON h.host_id = o.host_id "
+            "LEFT JOIN ipv6_reservations AS r "
+                "ON h.host_id = r.host_id "
+            "WHERE h.host_id = "
+                "(SELECT host_id FROM ipv6_reservations "
+                 "WHERE address = ? AND prefix_len = ?) "
+            "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+#endif
+
+    // Retrieves MySQL schema version.
+    {PgSqlHostDataSourceImpl::GET_VERSION, { OID_NONE },
+            "SELECT version, minor FROM schema_version"},
+
+    // Marks the end of the statements table.
+    {PgSqlHostDataSourceImpl::NUM_STATEMENTS, { 0 }, NULL, NULL}
+};
+
+PgSqlHostDataSourceImpl::
+PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
+#if 1
+    : host_exchange_(new PgSqlHostExchange()),
+#else
+    : host_exchange_(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY)),
+      host_ipv6_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY)),
+      host_ipv46_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::
+                                                     DHCP4_AND_DHCP6)),
+      host_ipv6_reservation_exchange_(new PgSqlIPv6ReservationExchange()),
+      host_option_exchange_(new PgSqlOptionExchange()),
+#endif
+
+      conn_(parameters) {
+
+    // Open the database.
+    conn_.openDatabase();
+
+    int i = 0;
+    for( ; tagged_statements[i].text != NULL ; ++i) {
+        conn_.prepareStatement(tagged_statements[i]);
+    }
+
+    // Just in case somebody foo-barred things
+    if (i != NUM_STATEMENTS) {
+        isc_throw(DbOpenError, "Number of statements prepared: " << i
+                  << " does not match expected count:" << NUM_STATEMENTS);
+    }
+}
+
+PgSqlHostDataSourceImpl::~PgSqlHostDataSourceImpl() {
+}
+
+uint64_t
+PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex,
+                                      PsqlBindArrayPtr& bind_array,
+                                      const bool return_last_id) {
+    uint64_t last_id = 0;
+    PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+                                 tagged_statements[stindex].nbparams,
+                                 &bind_array->values_[0],
+                                 &bind_array->lengths_[0],
+                                 &bind_array->formats_[0], 0));
+
+    int s = PQresultStatus(r);
+
+    if (s != PGRES_COMMAND_OK) {
+        // Failure: check for the special case of duplicate entry.  If this is
+        // the case, we return false to indicate that the row was not added.
+        // Otherwise we throw an exception.
+        if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+            return (false);
+        }
+
+        conn_.checkStatementError(r, tagged_statements[stindex]);
+    }
+
+    // @todo - getColumnValue variants could be made static, if they use
+    // PQfname for the column label, rather then columns_.  Then we wouldn't
+    // need an instance here.
+    if (return_last_id) {
+        PgSqlExchange ex;
+        ex.getColumnValue(r, 0, 0, last_id);
+    }
+
+    return (last_id);
+
+}
+
+#if 0
+void
+PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
+                                 const HostID& id) {
+    std::vector<MYSQL_BIND> bind =
+        host_ipv6_reservation_exchange_->createBindForSend(resv, id);
+
+    addStatement(INSERT_V6_RESRV, bind);
+}
+
+void
+PgSqlHostDataSourceImpl::addOption(const StatementIndex& stindex,
+                                   const OptionDescriptor& opt_desc,
+                                   const std::string& opt_space,
+                                   const OptionalValue<SubnetID>& subnet_id,
+                                   const HostID& id) {
+    std::vector<MYSQL_BIND> bind =
+        host_option_exchange_->createBindForSend(opt_desc, opt_space,
+                                                 subnet_id, id);
+
+    addStatement(stindex, bind);
+}
+
+void
+PgSqlHostDataSourceImpl::addOptions(const StatementIndex& stindex,
+                                    const ConstCfgOptionPtr& options_cfg,
+                                    const uint64_t host_id) {
+    // Get option space names and vendor space names and combine them within a
+    // single list.
+    std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
+    option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+                         vendor_spaces.end());
+
+    // For each option space retrieve all options and insert them into the
+    // database.
+    for (std::list<std::string>::const_iterator space = option_spaces.begin();
+         space != option_spaces.end(); ++space) {
+        OptionContainerPtr options = options_cfg->getAll(*space);
+        if (options && !options->empty()) {
+            for (OptionContainer::const_iterator opt = options->begin();
+                 opt != options->end(); ++opt) {
+                addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
+                          host_id);
+            }
+        }
+    }
+}
+#endif
+
+void
+PgSqlHostDataSourceImpl::
+getHostCollection(StatementIndex stindex, PsqlBindArrayPtr bind_array,
+                  boost::shared_ptr<PgSqlHostExchange> exchange,
+                  ConstHostCollection& result, bool single) const {
+
+    PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+                                 tagged_statements[stindex].nbparams,
+                                 &bind_array->values_[0],
+                                 &bind_array->lengths_[0],
+                                 &bind_array->formats_[0], 0));
+
+    conn_.checkStatementError(r, tagged_statements[stindex]);
+
+    int rows = PQntuples(r);
+    if (single && rows > 1) {
+        isc_throw(MultipleRecords, "multiple records were found in the "
+                      "database where only one was expected for query "
+                      << tagged_statements[stindex].name);
+    }
+
+    HostID prev_id = 0;
+    for(int row = 0; row < rows; ++row) {
+        HostPtr host = exchange->convertFromDatabase(r, row);
+        // Add new host only if there are no hosts or the host id of the
+        // most recently added host is different than the host id of the
+        // currently processed host.
+        if (host->getHostId() != prev_id) {
+            result.push_back(host);
+        }
+
+        prev_id = host->getHostId();
+    }
+}
+
+ConstHostPtr
+PgSqlHostDataSourceImpl::
+getHost(const SubnetID& subnet_id,
+        const Host::IdentifierType& identifier_type,
+        const uint8_t* identifier_begin,
+        const size_t identifier_len,
+        StatementIndex stindex,
+        boost::shared_ptr<PgSqlHostExchange> exchange) const {
+
+    // Set up the WHERE clause value
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    // Add the subnet id.
+    bind_array->add(subnet_id);
+
+    // Add the identifier value.
+    bind_array->add(identifier_begin, identifier_len);
+
+    // Add the Identifier type.
+    bind_array->add(static_cast<uint8_t>(identifier_type));
+
+    ConstHostCollection collection;
+    getHostCollection(stindex, bind_array, exchange, collection, true);
+
+    // Return single record if present, else clear the host.
+    ConstHostPtr result;
+    if (!collection.empty())
+        result = *collection.begin();
+
+    return (result);
+}
+
+std::pair<uint32_t, uint32_t> PgSqlHostDataSourceImpl::getVersion() const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
+
+    PgSqlResult r(PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0));
+    conn_.checkStatementError(r, tagged_statements[GET_VERSION]); 
+
+    istringstream tmp;
+    uint32_t version;
+    tmp.str(PQgetvalue(r, 0, 0));
+    tmp >> version;
+    tmp.str("");
+    tmp.clear();
+
+    uint32_t minor;
+    tmp.str(PQgetvalue(r, 0, 1));
+    tmp >> minor;
+
+    return (std::make_pair<uint32_t, uint32_t>(version, minor));
+}
+
+/*********** PgSqlHostDataSource *********************/
+
+
+PgSqlHostDataSource::
+PgSqlHostDataSource(const PgSqlConnection::ParameterMap& parameters)
+    : impl_(new PgSqlHostDataSourceImpl(parameters)) {
+}
+
+PgSqlHostDataSource::~PgSqlHostDataSource() {
+    delete impl_;
+}
+
+void
+PgSqlHostDataSource::add(const HostPtr& host) {
+    // Initiate PostgreSQL transaction as we will have to make multiple queries
+    // to insert host information into multiple tables. If that fails on
+    // any stage, the transaction will be rolled back by the destructor of
+    // the PgSqlTransaction class.
+    PgSqlTransaction transaction(impl_->conn_);
+
+    // Create the MYSQL_BIND array for the host
+    PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host);
+
+    // ... and insert the host.
+    uint32_t host_id = impl_->addStatement(PgSqlHostDataSourceImpl::INSERT_HOST,
+                                           bind_array, true);
+
+    // @todo TKM take this out
+    std::cout << "id of new host: " << host_id << std::endl;
+
+#if 0
+    // Insert DHCPv4 options.
+    ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+    if (cfg_option4) {
+        impl_->addOptions(PgSqlHostDataSourceImpl::INSERT_V4_OPTION,
+                          cfg_option4, host_id);
+    }
+
+    // Insert DHCPv6 options.
+    ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+    if (cfg_option6) {
+        impl_->addOptions(PgSqlHostDataSourceImpl::INSERT_V6_OPTION,
+                          cfg_option6, host_id);
+    }
+
+    // Insert IPv6 reservations.
+    IPv6ResrvRange v6resv = host->getIPv6Reservations();
+    if (std::distance(v6resv.first, v6resv.second) > 0) {
+        for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
+             ++resv) {
+            impl_->addResv(resv->second, host_id);
+        }
+    }
+
+#endif
+
+    // Everything went fine, so explicitly commit the transaction.
+    transaction.commit();
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const {
+
+    if (duid){
+        return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
+                       duid->getDuid().size()));
+
+    } else if (hwaddr) {
+        return (getAll(Host::IDENT_HWADDR,
+                       &hwaddr->hwaddr_[0],
+                       hwaddr->hwaddr_.size()));
+    }
+
+    return (ConstHostCollection());
+}
+
+#if 1
+ConstHostCollection
+PgSqlHostDataSource::getAll(const Host::IdentifierType&,
+                            const uint8_t*,
+                            const size_t) const {
+    ConstHostCollection result;
+    return (result);
+}
+#else
+ConstHostCollection
+PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Set up the WHERE clause value
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    // Identifier type.
+    bind_array->add(static_cast<uint8_t>(identifier_type));
+
+    // Identifier value.
+    bind_array->add(identifier_begin, identifier_len);
+
+    ConstHostCollection result;
+    impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
+                             bind_array, impl_->host_ipv46_exchange_,
+                             result, false);
+    return (result);
+}
+#endif
+
+ConstHostCollection
+PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+
+    // Set up the WHERE clause value
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    bind_array->add(address);
+
+    ConstHostCollection result;
+    impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_ADDR, bind_array,
+                             impl_->host_exchange_, result, false);
+
+    return (result);
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+                          const DuidPtr& duid) const {
+
+    /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "MySQL host data source get4() called with both"
+                  " hwaddr and duid, only one of them is allowed");
+    }
+    if (!hwaddr && !duid) {
+        isc_throw(BadValue, "MySQL host data source get4() called with "
+                  "neither hwaddr or duid specified, one of them is required");
+    }
+
+    // Choosing one of the identifiers
+    if (hwaddr) {
+        return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                     hwaddr->hwaddr_.size()));
+
+    } else if (duid) {
+        return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
+                     duid->getDuid().size()));
+    }
+
+    return (ConstHostPtr());
+}
+
+#if 1
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID&,
+                          const Host::IdentifierType&,
+                          const uint8_t*,
+                          const size_t) const {
+
+    return (HostPtr());
+}
+#else
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
+
+    return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
+                   identifier_len, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+                   impl_->host_exchange_));
+}
+#endif
+
+#if 1
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID&,
+                          const asiolink::IOAddress&) const {
+    return(HostPtr());
+}
+#else
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID& subnet_id,
+                          const asiolink::IOAddress& address) const {
+    // Set up the WHERE clause value
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    // Add the subnet id
+    bind_array->add(subnet_id);
+
+    // Add the address
+    bind_array->add(address);
+
+    ConstHostCollection collection;
+    impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+                             inbind, impl_->host_exchange_, collection, true);
+
+    // Return single record if present, else clear the host.
+    ConstHostPtr result;
+    if (!collection.empty())
+        result = *collection.begin();
+
+    return (result);
+}
+#endif
+
+#if 1
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID&, const DuidPtr&,
+                          const HWAddrPtr&) const {
+    return (HostPtr());
+}
+#else
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+                          const HWAddrPtr& hwaddr) const {
+
+    /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "MySQL host data source get6() called with both"
+                  " hwaddr and duid, only one of them is allowed");
+    }
+    if (!hwaddr && !duid) {
+        isc_throw(BadValue, "MySQL host data source get6() called with "
+                  "neither hwaddr or duid specified, one of them is required");
+    }
+
+    // Choosing one of the identifiers
+    if (hwaddr) {
+        return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                     hwaddr->hwaddr_.size()));
+    } else if (duid) {
+        return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
+                     duid->getDuid().size()));
+    }
+
+    return (ConstHostPtr());
+}
+#endif
+
+#if 1
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID&,
+                          const Host::IdentifierType&,
+                          const uint8_t*,
+                          const size_t) const {
+    return (ConstHostPtr());
+}
+#else
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
+
+    return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
+                   identifier_len, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
+                   impl_->host_ipv6_exchange_));
+}
+#endif
+
+#if 1
+ConstHostPtr
+PgSqlHostDataSource::get6(const asiolink::IOAddress&,
+                          const uint8_t) const {
+
+    ConstHostPtr result;
+    return(result);
+}
+#else
+ConstHostPtr
+PgSqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+                          const uint8_t prefix_len) const {
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[2];
+    memset(inbind, 0, sizeof(inbind));
+
+    std::string addr6 = prefix.toText();
+    unsigned long addr6_length = addr6.size();
+
+    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[0].buffer = reinterpret_cast<char*>
+                        (const_cast<char*>(addr6.c_str()));
+    inbind[0].length = &addr6_length;
+    inbind[0].buffer_length = addr6_length;
+
+
+    uint8_t tmp = prefix_len;
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&tmp);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+
+    ConstHostCollection collection;
+    impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
+                             inbind, impl_->host_ipv6_exchange_,
+                             collection, true);
+
+    // Return single record if present, else clear the host.
+    ConstHostPtr result;
+    if (!collection.empty()) {
+        result = *collection.begin();
+    }
+
+    return (result);
+}
+#endif
+
+// Miscellaneous database methods.
+
+std::string PgSqlHostDataSource::getName() const {
+    std::string name = "";
+    try {
+        name = impl_->conn_.getParameter("name");
+    } catch (...) {
+        // Return an empty name
+    }
+    return (name);
+}
+
+std::string PgSqlHostDataSource::getDescription() const {
+    return (std::string("Host data source that stores host information"
+                        "in PostgreSQL database"));
+}
+
+std::pair<uint32_t, uint32_t> PgSqlHostDataSource::getVersion() const {
+    return(impl_->getVersion());
+}
+
+#if 0
+// Do we really need these ?
+void
+PgSqlHostDataSource::commit() {
+    impl_->conn_.commit();
+}
+
+
+void
+PgSqlHostDataSource::rollback() {
+    impl_->conn_.rollback();
+}
+#endif
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h
new file mode 100644 (file)
index 0000000..d5fde34
--- /dev/null
@@ -0,0 +1,255 @@
+// Copyright (C) 2016 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_HOST_DATA_SOURCE_H
+#define PGSQL_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/pgsql_connection.h>
+#include <dhcpsrv/pgsql_exchange.h>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the implementation of the @ref PgSqlHostDataSource.
+class PgSqlHostDataSourceImpl;
+
+/// @brief PostgreSQL Host Data Source
+///
+/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
+/// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
+/// database is available and that the Kea schema has been created within it.
+class PgSqlHostDataSource: public BaseHostDataSource {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Uses the following keywords in the parameters passed to it to
+    /// connect to the database:
+    /// - name - Name of the database to which to connect (mandatory)
+    /// - host - Host to which to connect (optional, defaults to "localhost")
+    /// - user - Username under which to connect (optional)
+    /// - password - Password for "user" on the database (optional)
+    ///
+    /// If the database is successfully opened, the version number in the
+    /// schema_version table will be checked against hard-coded value in
+    /// the implementation file.
+    ///
+    /// Finally, all the SQL commands are pre-compiled.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    ///
+    /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+    /// @throw isc::dhcp::DbOpenError Error opening the database
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Virtual destructor.
+    ///
+    /// Releases prepared MySQL statements used by the backend.
+    virtual ~PgSqlHostDataSource();
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an MultipleRecords exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet and having
+    /// a reservation for a specified IPv4 address.
+    ///
+    /// One of the use cases for this method is to detect collisions between
+    /// dynamically allocated addresses and reserved addresses. When the new
+    /// address is assigned to a client, the allocation mechanism should check
+    /// if this address is not reserved for some other host and do not allocate
+    /// this address if reservation is present.
+    ///
+    /// Implementations of this method should guard against invalid addresses,
+    /// such as IPv6 address.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv4 address.
+    ///
+    /// @return Const @c Host object using a specified IPv4 address.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an MultipleRecords exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+            const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same host, where possible. For example, when the
+    /// reservation for the same HW address and subnet id is added twice, the
+    /// addHost method should throw an DuplicateEntry exception. Note, that
+    /// usually it is impossible to guard against adding duplicated host, where
+    /// one instance is identified by HW address, another one by DUID.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host);
+
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const {
+        return (std::string("mysql"));
+    }
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name.
+    ///
+    /// @return "mysql".
+    virtual std::string getName() const;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    ///
+    /// @return Description of the backend.
+    virtual std::string getDescription() const;
+
+    /// @brief Returns backend version.
+    ///
+    /// @return Version number stored in the database, as a pair of unsigned
+    ///         integers. "first" is the major version number, "second" the
+    ///         minor number.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        has failed.
+    virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+private:
+
+    /// @brief Pointer to the implementation of the @ref PgSqlHostDataSource.
+    PgSqlHostDataSourceImpl* impl_; 
+};
+
+}
+}
+
+#endif // PGSQL_HOST_DATA_SOURCE_H
index 74386b5bbafd68698fdf8d7a95eae35154295198..2ddf60c0131029e8021ffc523c15c28bd8bab492 100644 (file)
@@ -26,6 +26,8 @@ using namespace std;
 
 namespace {
 
+/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source columns
+
 /// @brief Catalog of all the SQL statements currently supported.  Note
 /// that the order columns appear in statement body must match the order they
 /// that the occur in the table.  This does not apply to the where clause.
index 9cc51975caff61526d0b49a2082fa415775a7397..8871706313310235e966d3e98f13d6036453f0cc 100755 (executable)
@@ -69,5 +69,48 @@ TEST(PgSqlExchangeTest, convertTimeTest) {
     EXPECT_EQ(ref_time, from_time);
 }
 
+TEST(PsqlBindArray, basicOperation) {
+    
+    PsqlBindArray b;
+
+    uint8_t small_int = 25;
+    b.add(small_int);
+
+    int reg_int = 376;
+    b.add(reg_int);
+
+    uint64_t big_int = 86749032;
+    b.add(big_int);
+
+    b.add((bool)(1));
+    b.add((bool)(0));
+
+    b.add(isc::asiolink::IOAddress("192.2.15.34"));
+    b.add(isc::asiolink::IOAddress("3001::1"));
+
+    std::string str("just a string");
+    b.add(str);
+
+    std::vector<uint8_t> bytes;
+    for (int i = 0; i < 10; i++) {
+        bytes.push_back(i+1);
+    }
+
+    b.add(bytes);
+
+    std::string expected = 
+        "0 : \"25\"\n"
+        "1 : \"376\"\n"
+        "2 : \"86749032\"\n"
+        "3 : \"TRUE\"\n"
+        "4 : \"FALSE\"\n"
+        "5 : \"3221360418\"\n"
+        "6 : \"3001::1\"\n"
+        "7 : \"just a string\"\n"
+        "8 : 0x010203040506070809\n";
+
+    EXPECT_EQ(expected, b.toText());
+}
+
 }; // namespace