Commit 479791d7 authored by Marcin Siodelski's avatar Marcin Siodelski

[#93,!35] Extended MySqlConnection with generic query functions.

parent 9994f64b
......@@ -1529,6 +1529,8 @@ AC_CONFIG_FILES([Makefile
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
......@@ -1593,6 +1595,7 @@ AC_CONFIG_FILES([Makefile
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
......
......@@ -17,128 +17,6 @@ using namespace isc::db;
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 {
......@@ -252,10 +130,9 @@ MySqlConfigBackendDHCPv4::getSubnet4(const ServerSelector& selector,
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)
......@@ -9,6 +9,8 @@ CLEANFILES = *.gcno *.gcda
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
......
// 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
// 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();