1 // Copyright (C) 2018-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_address.h>
10 #include <exceptions/exceptions.h>
11 #include <boost/date_time/gregorian/gregorian.hpp>
12 #include <mysql/mysql_binding.h>
13
14 using namespace boost::posix_time;
15 using namespace isc::asiolink;
16 using namespace isc::data;
17 using namespace isc::util;
18
19 namespace isc {
20 namespace db {
21
22 std::string
getString() const23 MySqlBinding::getString() const {
24 // Make sure the binding type is text.
25 validateAccess<std::string>();
26 if (length_ == 0) {
27 return (std::string());
28 }
29 return (std::string(buffer_.begin(), buffer_.begin() + length_));
30 }
31
32 std::string
getStringOrDefault(const std::string & default_value) const33 MySqlBinding::getStringOrDefault(const std::string& default_value) const {
34 if (amNull()) {
35 return (default_value);
36 }
37 return (getString());
38 }
39
40 ElementPtr
getJSON() const41 MySqlBinding::getJSON() const {
42 if (amNull()) {
43 return (ElementPtr());
44 }
45 std::string s = getString();
46 return (Element::fromJSON(s));
47 }
48
49 std::vector<uint8_t>
getBlob() const50 MySqlBinding::getBlob() const {
51 // Make sure the binding type is blob.
52 validateAccess<std::vector<uint8_t> >();
53 if (length_ == 0) {
54 return (std::vector<uint8_t>());
55 }
56 return (std::vector<uint8_t>(buffer_.begin(), buffer_.begin() + length_));
57 }
58
59 std::vector<uint8_t>
getBlobOrDefault(const std::vector<uint8_t> & default_value) const60 MySqlBinding::getBlobOrDefault(const std::vector<uint8_t>& default_value) const {
61 if (amNull()) {
62 return (default_value);
63 }
64 return (getBlob());
65 }
66
67 float
getFloat() const68 MySqlBinding::getFloat() const {
69 // It may seem a bit weird that we use getInteger template method
70 // for getting a floating point value. However, the getInteger method
71 // seems to be generic enough to support it. If we were to redo the
72 // API of this class we would probably introduce a getNumericValue
73 // method instead of getInteger. However, we already have getInteger
74 // used in many places so we should stick to it.
75 return (getInteger<float>());
76 }
77
78 ptime
getTimestamp() const79 MySqlBinding::getTimestamp() const {
80 // Make sure the binding type is timestamp.
81 validateAccess<ptime>();
82 // Copy the buffer contents into native timestamp structure and
83 // then convert it to posix time.
84 const MYSQL_TIME* database_time = reinterpret_cast<const MYSQL_TIME*>(&buffer_[0]);
85 return (convertFromDatabaseTime(*database_time));
86 }
87
88 ptime
getTimestampOrDefault(const ptime & default_value) const89 MySqlBinding::getTimestampOrDefault(const ptime& default_value) const {
90 if (amNull()) {
91 return (default_value);
92 }
93 return (getTimestamp());
94 }
95
96 MySqlBindingPtr
createString(const unsigned long length)97 MySqlBinding::createString(const unsigned long length) {
98 MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
99 length));
100 return (binding);
101 }
102
103 MySqlBindingPtr
createString(const std::string & value)104 MySqlBinding::createString(const std::string& value) {
105 MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
106 value.size()));
107 binding->setBufferValue(value.begin(), value.end());
108 return (binding);
109 }
110
111 MySqlBindingPtr
condCreateString(const Optional<std::string> & value)112 MySqlBinding::condCreateString(const Optional<std::string>& value) {
113 return (value.unspecified() ? MySqlBinding::createNull() : createString(value));
114 }
115
116 MySqlBindingPtr
createBlob(const unsigned long length)117 MySqlBinding::createBlob(const unsigned long length) {
118 MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::vector<uint8_t> >::column_type,
119 length));
120 return (binding);
121 }
122
123 MySqlBindingPtr
createFloat(const float value)124 MySqlBinding::createFloat(const float value) {
125 // It may seem a bit weird that we use createInteger template method
126 // for setting a floating point value. However, the setInteger method
127 // seems to be generic enough to support it. If we were to redo the
128 // API of this class we would probably introduce a createNumericValue
129 // method instead of createInteger. However, we already have createInteger
130 // used in many places so we should stick to it.
131 return (createInteger<float>(value));
132 }
133
134 MySqlBindingPtr
createBool(const bool value)135 MySqlBinding::createBool(const bool value) {
136 return (createInteger<uint8_t>(static_cast<uint8_t>(value)));
137 }
138
139 MySqlBindingPtr
condCreateBool(const util::Optional<bool> & value)140 MySqlBinding::condCreateBool(const util::Optional<bool>& value) {
141 if (value.unspecified()) {
142 return (MySqlBinding::createNull());
143 }
144
145 return (createInteger<uint8_t>(static_cast<uint8_t>(value.get())));
146 }
147
148 MySqlBindingPtr
condCreateIPv4Address(const Optional<IOAddress> & value)149 MySqlBinding::condCreateIPv4Address(const Optional<IOAddress>& value) {
150 // If the value is unspecified it doesn't matter what the value is.
151 if (value.unspecified()) {
152 return (MySqlBinding::createNull());
153 }
154
155 // Make sure it is an IPv4 address.
156 if (!value.get().isV4()) {
157 isc_throw(BadValue, "unable to create a MySQL binding: specified value '"
158 << value.get().toText() << "' is not an IPv4 address");
159 }
160
161 return (createInteger<uint32_t>(value.get().toUint32()));
162 }
163
164 MySqlBindingPtr
createTimestamp(const boost::posix_time::ptime & timestamp)165 MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
166 MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
167 MySqlBindingTraits<ptime>::length));
168 binding->setTimestampValue(timestamp);
169 return (binding);
170 }
171
172 MySqlBindingPtr
createTimestamp()173 MySqlBinding::createTimestamp() {
174 MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
175 MySqlBindingTraits<ptime>::length));
176 return (binding);
177 }
178
179 MySqlBindingPtr
createNull()180 MySqlBinding::createNull() {
181 MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_NULL, 0));
182 return (binding);
183 }
184
185 void
convertToDatabaseTime(const time_t input_time,MYSQL_TIME & output_time)186 MySqlBinding::convertToDatabaseTime(const time_t input_time,
187 MYSQL_TIME& output_time) {
188
189 // Clear output data.
190 memset(&output_time, 0, sizeof(MYSQL_TIME));
191
192 // Convert to broken-out time
193 struct tm time_tm;
194 (void) localtime_r(&input_time, &time_tm);
195
196 // Place in output expire structure.
197 output_time.year = time_tm.tm_year + 1900;
198 output_time.month = time_tm.tm_mon + 1; // Note different base
199 output_time.day = time_tm.tm_mday;
200 output_time.hour = time_tm.tm_hour;
201 output_time.minute = time_tm.tm_min;
202 output_time.second = time_tm.tm_sec;
203 output_time.second_part = 0; // No fractional seconds
204 output_time.neg = my_bool(0); // Not negative
205 }
206
207 void
convertToDatabaseTime(const boost::posix_time::ptime & input_time,MYSQL_TIME & output_time)208 MySqlBinding::convertToDatabaseTime(const boost::posix_time::ptime& input_time,
209 MYSQL_TIME& output_time) {
210 if (input_time.is_not_a_date_time()) {
211 isc_throw(BadValue, "Time value is not a valid posix time");
212 }
213
214 // Clear output data.
215 memset(&output_time, 0, sizeof(MYSQL_TIME));
216
217 output_time.year = input_time.date().year();
218 output_time.month = input_time.date().month();
219 output_time.day = input_time.date().day();
220 output_time.hour = input_time.time_of_day().hours();
221 output_time.minute = input_time.time_of_day().minutes();
222 output_time.second = input_time.time_of_day().seconds();
223 /// @todo Use fractional seconds instead of 0 when minimum supported
224 /// MySQL version has it.
225 output_time.second_part = 0;
226 /* output_time.second_part = input_time.time_of_day().fractional_seconds()
227 *1000000/time_duration::ticks_per_second(); */
228 output_time.neg = my_bool(0);
229 }
230
231 void
convertToDatabaseTime(const time_t cltt,const uint32_t valid_lifetime,MYSQL_TIME & expire)232 MySqlBinding::convertToDatabaseTime(const time_t cltt,
233 const uint32_t valid_lifetime,
234 MYSQL_TIME& expire) {
235
236 // Calculate expiry time. Store it in the 64-bit value so as we can detect
237 // overflows.
238 int64_t expire_time_64 = static_cast<int64_t>(cltt) +
239 static_cast<int64_t>(valid_lifetime);
240
241 // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
242 // beyond the max value of int32_t.
243 if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
244 isc_throw(BadValue, "Time value is too large: " << expire_time_64);
245 }
246
247 // Clear output data.
248 memset(&expire, 0, sizeof(MYSQL_TIME));
249
250 const time_t expire_time = static_cast<time_t>(expire_time_64);
251
252 // Convert to broken-out time
253 struct tm expire_tm;
254 (void) localtime_r(&expire_time, &expire_tm);
255
256 // Place in output expire structure.
257 expire.year = expire_tm.tm_year + 1900;
258 expire.month = expire_tm.tm_mon + 1; // Note different base
259 expire.day = expire_tm.tm_mday;
260 expire.hour = expire_tm.tm_hour;
261 expire.minute = expire_tm.tm_min;
262 expire.second = expire_tm.tm_sec;
263 expire.second_part = 0; // No fractional seconds
264 expire.neg = my_bool(0); // Not negative
265 }
266
267 void
convertFromDatabaseTime(const MYSQL_TIME & expire,uint32_t valid_lifetime,time_t & cltt)268 MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& expire,
269 uint32_t valid_lifetime,
270 time_t& cltt) {
271 // Copy across fields from MYSQL_TIME structure.
272 struct tm expire_tm;
273 memset(&expire_tm, 0, sizeof(expire_tm));
274
275 expire_tm.tm_year = expire.year - 1900;
276 expire_tm.tm_mon = expire.month - 1;
277 expire_tm.tm_mday = expire.day;
278 expire_tm.tm_hour = expire.hour;
279 expire_tm.tm_min = expire.minute;
280 expire_tm.tm_sec = expire.second;
281 expire_tm.tm_isdst = -1; // Let the system work out about DST
282
283 // Convert to local time
284 cltt = mktime(&expire_tm) - valid_lifetime;
285 }
286
287 ptime
convertFromDatabaseTime(const MYSQL_TIME & database_time)288 MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
289 /// @todo Use fractional seconds instead of 0 when minimum supported
290 /// MySQL version has it.
291 long fractional = 0;
292 // long fractional = database_time.second_part * time_duration::ticks_per_second()/1000000;
293 ptime pt(boost::gregorian::date(database_time.year,
294 boost::gregorian::greg_month(database_time.month),
295 database_time.day),
296 time_duration(database_time.hour, database_time.minute,
297 database_time.second, fractional));
298
299 return (pt);
300 }
301
MySqlBinding(enum_field_types buffer_type,const size_t length)302 MySqlBinding::MySqlBinding(enum_field_types buffer_type,
303 const size_t length)
304 // Make sure that the buffer has non-zero length in case we need to
305 // reference its first element to assign it to the MySQL binding.
306 : buffer_(length > 0 ? length : 1), length_(length),
307 null_value_(buffer_type == MYSQL_TYPE_NULL) {
308 memset(&bind_, 0, sizeof(MYSQL_BIND));
309 bind_.buffer_type = buffer_type;
310
311 if (buffer_type != MYSQL_TYPE_NULL) {
312 bind_.buffer = &buffer_[0];
313 bind_.buffer_length = length_;
314 bind_.length = &length_;
315 bind_.is_null = &null_value_;
316 }
317 }
318
319 void
setBufferLength(const unsigned long length)320 MySqlBinding::setBufferLength(const unsigned long length) {
321 length_ = length;
322 // It appears that the MySQL connectors sometimes require that the
323 // buffer is specified (set to a non-zero value), even if the buffer
324 // length is 0. We have found that setting the buffer to 0 value would
325 // cause the value inserted to the database be NULL. In order to avoid
326 // it, we simply make sure that the buffer length is at least 1 byte and
327 // provide the pointer to this byte within the binding.
328 buffer_.resize(length_ > 0 ? length_ : 1);
329 bind_.buffer = &buffer_[0];
330 bind_.buffer_length = length_;
331 }
332
333 void
setTimestampValue(const ptime & timestamp)334 MySqlBinding::setTimestampValue(const ptime& timestamp) {
335 MYSQL_TIME database_time;
336 convertToDatabaseTime(timestamp, database_time);
337 // Copy database time into the buffer.
338 memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&database_time),
339 sizeof(MYSQL_TIME));
340 bind_.buffer = &buffer_[0];
341 }
342
343 } // end of namespace isc::db
344 } // end of namespace isc
345