Kea  2.1.7-git
mysql_binding.cc
Go to the documentation of this file.
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
23 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
33 MySqlBinding::getStringOrDefault(const std::string& default_value) const {
34  if (amNull()) {
35  return (default_value);
36  }
37  return (getString());
38 }
39 
41 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>
50 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>
60 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
68 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
79 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
89 MySqlBinding::getTimestampOrDefault(const ptime& default_value) const {
90  if (amNull()) {
91  return (default_value);
92  }
93  return (getTimestamp());
94 }
95 
97 MySqlBinding::createString(const unsigned long length) {
99  length));
100  return (binding);
101 }
102 
104 MySqlBinding::createString(const std::string& value) {
106  value.size()));
107  binding->setBufferValue(value.begin(), value.end());
108  return (binding);
109 }
110 
112 MySqlBinding::condCreateString(const Optional<std::string>& value) {
113  return (value.unspecified() ? MySqlBinding::createNull() : createString(value));
114 }
115 
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 
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 
135 MySqlBinding::createBool() {
136  return (createInteger<uint8_t>(static_cast<uint8_t>(false)));
137 }
138 
140 MySqlBinding::createBool(const bool value) {
141  return (createInteger<uint8_t>(static_cast<uint8_t>(value)));
142 }
143 
145 MySqlBinding::condCreateBool(const util::Optional<bool>& value) {
146  if (value.unspecified()) {
147  return (MySqlBinding::createNull());
148  }
149 
150  return (createInteger<uint8_t>(static_cast<uint8_t>(value.get())));
151 }
152 
154 MySqlBinding::condCreateIPv4Address(const Optional<IOAddress>& value) {
155  // If the value is unspecified it doesn't matter what the value is.
156  if (value.unspecified()) {
157  return (MySqlBinding::createNull());
158  }
159 
160  // Make sure it is an IPv4 address.
161  if (!value.get().isV4()) {
162  isc_throw(BadValue, "unable to create a MySQL binding: specified value '"
163  << value.get().toText() << "' is not an IPv4 address");
164  }
165 
166  return (createInteger<uint32_t>(value.get().toUint32()));
167 }
168 
170 MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
173  binding->setTimestampValue(timestamp);
174  return (binding);
175 }
176 
178 MySqlBinding::createTimestamp() {
181  return (binding);
182 }
183 
185 MySqlBinding::createNull() {
186  MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_NULL, 0));
187  return (binding);
188 }
189 
190 void
191 MySqlBinding::convertToDatabaseTime(const time_t input_time,
192  MYSQL_TIME& output_time) {
193 
194  // Clear output data.
195  memset(&output_time, 0, sizeof(MYSQL_TIME));
196 
197  // Convert to broken-out time
198  struct tm time_tm;
199  (void) localtime_r(&input_time, &time_tm);
200 
201  // Place in output expire structure.
202  output_time.year = time_tm.tm_year + 1900;
203  output_time.month = time_tm.tm_mon + 1; // Note different base
204  output_time.day = time_tm.tm_mday;
205  output_time.hour = time_tm.tm_hour;
206  output_time.minute = time_tm.tm_min;
207  output_time.second = time_tm.tm_sec;
208  output_time.second_part = 0; // No fractional seconds
209  output_time.neg = my_bool(0); // Not negative
210 }
211 
212 void
213 MySqlBinding::convertToDatabaseTime(const boost::posix_time::ptime& input_time,
214  MYSQL_TIME& output_time) {
215  if (input_time.is_not_a_date_time()) {
216  isc_throw(BadValue, "Time value is not a valid posix time");
217  }
218 
219  // Clear output data.
220  memset(&output_time, 0, sizeof(MYSQL_TIME));
221 
222  output_time.year = input_time.date().year();
223  output_time.month = input_time.date().month();
224  output_time.day = input_time.date().day();
225  output_time.hour = input_time.time_of_day().hours();
226  output_time.minute = input_time.time_of_day().minutes();
227  output_time.second = input_time.time_of_day().seconds();
230  output_time.second_part = 0;
231 /* output_time.second_part = input_time.time_of_day().fractional_seconds()
232  *1000000/time_duration::ticks_per_second(); */
233  output_time.neg = my_bool(0);
234 }
235 
236 void
237 MySqlBinding::convertToDatabaseTime(const time_t cltt,
238  const uint32_t valid_lifetime,
239  MYSQL_TIME& expire) {
240 
241  // Calculate expiry time. Store it in the 64-bit value so as we can detect
242  // overflows.
243  int64_t expire_time_64 = static_cast<int64_t>(cltt) +
244  static_cast<int64_t>(valid_lifetime);
245 
246  // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
247  // beyond the max value of int32_t.
248  if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
249  isc_throw(BadValue, "Time value is too large: " << expire_time_64);
250  }
251 
252  // Clear output data.
253  memset(&expire, 0, sizeof(MYSQL_TIME));
254 
255  const time_t expire_time = static_cast<time_t>(expire_time_64);
256 
257  // Convert to broken-out time
258  struct tm expire_tm;
259  (void) localtime_r(&expire_time, &expire_tm);
260 
261  // Place in output expire structure.
262  expire.year = expire_tm.tm_year + 1900;
263  expire.month = expire_tm.tm_mon + 1; // Note different base
264  expire.day = expire_tm.tm_mday;
265  expire.hour = expire_tm.tm_hour;
266  expire.minute = expire_tm.tm_min;
267  expire.second = expire_tm.tm_sec;
268  expire.second_part = 0; // No fractional seconds
269  expire.neg = my_bool(0); // Not negative
270 }
271 
272 void
273 MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& expire,
274  uint32_t valid_lifetime,
275  time_t& cltt) {
276  // Copy across fields from MYSQL_TIME structure.
277  struct tm expire_tm;
278  memset(&expire_tm, 0, sizeof(expire_tm));
279 
280  expire_tm.tm_year = expire.year - 1900;
281  expire_tm.tm_mon = expire.month - 1;
282  expire_tm.tm_mday = expire.day;
283  expire_tm.tm_hour = expire.hour;
284  expire_tm.tm_min = expire.minute;
285  expire_tm.tm_sec = expire.second;
286  expire_tm.tm_isdst = -1; // Let the system work out about DST
287 
288  // Convert to local time
289  cltt = mktime(&expire_tm) - valid_lifetime;
290 }
291 
292 ptime
293 MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
296  long fractional = 0;
297  // long fractional = database_time.second_part * time_duration::ticks_per_second()/1000000;
298  ptime pt(boost::gregorian::date(database_time.year,
299  boost::gregorian::greg_month(database_time.month),
300  database_time.day),
301  time_duration(database_time.hour, database_time.minute,
302  database_time.second, fractional));
303 
304  return (pt);
305 }
306 
307 MySqlBinding::MySqlBinding(enum_field_types buffer_type,
308  const size_t length)
309  // Make sure that the buffer has non-zero length in case we need to
310  // reference its first element to assign it to the MySQL binding.
311  : buffer_(length > 0 ? length : 1), length_(length),
312  null_value_(buffer_type == MYSQL_TYPE_NULL) {
313  memset(&bind_, 0, sizeof(MYSQL_BIND));
314  bind_.buffer_type = buffer_type;
315 
316  if (buffer_type != MYSQL_TYPE_NULL) {
317  bind_.buffer = &buffer_[0];
318  bind_.buffer_length = length_;
319  bind_.length = &length_;
320  bind_.is_null = &null_value_;
321  }
322 }
323 
324 void
325 MySqlBinding::setBufferLength(const unsigned long length) {
326  length_ = length;
327  // It appears that the MySQL connectors sometimes require that the
328  // buffer is specified (set to a non-zero value), even if the buffer
329  // length is 0. We have found that setting the buffer to 0 value would
330  // cause the value inserted to the database be NULL. In order to avoid
331  // it, we simply make sure that the buffer length is at least 1 byte and
332  // provide the pointer to this byte within the binding.
333  buffer_.resize(length_ > 0 ? length_ : 1);
334  bind_.buffer = &buffer_[0];
335  bind_.buffer_length = length_;
336 }
337 
338 void
339 MySqlBinding::setTimestampValue(const ptime& timestamp) {
340  MYSQL_TIME database_time;
341  convertToDatabaseTime(timestamp, database_time);
342  // Copy database time into the buffer.
343  memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&database_time),
344  sizeof(MYSQL_TIME));
345  bind_.buffer = &buffer_[0];
346 }
347 
348 } // end of namespace isc::db
349 } // end of namespace isc
bool my_bool
my_bool type in MySQL 8.x.
void unspecified(bool unspecified)
Modifies the flag that indicates whether the value is specified or unspecified.
Definition: optional.h:136
T get() const
Retrieves the encapsulated value.
Definition: optional.h:114
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Definition: edns.h:19
Trait class for column types supported in MySQL.
Definition: mysql_binding.h:36
Defines the logger used by the top-level component of kea-lfc.
boost::shared_ptr< MySqlBinding > MySqlBindingPtr
Shared pointer to the Binding class.
MySQL binding used in prepared statements.