// Copyright (C) 2015-2026 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 <asiolink/io_service.h>
#include <database/db_exceptions.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_db_access.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/network_state.h>
#include <dhcpsrv/timer_mgr.h>
#include <mysql_hb_log.h>
#include <mysql_host_data_source.h>
#include <util/buffer.h>
#include <util/multi_threading_mgr.h>
#include <util/optional.h>
#include <config_backend/constants.h>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/array.hpp>
#include <boost/foreach.hpp>
#include <boost/pointer_cast.hpp>

#include <mysql.h>
#include <mysqld_error.h>
#include <stdint.h>

#include <mutex>
#include <string>

using namespace isc;
using namespace isc::asiolink;
using namespace isc::db;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace std;

namespace {

/// @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::LAST_IDENTIFIER_TYPE);

/// @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 MySqlHostExchange should be used to retrieve
/// information about IPv6 reservations and options.
///
/// Database schema contains several unique indexes to guard against adding
/// multiple hosts for the same client identifier in a single subnet and for
/// adding multiple hosts with a reservation for the same IPv4 address in a
/// single subnet. The exceptions that have to be taken into account are
/// listed below:
/// - zero or null IPv4 address indicates that there is no reservation for the
///   IPv4 address for the host,
/// - zero or null subnet identifier (either IPv4 or IPv6) indicates that
///   this subnet identifier must be ignored. Specifically, this is the case
///   when host reservation is for the DHCPv4 server, the IPv6 subnet id should
///   be ignored. Conversely, when host reservation is created for the DHCPv6 server,
///   the IPv4 subnet id should be ignored.
///
/// To exclude those special case values from the unique indexes, the MySQL
/// backend relies on the property of the unique indexes in MySQL, i.e. null
/// values are excluded from unique indexes. That means that there might be
/// multiple null values in a given column on which unique index is applied.
/// Therefore, the MySQL backend converts subnet identifiers and IPv4 addresses
/// of 0 to null before inserting a host to the database.
class MySqlHostExchange {
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 size_t HOST_ID_COL = 0;
    static const size_t DHCP_IDENTIFIER_COL = 1;
    static const size_t DHCP_IDENTIFIER_TYPE_COL = 2;
    static const size_t DHCP4_SUBNET_ID_COL = 3;
    static const size_t DHCP6_SUBNET_ID_COL = 4;
    static const size_t IPV4_ADDRESS_COL = 5;
    static const size_t HOSTNAME_COL = 6;
    static const size_t DHCP4_CLIENT_CLASSES_COL = 7;
    static const size_t DHCP6_CLIENT_CLASSES_COL = 8;
    static const size_t USER_CONTEXT_COL = 9;
    static const size_t DHCP4_NEXT_SERVER_COL = 10;
    static const size_t DHCP4_SERVER_HOSTNAME_COL = 11;
    static const size_t DHCP4_BOOT_FILE_NAME_COL = 12;
    static const size_t AUTH_KEY_COL = 13;
    //@}
    /// @brief Number of columns returned for SELECT queries sent by this class.
    static const size_t HOST_COLUMNS = 14;

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.
    MySqlHostExchange(const size_t additional_columns_num = 0)
        : columns_num_(HOST_COLUMNS + additional_columns_num),
          bind_(columns_num_), columns_(columns_num_),
          error_(columns_num_, MLM_FALSE), host_id_(0),
          dhcp_identifier_length_(0), dhcp_identifier_type_(0),
          dhcp4_subnet_id_(SUBNET_ID_UNUSED),
          dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0),
          hostname_length_(0), dhcp4_client_classes_length_(0),
          dhcp6_client_classes_length_(0),
          user_context_length_(0),
          dhcp4_next_server_(0),
          dhcp4_server_hostname_length_(0),
          dhcp4_boot_file_name_length_(0),
          auth_key_length_(0),
          dhcp4_subnet_id_null_(MLM_FALSE),
          dhcp6_subnet_id_null_(MLM_FALSE),
          ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
          dhcp4_client_classes_null_(MLM_FALSE),
          dhcp6_client_classes_null_(MLM_FALSE),
          user_context_null_(MLM_FALSE),
          dhcp4_next_server_null_(MLM_FALSE),
          dhcp4_server_hostname_null_(MLM_FALSE),
          dhcp4_boot_file_name_null_(MLM_FALSE),
          auth_key_null_(MLM_FALSE) {

        // Fill arrays with 0 so as they don't include any garbage.
        memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
        memset(hostname_, 0, sizeof(hostname_));
        memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
        memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
        memset(user_context_, 0, sizeof(user_context_));
        memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
        memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
        memset(auth_key_, 0, sizeof(auth_key_));

        // Set the column names for use by this class. This only comprises
        // names used by the MySqlHostExchange class. Derived classes will
        // need to set names for the columns they use.
        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";
        columns_[USER_CONTEXT_COL] = "user_context";
        columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
        columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
        columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
        columns_[AUTH_KEY_COL] = "auth_key";

        static_assert(13 < HOST_COLUMNS, "13 < HOST_COLUMNS");
    }

    /// @brief Virtual destructor.
    virtual ~MySqlHostExchange() {
    }

    /// @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));
    }

    /// @brief Returns value of host id.
    ///
    /// This method is used by derived classes.
    uint64_t getHostId() const {
        return (host_id_);
    }

    /// @brief Set error indicators
    ///
    /// Sets the error indicator for each of the MYSQL_BIND elements. It points
    /// the "error" field within an element of the input array to the
    /// corresponding element of the passed error array.
    ///
    /// @param bind Array of BIND elements
    /// @param error Array of error elements.  If there is an error in getting
    ///        data associated with one of the "bind" elements, the
    ///        corresponding element in the error array is set to MLM_TRUE.
    static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
                                   std::vector<my_bools>& error) {
        for (size_t i = 0; i < error.size(); ++i) {
            error[i] = MLM_FALSE;
            bind[i].error = reinterpret_cast<my_bool*>(&error[i]);
        }
    }

    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @param error Array of error elements.  An element is set to MLM_TRUE
    ///        if the corresponding column in the database is the source of
    ///        the error.
    /// @param names Array of column names, the same size as the error array.
    /// @param count Size of each of the arrays.
    static std::string getColumnsInError(std::vector<my_bools>& error,
                                         const std::vector<std::string>& names) {
        std::string result = "";

        // Accumulate list of column names
        for (size_t i = 0; i < names.size(); ++i) {
            if (error[i] == MLM_TRUE) {
                if (!result.empty()) {
                    result += ", ";
                }
                result += names[i];
            }
        }

        if (result.empty()) {
            result = "(None)";
        }

        return (result);
    }

    /// @brief Create MYSQL_BIND objects for Host Pointer
    ///
    /// Fills in the MYSQL_BIND array for sending data stored in the 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.
    /// @param unique_ip boolean value indicating if multiple reservations for the
    ///        same IP address are allowed (false) or not (true).
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host, const bool unique_ip) {
        // Store host object to ensure it remains valid.
        host_ = host;

        // 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.
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());

        // Set up the structures for the various components of the host structure.

        try {
            // host_id : INT UNSIGNED NOT NULL
            // The host_id is auto_incremented by MySQL database,
            // so we need to pass the NULL value
            host_id_ = 0;
            bind_[0].buffer_type = MYSQL_TYPE_LONG;
            bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
            bind_[0].is_unsigned = MLM_TRUE;

            // dhcp_identifier : VARBINARY(255) NOT NULL
            dhcp_identifier_length_ = host->getIdentifier().size();
            memcpy(static_cast<void*>(dhcp_identifier_buffer_),
                   &(host->getIdentifier())[0],
                   host->getIdentifier().size());

            bind_[1].buffer_type = MYSQL_TYPE_BLOB;
            bind_[1].buffer = dhcp_identifier_buffer_;
            bind_[1].buffer_length = dhcp_identifier_length_;
            bind_[1].length = &dhcp_identifier_length_;

            // dhcp_identifier_type : TINYINT NOT NULL
            dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
            bind_[2].buffer_type = MYSQL_TYPE_TINY;
            bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
            bind_[2].is_unsigned = MLM_TRUE;

            // dhcp4_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp4_subnet_id_
            dhcp4_subnet_id_ = host->getIPv4SubnetID();
            dhcp4_subnet_id_null_ = host->getIPv4SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
            bind_[3].buffer_type = MYSQL_TYPE_LONG;
            bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
            bind_[3].is_unsigned = MLM_TRUE;
            bind_[3].is_null = &dhcp4_subnet_id_null_;

            // dhcp6_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp6_subnet_id_
            dhcp6_subnet_id_ = host->getIPv6SubnetID();
            dhcp6_subnet_id_null_ = host->getIPv6SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
            bind_[4].buffer_type = MYSQL_TYPE_LONG;
            bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
            bind_[4].is_unsigned = MLM_TRUE;
            bind_[4].is_null = &dhcp6_subnet_id_null_;

            // ipv4_address : INT UNSIGNED NULL
            // The address in the Host structure is an IOAddress object.  Convert
            // this to an integer for storage.
            ipv4_address_ = host->getIPv4Reservation().toUint32();
            ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
            bind_[5].buffer_type = MYSQL_TYPE_LONG;
            bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
            bind_[5].is_unsigned = MLM_TRUE;
            bind_[5].is_null = &ipv4_address_null_;

            // hostname : VARCHAR(255) NULL
            strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
            hostname_length_ = host->getHostname().length();
            bind_[6].buffer_type = MYSQL_TYPE_STRING;
            bind_[6].buffer = reinterpret_cast<char*>(hostname_);
            bind_[6].buffer_length = hostname_length_;

            // dhcp4_client_classes : VARCHAR(255) NULL
            bind_[7].buffer_type = MYSQL_TYPE_STRING;
            // Override default separator to not include space after comma.
            string classes4_txt = host->getClientClasses4().toText(",");
            strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
            bind_[7].buffer = dhcp4_client_classes_;
            bind_[7].buffer_length = classes4_txt.length();

            // dhcp6_client_classes : VARCHAR(255) NULL
            bind_[8].buffer_type = MYSQL_TYPE_STRING;
            // Override default separator to not include space after comma.
            string classes6_txt = host->getClientClasses6().toText(",");
            strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
            bind_[8].buffer = dhcp6_client_classes_;
            bind_[8].buffer_length = classes6_txt.length();

            // user_context : TEXT NULL
            ConstElementPtr ctx = host->getContext();
            if (ctx) {
                bind_[9].buffer_type = MYSQL_TYPE_STRING;
                string ctx_txt = ctx->str();
                strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
                bind_[9].buffer = user_context_;
                bind_[9].buffer_length = ctx_txt.length();
            } else {
                bind_[9].buffer_type = MYSQL_TYPE_NULL;
            }

            // ipv4_address : INT UNSIGNED NULL
            // The address in the Host structure is an IOAddress object.  Convert
            // this to an integer for storage.
            dhcp4_next_server_ = host->getNextServer().toUint32();
            bind_[10].buffer_type = MYSQL_TYPE_LONG;
            bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
            bind_[10].is_unsigned = MLM_TRUE;
            // bind_[10].is_null = &MLM_FALSE; // commented out for performance
                                               // reasons, see memset() above

            // dhcp4_server_hostname
            bind_[11].buffer_type = MYSQL_TYPE_STRING;
            std::string server_hostname = host->getServerHostname();
            strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
                    SERVER_HOSTNAME_MAX_LEN - 1);
            bind_[11].buffer = dhcp4_server_hostname_;
            bind_[11].buffer_length = server_hostname.length();

            // dhcp4_boot_file_name
            bind_[12].buffer_type = MYSQL_TYPE_STRING;
            std::string boot_file_name = host->getBootFileName();
            strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
                    BOOT_FILE_NAME_MAX_LEN - 1);
            bind_[12].buffer = dhcp4_boot_file_name_;
            bind_[12].buffer_length = boot_file_name.length();

            // auth key
            bind_[13].buffer_type = MYSQL_TYPE_STRING;
            std::string auth_key = host->getKey().toText();
            std::strncpy(auth_key_, auth_key.c_str(), TEXT_AUTH_KEY_LEN - 1);
            auth_key_null_ = auth_key.empty() ? MLM_TRUE : MLM_FALSE;
            bind_[13].buffer = auth_key_;
            bind_[13].buffer_length = auth_key.length();

        } catch (const std::exception& ex) {
            isc_throw(DbOperationError,
                      "Could not create bind array from Host: "
                      << host->getHostname() << ", reason: " << ex.what());
        }

        // Add the data to the vector.
        std::vector<MYSQL_BIND> vec(bind_.begin(), bind_.begin() + HOST_COLUMNS);

        // When checking whether the IP is unique we need to bind the IPv4 address
        // at the end of the query as it has additional binding for the IPv4
        // address.
        if (unique_ip) {
            vec.push_back(bind_[5]); // ipv4_address
            vec.push_back(bind_[3]); // subnet_id
        }
        return (vec);
    }

    /// @brief Create BIND array to receive Host data.
    ///
    /// Creates a MYSQL_BIND array to receive Host data from the database.
    /// After data is successfully received, @ref retrieveHost can be called
    /// to retrieve the Host object.
    ///
    /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
    virtual std::vector<MYSQL_BIND> createBindForReceive() {
        // Initialize MYSQL_BIND array.
        // 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.
        // This also takes care of setting bind_[X].is_null to MLM_FALSE.
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());

        // host_id : INT UNSIGNED NOT NULL
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
        bind_[0].is_unsigned = MLM_TRUE;

        // dhcp_identifier : VARBINARY(255) NOT NULL
        dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
        bind_[1].buffer_length = dhcp_identifier_length_;
        bind_[1].length = &dhcp_identifier_length_;

        // dhcp_identifier_type : TINYINT NOT NULL
        bind_[2].buffer_type = MYSQL_TYPE_TINY;
        bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
        bind_[2].is_unsigned = MLM_TRUE;

        // dhcp4_subnet_id : INT UNSIGNED NULL
        dhcp4_subnet_id_null_ = MLM_FALSE;
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
        bind_[3].is_unsigned = MLM_TRUE;
        bind_[3].is_null = &dhcp4_subnet_id_null_;

        // dhcp6_subnet_id : INT UNSIGNED NULL
        dhcp6_subnet_id_null_ = MLM_FALSE;
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
        bind_[4].is_unsigned = MLM_TRUE;
        bind_[4].is_null = &dhcp6_subnet_id_null_;

        // ipv4_address : INT UNSIGNED NULL
        ipv4_address_null_ = MLM_FALSE;
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
        bind_[5].is_unsigned = MLM_TRUE;
        bind_[5].is_null = &ipv4_address_null_;

        // hostname : VARCHAR(255) NULL
        hostname_null_ = MLM_FALSE;
        hostname_length_ = sizeof(hostname_);
        bind_[6].buffer_type = MYSQL_TYPE_STRING;
        bind_[6].buffer = reinterpret_cast<char*>(hostname_);
        bind_[6].buffer_length = hostname_length_;
        bind_[6].length = &hostname_length_;
        bind_[6].is_null = &hostname_null_;

        // dhcp4_client_classes : VARCHAR(255) NULL
        dhcp4_client_classes_null_ = MLM_FALSE;
        dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
        bind_[7].buffer_type = MYSQL_TYPE_STRING;
        bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
        bind_[7].buffer_length = dhcp4_client_classes_length_;
        bind_[7].length = &dhcp4_client_classes_length_;
        bind_[7].is_null = &dhcp4_client_classes_null_;

        // dhcp6_client_classes : VARCHAR(255) NULL
        dhcp6_client_classes_null_ = MLM_FALSE;
        dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
        bind_[8].buffer_type = MYSQL_TYPE_STRING;
        bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
        bind_[8].buffer_length = dhcp6_client_classes_length_;
        bind_[8].length = &dhcp6_client_classes_length_;
        bind_[8].is_null = &dhcp6_client_classes_null_;

        // user_context : TEXT NULL
        user_context_null_ = MLM_FALSE;
        user_context_length_ = sizeof(user_context_);
        bind_[9].buffer_type = MYSQL_TYPE_STRING;
        bind_[9].buffer = reinterpret_cast<char*>(user_context_);
        bind_[9].buffer_length = user_context_length_;
        bind_[9].length = &user_context_length_;
        bind_[9].is_null = &user_context_null_;

        // dhcp4_next_server
        dhcp4_next_server_null_ = MLM_FALSE;
        bind_[10].buffer_type = MYSQL_TYPE_LONG;
        bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
        bind_[10].is_unsigned = MLM_TRUE;
        bind_[10].is_null = &dhcp4_next_server_null_;

        // dhcp4_server_hostname
        dhcp4_server_hostname_null_ = MLM_FALSE;
        dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
        bind_[11].buffer_type = MYSQL_TYPE_STRING;
        bind_[11].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
        bind_[11].buffer_length = dhcp4_server_hostname_length_;
        bind_[11].length = &dhcp4_server_hostname_length_;
        bind_[11].is_null = &dhcp4_server_hostname_null_;

        // dhcp4_boot_file_name
        dhcp4_boot_file_name_null_ = MLM_FALSE;
        dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
        bind_[12].buffer_type = MYSQL_TYPE_STRING;
        bind_[12].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
        bind_[12].buffer_length = dhcp4_boot_file_name_length_;
        bind_[12].length = &dhcp4_boot_file_name_length_;
        bind_[12].is_null = &dhcp4_boot_file_name_null_;

        // auth_key_
        auth_key_null_ = MLM_FALSE;
        auth_key_length_ = sizeof(auth_key_);
        bind_[13].buffer_type = MYSQL_TYPE_STRING;
        bind_[13].buffer = reinterpret_cast<char*>(auth_key_);
        bind_[13].buffer_length = auth_key_length_;
        bind_[13].length = &auth_key_length_;
        bind_[13].is_null = &auth_key_null_;

        // Add the error flags
        setErrorIndicators(bind_, error_);

        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
        return (bind_);
    }

    /// @brief Copy received data into Host object
    ///
    /// This function copies information about the host into a newly created
    /// @ref Host object. This method is called after @ref createBindForReceive.
    /// has been used.
    ///
    /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
    /// @ref Host object returned.
    HostPtr retrieveHost() {
        // Check if the identifier stored in the database is correct.
        if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
            isc_throw(BadValue, "invalid dhcp identifier type returned: "
                      << static_cast<int>(dhcp_identifier_type_));
        }
        // Set the dhcp identifier type in a variable of the appropriate
        // data type.
        Host::IdentifierType type =
            static_cast<Host::IdentifierType>(dhcp_identifier_type_);

        // Set DHCPv4 subnet ID to the value returned. If NULL returned,
        // set to 0.
        SubnetID ipv4_subnet_id(SUBNET_ID_UNUSED);
        if (dhcp4_subnet_id_null_ == MLM_FALSE) {
            ipv4_subnet_id = dhcp4_subnet_id_;
        }

        // Set DHCPv6 subnet ID to the value returned. If NULL returned,
        // set to 0.
        SubnetID ipv6_subnet_id(SUBNET_ID_UNUSED);
        if (dhcp6_subnet_id_null_ == MLM_FALSE) {
            ipv6_subnet_id = dhcp6_subnet_id_;
        }

        // Set IPv4 address reservation if it was given, if not, set IPv4 zero
        // address
        asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
        if (ipv4_address_null_ == MLM_FALSE) {
            ipv4_reservation = asiolink::IOAddress(ipv4_address_);
        }

        // Set hostname if non NULL value returned. Otherwise, leave an
        // empty string.
        std::string hostname;
        if (hostname_null_ == MLM_FALSE) {
            hostname = std::string(hostname_, hostname_length_);
        }

        // Set DHCPv4 client classes if non NULL value returned.
        std::string dhcp4_client_classes;
        if (dhcp4_client_classes_null_ == MLM_FALSE) {
            dhcp4_client_classes = std::string(dhcp4_client_classes_,
                                               dhcp4_client_classes_length_);
        }

        // Set DHCPv6 client classes if non NULL value returned.
        std::string dhcp6_client_classes;
        if (dhcp6_client_classes_null_ == MLM_FALSE) {
            dhcp6_client_classes = std::string(dhcp6_client_classes_,
                                               dhcp6_client_classes_length_);
        }

        // Convert user_context to string as well.
        std::string user_context;
        if (user_context_null_ == MLM_FALSE) {
            user_context.assign(user_context_, user_context_length_);
        }

        // Set next server value (siaddr) if non NULL value returned.
        asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
        if (dhcp4_next_server_null_ == MLM_FALSE) {
            next_server = asiolink::IOAddress(dhcp4_next_server_);
        }

        // Set server hostname (sname) if non NULL value returned.
        std::string dhcp4_server_hostname;
        if (dhcp4_server_hostname_null_ == MLM_FALSE) {
            dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
                                                dhcp4_server_hostname_length_);
        }

        // Set boot file name (file) if non NULL value returned.
        std::string dhcp4_boot_file_name;
        if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
            dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
                                               dhcp4_boot_file_name_length_);
        }

        // Set the auth key if a non empty array is retrieved
        std::string auth_key;
        if (auth_key_null_ == MLM_FALSE) {
            auth_key = std::string(auth_key_, auth_key_length_);
        }

        // Create and return Host object from the data gathered.
        HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
                           type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
                           hostname, dhcp4_client_classes, dhcp6_client_classes,
                           next_server, dhcp4_server_hostname,
                           dhcp4_boot_file_name, AuthKey(auth_key)));
        h->setHostId(host_id_);

        // Set the user context if there is one.
        if (!user_context.empty()) {
            try {
                ConstElementPtr ctx = Element::fromJSON(user_context);
                if (!ctx || (ctx->getType() != Element::map)) {
                    isc_throw(BadValue, "user context '" << user_context
                              << "' is not a JSON map");
                }
                h->setContext(ctx);
            } catch (const isc::data::JSONError& ex) {
                isc_throw(BadValue, "user context '" << user_context
                          << "' is invalid JSON: " << ex.what());
            }
        }

        return (h);
    }

    /// @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.
    ///
    /// This method must be overridden in the derived classes to also
    /// retrieve IPv6 reservations and DHCP options associated with a host.
    ///
    /// @param [out] hosts Collection of hosts to which a new host created
    ///        from the processed data should be inserted.
    virtual void processFetchedData(ConstHostCollection& hosts) {
        HostPtr host;
        // Add new host only if there are no hosts yet or the host id of the
        // most recently added host is different than the host id of the
        // currently processed host.
        if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
            // Create Host object from the fetched data and append it to the
            // collection.
            host = retrieveHost();
            hosts.push_back(host);
        }
    }

    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @return Comma-separated list of columns in error, or the string
    ///         "(None)".
    std::string getErrorColumns() {
        return (getColumnsInError(error_, columns_));
    }

protected:

    /// Number of columns returned in queries.
    size_t columns_num_;

    /// Vector of MySQL bindings.
    std::vector<MYSQL_BIND> bind_;

    /// Column names.
    std::vector<std::string> columns_;

    /// Error array.
    std::vector<my_bools> error_;

    /// Pointer to Host object holding information to be inserted into
    /// Hosts table.
    HostPtr host_;

private:

    /// Host identifier (primary key in Hosts table).
    uint64_t host_id_;

    /// Buffer holding client's identifier (e.g. DUID, HW address)
    /// in the binary format.
    uint8_t dhcp_identifier_buffer_[ClientId::MAX_CLIENT_ID_LEN];

    /// Length of a data in the dhcp_identifier_buffer_.
    unsigned long dhcp_identifier_length_;

    /// Type of the identifier in the dhcp_identifier_buffer_. This
    /// value corresponds to the @ref Host::IdentifierType value.
    uint8_t dhcp_identifier_type_;

    /// DHCPv4 subnet identifier.
    uint32_t dhcp4_subnet_id_;

    /// DHCPv6 subnet identifier.
    uint32_t dhcp6_subnet_id_;

    /// Reserved IPv4 address.
    uint32_t ipv4_address_;

    /// Name reserved for the host.
    char hostname_[HOSTNAME_MAX_LEN];

    /// Hostname length.
    unsigned long hostname_length_;

    /// A string holding comma separated list of DHCPv4 client classes.
    char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];

    /// A length of the string holding comma separated list of DHCPv4
    /// client classes.
    unsigned long dhcp4_client_classes_length_;

    /// A string holding comma separated list of DHCPv6 client classes.
    char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];

    /// A length of the string holding comma separated list of DHCPv6
    /// client classes.
    unsigned long dhcp6_client_classes_length_;

    /// @brief Buffer holding textual user context.
    char user_context_[USER_CONTEXT_MAX_LEN];

    /// @brief User context length.
    unsigned long user_context_length_;

    /// Next server address (siaddr).
    uint32_t dhcp4_next_server_;

    /// Server hostname (sname).
    char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];

    /// A length of the string holding server hostname.
    unsigned long dhcp4_server_hostname_length_;

    /// Boot file name (file).
    char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];

    /// A length of the string holding boot file name.
    unsigned long dhcp4_boot_file_name_length_;

    /// Authentication keys
    char auth_key_[TEXT_AUTH_KEY_LEN];

    /// The length of the string for holding keys
    unsigned long auth_key_length_;

    /// @name Boolean values indicating if values of specific columns in
    /// the database are NULL.
    //@{
    /// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
    my_bool dhcp4_subnet_id_null_;

    /// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
    my_bool dhcp6_subnet_id_null_;

    /// Boolean flag indicating if the value of IPv4 reservation is NULL.
    my_bool ipv4_address_null_;

    /// Boolean flag indicating if the value if hostname is NULL.
    my_bool hostname_null_;

    /// Boolean flag indicating if the value of DHCPv4 client classes is
    /// NULL.
    my_bool dhcp4_client_classes_null_;

    /// Boolean flag indicating if the value of DHCPv6 client classes is
    /// NULL.
    my_bool dhcp6_client_classes_null_;

    /// @brief Boolean flag indicating if the value of user context is NULL.
    my_bool user_context_null_;

    /// Boolean flag indicating if the value of next server is NULL.
    my_bool dhcp4_next_server_null_;

    /// Boolean flag indicating if the value of server hostname is NULL.
    my_bool dhcp4_server_hostname_null_;

    /// Boolean flag indicating if the value of boot file name is NULL.
    my_bool dhcp4_boot_file_name_null_;

    /// Boolean flag indicating if the value of string is NULL.
    my_bool auth_key_null_;

    //@}
};

/// @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 MySqlHostWithOptionsExchange : public MySqlHostExchange {
private:

    /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
    static const size_t OPTION_COLUMNS = 9;

    /// @brief Receives DHCPv4 or DHCPv6 options information from the
    /// dhcp4_options or dhcp6_options tables respectively.
    ///
    /// The MySqlHostWithOptionsExchange 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), cancelled_(false),
          user_context_length_(0), client_classes_length_(0),
          option_id_null_(MLM_FALSE), code_null_(MLM_FALSE),
          value_null_(MLM_FALSE), formatted_value_null_(MLM_FALSE),
          space_null_(MLM_FALSE), user_context_null_(MLM_FALSE),
          client_classes_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),
          cancelled_index_(start_column_ + 6),
          user_context_index_(start_column_ + 7),
          client_classes_index_(start_column_ + 8),
          most_recent_option_id_(0) {

            memset(value_, 0, sizeof(value_));
            memset(formatted_value_, 0, sizeof(formatted_value_));
            memset(space_, 0, sizeof(space_));
            memset(user_context_, 0, sizeof(user_context_));
        }

        /// @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) {
                space.assign(space_, space_length_);
            }

            // If empty or null space provided, use a default top level space.
            if (space.empty()) {
                space = (universe_ == Option::V4 ?
                         DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
            }

            // Convert formatted_value to string.
            std::string formatted_value;
            if (formatted_value_null_ == MLM_FALSE) {
                formatted_value.assign(formatted_value_, formatted_value_length_);
            }

            // Convert user_context to string.
            std::string user_context;
            if (user_context_null_ == MLM_FALSE) {
                user_context.assign(user_context_, user_context_length_);
            }

            // Convert client classes to string.
            std::string client_classes;
            if (client_classes_null_ == MLM_FALSE) {
                client_classes.assign(client_classes_, client_classes_length_);
            }

            // 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.

            // Check if this is a standard option.
            OptionDefinitionPtr def = LibDHCP::getOptionDef(space, 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_);
            }

            // Finish with a last resort option definition.
            if (!def) {
                def = LibDHCP::getLastResortOptionDef(space, code_);
            }

            OptionPtr option;

            // If no definition found, we use generic option type.
            if (!def) {
                // We have to pay attention if the value is NULL. If it is,
                // we must create an empty option instance. We can't rely on
                // the value_length_ because it may contain garbage for the
                // null values. Thus we check explicitly whether or not the
                // NULL flag is set.
                if (value_null_ == MLM_FALSE) {
                    OptionBuffer buf(value_, value_ + value_length_);
                    option.reset(new Option(universe_, code_, buf.begin(),
                                            buf.end()));
                } else {
                    option.reset(new Option(universe_, code_));
                }
            } 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_);
                    // Again, check if the value is null before submitting the
                    // buffer to the factory function.
                    option = def->optionFactory(universe_, code_, buf.begin(),
                                                value_null_ == MLM_FALSE ? buf.end() :
                                                buf.begin());
                } 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_, cancelled_,
                                  formatted_value);

            // Set the user context if there is one into the option descriptor.
            if (!user_context.empty()) {
                try {
                    ConstElementPtr ctx = Element::fromJSON(user_context);
                    if (!ctx || (ctx->getType() != Element::map)) {
                        isc_throw(BadValue, "user context '" << user_context
                                  << "' is no a JSON map");
                    }
                    desc.setContext(ctx);
                } catch (const isc::data::JSONError& ex) {
                    isc_throw(BadValue, "user context '" << user_context
                              << "' is invalid JSON: " << ex.what());
                }
            }

            // Set option's class tag client classes. Should always be content
            // per schema rules, even if its's an empty list '[  ]'.
            if (!client_classes.empty()) {
                try {
                    auto cclist_element = Element::fromJSON(client_classes);
                    desc.client_classes_.fromElement(cclist_element);
                } catch (const std::exception& ex) {
                    isc_throw(BadValue, "client classes '" << client_classes
                              << "' is invalid JSON: " << ex.what());
                }
            }

            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";
            columns[cancelled_index_] = "cancelled";
            columns[user_context_index_] = "user_context";
            columns[client_classes_index_] = "client_classes";
        }

        /// @brief Initialize binding table fields for options.
        ///
        /// Resets most_recent_option_id_ to 0 and other exchange members to
        /// default values.
        ///
        /// @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_ and other exchange members to
            // start over with options processing.
            most_recent_option_id_ = 0;

            option_id_ = 0;
            code_ = 0;
            persistent_ = false;
            cancelled_ = false;
            option_id_null_ = MLM_FALSE;
            code_null_ = MLM_FALSE;
            value_null_ = MLM_FALSE;
            formatted_value_null_ = MLM_FALSE;
            space_null_ = MLM_FALSE;
            user_context_null_ = MLM_FALSE;
            client_classes_null_ = MLM_FALSE;

            memset(value_, 0, sizeof(value_));
            memset(formatted_value_, 0, sizeof(formatted_value_));
            memset(space_, 0, sizeof(space_));
            memset(user_context_, 0, sizeof(user_context_));

            // 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;
            bind[option_id_index_].is_null = &option_id_null_;

            // 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;

            // cancelled : TINYINT(1) NOT NULL DEFAULT 0
            bind[cancelled_index_].buffer_type = MYSQL_TYPE_TINY;
            bind[cancelled_index_].buffer = reinterpret_cast<char*>(&cancelled_);
            bind[cancelled_index_].is_unsigned = MLM_TRUE;

            // user_context : TEXT NULL
            user_context_length_ = sizeof(user_context_);
            bind[user_context_index_].buffer_type = MYSQL_TYPE_STRING;
            bind[user_context_index_].buffer = reinterpret_cast<char*>(user_context_);
            bind[user_context_index_].buffer_length = user_context_length_;
            bind[user_context_index_].length = &user_context_length_;
            bind[user_context_index_].is_null = &user_context_null_;

            // client_classes : TEXT NOT NULL
            client_classes_length_ = sizeof(client_classes_);
            bind[client_classes_index_].buffer_type = MYSQL_TYPE_STRING;
            bind[client_classes_index_].buffer = reinterpret_cast<char*>(client_classes_);
            bind[client_classes_index_].buffer_length = client_classes_length_;
            bind[client_classes_index_].length = &client_classes_length_;
            bind[client_classes_index_].is_null = &client_classes_null_;
        }

    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.
        uint32_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_;

        /// @brief Flag indicating if option must be never sent.
        bool cancelled_;

        /// @brief Buffer holding textual user context of an option.
        char user_context_[USER_CONTEXT_MAX_LEN];

        /// @brief User context length.
        unsigned long user_context_length_;

        /// @brief Buffer holding textual client classes of an option.
        char client_classes_[cb::CLIENT_CLASS_LIST_BUF_LENGTH];

        /// @brief User context length.
        unsigned long client_classes_length_;

        /// @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_;

        /// @brief Boolean flag indicating if the DHCPv4 option user context is NULL.
        my_bool user_context_null_;

        /// @brief Boolean flag indicating if the DHCPv4 option client classes is NULL.
        my_bool client_classes_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 Cancelled
        size_t cancelled_index_;
        //@}

        /// @brief User context
        size_t user_context_index_;

        /// @brief Client classes
        size_t client_classes_index_;

        /// @brief Option id for last processed row.
        uint32_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.
    MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
                                 const size_t additional_columns_num = 0)
        : MySqlHostExchange(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) override {
        // 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() override {
        // The following call sets bind_ values between 0 and 8.
        static_cast<void>(MySqlHostExchange::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 MySqlHostWithOptionsExchange class with the
/// mechanisms to retrieve IPv6 reservations. This class is used in situations
/// 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 MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
private:

    /// @brief Number of columns holding IPv6 reservation information.
    static const size_t RESERVATION_COLUMNS = 7;

public:

    /// @brief Constructor.
    ///
    /// Apart from initializing the base class data structures it also
    /// initializes values representing IPv6 reservation information.
    MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
        : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
          reservation_id_(0),
          reserv_type_(0), reserv_type_null_(MLM_FALSE),
          ipv6_address_buffer_len_(0),
          excluded_prefix_null_(MLM_FALSE), excluded_prefix_buffer_len_(0),
          prefix_len_(0), iaid_(0),
          excluded_prefix_len_(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),
          excluded_prefix_index_(reservation_id_index_ + 5),
          excluded_prefix_len_index_(reservation_id_index_ + 6),
          most_recent_reservation_id_(0) {

        memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
        memset(excluded_prefix_buffer_, 0, sizeof(excluded_prefix_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";
        columns_[excluded_prefix_index_] = "excluded_prefix";
        columns_[excluded_prefix_len_index_] = "excluded_prefix_len";
    }

    /// @brief Returns last fetched reservation id.
    ///
    /// @return Reservation id or 0 if no reservation data is fetched.
    uint32_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.");
        }

        IOAddress addr6 = IOAddress::fromBytes(AF_INET6, ipv6_address_buffer_);
        IPv6Resrv r(type, addr6, prefix_len_);

        if (excluded_prefix_null_ == MLM_FALSE) {
            IOAddress prefix =
                IOAddress::fromBytes(AF_INET6, excluded_prefix_buffer_);
            r.setPDExclude(prefix, excluded_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) override {

        // Call parent class to fetch host information and options.
        MySqlHostWithOptionsExchange::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() override {
        // 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>(MySqlHostWithOptionsExchange::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 BINARY(16)
        ipv6_address_buffer_len_ = isc::asiolink::V6ADDRESS_LEN;
        bind_[address_index_].buffer_type = MYSQL_TYPE_BLOB;
        bind_[address_index_].buffer = reinterpret_cast<char*>(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;

        // excluded_prefix BINARY(16)
        excluded_prefix_null_ = MLM_FALSE;
        excluded_prefix_buffer_len_ = isc::asiolink::V6ADDRESS_LEN;
        bind_[excluded_prefix_index_].buffer_type = MYSQL_TYPE_BLOB;
        bind_[excluded_prefix_index_].buffer =
            reinterpret_cast<char*>(excluded_prefix_buffer_);
        bind_[excluded_prefix_index_].buffer_length = excluded_prefix_buffer_len_;
        bind_[excluded_prefix_index_].length = &excluded_prefix_buffer_len_;
        bind_[excluded_prefix_index_].is_null = &excluded_prefix_null_;

        // excluded_prefix_len : TINYINT
        bind_[excluded_prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
        bind_[excluded_prefix_len_index_].buffer =
            reinterpret_cast<char*>(&excluded_prefix_len_);
        bind_[excluded_prefix_len_index_].is_unsigned = MLM_TRUE;

        // Add the error flags
        setErrorIndicators(bind_, error_);

        return (bind_);
    }

private:

    /// @brief IPv6 reservation id.
    uint32_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.
    uint8_t ipv6_address_buffer_[isc::asiolink::V6ADDRESS_LEN];

    /// @brief Length of the textual address representation.
    unsigned long ipv6_address_buffer_len_;

    /// @brief Boolean flag indicating if excluded prefix field is null.
    my_bool excluded_prefix_null_;

    /// @brief Buffer holding excluded prefix in textual format.
    uint8_t excluded_prefix_buffer_[isc::asiolink::V6ADDRESS_LEN];

    /// @brief Length of the excluded prefix.
    unsigned long excluded_prefix_buffer_len_;

    /// @brief Length of the prefix (128 for addresses)
    uint8_t prefix_len_;

    /// @brief IAID.
    uint32_t iaid_;

    /// @brief Length of the excluded prefix (default 0)
    uint8_t excluded_prefix_len_;

    /// @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 Index of excluded_prefix column.
    size_t excluded_prefix_index_;

    /// @brief Index of excluded_prefix_len column.
    size_t excluded_prefix_len_index_;

    //@}

    /// @brief Reservation id for last processed row.
    uint32_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 MySqlHostIPv6Exchange 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 MySqlIPv6ReservationExchange {
private:

    /// @brief Set number of columns for ipv6_reservation table.
    static const size_t RESRV_COLUMNS = 8;

public:

    /// @brief Constructor
    ///
    /// Initialize class members representing a single IPv6 reservation.
    MySqlIPv6ReservationExchange()
        : host_id_(0), prefix_len_(0), type_(0), iaid_(0),
          resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128),
          addr6_length_(0),
          excluded_prefix_length_(0),
          excluded_prefix_len_(0) {

        // 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";
        columns_[5] = "excluded_prefix";
        columns_[6] = "excluded_prefix_len";

        static_assert(6 < RESRV_COLUMNS, "6 < 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
    /// @param unique_ip boolean value indicating if multiple reservations for the
    ///        same IP address are allowed (false) or not (true).
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
                                              const HostID& id,
                                              const bool unique_ip) {

        // 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 {
            addr6_ = resv.getPrefix().toBytes();
            if (addr6_.size() != isc::asiolink::V6ADDRESS_LEN) {
                isc_throw(DbOperationError, "createBindForSend() - prefix is not "
                          << isc::asiolink::V6ADDRESS_LEN << " bytes long");
            }

            addr6_length_ = isc::asiolink::V6ADDRESS_LEN;
            bind_[0].buffer_type = MYSQL_TYPE_BLOB;
            bind_[0].buffer = reinterpret_cast<char*>(&addr6_[0]);
            bind_[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
            bind_[0].length = &addr6_length_;

            // 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;

            // excluded_prefix BINARY(16) NULL
            Option6PDExcludePtr opt = resv.getPDExclude();
            IOAddress excluded_prefix("::");
            if (opt) {
                excluded_prefix = opt->getExcludedPrefix(resv.getPrefix(),
                                                         resv.getPrefixLen());
                excluded_prefix_ = excluded_prefix.toBytes();
                if (excluded_prefix_.size() != isc::asiolink::V6ADDRESS_LEN) {
                    isc_throw(DbOperationError, "createBindForSend() - "
                              << "excluded prefix is not "
                              << isc::asiolink::V6ADDRESS_LEN
                              << " bytes long");
                }
                excluded_prefix_length_ = isc::asiolink::V6ADDRESS_LEN;
                bind_[4].buffer_type = MYSQL_TYPE_BLOB;
                bind_[4].buffer = reinterpret_cast<char*>(&excluded_prefix_[0]);
                bind_[4].buffer_length = excluded_prefix_length_;
                bind_[4].length = &excluded_prefix_length_;
                excluded_prefix_len_ = opt->getExcludedPrefixLength();
            } else {
                bind_[4].buffer_type = MYSQL_TYPE_NULL;
                excluded_prefix_len_ = 0;
            }

            // excluded_prefix_len TINYINT UNSIGNED
            bind_[5].buffer_type = MYSQL_TYPE_TINY;
            bind_[5].buffer = reinterpret_cast<char*>(&excluded_prefix_len_);
            bind_[5].is_unsigned = MLM_TRUE;

            // host_id INT UNSIGNED NOT NULL
            bind_[6].buffer_type = MYSQL_TYPE_LONG;
            bind_[6].buffer = reinterpret_cast<char*>(&host_id_);
            bind_[6].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.
        std::vector<MYSQL_BIND> vec(&bind_[0], &bind_[RESRV_COLUMNS-1]);

        // When checking whether the IP is unique we need to bind the IPv6 address
        // and prefix length at the end of the query as it has additional binding
        // for the IPv6 address and prefix length.
        if (unique_ip) {
            vec.push_back(bind_[0]); // address
            vec.push_back(bind_[1]); // prefix_len
        }

        return (vec);
    }

private:

    /// @brief Host unique identifier.
    uint64_t host_id_;

    /// @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 Binary address data.
    std::vector<uint8_t> addr6_;

    /// @brief Binary address length.
    unsigned long addr6_length_;

    /// @brief Binary excluded prefix data.
    std::vector<uint8_t> excluded_prefix_;

    /// @brief Binary excluded prefix length.
    unsigned long excluded_prefix_length_;

    /// @brief Excluded prefix length.
    uint8_t excluded_prefix_len_;
};

/// @brief This class is used for inserting options into a database.
///
/// This class supports inserting both DHCPv4 and DHCPv6 options.
class MySqlOptionExchange {
    /// @brief Number of columns in the option tables holding bindable values.
    static const size_t OPTION_COLUMNS = 11;

public:

    /// @brief Constructor.
    MySqlOptionExchange()
        : type_(0), value_len_(0), formatted_value_len_(0), space_(),
          space_len_(0), persistent_(false), cancelled_(false),
          user_context_(), user_context_len_(0),
          client_classes_(), client_classes_len_(0),
          subnet_id_(SUBNET_ID_UNUSED),
          host_id_(0), option_() {

        static_assert(11 <= OPTION_COLUMNS, "11 <= 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 Optional<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 uint8_t* buf_ptr = 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;

            // cancelled: TINYINT(1) NOT NULL DEFAULT 0
            cancelled_ = opt_desc.cancelled_;
            bind_[6].buffer_type = MYSQL_TYPE_TINY;
            bind_[6].buffer = reinterpret_cast<char*>(&cancelled_);
            bind_[6].is_unsigned = MLM_TRUE;

            // user_context: TEST NULL,
            ConstElementPtr ctx = opt_desc.getContext();
            if (ctx) {
                user_context_ = ctx->str();
                user_context_len_ = user_context_.size();
                bind_[7].buffer_type = MYSQL_TYPE_STRING;
                bind_[7].buffer = const_cast<char*>(user_context_.c_str());
                bind_[7].buffer_length = user_context_len_;
                bind_[7].length = &user_context_len_;
            } else {
                bind_[7].buffer_type = MYSQL_TYPE_NULL;
            }

            // Client classes: TEXT NOT NULL
            client_classes_ = opt_desc.client_classes_.toElement()->str();
            client_classes_len_ = client_classes_.size();
            bind_[8].buffer_type = MYSQL_TYPE_STRING;
            bind_[8].buffer = const_cast<char*>(client_classes_.c_str());
            bind_[8].buffer_length = client_classes_len_;
            bind_[8].length = &client_classes_len_;

            // dhcp4_subnet_id: INT UNSIGNED NULL
            if (!subnet_id.unspecified()) {
                subnet_id_ = subnet_id;
                bind_[9].buffer_type = MYSQL_TYPE_LONG;
                bind_[9].buffer = reinterpret_cast<char*>(subnet_id_);
                bind_[9].is_unsigned = MLM_TRUE;

            } else {
                bind_[9].buffer_type = MYSQL_TYPE_NULL;
            }

            // host_id: INT UNSIGNED NOT NULL
            host_id_ = host_id;
            bind_[10].buffer_type = MYSQL_TYPE_LONG;
            bind_[10].buffer = reinterpret_cast<char*>(&host_id_);
            bind_[10].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.
    unsigned long value_len_;

    /// @brief Formatted option value length.
    unsigned long formatted_value_len_;

    /// @brief Option space name.
    std::string space_;

    /// @brief Option space name length.
    unsigned long space_len_;

    /// @brief Boolean flag indicating if the option is always returned to
    /// a client or only when requested.
    bool persistent_;

    /// @brief Boolean flag indicating if the option must be never returned
    /// to a client.
    bool cancelled_;

    /// @brief User context.
    std::string user_context_;

    /// @brief User context length.
    unsigned long user_context_len_;

    /// @brief Client classes.
    std::string client_classes_;

    /// @brief Client classes length.
    unsigned long client_classes_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];
};

}  // namespace

namespace isc {
namespace dhcp {

/// @brief MySQL Host Context
///
/// This class stores the thread context for the manager pool.
/// The class is needed by all get/update/delete functions which must use one
/// or more exchanges to perform database operations.
/// Each context provides a set of such exchanges for each thread.
/// The context instances are lazy initialized by the requesting thread by using
/// the manager's createContext function and are destroyed when the manager's
/// pool instance is destroyed.
class MySqlHostContext {
public:

    /// @brief Constructor
    ///
    /// @param parameters See MySqlHostMgr constructor.
    /// @param io_service_accessor The IOService accessor function.
    /// @param db_reconnect_callback The connection recovery callback.
    MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
                     IOServiceAccessorPtr io_service_accessor,
                     db::DbCallback db_reconnect_callback);

    /// The exchange objects are used for transfer of data to/from the database.
    /// They are pointed-to objects as the contents may change in "const" calls,
    /// while the rest of this object does not.  (At alternative would be to
    /// declare them as "mutable".)

    /// @brief Pointer to an object representing an exchange which can
    /// be used to retrieve hosts and DHCPv4 options.
    boost::shared_ptr<MySqlHostWithOptionsExchange> host_ipv4_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
    boost::shared_ptr<MySqlHostIPv6Exchange> 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<MySqlHostIPv6Exchange> host_ipv46_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to insert new IPv6 reservation.
    boost::shared_ptr<MySqlIPv6ReservationExchange> 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<MySqlOptionExchange> host_option_exchange_;

    /// @brief MySQL connection
    MySqlConnection conn_;

    /// @brief Indicates if the database is opened in read only mode.
    bool is_readonly_;
};

/// @brief MySQL Host Context Pool
///
/// This class provides a pool of contexts.
/// The manager will use this class to handle available contexts.
/// There is only one ContextPool per manager per back-end, which is created
/// and destroyed by the respective manager factory class.
class MySqlHostContextPool {
public:

    /// @brief The vector of available contexts.
    std::vector<MySqlHostContextPtr> pool_;

    /// @brief The mutex to protect pool access.
    std::mutex mutex_;
};

/// @brief Type of pointers to context pools.
typedef boost::shared_ptr<MySqlHostContextPool> MySqlHostContextPoolPtr;

/// @brief Implementation of the @ref MySqlHostDataSource.
class MySqlHostDataSourceImpl {
public:

    /// @brief Statement Tags
    ///
    /// The contents of the enum are indexes into the list of SQL statements.
    /// It is assumed that the order is such that the indices of statements
    /// reading the database are less than those of statements modifying the
    /// database.
    /// @note: please add new statements doing read only operations before
    /// the WRITE_STMTS_BEGIN position.
    enum StatementIndex {
        GET_HOST_DHCPID,           // Gets hosts by host identifier
        GET_HOST_ADDR,             // Gets hosts by IPv4 address
        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
        GET_HOST_SUBID6_ADDR,      // Gets host by IPv6 SubnetID and IPv6 prefix
        GET_HOST_ADDR6,            // Gets hosts by IPv6 address/prefix
        GET_HOST_SUBID4,           // Gets hosts by IPv4 SubnetID
        GET_HOST_SUBID6,           // Gets hosts by IPv6 SubnetID
        GET_HOST_HOSTNAME,         // Gets hosts by hostname
        GET_HOST_HOSTNAME_SUBID4,  // Gets hosts by hostname and IPv4 SubnetID
        GET_HOST_HOSTNAME_SUBID6,  // Gets hosts by hostname and IPv6 SubnetID
        GET_HOST_SUBID4_PAGE,      // Gets hosts by IPv4 SubnetID beginning by HID
        GET_HOST_SUBID6_PAGE,      // Gets hosts by IPv6 SubnetID beginning by HID
        GET_HOST_PAGE4,            // Gets v4 hosts beginning by HID
        GET_HOST_PAGE6,            // Gets v6 hosts beginning by HID
        INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
        INSERT_HOST_UNIQUE_IP,     // Insert new host to collection with checking for IP duplicates
        INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
        INSERT_V6_RESRV_UNIQUE,    // Insert v6 reservation with checking that it is unique
        INSERT_V4_HOST_OPTION,     // Insert DHCPv4 option
        INSERT_V6_HOST_OPTION,     // Insert DHCPv6 option
        DEL_HOST_ADDR4,            // Delete v4 host (subnet-id, addr4)
        DEL_HOST_ADDR6,            // Delete v6 host (subnet-id, addr6)
        DEL_HOST_SUBID4_ID,        // Delete v4 host (subnet-id, ident.type, identifier)
        DEL_HOST_SUBID6_ID,        // Delete v6 host (subnet-id, ident.type, identifier)
        NUM_STATEMENTS             // Number of statements
    };

    /// @brief Index of first statement performing write to the database.
    ///
    /// This value is used to mark border line between queries and other
    /// statements and statements performing write operation on the database,
    /// such as INSERT, DELETE, UPDATE.
    static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;

    /// @brief Constructor.
    ///
    /// This constructor opens database connection and initializes prepared
    /// statements used in the queries.
    MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);

    /// @brief Destructor.
    ~MySqlHostDataSourceImpl();

    /// @brief Attempts to reconnect the server to the host DB backend manager.
    ///
    /// This is a self-rescheduling function that attempts to reconnect to the
    /// server's host DB backends after connectivity to one or more have been
    /// lost. Upon entry it will attempt to reconnect via
    /// @ref HostDataSourceFactory::add.
    /// If this is successful, DHCP servicing is re-enabled and server returns
    /// to normal operation.
    ///
    /// If reconnection fails and the maximum number of retries has not been
    /// exhausted, it will schedule a call to itself to occur at the
    /// configured retry interval. DHCP service remains disabled.
    ///
    /// If the maximum number of retries has been exhausted an error is logged
    /// and the server shuts down.
    ///
    /// This function is passed to the connection recovery mechanism. It will be
    /// invoked when a connection loss is detected.
    ///
    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
    /// configured reconnect parameters.
    /// @return true if connection has been recovered, false otherwise.
    static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);

    /// @brief Create a new context.
    ///
    /// The database is opened with all the SQL commands pre-compiled.
    ///
    /// @return A new (never null) context.
    ///
    /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
    /// @throw isc::db::DbOperationError An operation on the open database has
    /// failed.
    MySqlHostContextPtr createContext() const;

    /// @brief Returns backend version.
    ///
    /// The method is called by the constructor before opening the database
    /// to verify that the schema version is correct.
    ///
    /// @param timer_name The DB reconnect timer name.
    /// @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 std::string& timer_name = std::string()) const;

    /// @brief Executes statements which inserts a row into one of the tables.
    ///
    /// @param ctx Context
    /// @param stindex Index of a statement being executed.
    /// @param bind Vector of MYSQL_BIND objects to be used when making the
    /// query.
    ///
    /// @throw isc::db::DuplicateEntry Database throws duplicate entry error
    void addStatement(MySqlHostContextPtr& ctx,
                      MySqlHostDataSourceImpl::StatementIndex stindex,
                      std::vector<MYSQL_BIND>& bind);

    /// @brief Executes statements that delete records.
    ///
    /// @param ctx Context
    /// @param stindex Index of a statement being executed.
    /// @param bind Vector of MYSQL_BIND objects to be used when making the
    /// query.
    ///
    /// @return true if any records were deleted, false otherwise
    bool delStatement(MySqlHostContextPtr& ctx,
                      StatementIndex stindex,
                      MYSQL_BIND* bind);

    /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
    ///
    /// @param ctx Context
    /// @param resv IPv6 Reservation to be added
    /// @param id ID of a host owning this reservation
    void addResv(MySqlHostContextPtr& ctx,
                 const IPv6Resrv& resv,
                 const HostID& id);

    /// @brief Inserts a single DHCP option into the database.
    ///
    /// @param ctx Context
    /// @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(MySqlHostContextPtr& ctx,
                   const MySqlHostDataSourceImpl::StatementIndex& stindex,
                   const OptionDescriptor& opt_desc,
                   const std::string& opt_space,
                   const Optional<SubnetID>& subnet_id,
                   const HostID& host_id);

    /// @brief Inserts multiple options into the database.
    ///
    /// @param ctx Context
    /// @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(MySqlHostContextPtr& ctx,
                    const StatementIndex& stindex,
                    const ConstCfgOptionPtr& options_cfg,
                    const uint64_t host_id);

    /// @brief Check Error and Throw Exception
    ///
    /// This method invokes @ref db::MySqlConnection::checkError.
    ///
    /// @param ctx Context
    /// @param status Status code: non-zero implies an error
    /// @param index Index of statement that caused the error
    /// @param what High-level description of the error
    ///
    /// @throw isc::dhcp::DbOperationError An operation on the open database has
    ///        failed.
    void checkError(MySqlHostContextPtr& ctx,
                    const int status,
                    const StatementIndex index,
                    const char* what) const;

    /// @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 ctx Context
    /// @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(MySqlHostContextPtr& ctx,
                           StatementIndex stindex,
                           MYSQL_BIND* bind,
                           boost::shared_ptr<MySqlHostExchange> exchange,
                           ConstHostCollection& result,
                           bool single) const;

    /// @brief Retrieves a host by subnet and client's unique identifier.
    ///
    /// This method is used by both MySqlHostDataSource::get4 and
    /// MySqlHOstDataSource::get6 methods.
    ///
    /// @param ctx Context
    /// @param subnet_id Subnet identifier.
    /// @param identifier_type Identifier type.
    /// @param identifier_begin Pointer to a beginning 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(MySqlHostContextPtr& ctx,
                         const SubnetID& subnet_id,
                         const Host::IdentifierType& identifier_type,
                         const uint8_t* identifier_begin,
                         const size_t identifier_len,
                         StatementIndex stindex,
                         boost::shared_ptr<MySqlHostExchange> exchange) const;

    /// @brief Throws exception if database is read only.
    ///
    /// This method should be called by the methods which write to the
    /// database. If the backend is operating in read-only mode this
    /// method will throw exception.
    ///
    /// @param ctx Context
    ///
    /// @throw DbReadOnly if backend is operating in read only mode.
    void checkReadOnly(MySqlHostContextPtr& ctx) const;

    /// @brief The parameters
    DatabaseConnection::ParameterMap parameters_;

    /// @brief Holds the setting whether the IP reservations must be unique or
    /// may be non-unique.
    bool ip_reservations_unique_;

    /// @brief The pool of contexts
    MySqlHostContextPoolPtr pool_;

    /// @brief Indicates if there is at least one connection that can no longer
    /// be used for normal operations.
    bool unusable_;

    /// @brief Timer name used to register database reconnect timer.
    std::string timer_name_;
};

namespace {

/// @brief Array of tagged statements.
typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS>
TaggedStatementArray;

/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
TaggedStatementArray tagged_statements = { {
    // 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.
    {MySqlHostDataSourceImpl::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, "
                "h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
                "o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
                "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
                "o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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"},

    // 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.
    {MySqlHostDataSourceImpl::GET_HOST_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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "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"},

    // 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.
    {MySqlHostDataSourceImpl::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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "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.
    {MySqlHostDataSourceImpl::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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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.
    {MySqlHostDataSourceImpl::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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "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.
    {MySqlHostDataSourceImpl::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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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"},

    // Retrieves host information, IPv6 reservations and DHCPv6 options
    // associated with a host using IPv6 subnet id and prefix. 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.
    {MySqlHostDataSourceImpl::GET_HOST_SUBID6_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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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.host_id IN "
                "(SELECT host_id FROM ipv6_reservations "
                    "WHERE address = ?) "
            "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // Retrieves host information, IPv6 reservations and DHCPv6 options
    // associated with a host using IPv6 address/prefix. This query
    // may return host information for one or more host reservations. Even
    // if only one host is found, 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.
    {MySqlHostDataSourceImpl::GET_HOST_ADDR6,
     "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, h.user_context, "
     "h.dhcp4_next_server, h.dhcp4_server_hostname, "
     "h.dhcp4_boot_file_name, h.auth_key, "
     "o.option_id, o.code, o.value, o.formatted_value, o.space, "
     "o.persistent, o.cancelled, o.user_context, o.client_classes, "
     "r.reservation_id, r.address, r.prefix_len, r.type, "
     "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
     "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 IN "
     "(SELECT host_id FROM ipv6_reservations "
     "WHERE address = ?) "
     "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // 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. Hosts are retrieved by IPv4 subnet id.
    {MySqlHostDataSourceImpl::GET_HOST_SUBID4,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "FROM hosts AS h "
            "LEFT JOIN dhcp4_options AS o "
                "ON h.host_id = o.host_id "
            "WHERE h.dhcp4_subnet_id = ? "
            "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. Hosts are retrieved
    // by IPv6 subnet id.
    {MySqlHostDataSourceImpl::GET_HOST_SUBID6,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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 = ? "
            "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // 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.
    {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME,
            "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, "
                "h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
                "o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
                "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
                "o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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 h.hostname = ? "
            "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},

    // Retrieves host information and DHCPv4 options using hostname and
    // subnet identifier. Left joining the dhcp4_options table results in
    // multiple rows being returned for the same host.
    {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "FROM hosts AS h "
            "LEFT JOIN dhcp4_options AS o "
                "ON h.host_id = o.host_id "
            "WHERE h.hostname = ? AND h.dhcp4_subnet_id = ? "
            "ORDER BY h.host_id, o.option_id"},

    // Retrieves host information, IPv6 reservations and DHCPv6 options
    // using hostname and subnet identifier. The number of rows returned
    // is a multiplication of number of IPv6 reservations and DHCPv6 options.
    {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "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.hostname = ? AND h.dhcp6_subnet_id = ? "
            "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // 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. Hosts are retrieved by IPv4 subnet id
    // and with a host id greater than the start one.
    // The number of hosts returned is lower or equal to the limit.
    {MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "FROM ( SELECT * FROM hosts AS h "
                    "WHERE h.dhcp4_subnet_id = ? AND h.host_id > ? "
                    "ORDER BY h.host_id "
                    "LIMIT ? ) AS h "
            "LEFT JOIN dhcp4_options AS o "
                "ON h.host_id = o.host_id "
            "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. Hosts are retrieved
    // by IPv6 subnet id and with a host id greater than the start one.
    // The number of hosts returned is lower or equal to the limit.
    {MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "FROM ( SELECT * FROM hosts AS h "
                    "WHERE h.dhcp6_subnet_id = ? AND h.host_id > ? "
                    "ORDER BY h.host_id "
                    "LIMIT ? ) 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 "
            "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // 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. Hosts are retrieved with a host id greater
    // than the start one.
    // The number of hosts returned is lower or equal to the limit.
    {MySqlHostDataSourceImpl::GET_HOST_PAGE4,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes "
            "FROM ( SELECT * FROM hosts AS h "
                    "WHERE h.host_id > ? "
                    "ORDER BY h.host_id "
                    "LIMIT ? ) AS h "
            "LEFT JOIN dhcp4_options AS o "
                "ON h.host_id = o.host_id "
            "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. Hosts are retrieved
    // with a host id greater than the start one.
    // The number of hosts returned is lower or equal to the limit.
    {MySqlHostDataSourceImpl::GET_HOST_PAGE6,
            "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, h.user_context, "
                "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                "h.dhcp4_boot_file_name, h.auth_key, "
                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                "r.reservation_id, r.address, r.prefix_len, r.type, "
                "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
            "FROM ( SELECT * FROM hosts AS h "
                    "WHERE h.host_id > ? "
                    "ORDER BY h.host_id "
                    "LIMIT ? ) 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 "
            "ORDER BY h.host_id, o.option_id, r.reservation_id"},

    // Inserts a host into the 'hosts' table without checking that there is
    // a reservation for the IP address.
    {MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
            "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, "
                "user_context, dhcp4_next_server, "
                "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},

    // Inserts a host into the 'hosts' table with checking that reserved IP
    // address is unique. The innermost query checks if there is at least
    // one host for the given IP/subnet combination. For checking whether
    // hosts exists or not it doesn't matter if we select actual columns,
    // thus SELECT 1 was used as an optimization to avoid selecting data
    // that will be ignored anyway. If the host does not exist the new
    // host is inserted. DUAL is a special MySQL table from which we can
    // select the values to be inserted. If the host with the given IP
    // address already exists the new host won't be inserted. The caller
    // can check the number of affected rows to detect that there was
    // a duplicate host in the database.
    {MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP,
            "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, "
                "user_context, dhcp4_next_server, "
                "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
                "SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM DUAL "
                    "WHERE NOT EXISTS ("
                        "SELECT 1 FROM hosts "
                            "WHERE ipv4_address = ? AND dhcp4_subnet_id = ? "
                        "LIMIT 1"
                    ")"},

    // Inserts a single IPv6 reservation into 'reservations' table without
    // checking that the inserted reservation is unique.
    {MySqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE,
            "INSERT INTO ipv6_reservations(address, prefix_len, type, "
                "dhcp6_iaid, excluded_prefix, excluded_prefix_len, host_id) "
            "VALUES (?, ?, ?, ?, ?, ?, ?)"},

    // Inserts a single IPv6 reservation into 'reservations' table with
    // checking that the inserted reservation is unique.
    {MySqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE,
            "INSERT INTO ipv6_reservations(address, prefix_len, type, "
                "dhcp6_iaid, excluded_prefix, excluded_prefix_len, host_id) "
                "SELECT ?, ?, ?, ?, ?, ?, ? FROM DUAL "
                    "WHERE NOT EXISTS ("
                        "SELECT 1 FROM ipv6_reservations "
                            "WHERE address = ? AND prefix_len = ? "
                        "LIMIT 1"
                    ")"},

    // Inserts a single DHCPv4 option into 'dhcp4_options' table.
    // Using fixed scope_id = 3, which associates an option with host.
    {MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
            "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
                "persistent, cancelled, user_context, client_classes, "
                "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.
    {MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
            "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
                "persistent, cancelled, user_context, client_classes, "
                "dhcp6_subnet_id, host_id, scope_id) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},

    // Delete IPv4 reservations by subnet id and reserved address.
    {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
            "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},

    // Delete IPv6 reservations by subnet id and reserved address/prefix.
    {MySqlHostDataSourceImpl::DEL_HOST_ADDR6,
            "DELETE h FROM hosts AS h "
            "INNER JOIN ipv6_reservations AS r "
                "ON h.host_id = r.host_id "
            "WHERE h.dhcp6_subnet_id = ? AND r.address = ?"},

    // Delete a single IPv4 reservation by subnet id and identifier.
    {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
            "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
            "AND dhcp_identifier = ?"},

    // Delete a single IPv6 reservation by subnet id and identifier.
    {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
            "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
            "AND dhcp_identifier = ?"}
    }
};

}  // namespace

// MySqlHostContext Constructor

MySqlHostContext::MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
                                   IOServiceAccessorPtr io_service_accessor,
                                   db::DbCallback db_reconnect_callback)
    : conn_(parameters, io_service_accessor, db_reconnect_callback),
      is_readonly_(true) {
}

// MySqlHostContextAlloc Constructor and Destructor

MySqlHostDataSource::MySqlHostContextAlloc::MySqlHostContextAlloc(
    MySqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {

    if (MultiThreadingMgr::instance().getMode()) {
        // multi-threaded
        {
            // we need to protect the whole pool_ operation, hence extra scope {}
            lock_guard<mutex> lock(mgr_.pool_->mutex_);
            if (!mgr_.pool_->pool_.empty()) {
                ctx_ = mgr_.pool_->pool_.back();
                mgr_.pool_->pool_.pop_back();
            }
        }
        if (!ctx_) {
            ctx_ = mgr_.createContext();
        }
    } else {
        // single-threaded
        if (mgr_.pool_->pool_.empty()) {
            isc_throw(Unexpected, "No available MySQL host context?!");
        }
        ctx_ = mgr_.pool_->pool_.back();
    }
}

MySqlHostDataSource::MySqlHostContextAlloc::~MySqlHostContextAlloc() {
    if (MultiThreadingMgr::instance().getMode()) {
        // multi-threaded
        lock_guard<mutex> lock(mgr_.pool_->mutex_);
        mgr_.pool_->pool_.push_back(ctx_);
        if (ctx_->conn_.isUnusable()) {
            mgr_.unusable_ = true;
        }
    } else if (ctx_->conn_.isUnusable()) {
        mgr_.unusable_ = true;
    }
}

MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
    : parameters_(parameters), ip_reservations_unique_(true), unusable_(false) {

    // Create unique timer name per instance.
    timer_name_ = "MySqlHostMgr[";
    timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
    timer_name_ += "]DbReconnectTimer";

    MySqlConnection::ensureSchemaVersion(parameters,
                                         DbCallback(&MySqlHostDataSourceImpl::dbReconnect),
                                         timer_name_);

    // Create an initial context.
    pool_.reset(new MySqlHostContextPool());
    pool_->pool_.push_back(createContext());
}

// Create context.

MySqlHostContextPtr
MySqlHostDataSourceImpl::createContext() const {
    MySqlHostContextPtr ctx(new MySqlHostContext(parameters_,
        IOServiceAccessorPtr(new IOServiceAccessor(&DatabaseConnection::getIOService)),
        &MySqlHostDataSourceImpl::dbReconnect));

    // Open the database.
    ctx->conn_.openDatabase();

    // Check if we have TLS when we required it.
    if (ctx->conn_.getTls()) {
        std::string cipher = ctx->conn_.getTlsCipher();
        if (cipher.empty()) {
            LOG_ERROR(mysql_hb_logger, MYSQL_HB_NO_TLS);
        } else {
            LOG_DEBUG(mysql_hb_logger, MYSQL_HB_DBG_TRACE,
                      MYSQL_HB_TLS_CIPHER)
                .arg(cipher);
        }
    }

    // Prepare query statements. Those are will be only used to retrieve
    // information from the database, so they can be used even if the
    // database is read only for the current user.
    ctx->conn_.prepareStatements(tagged_statements.begin(),
                                 tagged_statements.begin() + WRITE_STMTS_BEGIN);

    // Check if the backend is explicitly configured to operate with
    // read only access to the database.
    ctx->is_readonly_ = ctx->conn_.configuredReadOnly();

    // If we are using read-write mode for the database we also prepare
    // statements for INSERTS etc.
    if (!ctx->is_readonly_) {
        // Prepare statements for writing to the database, e.g. INSERT.
        ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
                                     tagged_statements.end());
    } else {
        LOG_INFO(mysql_hb_logger, MYSQL_HB_DB_READONLY);
    }

    // Create the exchange objects for use in exchanging data between the
    // program and the database.
    ctx->host_ipv4_exchange_.reset(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY));
    ctx->host_ipv6_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY));
    ctx->host_ipv46_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
    ctx->host_ipv6_reservation_exchange_.reset(new MySqlIPv6ReservationExchange());
    ctx->host_option_exchange_.reset(new MySqlOptionExchange());

    // Create ReconnectCtl for this connection.
    ctx->conn_.makeReconnectCtl(timer_name_, NetworkState::DB_CONNECTION + 11);

    return (ctx);
}

MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
}

bool
MySqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
    MultiThreadingCriticalSection cs;

    // Invoke application layer connection lost callback.
    if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
        return (false);
    }

    bool reopened = false;

    const std::string timer_name = db_reconnect_ctl->timerName();
    bool check = db_reconnect_ctl->checkRetries();

    // At least one connection was lost.
    try {
        CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
        std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
        for (std::string& hds : host_db_access_list) {
            auto parameters = DatabaseConnection::parse(hds);
            if (HostMgr::delBackend("mysql", hds, true)) {
                HostMgr::addBackend(hds);
            }
        }
        reopened = true;
    } catch (const std::exception& ex) {
        LOG_ERROR(mysql_hb_logger, MYSQL_HB_DB_RECONNECT_ATTEMPT_FAILED)
                .arg(ex.what());
    }

    if (reopened) {
        // Cancel the timer.
        if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
            TimerMgr::instance()->unregisterTimer(timer_name);
        }

        // Invoke application layer connection recovered callback.
        if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
            return (false);
        }
    } else {
        if (!check) {
            // We're out of retries, log it and initiate shutdown.
            LOG_ERROR(mysql_hb_logger, MYSQL_HB_DB_RECONNECT_FAILED)
                    .arg(db_reconnect_ctl->maxRetries());

            // Cancel the timer.
            if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
                TimerMgr::instance()->unregisterTimer(timer_name);
            }

            // Invoke application layer connection failed callback.
            DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
            return (false);
        }

        LOG_INFO(mysql_hb_logger, MYSQL_HB_DB_RECONNECT_ATTEMPT_SCHEDULE)
                .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
                .arg(db_reconnect_ctl->maxRetries())
                .arg(db_reconnect_ctl->retryInterval());

        // Start the timer.
        if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
            TimerMgr::instance()->registerTimer(timer_name,
                std::bind(&MySqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
                          db_reconnect_ctl->retryInterval(),
                          asiolink::IntervalTimer::ONE_SHOT);
        }
        TimerMgr::instance()->setup(timer_name);
    }

    return (true);
}

std::pair<uint32_t, uint32_t>
MySqlHostDataSourceImpl::getVersion(const std::string& timer_name) const {
    LOG_DEBUG(mysql_hb_logger, MYSQL_HB_DBG_TRACE_DETAIL, MYSQL_HB_DB_GET_VERSION);

    IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService));
    DbCallback cb(&MySqlHostDataSourceImpl::dbReconnect);

    return (MySqlConnection::getVersion(parameters_, ac, cb, timer_name, NetworkState::DB_CONNECTION + 11));
}

void
MySqlHostDataSourceImpl::addStatement(MySqlHostContextPtr& ctx,
                                      StatementIndex stindex,
                                      std::vector<MYSQL_BIND>& bind) {
    // Bind the parameters to the statement
    int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &bind[0]);
    checkError(ctx, status, stindex, "unable to bind parameters");

    // Execute the statement
    status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));

    if (status != 0) {
        // Failure: check for the special case of duplicate entry.
        if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
            isc_throw(DuplicateEntry, "Database duplicate entry error");
        }
        checkError(ctx, status, stindex, "unable to execute");
    }

    // If the number of rows inserted is 0 it means that the query detected
    // an attempt to insert duplicated data for which there is no unique
    // index in the database. Unique indexes are not created in the database
    // when it may be sometimes allowed to insert duplicated records per
    // server's configuration.
    my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.getStatement(stindex));
    if (numrows == 0) {
        isc_throw(DuplicateEntry, "Database duplicate entry error");
    }
}

bool
MySqlHostDataSourceImpl::delStatement(MySqlHostContextPtr& ctx,
                                      StatementIndex stindex,
                                      MYSQL_BIND* bind) {
    // Bind the parameters to the statement
    int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &bind[0]);
    checkError(ctx, status, stindex, "unable to bind parameters");

    // Execute the statement
    status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));

    if (status != 0) {
        checkError(ctx, status, stindex, "unable to execute");
    }

    // Let's check how many hosts were deleted.
    my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.getStatement(stindex));

    return (numrows != 0);
}

void
MySqlHostDataSourceImpl::addResv(MySqlHostContextPtr& ctx,
                                 const IPv6Resrv& resv,
                                 const HostID& id) {
    std::vector<MYSQL_BIND> bind = ctx->host_ipv6_reservation_exchange_->
        createBindForSend(resv, id, ip_reservations_unique_);

    addStatement(ctx, ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE, bind);
}

void
MySqlHostDataSourceImpl::addOption(MySqlHostContextPtr& ctx,
                                   const StatementIndex& stindex,
                                   const OptionDescriptor& opt_desc,
                                   const std::string& opt_space,
                                   const Optional<SubnetID>& subnet_id,
                                   const HostID& id) {
    std::vector<MYSQL_BIND> bind = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, subnet_id, id);

    addStatement(ctx, stindex, bind);
}

void
MySqlHostDataSourceImpl::addOptions(MySqlHostContextPtr& ctx,
                                    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 (auto const& space : option_spaces) {
        OptionContainerPtr options = options_cfg->getAllCombined(space);
        if (options && !options->empty()) {
            for (auto const& opt : *options) {
                addOption(ctx, stindex, opt, space, Optional<SubnetID>(), host_id);
            }
        }
    }
}

void
MySqlHostDataSourceImpl::checkError(MySqlHostContextPtr& ctx,
                                    const int status,
                                    const StatementIndex index,
                                    const char* what) const {
    ctx->conn_.checkError(status, index, what);
}

void
MySqlHostDataSourceImpl::getHostCollection(MySqlHostContextPtr& ctx,
                                           StatementIndex stindex,
                                           MYSQL_BIND* bind,
                                           boost::shared_ptr<MySqlHostExchange> exchange,
                                           ConstHostCollection& result,
                                           bool single) const {

    // Bind the selection parameters to the statement
    int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), bind);
    checkError(ctx, status, stindex, "unable to bind WHERE clause parameter");

    // Set up the MYSQL_BIND array for the data being returned and bind it to
    // the statement.
    std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
    status = mysql_stmt_bind_result(ctx->conn_.getStatement(stindex), &outbind[0]);
    checkError(ctx, status, stindex, "unable to bind SELECT clause parameters");

    // Execute the statement
    status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
    checkError(ctx, status, stindex, "unable to execute");

    // Ensure that all the lease information is retrieved in one go to avoid
    // overhead of going back and forth between client and server.
    status = mysql_stmt_store_result(ctx->conn_.getStatement(stindex));
    checkError(ctx, status, stindex, "unable to set up for storing all results");

    // Set up the fetch "release" object to release resources associated
    // with the call to mysql_stmt_fetch when this method exits, then
    // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
    // successful data fetch.
    MySqlFreeResult fetch_release(ctx->conn_.getStatement(stindex));
    while ((status = mysql_stmt_fetch(ctx->conn_.getStatement(stindex))) ==
           MLM_MYSQL_FETCH_SUCCESS) {
        try {
            exchange->processFetchedData(result);

        } catch (const isc::BadValue& ex) {
            // Rethrow the exception with a bit more data.
            isc_throw(BadValue, ex.what() << ". Statement is <" <<
                      ctx->conn_.text_statements_[stindex] << ">");
        }

        if (single && (result.size() > 1)) {
            isc_throw(MultipleRecords, "multiple records were found in the "
                      "database where only one was expected for query "
                      << ctx->conn_.text_statements_[stindex]);
        }
    }

    // How did the fetch end?
    // If mysql_stmt_fetch return value is equal to 1 an error occurred.
    if (status == MLM_MYSQL_FETCH_FAILURE) {
        // Error - unable to fetch results
        checkError(ctx, status, stindex, "unable to fetch results");

    } else if (status == MYSQL_DATA_TRUNCATED) {
        // Data truncated - throw an exception indicating what was at fault
        isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex]
                  << " returned truncated data: columns affected are "
                  << exchange->getErrorColumns());
    }
}

ConstHostPtr
MySqlHostDataSourceImpl::getHost(MySqlHostContextPtr& ctx,
                                 const SubnetID& subnet_id,
                                 const Host::IdentifierType& identifier_type,
                                 const uint8_t* identifier_begin,
                                 const size_t identifier_len,
                                 StatementIndex stindex,
                                 boost::shared_ptr<MySqlHostExchange> exchange) const {

    // Set up the WHERE clause value
    MYSQL_BIND inbind[3];
    memset(inbind, 0, sizeof(inbind));

    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
    inbind[0].is_unsigned = MLM_TRUE;

    // Identifier value.
    std::vector<char> identifier_vec(identifier_begin,
                                     identifier_begin + identifier_len);
    unsigned long length = identifier_vec.size();
    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
    inbind[2].buffer = &identifier_vec[0];
    inbind[2].buffer_length = length;
    inbind[2].length = &length;

    // Identifier type.
    char identifier_type_copy = static_cast<char>(identifier_type);
    inbind[1].buffer_type = MYSQL_TYPE_TINY;
    inbind[1].buffer = &identifier_type_copy;
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection collection;
    getHostCollection(ctx, stindex, inbind, exchange, collection, true);

    // Return single record if present, else clear the host.
    ConstHostPtr result;
    if (!collection.empty()) {
        result = *collection.begin();
    }

    return (result);
}

void
MySqlHostDataSourceImpl::checkReadOnly(MySqlHostContextPtr& ctx) const {
    if (ctx->is_readonly_) {
        isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
                  " operate in read only mode");
    }
}

MySqlHostDataSource::MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
    : impl_(new MySqlHostDataSourceImpl(parameters)) {
}

MySqlHostDataSource::~MySqlHostDataSource() {
}

DatabaseConnection::ParameterMap
MySqlHostDataSource::getParameters() const {
    return (impl_->parameters_);
}

void
MySqlHostDataSource::add(const HostPtr& host) {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);

    // Initiate MySQL 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 MySqlTransaction class.
    MySqlTransaction transaction(ctx->conn_);

    // If we're configured to check that an IP reservation within a given subnet
    // is unique, the IP reservation exists and the subnet is actually set
    // we will be using a special query that checks for uniqueness. Otherwise,
    // we will use a regular insert statement.
    bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
        && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;

    // Create the MYSQL_BIND array for the host
    std::vector<MYSQL_BIND> bind = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);

    // ... and insert the host.
    impl_->addStatement(ctx, unique_ip ? MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
                        MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP, bind);

    // Gets the last inserted hosts id
    uint64_t host_id = mysql_insert_id(ctx->conn_.mysql_);

    // Insert DHCPv4 options.
    ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
    if (cfg_option4) {
        impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
                          cfg_option4, host_id);
    }

    // Insert DHCPv6 options.
    ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
    if (cfg_option6) {
        impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
                          cfg_option6, host_id);
    }

    // Insert IPv6 reservations.
    IPv6ResrvRange v6resv = host->getIPv6Reservations();
    if (std::distance(v6resv.first, v6resv.second) > 0) {
        BOOST_FOREACH(auto const& resv, v6resv) {
            impl_->addResv(ctx, resv.second, host_id);
        }
    }

    // Everything went fine, so explicitly commit the transaction.
    transaction.commit();
}

bool
MySqlHostDataSource::del(const SubnetID& subnet_id,
                         const asiolink::IOAddress& addr) {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];

    uint32_t subnet = subnet_id;
    memset(inbind, 0, sizeof(inbind));
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    // v4
    if (addr.isV4()) {
        uint32_t addr4 = addr.toUint32();
        inbind[1].buffer_type = MYSQL_TYPE_LONG;
        inbind[1].buffer = reinterpret_cast<char*>(&addr4);
        inbind[1].is_unsigned = MLM_TRUE;

        return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
    }

    // v6
    std::vector<uint8_t>addr6 = addr.toBytes();
    if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
        isc_throw(DbOperationError, "del() - address is not "
                  << isc::asiolink::V6ADDRESS_LEN << " bytes long");
    }

    unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].buffer_type = MYSQL_TYPE_BLOB;
    inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
    inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].length = &addr6_length;

    return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR6, inbind));
}

bool
MySqlHostDataSource::del4(const SubnetID& subnet_id,
                          const Host::IdentifierType& identifier_type,
                          const uint8_t* identifier_begin,
                          const size_t identifier_len) {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);

    // Set up the WHERE clause value
    MYSQL_BIND inbind[3];

    // subnet-id
    memset(inbind, 0, sizeof(inbind));
    uint32_t subnet = static_cast<uint32_t>(subnet_id);
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    // identifier type
    char identifier_type_copy = static_cast<char>(identifier_type);
    inbind[1].buffer_type = MYSQL_TYPE_TINY;
    inbind[1].buffer = &identifier_type_copy;
    inbind[1].is_unsigned = MLM_TRUE;

    // identifier value
    std::vector<char> identifier_vec(identifier_begin,
                                     identifier_begin + identifier_len);
    unsigned long length = identifier_vec.size();
    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
    inbind[2].buffer = &identifier_vec[0];
    inbind[2].buffer_length = length;
    inbind[2].length = &length;

    ConstHostCollection collection;
    return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
}

bool
MySqlHostDataSource::del6(const SubnetID& subnet_id,
                          const Host::IdentifierType& identifier_type,
                          const uint8_t* identifier_begin,
                          const size_t identifier_len) {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);

    // Set up the WHERE clause value
    MYSQL_BIND inbind[3];

    // subnet-id
    memset(inbind, 0, sizeof(inbind));
    uint32_t subnet = static_cast<uint32_t>(subnet_id);
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    // identifier type
    char identifier_type_copy = static_cast<char>(identifier_type);
    inbind[1].buffer_type = MYSQL_TYPE_TINY;
    inbind[1].buffer = &identifier_type_copy;
    inbind[1].is_unsigned = MLM_TRUE;

    // identifier value
    std::vector<char> identifier_vec(identifier_begin,
                                     identifier_begin + identifier_len);
    unsigned long length = identifier_vec.size();
    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
    inbind[2].buffer = &identifier_vec[0];
    inbind[2].buffer_length = length;
    inbind[2].length = &length;

    ConstHostCollection collection;
    return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
}

ConstHostCollection
MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
                            const uint8_t* identifier_begin,
                            const size_t identifier_len) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    // Identifier type.
    char identifier_type_copy = static_cast<char>(identifier_type);
    inbind[1].buffer = &identifier_type_copy;
    inbind[1].buffer_type = MYSQL_TYPE_TINY;
    inbind[1].is_unsigned = MLM_TRUE;

    // Identifier value.
    std::vector<char> identifier_vec(identifier_begin,
                                     identifier_begin + identifier_len);
    unsigned long int length = identifier_vec.size();
    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
    inbind[0].buffer = &identifier_vec[0];
    inbind[0].buffer_length = length;
    inbind[0].length = &length;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_DHCPID, inbind,
                             ctx->host_ipv46_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[1];
    memset(inbind, 0, sizeof(inbind));
    uint32_t subnet = subnet_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4, inbind,
                             ctx->host_ipv4_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[1];
    memset(inbind, 0, sizeof(inbind));
    uint32_t subnet = subnet_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6, inbind,
                             ctx->host_ipv6_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[1];
    memset(inbind, 0, sizeof(inbind));

    // Hostname
    char hostname_[HOSTNAME_MAX_LEN];
    strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
    unsigned long length = hostname.length();
    inbind[0].buffer_type = MYSQL_TYPE_STRING;
    inbind[0].buffer = reinterpret_cast<char*>(hostname_);
    inbind[0].buffer_length = length;
    inbind[0].length = &length;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME, inbind,
                             ctx->host_ipv46_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAllbyHostname4(const std::string& hostname,
                                       const SubnetID& subnet_id) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    // Hostname
    char hostname_[HOSTNAME_MAX_LEN];
    strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
    unsigned long length = hostname.length();
    inbind[0].buffer_type = MYSQL_TYPE_STRING;
    inbind[0].buffer = reinterpret_cast<char*>(hostname_);
    inbind[0].buffer_length = length;
    inbind[0].length = &length;

    // Subnet ID
    uint32_t subnet = subnet_id;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&subnet);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4, inbind,
                             ctx->host_ipv4_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAllbyHostname6(const std::string& hostname,
                                       const SubnetID& subnet_id) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    // Hostname
    char hostname_[HOSTNAME_MAX_LEN];
    strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
    unsigned long length = hostname.length();
    inbind[0].buffer_type = MYSQL_TYPE_STRING;
    inbind[0].buffer = reinterpret_cast<char*>(hostname_);
    inbind[0].buffer_length = length;
    inbind[0].length = &length;

    // Subnet ID
    uint32_t subnet = subnet_id;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&subnet);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6, inbind,
                             ctx->host_ipv6_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getPage4(const SubnetID& subnet_id,
                              size_t& /*source_index*/,
                              uint64_t lower_host_id,
                              const HostPageSize& page_size) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[3];
    memset(inbind, 0, sizeof(inbind));

    // Bind subnet id
    uint32_t subnet = subnet_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    // Bind lower host id
    uint32_t host_id = lower_host_id;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&host_id);
    inbind[1].is_unsigned = MLM_TRUE;

    // Bind page size value
    uint32_t page_size_data = page_size.page_size_;
    inbind[2].buffer_type = MYSQL_TYPE_LONG;
    inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
    inbind[2].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE, inbind,
                             ctx->host_ipv4_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getPage6(const SubnetID& subnet_id,
                              size_t& /*source_index*/,
                              uint64_t lower_host_id,
                              const HostPageSize& page_size) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[3];
    memset(inbind, 0, sizeof(inbind));

    // Bind subnet id
    uint32_t subnet = subnet_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    // Bind lower host id
    uint32_t host_id = lower_host_id;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&host_id);
    inbind[1].is_unsigned = MLM_TRUE;

    // Bind page size value
    uint32_t page_size_data = page_size.page_size_;
    inbind[2].buffer_type = MYSQL_TYPE_LONG;
    inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
    inbind[2].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE, inbind,
                             ctx->host_ipv6_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getPage4(size_t& /*source_index*/,
                              uint64_t lower_host_id,
                              const HostPageSize& page_size) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    // Bind lower host id
    uint32_t host_id = lower_host_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&host_id);
    inbind[0].is_unsigned = MLM_TRUE;

    // Bind page size value
    uint32_t page_size_data = page_size.page_size_;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE4, inbind,
                             ctx->host_ipv4_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getPage6(size_t& /*source_index*/,
                              uint64_t lower_host_id,
                              const HostPageSize& page_size) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    // Bind lower host id
    uint32_t host_id = lower_host_id;
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&host_id);
    inbind[0].is_unsigned = MLM_TRUE;

    // Bind page size value
    uint32_t page_size_data = page_size.page_size_;
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE6, inbind,
                             ctx->host_ipv6_exchange_, result, false);

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[1];
    memset(inbind, 0, sizeof(inbind));

    uint32_t addr4 = address.toUint32();
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&addr4);
    inbind[0].is_unsigned = MLM_TRUE;

    ConstHostCollection result;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind,
                             ctx->host_ipv4_exchange_, result, false);

    return (result);
}

ConstHostPtr
MySqlHostDataSource::get4(const SubnetID& subnet_id,
                          const Host::IdentifierType& identifier_type,
                          const uint8_t* identifier_begin,
                          const size_t identifier_len) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
                           MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
                           ctx->host_ipv4_exchange_));
}

ConstHostPtr
MySqlHostDataSource::get4(const SubnetID& subnet_id,
                          const asiolink::IOAddress& address) const {
    if (!address.isV4()) {
        isc_throw(BadValue, "MySqlHostDataSource::get4(id, address): "
                  "wrong address type, address supplied is an IPv6 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    uint32_t subnet = subnet_id;
    memset(inbind, 0, sizeof(inbind));
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    uint32_t addr4 = address.toUint32();
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&addr4);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection collection;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
                             ctx->host_ipv4_exchange_, collection, true);

    // Return single record if present, else clear the host.
    ConstHostPtr result;
    if (!collection.empty()) {
        result = *collection.begin();
    }

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAll4(const SubnetID& subnet_id,
                             const asiolink::IOAddress& address) const {
    if (!address.isV4()) {
        isc_throw(BadValue, "MySqlHostDataSource::getAll4(id, address): "
                  "wrong address type, address supplied is an IPv6 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    uint32_t subnet = subnet_id;
    memset(inbind, 0, sizeof(inbind));
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
    inbind[0].is_unsigned = MLM_TRUE;

    uint32_t addr4 = address.toUint32();
    inbind[1].buffer_type = MYSQL_TYPE_LONG;
    inbind[1].buffer = reinterpret_cast<char*>(&addr4);
    inbind[1].is_unsigned = MLM_TRUE;

    ConstHostCollection collection;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
                             ctx->host_ipv4_exchange_, collection, false);
    return (collection);
}

ConstHostPtr
MySqlHostDataSource::get6(const SubnetID& subnet_id,
                          const Host::IdentifierType& identifier_type,
                          const uint8_t* identifier_begin,
                          const size_t identifier_len) const {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
                           MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
                           ctx->host_ipv6_exchange_));
}

ConstHostPtr
MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
                          const uint8_t prefix_len) const {
    if (!prefix.isV6()) {
        isc_throw(BadValue, "MySqlHostDataSource::get6(prefix, prefix_len): "
                  "wrong address type, address supplied is an IPv4 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    std::vector<uint8_t>addr6 = prefix.toBytes();
    if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
        isc_throw(DbOperationError, "get6() - prefix is not "
                  << isc::asiolink::V6ADDRESS_LEN << " bytes long");
    }

    unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
    inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
    inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[0].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(ctx, MySqlHostDataSourceImpl::GET_HOST_PREFIX, inbind,
                             ctx->host_ipv6_exchange_, collection, true);

    // Return single record if present, else clear the host.
    ConstHostPtr result;
    if (!collection.empty()) {
        result = *collection.begin();
    }

    return (result);
}

ConstHostPtr
MySqlHostDataSource::get6(const SubnetID& subnet_id,
                          const asiolink::IOAddress& address) const {
    if (!address.isV6()) {
        isc_throw(BadValue, "MySqlHostDataSource::get6(id, address): "
                  "wrong address type, address supplied is an IPv4 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
    inbind[0].is_unsigned = MLM_TRUE;

    std::vector<uint8_t>addr6 = address.toBytes();
    if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
        isc_throw(DbOperationError, "get6() - address is not "
                  << isc::asiolink::V6ADDRESS_LEN << " bytes long");
    }

    unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].buffer_type = MYSQL_TYPE_BLOB;
    inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
    inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].length = &addr6_length;

    ConstHostCollection collection;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
                             ctx->host_ipv6_exchange_, collection, true);

    // Return single record if present, else clear the host.
    ConstHostPtr result;
    if (!collection.empty()) {
        result = *collection.begin();
    }

    return (result);
}

ConstHostCollection
MySqlHostDataSource::getAll6(const SubnetID& subnet_id,
                             const asiolink::IOAddress& address) const {
    if (!address.isV6()) {
        isc_throw(BadValue, "MySqlHostDataSource::getAll6(id, address): "
                  "wrong address type, address supplied is an IPv4 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[2];
    memset(inbind, 0, sizeof(inbind));

    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
    inbind[0].buffer_type = MYSQL_TYPE_LONG;
    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
    inbind[0].is_unsigned = MLM_TRUE;

    std::vector<uint8_t>addr6 = address.toBytes();
    if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
        isc_throw(DbOperationError, "getAll6() - address is not "
                  << isc::asiolink::V6ADDRESS_LEN << " bytes long");
    }

    unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].buffer_type = MYSQL_TYPE_BLOB;
    inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
    inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[1].length = &addr6_length;

    ConstHostCollection collection;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
                             ctx->host_ipv6_exchange_, collection, false);
    return (collection);
}

ConstHostCollection
MySqlHostDataSource::getAll6(const IOAddress& address) const {
    if (!address.isV6()) {
        isc_throw(BadValue, "MySqlHostDataSource::getAll6(address): "
                            "wrong address type, address supplied is an IPv4 address");
    }

    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // Set up the WHERE clause value
    MYSQL_BIND inbind[1];
    memset(inbind, 0, sizeof(inbind));

    std::vector<uint8_t>addr6 = address.toBytes();
    if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
        isc_throw(DbOperationError, "getAll6() - address is not "
                                        << isc::asiolink::V6ADDRESS_LEN << " bytes long");
    }

    unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
    inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
    inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
    inbind[0].length = &addr6_length;

    ConstHostCollection collection;
    impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR6, inbind,
                             ctx->host_ipv6_exchange_, collection, false);
    return (collection);
}

void
MySqlHostDataSource::update(HostPtr const& host) {
    // Get a context.
    MySqlHostContextAlloc const context(*impl_);
    MySqlHostContextPtr ctx(context.ctx_);

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);

    // Initiate MySQL transaction as we will have to make multiple queries
    // to update host information into multiple tables. If that fails on
    // any stage, the transaction will be rolled back by the destructor of
    // the MySqlTransaction class.
    MySqlTransaction transaction(ctx->conn_);

    // As much as having dedicated prepared statements for updating tables would be consistent with
    // the implementation of other commands, it's difficult if not impossible to cover all cases for
    // updating the host to exactly as is described in the command, which may involve inserts and
    // deletes alongside updates. So let's delete and add. The delete cascades into all tables. The
    // add explicitly adds into all tables.
    BaseHostDataSource::update(host);

    // Everything went fine, so explicitly commit the transaction.
    transaction.commit();
}

// Miscellaneous database methods.

std::string
MySqlHostDataSource::getName() const {
    std::string name = "";
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    try {
        name = ctx->conn_.getParameter("name");
    } catch (...) {
        // Return an empty name
    }
    return (name);
}

std::string
MySqlHostDataSource::getDescription() const {
    return (std::string("Host data source that stores host information"
                        "in MySQL database"));
}

std::pair<uint32_t, uint32_t>
MySqlHostDataSource::getVersion(const std::string& timer_name) const {
    return (impl_->getVersion(timer_name));
}

void
MySqlHostDataSource::commit() {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);
    if (ctx->conn_.isTransactionStarted()) {
        ctx->conn_.commit();
    }
}

void
MySqlHostDataSource::rollback() {
    // Get a context
    MySqlHostContextAlloc get_context(*impl_);
    MySqlHostContextPtr ctx = get_context.ctx_;

    // If operating in read-only mode, throw exception.
    impl_->checkReadOnly(ctx);
    if (ctx->conn_.isTransactionStarted()) {
        ctx->conn_.rollback();
    }
}

bool
MySqlHostDataSource::setIPReservationsUnique(const bool unique) {
    impl_->ip_reservations_unique_ = unique;
    return (true);
}

bool
MySqlHostDataSource::isUnusable() {
    return (impl_->unusable_);
}

HostDataSourcePtr
MySqlHostDataSource::factory(const isc::db::DatabaseConnection::ParameterMap& parameters) {
    LOG_INFO(mysql_hb_logger, MYSQL_HB_DB)
        .arg(isc::db::DatabaseConnection::redactedAccessString(parameters));
    return (HostDataSourcePtr(new MySqlHostDataSource(parameters)));
}

std::string
MySqlHostDataSource::getDBVersion() {
    std::stringstream tmp;
    tmp << "MySQL backend " << MYSQL_SCHEMA_VERSION_MAJOR;
    tmp << "." << MYSQL_SCHEMA_VERSION_MINOR;
    tmp << ", library " << mysql_get_client_info();
    return (tmp.str());
}

}  // namespace dhcp
}  // namespace isc
