mysql_host_data_source.cc 63.5 KB
Newer Older
1
// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6 7 8 9 10 11 12

#include <config.h>

#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_host_data_source.h>
#include <dhcpsrv/db_exceptions.h>

13
#include <boost/pointer_cast.hpp>
14
#include <boost/static_assert.hpp>
15 16

#include <mysql.h>
17 18
#include <mysqld_error.h>

19
#include <stdint.h>
20 21 22 23
#include <string>

using namespace isc;
using namespace isc::dhcp;
24
using namespace isc::asiolink;
25 26 27
using namespace std;

namespace {
28 29

/// @brief Maximum size of an IPv6 address represented as a text string.
30
///
31 32 33 34
/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;

35 36
/// @brief Maximum length of classes stored in a dhcp4/6_client_classes
/// columns.
37 38 39 40 41 42 43 44
const size_t CLIENT_CLASSES_MAX_LEN = 255;

/// @brief Maximum length of the hostname stored in DNS.
///
/// This length is restricted by the length of the domain-name carried
/// in the Client FQDN %Option (see RFC4702 and RFC4704).
const size_t HOSTNAME_MAX_LEN = 255;

45 46 47 48 49
/// @brief Numeric value representing last supported identifier.
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
50

51 52
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
53
TaggedStatement tagged_statements[] = {
54
    // Inserts a host into the 'hosts' table.
55
    {MySqlHostDataSource::INSERT_HOST,
56
         "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
57 58
            "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
            "dhcp4_client_classes, dhcp6_client_classes) "
59
         "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
60 61

    // Inserts a single IPv6 reservation into 'reservations' table.
62
    {MySqlHostDataSource::INSERT_V6_RESRV,
63
         "INSERT INTO ipv6_reservations(address, prefix_len, type, "
64
            "dhcp6_iaid, host_id) "
65
         "VALUES (?,?,?,?,?)"},
66 67 68 69 70 71

    // Retrieves host information along with IPv6 reservations associated
    // with this host. If the host exists in multiple subnets, all hosts
    // having a specified identifier will be returned from those subnets.
    // Because LEFT JOIN clause is used, the number of rows returned for
    // a single host depends on the number of reservations.
72
    {MySqlHostDataSource::GET_HOST_DHCPID,
73 74 75 76 77 78 79
            "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, "
                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
            "FROM hosts AS h "
            "LEFT JOIN ipv6_reservations AS r "
                "ON h.host_id = r.host_id "
80
            "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ?"},
81 82 83 84

    // Retrieves host information by IPv4 address. This should typically
    // return a single host, but if we ever allow for defining subnets
    // with overlapping address pools, multiple hosts may be returned.
85 86 87 88 89 90
    {MySqlHostDataSource::GET_HOST_ADDR,
            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
                "dhcp4_client_classes, dhcp6_client_classes "
            "FROM hosts "
            "WHERE ipv4_address = ?"},
91 92 93

    // Retrieves host information by subnet identifier and unique
    // identifier of a client. This is expected to return a single host.
94 95 96 97 98 99 100
    {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID,
            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
                "dhcp4_client_classes, dhcp6_client_classes "
            "FROM hosts "
            "WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type = ? "
            "   AND dhcp_identifier = ?"},
101 102 103 104 105 106 107

    // Retrieves host information by subnet identifier and unique
    // identifier of a client. This query should return information
    // for a single host but multiple rows are returned due to
    // use of LEFT JOIN clause. The number of rows returned for a single
    // host dpeneds on the number of IPv6 reservations existing for
    // this client.
108
    {MySqlHostDataSource::GET_HOST_SUBID6_DHCPID,
109 110 111 112 113 114 115 116
            "SELECT DISTINCT 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, "
                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
            "FROM hosts AS h "
            "LEFT JOIN ipv6_reservations AS r "
                "ON h.host_id = r.host_id "
117
            "WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type = ? "
118 119
                "AND dhcp_identifier = ? "
            "ORDER BY h.host_id, r.prefix_len, r.address"},
120 121 122 123

    // Retrieves host information using subnet identifier and the
    // IPv4 address reservation. This should return inforamation for
    // a single host.
124 125 126 127 128 129
    {MySqlHostDataSource::GET_HOST_SUBID_ADDR,
            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
                "dhcp4_client_classes, dhcp6_client_classes "
            "FROM hosts "
            "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
130 131 132 133 134 135 136

    // Retrieves host information using IPv6 prefix and prefix length
    // or IPv6 address. This query returns host information for a
    // single host. However, multiple rows are returned by this
    // query due to use of LEFT JOIN clause with 'ipv6_reservations'
    // table. The number of rows returned depends on the number of
    // reservations for a particular host.
137
    {MySqlHostDataSource::GET_HOST_PREFIX,
138 139 140 141 142 143 144 145 146 147 148 149
            "SELECT DISTINCT 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, "
                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
            "FROM hosts AS h "
            "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, r.prefix_len, r.address"},
150 151

    // Retrieves MySQL schema version.
152 153 154
    {MySqlHostDataSource::GET_VERSION,
            "SELECT version, minor FROM schema_version"},

155
    // Marks the end of the statements table.
156 157 158
    {MySqlHostDataSource::NUM_STATEMENTS, NULL}
};

159 160 161 162 163 164 165 166 167 168 169 170
/// @brief This class provides mechanisms for sending and retrieving
/// information from the 'hosts' table.
///
/// This class should be used to create new entries in the 'hosts'
/// table and to retrieve DHCPv4 reservations from this table. The
/// queries used with this class do not retrieve IPv6 reservations for
/// the hosts to minimize negative impact on performance.
///
/// The derived class MySqlHostIPv6Exchange extends this class to facilitate
/// retrieving IPv6 reservations along with the host information.
class MySqlHostExchange {
private:
171

172
    /// @brief Number of columns returned for queries used with this class.
173 174 175 176 177 178 179 180
    static const size_t HOST_COLUMNS = 9;

public:

    /// @brief Constructor
    ///
    /// The initialization of the variables here is only to satisfy cppcheck -
    /// all variables are initialized/set in the methods before they are used.
181 182 183 184 185 186 187
    MySqlHostExchange()
        : bind_(HOST_COLUMNS), columns_(HOST_COLUMNS),
          error_(HOST_COLUMNS, MLM_FALSE), host_id_(0),
          dhcp_identifier_length_(0), dhcp_identifier_type_(0),
          dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
          hostname_length_(0), dhcp4_client_classes_length_(0),
          dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE),
188 189
          dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE),
          hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE),
190
          dhcp6_client_classes_null_(MLM_FALSE) {
191

192
        // Fill arrays with 0 so as they don't include any garbage.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        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_));

        // Set the column names (for error messages)
        columns_[0] = "host_id";
        columns_[1] = "dhcp_identifier";
        columns_[2] = "dhcp_identifier_type";
        columns_[3] = "dhcp4_subnet_id";
        columns_[4] = "dhcp6_subnet_id";
        columns_[5] = "ipv4_address";
        columns_[6] = "hostname";
        columns_[7] = "dhcp4_client_classes";
        columns_[8] = "dhcp6_client_classes";
208

209
        BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
210 211 212 213
    };

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

216
    /// @brief Returns value of host id.
217 218
    ///
    /// This method is used by derived classes.
219 220
    uint64_t getHostId() const {
        return (host_id_);
221
    };
222 223 224 225 226 227 228 229 230 231 232

    /// @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.
233 234 235
    static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
                                   std::vector<my_bool>& error) {
        for (size_t i = 0; i < error.size(); ++i) {
236 237 238
            error[i] = MLM_FALSE;
            bind[i].error = reinterpret_cast<char*>(&error[i]);
        }
239
    };
240 241 242 243 244 245 246 247 248 249 250 251 252 253

    /// @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.
254 255
    static std::string getColumnsInError(std::vector<my_bool>& error,
                                         const std::vector<std::string>& names) {
256 257 258
        std::string result = "";

        // Accumulate list of column names
259
        for (size_t i = 0; i < names.size(); ++i) {
260 261 262 263 264 265 266 267 268 269 270 271 272
            if (error[i] == MLM_TRUE) {
                if (!result.empty()) {
                    result += ", ";
                }
                result += names[i];
            }
        }

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

        return (result);
273
    };
274 275 276

    /// @brief Create MYSQL_BIND objects for Host Pointer
    ///
277 278
    /// Fills in the MYSQL_BIND array for sending data stored in the Host object
    /// to the database.
279 280 281 282 283 284 285 286 287 288 289 290 291
    ///
    /// @param host Host object to be added to the database.
    ///        None of the fields in the host reservation are modified -
    ///        the host data is only read.
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
        // 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
292 293
        // benefit while being safe approach.
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
294 295 296 297 298 299 300 301 302 303 304 305 306

        // 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_ = static_cast<uint32_t>(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(128) NOT NULL
307 308 309 310 311 312 313 314 315
            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_;
316 317

            // dhcp_identifier_type : TINYINT NOT NULL
318 319 320 321
            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;
322 323 324 325

            // dhcp4_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp4_subnet_id_
326
            dhcp4_subnet_id_ = host->getIPv4SubnetID();
327 328 329 330 331 332 333
            bind_[3].buffer_type = MYSQL_TYPE_LONG;
            bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
            bind_[3].is_unsigned = MLM_TRUE;

            // dhcp6_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp6_subnet_id_
334
            dhcp6_subnet_id_ = host->getIPv6SubnetID();
335 336 337 338 339 340 341
            bind_[4].buffer_type = MYSQL_TYPE_LONG;
            bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
            bind_[4].is_unsigned = MLM_TRUE;

            // ipv4_address : INT UNSIGNED NULL
            // The address in the Host structure is an IOAddress object.  Convert
            // this to an integer for storage.
342
            ipv4_address_ = static_cast<uint32_t>(host->getIPv4Reservation());
343 344 345 346
            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 = &MLM_FALSE; // commented out for performance
347
                                                      // reasons, see memset() above
348 349

            // hostname : VARCHAR(255) NULL
350 351
            strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
            hostname_length_ = host->getHostname().length();
352 353 354 355 356 357
            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;
358 359
            // Override default separator to not include space after comma.
            string classes4_txt = host->getClientClasses4().toText(",");
360 361 362 363 364 365
            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;
366 367
            // Override default separator to not include space after comma.
            string classes6_txt = host->getClientClasses6().toText(",");
368 369 370
            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();
371
            bind_[8].buffer_length = sizeof(host->getClientClasses6());
372 373 374 375

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

        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
382
    };
383

384
    /// @brief Create BIND array to receive Host data.
385
    ///
386 387 388
    /// 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.
389
    ///
390 391
    /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
    virtual std::vector<MYSQL_BIND> createBindForReceive() {
392 393 394 395 396 397
        // 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 seeeting bind_[X].is_null to MLM_FALSE.
398
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465

        // 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(128) 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_;

        // Add the error flags
466
        setErrorIndicators(bind_, error_);
467 468 469

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

473
    /// @brief Copy received data into Host object
474
    ///
475 476 477
    /// This function copies information about the host into a newly created
    /// @ref Host object. This method is called after @ref createBindForReceive.
    /// has been used.
478
    ///
479 480 481
    /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
    /// @ref Host object returned.
    HostPtr retrieveHost() {
482 483
        // Check if the identifier stored in the database is correct.
        if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
484
            isc_throw(BadValue, "invalid dhcp identifier type returned: "
485
                      << static_cast<int>(dhcp_identifier_type_));
486
        }
487 488 489
        // Set the dhcp identifier type in a variable of the appropriate data type.
        Host::IdentifierType type =
            static_cast<Host::IdentifierType>(dhcp_identifier_type_);
490

491
        // Set DHCPv4 subnet ID to the value returned. If NULL returned, set to 0.
492 493 494 495 496
        SubnetID ipv4_subnet_id(0);
        if (dhcp4_subnet_id_null_ == MLM_FALSE) {
            ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
        }

497
        // Set DHCPv6 subnet ID to the value returned. If NULL returned, set to 0.
498 499 500 501 502 503 504 505 506 507 508
        SubnetID ipv6_subnet_id(0);
        if (dhcp6_subnet_id_null_ == MLM_FALSE) {
            ipv6_subnet_id = static_cast<SubnetID>(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_);
        }

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

515 516
        // Set DHCPv4 client classes if non NULL value returned.
        std::string dhcp4_client_classes;
517
        if (dhcp4_client_classes_null_ == MLM_FALSE) {
518 519
            dhcp4_client_classes = std::string(dhcp4_client_classes_,
                                               dhcp4_client_classes_length_);
520 521
        }

522 523
        // Set DHCPv6 client classes if non NULL value returned.
        std::string dhcp6_client_classes;
524
        if (dhcp6_client_classes_null_ == MLM_FALSE) {
525 526
            dhcp6_client_classes = std::string(dhcp6_client_classes_,
                                               dhcp6_client_classes_length_);
527 528
        }

529
        // Create and return Host object from the data gathered.
530 531
        HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
                           type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
532
                           hostname, dhcp4_client_classes, dhcp6_client_classes));
533 534 535
        h->setHostId(host_id_);

        return (h);
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
    };

    /// @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.
    ///
    /// @todo This method will need to be extended to process options
    /// associated with hosts.
    ///
    /// @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);
        }
562 563 564 565 566 567 568 569 570 571 572 573 574
    }

    /// @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() {
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
        return (getColumnsInError(error_, columns_));
    };

protected:

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

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

    /// Error array.
    std::vector<my_bool> 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_[DUID::MAX_DUID_LEN];

    /// Length of a data in the dhcp_identifier_buffer_.
    size_t 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_;

    /// @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 This class provides mechanisms for sending and retrieving
/// host information and associated IPv6 reservations.
///
/// This class extends the @ref MySqlHostExchange class with the
/// mechanisms to retrieve IPv6 reservations along with host
/// information. It is assumed that both host data and IPv6
/// reservations are retrieved with a single query (using LEFT JOIN
/// MySQL clause). Because the host to IPv6 reservation is a 1-to-many
/// relation, the same row from the Host table is returned many times
/// (for each IPv6 reservation). This class is responsible for
/// converting those multiple host instances into a single Host
/// object with multiple IPv6 reservations.
class MySqlHostIPv6Exchange : public MySqlHostExchange {
private:

    /// @brief Number of columns returned in the queries used by
    /// @ref MySqlHostIPv6Exchange.
    static const size_t RESERVATION_COLUMNS = 13;

public:

    /// @brief Constructor.
    ///
    /// Apart from initializing the base class data structures it also
    /// initializes values representing IPv6 reservation information.
    MySqlHostIPv6Exchange()
        : MySqlHostExchange(), reserv_type_(0), reserv_type_null_(MLM_FALSE),
692
          ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0) {
693

694 695
        memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));

696 697 698 699 700 701 702 703 704 705 706 707 708 709
        // Append additional columns returned by the queries.
        columns_.push_back("address");
        columns_.push_back("prefix_len");
        columns_.push_back("type");
        columns_.push_back("dhcp6_iaid");

        // Resize binding table initialized in the base class. Do not
        // run memset on this table, because it is when createBindForReceive
        // is called.
        bind_.resize(RESERVATION_COLUMNS);

        // Resize error table.
        error_.resize(RESERVATION_COLUMNS);
        std::fill(&error_[0], &error_[RESERVATION_COLUMNS], MLM_FALSE);
710 711
    }

712 713 714 715 716 717 718 719
    /// @brief Checks if a currently processed row contains IPv6 reservation.
    ///
    /// @return true if IPv6 reservation data is non-null for the processed
    /// row, false otherwise.
    bool hasReservation() const {
        return (reserv_type_null_ == MLM_FALSE);
    };

720 721 722 723 724 725
    /// @brief Create 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)
726
    IPv6Resrv retrieveReservation() {
727 728 729 730 731 732 733 734 735 736 737 738 739 740
        // 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,
741 742 743
                      "invalid IPv6 reservation type returned: "
                      << static_cast<int>(reserv_type_)
                      << ". Only 0 or 2 are allowed.");
744 745
        }

746
        ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
747 748 749
        std::string address = ipv6_address_buffer_;
        IPv6Resrv r(type, IOAddress(address), prefix_len_);
        return (r);
750
    };
751

752
    /// @brief Processes one row of data fetched from a database.
753
    ///
754 755 756 757 758
    /// 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.
759
    ///
760 761 762
    /// For any returned row which contains IPv6 reservation information it
    /// creates a @ref IPv6Resrv and appends it to the collection of the
    /// IPv6 reservations in a Host object.
763
    ///
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
    /// @todo This method will need to be extended to process DHCPv6 options
    /// associated with hosts.
    ///
    /// @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;
        HostPtr most_recent_host;

        // If there are any hosts already created, let's obtain an instance
        // to the most recently added host. We will have to check if the
        // currently processed row contains some data for this host or a
        // different host. In the former case, we'll need to update the
        // host information.
        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.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
787
            most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
788 789 790 791 792 793 794 795 796 797
        }

        // If there is no existing host or the new host id doesn't match
        // we need to create a new host.
        if (!most_recent_host || (most_recent_host->getHostId() != getHostId())) {
            host = retrieveHost();
            // If the row also contains IPv6 reservation we should add it
            // to the host.
            if (hasReservation()) {
                host->addReservation(retrieveReservation());
798
            }
799 800
            // In any case let's put the new host in the results.
            hosts.push_back(host);
801

802 803 804 805
        // If the returned row pertains to an existing host, let's just
        // add a reservation.
        } else if (hasReservation() && most_recent_host) {
            most_recent_host->addReservation(retrieveReservation());
806 807 808
        }
    }

809 810 811 812 813 814 815 816 817
    /// @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() {
818
        // The following call sets bind_ values between 0 and 8.
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
        static_cast<void>(MySqlHostExchange::createBindForReceive());

        // IPv6 address/prefix VARCHAR(39)
        ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
        bind_[9].buffer_type = MYSQL_TYPE_STRING;
        bind_[9].buffer = ipv6_address_buffer_;
        bind_[9].buffer_length = ipv6_address_buffer_len_;
        bind_[9].length = &ipv6_address_buffer_len_;

        // prefix_len : TINYINT
        bind_[10].buffer_type = MYSQL_TYPE_TINY;
        bind_[10].buffer = reinterpret_cast<char*>(&prefix_len_);
        bind_[10].is_unsigned = MLM_TRUE;

        // (reservation) type : TINYINT
        reserv_type_null_ = MLM_FALSE;
        bind_[11].buffer_type = MYSQL_TYPE_TINY;
        bind_[11].buffer = reinterpret_cast<char*>(&reserv_type_);
        bind_[11].is_unsigned = MLM_TRUE;
        bind_[11].is_null = &reserv_type_null_;

        // dhcp6_iaid INT UNSIGNED
        bind_[12].buffer_type = MYSQL_TYPE_LONG;
        bind_[12].buffer = reinterpret_cast<char*>(&iaid_);
        bind_[12].is_unsigned = MLM_TRUE;

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

853
private:
854 855 856 857 858 859 860 861 862 863 864

    /// @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.
865
    char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
866 867 868 869 870 871 872 873 874 875

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

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

    /// @brief IAID.
    uint8_t iaid_;

876 877
};

878 879 880 881 882 883 884
/// @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 MySqlIPv6HostExchange class should be
/// used instead.
///
885
/// When a new IPv6 reservation is inserted into the database, an appropriate
886 887
/// host must be defined in the hosts table. An attempt to insert IPv6
/// reservation for non-existing host will result in failure.
888
class MySqlIPv6ReservationExchange {
889 890 891
private:

    /// @brief Set number of columns for ipv6_reservation table.
892
    static const size_t RESRV_COLUMNS = 6;
893 894

public:
895

896 897
    /// @brief Constructor
    ///
898
    /// Initialize class members representing a single IPv6 reservation.
899
    MySqlIPv6ReservationExchange()
900
        : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
901 902
          iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {

903
        // Reset error table.
904 905 906 907 908 909 910 911 912 913 914
        std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);

        // Set the column names (for error messages)
        columns_[0] = "host_id";
        columns_[1] = "address";
        columns_[2] = "prefix_len";
        columns_[3] = "type";
        columns_[4] = "dhcp6_iaid";
        BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
    }

915
    /// @brief Create MYSQL_BIND objects for IPv6 Reservation.
916
    ///
917 918
    /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
    /// object to the database.
919
    ///
920 921
    /// @param resv An object representing IPv6 reservation which will be
    ///        sent to the database.
922 923 924
    ///        None of the fields in the reservation are modified -
    ///        the reservation data is only read.
    /// @param id ID of a host owning this reservation
925 926
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
927 928
    std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
                                              const HostID& id) {
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946

        // Store the values to ensure they remain valid.
        resv_ = resv;
        host_id_ = id;

        // Initialize prior to constructing the array of MYSQL_BIND structures.
        // It sets all fields, including is_null, to zero, so we need to set
        // is_null only if it should be true. This gives up minor performance
        // benefit while being safe approach. For improved readability, the
        // code that explicitly sets is_null is there, but is commented out.
        memset(bind_, 0, sizeof(bind_));

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

        try {
            // address VARCHAR(39)
            address_ = resv.getPrefix().toText();
            address_len_ = address_.length();
947 948
            bind_[0].buffer_type = MYSQL_TYPE_BLOB;
            bind_[0].buffer = reinterpret_cast<char*>
949
                (const_cast<char*>(address_.c_str()));
950 951
            bind_[0].buffer_length = address_len_;
            bind_[0].length = &address_len_;
952 953 954

            // prefix_len tinyint
            prefix_len_ = resv.getPrefixLen();
955 956 957
            bind_[1].buffer_type = MYSQL_TYPE_TINY;
            bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
            bind_[1].is_unsigned = MLM_TRUE;
958 959 960 961

            // type tinyint
            // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
            type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
962 963 964
            bind_[2].buffer_type = MYSQL_TYPE_TINY;
            bind_[2].buffer = reinterpret_cast<char*>(&type_);
            bind_[2].is_unsigned = MLM_TRUE;
965 966 967 968

            // dhcp6_iaid INT UNSIGNED
            /// @todo: We don't support iaid in the IPv6Resrv yet.
            iaid_ = 0;
969 970 971
            bind_[3].buffer_type = MYSQL_TYPE_LONG;
            bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
            bind_[3].is_unsigned = MLM_TRUE;
972

973
            // host_id INT UNSIGNED NOT NULL
974 975 976
            bind_[4].buffer_type = MYSQL_TYPE_LONG;
            bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
            bind_[4].is_unsigned = MLM_TRUE;
977

978 979
        } catch (const std::exception& ex) {
            isc_throw(DbOperationError,
980 981
                      "Could not create bind array from IPv6 Reservation: "
                      << resv_.toText() << ", reason: " << ex.what());
982 983
        }

984 985
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
986 987
        // RESRV_COLUMNS -1 as we do not set reservation_id.
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
988 989 990
    }

private:
991

992 993
    /// @brief Host unique identifier.
    uint64_t host_id_;
994

995 996
    /// @brief Address (or prefix).
    std::string address_;
997

998 999
    /// @brief Length of the textual address representation.
    size_t address_len_;
1000

1001 1002
     /// @brief Length of the prefix (128 for addresses).
    uint8_t prefix_len_;
1003

1004 1005
    /// @brief Reservation type.
    uint8_t type_;
1006

1007 1008
    /// @brief IAID.
    uint8_t iaid_;
1009

1010 1011
    /// @brief Object holding reservation being sent to the database.
    IPv6Resrv resv_;
1012

1013 1014
    /// @brief Array of MySQL bindings.
    MYSQL_BIND bind_[RESRV_COLUMNS];
1015

1016 1017
    /// @brief Array of strings holding columns names.
    std::string columns_[RESRV_COLUMNS];
1018

1019 1020 1021
    /// @brief Array of boolean values indicating if error occurred
    /// for respective columns.
    my_bool error_[RESRV_COLUMNS];
1022 1023
};

1024
} // end of anonymous namespace
1025

1026 1027
namespace isc {
namespace dhcp {
1028

1029 1030 1031
/// @brief Implementation of the @ref MySqlHostDataSource.
class MySqlHostDataSourceImpl {
public:
1032

1033 1034 1035 1036 1037
    /// @brief Constructor.
    ///
    /// This constructor opens database connection and initializes prepared
    /// statements used in the queries.
    MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters);
1038

1039 1040
    /// @brief Destructor.
    ~MySqlHostDataSourceImpl();
1041

1042
    /// @brief Executes query which inserts a row into one of the tables.
1043
    ///
1044 1045 1046
    /// @param stindex Index of a statement being executed.
    /// @param bind Vector of MYSQL_BIND objects to be used when making the
    /// query.
1047
    ///
1048
    /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
1049 1050
    void addQuery(MySqlHostDataSource::StatementIndex stindex,
                  std::vector<MYSQL_BIND>& bind);
1051

1052
    /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
1053
    ///
1054 1055 1056
    /// @param resv IPv6 Reservation to be added
    /// @param id ID of a host owning this reservation
    void addResv(const IPv6Resrv& resv, const HostID& id);
1057

1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
    /// @brief Check Error and Throw Exception
    ///
    /// Virtually all MySQL functions return a status which, if non-zero,
    /// indicates an error.  This inline function conceals a lot of error
    /// checking/exception-throwing code.
    ///
    /// @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(const int status,
                    const MySqlHostDataSource::StatementIndex index,
                    const char* what) const;

    /// @brief Creates collection of @ref Host objects with associated
    /// information such as IPv6 reservations.
    ///
    /// 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.
    /// Whether IPv6 reservations are assigned to the @ref Host objects
1081
    /// depends on the type of the exchange object.
1082 1083
    ///
    /// @param stindex Statement index.
1084 1085 1086
    /// @param bind Pointer to an array of MySQL bindings.
    /// @param exchange Pointer to the exchange object used for the
    /// particular query.
1087 1088 1089 1090 1091 1092 1093 1094
    /// @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(MySqlHostDataSource::StatementIndex stindex,
                           MYSQL_BIND* bind,
                           boost::shared_ptr<MySqlHostExchange> exchange,
                           ConstHostCollection& result, bool single) const;

1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
    /// @brief Retrieves a host by subnet and client's unique identifier.
    ///
    /// This method is used by both MySqlHostDataSource::get4 and
    /// MySqlHOstDataSource::get6 methods.
    ///
    /// @param subnet_id Subnet identifier.
    /// @param identifier_type Identifier type.
    /// @param identifier_begin Pointer to a begining of a buffer containing
    /// an identifier.
    /// @param identifier_len Identifier length.
    /// @param stindex Statement index.
    /// @param exchange Pointer to the exchange object used for the
    /// particular query.
    ///
    /// @return Pointer to const instance of Host or null pointer if
    /// no host found.
    ConstHostPtr getHost(const SubnetID& subnet_id,
                         const Host::IdentifierType& identifier_type,
                         const uint8_t* identifier_begin,
                         const size_t identifier_len,
                         MySqlHostDataSource::StatementIndex stindex,
                         boost::shared_ptr<MySqlHostExchange> exchange) const;

1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
    /// @brief Pointer to the object representing an exchange which
    /// can be used to retrieve DHCPv4 reservation.
    boost::shared_ptr<MySqlHostExchange> host_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to retrieve DHCPv6 reservations.
    boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_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 MySQL connection
    MySqlConnection conn_;
1132 1133 1134

};

1135 1136 1137 1138 1139 1140
MySqlHostDataSourceImpl::
MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
    : host_exchange_(new MySqlHostExchange()),
      host_ipv6_exchange_(new MySqlHostIPv6Exchange()),
      host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
      conn_(parameters) {
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159

    // Open the database.
    conn_.openDatabase();

    // Enable autocommit.  To avoid a flush to disk on every commit, the global
    // parameter innodb_flush_log_at_trx_commit should be set to 2.  This will
    // cause the changes to be written to the log, but flushed to disk in the
    // background every second.  Setting the parameter to that value will speed
    // up the system, but at the risk of losing data if the system crashes.
    my_bool result = mysql_autocommit(conn_.mysql_, 1);
    if (result != 0) {
        isc_throw(DbOperationError, mysql_error(conn_.mysql_));
    }

    // Prepare all statements likely to be used.
    conn_.prepareStatements(tagged_statements,
            MySqlHostDataSource::NUM_STATEMENTS);
}

1160
MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
    // Free up the prepared statements, ignoring errors. (What would we do
    // about them? We're destroying this object and are not really concerned
    // with errors on a database connection that is about to go away.)
    for (int i = 0; i < conn_.statements_.size(); ++i) {
        if (conn_.statements_[i] != NULL) {
            (void) mysql_stmt_close(conn_.statements_[i]);
            conn_.statements_[i] = NULL;
        }
    }

    // There is no need to close the database in this destructor: it is
    // closed in the destructor of the mysql_ member variable.
}

void
1176 1177
MySqlHostDataSourceImpl::addQuery(MySqlHostDataSource::StatementIndex stindex,
                                  std::vector<MYSQL_BIND>& bind) {
1178

1179 1180 1181
    // Bind the parameters to the statement
    int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
    checkError(status, stindex, "unable to bind parameters");
1182

1183 1184 1185 1186 1187 1188
    // Execute the statement
    status = mysql_stmt_execute(conn_.statements_[stindex]);
    if (status != 0) {
        // Failure: check for the special case of duplicate entry.
        if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
            isc_throw(DuplicateEntry, "Database duplicate entry error");
1189
        }
1190
        checkError(status, stindex, "unable to execute");
1191 1192 1193 1194
    }
}

void
1195 1196
MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
                                 const HostID& id) {
1197
    std::vector<MYSQL_BIND> bind =
1198
        host_ipv6_reservation_exchange_->createBindForSend(resv, id);
1199

1200
    addQuery(MySqlHostDataSource::INSERT_V6_RESRV, bind);
1201 1202 1203
}

void
1204 1205 1206 1207 1208 1209 1210 1211 1212
MySqlHostDataSourceImpl::
checkError(const int status, const MySqlHostDataSource::StatementIndex index,
           const char* what) const {
    if (status != 0) {
        isc_throw(DbOperationError, what << " for <"
                  << conn_.text_statements_[index] << ">, reason: "
                  << mysql_error(conn_.mysql_) << " (error code "
                  << mysql_errno(conn_.mysql_) << ")");
    }
1213 1214 1215
}

void
1216 1217 1218 1219
MySqlHostDataSourceImpl::
getHostCollection(MySqlHostDataSource::StatementIndex stindex,
                  MYSQL_BIND* bind, boost::shared_ptr<MySqlHostExchange> exchange,
                  ConstHostCollection& result, bool single) const {
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241

    // Bind the selection parameters to the statement
    int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
    checkError(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(conn_.statements_[stindex], &outbind[0]);
    checkError(status, stindex, "unable to bind SELECT clause parameters");

    // Execute the statement
    status = mysql_stmt_execute(conn_.statements_[stindex]);
    checkError(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(conn_.statements_[stindex]);
    checkError(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
1242 1243
    // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
    // successful data fetch.
1244
    MySqlFreeResult fetch_release(conn_.statements_[stindex]);
1245 1246
    while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) ==
           MLM_MYSQL_FETCH_SUCCESS) {
1247
        try {
1248
            exchange->processFetchedData(result);
1249 1250 1251 1252 1253 1254 1255

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