src/hooks/dhcp/high_availability/tests/Makefile
src/hooks/dhcp/lease_cmds/Makefile
src/hooks/dhcp/lease_cmds/tests/Makefile
+ src/hooks/dhcp/mysql_cb/Makefile
+ src/hooks/dhcp/mysql_cb/tests/Makefile
src/hooks/dhcp/user_chk/Makefile
src/hooks/dhcp/user_chk/tests/Makefile
src/hooks/dhcp/user_chk/tests/test_data_files_config.h
src/lib/log/tests/tempdir.h
src/lib/mysql/Makefile
src/lib/mysql/testutils/Makefile
+ src/lib/mysql/tests/Makefile
src/lib/pgsql/Makefile
src/lib/pgsql/tests/Makefile
src/lib/pgsql/testutils/Makefile
namespace {
-class Binding;
-
-typedef boost::shared_ptr<Binding> BindingPtr;
-
-class Binding {
-public:
-
- enum_field_types getType() const {
- return (bind_.buffer_type);
- }
-
- MYSQL_BIND& getMySqlBinding() {
- return (bind_);
- }
-
- void setBufferValue(const std::string& value) {
- buffer_.assign(value.begin(), value.end());
- bind_.buffer = &buffer_[0];
- bind_.buffer_length = value.size();
- }
-
- template<typename T>
- T getValue() const {
- const T* value = reinterpret_cast<const T*>(&buffer_[0]);
- return (*value);
- }
-
- bool amNull() const {
- return (null_value_ == MLM_TRUE);
- }
-
- static BindingPtr createString(const unsigned long length = 512) {
- BindingPtr binding(new Binding(MYSQL_TYPE_STRING));
- binding->setBufferLength(length);
- return (binding);
- }
-
- static BindingPtr createString(const std::string& value) {
- BindingPtr binding(new Binding(MYSQL_TYPE_STRING));
- binding->setBufferValue(value);
- return (binding);
- }
-
- static BindingPtr createTimestamp() {
- BindingPtr binding(new Binding(MYSQL_TYPE_TIMESTAMP));
- binding->setBufferLength(sizeof(MYSQL_TIME));
- return (binding);
- }
-
-private:
-
- Binding(enum_field_types buffer_type)
- : buffer_(), length_(0), null_value_(MLM_FALSE) {
- bind_.buffer_type = buffer_type;
- bind_.length = &length_;
- bind_.is_null = &null_value_;
- }
-
- void setBufferLength(const unsigned long length) {
- length_ = length;
- buffer_.resize(length_);
- bind_.buffer = &buffer_[0];
- bind_.buffer_length = length_;
- }
-
- std::vector<uint8_t> buffer_;
-
- unsigned long length_;
-
- my_bool null_value_;
-
- MYSQL_BIND bind_;
-};
-
-typedef std::vector<BindingPtr> BindingCollection;
-
-class DatabaseExchange {
-public:
-
- typedef std::function<void()> ConsumeResultFun;
-
- void selectQuery(MYSQL_STMT* statement,
- const BindingCollection& in_bindings,
- BindingCollection& out_bindings,
- ConsumeResultFun process_result) {
- std::vector<MYSQL_BIND> in_bind_vec;
- for (BindingPtr in_binding : in_bindings) {
- in_bind_vec.push_back(in_binding->getMySqlBinding());
- }
-
- int status = 0;
-
- if (!in_bind_vec.empty()) {
- status = mysql_stmt_bind_param(statement, &in_bind_vec[0]);
- }
-
- std::vector<MYSQL_BIND> out_bind_vec;
- for (BindingPtr out_binding : out_bindings) {
- out_bind_vec.push_back(out_binding->getMySqlBinding());
- }
-
- if (!out_bind_vec.empty()) {
- status = mysql_stmt_bind_result(statement, &out_bind_vec[0]);
- }
-
- status = mysql_stmt_execute(statement);
-
- status = mysql_stmt_store_result(statement);
-
- MySqlFreeResult fetch_release(statement);
- while ((status = mysql_stmt_fetch(statement)) ==
- MLM_MYSQL_FETCH_SUCCESS) {
- try {
- process_result();
- } catch (...) {
- throw;
- }
- }
- }
-
-};
-
}
namespace isc {
BindingCollection out_bindings;
out_bindings.push_back(Binding::createString());
- DatabaseExchange xchg;
- xchg.selectQuery(impl_->conn_.statements_[MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID],
- in_bindings, out_bindings,
- [&out_bindings]() {
+ impl_->conn_.selectQuery(MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID,
+ in_bindings, out_bindings,
+ [&out_bindings]() {
uint32_t hostname = out_bindings[0]->getValue<uint32_t>();
});
-SUBDIRS = . testutils
+SUBDIRS = . testutils tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
lib_LTLIBRARIES = libkea-mysql.la
libkea_mysql_la_SOURCES = mysql_connection.cc mysql_connection.h
+libkea_mysql_la_SOURCES += mysql_binding.cc mysql_binding.h
+libkea_mysql_la_SOURCES += mysql_constants.h
libkea_mysql_la_LIBADD = $(top_builddir)/src/lib/database/libkea-database.la
libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
--- /dev/null
+// Copyright (C) 2018 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 <mysql/mysql_binding.h>
+
+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::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_));
+}
+
+boost::posix_time::ptime
+MySqlBinding::getTimestamp() const {
+ // Make sure the binding type is timestamp.
+ validateAccess<boost::posix_time::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));
+}
+
+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::createBlob(const unsigned long length) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::vector<uint8_t> >::column_type,
+ length));
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<boost::posix_time::ptime>::column_type,
+ MySqlBindingTraits<boost::posix_time::ptime>::length));
+ binding->setTimestampValue(timestamp);
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createTimestamp() {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<boost::posix_time::ptime>::column_type,
+ MySqlBindingTraits<boost::posix_time::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) {
+
+ // 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 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);
+ }
+
+ const time_t expire_time = static_cast<const 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;
+}
+
+boost::posix_time::ptime
+MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
+ // Copy across fields from MYSQL_TIME structure.
+ struct tm converted_tm;
+ memset(&converted_tm, 0, sizeof(converted_tm));
+
+ converted_tm.tm_year = database_time.year - 1900;
+ converted_tm.tm_mon = database_time.month - 1;
+ converted_tm.tm_mday = database_time.day;
+ converted_tm.tm_hour = database_time.hour;
+ converted_tm.tm_min = database_time.minute;
+ converted_tm.tm_sec = database_time.second;
+ converted_tm.tm_isdst = -1; // Let the system work out about DST
+
+ // Convert to local time
+ return (boost::posix_time::ptime_from_tm(converted_tm));
+}
+
+MySqlBinding::MySqlBinding(enum_field_types buffer_type,
+ const size_t length)
+ : buffer_(length), 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;
+ buffer_.resize(length_);
+ bind_.buffer = &buffer_[0];
+ bind_.buffer_length = length_;
+}
+
+void
+MySqlBinding::setTimestampValue(const boost::posix_time::ptime& timestamp) {
+ // Convert timestamp to tm structure.
+ tm td_tm = to_tm(timestamp);
+ // Convert tm value to time_t.
+ time_t tt = mktime(&td_tm);
+ // Convert time_t to database time.
+ MYSQL_TIME database_time;
+ convertToDatabaseTime(tt, 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
--- /dev/null
+// Copyright (C) 2018 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/.
+
+#ifndef MYSQL_BINDING_H
+#define MYSQL_BINDING_H
+
+#include <database/database_connection.h>
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/conversion.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+#include <mysql/mysql_constants.h>
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <cstdint>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace db {
+
+/// @brief Trait class for column types supported in MySQL.
+///
+/// This class is used to map C++ types to MySQL column types
+/// defined in MySQL C API and their sizes. Specializations of
+/// this class provide such mapping. The default is a BLOB type
+/// which can be used for various input types.
+template<typename T>
+struct MySqlBindingTraits {
+ /// @brief Column type represented in MySQL C API.
+ static const enum_field_types column_type = MYSQL_TYPE_BLOB;
+ /// @brief Length of data in this column.
+ ///
+ /// The value of 0 is used for variable size columns.
+ static const size_t length = 0;
+ /// @brief Boolean value indicating if the numeric value is
+ /// unsigned.
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TEXT type.
+template<>
+struct MySqlBindingTraits<std::string> {
+ static const enum_field_types column_type = MYSQL_TYPE_STRING;
+ static const size_t length = 0;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TIMESTAMP type.
+template<>
+struct MySqlBindingTraits<boost::posix_time::ptime> {
+ static const enum_field_types column_type = MYSQL_TYPE_TIMESTAMP;
+ static const size_t length = sizeof(MYSQL_TIME);
+ static const bool am_unsignged = false;
+};
+
+/// @brief Specialization for MySQL TINYINT type.
+template<>
+struct MySqlBindingTraits<int8_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_TINY;
+ static const size_t length = 1;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TINYINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint8_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_TINY;
+ static const size_t length = 1;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Speclialization for MySQL SMALLINT type.
+template<>
+struct MySqlBindingTraits<int16_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_SHORT;
+ static const size_t length = 2;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL SMALLINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint16_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_SHORT;
+ static const size_t length = 2;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Specialization for MySQL INT type.
+template<>
+struct MySqlBindingTraits<int32_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONG;
+ static const size_t length = 4;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL INT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint32_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONG;
+ static const size_t length = 4;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Specialization for MySQL BIGINT type.
+template<>
+struct MySqlBindingTraits<int64_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONGLONG;
+ static const size_t length = 8;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL BIGINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint64_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONGLONG;
+ static const size_t length = 8;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Forward declaration of @c MySqlBinding class.
+class MySqlBinding;
+
+/// @brief Shared pointer to the @c Binding class.
+typedef boost::shared_ptr<MySqlBinding> MySqlBindingPtr;
+
+/// @brief MySQL binding used in prepared statements.
+///
+/// Kea uses prepared statements to execute queries in a database.
+/// Prepared statements include placeholders for the input parameters.
+/// These parameters are passed to the prepared statements via a
+/// collection of @c MYSQL_BIND structures. The same structures are
+/// used to receive the values from the database as a result of
+/// SELECT statements.
+///
+/// The @c MYSQL_BIND structure contains information about the
+/// data type and length. It also contains pointer to the buffer
+/// actually holding the data to be passed to the database, a
+/// flag indicating if the value is null etc.
+///
+/// The @c MySqlBinding is a C++ wrapper around this structure which
+/// is meant to ease management of the MySQL bindings. The major
+/// benefit is that the @c MySqlBinding class owns the buffer,
+/// holding the data as well as other variables which are assigned
+/// to the @c MYSQL_BIND structure. It also automatically detects
+/// the appropriate @c enum_field_types value based on the C++
+/// type used in the binding.
+class MySqlBinding {
+public:
+
+ /// @brief Returns MySQL column type for the binding.
+ ///
+ /// @return column type, e.g. MYSQL_TYPE_STRING.
+ enum_field_types getType() const {
+ return (bind_.buffer_type);
+ }
+
+ /// @brief Returns reference to the native binding.
+ ///
+ /// The returned reference is only valid for the lifetime of the
+ /// object. Make sure that the object is not destroyed as long
+ /// as the binding is required. In particular, do not destroy this
+ /// object before database query is complete.
+ ///
+ /// @return Reference to native MySQL binding.
+ MYSQL_BIND& getMySqlBinding() {
+ return (bind_);
+ }
+
+ /// @brief Returns value held in the binding as string.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_STRING.
+ ///
+ /// @return String value.
+ std::string getString() const;
+
+ /// @brief Returns value held in the binding as blob.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_BLOB.
+ ///
+ /// @return Blob in a vactor.
+ std::vector<uint8_t> getBlob() const;
+
+ /// @brief Returns numeric value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type does not match the template parameter.
+ ///
+ /// @return Numeric value of a specified type.
+ template<typename T>
+ T getInteger() const {
+ // Make sure that the binding type is numeric.
+ validateAccess<T>();
+
+ // Convert the buffer to a numeric type and then return a copy.
+ const T* value = reinterpret_cast<const T*>(&buffer_[0]);
+ return (*value);
+ }
+
+ /// @brief Returns timestamp value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_TIMESTAMP.
+ ///
+ /// @return Timestamp converted to posix time.
+ boost::posix_time::ptime getTimestamp() const;
+
+ /// @brief Checks if the bound value is NULL.
+ ///
+ /// @return true if the value in the binding is NULL, false otherwise.
+ bool amNull() const {
+ return (null_value_ == MLM_TRUE);
+ }
+
+ /// @brief Creates binding of text type for receiving data.
+ ///
+ /// @param length Length of the buffer into which received data will
+ /// be stored.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createString(const unsigned long length);
+
+ /// @brief Creates binding of text type for sending data.
+ ///
+ /// @param value String value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createString(const std::string& value);
+
+ /// @brief Creates binding of blob type for receiving data.
+ ///
+ /// @param length Length of the buffer into which received data will
+ /// be stored.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createBlob(const unsigned long length);
+
+ /// @brief Creates binding of blob type for sending data.
+ ///
+ /// @tparam Iterator Type of the iterator.
+ ///
+ /// @param begin Iterator pointing to the beginning of the input
+ /// buffer holding the data to be sent to the database.
+ /// @param end Iterator pointing to the end of the input buffer
+ /// holding the data to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename Iterator>
+ static MySqlBindingPtr createBlob(Iterator begin, Iterator end) {
+ MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_BLOB,
+ std::distance(begin, end)));
+ binding->setBufferValue(begin, end);
+ return (binding);
+ }
+
+ /// @brief Creates binding of numeric type for receiving data.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr createInteger() {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<T>::column_type,
+ MySqlBindingTraits<T>::length));
+ binding->setValue<T>(0);
+ return (binding);
+ }
+
+ /// @brief Creates binding of numeric type for sending data.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @param value Numeric value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr createInteger(T value) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<T>::column_type,
+ MySqlBindingTraits<T>::length));
+ binding->setValue(value);
+ return (binding);
+ }
+
+ /// @brief Creates binding of timestamp type for receiving data.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createTimestamp();
+
+ /// @brief Creates binding of timestamp type for sending data.
+ ///
+ /// @param timestamp Timestamp value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createTimestamp(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Creates binding encapsulating a NULL value.
+ ///
+ /// This method is used to create a binding encapsulating a NULL
+ /// value, which can be used to assign NULL to any type of column.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createNull();
+
+ /// @brief Converts time_t value to database time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @param output_time Reference to MYSQL_TIME object where converted time
+ /// will be put.
+ static void convertToDatabaseTime(const time_t input_time,
+ MYSQL_TIME& output_time);
+
+ /// @brief Converts Lease Time to Database Times
+ ///
+ /// Within the DHCP servers, times are stored as client last transmit time
+ /// and valid lifetime. In the database, the information is stored as
+ /// valid lifetime and "expire" (time of expiry of the lease). They are
+ /// related by the equation:
+ ///
+ /// - expire = client last transmit time + valid lifetime
+ ///
+ /// This method converts from the times in the lease object into times
+ /// able to be added to the database.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ /// @param expire Reference to MYSQL_TIME object where the expiry time of
+ /// the DHCP lease will be put.
+ ///
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ static void convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire);
+
+ /// @brief Converts Database Time to Lease Times
+ ///
+ /// Within the database, time is stored as "expire" (time of expiry of the
+ /// lease) and valid lifetime. In the DHCP server, the information is
+ /// stored client last transmit time and valid lifetime. These are related
+ /// by the equation:
+ ///
+ /// - client last transmit time = expire - valid_lifetime
+ ///
+ /// This method converts from the times in the database into times
+ /// able to be inserted into the lease object.
+ ///
+ /// @param expire Reference to MYSQL_TIME object from where the expiry
+ /// time of the lease is taken.
+ /// @param valid_lifetime lifetime of the lease.
+ /// @param cltt Reference to location where client last transmit time
+ /// is put.
+ static void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime,
+ time_t& cltt);
+
+ /// @brief Converts database time to posix time.
+ ///
+ /// @param database_time Reference to MYSQL_TIME object where database
+ /// time is stored.
+ ///
+ /// @return Database time converted to posix time.
+ static boost::posix_time::ptime
+ convertFromDatabaseTime(const MYSQL_TIME& database_time);
+
+private:
+
+ /// @brief Private constructor.
+ ///
+ /// This constructor is private because MySQL bindings should only be
+ /// created using static factory functions.
+ ///
+ /// @param buffer_type MySQL buffer type as defined in MySQL C API.
+ /// @param length Buffer length.
+ MySqlBinding(enum_field_types buffer_type, const size_t length);
+
+ /// @brief Assigns new value to a buffer.
+ ///
+ /// @tparam Iterator Type of the iterators marking beginning and end
+ /// of the range to be assigned to the buffer.
+ ///
+ /// @param begin Iterator pointing to the beginning of the assigned
+ /// range.
+ /// @param end Iterator pointing to the end of the assigned range.
+ template<typename Iterator>
+ void setBufferValue(Iterator begin, Iterator end) {
+ buffer_.assign(begin, end);
+ bind_.buffer = &buffer_[0];
+ bind_.buffer_length = std::distance(begin, end);
+ }
+
+ /// @brief Resizes the buffer and assigns new length to the binding.
+ ///
+ /// @param length New buffer length to be used.
+ void setBufferLength(const unsigned long length);
+
+ /// @brief Copies numeric value to a buffer and modifies "unsigned" flag
+ /// accoriding to the numeric type used.
+ ///
+ /// @tparam T Type of the numeric value.
+ ///
+ /// @param value Value to be copied to the buffer.
+ template<typename T>
+ void setValue(T value) {
+ memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&value),
+ sizeof(value));
+ bind_.buffer = &buffer_[0];
+ bind_.is_unsigned = (MySqlBindingTraits<T>::am_unsigned ? MLM_TRUE : MLM_FALSE);
+ }
+
+ /// @brief Converts timestamp to database time value and copies it to
+ /// the buffer.
+ ///
+ /// @param timestamp Timestamp value as posix time.
+ void setTimestampValue(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Checks if the data accessor called is matching the type
+ /// of the data held in the binding.
+ ///
+ /// @tparam Data type requested, e.g. @c std::string.
+ ///
+ /// @throw InvalidOperation if the requested data type is not matching
+ /// the type of the binding, e.g. called @c getString but the binding
+ /// type is @c MYSQL_TYPE_LONG.
+ template<typename T>
+ void validateAccess() const {
+ // Can't retrieve null value.
+ if (amNull()) {
+ isc_throw(InvalidOperation, "retrieved value is null");
+ }
+ // The type of the accessor must match the stored data type.
+ if (MySqlBindingTraits<T>::column_type != getType()) {
+ isc_throw(InvalidOperation, "mismatched column type");
+ }
+ }
+
+ /// @brief Data buffer (both input and output).
+ std::vector<uint8_t> buffer_;
+
+ /// @brief Buffer length.
+ unsigned long length_;
+
+ /// @brief Flag indicating whether the value of the binding is NULL.
+ my_bool null_value_;
+
+ /// @brief Native MySQL binding wrapped by this class.
+ MYSQL_BIND bind_;
+};
+
+/// @brief Collection of bindings.
+typedef std::vector<MySqlBindingPtr> BindingCollection;
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
// 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 <database/db_log.h>
namespace isc {
namespace db {
-const my_bool MLM_FALSE = 0;
-const my_bool MLM_TRUE = 1;
-const int MLM_MYSQL_FETCH_SUCCESS = 0;
-const int MLM_MYSQL_FETCH_FAILURE = 1;
-
/// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc
const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
void
MySqlConnection::convertToDatabaseTime(const time_t input_time,
MYSQL_TIME& output_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
+ MySqlBinding::convertToDatabaseTime(input_time, output_time);
}
void
MySqlConnection::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);
- }
-
- const time_t expire_time = static_cast<const 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
+ MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire);
}
void
MySqlConnection::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;
+ uint32_t valid_lifetime, time_t& cltt) {
+ MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt);
}
void
#define MYSQL_CONNECTION_H
#include <database/database_connection.h>
+#include <database/db_exceptions.h>
#include <database/db_log.h>
#include <exceptions/exceptions.h>
+#include <mysql/mysql_binding.h>
+#include <mysql/mysql_constants.h>
#include <boost/scoped_ptr.hpp>
#include <mysql.h>
#include <mysqld_error.h>
#include <errmsg.h>
+#include <functional>
#include <vector>
#include <stdint.h>
namespace isc {
namespace db {
-/// @name MySQL constants.
-///
-//@{
-
-/// @brief MySQL false value.
-extern const my_bool MLM_FALSE;
-
-/// @brief MySQL true value.
-extern const my_bool MLM_TRUE;
-
-/// @brief MySQL fetch success code.
-extern const int MLM_MYSQL_FETCH_SUCCESS;
-
-/// @brief MySQL fetch failure code.
-extern const int MLM_MYSQL_FETCH_FAILURE;
-
-//@}
-
-/// @name Current database schema version values.
-//@{
-const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 7;
-const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
-
-//@}
/// @brief Fetch and Release MySQL Results
///
class MySqlConnection : public db::DatabaseConnection {
public:
+ /// @brief Function invoked to process fetched row.
+ typedef std::function<void(BindingCollection&)> ConsumeResultFun;
+
/// @brief Constructor
///
/// Initialize MySqlConnection object with parameters needed for connection.
/// @brief Starts Transaction
void startTransaction();
+ /// @brief Executes SELECT query using prepared statement.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement. The size of
+ /// the @c out_bindings must match the number of selected columns. The
+ /// output bindings must be created and must encapsulate values of
+ /// the appropriate type, e.g. string, uint32_t etc.
+ ///
+ /// This method executes prepared statement using provided bindings and
+ /// calls @c process_result function for each returned row. The
+ /// @c process_result function is implemented by the caller and should
+ /// gather and store each returned row in an external data structure prior
+ /// to returning because the values in the @c out_bindings will be
+ /// overwritten by the values of the next returned row when this function
+ /// is called again.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitue placeholders
+ /// in the query.
+ /// @param [out] out_bindings Output bindings where retrieved data will be
+ /// stored.
+ /// @param process_result Pointer to the function to be invoked for each
+ /// retrieved row. This function consumes the retrieved data from the
+ /// output bindings.
+ template<typename StatementIndex>
+ void selectQuery(const StatementIndex& index,
+ const BindingCollection& in_bindings,
+ BindingCollection& out_bindings,
+ ConsumeResultFun process_result) {
+ // Extract native input bindings.
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ int status = 0;
+ if (!in_bind_vec.empty()) {
+ // Bind parameters to the prepared statement.
+ status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ }
+
+ // Bind variables that will receive results as well.
+ std::vector<MYSQL_BIND> out_bind_vec;
+ for (MySqlBindingPtr out_binding : out_bindings) {
+ out_bind_vec.push_back(out_binding->getMySqlBinding());
+ }
+ if (!out_bind_vec.empty()) {
+ status = mysql_stmt_bind_result(statements_[index], &out_bind_vec[0]);
+ }
+
+ // Execute query.
+ status = mysql_stmt_execute(statements_[index]);
+ checkError(status, index, "unable to execute");
+
+ status = mysql_stmt_store_result(statements_[index]);
+ checkError(status, index, "unable to set up for storing all results");
+
+ // Fetch results.
+ MySqlFreeResult fetch_release(statements_[index]);
+ while ((status = mysql_stmt_fetch(statements_[index])) ==
+ MLM_MYSQL_FETCH_SUCCESS) {
+ try {
+ // For each returned row call user function which should
+ // consume the row and copy the data to a safe place.
+ process_result(out_bindings);
+
+ } catch (const std::exception& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ text_statements_[index] << ">");
+ }
+ }
+
+ // How did the fetch end?
+ // If mysql_stmt_fetch return value is equal to 1 an error occurred.
+ if (status == MLM_MYSQL_FETCH_FAILURE) {
+ // Error - unable to fetch results
+ checkError(status, index, "unable to fetch results");
+
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, text_statements_[index]
+ << " returned truncated data");
+ }
+ }
+
+ /// @brief Executes INSERT prepared statement.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided bindings to
+ /// insert data into the database.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitue placeholders
+ /// in the query.
+ template<typename StatementIndex>
+ void insertQuery(const StatementIndex& index,
+ const BindingCollection& in_bindings) {
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ checkError(status, index, "unable to bind parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(statements_[index]);
+
+ if (status != 0) {
+ // Failure: check for the special case of duplicate entry.
+ if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+ checkError(status, index, "unable to execute");
+ }
+ }
+
+ /// @brief Executes UPDATE or DELETE prepared statement and returns
+ /// the number of affected rows.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitue placeholders
+ /// in the query.
+ ///
+ /// @return Number of affected rows.
+ template<typename StatementIndex>
+ uint64_t updateDeleteQuery(const StatementIndex& index,
+ const BindingCollection& in_bindings) {
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ checkError(status, index, "unable to bind parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(statements_[index]);
+
+ if (status != 0) {
+ checkError(status, index, "unable to execute");
+ }
+
+ // Let's return how many rows were affected.
+ return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[index])));
+ }
+
+
/// @brief Commit Transactions
///
/// Commits all pending database operations. On databases that don't
--- /dev/null
+// Copyright (C) 2018 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/.
+
+#ifndef MYSQL_CONSTANTS_H
+#define MYSQL_CONSTANTS_H
+
+#include <mysql.h>
+
+namespace isc {
+namespace db {
+
+/// @name MySQL constants.
+///
+//@{
+
+/// @brief MySQL false value.
+const my_bool MLM_FALSE = 0;
+
+/// @brief MySQL true value.
+const my_bool MLM_TRUE = 1;
+
+/// @brief MySQL fetch success code.
+const int MLM_MYSQL_FETCH_SUCCESS = 0;
+
+/// @brief MySQL fetch failure code.
+const int MLM_MYSQL_FETCH_FAILURE = 0;
+
+//@}
+
+/// @name Current database schema version values.
+//@{
+const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 7;
+const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
+
+//@}
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
--- /dev/null
+/libmysql_unittests
\ No newline at end of file
--- /dev/null
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libmysql_unittests
+
+libmysql_unittests_SOURCES = mysql_connection_unittest.cc
+libmysql_unittests_SOURCES += run_unittests.cc
+
+libmysql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libmysql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(MYSQL_LIBS)
+
+libmysql_unittests_LDADD = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libmysql_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
--- /dev/null
+// Copyright (C) 2018 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 <mysql/mysql_connection.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <array>
+
+using namespace isc::db;
+using namespace isc::db::test;
+
+namespace {
+
+/// @brief Test fixture class for @c MySqlConnection class.
+class MySqlConnectionTest : public ::testing::Test {
+public:
+
+ /// @brief Indexes of prepared statements used within the tests.
+ enum StatementIndex {
+ GET_BY_INT_VALUE,
+ DELETE_BY_INT_VALUE,
+ INSERT_VALUE,
+ NUM_STATEMENTS
+ };
+
+ /// @brief Array of tagged MySQL statements.
+ typedef std::array<TaggedStatement, NUM_STATEMENTS>
+ TaggedStatementArray;
+
+ /// @brief Prepared MySQL statements used in the tests.
+ TaggedStatementArray tagged_statements = { {
+ { GET_BY_INT_VALUE,
+ "SELECT tinyint_value, int_value, bigint_value, string_value,"
+ " blob_value, timestamp_value"
+ " FROM mysql_connection_test WHERE int_value = ?" },
+
+ { DELETE_BY_INT_VALUE,
+ "DELETE FROM mysql_connection_test WHERE int_value = ?" },
+
+ { INSERT_VALUE,
+ "INSERT INTO mysql_connection_test (tinyint_value, int_value,"
+ "bigint_value, string_value, blob_value, timestamp_value)"
+ " VALUES (?, ?, ?, ?, ?, ?)" }
+ }
+ };
+
+
+ /// @brief Constructor.
+ ///
+ /// Re-creates database schema, opens new database connection and creates
+ /// prepared statements used in tests. Created schema contains a test
+ /// table @c mysql_connection_test which includes 6 columns of various
+ /// types.
+ MySqlConnectionTest()
+ : conn_(DatabaseConnection::parse(validMySQLConnectionString())) {
+
+ try {
+ // Open new connection.
+ conn_.openDatabase();
+ my_bool result = mysql_autocommit(conn_.mysql_, 1);
+ if (result != 0) {
+ isc_throw(DbOperationError, "failed to set autocommit option "
+ "for test MySQL connection");
+ }
+
+ // Create mysql_connection_test table.
+ createTestTable();
+
+ // Created prepared statements for basic queries to test table.
+ conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.end());
+
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Removes test table from the database.
+ virtual ~MySqlConnectionTest() {
+ conn_.rollback();
+ dropTestTable();
+ }
+
+ /// @brief Creates test table @c mysql_connection_test.
+ ///
+ /// The new table contains 6 columns of various data types. All of
+ /// the columns accept null values.
+ void createTestTable() {
+ runQuery("CREATE TABLE IF NOT EXISTS mysql_connection_test ("
+ "tinyint_value TINYINT NULL,"
+ "int_value INT NULL,"
+ "bigint_value BIGINT NULL,"
+ "string_value TEXT NULL,"
+ "blob_value BLOB NULL,"
+ "timestamp_value TIMESTAMP NULL"
+ ")");
+ }
+
+ /// @brief Drops test table.
+ void dropTestTable() {
+ runQuery("DROP TABLE IF EXISTS mysql_connection_test");
+ }
+
+ /// @brief Runs MySQL query on the opened connection.
+ ///
+ /// @param sql Query in the textual form.
+ void runQuery(const std::string& sql) {
+ MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
+ if (stmt == NULL) {
+ isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+ "statement structure, reason: " << mysql_error(conn_.mysql_));
+ }
+
+ int status = mysql_stmt_prepare(stmt, sql.c_str(), sql.length());
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to prepare MySQL statement <"
+ << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ }
+
+ // Execute the prepared statement.
+ if (mysql_stmt_execute(stmt) != 0) {
+ isc_throw(DbOperationError, "cannot execute MySQL query <"
+ << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ }
+
+ // Discard the statement and its resources
+ mysql_stmt_close(stmt);
+ }
+
+
+ /// @brief Tests inserting and retrieving data from the database.
+ ///
+ /// In this test data carried in the bindings is inserted into the database.
+ /// Then this data is retrieved from the database and compared with the
+ /// orginal.
+ ///
+ /// @param in_bindings Collection of bindings encapsulating the data to
+ /// be inserted into the database and then retrieved.
+ void testInsertSelect(const BindingCollection& in_bindings) {
+ // Expecting 6 bindings because we have 6 columns in our table.
+ ASSERT_EQ(6, in_bindings.size());
+ // We are going to select by int_value so this value must not be null.
+ ASSERT_FALSE(in_bindings[1]->amNull());
+
+ // Store data in the database.
+ ASSERT_NO_THROW(conn_.insertQuery(MySqlConnectionTest::INSERT_VALUE,
+ in_bindings));
+
+ // Create input binding for select query.
+ BindingCollection bindings =
+ { MySqlBinding::createInteger<uint32_t>(in_bindings[1]->getInteger<uint32_t>()) };
+
+ // Also, create output (placeholder) bindings for receiving data.
+ BindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint8_t>(),
+ MySqlBinding::createInteger<uint32_t>(),
+ MySqlBinding::createInteger<int64_t>(),
+ MySqlBinding::createString(512),
+ MySqlBinding::createBlob(512),
+ MySqlBinding::createTimestamp()
+ };
+
+ // Execute select statement. We expect one row to be returned. For this
+ // returned row the lambda provided as 4th argument should be executed.
+ ASSERT_NO_THROW(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
+ bindings, out_bindings,
+ [&](BindingCollection& out_bindings) {
+
+ // Compare received data with input data assuming they are both non-null.
+
+ if (!out_bindings[0]->amNull() && !in_bindings[0]->amNull()) {
+ EXPECT_EQ(static_cast<int>(in_bindings[0]->getInteger<uint8_t>()),
+ static_cast<int>(out_bindings[0]->getInteger<uint8_t>()));
+ }
+
+ if (!out_bindings[1]->amNull() && !in_bindings[1]->amNull()) {
+ EXPECT_EQ(in_bindings[1]->getInteger<uint32_t>(),
+ out_bindings[1]->getInteger<uint32_t>());
+ }
+
+ if (!out_bindings[2]->amNull() && !in_bindings[2]->amNull()) {
+ EXPECT_EQ(in_bindings[2]->getInteger<int64_t>(),
+ out_bindings[2]->getInteger<int64_t>());
+ }
+
+ if (!out_bindings[3]->amNull() && !in_bindings[3]->amNull()) {
+ EXPECT_EQ(in_bindings[3]->getString(),
+ out_bindings[3]->getString());
+ }
+
+ if (!out_bindings[4]->amNull() && !in_bindings[4]->amNull()) {
+ EXPECT_EQ(in_bindings[4]->getBlob(),
+ out_bindings[4]->getBlob());
+ }
+
+ if (!out_bindings[5]->amNull() && !in_bindings[5]->amNull()) {
+ EXPECT_TRUE(in_bindings[5]->getTimestamp() ==
+ out_bindings[5]->getTimestamp());
+ }
+ }));
+
+ // Make sure that null values were returned for columns for which null
+ // was set.
+ ASSERT_EQ(in_bindings.size(), out_bindings.size());
+ for (auto i = 0; i < in_bindings.size(); ++i) {
+ EXPECT_EQ(in_bindings[i]->amNull(), out_bindings[i]->amNull())
+ << "null value test failed for binding #" << i;
+ }
+ }
+
+ /// @brief Test MySQL connection.
+ MySqlConnection conn_;
+
+};
+
+// Test that non-null values of various types can be inserted and retrieved
+// from the dataabse.
+TEST_F(MySqlConnectionTest, select) {
+ std::string blob = "myblob";
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createTimestamp(boost::posix_time::microsec_clock::universal_time())
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that null value can be inserted to a column having numeric type and
+// retrieved.
+TEST_F(MySqlConnectionTest, selectNullInteger) {
+ std::string blob = "myblob";
+ BindingCollection in_bindings = {
+ MySqlBinding::createNull(),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createTimestamp(boost::posix_time::microsec_clock::universal_time())
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that null value can be inserted to a column having string type and
+// retrieved.
+TEST_F(MySqlConnectionTest, selectNullString) {
+ std::string blob = "myblob";
+
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createNull(),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createTimestamp(boost::posix_time::microsec_clock::universal_time())
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that null value can be inserted to a column having blob type and
+// retrieved.
+TEST_F(MySqlConnectionTest, selectNullBlob) {
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createNull(),
+ MySqlBinding::createTimestamp(boost::posix_time::microsec_clock::universal_time())
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that null value can be inserted to a column having timestamp type and
+// retrieved.
+TEST_F(MySqlConnectionTest, selectNullTimestamp) {
+ std::string blob = "myblob";
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createNull()
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that empty string and empty blob can be inserted to a database.
+TEST_F(MySqlConnectionTest, selectEmptyStringBlob) {
+ std::string blob = "";
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString(""),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createTimestamp(boost::posix_time::microsec_clock::universal_time())
+ };
+
+ testInsertSelect(in_bindings);
+}
+
+// Test that a row can be deleted from the database.
+TEST_F(MySqlConnectionTest, deleteByValue) {
+ // Insert a row with numeric values.
+ BindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createNull(),
+ MySqlBinding::createNull(),
+ MySqlBinding::createNull()
+ };
+ ASSERT_NO_THROW(conn_.insertQuery(MySqlConnectionTest::INSERT_VALUE,
+ in_bindings));
+
+ // This variable will be checked to see if the row has been deleted
+ // from the database.
+ bool deleted = false;
+
+ // Execute delete query but use int_value of non existing row.
+ // The row should not be deleted.
+ in_bindings = { MySqlBinding::createInteger<uint32_t>(1) };
+ ASSERT_NO_THROW(deleted = conn_.updateDeleteQuery(MySqlConnectionTest::DELETE_BY_INT_VALUE,
+ in_bindings));
+ ASSERT_FALSE(deleted);
+
+ // This time use the correct value.
+ in_bindings = { MySqlBinding::createInteger<uint32_t>(1024) };
+ ASSERT_NO_THROW(deleted = conn_.updateDeleteQuery(MySqlConnectionTest::DELETE_BY_INT_VALUE,
+ in_bindings));
+ // The row should have been deleted.
+ ASSERT_TRUE(deleted);
+
+ // Let's confirm that it has been deleted by issuing a select query.
+ BindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint8_t>(),
+ MySqlBinding::createInteger<uint32_t>(),
+ MySqlBinding::createInteger<int64_t>(),
+ MySqlBinding::createString(512),
+ MySqlBinding::createBlob(512),
+ MySqlBinding::createTimestamp()
+ };
+
+ ASSERT_NO_THROW(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
+ in_bindings, out_bindings,
+ [&deleted](BindingCollection& out_bindings) {
+ // This will be executed if the row is returned as a result of
+ // select query. We expect that this is not executed.
+ deleted = false;
+ }));
+ // Make sure that select query returned nothing.
+ EXPECT_TRUE(deleted);
+}
+
+}
--- /dev/null
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}