1 // Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <asiolink/io_service.h>
10 #include <database/db_exceptions.h>
11 #include <dhcp/libdhcp++.h>
12 #include <dhcp/option.h>
13 #include <dhcp/option_definition.h>
14 #include <dhcp/option_space.h>
15 #include <dhcpsrv/cfg_db_access.h>
16 #include <dhcpsrv/cfg_option.h>
17 #include <dhcpsrv/cfgmgr.h>
18 #include <dhcpsrv/dhcpsrv_log.h>
19 #include <dhcpsrv/host_mgr.h>
20 #include <dhcpsrv/pgsql_host_data_source.h>
21 #include <dhcpsrv/timer_mgr.h>
22 #include <util/buffer.h>
23 #include <util/multi_threading_mgr.h>
24 #include <util/optional.h>
25 
26 #include <boost/algorithm/string/split.hpp>
27 #include <boost/algorithm/string/classification.hpp>
28 #include <boost/array.hpp>
29 #include <boost/pointer_cast.hpp>
30 #include <boost/static_assert.hpp>
31 
32 #include <stdint.h>
33 
34 #include <mutex>
35 #include <string>
36 
37 using namespace isc;
38 using namespace isc::asiolink;
39 using namespace isc::db;
40 using namespace isc::dhcp;
41 using namespace isc::util;
42 using namespace isc::data;
43 using namespace std;
44 
45 namespace {
46 
47 /// @brief Maximum length of option value.
48 /// The maximum size of the raw option data that may be read from the
49 /// database.
50 const size_t OPTION_VALUE_MAX_LEN = 4096;
51 
52 /// @brief Numeric value representing last supported identifier.
53 ///
54 /// This value is used to validate whether the identifier type stored in
55 /// a database is within bounds. of supported identifiers.
56 const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
57 
58 /// @brief Maximum length of DHCP identifier value.
59 const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
60 
61 /// @brief This class provides mechanisms for sending and retrieving
62 /// information from the 'hosts' table.
63 ///
64 /// This class is used to insert and retrieve entries from the 'hosts' table.
65 /// The queries used with this class do not retrieve IPv6 reservations or
66 /// options associated with a host to minimize impact on performance. Other
67 /// classes derived from @ref PgSqlHostExchange should be used to retrieve
68 /// information about IPv6 reservations and options.
69 ///
70 /// Database schema contains several unique indexes to guard against adding
71 /// multiple hosts for the same client identifier in a single subnet and for
72 /// adding multiple hosts with a reservation for the same IPv4 address in a
73 /// single subnet. The exceptions that have to be taken into account are
74 /// listed below:
75 /// - zero or null IPv4 address indicates that there is no reservation for the
76 ///   IPv4 address for the host,
77 /// - null subnet identifier (either IPv4 or IPv6) indicates that
78 ///   this subnet identifier must be ignored. Specifically, this is the case
79 ///   when host reservation is created for the DHCPv4 server, the IPv6 subnet id
80 ///   should be ignored. Conversely, when host reservation is created for the
81 ///   DHCPv6 server, the IPv4 subnet id should be ignored.
82 ///   NOTE! Zero is the "global" subnet id as Kea 1.5.0
83 ///
84 /// To exclude those special case values, the Postgres backend uses partial
85 /// indexes, i.e. the only values that are included in the index are those that
86 /// are non-zero and non-null.
87 class PgSqlHostExchange : public PgSqlExchange {
88 private:
89 
90     /// @brief Column numbers for each column in the hosts table.
91     /// These are used for both retrieving data and for looking up
92     /// column labels for logging.  Note that their numeric order
93     /// MUST match that of the column order in the hosts table.
94     static const int HOST_ID_COL = 0;
95     static const int DHCP_IDENTIFIER_COL = 1;
96     static const int DHCP_IDENTIFIER_TYPE_COL = 2;
97     static const int DHCP4_SUBNET_ID_COL = 3;
98     static const int DHCP6_SUBNET_ID_COL = 4;
99     static const int IPV4_ADDRESS_COL = 5;
100     static const int HOSTNAME_COL = 6;
101     static const int DHCP4_CLIENT_CLASSES_COL = 7;
102     static const int DHCP6_CLIENT_CLASSES_COL = 8;
103     static const int USER_CONTEXT_COL = 9;
104     static const int DHCP4_NEXT_SERVER_COL = 10;
105     static const int DHCP4_SERVER_HOSTNAME_COL = 11;
106     static const int DHCP4_BOOT_FILE_NAME_COL = 12;
107     static const int AUTH_KEY_COL = 13;
108     /// @brief Number of columns returned for SELECT queries sent by this class.
109     static const size_t HOST_COLUMNS = 14;
110 
111 public:
112 
113     /// @brief Constructor
114     ///
115     /// @param additional_columns_num This value is set by the derived classes
116     /// to indicate how many additional columns will be returned by SELECT
117     /// queries performed by the derived class. This constructor will allocate
118     /// resources for these columns, e.g. binding table, error indicators.
PgSqlHostExchange(const size_t additional_columns_num=0)119     PgSqlHostExchange(const size_t additional_columns_num = 0)
120         : PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
121         // Set the column names for use by this class. This only comprises
122         // names used by the PgSqlHostExchange class. Derived classes will
123         // need to set names for the columns they use.  Currently these are
124         // only used for logging purposes.
125         columns_[HOST_ID_COL] = "host_id";
126         columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
127         columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
128         columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
129         columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
130         columns_[IPV4_ADDRESS_COL] = "ipv4_address";
131         columns_[HOSTNAME_COL] = "hostname";
132         columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
133         columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
134         columns_[USER_CONTEXT_COL] = "user_context";
135         columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
136         columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
137         columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
138         columns_[AUTH_KEY_COL] = "auth_key";
139 
140         BOOST_STATIC_ASSERT(12 < HOST_COLUMNS);
141     };
142 
143     /// @brief Virtual destructor.
~PgSqlHostExchange()144     virtual ~PgSqlHostExchange() {
145     }
146 
147     /// @brief Reinitializes state information
148     ///
149     /// This function should be called in between statement executions.
150     /// Deriving classes should invoke this method as well as be reset
151     /// all of their own stateful values.
clear()152     virtual void clear() {
153         host_.reset();
154     };
155 
156     /// @brief Returns index of the first uninitialized column name.
157     ///
158     /// This method is called by the derived classes to determine which
159     /// column indexes are available for the derived classes within a
160     /// binding array, error array and column names. This method
161     /// determines the first available index by searching the first
162     /// empty value within the columns_ vector. Previously we relied on
163     /// the fixed values set for each class, but this was hard to maintain
164     /// when new columns were added to the SELECT queries. It required
165     /// modifying indexes in all derived classes.
166     ///
167     /// Derived classes must call this method in their constructors and
168     /// use returned value as an index for the first column used by the
169     /// derived class and increment this value for each subsequent column.
findAvailColumn() const170     size_t findAvailColumn() const {
171         std::vector<std::string>::const_iterator empty_column =
172             std::find(columns_.begin(), columns_.end(), std::string());
173         return (std::distance(columns_.begin(), empty_column));
174     }
175 
176     /// @brief Returns value of host id in the given row.
177     ///
178     /// This method is used to "look ahead" at the host_id in a row
179     /// without having to call retrieveHost()
getHostId(const PgSqlResult & r,int row)180     HostID getHostId(const PgSqlResult& r, int row) {
181         HostID host_id;
182         getColumnValue(r, row, HOST_ID_COL, host_id);
183         return (host_id);
184     }
185 
186     /// @brief Populate a bind array from a host
187     ///
188     /// Constructs a PsqlBindArray for sending data stored in a Host object
189     /// to the database.
190     ///
191     /// @param host Host object to be added to the database.
192     ///        None of the fields in the host reservation are modified -
193     ///        the host data is only read.
194     /// @param unique_ip boolean value indicating if multiple reservations for the
195     ///        same IP address are allowed (false) or not (true).
196     ///
197     /// @return pointer to newly constructed bind_array containing the
198     /// bound values extracted from host
199     ///
200     /// @throw DbOperationError if bind_array cannot be populated.
createBindForSend(const HostPtr & host,const bool unique_ip)201     PsqlBindArrayPtr createBindForSend(const HostPtr& host, const bool unique_ip) {
202         if (!host) {
203             isc_throw(BadValue, "createBindForSend:: host object is NULL");
204         }
205 
206         // Store the host to ensure bound values remain in scope
207         host_ = host;
208 
209         // Bind the host data to the array
210         PsqlBindArrayPtr bind_array(new PsqlBindArray());
211         try {
212             // host_id : is auto_incremented skip it
213 
214             // dhcp_identifier : BYTEA NOT NULL
215             bind_array->add(host->getIdentifier());
216 
217             // dhcp_identifier_type : SMALLINT NOT NULL
218             bind_array->add(host->getIdentifierType());
219 
220             // dhcp4_subnet_id : INT NULL
221             if (host->getIPv4SubnetID() == SUBNET_ID_UNUSED) {
222                 bind_array->addNull();
223             }
224             else {
225                 bind_array->add(host->getIPv4SubnetID());
226             }
227 
228             // dhcp6_subnet_id : INT NULL
229             if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
230                 bind_array->addNull();
231             }
232             else {
233                 bind_array->add(host->getIPv6SubnetID());
234             }
235 
236             // ipv4_address : BIGINT NULL
237             bind_array->add((host->getIPv4Reservation()));
238 
239             // hostname : VARCHAR(255) NULL
240             bind_array->add(host->getHostname());
241 
242             // dhcp4_client_classes : VARCHAR(255) NULL
243             // Override default separator to not include space after comma.
244             bind_array->addTempString(host->getClientClasses4().toText(","));
245 
246             // dhcp6_client_classes : VARCHAR(255) NULL
247             bind_array->addTempString(host->getClientClasses6().toText(","));
248 
249             // user_context: TEXT NULL
250             ConstElementPtr ctx = host->getContext();
251             if (ctx) {
252                 std::string user_context_ = ctx->str();
253                 bind_array->addTempString(user_context_);
254             } else {
255                 bind_array->addNull();
256             }
257 
258             // dhcp4_next_server : BIGINT NULL
259             bind_array->add((host->getNextServer()));
260 
261             // dhcp4_server_hostname : VARCHAR(64)
262             bind_array->add(host->getServerHostname());
263 
264             // dhcp4_boot_file_name : VARCHAR(128)
265             bind_array->add(host->getBootFileName());
266 
267             // add auth keys
268             std::string key = host->getKey().toText();
269             if (key.empty()) {
270                 bind_array->addNull();
271             } else {
272                 bind_array->add(key);
273             }
274 
275             // When checking whether the IP is unique we need to bind the IPv4 address
276             // at the end of the query as it has additional binding for the IPv4
277             // address.
278             if (unique_ip) {
279                 bind_array->add(host->getIPv4Reservation()); // ipv4_address
280                 bind_array->add(host->getIPv4SubnetID()); // subnet_id
281             }
282 
283 
284         } catch (const std::exception& ex) {
285             host_.reset();
286             isc_throw(DbOperationError,
287                       "Could not create bind array from Host: "
288                       << host->getHostname() << ", reason: " << ex.what());
289         }
290 
291         return (bind_array);
292     };
293 
294     /// @brief Processes one row of data fetched from a database.
295     ///
296     /// The processed data must contain host id, which uniquely identifies a
297     /// host. This method creates a host and inserts it to the hosts collection
298     /// only if the last inserted host has a different host id. This prevents
299     /// adding duplicated hosts to the collection, assuming that processed
300     /// rows are primarily ordered by host id column.
301     ///
302     /// This method must be overridden in the derived classes to also
303     /// retrieve IPv6 reservations and DHCP options associated with a host.
304     ///
305     /// @param [out] hosts Collection of hosts to which a new host created
306     ///        from the processed data should be inserted.
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)307     virtual void processRowData(ConstHostCollection& hosts,
308                                 const PgSqlResult& r, int row) {
309         // Peek at the host id , so we can skip it if we already have it
310         // This lets us avoid constructing a copy of host for each
311         // of its sub-rows (options, etc...)
312         HostID row_host_id = getHostId(r, row);
313 
314         // Add new host only if there are no hosts or the host id of the
315         // most recently added host is different than the host id of the
316         // currently processed host.
317         if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
318             HostPtr host = retrieveHost(r, row, row_host_id);
319             hosts.push_back(host);
320         }
321     }
322 
323     /// @brief Creates a Host object from a given row in a result set.
324     ///
325     /// @param r result set containing one or more rows from the hosts table
326     /// @param row index within the result set of the row to process
327     /// @param peeked_host_id if the caller has peeked ahead at the row's
328     /// host_id, it can be passed in here to avoid fetching it from the row
329     /// a second time.
330     ///
331     /// @return HostPtr to the newly created Host object
332     ///
333     /// @throw DbOperationError if the host cannot be created.
retrieveHost(const PgSqlResult & r,int row,const HostID & peeked_host_id=0)334     HostPtr retrieveHost(const PgSqlResult& r, int row,
335                          const HostID& peeked_host_id = 0) {
336 
337         // If the caller peeked ahead at the host_id use that, otherwise
338         // read it from the row.
339         HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
340 
341         // dhcp_identifier : BYTEA NOT NULL
342         uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
343         size_t identifier_len;
344         convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
345                              sizeof(identifier_value), identifier_len);
346 
347         // dhcp_identifier_type : SMALLINT NOT NULL
348         uint8_t type;
349         getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
350         if (type > MAX_IDENTIFIER_TYPE) {
351             isc_throw(BadValue, "invalid dhcp identifier type returned: "
352                       << static_cast<int>(type));
353         }
354 
355         Host::IdentifierType identifier_type =
356             static_cast<Host::IdentifierType>(type);
357 
358         // dhcp4_subnet_id : INT NULL
359         uint32_t subnet_id(SUBNET_ID_UNUSED);
360         if (!isColumnNull(r, row, DHCP4_SUBNET_ID_COL)) {
361             getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
362         }
363         SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
364 
365         // dhcp6_subnet_id : INT NULL
366         subnet_id = SUBNET_ID_UNUSED;
367         if (!isColumnNull(r, row, DHCP6_SUBNET_ID_COL)) {
368             getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
369         }
370         SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
371 
372         // ipv4_address : BIGINT NULL
373         uint32_t addr4(0);
374         if (!isColumnNull(r, row, IPV4_ADDRESS_COL)) {
375             getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
376         }
377         isc::asiolink::IOAddress ipv4_reservation(addr4);
378 
379         // hostname : VARCHAR(255) NULL
380         std::string hostname;
381         if (!isColumnNull(r, row, HOSTNAME_COL)) {
382             getColumnValue(r, row, HOSTNAME_COL, hostname);
383         }
384 
385         // dhcp4_client_classes : VARCHAR(255) NULL
386         std::string dhcp4_client_classes;
387         if (!isColumnNull(r, row, DHCP4_CLIENT_CLASSES_COL)) {
388             getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
389         }
390 
391         // dhcp6_client_classes : VARCHAR(255) NULL
392         std::string dhcp6_client_classes;
393         if (!isColumnNull(r, row, DHCP6_CLIENT_CLASSES_COL)) {
394             getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
395         }
396 
397         // user_context: TEXT
398         std::string user_context;
399         if (!isColumnNull(r, row, USER_CONTEXT_COL)) {
400             getColumnValue(r, row, USER_CONTEXT_COL, user_context);
401         }
402 
403         // dhcp4_next_server : BIGINT NULL
404         uint32_t dhcp4_next_server_as_uint32(0);
405         if (!isColumnNull(r, row, DHCP4_NEXT_SERVER_COL)) {
406             getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
407         }
408         isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
409 
410         // dhcp4_server_hostname : VARCHAR(64)
411         std::string dhcp4_server_hostname;
412         if (!isColumnNull(r, row, DHCP4_SERVER_HOSTNAME_COL)) {
413             getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
414         }
415 
416         // dhcp4_boot_file_name : VARCHAR(128)
417         std::string dhcp4_boot_file_name;
418         if (!isColumnNull(r, row, DHCP4_BOOT_FILE_NAME_COL)) {
419             getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
420         }
421 
422         // auth_key : VARCHAR(16)
423         std::string auth_key;
424         if (!isColumnNull(r, row, AUTH_KEY_COL)) {
425             getColumnValue(r, row, AUTH_KEY_COL, auth_key);
426         }
427 
428         // Finally, attempt to create the new host.
429         HostPtr host;
430         try {
431             host.reset(new Host(identifier_value, identifier_len,
432                                 identifier_type, dhcp4_subnet_id,
433                                 dhcp6_subnet_id, ipv4_reservation, hostname,
434                                 dhcp4_client_classes, dhcp6_client_classes,
435                                 dhcp4_next_server, dhcp4_server_hostname,
436                                 dhcp4_boot_file_name, AuthKey(auth_key)));
437 
438             // Set the user context if there is one.
439             if (!user_context.empty()) {
440                 try {
441                     ConstElementPtr ctx = Element::fromJSON(user_context);
442                     if (!ctx || (ctx->getType() != Element::map)) {
443                         isc_throw(BadValue, "user context '" << user_context
444                                   << "' is not a JSON map");
445                     }
446                     host->setContext(ctx);
447                 } catch (const isc::data::JSONError& ex) {
448                     isc_throw(BadValue, "user context '" << user_context
449                               << "' is invalid JSON: " << ex.what());
450                 }
451             }
452 
453             host->setHostId(host_id);
454         } catch (const isc::Exception& ex) {
455             isc_throw(DbOperationError, "Could not create host: " << ex.what());
456         }
457 
458         return(host);
459     };
460 
461 protected:
462     /// Pointer to Host object holding information to be inserted into
463     /// Hosts table.  This is used to retain scope.
464     HostPtr host_;
465 };
466 
467 /// @brief Extends base exchange class with ability to retrieve DHCP options
468 /// from the 'dhcp4_options' and 'dhcp6_options' tables.
469 ///
470 /// This class provides means to retrieve both DHCPv4 and DHCPv6 options
471 /// along with the host information. It is not used to retrieve IPv6
472 /// reservations. The following types of queries are supported:
473 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
474 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
475 /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
476 class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
477 private:
478 
479     /// @brief Number of columns holding DHCPv4  or DHCPv6 option information.
480     static const size_t OPTION_COLUMNS = 7;
481 
482     /// @brief Receives DHCPv4 or DHCPv6 options information from the
483     /// dhcp4_options or dhcp6_options tables respectively.
484     ///
485     /// The PgSqlHostWithOptionsExchange class holds two respective instances
486     /// of this class, one for receiving DHCPv4 options, one for receiving
487     /// DHCPv6 options.
488     ///
489     /// The following are the basic functions of this class:
490     /// - bind class members to specific columns in PgSQL binding tables,
491     /// - set DHCP options specific column names,
492     /// - create instances of options retrieved from the database.
493     ///
494     /// The reason for isolating those functions in a separate C++ class is
495     /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
496     class OptionProcessor {
497     public:
498 
499         /// @brief Constructor.
500         ///
501         /// @param universe V4 or V6. The type of the options' instances
502         /// created by this class depends on this parameter.
503         /// @param start_column Index of the first column to be used by this
504         /// class.
OptionProcessor(const Option::Universe & universe,const size_t start_column)505         OptionProcessor(const Option::Universe& universe,
506                         const size_t start_column)
507         : universe_(universe), start_column_(start_column),
508           option_id_index_(start_column), code_index_(start_column_ + 1),
509           value_index_(start_column_ + 2),
510           formatted_value_index_(start_column_ + 3),
511           space_index_(start_column_ + 4),
512           persistent_index_(start_column_ + 5),
513           user_context_index_(start_column_ + 6),
514           most_recent_option_id_(0) {
515         }
516 
517         /// @brief Reinitializes state information
518         ///
519         /// This function should be called prior to processing a fetched
520         /// set of options.
clear()521         void clear() {
522             most_recent_option_id_ = 0;
523         }
524 
525         /// @brief Creates instance of the currently processed option.
526         ///
527         /// This method detects if the currently processed option is a new
528         /// instance. It makes its determination by comparing the identifier
529         /// of the currently processed option, with the most recently processed
530         /// option. If the current value is greater than the id of the recently
531         /// processed option it is assumed that the processed row holds new
532         /// option information. In such case the option instance is created and
533         /// inserted into the configuration passed as argument.
534         ///
535         /// This logic is necessary to deal with result sets made from multiple
536         /// left joins which contain duplicated data.  For instance queries
537         /// returning both v4 and v6 options for a host would generate result
538         /// sets similar to this:
539         /// @code
540         ///
541         /// row 0: host-1  v4-opt-1  v6-opt-1
542         /// row 1: host-1  v4-opt-1  v6-opt-2
543         /// row 2: host-1  v4-opt-1  v6-opt-3
544         /// row 4: host-1  v4-opt-2  v6-opt-1
545         /// row 5: host-1  v4-opt-2  v6-opt-2
546         /// row 6: host-1  v4-opt-2  v6-opt-3
547         /// row 7: host-2  v4-opt-1  v6-opt-1
548         /// row 8: host-2  v4-opt-2  v6-opt-1
549         ///  :
550         /// @endcode
551         ///
552         /// @param cfg Pointer to the configuration object into which new
553         /// option instances should be inserted.
554         /// @param r result set containing one or more rows from a dhcp
555         /// options table.
556         /// @param row index within the result set of the row to process
retrieveOption(const CfgOptionPtr & cfg,const PgSqlResult & r,int row)557         void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
558                             int row) {
559             // If the option id on this row is NULL, then there's no
560             // option of this type (4/6) on this row to fetch, so bail.
561             if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
562                 return;
563             }
564 
565             // option_id: INT
566             uint64_t option_id;
567             PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
568 
569             // The row option id must be greater than id if the most recent
570             // option because they are ordered by option id. Otherwise
571             // we assume that we have already processed this option.
572             if (most_recent_option_id_ >= option_id) {
573                 return;
574             }
575 
576             // Remember current option id as the most recent processed one. We
577             // will be comparing it with option ids in subsequent rows.
578             most_recent_option_id_ = option_id;
579 
580             // code: SMALLINT NOT NULL
581             uint16_t code;
582             PgSqlExchange::getColumnValue(r, row, code_index_, code);
583 
584             // value: BYTEA
585             uint8_t value[OPTION_VALUE_MAX_LEN];
586             size_t value_len(0);
587             if (!isColumnNull(r, row, value_index_)) {
588                 PgSqlExchange::convertFromBytea(r, row, value_index_, value,
589                                                 sizeof(value), value_len);
590             }
591 
592             // formatted_value: TEXT
593             std::string formatted_value;
594             if (!isColumnNull(r, row, formatted_value_index_)) {
595                 PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
596                                               formatted_value);
597             }
598 
599             // space: VARCHAR(128)
600             std::string space;
601             if (!isColumnNull(r, row, space_index_)) {
602                 PgSqlExchange::getColumnValue(r, row, space_index_, space);
603             }
604 
605             // If empty or null space provided, use a default top level space.
606             if (space.empty()) {
607                 space = (universe_ == Option::V4 ?
608                          DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
609             }
610 
611             // persistent: BOOL default false
612             bool persistent;
613             PgSqlExchange::getColumnValue(r, row, persistent_index_,
614                                           persistent);
615 
616             // user_context: TEXT
617             std::string user_context;
618             if (!isColumnNull(r, row, user_context_index_)) {
619                 PgSqlExchange::getColumnValue(r, row, user_context_index_,
620                                               user_context);
621             }
622 
623             // Options are held in a binary or textual format in the database.
624             // This is similar to having an option specified in a server
625             // configuration file. Such option is converted to appropriate C++
626             // class, using option definition. Thus, we need to find the
627             // option definition for this option code and option space.
628 
629             // If the option space is a standard DHCPv4 or DHCPv6 option space,
630             // this is most likely a standard option, for which we have a
631             // definition created within libdhcp++.
632             OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
633 
634             // Otherwise, we may check if this an option encapsulated within the
635             // vendor space.
636             if (!def && (space != DHCP4_OPTION_SPACE) &&
637                 (space != DHCP6_OPTION_SPACE)) {
638                 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
639                 if (vendor_id > 0) {
640                     def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
641                                                       code);
642                 }
643             }
644 
645             // In all other cases, we use runtime option definitions, which
646             // should be also registered within the libdhcp++.
647             if (!def) {
648                 def = LibDHCP::getRuntimeOptionDef(space, code);
649             }
650 
651             OptionPtr option;
652 
653             if (!def) {
654                 // If no definition found, we use generic option type.
655                 OptionBuffer buf(value, value + value_len);
656                 option.reset(new Option(universe_, code, buf.begin(),
657                                         buf.end()));
658             } else {
659                 // The option value may be specified in textual or binary format
660                 // in the database. If formatted_value is empty, the binary
661                 // format is used. Depending on the format we use a different
662                 // variant of the optionFactory function.
663                 if (formatted_value.empty()) {
664                     OptionBuffer buf(value, value + value_len);
665                     option = def->optionFactory(universe_, code, buf.begin(),
666                                                 buf.end());
667                 } else {
668                     // Spit the value specified in comma separated values
669                     // format.
670                     std::vector<std::string> split_vec;
671                     boost::split(split_vec, formatted_value,
672                                  boost::is_any_of(","));
673                     option = def->optionFactory(universe_, code, split_vec);
674                 }
675             }
676 
677             OptionDescriptor desc(option, persistent, formatted_value);
678 
679             // Set the user context if there is one into the option descriptor.
680             if (!user_context.empty()) {
681                 try {
682                     ConstElementPtr ctx = Element::fromJSON(user_context);
683                     if (!ctx || (ctx->getType() != Element::map)) {
684                         isc_throw(BadValue, "user context '" << user_context
685                                   << "' is no a JSON map");
686                     }
687                     desc.setContext(ctx);
688                 } catch (const isc::data::JSONError& ex) {
689                     isc_throw(BadValue, "user context '" << user_context
690                               << "' is invalid JSON: " << ex.what());
691                 }
692             }
693 
694             cfg->add(desc, space);
695         }
696 
697         /// @brief Specify column names.
698         ///
699         /// @param [out] columns Reference to a vector holding names of option
700         /// specific columns.
setColumnNames(std::vector<std::string> & columns)701         void setColumnNames(std::vector<std::string>& columns) {
702             columns[option_id_index_] = "option_id";
703             columns[code_index_] = "code";
704             columns[value_index_] = "value";
705             columns[formatted_value_index_] = "formatted_value";
706             columns[space_index_] = "space";
707             columns[persistent_index_] = "persistent";
708             columns[user_context_index_] = "user_context";
709         }
710 
711     private:
712         /// @brief Universe: V4 or V6.
713         Option::Universe universe_;
714 
715         /// @brief Index of first column used by this class.
716         size_t start_column_;
717 
718         //@}
719 
720         /// @name Indexes of the specific columns
721         //@{
722         /// @brief Option id
723         size_t option_id_index_;
724 
725         /// @brief Code
726         size_t code_index_;
727 
728         /// @brief Value
729         size_t value_index_;
730 
731         /// @brief Formatted value
732         size_t formatted_value_index_;
733 
734         /// @brief Space
735         size_t space_index_;
736 
737         /// @brief Persistent
738         size_t persistent_index_;
739         //@}
740 
741         /// @brief User context
742         size_t user_context_index_;
743 
744         /// @brief Option id for last processed row.
745         uint64_t most_recent_option_id_;
746     };
747 
748     /// @brief Pointer to the @ref OptionProcessor class.
749     typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
750 
751 public:
752 
753     /// @brief DHCP option types to be fetched from the database.
754     ///
755     /// Supported types are:
756     /// - Only DHCPv4 options,
757     /// - Only DHCPv6 options,
758     /// - Both DHCPv4 and DHCPv6 options.
759     enum FetchedOptions {
760         DHCP4_ONLY,
761         DHCP6_ONLY,
762         DHCP4_AND_DHCP6
763     };
764 
765     /// @brief Constructor.
766     ///
767     /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
768     /// be fetched from the database for a host.
769     /// @param additional_columns_num Number of additional columns for which
770     /// resources should be allocated, e.g. binding table, column names etc.
771     /// This parameter should be set to a non zero value by derived classes to
772     /// allocate resources for the columns supported by derived classes.
PgSqlHostWithOptionsExchange(const FetchedOptions & fetched_options,const size_t additional_columns_num=0)773     PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
774                                  const size_t additional_columns_num = 0)
775         : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
776                             + additional_columns_num),
777           opt_proc4_(), opt_proc6_() {
778 
779         // Create option processor for DHCPv4 options, if required.
780         if ((fetched_options == DHCP4_ONLY) ||
781             (fetched_options == DHCP4_AND_DHCP6)) {
782             opt_proc4_.reset(new OptionProcessor(Option::V4,
783                                                  findAvailColumn()));
784             opt_proc4_->setColumnNames(columns_);
785         }
786 
787         // Create option processor for DHCPv6 options, if required.
788         if ((fetched_options == DHCP6_ONLY) ||
789             (fetched_options == DHCP4_AND_DHCP6)) {
790             opt_proc6_.reset(new OptionProcessor(Option::V6,
791                                                  findAvailColumn()));
792             opt_proc6_->setColumnNames(columns_);
793         }
794     }
795 
796     /// @brief Clears state information
797     ///
798     /// This function should be called in between statement executions.
799     /// Deriving classes should invoke this method as well as be reset
800     /// all of their own stateful values.
clear()801     virtual void clear() {
802         PgSqlHostExchange::clear();
803         if (opt_proc4_) {
804             opt_proc4_->clear();
805         }
806 
807         if (opt_proc6_) {
808             opt_proc6_->clear();
809         }
810     }
811 
812     /// @brief Processes the current row.
813     ///
814     /// The fetched row includes both host information and DHCP option
815     /// information. Because the SELECT queries use one or more LEFT JOIN
816     /// clauses, the result set may contain duplicated host or options
817     /// entries. This method detects duplicated information and discards such
818     /// entries.
819     ///
820     /// @param [out] hosts Container holding parsed hosts and options.
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)821     virtual void processRowData(ConstHostCollection& hosts,
822                                 const PgSqlResult& r, int row) {
823         HostPtr current_host;
824         if (hosts.empty()) {
825             // Must be the first one, fetch it.
826             current_host = retrieveHost(r, row);
827             hosts.push_back(current_host);
828         } else {
829             // Peek at the host id so we can skip it if we already have
830             // this host.  This lets us avoid retrieving the host needlessly
831             // for each of its sub-rows (options, etc...).
832             HostID row_host_id = getHostId(r, row);
833             current_host = boost::const_pointer_cast<Host>(hosts.back());
834 
835             // if the row's host id is greater than the one we've been
836             // working on we're starting a new host, so fetch it.
837             if (row_host_id > current_host->getHostId()) {
838                 current_host = retrieveHost(r, row, row_host_id);
839                 hosts.push_back(current_host);
840             }
841         }
842 
843         // Parse DHCPv4 options if required to do so.
844         if (opt_proc4_) {
845             CfgOptionPtr cfg = current_host->getCfgOption4();
846             opt_proc4_->retrieveOption(cfg, r, row);
847         }
848 
849         // Parse DHCPv6 options if required to do so.
850         if (opt_proc6_) {
851             CfgOptionPtr cfg = current_host->getCfgOption6();
852             opt_proc6_->retrieveOption(cfg, r, row);
853         }
854     }
855 
856 private:
857 
858     /// @brief Returns a number of columns required to retrieve option data.
859     ///
860     /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
861     /// DHCPv6 a different number of columns is required in the binding array.
862     /// This method returns the number of required columns, according to the
863     /// value of @c fetched_columns passed in the constructor.
864     ///
865     /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
866     /// both types of options should be retrieved.
867     ///
868     /// @return Number of required columns.
getRequiredColumnsNum(const FetchedOptions & fetched_options)869     static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
870         return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
871                 OPTION_COLUMNS);
872     }
873 
874     /// @brief Pointer to DHCPv4 options processor.
875     ///
876     /// If this object is NULL, the DHCPv4 options are not fetched.
877     OptionProcessorPtr opt_proc4_;
878 
879     /// @brief Pointer to DHCPv6 options processor.
880     ///
881     /// If this object is NULL, the DHCPv6 options are not fetched.
882     OptionProcessorPtr opt_proc6_;
883 };
884 
885 /// @brief This class provides mechanisms for sending and retrieving
886 /// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
887 ///
888 /// This class extends the @ref PgSqlHostWithOptionsExchange class with the
889 /// mechanisms to retrieve IPv6 reservations. This class is used in situations
890 /// when it is desired to retrieve DHCPv6 specific information about the host
891 /// (DHCPv6 options and reservations), or entire information about the host
892 /// (DHCPv4 options, DHCPv6 options and reservations). The following are the
893 /// queries used with this class:
894 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
895 ///   LEFT JOIN ipv6_reservations ...
896 /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
897 class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
898 private:
899 
900     /// @brief Number of columns holding IPv6 reservation information.
901     static const size_t RESERVATION_COLUMNS = 5;
902 
903 public:
904 
905     /// @brief Constructor.
906     ///
907     /// Apart from initializing the base class data structures it also
908     /// initializes values representing IPv6 reservation information.
PgSqlHostIPv6Exchange(const FetchedOptions & fetched_options)909     PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
910         : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
911           reservation_id_index_(findAvailColumn()),
912           address_index_(reservation_id_index_ + 1),
913           prefix_len_index_(reservation_id_index_ + 2),
914           type_index_(reservation_id_index_ + 3),
915           iaid_index_(reservation_id_index_ + 4),
916           most_recent_reservation_id_(0) {
917 
918         // Provide names of additional columns returned by the queries.
919         columns_[reservation_id_index_] = "reservation_id";
920         columns_[address_index_] = "address";
921         columns_[prefix_len_index_] = "prefix_len";
922         columns_[type_index_] = "type";
923         columns_[iaid_index_] = "dhcp6_iaid";
924 
925         BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
926     }
927 
928     /// @brief Reinitializes state information
929     ///
930     /// This function should be called in between statement executions.
931     /// Deriving classes should invoke this method as well as be reset
932     /// all of their own stateful values.
clear()933     void clear() {
934         PgSqlHostWithOptionsExchange::clear();
935         most_recent_reservation_id_ = 0;
936     }
937 
938     /// @brief Returns reservation id from the row.
939     ///
940     /// @return Reservation id or 0 if no reservation data is fetched.
getReservationId(const PgSqlResult & r,int row) const941     uint64_t getReservationId(const PgSqlResult& r, int row) const {
942         uint64_t resv_id = 0;
943         if (!isColumnNull(r, row, reservation_id_index_)) {
944             getColumnValue(r, row, reservation_id_index_, resv_id);
945         }
946 
947         return (resv_id);
948     };
949 
950     /// @brief Creates IPv6 reservation from the data contained in the
951     /// currently processed row.
952     ///
953     /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
retrieveReservation(const PgSqlResult & r,int row)954     IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
955 
956         // type: SMALLINT NOT NULL
957         uint16_t tmp;
958         getColumnValue(r, row, type_index_, tmp);
959 
960         // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
961         IPv6Resrv::Type resv_type;
962         switch (tmp) {
963         case 0:
964             resv_type = IPv6Resrv::TYPE_NA;
965             break;
966 
967         case 2:
968             resv_type = IPv6Resrv::TYPE_PD;
969             break;
970 
971         default:
972             isc_throw(BadValue,
973                       "invalid IPv6 reservation type returned: "
974                       << tmp << ". Only 0 or 2 are allowed.");
975         }
976 
977         // address VARCHAR(39) NOT NULL
978         isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
979 
980         // prefix_len: SMALLINT NOT NULL
981         uint16_t prefix_len;
982         getColumnValue(r, row, prefix_len_index_, prefix_len);
983 
984         // @todo once we support populating iaid
985         // iaid: INT
986         // int iaid;
987         // getColumnValue(r, row, iaid_index_, iaid);
988 
989         // Create the reservation.
990         IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
991         return (reservation);
992     };
993 
994     /// @brief Processes one row of data fetched from a database.
995     ///
996     /// The processed data must contain host id, which uniquely identifies a
997     /// host. This method creates a host and inserts it to the hosts collection
998     /// only if the last inserted host has a different host id. This prevents
999     /// adding duplicated hosts to the collection, assuming that processed
1000     /// rows are primarily ordered by host id column.
1001     ///
1002     /// Depending on the value of the @c fetched_options specified in the
1003     /// constructor, this method also parses options returned as a result
1004     /// of SELECT queries.
1005     ///
1006     /// For any returned row which contains IPv6 reservation information it
1007     /// checks if the reservation is not a duplicate of previously parsed
1008     /// reservation and appends the IPv6Resrv object into the host object
1009     /// if the parsed row contains new reservation information.
1010     ///
1011     /// @param [out] hosts Collection of hosts to which a new host created
1012     ///        from the processed data should be inserted.
1013     /// @param r result set containing one or more rows of fetched data
1014     /// @param row index within the result set of the row to process
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)1015     virtual void processRowData(ConstHostCollection& hosts,
1016                                 const PgSqlResult& r, int row) {
1017         // Call parent class to fetch host information and options.
1018         PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
1019 
1020         // Shouldn't happen but just in case
1021         if (hosts.empty()) {
1022             isc_throw(Unexpected, "no host information while retrieving"
1023                       " IPv6 reservation");
1024         }
1025 
1026         // If we have reservation id we haven't seen yet, retrieve the
1027         // the reservation, adding it to the current host
1028         uint64_t reservation_id = getReservationId(r, row);
1029         if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
1030             HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
1031             host->addReservation(retrieveReservation(r, row));
1032             most_recent_reservation_id_ = reservation_id;
1033         }
1034     }
1035 
1036 private:
1037     /// @name Indexes of columns holding information about IPv6 reservations.
1038     //@{
1039     /// @brief Index of reservation_id column.
1040     size_t reservation_id_index_;
1041 
1042     /// @brief Index of address column.
1043     size_t address_index_;
1044 
1045     /// @brief Index of prefix_len column.
1046     size_t prefix_len_index_;
1047 
1048     /// @brief Index of type column.
1049     size_t type_index_;
1050 
1051     /// @brief Index of IAID column.
1052     size_t iaid_index_;
1053 
1054     //@}
1055 
1056     /// @brief Reservation id for last processed row.
1057     uint64_t most_recent_reservation_id_;
1058 };
1059 
1060 /// @brief This class is used for storing IPv6 reservations in a PgSQL database.
1061 ///
1062 /// This class is only used to insert IPv6 reservations into the
1063 /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
1064 /// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
1065 /// used instead.
1066 ///
1067 /// When a new IPv6 reservation is inserted into the database, an appropriate
1068 /// host must be defined in the hosts table. An attempt to insert IPv6
1069 /// reservation for non-existing host will result in failure.
1070 class PgSqlIPv6ReservationExchange : public PgSqlExchange {
1071 private:
1072 
1073     /// @brief Set number of columns for ipv6_reservation table.
1074     static const size_t RESRV_COLUMNS = 6;
1075 
1076 public:
1077 
1078     /// @brief Constructor
1079     ///
1080     /// Initialize class members representing a single IPv6 reservation.
PgSqlIPv6ReservationExchange()1081     PgSqlIPv6ReservationExchange()
1082         : PgSqlExchange(RESRV_COLUMNS),
1083           resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
1084         // Set the column names (for error messages)
1085         columns_[0] = "host_id";
1086         columns_[1] = "address";
1087         columns_[2] = "prefix_len";
1088         columns_[3] = "type";
1089         columns_[4] = "dhcp6_iaid";
1090         BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
1091     }
1092 
1093     /// @brief Populate a bind array representing an IPv6 reservation
1094     ///
1095     /// Constructs a PsqlBindArray for an IPv6 reservation to the database.
1096     ///
1097     /// @param resv The IPv6 reservation to be added to the database.
1098     ///        None of the fields in the reservation are modified -
1099     /// @param host_id ID of the host to which this reservation belongs.
1100     /// @param unique_ip boolean value indicating if multiple reservations for the
1101     ///        same IP address are allowed (false) or not (true).
1102     ///
1103     /// @return pointer to newly constructed bind_array containing the
1104     /// bound values extracted the IPv6 reservation
1105     ///
1106     /// @throw DbOperationError if bind_array cannot be populated.
createBindForSend(const IPv6Resrv & resv,const HostID & host_id,const bool unique_ip)1107     PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
1108                                        const HostID& host_id,
1109                                        const bool unique_ip) {
1110         // Store the values to ensure they remain valid.
1111         // Technically we don't need this, as currently all the values
1112         // are converted to strings and stored by the bind array.
1113         resv_ = resv;
1114 
1115         PsqlBindArrayPtr bind_array(new PsqlBindArray());
1116 
1117         try {
1118             // address VARCHAR(39) NOT NULL
1119             bind_array->add(resv.getPrefix());
1120 
1121             // prefix_len: SMALLINT NOT NULL
1122             bind_array->add(resv.getPrefixLen());
1123 
1124             // type: SMALLINT NOT NULL
1125             // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
1126             uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
1127             bind_array->add(type);
1128 
1129             // dhcp6_iaid: INT UNSIGNED
1130             /// @todo: We don't support iaid in the IPv6Resrv yet.
1131             bind_array->addNull();
1132 
1133             // host_id: BIGINT NOT NULL
1134             bind_array->add(host_id);
1135 
1136             // When checking whether the IP is unique we need to bind the IPv6 address
1137             // and prefix length at the end of the query as it has additional binding
1138             // for the IPv6 address and prefix length.
1139             if (unique_ip) {
1140                 bind_array->add(resv.getPrefix()); // address
1141                 bind_array->add(resv.getPrefixLen()); // prefix_len
1142             }
1143         } catch (const std::exception& ex) {
1144             isc_throw(DbOperationError,
1145                       "Could not create bind array from IPv6 Reservation: "
1146                       << resv_.toText() << ", reason: " << ex.what());
1147         }
1148 
1149         return (bind_array);
1150     }
1151 
1152 private:
1153     /// @brief Object holding reservation being sent to the database.
1154     IPv6Resrv resv_;
1155 };
1156 
1157 /// @brief This class is used for inserting options into a database.
1158 ///
1159 /// This class supports inserting both DHCPv4 and DHCPv6 options.
1160 class PgSqlOptionExchange : public PgSqlExchange {
1161 private:
1162 
1163     static const int OPTION_ID_COL = 0;
1164     static const int CODE_COL = 1;
1165     static const int VALUE_COL = 2;
1166     static const int FORMATTED_VALUE_COL = 3;
1167     static const int SPACE_COL = 4;
1168     static const int PERSISTENT_COL = 5;
1169     static const int USER_CONTEXT_COL = 6;
1170     static const int DHCP_CLIENT_CLASS_COL = 7;
1171     static const int DHCP_SUBNET_ID_COL = 8;
1172     static const int HOST_ID_COL = 9;
1173     static const int SCOPE_ID_COL = 10;
1174 
1175     /// @brief Number of columns in the tables holding options.
1176     static const size_t OPTION_COLUMNS = 11;
1177 
1178 public:
1179 
1180     /// @brief Constructor.
PgSqlOptionExchange()1181     PgSqlOptionExchange()
1182         : PgSqlExchange(OPTION_COLUMNS), value_(),
1183           value_len_(0), option_() {
1184         columns_[OPTION_ID_COL] = "option_id";
1185         columns_[CODE_COL] = "code";
1186         columns_[VALUE_COL] = "value";
1187         columns_[FORMATTED_VALUE_COL] = "formatted_value";
1188         columns_[SPACE_COL] = "space";
1189         columns_[PERSISTENT_COL] = "persistent";
1190         columns_[USER_CONTEXT_COL] = "user_context";
1191         columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
1192         columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
1193         columns_[HOST_ID_COL] = "host_id";
1194         columns_[SCOPE_ID_COL] = "scope_id";
1195 
1196         BOOST_STATIC_ASSERT(10 < OPTION_COLUMNS);
1197     }
1198 
1199     /// @brief Creates binding array to insert option data into database.
1200     ///
1201     /// @param opt_desc option descriptor of the option to write
1202     /// @param opt_space name of the option space to which the option belongs
1203     /// @param host_id host id of the host to which the option belongs
1204     ///
1205     /// @return pointer to newly constructed bind_array containing the
1206     /// bound values extracted from host
createBindForSend(const OptionDescriptor & opt_desc,const std::string & opt_space,const HostID & host_id)1207     PsqlBindArrayPtr createBindForSend(const OptionDescriptor& opt_desc,
1208                                        const std::string& opt_space,
1209                                        const HostID& host_id) {
1210         // Hold pointer to the option to make sure it remains valid until
1211         // we complete a query.
1212         option_ = opt_desc.option_;
1213 
1214         // Create the bind-array
1215         PsqlBindArrayPtr bind_array(new PsqlBindArray());
1216 
1217         try {
1218             // option_id: is auto_incremented so skip it
1219 
1220             // code: SMALLINT UNSIGNED NOT NULL
1221             bind_array->add(option_->getType());
1222 
1223             // value: BYTEA NULL
1224             if (opt_desc.formatted_value_.empty() &&
1225                 (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
1226                 // The formatted_value is empty and the option value is
1227                 // non-empty so we need to prepare on-wire format for the
1228                 // option and store it in the database as a BYTEA.
1229                 OutputBuffer buf(opt_desc.option_->len());
1230                 opt_desc.option_->pack(buf);
1231                 const char* buf_ptr = static_cast<const char*>(buf.getData());
1232                 value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
1233                               buf_ptr + buf.getLength());
1234                 value_len_ = value_.size();
1235                 bind_array->add(value_);
1236             } else {
1237                 // No value or formatted_value specified. In this case, the
1238                 // value BYTEA should be NULL.
1239                 bind_array->addNull(PsqlBindArray::BINARY_FMT);
1240             }
1241 
1242             // formatted_value: TEXT NULL,
1243             if (!opt_desc.formatted_value_.empty()) {
1244                 bind_array->addTempString(opt_desc.formatted_value_);
1245             } else {
1246                 bind_array->addNull();
1247             }
1248 
1249             // space: VARCHAR(128) NULL
1250             if (!opt_space.empty()) {
1251                 bind_array->addTempString(opt_space);
1252             } else {
1253                 bind_array->addNull();
1254             }
1255 
1256             // persistent: BOOLEAN DEFAULT false
1257             bind_array->add(opt_desc.persistent_);
1258 
1259             // user_context: TEXT NULL,
1260             ConstElementPtr ctx = opt_desc.getContext();
1261             if (ctx) {
1262                 std::string user_context_ = ctx->str();
1263                 bind_array->addTempString(user_context_);
1264             } else {
1265                 bind_array->addNull();
1266             }
1267 
1268             // host_id: INT NULL
1269             if (!host_id) {
1270                 isc_throw(BadValue, "host_id cannot be null");
1271             }
1272             bind_array->add(host_id);
1273 
1274         } catch (const std::exception& ex) {
1275             isc_throw(DbOperationError,
1276                       "Could not create bind array for inserting DHCP "
1277                       "host option: " << option_->toText() << ", reason: "
1278                       << ex.what());
1279         }
1280 
1281         return (bind_array);
1282     }
1283 
1284 private:
1285 
1286     /// @brief Option value as binary.
1287     std::vector<uint8_t> value_;
1288 
1289     /// @brief Option value length.
1290     size_t value_len_;
1291 
1292     /// @brief Pointer to currently parsed option.
1293     OptionPtr option_;
1294 };
1295 
1296 }  // namespace
1297 
1298 namespace isc {
1299 namespace dhcp {
1300 
1301 /// @brief PostgreSQL Host Context
1302 ///
1303 /// This class stores the thread context for the manager pool.
1304 /// The class is needed by all get/update/delete functions which must use one
1305 /// or more exchanges to perform database operations.
1306 /// Each context provides a set of such exchanges for each thread.
1307 /// The context instances are lazy initialized by the requesting thread by using
1308 /// the manager's createContext function and are destroyed when the manager's
1309 /// pool instance is destroyed.
1310 class PgSqlHostContext {
1311 public:
1312 
1313     /// @brief Constructor
1314     ///
1315     /// @param parameters See PgSqlHostMgr constructor.
1316     /// @param io_service_accessor The IOService accessor function.
1317     /// @param db_reconnect_callback The connection recovery callback.
1318     PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
1319                      IOServiceAccessorPtr io_service_accessor,
1320                      db::DbCallback db_reconnect_callback);
1321 
1322     /// The exchange objects are used for transfer of data to/from the database.
1323     /// They are pointed-to objects as the contents may change in "const" calls,
1324     /// while the rest of this object does not.  (At alternative would be to
1325     /// declare them as "mutable".)
1326 
1327     /// @brief Pointer to the object representing an exchange which
1328     /// can be used to retrieve hosts and DHCPv4 options.
1329     boost::shared_ptr<PgSqlHostWithOptionsExchange> host_ipv4_exchange_;
1330 
1331     /// @brief Pointer to an object representing an exchange which can
1332     /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
1333     boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
1334 
1335     /// @brief Pointer to an object representing an exchange which can
1336     /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
1337     /// IPv6 reservations using a single query.
1338     boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
1339 
1340     /// @brief Pointer to an object representing an exchange which can
1341     /// be used to insert new IPv6 reservation.
1342     boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
1343 
1344     /// @brief Pointer to an object representing an exchange which can
1345     /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
1346     /// or dhcp6_options table.
1347     boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
1348 
1349     /// @brief PostgreSQL connection
1350     PgSqlConnection conn_;
1351 
1352     /// @brief Indicates if the database is opened in read only mode.
1353     bool is_readonly_;
1354 };
1355 
1356 /// @brief PostgreSQL Host Context Pool
1357 ///
1358 /// This class provides a pool of contexts.
1359 /// The manager will use this class to handle available contexts.
1360 /// There is only one ContextPool per manager per back-end, which is created
1361 /// and destroyed by the respective manager factory class.
1362 class PgSqlHostContextPool {
1363 public:
1364 
1365     /// @brief The vector of available contexts.
1366     std::vector<PgSqlHostContextPtr> pool_;
1367 
1368     /// @brief The mutex to protect pool access.
1369     std::mutex mutex_;
1370 };
1371 
1372 /// @brief Type of pointers to context pools.
1373 typedef boost::shared_ptr<PgSqlHostContextPool> PgSqlHostContextPoolPtr;
1374 
1375 /// @brief Implementation of the @ref PgSqlHostDataSource.
1376 class PgSqlHostDataSourceImpl {
1377 public:
1378 
1379     /// @brief Statement Tags
1380     ///
1381     /// The contents of the enum are indexes into the list of SQL statements.
1382     /// It is assumed that the order is such that the indices of statements
1383     /// reading the database are less than those of statements modifying the
1384     /// database.
1385     /// @note: please add new statements doing read only operations before
1386     /// the WRITE_STMTS_BEGIN position.
1387     enum StatementIndex {
1388         GET_HOST_DHCPID,           // Gets hosts by host identifier
1389         GET_HOST_ADDR,             // Gets hosts by IPv4 address
1390         GET_HOST_SUBID4_DHCPID,    // Gets host by IPv4 SubnetID, HW address/DUID
1391         GET_HOST_SUBID6_DHCPID,    // Gets host by IPv6 SubnetID, HW address/DUID
1392         GET_HOST_SUBID_ADDR,       // Gets host by IPv4 SubnetID and IPv4 address
1393         GET_HOST_PREFIX,           // Gets host by IPv6 prefix
1394         GET_HOST_SUBID6_ADDR,      // Gets host by IPv6 SubnetID and IPv6 prefix
1395         GET_HOST_SUBID4,           // Gets hosts by IPv4 SubnetID
1396         GET_HOST_SUBID6,           // Gets hosts by IPv6 SubnetID
1397         GET_HOST_HOSTNAME,         // Gets hosts by hostname
1398         GET_HOST_HOSTNAME_SUBID4,  // Gets hosts by hostname and IPv4 SubnetID
1399         GET_HOST_HOSTNAME_SUBID6,  // Gets hosts by hostname and IPv6 SubnetID
1400         GET_HOST_SUBID4_PAGE,      // Gets hosts by IPv4 SubnetID beginning by HID
1401         GET_HOST_SUBID6_PAGE,      // Gets hosts by IPv6 SubnetID beginning by HID
1402         GET_HOST_PAGE4,            // Gets v4 hosts beginning by HID
1403         GET_HOST_PAGE6,            // Gets v6 hosts beginning by HID
1404         INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
1405         INSERT_HOST_UNIQUE_IP,     // Insert new host to collection with checking for IP duplicates
1406         INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
1407         INSERT_V6_RESRV_UNIQUE,    // Insert v6 reservation with checking that it is unique
1408         INSERT_V4_HOST_OPTION,     // Insert DHCPv4 option
1409         INSERT_V6_HOST_OPTION,     // Insert DHCPv6 option
1410         DEL_HOST_ADDR4,            // Delete v4 host (subnet-id, addr4)
1411         DEL_HOST_ADDR6,            // Delete v6 host (subnet-id, addr6)
1412         DEL_HOST_SUBID4_ID,        // Delete v4 host (subnet-id, ident.type, identifier)
1413         DEL_HOST_SUBID6_ID,        // Delete v6 host (subnet-id, ident.type, identifier)
1414         NUM_STATEMENTS             // Number of statements
1415     };
1416 
1417     /// @brief Index of first statement performing write to the database.
1418     ///
1419     /// This value is used to mark border line between queries and other
1420     /// statements and statements performing write operation on the database,
1421     /// such as INSERT, DELETE, UPDATE.
1422     static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;
1423 
1424     /// @brief Constructor.
1425     ///
1426     /// This constructor opens database connection and initializes prepared
1427     /// statements used in the queries.
1428     PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);
1429 
1430     /// @brief Destructor.
1431     ~PgSqlHostDataSourceImpl();
1432 
1433     /// @brief Attempts to reconnect the server to the host DB backend manager.
1434     ///
1435     /// This is a self-rescheduling function that attempts to reconnect to the
1436     /// server's host DB backends after connectivity to one or more have been
1437     /// lost. Upon entry it will attempt to reconnect via
1438     /// @ref HostDataSourceFactory::add.
1439     /// If this is successful, DHCP servicing is re-enabled and server returns
1440     /// to normal operation.
1441     ///
1442     /// If reconnection fails and the maximum number of retries has not been
1443     /// exhausted, it will schedule a call to itself to occur at the
1444     /// configured retry interval. DHCP service remains disabled.
1445     ///
1446     /// If the maximum number of retries has been exhausted an error is logged
1447     /// and the server shuts down.
1448     ///
1449     /// This function is passed to the connection recovery mechanism. It will be
1450     /// invoked when a connection loss is detected.
1451     ///
1452     /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
1453     /// configured reconnect parameters.
1454     /// @return true if connection has been recovered, false otherwise.
1455     static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
1456 
1457     /// @brief Create a new context.
1458     ///
1459     /// The database is opened with all the SQL commands pre-compiled.
1460     ///
1461     /// @return A new (never null) context.
1462     ///
1463     /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
1464     /// @throw isc::db::DbOperationError An operation on the open database has
1465     /// failed.
1466     PgSqlHostContextPtr createContext() const;
1467 
1468     /// @brief Executes statements which insert a row into one of the tables.
1469     ///
1470     /// @param ctx Context
1471     /// @param stindex Index of a statement being executed.
1472     /// @param bind Vector of PgsqlBindArray objects to be used for the query
1473     /// @param return_last_id flag indicating whether or not the insert
1474     /// returns the primary key of from the row inserted via " RETURNING
1475     /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
1476     /// clause causes the INSERT to return a result set that should consist
1477     /// of a single row with one column, the value of the primary key.
1478     /// Defaults to false.
1479     ///
1480     /// @return 0 if return_last_id is false, otherwise it returns the
1481     /// the value in the result set in the first col of the first row.
1482     ///
1483     /// @throw isc::db::DuplicateEntry Database throws duplicate entry error
1484     uint64_t addStatement(PgSqlHostContextPtr& ctx,
1485                           PgSqlHostDataSourceImpl::StatementIndex stindex,
1486                           PsqlBindArrayPtr& bind,
1487                           const bool return_last_id = false);
1488 
1489     /// @brief Executes statements that delete records.
1490     ///
1491     /// @param ctx Context
1492     /// @param stindex Index of a statement being executed.
1493     /// @param bind pointer to PsqlBindArray objects to be used for the query
1494     ///
1495     /// @return true if any records were deleted, false otherwise
1496     bool delStatement(PgSqlHostContextPtr& ctx,
1497                       PgSqlHostDataSourceImpl::StatementIndex stindex,
1498                       PsqlBindArrayPtr& bind);
1499 
1500     /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
1501     ///
1502     /// @param ctx Context
1503     /// @param resv IPv6 Reservation to be added
1504     /// @param id ID of a host owning this reservation
1505     void addResv(PgSqlHostContextPtr& ctx,
1506                  const IPv6Resrv& resv,
1507                  const HostID& id);
1508 
1509     /// @brief Inserts a single DHCP option into the database.
1510     ///
1511     /// @param ctx Context
1512     /// @param stindex Index of a statement being executed.
1513     /// @param opt_desc Option descriptor holding information about an option
1514     /// to be inserted into the database.
1515     /// @param opt_space Option space name.
1516     /// @param subnet_id Subnet identifier.
1517     /// @param host_id Host identifier.
1518     void addOption(PgSqlHostContextPtr& ctx,
1519                    const PgSqlHostDataSourceImpl::StatementIndex& stindex,
1520                    const OptionDescriptor& opt_desc,
1521                    const std::string& opt_space,
1522                    const Optional<SubnetID>& subnet_id,
1523                    const HostID& host_id);
1524 
1525     /// @brief Inserts multiple options into the database.
1526     ///
1527     /// @param ctx Context
1528     /// @param stindex Index of a statement being executed.
1529     /// @param options_cfg An object holding a collection of options to be
1530     /// inserted into the database.
1531     /// @param host_id Host identifier retrieved using getColumnValue
1532     ///                in addStatement method
1533     void addOptions(PgSqlHostContextPtr& ctx,
1534                     const StatementIndex& stindex,
1535                     const ConstCfgOptionPtr& options_cfg,
1536                     const uint64_t host_id);
1537 
1538     /// @brief Creates collection of @ref Host objects with associated
1539     /// information such as IPv6 reservations and/or DHCP options.
1540     ///
1541     /// This method performs a query which returns host information from
1542     /// the 'hosts' table. The query may also use LEFT JOIN clause to
1543     /// retrieve information from other tables, e.g. ipv6_reservations,
1544     /// dhcp4_options and dhcp6_options.
1545     /// Whether IPv6 reservations and/or options are assigned to the
1546     /// @ref Host objects depends on the type of the exchange object.
1547     ///
1548     /// @param ctx Context
1549     /// @param stindex Statement index.
1550     /// @param bind Pointer to an array of PgSQL bindings.
1551     /// @param exchange Pointer to the exchange object used for the
1552     /// particular query.
1553     /// @param [out] result Reference to the collection of hosts returned.
1554     /// @param single A boolean value indicating if a single host is
1555     /// expected to be returned, or multiple hosts.
1556     void getHostCollection(PgSqlHostContextPtr& ctx,
1557                            StatementIndex stindex,
1558                            PsqlBindArrayPtr bind,
1559                            boost::shared_ptr<PgSqlHostExchange> exchange,
1560                            ConstHostCollection& result,
1561                            bool single) const;
1562 
1563     /// @brief Retrieves a host by subnet and client's unique identifier.
1564     ///
1565     /// This method is used by both PgSqlHostDataSource::get4 and
1566     /// PgSqlHostDataSource::get6 methods.
1567     ///
1568     /// @param ctx Context
1569     /// @param subnet_id Subnet identifier.
1570     /// @param identifier_type Identifier type.
1571     /// @param identifier_begin Pointer to a beginning of a buffer containing
1572     /// an identifier.
1573     /// @param identifier_len Identifier length.
1574     /// @param stindex Statement index.
1575     /// @param exchange Pointer to the exchange object used for the
1576     /// particular query.
1577     ///
1578     /// @return Pointer to const instance of Host or null pointer if
1579     /// no host found.
1580     ConstHostPtr getHost(PgSqlHostContextPtr& ctx,
1581                          const SubnetID& subnet_id,
1582                          const Host::IdentifierType& identifier_type,
1583                          const uint8_t* identifier_begin,
1584                          const size_t identifier_len,
1585                          StatementIndex stindex,
1586                          boost::shared_ptr<PgSqlHostExchange> exchange) const;
1587 
1588     /// @brief Throws exception if database is read only.
1589     ///
1590     /// This method should be called by the methods which write to the
1591     /// database. If the backend is operating in read-only mode this
1592     /// method will throw exception.
1593     ///
1594     /// @param ctx Context
1595     ///
1596     /// @throw DbReadOnly if backend is operating in read only mode.
1597     void checkReadOnly(PgSqlHostContextPtr& ctx) const;
1598 
1599     /// @brief Returns PostgreSQL schema version of the open database
1600     ///
1601     /// @return Version number stored in the database, as a pair of unsigned
1602     ///         integers. "first" is the major version number, "second" the
1603     ///         minor number.
1604     ///
1605     /// @throw isc::db::DbOperationError An operation on the open database
1606     ///        has failed.
1607     std::pair<uint32_t, uint32_t> getVersion() const;
1608 
1609     /// @brief The parameters
1610     DatabaseConnection::ParameterMap parameters_;
1611 
1612     /// @brief Holds the setting whether the IP reservations must be unique or
1613     /// may be non-unique.
1614     bool ip_reservations_unique_;
1615 
1616     /// @brief The pool of contexts
1617     PgSqlHostContextPoolPtr pool_;
1618 
1619     /// @brief Indicates if there is at least one connection that can no longer
1620     /// be used for normal operations.
1621     bool unusable_;
1622 
1623     /// @brief Timer name used to register database reconnect timer.
1624     std::string timer_name_;
1625 };
1626 
1627 namespace {
1628 
1629 /// @brief Array of tagged statements.
1630 typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
1631 TaggedStatementArray;
1632 
1633 /// @brief Prepared PosgreSQL statements used by the backend to insert and
1634 /// retrieve reservation data from the database.
1635 TaggedStatementArray tagged_statements = { {
1636     // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
1637     // Retrieves host information, IPv6 reservations and both DHCPv4 and
1638     // DHCPv6 options associated with the host. The LEFT JOIN clause is used
1639     // to retrieve information from 4 different tables using a single query.
1640     // Hence, this query returns multiple rows for a single host.
1641     {2,
1642      { OID_BYTEA, OID_INT2 },
1643      "get_host_dhcpid",
1644      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1645      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1646      "  h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1647      "  h.user_context, "
1648      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1649      "  h.dhcp4_boot_file_name, h.auth_key, "
1650      "  o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1651      "  o4.persistent, o4.user_context, "
1652      "  o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1653      "  o6.persistent, o6.user_context, "
1654      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1655      "FROM hosts AS h "
1656      "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1657      "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1658      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1659      "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
1660      "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1661     },
1662 
1663     // PgSqlHostDataSourceImpl::GET_HOST_ADDR
1664     // Retrieves host information along with the DHCPv4 options associated with
1665     // it. Left joining the dhcp4_options table results in multiple rows being
1666     // returned for the same host. The host is retrieved by IPv4 address.
1667     {1,
1668      { OID_INT8 },
1669      "get_host_addr",
1670      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1671      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1672      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1673      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1674      "  h.dhcp4_boot_file_name, h.auth_key, "
1675      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1676      "  o.persistent, o.user_context "
1677      "FROM hosts AS h "
1678      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1679      "WHERE ipv4_address = $1 "
1680      "ORDER BY h.host_id, o.option_id"
1681     },
1682 
1683     // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
1684     // Retrieves host information and DHCPv4 options using subnet identifier
1685     // and client's identifier. Left joining the dhcp4_options table results in
1686     // multiple rows being returned for the same host.
1687     {3,
1688      { OID_INT8, OID_INT2, OID_BYTEA },
1689      "get_host_subid4_dhcpid",
1690      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1691      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1692      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1693      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1694      "  h.dhcp4_boot_file_name, h.auth_key, "
1695      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1696      "  o.persistent, o.user_context "
1697      "FROM hosts AS h "
1698      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1699      "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1700      "  AND h.dhcp_identifier = $3 "
1701      "ORDER BY h.host_id, o.option_id"
1702     },
1703 
1704     // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
1705     // Retrieves host information, IPv6 reservations and DHCPv6 options
1706     // associated with a host. The number of rows returned is a multiplication
1707     // of number of IPv6 reservations and DHCPv6 options.
1708     {3,
1709      { OID_INT8, OID_INT2, OID_BYTEA },
1710      "get_host_subid6_dhcpid",
1711      "SELECT h.host_id, h.dhcp_identifier, "
1712      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1713      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1714      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1715      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1716      "  h.dhcp4_boot_file_name, h.auth_key, "
1717      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1718      "  o.persistent, o.user_context, "
1719      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1720      "FROM hosts AS h "
1721      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1722      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1723      "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1724      "  AND h.dhcp_identifier = $3 "
1725      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1726     },
1727 
1728     // PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
1729     // Retrieves host information and DHCPv4 options for the host using subnet
1730     // identifier and IPv4 reservation. Left joining the dhcp4_options table
1731     // results in multiple rows being returned for the host. The number of
1732     // rows depends on the number of options defined for the host.
1733     {2,
1734      { OID_INT8, OID_INT8 },
1735      "get_host_subid_addr",
1736      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1737      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1738      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1739      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1740      "  h.dhcp4_boot_file_name, h.auth_key, "
1741      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1742      "  o.persistent, o.user_context "
1743      "FROM hosts AS h "
1744      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1745      "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
1746      "ORDER BY h.host_id, o.option_id"
1747     },
1748 
1749     // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
1750     // Retrieves host information, IPv6 reservations and DHCPv6 options
1751     // associated with a host using prefix and prefix length. This query
1752     // returns host information for a single host. However, multiple rows
1753     // are returned due to left joining IPv6 reservations and DHCPv6 options.
1754     // The number of rows returned is multiplication of number of existing
1755     // IPv6 reservations and DHCPv6 options.
1756     {2,
1757      { OID_VARCHAR, OID_INT2 },
1758      "get_host_prefix",
1759      "SELECT h.host_id, h.dhcp_identifier, "
1760      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1761      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1762      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1763      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1764      "  h.dhcp4_boot_file_name, h.auth_key, "
1765      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1766      "  o.persistent, o.user_context, "
1767      "  r.reservation_id, r.address, r.prefix_len, r.type, "
1768      "  r.dhcp6_iaid "
1769      "FROM hosts AS h "
1770      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1771      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1772      "WHERE h.host_id = "
1773      "  (SELECT host_id FROM ipv6_reservations "
1774      "   WHERE address = $1 AND prefix_len = $2) "
1775      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1776     },
1777 
1778     // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR
1779     // Retrieves host information, IPv6 reservations and DHCPv6 options
1780     // associated with a host using IPv6 subnet id and prefix. This query
1781     // returns host information for a single host. However, multiple rows
1782     // are returned due to left joining IPv6 reservations and DHCPv6 options.
1783     // The number of rows returned is multiplication of number of existing
1784     // IPv6 reservations and DHCPv6 options.
1785     {2,
1786      { OID_INT8, OID_VARCHAR },
1787      "get_host_subid6_addr",
1788      "SELECT h.host_id, h.dhcp_identifier, "
1789      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1790      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1791      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1792      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1793      "  h.dhcp4_boot_file_name, h.auth_key, "
1794      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1795      "  o.persistent, o.user_context, "
1796      "  r.reservation_id, r.address, r.prefix_len, r.type, "
1797      "  r.dhcp6_iaid "
1798      "FROM hosts AS h "
1799      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1800      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1801      "WHERE h.dhcp6_subnet_id = $1 AND r.address = $2 "
1802      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1803     },
1804 
1805     // PgSqlHostDataSourceImpl::GET_HOST_SUBID4
1806     //
1807     // Retrieves host information for all hosts in a subnet, along with the
1808     // DHCPv4 options associated with it. Left joining the dhcp4_options table
1809     // results in multiple rows being returned for the same host. The hosts are
1810     // retrieved by subnet id.
1811     {1,
1812      { OID_INT8 },
1813      "get_host_subid4",
1814      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1815      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1816      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1817      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1818      "  h.dhcp4_boot_file_name, h.auth_key, "
1819      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1820      "  o.persistent, o.user_context "
1821      "FROM hosts AS h "
1822      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1823      "WHERE h.dhcp4_subnet_id = $1 "
1824      "ORDER BY h.host_id, o.option_id"
1825     },
1826 
1827     // PgSqlHostDataSourceImpl::GET_HOST_SUBID6
1828     //
1829     // Retrieves host information, IPv6 reservations and DHCPv6 options
1830     // associated with all hosts using the IPv6 subnet id. This query returns
1831     // host information for many hosts. However, multiple rows are
1832     // returned due to left joining IPv6 reservations and DHCPv6 options.
1833     // The number of rows returned is multiplication of number of existing
1834     // IPv6 reservations and DHCPv6 options for each host in a subnet. There
1835     // are usually many hosts in a subnet. The amount of returned data may
1836     // be huge.
1837     {1,
1838      { OID_INT8 },
1839      "get_host_subid6",
1840      "SELECT h.host_id, h.dhcp_identifier, "
1841      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1842      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1843      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1844      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1845      "  h.dhcp4_boot_file_name, h.auth_key, "
1846      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1847      "  o.persistent, o.user_context, "
1848      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1849      "FROM hosts AS h "
1850      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1851      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1852      "WHERE h.dhcp6_subnet_id = $1 "
1853      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1854     },
1855 
1856     // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME
1857     // Retrieves host information, IPv6 reservations and both DHCPv4 and
1858     // DHCPv6 options associated with all hosts using the hostname.
1859     // The LEFT JOIN clause is used to retrieve information from 4 different
1860     // tables using a single query. Hence, this query returns multiple rows
1861     // for a single host.
1862     {1,
1863      { OID_VARCHAR },
1864      "get_host_hostname",
1865      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1866      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1867      "  h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1868      "  h.user_context, "
1869      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1870      "  h.dhcp4_boot_file_name, h.auth_key, "
1871      "  o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1872      "  o4.persistent, o4.user_context, "
1873      "  o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1874      "  o6.persistent, o6.user_context, "
1875      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1876      "FROM hosts AS h "
1877      "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1878      "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1879      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1880      "WHERE lower(h.hostname) = $1 "
1881      "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1882     },
1883 
1884     // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4
1885     // Retrieves host information for all hosts with a hostname in a subnet,
1886     // along with the DHCPv4 options associated with it. Left joining
1887     // the dhcp4_options table results in multiple rows being returned for
1888     // the same host.
1889     {2,
1890      { OID_VARCHAR, OID_INT8 },
1891      "get_host_hostname_subid4",
1892      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1893      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1894      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1895      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1896      "  h.dhcp4_boot_file_name, h.auth_key, "
1897      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1898      "  o.persistent, o.user_context "
1899      "FROM hosts AS h "
1900      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1901      "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
1902      "ORDER BY h.host_id, o.option_id"
1903     },
1904 
1905     // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6
1906     // Retrieves host information, IPv6 reservations and DHCPv6 options
1907     // associated with all hosts using the hostname and the IPv6 subnet id.
1908     // This query returns host information for many hosts. However, multiple
1909     // rows are returned due to left joining IPv6 reservations and DHCPv6
1910     // options. The number of rows returned is multiplication of number of
1911     // existing IPv6 reservations and DHCPv6 options for each host in a subnet.
1912     {2,
1913      { OID_VARCHAR, OID_INT8 },
1914      "get_host_hostname_subid6",
1915      "SELECT h.host_id, h.dhcp_identifier, "
1916      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1917      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1918      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1919      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1920      "  h.dhcp4_boot_file_name, h.auth_key, "
1921      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1922      "  o.persistent, o.user_context, "
1923      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1924      "FROM hosts AS h "
1925      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1926      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1927      "WHERE lower(h.hostname) = $1 AND h.dhcp6_subnet_id = $2 "
1928      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1929     },
1930 
1931     // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE
1932     // Retrieves host information along with the DHCPv4 options associated with
1933     // it. Left joining the dhcp4_options table results in multiple rows being
1934     // returned for the same host. The hosts are retrieved by subnet id,
1935     // starting from specified host id. Specified number of hosts is returned.
1936     {3,
1937      { OID_INT8, OID_INT8, OID_INT8 },
1938      "get_host_subid4_page",
1939      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1940      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1941      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1942      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1943      "  h.dhcp4_boot_file_name, h.auth_key, "
1944      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1945      "  o.persistent, o.user_context "
1946      "FROM ( SELECT * FROM hosts AS h "
1947      "       WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
1948      "       ORDER BY h.host_id "
1949      "       LIMIT $3 ) AS h "
1950      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1951      "ORDER BY h.host_id, o.option_id"
1952     },
1953 
1954     // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE
1955     // Retrieves host information, IPv6 reservations and DHCPv6 options
1956     // associated with a host using IPv6 subnet id. This query returns
1957     // host information for a single host. However, multiple rows are
1958     // returned due to left joining IPv6 reservations and DHCPv6 options.
1959     // The number of rows returned is multiplication of number of existing
1960     // IPv6 reservations and DHCPv6 options.
1961     {3,
1962      { OID_INT8, OID_INT8, OID_INT8 },
1963      "get_host_subid6_page",
1964      "SELECT h.host_id, h.dhcp_identifier, "
1965      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1966      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1967      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1968      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1969      "  h.dhcp4_boot_file_name, h.auth_key, "
1970      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1971      "  o.persistent, o.user_context, "
1972      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1973      "FROM ( SELECT * FROM hosts AS h "
1974      "       WHERE h.dhcp6_subnet_id = $1 AND h.host_id > $2 "
1975      "       ORDER BY h.host_id "
1976      "       LIMIT $3 ) AS h "
1977      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1978      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1979      "ORDER BY h.host_id, o.option_id, r.reservation_id"
1980     },
1981 
1982     // PgSqlHostDataSourceImpl::GET_HOST_PAGE4
1983     // Retrieves host information along with the DHCPv4 options associated with
1984     // it. Left joining the dhcp4_options table results in multiple rows being
1985     // returned for the same host. The hosts are retrieved starting from
1986     // specified host id. Specified number of hosts is returned.
1987     {2,
1988      { OID_INT8, OID_INT8 },
1989      "get_host_page4",
1990      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1991      "  h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1992      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1993      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
1994      "  h.dhcp4_boot_file_name, h.auth_key, "
1995      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
1996      "  o.persistent, o.user_context "
1997      "FROM ( SELECT * FROM hosts AS h "
1998      "       WHERE h.host_id > $1 "
1999      "       ORDER BY h.host_id "
2000      "       LIMIT $2 ) AS h "
2001      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
2002      "ORDER BY h.host_id, o.option_id"
2003     },
2004 
2005     // PgSqlHostDataSourceImpl::GET_HOST_PAGE6
2006     // Retrieves host information, IPv6 reservations and DHCPv6 options
2007     // associated with a host using IPv6 subnet id. This query returns
2008     // host information for a single host. However, multiple rows are
2009     // returned due to left joining IPv6 reservations and DHCPv6 options.
2010     // The number of rows returned is multiplication of number of existing
2011     // IPv6 reservations and DHCPv6 options.
2012     {2,
2013      { OID_INT8, OID_INT8 },
2014      "get_host_page6",
2015      "SELECT h.host_id, h.dhcp_identifier, "
2016      "  h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2017      "  h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2018      "  h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2019      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
2020      "  h.dhcp4_boot_file_name, h.auth_key, "
2021      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
2022      "  o.persistent, o.user_context, "
2023      "  r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
2024      "FROM ( SELECT * FROM hosts AS h "
2025      "       WHERE h.host_id > $1 "
2026      "       ORDER BY h.host_id "
2027      "       LIMIT $2 ) AS h "
2028      "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
2029      "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
2030      "ORDER BY h.host_id, o.option_id, r.reservation_id"
2031     },
2032 
2033     // PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP
2034     // Inserts a host into the 'hosts' table without checking that there is
2035     // a reservation for the IP address.
2036     {13,
2037      { OID_BYTEA, OID_INT2,
2038        OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
2039        OID_VARCHAR, OID_VARCHAR, OID_TEXT,
2040        OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR},
2041      "insert_host_non_unique_ip",
2042      "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2043      "  dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2044      "  dhcp4_client_classes, dhcp6_client_classes, user_context, "
2045      "  dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2046      "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
2047      "RETURNING host_id"
2048     },
2049 
2050     // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
2051     // Inserts a host into the 'hosts' table with checking that reserved IP
2052     // address is unique. The innermost query checks if there is at least
2053     // one host for the given IP/subnet combination. For checking whether
2054     // hosts exists or not it doesn't matter if we select actual columns,
2055     // thus SELECT 1 was used as an optimization to avoid selecting data
2056     // that will be ignored anyway. If it does not exist the new host is
2057     // inserted. If the host with the given IP address already exists the
2058     // new host won't be inserted. The caller can check the number of
2059     // affected rows to detect that there was a duplicate host in the
2060     // database. Returns the inserted host id.
2061     {15,
2062      { OID_BYTEA, OID_INT2,
2063        OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
2064        OID_VARCHAR, OID_VARCHAR, OID_TEXT,
2065        OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR,
2066        OID_INT8, OID_INT8},
2067      "insert_host_unique_ip",
2068      "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2069      "  dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2070      "  dhcp4_client_classes, dhcp6_client_classes, user_context, "
2071      "  dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2072      "  SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
2073      "    WHERE NOT EXISTS ("
2074      "      SELECT 1 FROM hosts WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
2075      "      LIMIT 1"
2076      "    ) "
2077      "RETURNING host_id"
2078     },
2079 
2080     // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
2081     // Inserts a single IPv6 reservation into 'reservations' table without
2082     // checking that the inserted reservation is unique.
2083     {5,
2084      { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
2085      "insert_v6_resrv_non_unique",
2086      "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2087      "  dhcp6_iaid, host_id) "
2088      "VALUES ($1, $2, $3, $4, $5)"
2089     },
2090 
2091     // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
2092     // Inserts a single IPv6 reservation into 'reservations' table with
2093     // checking that the inserted reservation is unique.
2094     {7,
2095      { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4, OID_VARCHAR, OID_INT2 },
2096      "insert_v6_resrv_unique",
2097      "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2098      "  dhcp6_iaid, host_id) "
2099      "SELECT $1, $2, $3, $4, $5 "
2100      "  WHERE NOT EXISTS ("
2101      "      SELECT 1 FROM ipv6_reservations"
2102      "          WHERE address = $6 AND prefix_len = $7"
2103      "      LIMIT 1"
2104      "  )"
2105     },
2106 
2107     // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
2108     // Inserts a single DHCPv4 option into 'dhcp4_options' table.
2109     // Using fixed scope_id = 3, which associates an option with host.
2110     {7,
2111      { OID_INT2, OID_BYTEA, OID_TEXT,
2112        OID_VARCHAR, OID_BOOL, OID_TEXT, OID_INT8 },
2113      "insert_v4_host_option",
2114      "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
2115      "  persistent, user_context, host_id, scope_id) "
2116      "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2117     },
2118 
2119     // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
2120     // Inserts a single DHCPv6 option into 'dhcp6_options' table.
2121     // Using fixed scope_id = 3, which associates an option with host.
2122     {7,
2123      { OID_INT2, OID_BYTEA, OID_TEXT,
2124        OID_VARCHAR, OID_BOOL, OID_TEXT, OID_INT8 },
2125      "insert_v6_host_option",
2126      "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
2127      "  persistent, user_context, host_id, scope_id) "
2128      "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2129     },
2130 
2131     // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
2132     // Deletes a v4 host that matches (subnet-id, addr4)
2133     {2,
2134      { OID_INT8, OID_INT8 },
2135      "del_host_addr4",
2136      "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
2137     },
2138 
2139     // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
2140     // Deletes a v6 host that matches (subnet-id, addr6)
2141     {2,
2142      { OID_INT8, OID_VARCHAR },
2143      "del_host_addr6",
2144      "DELETE FROM hosts USING ipv6_reservations "
2145      "  WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = $2"
2146     },
2147 
2148     // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
2149     // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
2150     {3,
2151      { OID_INT8, OID_INT2, OID_BYTEA },
2152      "del_host_subid4_id",
2153      "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
2154      "AND dhcp_identifier_type = $2 "
2155      "AND dhcp_identifier = $3"
2156     },
2157 
2158     // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
2159     // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
2160     {3,
2161      { OID_INT8, OID_INT2, OID_BYTEA },
2162      "del_host_subid6_id",
2163      "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
2164      "AND dhcp_identifier_type = $2 "
2165      "AND dhcp_identifier = $3"
2166     }
2167 }
2168 };
2169 
2170 }  // namespace
2171 
2172 // PgSqlHostContext Constructor
2173 
PgSqlHostContext(const DatabaseConnection::ParameterMap & parameters,IOServiceAccessorPtr io_service_accessor,db::DbCallback db_reconnect_callback)2174 PgSqlHostContext::PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
2175                                    IOServiceAccessorPtr io_service_accessor,
2176                                    db::DbCallback db_reconnect_callback)
2177     : conn_(parameters, io_service_accessor, db_reconnect_callback),
2178       is_readonly_(true) {
2179 }
2180 
2181 // PgSqlHostContextAlloc Constructor and Destructor
2182 
PgSqlHostContextAlloc(PgSqlHostDataSourceImpl & mgr)2183 PgSqlHostDataSource::PgSqlHostContextAlloc::PgSqlHostContextAlloc(
2184     PgSqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
2185 
2186     if (MultiThreadingMgr::instance().getMode()) {
2187         // multi-threaded
2188         {
2189             // we need to protect the whole pool_ operation, hence extra scope {}
2190             lock_guard<mutex> lock(mgr_.pool_->mutex_);
2191             if (!mgr_.pool_->pool_.empty()) {
2192                 ctx_ = mgr_.pool_->pool_.back();
2193                 mgr_.pool_->pool_.pop_back();
2194             }
2195         }
2196         if (!ctx_) {
2197             ctx_ = mgr_.createContext();
2198         }
2199     } else {
2200         // single-threaded
2201         if (mgr_.pool_->pool_.empty()) {
2202             isc_throw(Unexpected, "No available PostgreSQL host context?!");
2203         }
2204         ctx_ = mgr_.pool_->pool_.back();
2205     }
2206 }
2207 
~PgSqlHostContextAlloc()2208 PgSqlHostDataSource::PgSqlHostContextAlloc::~PgSqlHostContextAlloc() {
2209     if (MultiThreadingMgr::instance().getMode()) {
2210         // multi-threaded
2211         lock_guard<mutex> lock(mgr_.pool_->mutex_);
2212         mgr_.pool_->pool_.push_back(ctx_);
2213         if (ctx_->conn_.isUnusable()) {
2214             mgr_.unusable_ = true;
2215         }
2216     } else if (ctx_->conn_.isUnusable()) {
2217         mgr_.unusable_ = true;
2218     }
2219 }
2220 
PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap & parameters)2221 PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
2222     : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
2223       timer_name_("") {
2224 
2225     // Create unique timer name per instance.
2226     timer_name_ = "PgSqlHostMgr[";
2227     timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
2228     timer_name_ += "]DbReconnectTimer";
2229 
2230     // Validate the schema version first.
2231     std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
2232                                                PG_SCHEMA_VERSION_MINOR);
2233     std::pair<uint32_t, uint32_t> db_version = getVersion();
2234     if (code_version != db_version) {
2235         isc_throw(DbOpenError,
2236                   "PostgreSQL schema version mismatch: need version: "
2237                       << code_version.first << "." << code_version.second
2238                       << " found version: " << db_version.first << "."
2239                       << db_version.second);
2240     }
2241 
2242     // Create an initial context.
2243     pool_.reset(new PgSqlHostContextPool());
2244     pool_->pool_.push_back(createContext());
2245 }
2246 
2247 // Create context.
2248 
2249 PgSqlHostContextPtr
createContext() const2250 PgSqlHostDataSourceImpl::createContext() const {
2251     PgSqlHostContextPtr ctx(new PgSqlHostContext(parameters_,
2252         IOServiceAccessorPtr(new IOServiceAccessor(&HostMgr::getIOService)),
2253         &PgSqlHostDataSourceImpl::dbReconnect));
2254 
2255     // Open the database.
2256     ctx->conn_.openDatabase();
2257 
2258     // Now prepare the SQL statements.
2259     ctx->conn_.prepareStatements(tagged_statements.begin(),
2260                                  tagged_statements.begin() + WRITE_STMTS_BEGIN);
2261 
2262     // Check if the backend is explicitly configured to operate with
2263     // read only access to the database.
2264     ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
2265 
2266     // If we are using read-write mode for the database we also prepare
2267     // statements for INSERTS etc.
2268     if (!ctx->is_readonly_) {
2269         ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
2270                                      tagged_statements.end());
2271     } else {
2272         LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY);
2273     }
2274 
2275     ctx->host_ipv4_exchange_.reset(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY));
2276     ctx->host_ipv6_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY));
2277     ctx->host_ipv46_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
2278     ctx->host_ipv6_reservation_exchange_.reset(new PgSqlIPv6ReservationExchange());
2279     ctx->host_option_exchange_.reset(new PgSqlOptionExchange());
2280 
2281     // Create ReconnectCtl for this connection.
2282     ctx->conn_.makeReconnectCtl(timer_name_);
2283 
2284     return (ctx);
2285 }
2286 
~PgSqlHostDataSourceImpl()2287 PgSqlHostDataSourceImpl::~PgSqlHostDataSourceImpl() {
2288 }
2289 
2290 bool
dbReconnect(ReconnectCtlPtr db_reconnect_ctl)2291 PgSqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
2292     MultiThreadingCriticalSection cs;
2293 
2294     // Invoke application layer connection lost callback.
2295     if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
2296         return (false);
2297     }
2298 
2299     bool reopened = false;
2300 
2301     const std::string timer_name = db_reconnect_ctl->timerName();
2302 
2303     // At least one connection was lost.
2304     try {
2305         CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
2306         std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
2307         for (std::string& hds : host_db_access_list) {
2308             auto parameters = DatabaseConnection::parse(hds);
2309             if (HostMgr::delBackend("postgresql", hds, true)) {
2310                 HostMgr::addBackend(hds);
2311             }
2312         }
2313         reopened = true;
2314     } catch (const std::exception& ex) {
2315         LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED)
2316                 .arg(ex.what());
2317     }
2318 
2319     if (reopened) {
2320         // Cancel the timer.
2321         if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2322             TimerMgr::instance()->unregisterTimer(timer_name);
2323         }
2324 
2325         // Invoke application layer connection recovered callback.
2326         if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
2327             return (false);
2328         }
2329     } else {
2330         if (!db_reconnect_ctl->checkRetries()) {
2331             // We're out of retries, log it and initiate shutdown.
2332             LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED)
2333                     .arg(db_reconnect_ctl->maxRetries());
2334 
2335             // Cancel the timer.
2336             if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2337                 TimerMgr::instance()->unregisterTimer(timer_name);
2338             }
2339 
2340             // Invoke application layer connection failed callback.
2341             DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
2342             return (false);
2343         }
2344 
2345         LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE)
2346                 .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
2347                 .arg(db_reconnect_ctl->maxRetries())
2348                 .arg(db_reconnect_ctl->retryInterval());
2349 
2350         // Start the timer.
2351         if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
2352             TimerMgr::instance()->registerTimer(timer_name,
2353                 std::bind(&PgSqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
2354                           db_reconnect_ctl->retryInterval(),
2355                           asiolink::IntervalTimer::ONE_SHOT);
2356         }
2357         TimerMgr::instance()->setup(timer_name);
2358     }
2359 
2360     return (true);
2361 }
2362 
2363 uint64_t
addStatement(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr & bind_array,const bool return_last_id)2364 PgSqlHostDataSourceImpl::addStatement(PgSqlHostContextPtr& ctx,
2365                                       StatementIndex stindex,
2366                                       PsqlBindArrayPtr& bind_array,
2367                                       const bool return_last_id) {
2368     uint64_t last_id = 0;
2369     PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2370                                  tagged_statements[stindex].nbparams,
2371                                  &bind_array->values_[0],
2372                                  &bind_array->lengths_[0],
2373                                  &bind_array->formats_[0], 0));
2374 
2375     int s = PQresultStatus(r);
2376 
2377     if (s != PGRES_COMMAND_OK) {
2378         // Failure: check for the special case of duplicate entry.
2379         if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
2380             isc_throw(DuplicateEntry, "Database duplicate entry error");
2381         }
2382 
2383         // Connection determines if the error is fatal or not, and
2384         // throws the appropriate exception
2385         ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2386     }
2387 
2388     // Get the number of affected rows.
2389     char* rows_affected = PQcmdTuples(r);
2390     if (!rows_affected) {
2391         isc_throw(DbOperationError,
2392                   "Could not retrieve the number of affected rows.");
2393     }
2394 
2395     // If the number of rows inserted is 0 it means that the query detected
2396     // an attempt to insert duplicated data for which there is no unique
2397     // index in the database. Unique indexes are not created in the database
2398     // when it may be sometimes allowed to insert duplicated records per
2399     // server's configuration.
2400     if (rows_affected[0] == '0') {
2401         isc_throw(DuplicateEntry, "Database duplicate entry error");
2402     }
2403 
2404     if (return_last_id) {
2405         PgSqlExchange::getColumnValue(r, 0, 0, last_id);
2406     }
2407 
2408     return (last_id);
2409 }
2410 
2411 bool
delStatement(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr & bind_array)2412 PgSqlHostDataSourceImpl::delStatement(PgSqlHostContextPtr& ctx,
2413                                       StatementIndex stindex,
2414                                       PsqlBindArrayPtr& bind_array) {
2415     PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2416                                  tagged_statements[stindex].nbparams,
2417                                  &bind_array->values_[0],
2418                                  &bind_array->lengths_[0],
2419                                  &bind_array->formats_[0], 0));
2420 
2421     int s = PQresultStatus(r);
2422 
2423     if (s != PGRES_COMMAND_OK) {
2424         // Connection determines if the error is fatal or not, and
2425         // throws the appropriate exception
2426         ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2427     }
2428 
2429     // Now check how many rows (hosts) were deleted. This should be either
2430     // "0" or "1".
2431     char* rows_deleted = PQcmdTuples(r);
2432     if (!rows_deleted) {
2433         isc_throw(DbOperationError,
2434                   "Could not retrieve the number of deleted rows.");
2435     }
2436     return (rows_deleted[0] != '0');
2437 }
2438 
2439 void
addResv(PgSqlHostContextPtr & ctx,const IPv6Resrv & resv,const HostID & id)2440 PgSqlHostDataSourceImpl::addResv(PgSqlHostContextPtr& ctx,
2441                                  const IPv6Resrv& resv,
2442                                  const HostID& id) {
2443     PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
2444         createBindForSend(resv, id, ip_reservations_unique_);
2445 
2446     addStatement(ctx,
2447                  ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE,
2448                  bind_array);
2449 }
2450 
2451 void
addOption(PgSqlHostContextPtr & ctx,const StatementIndex & stindex,const OptionDescriptor & opt_desc,const std::string & opt_space,const Optional<SubnetID> &,const HostID & id)2452 PgSqlHostDataSourceImpl::addOption(PgSqlHostContextPtr& ctx,
2453                                    const StatementIndex& stindex,
2454                                    const OptionDescriptor& opt_desc,
2455                                    const std::string& opt_space,
2456                                    const Optional<SubnetID>&,
2457                                    const HostID& id) {
2458     PsqlBindArrayPtr bind_array = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, id);
2459 
2460     addStatement(ctx, stindex, bind_array);
2461 }
2462 
2463 void
addOptions(PgSqlHostContextPtr & ctx,const StatementIndex & stindex,const ConstCfgOptionPtr & options_cfg,const uint64_t host_id)2464 PgSqlHostDataSourceImpl::addOptions(PgSqlHostContextPtr& ctx,
2465                                     const StatementIndex& stindex,
2466                                     const ConstCfgOptionPtr& options_cfg,
2467                                     const uint64_t host_id) {
2468     // Get option space names and vendor space names and combine them within a
2469     // single list.
2470     std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
2471     std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
2472     option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
2473                          vendor_spaces.end());
2474 
2475     // For each option space retrieve all options and insert them into the
2476     // database.
2477     for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
2478         OptionContainerPtr options = options_cfg->getAll(*space);
2479         if (options && !options->empty()) {
2480             for (auto opt = options->begin(); opt != options->end(); ++opt) {
2481                 addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
2482             }
2483         }
2484     }
2485 }
2486 
2487 void
getHostCollection(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr bind_array,boost::shared_ptr<PgSqlHostExchange> exchange,ConstHostCollection & result,bool single) const2488 PgSqlHostDataSourceImpl::getHostCollection(PgSqlHostContextPtr& ctx,
2489                                            StatementIndex stindex,
2490                                            PsqlBindArrayPtr bind_array,
2491                                            boost::shared_ptr<PgSqlHostExchange> exchange,
2492                                            ConstHostCollection& result,
2493                                            bool single) const {
2494 
2495     exchange->clear();
2496     PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2497                                  tagged_statements[stindex].nbparams,
2498                                  &bind_array->values_[0],
2499                                  &bind_array->lengths_[0],
2500                                  &bind_array->formats_[0], 0));
2501 
2502     ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2503 
2504     int rows = r.getRows();
2505     for (int row = 0; row < rows; ++row) {
2506         exchange->processRowData(result, r, row);
2507 
2508         if (single && result.size() > 1) {
2509             isc_throw(MultipleRecords, "multiple records were found in the "
2510                       "database where only one was expected for query "
2511                       << tagged_statements[stindex].name);
2512         }
2513     }
2514 }
2515 
2516 ConstHostPtr
getHost(PgSqlHostContextPtr & ctx,const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len,StatementIndex stindex,boost::shared_ptr<PgSqlHostExchange> exchange) const2517 PgSqlHostDataSourceImpl::getHost(PgSqlHostContextPtr& ctx,
2518                                  const SubnetID& subnet_id,
2519                                  const Host::IdentifierType& identifier_type,
2520                                  const uint8_t* identifier_begin,
2521                                  const size_t identifier_len,
2522                                  StatementIndex stindex,
2523                                  boost::shared_ptr<PgSqlHostExchange> exchange) const {
2524 
2525     // Set up the WHERE clause value
2526     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2527 
2528     // Add the subnet id.
2529     bind_array->add(subnet_id);
2530 
2531     // Add the Identifier type.
2532     bind_array->add(static_cast<uint8_t>(identifier_type));
2533 
2534     // Add the identifier value.
2535     bind_array->add(identifier_begin, identifier_len);
2536 
2537     ConstHostCollection collection;
2538     getHostCollection(ctx, stindex, bind_array, exchange, collection, true);
2539 
2540     // Return single record if present, else clear the host.
2541     ConstHostPtr result;
2542     if (!collection.empty()) {
2543         result = *collection.begin();
2544     }
2545 
2546     return (result);
2547 }
2548 
2549 std::pair<uint32_t, uint32_t>
getVersion() const2550 PgSqlHostDataSourceImpl::getVersion() const {
2551     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
2552               DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
2553     return (PgSqlConnection::getVersion(parameters_));
2554 }
2555 
2556 void
checkReadOnly(PgSqlHostContextPtr & ctx) const2557 PgSqlHostDataSourceImpl::checkReadOnly(PgSqlHostContextPtr& ctx) const {
2558     if (ctx->is_readonly_) {
2559         isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
2560                   " to operate in read only mode");
2561     }
2562 }
2563 
2564 /*********** PgSqlHostDataSource *********************/
2565 
PgSqlHostDataSource(const DatabaseConnection::ParameterMap & parameters)2566 PgSqlHostDataSource::PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
2567     : impl_(new PgSqlHostDataSourceImpl(parameters)) {
2568 }
2569 
~PgSqlHostDataSource()2570 PgSqlHostDataSource::~PgSqlHostDataSource() {
2571 }
2572 
2573 DatabaseConnection::ParameterMap
getParameters() const2574 PgSqlHostDataSource::getParameters() const {
2575     return (impl_->parameters_);
2576 }
2577 
2578 void
add(const HostPtr & host)2579 PgSqlHostDataSource::add(const HostPtr& host) {
2580     // Get a context
2581     PgSqlHostContextAlloc get_context(*impl_);
2582     PgSqlHostContextPtr ctx = get_context.ctx_;
2583 
2584     // If operating in read-only mode, throw exception.
2585     impl_->checkReadOnly(ctx);
2586 
2587     // Initiate PostgreSQL transaction as we will have to make multiple queries
2588     // to insert host information into multiple tables. If that fails on
2589     // any stage, the transaction will be rolled back by the destructor of
2590     // the PgSqlTransaction class.
2591     PgSqlTransaction transaction(ctx->conn_);
2592 
2593     // If we're configured to check that an IP reservation within a given subnet
2594     // is unique, the IP reservation exists and the subnet is actually set
2595     // we will be using a special query that checks for uniqueness. Otherwise,
2596     // we will use a regular insert statement.
2597     bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
2598         && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
2599 
2600     // Create the PgSQL Bind array for the host
2601     PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
2602 
2603     // ... and insert the host.
2604     uint32_t host_id = impl_->addStatement(ctx,
2605                                            unique_ip ? PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
2606                                            PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
2607                                            bind_array, true);
2608 
2609     // Insert DHCPv4 options.
2610     ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
2611     if (cfg_option4) {
2612         impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
2613                           cfg_option4, host_id);
2614     }
2615 
2616     // Insert DHCPv6 options.
2617     ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
2618     if (cfg_option6) {
2619         impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
2620                           cfg_option6, host_id);
2621     }
2622 
2623     // Insert IPv6 reservations.
2624     IPv6ResrvRange v6resv = host->getIPv6Reservations();
2625     if (std::distance(v6resv.first, v6resv.second) > 0) {
2626         for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
2627              ++resv) {
2628             impl_->addResv(ctx, resv->second, host_id);
2629         }
2630     }
2631 
2632     // Everything went fine, so explicitly commit the transaction.
2633     transaction.commit();
2634 }
2635 
2636 bool
del(const SubnetID & subnet_id,const asiolink::IOAddress & addr)2637 PgSqlHostDataSource::del(const SubnetID& subnet_id,
2638                          const asiolink::IOAddress& addr) {
2639     // Get a context
2640     PgSqlHostContextAlloc get_context(*impl_);
2641     PgSqlHostContextPtr ctx = get_context.ctx_;
2642 
2643     // If operating in read-only mode, throw exception.
2644     impl_->checkReadOnly(ctx);
2645 
2646     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2647     bind_array->add(subnet_id);
2648 
2649     // v4
2650     if (addr.isV4()) {
2651         bind_array->add(addr);
2652         return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
2653                                     bind_array));
2654     }
2655 
2656     // v6
2657     bind_array->add(addr.toText());
2658 
2659     return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
2660                                 bind_array));
2661 }
2662 
2663 bool
del4(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len)2664 PgSqlHostDataSource::del4(const SubnetID& subnet_id,
2665                           const Host::IdentifierType& identifier_type,
2666                           const uint8_t* identifier_begin,
2667                           const size_t identifier_len) {
2668     // Get a context
2669     PgSqlHostContextAlloc get_context(*impl_);
2670     PgSqlHostContextPtr ctx = get_context.ctx_;
2671 
2672     // If operating in read-only mode, throw exception.
2673     impl_->checkReadOnly(ctx);
2674 
2675     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2676 
2677     // Subnet-id
2678     bind_array->add(subnet_id);
2679 
2680     // identifier-type
2681     bind_array->add(static_cast<uint8_t>(identifier_type));
2682 
2683     // identifier
2684     bind_array->add(identifier_begin, identifier_len);
2685 
2686     return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
2687                                 bind_array));
2688 }
2689 
2690 bool
del6(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len)2691 PgSqlHostDataSource::del6(const SubnetID& subnet_id,
2692                           const Host::IdentifierType& identifier_type,
2693                           const uint8_t* identifier_begin,
2694                           const size_t identifier_len) {
2695     // Get a context
2696     PgSqlHostContextAlloc get_context(*impl_);
2697     PgSqlHostContextPtr ctx = get_context.ctx_;
2698 
2699     // If operating in read-only mode, throw exception.
2700     impl_->checkReadOnly(ctx);
2701 
2702     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2703 
2704     // Subnet-id
2705     bind_array->add(subnet_id);
2706 
2707     // identifier-type
2708     bind_array->add(static_cast<uint8_t>(identifier_type));
2709 
2710     // identifier
2711     bind_array->add(identifier_begin, identifier_len);
2712 
2713     return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
2714                                 bind_array));
2715 }
2716 
2717 ConstHostCollection
getAll(const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const2718 PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
2719                             const uint8_t* identifier_begin,
2720                             const size_t identifier_len) const {
2721     // Get a context
2722     PgSqlHostContextAlloc get_context(*impl_);
2723     PgSqlHostContextPtr ctx = get_context.ctx_;
2724 
2725     // Set up the WHERE clause value
2726     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2727 
2728     // Identifier value.
2729     bind_array->add(identifier_begin, identifier_len);
2730 
2731     // Identifier type.
2732     bind_array->add(static_cast<uint8_t>(identifier_type));
2733 
2734     ConstHostCollection result;
2735     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
2736                              bind_array, ctx->host_ipv46_exchange_, result, false);
2737 
2738     return (result);
2739 }
2740 
2741 ConstHostCollection
getAll4(const SubnetID & subnet_id) const2742 PgSqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
2743     // Get a context
2744     PgSqlHostContextAlloc get_context(*impl_);
2745     PgSqlHostContextPtr ctx = get_context.ctx_;
2746 
2747     // Set up the WHERE clause value
2748     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2749 
2750     // Add the subnet id.
2751     bind_array->add(subnet_id);
2752 
2753     ConstHostCollection result;
2754     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4,
2755                              bind_array, ctx->host_ipv4_exchange_, result, false);
2756 
2757     return (result);
2758 }
2759 
2760 ConstHostCollection
getAll6(const SubnetID & subnet_id) const2761 PgSqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
2762     // Get a context
2763     PgSqlHostContextAlloc get_context(*impl_);
2764     PgSqlHostContextPtr ctx = get_context.ctx_;
2765 
2766     // Set up the WHERE clause value
2767     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2768 
2769     // Add the subnet id.
2770     bind_array->add(subnet_id);
2771 
2772     ConstHostCollection result;
2773     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6,
2774                              bind_array, ctx->host_ipv6_exchange_, result, false);
2775 
2776     return (result);
2777 }
2778 
2779 ConstHostCollection
getAllbyHostname(const std::string & hostname) const2780 PgSqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
2781     // Get a context
2782     PgSqlHostContextAlloc get_context(*impl_);
2783     PgSqlHostContextPtr ctx = get_context.ctx_;
2784 
2785     // Set up the WHERE clause value
2786     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2787 
2788     // Add the hostname.
2789     bind_array->add(hostname);
2790 
2791     ConstHostCollection result;
2792     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME,
2793                              bind_array, ctx->host_ipv46_exchange_, result, false);
2794 
2795     return (result);
2796 }
2797 
2798 ConstHostCollection
getAllbyHostname4(const std::string & hostname,const SubnetID & subnet_id) const2799 PgSqlHostDataSource::getAllbyHostname4(const std::string& hostname,
2800                                        const SubnetID& subnet_id) const {
2801     // Get a context
2802     PgSqlHostContextAlloc get_context(*impl_);
2803     PgSqlHostContextPtr ctx = get_context.ctx_;
2804 
2805     // Set up the WHERE clause value
2806     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2807 
2808     // Add the hostname.
2809     bind_array->add(hostname);
2810 
2811     // Add the subnet id.
2812     bind_array->add(subnet_id);
2813 
2814     ConstHostCollection result;
2815     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
2816                              bind_array, ctx->host_ipv4_exchange_, result, false);
2817 
2818     return (result);
2819 }
2820 
2821 ConstHostCollection
getAllbyHostname6(const std::string & hostname,const SubnetID & subnet_id) const2822 PgSqlHostDataSource::getAllbyHostname6(const std::string& hostname,
2823                                        const SubnetID& subnet_id) const {
2824     // Get a context
2825     PgSqlHostContextAlloc get_context(*impl_);
2826     PgSqlHostContextPtr ctx = get_context.ctx_;
2827 
2828     // Set up the WHERE clause value
2829     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2830 
2831     // Add the hostname.
2832     bind_array->add(hostname);
2833 
2834     // Add the subnet id.
2835     bind_array->add(subnet_id);
2836 
2837     ConstHostCollection result;
2838     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
2839                              bind_array, ctx->host_ipv6_exchange_, result, false);
2840 
2841     return (result);
2842 }
2843 
2844 ConstHostCollection
getPage4(const SubnetID & subnet_id,size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2845 PgSqlHostDataSource::getPage4(const SubnetID& subnet_id,
2846                               size_t& /*source_index*/,
2847                               uint64_t lower_host_id,
2848                               const HostPageSize& page_size) const {
2849     // Get a context
2850     PgSqlHostContextAlloc get_context(*impl_);
2851     PgSqlHostContextPtr ctx = get_context.ctx_;
2852 
2853     // Set up the WHERE clause value
2854     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2855 
2856     // Add the subnet id.
2857     bind_array->add(subnet_id);
2858 
2859     // Add the lower bound host id.
2860     bind_array->add(lower_host_id);
2861 
2862     // Add the page size value.
2863     string page_size_data =
2864         boost::lexical_cast<std::string>(page_size.page_size_);
2865     bind_array->add(page_size_data);
2866 
2867     ConstHostCollection result;
2868     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
2869                              bind_array, ctx->host_ipv4_exchange_, result, false);
2870 
2871     return (result);
2872 }
2873 
2874 ConstHostCollection
getPage6(const SubnetID & subnet_id,size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2875 PgSqlHostDataSource::getPage6(const SubnetID& subnet_id,
2876                               size_t& /*source_index*/,
2877                               uint64_t lower_host_id,
2878                               const HostPageSize& page_size) const {
2879     // Get a context
2880     PgSqlHostContextAlloc get_context(*impl_);
2881     PgSqlHostContextPtr ctx = get_context.ctx_;
2882 
2883     // Set up the WHERE clause value
2884     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2885 
2886     // Add the subnet id.
2887     bind_array->add(subnet_id);
2888 
2889     // Add the lower bound host id.
2890     bind_array->add(lower_host_id);
2891 
2892     // Add the page size value.
2893     string page_size_data =
2894         boost::lexical_cast<std::string>(page_size.page_size_);
2895     bind_array->add(page_size_data);
2896 
2897     ConstHostCollection result;
2898     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
2899                              bind_array, ctx->host_ipv6_exchange_, result, false);
2900 
2901     return (result);
2902 }
2903 
2904 ConstHostCollection
getPage4(size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2905 PgSqlHostDataSource::getPage4(size_t& /*source_index*/,
2906                               uint64_t lower_host_id,
2907                               const HostPageSize& page_size) const {
2908     // Get a context
2909     PgSqlHostContextAlloc get_context(*impl_);
2910     PgSqlHostContextPtr ctx = get_context.ctx_;
2911 
2912     // Set up the WHERE clause value
2913     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2914 
2915     // Add the lower bound host id.
2916     bind_array->add(lower_host_id);
2917 
2918     // Add the page size value.
2919     string page_size_data =
2920         boost::lexical_cast<std::string>(page_size.page_size_);
2921     bind_array->add(page_size_data);
2922 
2923     ConstHostCollection result;
2924     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE4,
2925                              bind_array, ctx->host_ipv4_exchange_, result, false);
2926 
2927     return (result);
2928 }
2929 
2930 ConstHostCollection
getPage6(size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2931 PgSqlHostDataSource::getPage6(size_t& /*source_index*/,
2932                               uint64_t lower_host_id,
2933                               const HostPageSize& page_size) const {
2934     // Get a context
2935     PgSqlHostContextAlloc get_context(*impl_);
2936     PgSqlHostContextPtr ctx = get_context.ctx_;
2937 
2938     // Set up the WHERE clause value
2939     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2940 
2941     // Add the lower bound host id.
2942     bind_array->add(lower_host_id);
2943 
2944     // Add the page size value.
2945     string page_size_data =
2946         boost::lexical_cast<std::string>(page_size.page_size_);
2947     bind_array->add(page_size_data);
2948 
2949     ConstHostCollection result;
2950     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE6,
2951                              bind_array, ctx->host_ipv6_exchange_, result, false);
2952 
2953     return (result);
2954 }
2955 
2956 ConstHostCollection
getAll4(const asiolink::IOAddress & address) const2957 PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
2958     // Get a context
2959     PgSqlHostContextAlloc get_context(*impl_);
2960     PgSqlHostContextPtr ctx = get_context.ctx_;
2961 
2962     // Set up the WHERE clause value
2963     PsqlBindArrayPtr bind_array(new PsqlBindArray());
2964 
2965     // v4 Reservation address
2966     bind_array->add(address);
2967 
2968     ConstHostCollection result;
2969     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR,
2970                              bind_array, ctx->host_ipv4_exchange_, result, false);
2971 
2972     return (result);
2973 }
2974 
2975 ConstHostPtr
get4(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const2976 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
2977                           const Host::IdentifierType& identifier_type,
2978                           const uint8_t* identifier_begin,
2979                           const size_t identifier_len) const {
2980     // Get a context
2981     PgSqlHostContextAlloc get_context(*impl_);
2982     PgSqlHostContextPtr ctx = get_context.ctx_;
2983 
2984     return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
2985                            PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
2986                            ctx->host_ipv4_exchange_));
2987 }
2988 
2989 ConstHostPtr
get4(const SubnetID & subnet_id,const asiolink::IOAddress & address) const2990 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
2991                           const asiolink::IOAddress& address) const {
2992     // Get a context
2993     PgSqlHostContextAlloc get_context(*impl_);
2994     PgSqlHostContextPtr ctx = get_context.ctx_;
2995 
2996     if (!address.isV4()) {
2997         isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
2998                   " wrong address type, address supplied is an IPv6 address");
2999     }
3000 
3001     // Set up the WHERE clause value
3002     PsqlBindArrayPtr bind_array(new PsqlBindArray());
3003 
3004     // Add the subnet id
3005     bind_array->add(subnet_id);
3006 
3007     // Add the address
3008     bind_array->add(address);
3009 
3010     ConstHostCollection collection;
3011     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3012                              bind_array, ctx->host_ipv4_exchange_, collection, true);
3013 
3014     // Return single record if present, else clear the host.
3015     ConstHostPtr result;
3016     if (!collection.empty()) {
3017         result = *collection.begin();
3018     }
3019 
3020     return (result);
3021 }
3022 
3023 ConstHostCollection
getAll4(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3024 PgSqlHostDataSource::getAll4(const SubnetID& subnet_id,
3025                              const asiolink::IOAddress& address) const {
3026     // Get a context
3027     PgSqlHostContextAlloc get_context(*impl_);
3028     PgSqlHostContextPtr ctx = get_context.ctx_;
3029 
3030     if (!address.isV4()) {
3031         isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
3032                   " wrong address type, address supplied is an IPv6 address");
3033     }
3034 
3035     // Set up the WHERE clause value
3036     PsqlBindArrayPtr bind_array(new PsqlBindArray());
3037 
3038     // Add the subnet id
3039     bind_array->add(subnet_id);
3040 
3041     // Add the address
3042     bind_array->add(address);
3043 
3044     ConstHostCollection collection;
3045     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3046                              bind_array, ctx->host_ipv4_exchange_, collection, false);
3047     return (collection);
3048 }
3049 
3050 ConstHostPtr
get6(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const3051 PgSqlHostDataSource::get6(const SubnetID& subnet_id,
3052                           const Host::IdentifierType& identifier_type,
3053                           const uint8_t* identifier_begin,
3054                           const size_t identifier_len) const {
3055     // Get a context
3056     PgSqlHostContextAlloc get_context(*impl_);
3057     PgSqlHostContextPtr ctx = get_context.ctx_;
3058 
3059     return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
3060                            PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
3061                            ctx->host_ipv6_exchange_));
3062 }
3063 
3064 ConstHostPtr
get6(const asiolink::IOAddress & prefix,const uint8_t prefix_len) const3065 PgSqlHostDataSource::get6(const asiolink::IOAddress& prefix,
3066                           const uint8_t prefix_len) const {
3067     if (!prefix.isV6()) {
3068         isc_throw(BadValue, "PgSqlHostDataSource::get6(prefix, prefix_len): "
3069                   "wrong address type, address supplied is an IPv4 address");
3070     }
3071 
3072     // Get a context
3073     PgSqlHostContextAlloc get_context(*impl_);
3074     PgSqlHostContextPtr ctx = get_context.ctx_;
3075 
3076     // Set up the WHERE clause value
3077     PsqlBindArrayPtr bind_array(new PsqlBindArray());
3078 
3079     // Add the prefix
3080     bind_array->add(prefix);
3081 
3082     // Add the prefix length
3083     bind_array->add(prefix_len);
3084 
3085     ConstHostCollection collection;
3086     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
3087                              bind_array, ctx->host_ipv6_exchange_, collection, true);
3088 
3089     // Return single record if present, else clear the host.
3090     ConstHostPtr result;
3091     if (!collection.empty()) {
3092         result = *collection.begin();
3093     }
3094 
3095     return (result);
3096 }
3097 
3098 ConstHostPtr
get6(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3099 PgSqlHostDataSource::get6(const SubnetID& subnet_id,
3100                           const asiolink::IOAddress& address) const {
3101     if (!address.isV6()) {
3102         isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3103                   "wrong address type, address supplied is an IPv4 address");
3104     }
3105 
3106     // Get a context
3107     PgSqlHostContextAlloc get_context(*impl_);
3108     PgSqlHostContextPtr ctx = get_context.ctx_;
3109 
3110     // Set up the WHERE clause value
3111     PsqlBindArrayPtr bind_array(new PsqlBindArray());
3112 
3113     // Add the subnet id
3114     bind_array->add(subnet_id);
3115 
3116     // Add the prefix
3117     bind_array->add(address);
3118 
3119     ConstHostCollection collection;
3120     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3121                              bind_array, ctx->host_ipv6_exchange_, collection, true);
3122 
3123     // Return single record if present, else clear the host.
3124     ConstHostPtr result;
3125     if (!collection.empty()) {
3126         result = *collection.begin();
3127     }
3128 
3129     return (result);
3130 }
3131 
3132 ConstHostCollection
getAll6(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3133 PgSqlHostDataSource::getAll6(const SubnetID& subnet_id,
3134                              const asiolink::IOAddress& address) const {
3135     if (!address.isV6()) {
3136         isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3137                   "wrong address type, address supplied is an IPv4 address");
3138     }
3139 
3140     // Get a context
3141     PgSqlHostContextAlloc get_context(*impl_);
3142     PgSqlHostContextPtr ctx = get_context.ctx_;
3143 
3144     // Set up the WHERE clause value
3145     PsqlBindArrayPtr bind_array(new PsqlBindArray());
3146 
3147     // Add the subnet id
3148     bind_array->add(subnet_id);
3149 
3150     // Add the prefix
3151     bind_array->add(address);
3152 
3153     ConstHostCollection collection;
3154     impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3155                              bind_array, ctx->host_ipv6_exchange_, collection, false);
3156     return (collection);
3157 }
3158 
3159 
3160 // Miscellaneous database methods.
3161 
3162 std::string
getName() const3163 PgSqlHostDataSource::getName() const {
3164     std::string name = "";
3165     // Get a context
3166     PgSqlHostContextAlloc get_context(*impl_);
3167     PgSqlHostContextPtr ctx = get_context.ctx_;
3168 
3169     try {
3170         name = ctx->conn_.getParameter("name");
3171     } catch (...) {
3172         // Return an empty name
3173     }
3174     return (name);
3175 }
3176 
3177 std::string
getDescription() const3178 PgSqlHostDataSource::getDescription() const {
3179     return (std::string("Host data source that stores host information"
3180                         "in PostgreSQL database"));
3181 }
3182 
3183 std::pair<uint32_t, uint32_t>
getVersion() const3184 PgSqlHostDataSource::getVersion() const {
3185     return(impl_->getVersion());
3186 }
3187 
3188 void
commit()3189 PgSqlHostDataSource::commit() {
3190     // Get a context
3191     PgSqlHostContextAlloc get_context(*impl_);
3192     PgSqlHostContextPtr ctx = get_context.ctx_;
3193 
3194     // If operating in read-only mode, throw exception.
3195     impl_->checkReadOnly(ctx);
3196     ctx->conn_.commit();
3197 }
3198 
3199 void
rollback()3200 PgSqlHostDataSource::rollback() {
3201     // Get a context
3202     PgSqlHostContextAlloc get_context(*impl_);
3203     PgSqlHostContextPtr ctx = get_context.ctx_;
3204 
3205     // If operating in read-only mode, throw exception.
3206     impl_->checkReadOnly(ctx);
3207     ctx->conn_.rollback();
3208 }
3209 
3210 bool
setIPReservationsUnique(const bool unique)3211 PgSqlHostDataSource::setIPReservationsUnique(const bool unique) {
3212     impl_->ip_reservations_unique_ = unique;
3213     return (true);
3214 }
3215 
3216 bool
isUnusable()3217 PgSqlHostDataSource::isUnusable() {
3218     return (impl_->unusable_);
3219 }
3220 
3221 }  // namespace dhcp
3222 }  // namespace isc
3223