mysql_lease_mgr.cc 66.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <config.h>
16

17
#include <asiolink/io_address.h>
Stephen Morris's avatar
Stephen Morris committed
18
#include <dhcp/duid.h>
19
#include <dhcp/hwaddr.h>
20
#include <dhcpsrv/dhcpsrv_log.h>
21
#include <dhcpsrv/mysql_lease_mgr.h>
22

23
#include <boost/static_assert.hpp>
24
25
26
27
#include <mysql/mysqld_error.h>

#include <iostream>
#include <iomanip>
28
#include <sstream>
29
30
31
#include <string>
#include <time.h>

32
using namespace isc;
33
using namespace isc::dhcp;
34
35
using namespace std;

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/// @file
///
/// This file holds the implementation of the Lease Manager using MySQL.  The
/// implementation uses MySQL's C API, as it comes as standard with the MySQL
/// client libraries.
///
/// In general, each of the database access methods corresponds to one SQL
/// statement.  To avoid the overhead of parsing a statement every time it is
/// used, when the database is opened "prepared statements" are created -
/// essentially doing the SQL parsing up front.  Every time a method is used
/// to access data, the corresponding prepared statement is referenced. Each
/// prepared statement contains a set of placeholders for data, each
/// placeholder being for:
///
/// - data being added to the database (as in adding or updating a lease)
/// - data being retrieved from the database (as in getting lease information)
/// - selection criteria used to determine which records to update/retrieve.
///
/// All such data is associated with the prepared statment using an array of
/// MYSQL_BIND structures.  Each element in the array corresponds to one
/// parameter in the prepared statement - the first element in the array is
/// associated with the first parameter, the second element with the second
/// parameter etc.
///
/// Within this file, the setting up of the MYSQL_BIND arrays for data being
/// passed to and retrieved from the database is handled in the
/// isc::dhcp::MySqlLease4Exchange and isc::dhcp::MySqlLease6Exchange classes.
/// The classes also hold intermediate variables required for exchanging some
/// of the data.
///
/// With these exchange objects in place, many of the methods follow similar
/// logic:
/// - Set up the MYSQL_BIND array for data being transferred to/from the
///   database.  For data being transferred to the database, some of the
///   data is extracted from the lease to intermediate variables, whilst
///   in other cases the MYSQL_BIND arrays point to the data in the lease.
/// - Set up the MYSQL_BIND array for the data selection parameters.
/// - Bind these arrays to the prepared statement.
/// - Execute the statement.
/// - If there is output, copy the data from the bound variables to the output
///   lease object.

78
79
namespace {
///@{
80
81

/// @brief Maximum size of database fields
82
83
84
85
86
///
/// The following constants define buffer sizes for variable length database
/// fields.  The values should be greater than or equal to the length set in
/// the schema definition.
///
87
88
89
/// The exception is the length of any VARCHAR fields: buffers for these should
/// be set greater than or equal to the length of the field plus 1: this allows
/// for the insertion of a trailing null whatever data is returned.
90

91
92
93
94
95
96
97
98
99
/// @brief Maximum size of an IPv6 address represented as a text string.
///
/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;

/// @brief Maximum size of a hardware address.
const size_t HWADDR_MAX_LEN = 20;

100
101
102
103
/// @brief MySQL True/False constants
///
/// Declare typed values so as to avoid problems of data conversion.  These
/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
104
/// avoid any likely conflicts with variables in header files named TRUE or
105
/// FALSE.
106
107
108
109

const my_bool MLM_FALSE = 0;                ///< False value
const my_bool MLM_TRUE = 1;                 ///< True value

110
///@}
111
112
113

/// @brief MySQL Selection Statements
///
114
115
116
/// Each statement is associated with an index, which is used to reference the
/// associated prepared statement.

117
118
119
120
121
122
struct TaggedStatement {
    MySqlLeaseMgr::StatementIndex index;
    const char*                   text;
};

TaggedStatement tagged_statements[] = {
123
124
    {MySqlLeaseMgr::DELETE_LEASE4,
                    "DELETE FROM lease4 WHERE address = ?"},
125
126
    {MySqlLeaseMgr::DELETE_LEASE6,
                    "DELETE FROM lease6 WHERE address = ?"},
127
128
129
130
131
132
133
134
135
136
    {MySqlLeaseMgr::GET_LEASE4_ADDR,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE address = ?"},
    {MySqlLeaseMgr::GET_LEASE4_CLIENTID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE client_id = ?"},
137
138
139
140
141
    {MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE client_id = ? AND subnet_id = ?"},
142
143
144
145
146
    {MySqlLeaseMgr::GET_LEASE4_HWADDR,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE hwaddr = ?"},
147
148
149
150
151
    {MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE hwaddr = ? AND subnet_id = ?"},
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    {MySqlLeaseMgr::GET_LEASE6_ADDR,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE address = ?"},
    {MySqlLeaseMgr::GET_LEASE6_DUID_IAID,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE duid = ? AND iaid = ?"},
    {MySqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE duid = ? AND iaid = ? AND subnet_id = ?"},
    {MySqlLeaseMgr::GET_VERSION,
                    "SELECT version, minor FROM schema_version"},
172
173
174
175
    {MySqlLeaseMgr::INSERT_LEASE4,
                    "INSERT INTO lease4(address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id) "
                            "VALUES (?, ?, ?, ?, ?, ?)"},
176
177
178
179
180
    {MySqlLeaseMgr::INSERT_LEASE6,
                    "INSERT INTO lease6(address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len) "
                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
181
182
183
184
185
    {MySqlLeaseMgr::UPDATE_LEASE4,
                    "UPDATE lease4 SET address = ?, hwaddr = ?, "
                        "client_id = ?, valid_lifetime = ?, expire = ?, "
                        "subnet_id = ? "
                            "WHERE address = ?"},
186
187
188
189
190
191
192
193
    {MySqlLeaseMgr::UPDATE_LEASE6,
                    "UPDATE lease6 SET address = ?, duid = ?, "
                        "valid_lifetime = ?, expire = ?, subnet_id = ?, "
                        "pref_lifetime = ?, lease_type = ?, iaid = ?, "
                        "prefix_len = ? "
                            "WHERE address = ?"},
    // End of list sentinel
    {MySqlLeaseMgr::NUM_STATEMENTS, NULL}
194
195
};

196
197
};  // Anonymous namespace

198
199


200
201
202
namespace isc {
namespace dhcp {

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/// @brief Common MySQL and Lease Data Methods
///
/// The MySqlLease4Exchange and MySqlLease6Exchange classes provide the
/// functionaility to set up binding information between variables in the
/// program and data extracted from the database.  This class is the common
/// base to both of them, containing some common methods.

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

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

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

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

        return (result);
    }
};


266
267
268
269
270
/// @brief Exchange MySQL and Lease4 Data
///
/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
/// describe the parameters in the prepared statements.  Where information is
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
271
/// structure is identical.  This class handles the creation of that array.
272
273
///
/// Owing to the MySQL API, the process requires some intermediate variables
274
/// to hold things like data length etc.  This object holds those variables.
275
276
277
278
///
/// @note There are no unit tests for this class.  It is tested indirectly
/// in all MySqlLeaseMgr::xxx4() calls where it is used.

279
class MySqlLease4Exchange : public MySqlLeaseExchange {
280
281
    /// @brief Set number of database columns for this lease structure
    static const size_t LEASE_COLUMNS = 6;
282

283
public:
284
285
    /// @brief Constructor
    ///
286
    /// The initialization of the variables here is only to satisfy cppcheck -
287
288
    /// all variables are initialized/set in the methods before they are used.
    MySqlLease4Exchange() : addr4_(0), hwaddr_length_(0), client_id_length_(0) {
289
290
        memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
        memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
291
        std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
292

293
294
295
296
297
298
299
300
        // Set the column names (for error messages)
        columns_[0] = "address";
        columns_[1] = "hwaddr";
        columns_[2] = "client_id";
        columns_[3] = "valid_lifetime";
        columns_[4] = "expire";
        columns_[5] = "subnet_id";
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
301
302
303
304
    }

    /// @brief Create MYSQL_BIND objects for Lease4 Pointer
    ///
305
306
    /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
    /// the database.
307
    ///
308
309
    /// @param lease Lease object to be added to the database.  None of the
    ///        fields in the lease are modified - the lease data is only read.
310
311
312
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const Lease4Ptr& lease) {
313

314
315
316
        // Store lease object to ensure it remains valid.
        lease_ = lease;

317
        // Initialize prior to constructing the array of MYSQL_BIND structures.
318
319
        memset(bind_, 0, sizeof(bind_));

320
321
322
        // Set up the structures for the various components of the lease4
        // structure.

323
        // Address: uint32_t
324
        // The address in the Lease structure is an IOAddress object.  Convert
325
        // this to an integer for storage.
326
327
328
        addr4_ = static_cast<uint32_t>(lease_->addr_);
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
329
        bind_[0].is_unsigned = MLM_TRUE;
330

331
        // hwaddr: varbinary(128)
332
333
334
335
336
337
338
339
        // For speed, we avoid copying the data into temporary storage and
        // instead extract it from the lease structure directly.
        hwaddr_length_ = lease_->hwaddr_.size();
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(&(lease_->hwaddr_[0]));
        bind_[1].buffer_length = hwaddr_length_;
        bind_[1].length = &hwaddr_length_;

340
        // client_id: varbinary(128)
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
        if (lease_->client_id_) {
            client_id_ = lease_->client_id_->getClientId();
            client_id_length_ = client_id_.size();
            bind_[2].buffer_type = MYSQL_TYPE_BLOB;
            bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
            bind_[2].buffer_length = client_id_length_;
            bind_[2].length = &client_id_length_;
        } else {
            bind_[2].buffer_type = MYSQL_TYPE_NULL;

            // According to http://dev.mysql.com/doc/refman/5.5/en/
            // c-api-prepared-statement-data-structures.html, the other
            // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
            // but let's set them to some sane values in case earlier versions
            // didn't have that assumption.
            static my_bool no_clientid = MLM_TRUE;
            bind_[2].buffer = NULL;
            bind_[2].is_null = &no_clientid;
        }
360
361
362
363

        // valid lifetime: unsigned int
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
364
        bind_[3].is_unsigned = MLM_TRUE;
365
366
367
368

        // expire: timestamp
        // The lease structure holds the client last transmission time (cltt_)
        // For convenience for external tools, this is converted to lease
369
        // expiry time (expire).  The relationship is given by:
370
371
372
        //
        // expire = cltt_ + valid_lft_
        //
373
        // @todo Handle overflows - a large enough valid_lft_ could cause
374
        //       an overflow on a 32-bit system.
375
376
377
378
379
380
381
382
383
384
        MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
                                             expire_);
        bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[4].buffer = reinterpret_cast<char*>(&expire_);
        bind_[4].buffer_length = sizeof(expire_);

        // subnet_id: unsigned int
        // Can use lease_->subnet_id_ directly as it is of type uint32_t.
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
385
        bind_[5].is_unsigned = MLM_TRUE;
386

387
388
389
390
391
392
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);

393
394
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
395
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
396
397
398
399
400
    }

    /// @brief Create BIND array to receive data
    ///
    /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
401
    /// After data is successfully received, getLeaseData() can be used to copy
402
403
404
405
    /// it to a Lease6 object.
    ///
    std::vector<MYSQL_BIND> createBindForReceive() {

406
        // Initialize MYSQL_BIND array.
407
408
409
410
411
        memset(bind_, 0, sizeof(bind_));

        // address:  uint32_t
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
412
        bind_[0].is_unsigned = MLM_TRUE;
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430

        // hwaddr: varbinary(20)
        hwaddr_length_ = sizeof(hwaddr_buffer_);
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
        bind_[1].buffer_length = hwaddr_length_;
        bind_[1].length = &hwaddr_length_;

        // client_id: varbinary(128)
        client_id_length_ = sizeof(client_id_buffer_);
        bind_[2].buffer_type = MYSQL_TYPE_BLOB;
        bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
        bind_[2].buffer_length = client_id_length_;
        bind_[2].length = &client_id_length_;

        // lease_time: unsigned int
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
431
        bind_[3].is_unsigned = MLM_TRUE;
432
433
434
435
436
437
438
439
440

        // expire: timestamp
        bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[4].buffer = reinterpret_cast<char*>(&expire_);
        bind_[4].buffer_length = sizeof(expire_);

        // subnet_id: unsigned int
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
441
        bind_[5].is_unsigned = MLM_TRUE;
442

443
444
445
446
447
448
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);

449
450
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
451
        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
452
453
454
455
456
    }

    /// @brief Copy Received Data into Lease6 Object
    ///
    /// Called after the MYSQL_BIND array created by createBindForReceive()
457
    /// has been used, this copies data from the internal member variables
458
    /// into a Lease4 objec.
459
    ///
460
    /// @return Lease4Ptr Pointer to a Lease6 object holding the relevant
461
462
    ///         data.
    Lease4Ptr getLeaseData() {
463
464
        // Convert times received from the database to times for the lease
        // structure
465
466
467
        time_t cltt = 0;
        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);

468
        // note that T1 and T2 are not stored
469
470
        return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
                                     client_id_buffer_, client_id_length_,
471
                                     valid_lifetime_, 0, 0, cltt, subnet_id_)));
472
473
    }

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

488
private:
489

490
    // Note: All array lengths are equal to the corresponding variable in the
491
492
    //       schema.
    // Note: Arrays are declared fixed length for speed of creation
493
    uint32_t        addr4_;             ///< IPv4 address
494
495
496
    MYSQL_BIND      bind_[LEASE_COLUMNS]; ///< Bind array
    std::string     columns_[LEASE_COLUMNS];///< Column names
    my_bool         error_[LEASE_COLUMNS];  ///< Error array
497
    std::vector<uint8_t> hwaddr_;       ///< Hardware address
498
499
    uint8_t         hwaddr_buffer_[HWADDR_MAX_LEN];
                                        ///< Hardware address buffer
500
501
    unsigned long   hwaddr_length_;     ///< Hardware address length
    std::vector<uint8_t> client_id_;    ///< Client identification
Stephen Morris's avatar
Stephen Morris committed
502
    uint8_t         client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
503
                                        ///< Client ID buffer
504
505
506
507
    unsigned long   client_id_length_;  ///< Client ID address length
    MYSQL_TIME      expire_;            ///< Lease expiry time
    Lease4Ptr       lease_;             ///< Pointer to lease object
    uint32_t        subnet_id_;         ///< Subnet identification
508
    uint32_t        valid_lifetime_;    ///< Lease time
509
510
511
512
};



513
/// @brief Exchange MySQL and Lease6 Data
514
///
515
516
/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
/// describe the parameters in the prepared statements.  Where information is
517
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
518
/// structure is identical.  This class handles the creation of that array.
519
///
520
/// Owing to the MySQL API, the process requires some intermediate variables
521
/// to hold things like data length etc.  This object holds those variables.
522
523
524
///
/// @note There are no unit tests for this class.  It is tested indirectly
/// in all MySqlLeaseMgr::xxx6() calls where it is used.
525

526
class MySqlLease6Exchange : public MySqlLeaseExchange {
527
528
    /// @brief Set number of database columns for this lease structure
    static const size_t LEASE_COLUMNS = 9;
529

530
531
public:
    /// @brief Constructor
532
    ///
533
534
535
    /// The initialization of the variables here is nonly to satisfy cppcheck -
    /// all variables are initialized/set in the methods before they are used.
    MySqlLease6Exchange() : addr6_length_(0), duid_length_(0) {
536
537
        memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
        memset(duid_buffer_, 0, sizeof(duid_buffer_));
538
        std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
539

540
541
542
543
544
545
546
547
548
549
        // Set the column names (for error messages)
        columns_[0] = "address";
        columns_[1] = "duid";
        columns_[2] = "valid_lifetime";
        columns_[3] = "expire";
        columns_[4] = "subnet_id";
        columns_[5] = "pref_lifetime";
        columns_[6] = "lease_type";
        columns_[7] = "iaid";
        columns_[8] = "prefix_len";
550
        BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
551
552
553
554
    }

    /// @brief Create MYSQL_BIND objects for Lease6 Pointer
    ///
555
556
    /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
    /// the database.
557
    ///
558
559
560
561
    /// @param lease Lease object to be added to the database.
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const Lease6Ptr& lease) {
562
563
564
        // Store lease object to ensure it remains valid.
        lease_ = lease;

565
566
        // Ensure bind_ array clear for constructing the MYSQL_BIND structures
        // for this lease.
567
568
        memset(bind_, 0, sizeof(bind_));

569
        // address: varchar(39)
570
571
572
        addr6_ = lease_->addr_.toText();
        addr6_length_ = addr6_.size();

573
574
575
        // In the following statement, the string is being read.  However, the
        // MySQL C interface does not use "const", so the "buffer" element
        // is declared as "char*" instead of "const char*".  To resolve this,
576
        // the "const" is discarded.  (Note that the address of addr6_.c_str()
577
        // is guaranteed to be valid until the next non-const operation on
578
579
        // addr6_.)
        //
580
581
582
        // The const_cast could be avoided by copying the string to a writeable
        // buffer and storing the address of that in the "buffer" element.
        // However, this introduces a copy operation (with additional overhead)
583
        // purely to get round the structures introduced by design of the
584
585
586
        // MySQL interface (which uses the area pointed to by "buffer" as input
        // when specifying query parameters and as output when retrieving data).
        // For that reason, "const_cast" has been used.
587
588
589
590
591
        bind_[0].buffer_type = MYSQL_TYPE_STRING;
        bind_[0].buffer = const_cast<char*>(addr6_.c_str());
        bind_[0].buffer_length = addr6_length_;
        bind_[0].length = &addr6_length_;

592
593
594
        // duid: varchar(128)
        duid_ = lease_->duid_->getDuid();
        duid_length_ = duid_.size();
595

596
597
598
599
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(&(duid_[0]));
        bind_[1].buffer_length = duid_length_;
        bind_[1].length = &duid_length_;
600

601
        // valid lifetime: unsigned int
602
        bind_[2].buffer_type = MYSQL_TYPE_LONG;
603
        bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
604
        bind_[2].is_unsigned = MLM_TRUE;
605
606

        // expire: timestamp
607
608
609
610
611
        // The lease structure holds the client last transmission time (cltt_)
        // For convenience for external tools, this is converted to lease
        /// expiry time (expire).  The relationship is given by:
        //
        // expire = cltt_ + valid_lft_
612
        //
613
        // @todo Handle overflows
614
615
        MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
                                             expire_);
616
617
618
        bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[3].buffer = reinterpret_cast<char*>(&expire_);
        bind_[3].buffer_length = sizeof(expire_);
619
620
621

        // subnet_id: unsigned int
        // Can use lease_->subnet_id_ directly as it is of type uint32_t.
622
623
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
624
        bind_[4].is_unsigned = MLM_TRUE;
625
626
627

        // pref_lifetime: unsigned int
        // Can use lease_->preferred_lft_ directly as it is of type uint32_t.
628
629
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
630
        bind_[5].is_unsigned = MLM_TRUE;
631
632

        // lease_type: tinyint
633
        // Must convert to uint8_t as lease_->type_ is a LeaseType variable.
634
        lease_type_ = lease_->type_;
635
636
        bind_[6].buffer_type = MYSQL_TYPE_TINY;
        bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
637
        bind_[6].is_unsigned = MLM_TRUE;
638
639
640

        // iaid: unsigned int
        // Can use lease_->iaid_ directly as it is of type uint32_t.
641
642
        bind_[7].buffer_type = MYSQL_TYPE_LONG;
        bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
643
        bind_[7].is_unsigned = MLM_TRUE;
644
645
646

        // prefix_len: unsigned tinyint
        // Can use lease_->prefixlen_ directly as it is uint32_t.
647
648
        bind_[8].buffer_type = MYSQL_TYPE_TINY;
        bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
649
        bind_[8].is_unsigned = MLM_TRUE;
650

651
652
653
654
655
656
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);

657
658
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
659
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
660
661
    }

662
663
664
665
666
667
    /// @brief Create BIND array to receive data
    ///
    /// Creates a MYSQL_BIND array to receive Lease6 data from the database.
    /// After data is successfully received, getLeaseData() is used to copy
    /// it to a Lease6 object.
    ///
668
669
    /// @return Vector of MySQL BIND objects passed to the MySQL data retrieval
    ///         functions.
670
    std::vector<MYSQL_BIND> createBindForReceive() {
671

672
        // Initialize MYSQL_BIND array.
673
674
        memset(bind_, 0, sizeof(bind_));

675
        // address:  varchar(39)
676
        // A Lease6_ address has a maximum of 39 characters.  The array is
677
678
        // one byte longer than this to guarantee that we can always null
        // terminate it whatever is returned.
679
680
681
682
683
684
685
        addr6_length_ = sizeof(addr6_buffer_) - 1;
        bind_[0].buffer_type = MYSQL_TYPE_STRING;
        bind_[0].buffer = addr6_buffer_;
        bind_[0].buffer_length = addr6_length_;
        bind_[0].length = &addr6_length_;

        // client_id: varbinary(128)
686
        duid_length_ = sizeof(duid_buffer_);
687
688
689
690
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
        bind_[1].buffer_length = duid_length_;
        bind_[1].length = &duid_length_;
691
692

        // lease_time: unsigned int
693
694
        bind_[2].buffer_type = MYSQL_TYPE_LONG;
        bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
695
        bind_[2].is_unsigned = MLM_TRUE;
696
697

        // expire: timestamp
698
699
700
        bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[3].buffer = reinterpret_cast<char*>(&expire_);
        bind_[3].buffer_length = sizeof(expire_);
701
702

        // subnet_id: unsigned int
703
704
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
705
        bind_[4].is_unsigned = MLM_TRUE;
706
707

        // pref_lifetime: unsigned int
708
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
709
        bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
710
        bind_[5].is_unsigned = MLM_TRUE;
711

712
713
714
        // lease_type: tinyint
        bind_[6].buffer_type = MYSQL_TYPE_TINY;
        bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
715
        bind_[6].is_unsigned = MLM_TRUE;
716

717
718
719
        // iaid: unsigned int
        bind_[7].buffer_type = MYSQL_TYPE_LONG;
        bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
720
        bind_[7].is_unsigned = MLM_TRUE;
721

722
        // prefix_len: unsigned tinyint
723
724
        bind_[8].buffer_type = MYSQL_TYPE_TINY;
        bind_[8].buffer = reinterpret_cast<char*>(&prefixlen_);
725
        bind_[8].is_unsigned = MLM_TRUE;
726

727
728
729
730
731
732
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);

733
734
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
735
        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
736
737
738
739
740
    }

    /// @brief Copy Received Data into Lease6 Object
    ///
    /// Called after the MYSQL_BIND array created by createBindForReceive()
741
    /// has been used, this copies data from the internal member variables
742
743
744
745
746
    /// into a Lease6 object.
    ///
    /// @return Lease6Ptr Pointer to a Lease6 object holding the relevant
    ///         data.
    ///
747
    /// @throw isc::BadValue Unable to convert Lease Type value in database
748
    Lease6Ptr getLeaseData() {
749
750
        // The address buffer is declared larger than the buffer size passed
        // to the access function so that we can always append a null byte.
751
        // Create the IOAddress object corresponding to the received data.
752
753
        addr6_buffer_[addr6_length_] = '\0';
        std::string address = addr6_buffer_;
754
        isc::asiolink::IOAddress addr(address);
755

756
        // Set the lease type in a variable of the appropriate data type, which
757
        // has been initialized with an arbitrary (but valid) value.
758
        Lease6::LeaseType type = Lease6::LEASE_IA_NA;
759
760
        switch (lease_type_) {
            case Lease6::LEASE_IA_NA:
761
                type = Lease6::LEASE_IA_NA;
762
763
764
                break;

            case Lease6::LEASE_IA_TA:
765
                type = Lease6::LEASE_IA_TA;
766
767
768
                break;

            case Lease6::LEASE_IA_PD:
769
                type = Lease6::LEASE_IA_PD;
770
771
772
                break;

            default:
773
774
                isc_throw(BadValue, "invalid lease type returned (" <<
                          lease_type_ << ") for lease with address " <<
775
                          address << ". Only 0, 1, or 2 are allowed.");
776
        }
777
778
779
780
781
782
783
784
785
786
787
788

        // Set up DUID,
        DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));

        // Create the lease and set the cltt (after converting from the
        // expire time retrieved from the database).
        Lease6Ptr result(new Lease6(type, addr, duid_ptr, iaid_,
                                    pref_lifetime_, valid_lifetime_, 0, 0,
                                    subnet_id_, prefixlen_));
        time_t cltt = 0;
        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
        result->cltt_ = cltt;
789
790
791
792

        return (result);
    }

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

807
private:
808
    // Note: All array lengths are equal to the corresponding variable in the
809
    // schema.
810
    // Note: arrays are declared fixed length for speed of creation
811
    std::string     addr6_;             ///< String form of address
812
    char            addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];  ///< Character
813
                                        ///< array form of V6 address
814
    unsigned long   addr6_length_;      ///< Length of the address
815
816
    MYSQL_BIND      bind_[LEASE_COLUMNS]; ///< Bind array
    std::string     columns_[LEASE_COLUMNS];///< Column names
817
    std::vector<uint8_t> duid_;         ///< Client identification
Stephen Morris's avatar
Stephen Morris committed
818
    uint8_t         duid_buffer_[DUID::MAX_DUID_LEN]; ///< Buffer form of DUID
819
    unsigned long   duid_length_;       ///< Length of the DUID
820
    my_bool         error_[LEASE_COLUMNS]; ///< Error indicators
821
    MYSQL_TIME      expire_;            ///< Lease expiry time
822
    uint32_t        iaid_;              ///< Identity association ID
823
824
    Lease6Ptr       lease_;             ///< Pointer to lease object
    uint8_t         lease_type_;        ///< Lease type
825
826
827
    uint8_t         prefixlen_;         ///< Prefix length
    uint32_t        pref_lifetime_;     ///< Preferred lifetime
    uint32_t        subnet_id_;         ///< Subnet identification
828
    uint32_t        valid_lifetime_;    ///< Lease time
829
830
831
};


832
833
834
835
/// @brief Fetch and Release MySQL Results
///
/// When a MySQL statement is exected, to fetch the results the function
/// mysql_stmt_fetch() must be called.  As well as getting data, this
836
837
838
/// allocates internal state.  Subsequent calls to mysql_stmt_fetch can be
/// made, but when all the data is retrieved, mysql_stmt_free_result must be
/// called to free up the resources allocated.
839
///
840
841
/// Created prior to the first fetch, this class's destructor calls
/// mysql_stmt_free_result, so eliminating the need for an explicit release
842
/// in the method calling mysql_stmt_free_result.  In this way, it guarantees
843
844
/// that the resources are released even if the MySqlLeaseMgr method concerned
/// exits via an exception.
845

846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
class MySqlFreeResult {
public:

    /// @brief Constructor
    ///
    /// Store the pointer to the statement for which data is being fetched.
    ///
    /// Note that according to the MySQL documentation, mysql_stmt_free_result
    /// only releases resources if a cursor has been allocated for the
    /// statement.  This implies that it is a no-op if none have been.  Either
    /// way, any error from mysql_stmt_free_result is ignored. (Generating
    /// an exception is not much help, as it will only confuse things if the
    /// method calling mysql_stmt_fetch is exiting via an exception.)
    MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
    {}

    /// @brief Destructor
    ///
    /// Frees up fetch context if a fetch has been successfully executed.
    ~MySqlFreeResult() {
        (void) mysql_stmt_free_result(statement_);
    }

private:
    MYSQL_STMT*     statement_;     ///< Statement for which results are freed
};
872

873
// MySqlLeaseMgr Constructor and Destructor
874

875
MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
876
    : LeaseMgr(parameters) {
877

878
    // Open the database.
879
880
    openDatabase();

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

    // Prepare all statements likely to be used.
    prepareStatements();
893

894
    // Create the exchange objects for use in exchanging data between the
895
    // program and the database.
896
    exchange4_.reset(new MySqlLease4Exchange());
897
    exchange6_.reset(new MySqlLease6Exchange());
898
899
}

900

901
902
MySqlLeaseMgr::~MySqlLeaseMgr() {
    // Free up the prepared statements, ignoring errors. (What would we do
903
904
    // about them? We're destroying this object and are not really concerned
    // with errors on a database connection that is about to go away.)
905
906
907
908
909
910
    for (int i = 0; i < statements_.size(); ++i) {
        if (statements_[i] != NULL) {
            (void) mysql_stmt_close(statements_[i]);
            statements_[i] = NULL;
        }
    }
911
912
913

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

916

917
918
919
920
// Time conversion methods.
//
// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
// from the current timezone to UTC for storage, and from UTC to the current
921
922
923
924
925
926
// timezone for retrieval.
//
// This causes no problems providing that:
// a) cltt is given in local time
// b) We let the system take care of timezone conversion when converting
//    from a time read from the database into a local time.
927
928

void
929
930
MySqlLeaseMgr::convertToDatabaseTime(time_t cltt, uint32_t valid_lifetime,
                                    MYSQL_TIME& expire) {
931

932
    // Calculate expiry time.
933
    // @TODO: handle overflows
934
935
936
    time_t expire_time = cltt + valid_lifetime;

    // Convert to broken-out time
937
938
939
940
941
942
943
944
945
946
    struct tm expire_tm;
    (void) localtime_r(&expire_time, &expire_tm);

    // Place in output expire structure.
    expire.year = expire_tm.tm_year + 1900;
    expire.month = expire_tm.tm_mon + 1;     // Note different base
    expire.day = expire_tm.tm_mday;
    expire.hour = expire_tm.tm_hour;
    expire.minute = expire_tm.tm_min;
    expire.second = expire_tm.tm_sec;
947
948
    expire.second_part = 0;                  // No fractional seconds
    expire.neg = my_bool(0);                 // Not negative
949
950
951
}

void
952
MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
953
                                       uint32_t valid_lifetime, time_t& cltt) {
954
955
956

    // Copy across fields from MYSQL_TIME structure.
    struct tm expire_tm;
957
    memset(&expire_tm, 0, sizeof(expire_tm));
958
959
960
961
962
963
964

    expire_tm.tm_year = expire.year - 1900;
    expire_tm.tm_mon = expire.month - 1;
    expire_tm.tm_mday = expire.day;
    expire_tm.tm_hour = expire.hour;
    expire_tm.tm_min = expire.minute;
    expire_tm.tm_sec = expire.second;
965
    expire_tm.tm_isdst = -1;    // Let the system work out about DST
966
967

    // Convert to local time
968
    cltt = mktime(&expire_tm) - valid_lifetime;
969
970
}

971

972

973
// Open the database using the parameters passed to the constructor.
974

975
976
977
978
void
MySqlLeaseMgr::openDatabase() {

    // Set up the values of the parameters
979
    const char* host = "localhost";
980
981
982
983
984
    string shost;
    try {
        shost = getParameter("host");
        host = shost.c_str();
    } catch (...) {
985
        // No host.  Fine, we'll use "localhost"
986
987
988
989
990
991
992
993
    }

    const char* user = NULL;
    string suser;
    try {
        suser = getParameter("user");
        user = suser.c_str();
    } catch (...) {
994
        // No user.  Fine, we'll use NULL
995
996
997
998
999
1000
1001
1002
    }

    const char* password = NULL;
    string spassword;
    try {
        spassword = getParameter("password");
        password = spassword.c_str();
    } catch (...) {
1003
        // No password.  Fine, we'll use NULL
1004
1005
1006
1007
1008
1009
1010
1011
    }

    const char* name = NULL;
    string sname;
    try {
        sname = getParameter("name");
        name = sname.c_str();
    } catch (...) {
1012
1013
        // No database name.  Throw a "NoName" exception
        isc_throw(NoDatabaseName, "must specified a name for the database");
1014
1015
    }

1016
    // Set options for the connection:
1017
    //
1018
1019
1020
1021
    // Automatic reconnection: after a period of inactivity, the client will
    // disconnect from the database.  This option causes it to automatically
    // reconnect when another operation is about to be done.
    my_bool auto_reconnect = MLM_TRUE;
1022
1023
1024
1025
1026
1027
    int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
    if (result != 0) {
        isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
                  mysql_error(mysql_));
    }

1028
1029
1030
1031
1032
1033
1034
1035
1036
    // Open the database.
    //
    // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
    // the affected rows are the number of rows found that match the
    // WHERE clause of the SQL statement, not the rows changed.  The reason
    // here is that MySQL apparently does not update a row if data has not
    // changed and so the "affected rows" (retrievable from MySQL) is zero.
    // This makes it hard to distinguish whether the UPDATE changed no rows
    // because no row matching the WHERE clause was found, or because a
1037
    // row was found but no data was altered.
1038
    MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
1039
                                       0, NULL, CLIENT_FOUND_ROWS);
1040
1041
1042
1043
1044
    if (status != mysql_) {
        isc_throw(DbOpenError, mysql_error(mysql_));
    }
}

1045
// Prepared statement setup.  The textual form of an SQL statement is stored
1046
1047
1048
// in a vector of strings (text_statements_) and is used in the output of
// error messages.  The SQL statement is also compiled into a "prepared
// statement" (stored in statements_), which avoids the overhead of compilation
1049
1050
// during use.  As prepared statements have resources allocated to them, the
// class destructor explicitly destroys them.
1051

1052
1053
1054
1055
1056
void
MySqlLeaseMgr::prepareStatement(StatementIndex index, const char* text) {
    // Validate that there is space for the statement in the statements array
    // and that nothing has been placed there before.
    if ((index >= statements_.size()) || (statements_[index] != NULL)) {
1057
1058
1059
        isc_throw(InvalidParameter, "invalid prepared statement index (" <<
                  static_cast<int>(index) << ") or indexed prepared " <<
                  "statement is not null");
1060
1061
1062
    }

    // All OK, so prepare the statement
1063
    text_statements_[index] = std::string(text);
1064
1065
1066
    statements_[index] = mysql_stmt_init(mysql_);
    if (statements_[index] == NULL) {
        isc_throw(DbOperationError, "unable to allocate MySQL prepared "
1067
                  "statement structure, reason: " << mysql_error(mysql_));
1068
1069
1070
1071
1072
1073
1074
1075
1076
    }

    int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
    if (status != 0) {
        isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
                  text << ">, reason: " << mysql_error(mysql_));
    }
}

1077

1078
1079
1080
1081
1082
void
MySqlLeaseMgr::prepareStatements() {
    // Allocate space for all statements
    statements_.clear();
    statements_.resize(NUM_STATEMENTS, NULL);
1083

1084
1085
    text_statements_.clear();
    text_statements_.resize(NUM_STATEMENTS, std::string(""));
1086

1087
1088
1089
1090
1091
    // Created the MySQL prepared statements for each DML statement.
    for (int i = 0; tagged_statements[i].text != NULL; ++i) {
        prepareStatement(tagged_statements[i].index,
                         tagged_statements[i].text);
    }
1092
1093
}

1094
// Add leases to the database.  The two public methods accept a lease object
1095
// (either V4 of V6), bind the contents to the appropriate prepared
1096
// statement, then call common code to execute the statement.
1097

1098
bool
1099
1100
MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
                              std::vector<MYSQL_BIND>& bind) {
1101
1102

    // Bind the parameters to the statement
1103
1104
    int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
    checkError(status, stindex, "unable to bind parameters");
1105
1106

    // Execute the statement
1107
    status = mysql_stmt_execute(statements_[stindex]);
1108
1109
1110
1111
1112
1113
1114
1115
    if (status != 0) {

        // Failure: check for the special case of duplicate entry.  If this is
        // the case, we return false to indicate that the row was not added.
        // Otherwise we throw an exception.
        if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
            return (false);
        }
1116
        checkError(status, stindex, "unable to execute");
1117
    }
1118

1119
1120
    // Insert succeeded
    return (true);
1121
1122
}

1123
1124
bool
MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
1125
1126
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MYSQL_ADD_ADDR4).arg(lease->addr_.toText());
1127

1128
1129
1130
1131
    // Create the MYSQL_BIND array for the lease
    std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);

    // ... and drop to common code.
1132
    return (addLeaseCommon(INSERT_LEASE4, bind));
1133
1134
1135
1136
}

bool
MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
1137
1138
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MYSQL_ADD_ADDR6).arg(lease->addr_.toText());
1139

1140
1141
1142
1143
    // Create the MYSQL_BIND array for the lease
    std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);

    // ... and drop to common code.
1144
    return (addLeaseCommon(INSERT_LEASE6, bind));
1145
1146
}

1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
// Extraction of leases from the database.
//
// All getLease() methods ultimately call getLeaseCollection().  This
// binds the input parameters passed to it with the appropriate prepared
// statement and executes the statement.  It then gets the results from the
// database.  getlease() methods that expect a single result back call it
// with the "single" parameter set true: this causes an exception to be
// generated if multiple records can be retrieved from the result set. (Such
// an occurrence either indicates corruption in the database, or that an
// assumption that a query can only return a single record is incorrect.)
// Methods that require a collection of records have "single" set to the
// default value of false.  The logic is the same for both Lease4 and Lease6
// objects,  so the code is templated.
//
// Methods that require a collection of objects access this method through
// two interface methods (also called getLeaseCollection()).  These are
// short enough as to be defined in the header file: all they do is to supply
// the appropriate MySqlLeaseXExchange object depending on the type of the
// LeaseCollection objects passed to them.
//
// Methods that require a single object to be returned access the method
// through two interface methods (called getLease()).  As well as supplying
// the appropriate exchange object, they convert between lease collection
// holding zero or one leases into an appropriate Lease object.
1171
1172
1173

template <typename Exchange, typename LeaseCollection>
void MySqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
1174
                                       MYSQL_BIND* bind,
1175
                                       Exchange& exchange,
1176
1177
                                       LeaseCollection& result,
                                       bool single) const {
1178

1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
    // Bind the selection parameters to the statement