summaryrefslogtreecommitdiffstats
path: root/src/lib/mysql/mysql_binding.cc
blob: 67ad95d09ef51d8c7f2df8f04c6230a1785ca4aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_address.h>
#include <exceptions/exceptions.h>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <mysql/mysql_binding.h>

using namespace boost::posix_time;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::util;

namespace isc {
namespace db {

std::string
MySqlBinding::getString() const {
    // Make sure the binding type is text.
    validateAccess<std::string>();
    if (length_ == 0) {
        return (std::string());
    }
    return (std::string(buffer_.begin(), buffer_.begin() + length_));
}

std::string
MySqlBinding::getStringOrDefault(const std::string& default_value) const {
    if (amNull()) {
        return (default_value);
    }
    return (getString());
}

ElementPtr
MySqlBinding::getJSON() const {
    if (amNull()) {
        return (ElementPtr());
    }
    std::string s = getString();
    return (Element::fromJSON(s));
}

std::vector<uint8_t>
MySqlBinding::getBlob() const {
    // Make sure the binding type is blob.
    validateAccess<std::vector<uint8_t> >();
    if (length_ == 0) {
        return (std::vector<uint8_t>());
    }
    return (std::vector<uint8_t>(buffer_.begin(), buffer_.begin() + length_));
}

std::vector<uint8_t>
MySqlBinding::getBlobOrDefault(const std::vector<uint8_t>& default_value) const {
    if (amNull()) {
        return (default_value);
    }
    return (getBlob());
}

float
MySqlBinding::getFloat() const {
    // It may seem a bit weird that we use getInteger template method
    // for getting a floating point value. However, the getInteger method
    // seems to be generic enough to support it. If we were to redo the
    // API of this class we would probably introduce a getNumericValue
    // method instead of getInteger. However, we already have getInteger
    // used in many places so we should stick to it.
    return (getInteger<float>());
}

ptime
MySqlBinding::getTimestamp() const {
    // Make sure the binding type is timestamp.
    validateAccess<ptime>();
    // Copy the buffer contents into native timestamp structure and
    // then convert it to posix time.
    const MYSQL_TIME* database_time = reinterpret_cast<const MYSQL_TIME*>(&buffer_[0]);
    return (convertFromDatabaseTime(*database_time));
}

ptime
MySqlBinding::getTimestampOrDefault(const ptime& default_value) const {
    if (amNull()) {
        return (default_value);
    }
    return (getTimestamp());
}

MySqlBindingPtr
MySqlBinding::createString(const unsigned long length) {
    MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
                                             length));
    return (binding);
}

MySqlBindingPtr
MySqlBinding::createString(const std::string& value) {
    MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
                                             value.size()));
    binding->setBufferValue(value.begin(), value.end());
    return (binding);
}

MySqlBindingPtr
MySqlBinding::condCreateString(const Optional<std::string>& value) {
    return (value.unspecified() ? MySqlBinding::createNull() : createString(value));
}

MySqlBindingPtr
MySqlBinding::createBlob(const unsigned long length) {
    MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::vector<uint8_t> >::column_type,
                                   length));
    return (binding);
}

MySqlBindingPtr
MySqlBinding::createFloat(const float value) {
    // It may seem a bit weird that we use createInteger template method
    // for setting a floating point value. However, the setInteger method
    // seems to be generic enough to support it. If we were to redo the
    // API of this class we would probably introduce a createNumericValue
    // method instead of createInteger. However, we already have createInteger
    // used in many places so we should stick to it.
    return (createInteger<float>(value));
}

MySqlBindingPtr
MySqlBinding::createBool() {
    return (createInteger<uint8_t>(static_cast<uint8_t>(false)));
}

MySqlBindingPtr
MySqlBinding::createBool(const bool value) {
    return (createInteger<uint8_t>(static_cast<uint8_t>(value)));
}

MySqlBindingPtr
MySqlBinding::condCreateBool(const util::Optional<bool>& value) {
    if (value.unspecified()) {
        return (MySqlBinding::createNull());
    }

    return (createInteger<uint8_t>(static_cast<uint8_t>(value.get())));
}

MySqlBindingPtr
MySqlBinding::condCreateIPv4Address(const Optional<IOAddress>& value) {
    // If the value is unspecified it doesn't matter what the value is.
    if (value.unspecified()) {
        return (MySqlBinding::createNull());
    }

    // Make sure it is an IPv4 address.
    if (!value.get().isV4()) {
        isc_throw(BadValue, "unable to create a MySQL binding: specified value '"
                  << value.get().toText() << "' is not an IPv4 address");
    }

    return (createInteger<uint32_t>(value.get().toUint32()));
}

MySqlBindingPtr
MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
    MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
                                   MySqlBindingTraits<ptime>::length));
    binding->setTimestampValue(timestamp);
    return (binding);
}

MySqlBindingPtr
MySqlBinding::createTimestamp() {
    MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
                                             MySqlBindingTraits<ptime>::length));
    return (binding);
}

MySqlBindingPtr
MySqlBinding::createNull() {
    MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_NULL, 0));
    return (binding);
}

void
MySqlBinding::convertToDatabaseTime(const time_t input_time,
                                    MYSQL_TIME& output_time) {

    // Clear output data.
    memset(&output_time, 0, sizeof(MYSQL_TIME));

    // Convert to broken-out time
    struct tm time_tm;
    (void) localtime_r(&input_time, &time_tm);

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

void
MySqlBinding::convertToDatabaseTime(const boost::posix_time::ptime& input_time,
                                    MYSQL_TIME& output_time) {
    if (input_time.is_not_a_date_time()) {
        isc_throw(BadValue, "Time value is not a valid posix time");
    }

    // Clear output data.
    memset(&output_time, 0, sizeof(MYSQL_TIME));

    output_time.year = input_time.date().year();
    output_time.month = input_time.date().month();
    output_time.day = input_time.date().day();
    output_time.hour = input_time.time_of_day().hours();
    output_time.minute = input_time.time_of_day().minutes();
    output_time.second = input_time.time_of_day().seconds();
    /// @todo Use fractional seconds instead of 0 when minimum supported
    /// MySQL version has it.
    output_time.second_part = 0;
/*    output_time.second_part = input_time.time_of_day().fractional_seconds()
        *1000000/time_duration::ticks_per_second(); */
    output_time.neg = my_bool(0);
}

void
MySqlBinding::convertToDatabaseTime(const time_t cltt,
                                    const uint32_t valid_lifetime,
                                    MYSQL_TIME& expire) {

    // Calculate expiry time. Store it in the 64-bit value so as we can detect
    // overflows.
    int64_t expire_time_64 = static_cast<int64_t>(cltt) +
        static_cast<int64_t>(valid_lifetime);

    // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
    // beyond the max value of int32_t.
    if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
        isc_throw(BadValue, "Time value is too large: " << expire_time_64);
    }

    // Clear output data.
    memset(&expire, 0, sizeof(MYSQL_TIME));

    const time_t expire_time = static_cast<time_t>(expire_time_64);

    // Convert to broken-out time
    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;
    expire.second_part = 0;                  // No fractional seconds
    expire.neg = my_bool(0);                 // Not negative
}

void
MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& expire,
                                      uint32_t valid_lifetime,
                                      time_t& cltt) {
    // Copy across fields from MYSQL_TIME structure.
    struct tm expire_tm;
    memset(&expire_tm, 0, sizeof(expire_tm));

    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;
    expire_tm.tm_isdst = -1;    // Let the system work out about DST

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

ptime
MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
    /// @todo Use fractional seconds instead of 0 when minimum supported
    /// MySQL version has it.
    long fractional = 0;
    // long fractional = database_time.second_part * time_duration::ticks_per_second()/1000000;
    ptime pt(boost::gregorian::date(database_time.year,
                                    boost::gregorian::greg_month(database_time.month),
                                    database_time.day),
             time_duration(database_time.hour, database_time.minute,
                           database_time.second, fractional));

    return (pt);
}

MySqlBinding::MySqlBinding(enum_field_types buffer_type,
                           const size_t length)
    // Make sure that the buffer has non-zero length in case we need to
    // reference its first element to assign it to the MySQL binding.
    : buffer_(length > 0 ? length : 1), length_(length),
      null_value_(buffer_type == MYSQL_TYPE_NULL) {
    memset(&bind_, 0, sizeof(MYSQL_BIND));
    bind_.buffer_type = buffer_type;

    if (buffer_type != MYSQL_TYPE_NULL) {
        bind_.buffer = &buffer_[0];
        bind_.buffer_length = length_;
        bind_.length = &length_;
        bind_.is_null = &null_value_;
    }
}

void
MySqlBinding::setBufferLength(const unsigned long length) {
    length_ = length;
    // It appears that the MySQL connectors sometimes require that the
    // buffer is specified (set to a non-zero value), even if the buffer
    // length is 0. We have found that setting the buffer to 0 value would
    // cause the value inserted to the database be NULL. In order to avoid
    // it, we simply make sure that the buffer length is at least 1 byte and
    // provide the pointer to this byte within the binding.
    buffer_.resize(length_ > 0 ? length_ : 1);
    bind_.buffer = &buffer_[0];
    bind_.buffer_length = length_;
}

void
MySqlBinding::setTimestampValue(const ptime& timestamp) {
    MYSQL_TIME database_time;
    convertToDatabaseTime(timestamp, database_time);
    // Copy database time into the buffer.
    memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&database_time),
           sizeof(MYSQL_TIME));
    bind_.buffer = &buffer_[0];
}

} // end of namespace isc::db
} // end of namespace isc