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