Kea  1.9.9-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(const bool value) {
136  return (createInteger<uint8_t>(static_cast<uint8_t>(value)));
137 }
138 
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 
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 
165 MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
168  binding->setTimestampValue(timestamp);
169  return (binding);
170 }
171 
173 MySqlBinding::createTimestamp() {
176  return (binding);
177 }
178 
180 MySqlBinding::createNull() {
181  MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_NULL, 0));
182  return (binding);
183 }
184 
185 void
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
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();
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
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
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
288 MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
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 
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
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
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
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:121
static void convertToDatabaseTime(const time_t input_time, MYSQL_TIME &output_time)
Converts time_t value to database time.
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
#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
const OutputBuffer * buffer_
Trait class for column types supported in MySQL.
Definition: mysql_binding.h:36
Defines the logger used by the top-level component of kea-dhcp-ddns.
T get() const
Retrieves the encapsulated value.
Definition: optional.h:112
boost::shared_ptr< MySqlBinding > MySqlBindingPtr
Shared pointer to the Binding class.
MySQL binding used in prepared statements.