summaryrefslogtreecommitdiffstats
path: root/src/lib/mysql/mysql_binding.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/mysql/mysql_binding.cc349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/lib/mysql/mysql_binding.cc b/src/lib/mysql/mysql_binding.cc
new file mode 100644
index 0000000..67ad95d
--- /dev/null
+++ b/src/lib/mysql/mysql_binding.cc
@@ -0,0 +1,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