pgsql_host_data_source.cc 73 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/pgsql_host_data_source.h>
#include <dhcpsrv/db_exceptions.h>
#include <util/buffer.h>
#include <util/optional_value.h>

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

#include <stdint.h>
#include <string>

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

namespace {

/// @brief Maximum length of option value.
37
38
/// The maximum size of the raw option data that may be read from the
/// database. 
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
const size_t OPTION_VALUE_MAX_LEN = 4096;

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

/// @brief Maximum length of DHCP identifier value.
const size_t DHCP_IDENTIFIER_MAX_LEN = 128;

/// @brief This class provides mechanisms for sending and retrieving
/// information from the 'hosts' table.
///
/// This class is used to insert and retrieve entries from the 'hosts' table.
/// The queries used with this class do not retrieve IPv6 reservations or
/// options associated with a host to minimize impact on performance. Other
/// classes derived from @ref PgSqlHostExchange should be used to retrieve
/// information about IPv6 reservations and options.
class PgSqlHostExchange : public PgSqlExchange {
private:

    /// @brief Column numbers for each column in the hosts table.
    /// These are used for both retrieving data and for looking up
    /// column labels for logging.  Note that their numeric order
    /// MUST match that of the column order in the hosts table.
    static const int HOST_ID_COL = 0;
    static const int DHCP_IDENTIFIER_COL = 1;
    static const int DHCP_IDENTIFIER_TYPE_COL = 2;
    static const int DHCP4_SUBNET_ID_COL = 3;
    static const int DHCP6_SUBNET_ID_COL = 4;
    static const int IPV4_ADDRESS_COL = 5;
    static const int HOSTNAME_COL = 6;
    static const int DHCP4_CLIENT_CLASSES_COL = 7;
    static const int DHCP6_CLIENT_CLASSES_COL = 8;
    /// @brief Number of columns returned for SELECT queries send by this class.
    static const size_t HOST_COLUMNS = 9;

public:

    /// @brief Constructor
    ///
    /// @param additional_columns_num This value is set by the derived classes
    /// to indicate how many additional columns will be returned by SELECT
    /// queries performed by the derived class. This constructor will allocate
    /// resources for these columns, e.g. binding table, error indicators.
    PgSqlHostExchange(const size_t additional_columns_num = 0)
86
        : PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
        // Set the column names for use by this class. This only comprises
        // names used by the PgSqlHostExchange class. Derived classes will
        // need to set names for the columns they use.  Currenty these are
        // only used for logging purposes.
        columns_[HOST_ID_COL] = "host_id";
        columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
        columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
        columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
        columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
        columns_[IPV4_ADDRESS_COL] = "ipv4_address";
        columns_[HOSTNAME_COL] = "hostname";
        columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
        columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";

        BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
    };

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

108
    /// @brief Reinitializes state information
109
    ///
110
    /// This function should be called in between statement executions.
111
    /// Deriving classes should invoke this method as well as be reset
112
    /// all of their own stateful values.
113
114
115
116
    virtual void clear() {
        host_.reset();
    };

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    /// @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));
    }

137
    /// @brief Returns value of host id in the given row.
138
    ///
139
    /// This method is used to "look ahead" at the host_id in a row
140
141
    /// without having to call retrieveHost()
    HostID getHostId(const PgSqlResult& r, int row) {
142
143
144
145
        HostID host_id;
        getColumnValue(r, row, HOST_ID_COL, host_id);
        return (host_id);
    }
146
147
148
149
150
151
152
153
154
155

    /// @brief Populate a bind array from a host
    ///
    /// Constructs a PsqlBindArray for sending data stored in a Host object
    /// to the database.
    ///
    /// @param host Host object to be added to the database.
    ///        None of the fields in the host reservation are modified -
    ///        the host data is only read.
    ///
156
    /// @return pointer to newly constructed bind_array containing the
157
158
159
    /// bound values extracted from host
    ///
    /// @throw DbOperationError if bind_array cannot be populated.
160
    PsqlBindArrayPtr
161
162
163
164
165
166
167
168
    createBindForSend(const HostPtr& host) {
        if (!host) {
            isc_throw(BadValue, "createBindForSend:: host object is NULL");
        }

        // Store the host to ensure bound values remain in scope
        host_ = host;

169
        // Bind the host data to the array
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
        PsqlBindArrayPtr bind_array(new PsqlBindArray());
        try {
            // host_id : is auto_incremented skip it

            // dhcp_identifier : BYTEA NOT NULL
            bind_array->add(host->getIdentifier());

            // dhcp_identifier_type : SMALLINT NOT NULL
            bind_array->add(host->getIdentifierType());

            // dhcp4_subnet_id : INT NULL
            bind_array->add(host->getIPv4SubnetID());

            // dhcp6_subnet_id : INT NULL
            bind_array->add(host->getIPv6SubnetID());

186
            // ipv4_address : BIGINT NULL
187
            bind_array->add(host->getIPv4Reservation());
188

189
            // hostname : VARCHAR(255) NULL
190
            bind_array->add(host->getHostname());
191
192
193

            // dhcp4_client_classes : VARCHAR(255) NULL
            // Override default separator to not include space after comma.
194
            bind_array->addTempString(host->getClientClasses4().toText(","));
195
196

            // dhcp6_client_classes : VARCHAR(255) NULL
197
            bind_array->addTempString(host->getClientClasses6().toText(","));
198
199
200
201
202
203
204
205
206
207
        } catch (const std::exception& ex) {
            host_.reset();
            isc_throw(DbOperationError,
                      "Could not create bind array from Host: "
                      << host->getHostname() << ", reason: " << ex.what());
        }

        return (bind_array);
    };

208
209
210
211
212
213
214
215
216
217
218
219
220
    /// @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 overriden 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.
221
    virtual void processRowData(ConstHostCollection& hosts,
222
223
224
225
                                    const PgSqlResult& r, int row) {
        // Peek at the host id , so we can skip it if we already have it
        // This lets us avoid constructing a copy of host for each
        // of its sub-rows (options, etc...)
226
        HostID row_host_id = getHostId(r, row);
227
228
229
230

        // Add new host only if there are no hosts or the host id of the
        // most recently added host is different than the host id of the
        // currently processed host.
231
232
        if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
            HostPtr host = retrieveHost(r, row, row_host_id);
233
234
235
236
            hosts.push_back(host);
        }
    }

237
238
    /// @brief Creates a Host object from a given row in a result set.
    ///
239
240
    /// @param r result set containing one or more rows from the hosts table
    /// @param row index within the result set of the row to process
241
    /// @param peeked_host_id if the caller has peeked ahead at the row's
242
    /// host_id, it can be passed in here to avoid fetching it from the row
243
    /// a second time.
244
245
246
    ///
    /// @return HostPtr to the newly created Host object
    /// @throw DbOperationError if the host cannot be created.
247
    HostPtr retrieveHost(const PgSqlResult& r, int row,
248
        const HostID& peeked_host_id = 0) {
249

250
        // If the caller peeked ahead at the host_id use that, otherwise
251
252
        // read it from the row.
        HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271

        // dhcp_identifier : BYTEA NOT NULL
        uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
        size_t identifier_len;
        convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
                             sizeof(identifier_value), identifier_len);

        // dhcp_identifier_type : SMALLINT NOT NULL
        uint8_t type;
        getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
        if (type > MAX_IDENTIFIER_TYPE) {
            isc_throw(BadValue, "invalid dhcp identifier type returned: "
                      << static_cast<int>(type));
        }

        Host::IdentifierType identifier_type =
            static_cast<Host::IdentifierType>(type);

        // dhcp4_subnet_id : INT NULL
272
273
        uint32_t subnet_id;
        getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
274
275
276
        SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);

        // dhcp6_subnet_id : INT NULL
277
        getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
278
279
280
        SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);

        // ipv4_address : BIGINT NULL
281
282
        uint32_t addr4;
        getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
283
284
285
286
287
288
289
290
291
292
293
294
295
296
        isc::asiolink::IOAddress ipv4_reservation(addr4);

        // hostname : VARCHAR(255) NULL
        std::string hostname;
        getColumnValue(r, row, HOSTNAME_COL, hostname);

        // dhcp4_client_classes : VARCHAR(255) NULL
        std::string dhcp4_client_classes;
        getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);

        // dhcp6_client_classes : VARCHAR(255) NULL
        std::string dhcp6_client_classes;
        getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);

297
        // Finally, attempt to create the new host.
298
299
300
301
302
        HostPtr host;
        try {
            host.reset(new Host(identifier_value, identifier_len,
                                identifier_type, dhcp4_subnet_id,
                                dhcp6_subnet_id, ipv4_reservation, hostname,
303
                                dhcp4_client_classes, dhcp6_client_classes));
304
305
306
307
308

            host->setHostId(host_id);
        } catch (const isc::Exception& ex) {
            isc_throw(DbOperationError, "Could not create host: " << ex.what());
        }
309

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
        return(host);
    };

protected:
    /// Pointer to Host object holding information to be inserted into
    /// Hosts table.  This is used to retain scope.
    HostPtr host_;
};


/// @brief Extends base exchange class with ability to retrieve DHCP options
/// from the 'dhcp4_options' and 'dhcp6_options' tables.
///
/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
/// along with the host information. It is not used to retrieve IPv6
/// reservations. The following types of queries are supported:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
private:

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

    /// @brief Receives DHCPv4 or DHCPv6 options information from the
    /// dhcp4_options or dhcp6_options tables respectively.
    ///
    /// The PgSqlHostWithOptionsExchange class holds two respective instances
    /// of this class, one for receiving DHCPv4 options, one for receiving
    /// DHCPv6 options.
    ///
    /// The following are the basic functions of this class:
    /// - bind class members to specific columns in MySQL binding tables,
    /// - set DHCP options specific column names,
    /// - create instances of options retrieved from the database.
    ///
    /// The reason for isolating those functions in a separate C++ class is
    /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
    class OptionProcessor {
    public:

        /// @brief Constructor.
        ///
        /// @param universe V4 or V6. The type of the options' instances
        /// created by this class depends on this parameter.
        /// @param start_column Index of the first column to be used by this
        /// class.
        OptionProcessor(const Option::Universe& universe,
                        const size_t start_column)
360
        : universe_(universe), start_column_(start_column),
361
362
363
364
365
366
367
368
          option_id_index_(start_column), code_index_(start_column_ + 1),
          value_index_(start_column_ + 2),
          formatted_value_index_(start_column_ + 3),
          space_index_(start_column_ + 4),
          persistent_index_(start_column_ + 5),
          most_recent_option_id_(0) {
        }

369
370
371
372
373
374
        /// @brief Reintializes state information
        ///
        /// This function should be called prior to processing a fetched
        /// set of options.
        void clear() {
            most_recent_option_id_ = 0;
375
376
377
378
379
        }

        /// @brief Creates instance of the currently processed option.
        ///
        /// This method detects if the currently processed option is a new
380
        /// instance. It makes its determination by comparing the identifier
381
382
383
384
385
386
        /// 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.
        ///
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
        /// This logic is necessary to deal with result sets made from multiple
        /// left joins which contain duplicated data.  For instance queries
        /// returning both v4 and v6 options for a host would generate result
        /// sets similar to this:
        /// @code
        ///
        /// row 0: host-1  v4-opt-1  v6-opt-1
        /// row 1: host-1  v4-opt-1  v6-opt-2
        /// row 2: host-1  v4-opt-1  v6-opt-3
        /// row 4: host-1  v4-opt-2  v6-opt-1
        /// row 5: host-1  v4-opt-2  v6-opt-2
        /// row 6: host-1  v4-opt-2  v6-opt-3
        /// row 7: host-2  v4-opt-1  v6-opt-1
        /// row 8: host-2  v4-opt-2  v6-opt-1
        ///  :
        /// @endcode
        ///
404
405
        /// @param cfg Pointer to the configuration object into which new
        /// option instances should be inserted.
406
407
408
409
        /// @param r result set containing one or more rows from a dhcp
        /// options table.
        /// @param row index within the result set of the row to process
        void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
410
                            int row) {
411
412
            // If the option id on this row is NULL, then there's no
            // option of this type (4/6) on this row to fetch, so bail.
413
414
415
416
            if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
                return;
            }

417
            // option_id: INT
418
419
420
            uint64_t option_id;
            PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);

421
422
423
424
            // The row option id must be greater than id if the most recent
            // option because they are ordered by option id. Otherwise
            // we assume that we have already processed this option.
            if (most_recent_option_id_ >= option_id) {
425
426
427
428
429
                return;
            }

            // Remember current option id as the most recent processed one. We
            // will be comparing it with option ids in subsequent rows.
430
            most_recent_option_id_ = option_id;
431

432
433
434
435
            // code: SMALLINT NOT NULL
            uint16_t code;
            PgSqlExchange::getColumnValue(r, row, code_index_, code);

436
            // value: BYTEA
437
438
            uint8_t value[OPTION_VALUE_MAX_LEN];
            size_t value_len;
439
            PgSqlExchange::convertFromBytea(r, row, value_index_, value,
440
                                            sizeof(value), value_len);
441

442
            // formatted_value: TEXT
443
            std::string formatted_value;
444
445
446
447
448
449
450
451
452
453
454
            PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
                                          formatted_value);

            // space: VARCHAR(128)
            std::string space;
            PgSqlExchange::getColumnValue(r, row, space_index_, space);

            // persistent: BOOL default false
            bool persistent;
            PgSqlExchange::getColumnValue(r, row, persistent_index_,
                                          persistent);
455
456
457
458
459
460
461
462
463
464
465

            // Options are held in a binary or textual format in the database.
            // This is similar to having an option specified in a server
            // configuration file. Such option is converted to appropriate C++
            // class, using option definition. Thus, we need to find the
            // option definition for this option code and option space.

            // If the option space is a standard DHCPv4 or DHCPv6 option space,
            // this is most likely a standard option, for which we have a
            // definition created within libdhcp++.
            OptionDefinitionPtr def;
466
467
468
            if ((space == DHCP4_OPTION_SPACE) ||
                (space == DHCP6_OPTION_SPACE)) {
                def = LibDHCP::getOptionDef(universe_, code);
469
470
471
472
473
474
475
476
            }

            // 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) {
477
478
                    def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
                                                      code);
479
480
481
482
483
484
                }
            }

            // In all other cases, we use runtime option definitions, which
            // should be also registered within the libdhcp++.
            if (!def) {
485
                def = LibDHCP::getRuntimeOptionDef(space, code);
486
487
488
489
490
491
            }

            OptionPtr option;

            if (!def) {
                // If no definition found, we use generic option type.
492
493
                OptionBuffer buf(value, value + value_len);
                option.reset(new Option(universe_, code, buf.begin(),
494
495
496
497
498
499
500
                                        buf.end()));
            } else {
                // The option value may be specified in textual or binary format
                // in the database. If formatted_value is empty, the binary
                // format is used. Depending on the format we use a different
                // variant of the optionFactory function.
                if (formatted_value.empty()) {
501
502
                    OptionBuffer buf(value, value + value_len);
                    option = def->optionFactory(universe_, code, buf.begin(),
503
504
505
506
507
                                                buf.end());
                } else {
                    // Spit the value specified in comma separated values
                    // format.
                    std::vector<std::string> split_vec;
508
                    boost::split(split_vec, formatted_value,
509
510
                                 boost::is_any_of(","));
                    option = def->optionFactory(universe_, code, split_vec);
511
512
513
                }
            }

514
            OptionDescriptor desc(option, persistent, formatted_value);
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
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
562
563
564
565
566
567
568
569
570
571
572
573
574
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
            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";
        }

    private:
        /// @brief Universe: V4 or V6.
        Option::Universe universe_;

        /// @brief Index of first column used by this class.
        size_t start_column_;

        //@}

        /// @name Indexes of the specific columns
        //@{
        /// @brief Option id
        size_t option_id_index_;

        /// @brief Code
        size_t code_index_;

        /// @brief Value
        size_t value_index_;

        /// @brief Formatted value
        size_t formatted_value_index_;

        /// @brief Space
        size_t space_index_;

        /// @brief Persistent
        size_t persistent_index_;
        //@}

        /// @brief Option id for last processed row.
        uint64_t most_recent_option_id_;
    };

    /// @brief Pointer to the @ref OptionProcessor class.
    typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;

public:

    /// @brief DHCP option types to be fetched from the database.
    ///
    /// Supported types are:
    /// - Only DHCPv4 options,
    /// - Only DHCPv6 options,
    /// - Both DHCPv4 and DHCPv6 options.
    enum FetchedOptions {
        DHCP4_ONLY,
        DHCP6_ONLY,
        DHCP4_AND_DHCP6
    };

    /// @brief Constructor.
    ///
    /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
    /// be fetched from the database for a host.
    /// @param additional_columns_num Number of additional columns for which
    /// resources should be allocated, e.g. binding table, column names etc.
    /// This parameter should be set to a non zero value by derived classes to
    /// allocate resources for the columns supported by derived classes.
    PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
                                 const size_t additional_columns_num = 0)
        : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
                            + additional_columns_num),
          opt_proc4_(), opt_proc6_() {

        // Create option processor for DHCPv4 options, if required.
        if ((fetched_options == DHCP4_ONLY) ||
            (fetched_options == DHCP4_AND_DHCP6)) {
            opt_proc4_.reset(new OptionProcessor(Option::V4,
                                                 findAvailColumn()));
            opt_proc4_->setColumnNames(columns_);
        }

        // Create option processor for DHCPv6 options, if required.
        if ((fetched_options == DHCP6_ONLY) ||
            (fetched_options == DHCP4_AND_DHCP6)) {
            opt_proc6_.reset(new OptionProcessor(Option::V6,
                                                 findAvailColumn()));
            opt_proc6_->setColumnNames(columns_);
        }
    }

613
614
    /// @brief Clears state information
    ///
615
    /// This function should be called in between statement executions.
616
    /// Deriving classes should invoke this method as well as be reset
617
    /// all of their own stateful values.
618
619
620
621
622
623
624
625
626
627
628
    virtual void clear() {
        PgSqlHostExchange::clear();
        if (opt_proc4_) {
            opt_proc4_->clear();
        }

        if (opt_proc6_) {
            opt_proc6_->clear();
        }
    }

629
630
    /// @brief Processes the current row.
    ///
631
632
633
634
635
    /// The fetched row includes both host information and DHCP option
    /// information. Because the SELECT queries use one or more LEFT JOIN
    /// clauses, the result set may contain duplicated host or options 
    /// entries. This method detects duplicated information and discards such 
    /// entries.
636
637
    ///
    /// @param [out] hosts Container holding parsed hosts and options.
638
    virtual void processRowData(ConstHostCollection& hosts,
639
640
641
                                const PgSqlResult& r, int row) {
        HostPtr current_host;
        if (hosts.empty()) {
642
            // Must be the first one, fetch it.
643
644
645
646
647
            current_host = retrieveHost(r, row);
            hosts.push_back(current_host);
        } else {
            // Peek at the host id so we can skip it if we already have
            // this host.  This lets us avoid retrieving the host needlessly
648
            // for each of its sub-rows (options, etc...).
649
650
            HostID row_host_id = getHostId(r, row);
            current_host = boost::const_pointer_cast<Host>(hosts.back());
651
652
653

            // if the row's host id is greater than the one we've been
            // working on we're starting a new host, so fetch it.
654
655
656
657
            if (row_host_id > current_host->getHostId()) {
                current_host = retrieveHost(r, row, row_host_id);
                hosts.push_back(current_host);
            }
658
659
660
661
        }

        // Parse DHCPv4 options if required to do so.
        if (opt_proc4_) {
662
663
            CfgOptionPtr cfg = current_host->getCfgOption4();
            opt_proc4_->retrieveOption(cfg, r, row);
664
665
666
667
        }

        // Parse DHCPv6 options if required to do so.
        if (opt_proc6_) {
668
669
            CfgOptionPtr cfg = current_host->getCfgOption6();
            opt_proc6_->retrieveOption(cfg, r, row);
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
        }
    }

private:

    /// @brief Returns a number of columns required to retrieve option data.
    ///
    /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
    /// DHCPv6 a different number of columns is required in the binding array.
    /// This method returns the number of required columns, according to the
    /// value of @c fetched_columns passed in the constructor.
    ///
    /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
    /// both types of options should be retrieved.
    ///
    /// @return Number of required columns.
    static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
        return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
                OPTION_COLUMNS);
    }

    /// @brief Pointer to DHCPv4 options processor.
    ///
    /// If this object is NULL, the DHCPv4 options are not fetched.
    OptionProcessorPtr opt_proc4_;

    /// @brief Pointer to DHCPv6 options processor.
    ///
    /// If this object is NULL, the DHCPv6 options are not fetched.
    OptionProcessorPtr opt_proc6_;
};

/// @brief This class provides mechanisms for sending and retrieving
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
///
/// This class extends the @ref PgSqlHostWithOptionsExchange class with the
706
/// mechanisms to retrieve IPv6 reservations. This class is used in situations
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
/// when it is desired to retrieve DHCPv6 specific information about the host
/// (DHCPv6 options and reservations), or entire information about the host
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
/// queries used with this class:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
///   LEFT JOIN ipv6_reservations ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
private:

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

public:

    /// @brief Constructor.
    ///
    /// Apart from initializing the base class data structures it also
    /// initializes values representing IPv6 reservation information.
    PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
        : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
          reservation_id_index_(findAvailColumn()),
          address_index_(reservation_id_index_ + 1),
          prefix_len_index_(reservation_id_index_ + 2),
          type_index_(reservation_id_index_ + 3),
          iaid_index_(reservation_id_index_ + 4),
          most_recent_reservation_id_(0) {

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

742
        BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
743
744
    }

745
    /// @brief Reinitializes state information
746
    ///
747
    /// This function should be called in between statement executions.
748
    /// Deriving classes should invoke this method as well as be reset
749
    /// all of their own stateful values.
750
751
752
753
754
755
    void clear() {
        PgSqlHostWithOptionsExchange::clear();
        most_recent_reservation_id_ = 0;
    }

    /// @brief Returns reservation id from the row.
756
757
    ///
    /// @return Reservation id or 0 if no reservation data is fetched.
758
759
760
761
    uint64_t getReservationId(const PgSqlResult& r, int row) const {
        uint64_t resv_id = 0;
        if (!isColumnNull(r, row, reservation_id_index_)) {
            getColumnValue(r, row, reservation_id_index_, resv_id);
762
        }
763

764
        return (resv_id);
765
766
767
768
769
770
771
772
    };

    /// @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)
773
    IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
774
775

        // type: SMALLINT NOT NULL
776
777
        uint16_t tmp;
        getColumnValue(r, row, type_index_, tmp);
778

779
780
        // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
        IPv6Resrv::Type resv_type;
781
        switch (tmp) {
782
        case 0:
783
            resv_type = IPv6Resrv::TYPE_NA;
784
785
786
            break;

        case 2:
787
            resv_type = IPv6Resrv::TYPE_PD;
788
789
790
791
792
            break;

        default:
            isc_throw(BadValue,
                      "invalid IPv6 reservation type returned: "
793
                      << tmp << ". Only 0 or 2 are allowed.");
794
795
        }

796
        // address VARCHAR(39) NOT NULL
797
798
        isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));

799
800
        // prefix_len: SMALLINT NOT NULL
        uint16_t prefix_len;
801
802
        getColumnValue(r, row, prefix_len_index_, prefix_len);

803
804
805
806
807
808
        // @todo once we support populating iaid
        // iaid: INT
        // int iaid;
        // getColumnValue(r, row, iaid_index_, iaid);

        // Create the reservation.
809
810
        IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
        return (reservation);
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
    };

    /// @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.
832
833
    /// @param r result set containing one or more rows of fetched data
    /// @param row index within the result set of the row to process
834
835
    virtual void processRowData(ConstHostCollection& hosts,
                                const PgSqlResult& r, int row) {
836
        // Call parent class to fetch host information and options.
837
        PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
838

839
        // Shouldn't happen but just in case
840
841
842
843
844
        if (hosts.empty()) {
            isc_throw(Unexpected, "no host information while retrieving"
                      " IPv6 reservation");
        }

845
846
847
848
849
850
        // If we have reservation id we havent' seen yet, retrive the
        // the reservation, adding it to the current host
        uint64_t reservation_id = getReservationId(r, row);
        if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
            HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
            host->addReservation(retrieveReservation(r, row));
851
            most_recent_reservation_id_ = reservation_id;
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
        }
    }

private:
    /// @name Indexes of columns holding information about IPv6 reservations.
    //@{
    /// @brief Index of reservation_id column.
    size_t reservation_id_index_;

    /// @brief Index of address column.
    size_t address_index_;

    /// @brief Index of prefix_len column.
    size_t prefix_len_index_;

    /// @brief Index of type column.
    size_t type_index_;

    /// @brief Index of IAID column.
    size_t iaid_index_;

    //@}

    /// @brief Reservation id for last processed row.
    uint64_t most_recent_reservation_id_;

};

/// @brief This class is used for storing IPv6 reservations in a MySQL database.
///
/// This class is only used to insert IPv6 reservations into the
/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
/// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
/// used instead.
///
/// When a new IPv6 reservation is inserted into the database, an appropriate
/// host must be defined in the hosts table. An attempt to insert IPv6
/// reservation for non-existing host will result in failure.
890
class PgSqlIPv6ReservationExchange : public PgSqlExchange {
891
892
893
894
895
896
897
898
899
900
901
private:

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

public:

    /// @brief Constructor
    ///
    /// Initialize class members representing a single IPv6 reservation.
    PgSqlIPv6ReservationExchange()
902
        : PgSqlExchange(RESRV_COLUMNS),
903
          resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
904
905
906
907
908
909
        // 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";
910
        BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
911
912
    }

913
    /// @brief Populate a bind array representing an IPv6 reservation
914
    ///
915
    /// Constructs a PsqlBindArray for an IPv6 reservation to the database.
916
    ///
917
    /// @param resv The IPv6 reservation to be added to the database.
918
    ///        None of the fields in the reservation are modified -
919
    /// @param host_id ID of the host to which this reservation belongs.
920
    ///
921
922
923
924
    /// @return pointer to newly constructed bind_array containing the
    /// bound values extracted the IPv6 reservation
    ///
    /// @throw DbOperationError if bind_array cannot be populated.
925
    PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
926
                                       const HostID& host_id) {
927
        // Store the values to ensure they remain valid.
928
929
        // Technically we don't need this, as currently all the values
        // are converted to strings and stored by the bind array.
930
931
        resv_ = resv;

932
        PsqlBindArrayPtr bind_array(new PsqlBindArray());
933
934

        try {
935
936
937
938
939
940
941
            // address VARCHAR(39) NOT NULL
            bind_array->add(resv.getPrefix());

            // prefix_len: SMALLINT NOT NULL
            bind_array->add(resv.getPrefixLen());

            // type: SMALLINT NOT NULL
942
            // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
943
944
            uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
            bind_array->add(type);
945

946
            // dhcp6_iaid: INT UNSIGNED
947
            /// @todo: We don't support iaid in the IPv6Resrv yet.
948
            bind_array->addNull();
949

950
951
            // host_id: BIGINT NOT NULL
            bind_array->add(host_id);
952
953
954
955
956
957
        } catch (const std::exception& ex) {
            isc_throw(DbOperationError,
                      "Could not create bind array from IPv6 Reservation: "
                      << resv_.toText() << ", reason: " << ex.what());
        }

958
        return (bind_array);
959
960
961
962
963
964
965
966
967
968
    }

private:
    /// @brief Object holding reservation being sent to the database.
    IPv6Resrv resv_;
};

/// @brief This class is used for inserting options into a database.
///
/// This class supports inserting both DHCPv4 and DHCPv6 options.
969
class PgSqlOptionExchange : public PgSqlExchange {
970
971
private:

972
973
974
975
976
977
978
979
980
981
982
    static const int OPTION_ID_COL = 0;
    static const int CODE_COL = 1;
    static const int VALUE_COL = 2;
    static const int FORMATTED_VALUE_COL = 3;
    static const int SPACE_COL = 4;
    static const int PERSISTENT_COL = 5;
    static const int DHCP_CLIENT_CLASS_COL = 6;
    static const int DHCP_SUBNET_ID_COL = 7;
    static const int HOST_ID_COL = 8;
    static const int SCOPE_ID_COL = 9;

983
    /// @brief Number of columns in the tables holding options.
984
    static const size_t OPTION_COLUMNS = 10;
985
986
987
988
989

public:

    /// @brief Constructor.
    PgSqlOptionExchange()
990
991
992
993
994
995
996
997
998
999
1000
        : PgSqlExchange(OPTION_COLUMNS) {
        columns_[OPTION_ID_COL] = "option_id";
        columns_[CODE_COL] = "code";
        columns_[VALUE_COL] = "value";
        columns_[FORMATTED_VALUE_COL] = "formatted_value";
        columns_[SPACE_COL] = "space";
        columns_[PERSISTENT_COL] = "persistent";
        columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
        columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
        columns_[HOST_ID_COL] = "host_id";
        columns_[SCOPE_ID_COL] = "scope_id";
1001

1002
        BOOST_STATIC_ASSERT(9 < OPTION_COLUMNS);
1003
1004
1005
1006
    }

    /// @brief Creates binding array to insert option data into database.
    ///
1007
1008
1009
1010
1011
1012
    /// @param opt_desc option descriptor of the option to write
    /// @param opt_space name of the option space to which the option belongs
    /// @param host_id host id of the host to which the option belongs
    ///
    /// @return pointer to newly constructed bind_array containing the
    /// bound values extracted from host
1013
    PsqlBindArrayPtr
1014
1015
1016
1017
1018
1019
1020
    createBindForSend(const OptionDescriptor& opt_desc,
                      const std::string& opt_space,
                      const HostID& host_id) {
        // Hold pointer to the option to make sure it remains valid until
        // we complete a query.
        option_ = opt_desc.option_;

1021
        // Create the bind-array
1022
        PsqlBindArrayPtr bind_array(new PsqlBindArray());
1023
1024

        try {
1025
            // option_id: is auto_incremented so skip it
1026
1027

            // code: SMALLINT UNSIGNED NOT NULL
1028
            bind_array->add(option_->getType());
1029

1030
            // value: BYTEA NULL
1031
1032
1033
1034
            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
1035
                // option and store it in the database as a BYTEA.
1036
1037
1038
1039
1040
1041
                OutputBuffer buf(opt_desc.option_->len());
                opt_desc.option_->pack(buf);
                const char* buf_ptr = static_cast<const char*>(buf.getData());
                value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
                              buf_ptr + buf.getLength());
                value_len_ = value_.size();
1042
                bind_array->add(value_);
1043
1044
            } else {
                // No value or formatted_value specified. In this case, the
1045
1046
                // value BYTEA should be NULL.
                bind_array->addNull(PsqlBindArray::BINARY_FMT);
1047
1048
1049
            }

            // formatted_value: TEXT NULL,
1050
1051
1052
            if (!opt_desc.formatted_value_.empty()) {
                bind_array->addTempString(opt_desc.formatted_value_);
            } else {
1053
1054
                bind_array->addNull();
            }
1055

1056
            // space: VARCHAR(128) NULL
1057
1058
            if (!opt_space.empty()) {
                bind_array->addTempString(opt_space);
1059
            } else {
1060
                bind_array->addNull();
1061
1062
            }

1063
1064
            // persistent: BOOLEAN DEFAULT false
            bind_array->add(opt_desc.persistent_);
1065

1066
1067
1068
            // host_id: INT NULL
            if (!host_id) {
                isc_throw(BadValue, "host_id cannot be null");
1069
            }
1070
            bind_array->add(host_id);
1071

1072
1073
1074
        } catch (const std::exception& ex) {
            isc_throw(DbOperationError,
                      "Could not create bind array for inserting DHCP "
1075
                      "host option: " << option_->toText() << ", reason: "
1076
1077
1078
                      << ex.what());
        }

1079
        return (bind_array);
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
    }

private:

    /// @brief Option value as binary.
    std::vector<uint8_t> value_;

    /// @brief Option value length.
    size_t value_len_;

    /// @brief Pointer to currently parsed option.
    OptionPtr option_;
};

} // end of anonymous namespace


namespace isc {
namespace dhcp {

/// @brief Implementation of the @ref PgSqlHostDataSource.
class PgSqlHostDataSourceImpl {
public:

    /// @brief Statement Tags
    ///
    /// The contents of the enum are indexes into the list of SQL statements
    enum StatementIndex {
        INSERT_HOST,            // Insert new host to collection
        INSERT_V6_RESRV,        // Insert v6 reservation
1110
1111
        INSERT_V4_HOST_OPTION,  // Insert DHCPv4 option
        INSERT_V6_HOST_OPTION,  // Insert DHCPv6 option
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
        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_VERSION,            // Obtain version number
        NUM_STATEMENTS          // Number of statements
    };

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

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

    /// @brief Executes statements which insert a row into one of the tables.
    ///
    /// @param stindex Index of a statement being executed.
    /// @param bind Vector of MYSQL_BIND objects to be used when making the
    /// query.
    /// @param return_last_id flag indicating whether or not the insert
1137
    /// returns the primary key of from the row inserted via " RETURNING
1138
1139
1140
1141
1142
    /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
    /// clause causes the INSERT to return a result set that should consist
    /// of a single row with one column, the value of the primary key.
    /// Defaults to false.
    ///
1143
    /// @return 0 if return_last_id is false, otherwise it returns the
1144
    /// the value in the result set in the first col of the first row.
1145
1146
1147
    ///
    /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
    uint64_t addStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
1148
                          PsqlBindArrayPtr& bind,
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
                          const bool return_last_id = false);

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

    /// @brief Inserts a single DHCP option into the database.
    ///
    /// @param stindex Index of a statement being executed.
    /// @param opt_desc Option descriptor holding information about an option
    /// to be inserted into the database.
    /// @param opt_space Option space name.
    /// @param subnet_id Subnet identifier.
    /// @param host_id Host identifier.
    void addOption(const PgSqlHostDataSourceImpl::StatementIndex& stindex,
                   const OptionDescriptor& opt_desc,
                   const std::string& opt_space,
                   const OptionalValue<SubnetID>& subnet_id,
                   const HostID& host_id);

    /// @brief Inserts multiple options into the database.
    ///
    /// @param stindex Index of a statement being executed.
    /// @param options_cfg An object holding a collection of options to be
    /// inserted into the database.
    /// @param host_id Host identifier retrieved using @c mysql_insert_id.
1177
    void addOptions(const StatementIndex& stindex,
1178
                    const ConstCfgOptionPtr& options_cfg,
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
                    const uint64_t host_id);

    /// @brief Creates collection of @ref Host objects with associated
    /// information such as IPv6 reservations and/or DHCP options.
    ///
    /// This method performs a query which returns host information from
    /// the 'hosts' table. The query may also use LEFT JOIN clause to
    /// retrieve information from other tables, e.g. ipv6_reservations,
    /// dhcp4_options and dhcp6_options.
    /// Whether IPv6 reservations and/or options are assigned to the
    /// @ref Host objects depends on the type of the exchange object.
    ///
    /// @param stindex Statement index.
    /// @param bind Pointer to an array of MySQL bindings.
    /// @param exchange Pointer to the exchange object used for the
    /// particular query.
    /// @param [out] result Reference to the collection of hosts returned.
    /// @param single A boolean value indicating if a single host is
    /// expected to be returned, or multiple hosts.
    void getHostCollection(StatementIndex stindex, PsqlBindArrayPtr bind,
                           boost::shared_ptr<PgSqlHostExchange> exchange,
                           ConstHostCollection& result, bool single) const;

    /// @brief Retrieves a host by subnet and client's unique identifier.
    ///
    /// This method is used by both PgSqlHostDataSource::get4 and
1205
    /// PgSqlHostDataSource::get6 methods.
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
    ///
    /// @param subnet_id Subnet identifier.
    /// @param identifier_type Identifier type.
    /// @param identifier_begin Pointer to a begining of a buffer containing
    /// an identifier.
    /// @param identifier_len Identifier length.
    /// @param stindex Statement index.
    /// @param exchange Pointer to the exchange object used for the
    /// particular query.
    ///
    /// @return Pointer to const instance of Host or null pointer if
    /// no host found.
    ConstHostPtr getHost(const SubnetID& subnet_id,
                         const Host::IdentifierType& identifier_type,
                         const uint8_t* identifier_begin,
                         const size_t identifier_len,
                         StatementIndex stindex,
                         boost::shared_ptr<PgSqlHostExchange> exchange) const;


    /// @brief Returns PostgreSQL schema version of the open database
    ///
    /// @return Version number stored in the database, as a pair of unsigned
    ///         integers. "first" is the major version number, "second" the
    ///         minor number.
    ///
    /// @throw isc::dhcp::DbOperationError An operation on the open database
    ///        has failed.
    std::pair<uint32_t, uint32_t> getVersion() const;

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

1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
    /// @brief Pointer to an object representing an exchange which can
    /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
    boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
    /// IPv6 reservations using a single query.
    boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to insert new IPv6 reservation.
    boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;

    /// @brief Pointer to an object representing an exchange which can
    /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
    /// or dhcp6_options table.
    boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;

    /// @brief MySQL connection
    PgSqlConnection conn_;

};

1263
1264
1265
1266
namespace {

/// @brief Prepared PosgreSQL statements used by the backend to insert and
/// retrieve reservation data from the database.
1267
PgSqlTaggedStatement tagged_statements[] = {
1268
    // PgSqlHostDataSourceImpl::INSERT_HOST
1269
    // Inserts a host into the 'hosts' table. Returns the inserted host id.
1270
    {8, 
1271
     { OID_BYTEA, OID_INT2,
1272
       OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
1273
       OID_VARCHAR, OID_VARCHAR },
1274
     "insert_host",
1275
     "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
1276
1277
1278
1279
     "  dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
     "  dhcp4_client_classes, dhcp6_client_classes) "
     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
    },
1280

1281
    //PgSqlHostDataSourceImpl::INSERT_V6_RESRV
1282
    // Inserts a single IPv6 reservation into 'reservations' table.
1283
    {5, 
1284
1285
1286
1287
1288
1289
     { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
     "insert_v6_resrv",
     "INSERT INTO ipv6_reservations(address, prefix_len, type, "
     "  dhcp6_iaid, host_id) "
     "VALUES ($1, $2, $3, $4, $5)"
    },
1290

1291
    // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
1292
1293
    // Inserts a single DHCPv4 option into 'dhcp4_options' table.
    // Using fixed scope_id = 3, which associates an option with host.
1294
    {6, 
1295
1296
1297
     { OID_INT2, OID_BYTEA, OID_TEXT,
       OID_VARCHAR, OID_BOOL, OID_INT8},
     "insert_v4_host_option",
1298
     "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
1299
1300
     "  persistent, host_id, scope_id) "
     "VALUES ($1, $2, $3, $4, $5, $6, 3)"
1301
    },
1302

1303
    // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
1304
1305
    // Inserts a single DHCPv6 option into 'dhcp6_options' table.
    // Using fixed scope_id = 3, which associates an option with host.
1306
    {6,
1307
1308
1309
     { OID_INT2, OID_BYTEA, OID_TEXT,
       OID_VARCHAR, OID_BOOL, OID_INT8},
     "insert_v6_host_option",
1310
     "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
1311
1312
     "  persistent, host_id, scope_id) "
     "VALUES ($1, $2, $3, $4, $5, $6, 3)"
1313
1314
    },

1315
    // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
1316
1317
1318
1319
    // 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.
1320
    {2, 
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
     { OID_BYTEA, OID_INT2 },
     "get_host_dhcpid",
     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
     "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
     "  h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
     "  o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
     "  o4.persistent, "
     "  o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
     "  o6.persistent, "
     "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
     "FROM hosts AS h "
     "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
     "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
     "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
     "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
     "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
    },
1338

1339
    // PgSqlHostDataSourceImpl::GET_HOST_ADDR
1340
1341
1342
    // 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.
1343
    {1,
1344
     { OID_INT8 }, "get_host_addr",
1345
     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1346
1347
1348
1349
1350
1351
1352
1353
     "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
     "  h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
     "  o.value, o.formatted_value, o.space, o.persistent "
     "FROM hosts AS h "
     "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
     "WHERE ipv4_address = $1 "
     "ORDER BY h.host_id, o.option_id"
    },
1354

1355
    //PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
1356
1357
1358
    // 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.
1359
    {3,
1360
     { OID_INT4, OID_INT2, OID_BYTEA },
1361
     "get_host_subid4_dhcpid",
1362
     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1363
     "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1364
     "  h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
1365
     "  o.value, o.formatted_value, o.space, o.persistent "
1366
1367
1368
1369
     "FROM hosts AS h "
     "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
     "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
     "   AND h.dhcp_identifier = $3 "
1370
1371
     "ORDER BY h.host_id, o.option_id"
    },
1372

1373
    //PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
1374
1375
1376
    // 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.
1377
    {3,
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
     { OID_INT4, OID_INT2, OID_BYTEA },
     "get_host_subid6_dhcpid",
     "SELECT h.host_id, h.dhcp_identifier, "
     "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
     "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
     "  h.dhcp4_client_classes, h.dhcp6_client_classes, "
     "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
     "  o.persistent, "
     "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
     "FROM hosts AS h "
     "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
     "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
     "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
     " AND h.dhcp_identifier = $3 "
     "ORDER BY h.host_id, o.option_id, r.reservation_id"
    },
1394

1395
    //PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
1396
1397
1398
1399
    // 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.
1400
    {2,
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
     { OID_INT4, OID_INT8 },
     "get_host_subid_addr",
     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
     "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
     "  h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
     "  o.value, o.formatted_value, o.space, o.persistent "
     "FROM hosts AS h "
     "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
     "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
     "ORDER BY h.host_id, o.option_id"
    },
1412

1413
    // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
1414
1415
1416
1417
1418
1419
    // 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.
1420
    {2, 
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
     { OID_VARCHAR, OID_INT2 },
     "get_host_prefix",
     "SELECT h.host_id, h.dhcp_identifier, "
     "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
     "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
     "  h.dhcp4_client_classes, h.dhcp6_client_classes, "
     "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
     "  o.persistent, "
     "  r.reservation_id, r.address, r.prefix_len, r.type, "
     "  r.dhcp6_iaid "
     "FROM hosts AS h "
     "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
     "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
     "WHERE h.host_id = "
     "  (SELECT host_id FROM ipv6_reservations "
     "   WHERE address = $1 AND prefix_len = $2) "
     "ORDER BY h.host_id, o.option_id, r.reservation_id"
    },
1439

1440
    //PgSqlHostDataSourceImpl::GET_VERSION
1441
    // Retrieves MySQL schema version.
1442
    {0, 
1443
1444
1445
1446
     { OID_NONE },
     "get_version",
     "SELECT version, minor FROM schema_version"
    },
1447
1448

    // Marks the end of the statements table.
1449
    {0, { 0 }, NULL, NULL}
1450
1451
};

1452
1453
}; // end anonymous namespace

1454
1455
1456
1457
1458
1459
1460
PgSqlHostDataSourceImpl::
PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
    : host_exchange_(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY)),
      host_ipv6_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY)),
      host_ipv46_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::
                                                     DHCP4_AND_DHCP6)),
      host_ipv6_reservation_exchange_(new PgSqlIPv6ReservationExchange()),
1461
      host_option_exchange_(new PgSqlOptionExchange()),
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
      conn_(parameters) {

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

    int i = 0;
    for( ; tagged_statements[i].text !=