Commit 09e4931b authored by Thomas Markwalder's avatar Thomas Markwalder

[4276] Created new base class, PgSqlExchange

src/lib/dhcpsrv/Makefile.am
    Added pgsql_exchange.cc and pgsql_exchange.h

src/lib/dhcpsrv/pgsql_exchange.h
src/lib/dhcpsrv/pgsql_exchange.cc
    New files, containng new base class PgSqlExchange from
    which was distilled from PgSqlLeaseExchange

src/lib/dhcpsrv/pgsql_lease_mgr.cc
    Refactored exchange classes to use new base class
    Moved PsqlBindArray into pgsql_exchange.*
parent b1c67df1
......@@ -134,6 +134,7 @@ libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
if HAVE_PGSQL
libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h
libkea_dhcpsrv_la_SOURCES += pgsql_exchange.cc pgsql_exchange.h
libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
endif
libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
......
// Copyright (C) 2016 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 <dhcpsrv/pgsql_exchange.h>
#include <boost/lexical_cast.hpp>
#include <iomanip>
#include <sstream>
#include <vector>
namespace isc {
namespace dhcp {
const int PsqlBindArray::TEXT_FMT = 0;
const int PsqlBindArray::BINARY_FMT = 1;
const char* PsqlBindArray::TRUE_STR = "TRUE";
const char* PsqlBindArray::FALSE_STR = "FALSE";
void PsqlBindArray::add(const char* value) {
values_.push_back(value);
lengths_.push_back(strlen(value));
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::string& value) {
values_.push_back(value.c_str());
lengths_.push_back(value.size());
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::vector<uint8_t>& data) {
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
lengths_.push_back(data.size());
formats_.push_back(BINARY_FMT);
}
void PsqlBindArray::add(const bool& value) {
add(value ? TRUE_STR : FALSE_STR);
}
std::string PsqlBindArray::toText() {
std::ostringstream stream;
for (int i = 0; i < values_.size(); ++i) {
stream << i << " : ";
if (formats_[i] == TEXT_FMT) {
stream << "\"" << values_[i] << "\"" << std::endl;
} else {
const char *data = values_[i];
if (lengths_[i] == 0) {
stream << "empty" << std::endl;
} else {
stream << "0x";
for (int i = 0; i < lengths_[i]; ++i) {
stream << std::setfill('0') << std::setw(2)
<< std::setbase(16)
<< static_cast<unsigned int>(data[i]);
}
stream << std::endl;
}
}
}
return (stream.str());
}
std::string
PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
struct tm tinfo;
char buffer[20];
localtime_r(&input_time, &tinfo);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
return (std::string(buffer));
}
std::string
PgSqlExchange::convertToDatabaseTime(const time_t cltt,
const uint32_t valid_lifetime) {
// 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);
// It has been observed that the PostgreSQL doesn't deal well with the
// timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
// beginning of the epoch (around year 2038). The value is often
// stored in the database but it is invalid when read back (overflow?).
// Hence, the maximum timestamp value is restricted here.
if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
}
return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
}
time_t
PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
// Convert string time value to time_t
time_t new_time;
try {
new_time = (boost::lexical_cast<time_t>(db_time_val));
} catch (const std::exception& ex) {
isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
}
return (new_time);
}
const char*
PgSqlExchange::getRawColumnValue(PGresult*& r, const int row,
const size_t col) const {
const char* value = PQgetvalue(r, row, col);
if (!value) {
isc_throw(DbOperationError, "getRawColumnValue no data for :"
<< getColumnLabel(col) << " row:" << row);
}
return (value);
}
void
PgSqlExchange::getColumnValue(PGresult*& r, const int row, const size_t col,
bool &value) const {
const char* data = getRawColumnValue(r, row, col);
if (!strlen(data) || *data == 'f') {
value = false;
} else if (*data == 't') {
value = true;
} else {
isc_throw(DbOperationError, "Invalid boolean data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : must be 't' or 'f'");
}
}
void
PgSqlExchange::getColumnValue(PGresult*& r, const int row, const size_t col,
uint32_t &value) const {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<uint32_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint32_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
void
PgSqlExchange::getColumnValue(PGresult*& r, const int row, const size_t col,
int32_t &value) const {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<int32_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid int32_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
void
PgSqlExchange::getColumnValue(PGresult*& r, const int row, const size_t col,
uint8_t &value) const {
const char* data = getRawColumnValue(r, row, col);
try {
// lexically casting as uint8_t doesn't convert from char
// so we use uint16_t and implicitly convert.
value = boost::lexical_cast<uint16_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint8_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
void
PgSqlExchange::convertFromBytea(PGresult*& r, const int row, const size_t col,
uint8_t* buffer, const size_t buffer_size,
size_t &bytes_converted) const {
// Returns converted bytes in a dynamically allocated buffer, and
// sets bytes_converted.
unsigned char* bytes = PQunescapeBytea((const unsigned char*)
(getRawColumnValue(r, row, col)),
&bytes_converted);
// Unlikely it couldn't allocate it but you never know.
if (!bytes) {
isc_throw (DbOperationError, "PQunescapeBytea failed for:"
<< getColumnLabel(col) << " row:" << row);
}
// Make sure it's not larger than expected.
if (bytes_converted > buffer_size) {
// Free the allocated buffer first!
PQfreemem(bytes);
isc_throw (DbOperationError, "Converted data size: "
<< bytes_converted << " is too large for: "
<< getColumnLabel(col) << " row:" << row);
}
// Copy from the allocated buffer to caller's buffer the free up
// the allocated buffer.
memcpy(buffer, bytes, bytes_converted);
PQfreemem(bytes);
}
std::string
PgSqlExchange::getColumnLabel(const size_t column) const {
if (column > column_labels_.size()) {
std::ostringstream os;
os << "Unknown column:" << column;
return (os.str());
}
return (column_labels_[column]);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace
// Copyright (C) 2016 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 PGSQL_EXCHANGE_MGR_H
#define PGSQL_EXCHANGE_MGR_H
#include <dhcpsrv/pgsql_connection.h>
#include <vector>
namespace isc {
namespace dhcp {
/// @brief Structure used to bind C++ input values to dynamic SQL parameters
/// The structure contains three vectors which store the input values,
/// data lengths, and formats. These vectors are passed directly into the
/// PostgreSQL execute call.
///
/// Note that the data values are stored as pointers. These pointers need to
/// valid for the duration of the PostgreSQL statement execution. In other
/// words populating them with pointers to values that go out of scope before
/// statement is executed is a bad idea.
struct PsqlBindArray {
/// @brief Vector of pointers to the data values.
std::vector<const char *> values_;
/// @brief Vector of data lengths for each value.
std::vector<int> lengths_;
/// @brief Vector of "format" for each value. A value of 0 means the
/// value is text, 1 means the value is binary.
std::vector<int> formats_;
/// @brief Format value for text data.
static const int TEXT_FMT;
/// @brief Format value for binary data.
static const int BINARY_FMT;
/// @brief Constant string passed to DB for boolean true values.
static const char* TRUE_STR;
/// @brief Constant string passed to DB for boolean false values.
static const char* FALSE_STR;
/// @brief Fetches the number of entries in the array.
/// @return Returns size_t containing the number of entries.
size_t size() {
return (values_.size());
}
/// @brief Indicates it the array is empty.
/// @return Returns true if there are no entries in the array, false
/// otherwise.
bool empty() {
return (values_.empty());
}
/// @brief Adds a char array to bind array based
///
/// Adds a TEXT_FMT value to the end of the bind array, using the given
/// char* as the data source. Note that value is expected to be NULL
/// terminated.
///
/// @param value char array containing the null-terminated text to add.
void add(const char* value);
/// @brief Adds an string value to the bind array
///
/// Adds a TEXT formatted value to the end of the bind array using the
/// given string as the data source.
///
/// @param value std::string containing the value to add.
void add(const std::string& value);
/// @brief Adds a binary value to the bind array.
///
/// Adds a BINARY_FMT value to the end of the bind array using the
/// given vector as the data source.
///
/// @param data vector of binary bytes.
void add(const std::vector<uint8_t>& data);
/// @brief Adds a boolean value to the bind array.
///
/// Converts the given boolean value to its corresponding to PostgreSQL
/// string value and adds it as a TEXT_FMT value to the bind array.
///
/// @param value bool value to add.
void add(const bool& value);
/// @brief Dumps the contents of the array to a string.
/// @return std::string containing the dump
std::string toText();
};
/// @brief Base class for marshalling data to and from PostgreSQL.
///
/// Provides the common functionality to set up binding information between
/// application objects in the program and their representation in the
/// database, and for retrieving column values from rows of a result set.
class PgSqlExchange {
public:
/// @brief Constructor
PgSqlExchange(){}
/// @brief Destructor
virtual ~PgSqlExchange(){}
/// @brief Converts time_t value to a text representation in local time.
///
/// @param input_time A time_t value representing time.
/// @return std::string containing stringified time.
static std::string convertToDatabaseTime(const time_t input_time);
/// @brief Converts lease expiration time to a text representation in
/// local time.
///
/// The expiration time is calculated as a sum of the cltt (client last
/// transmit time) and the valid lifetime.
///
/// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
/// table columns using this value should be typed as TIMESTAMP WITH
/// TIME ZONE. For such columns PostgreSQL assumes input strings without
/// timezones should be treated as in local time and are converted to UTC
/// when stored. Likewise, these columns are automatically adjusted
/// upon retrieval unless fetched via "extract(epoch from <column>))".
///
/// @param cltt Client last transmit time
/// @param valid_lifetime Valid lifetime
///
/// @return std::string containing the stringified time
/// @throw isc::BadValue if the sum of the calculated expiration time is
/// greater than the value of @c DataSource::MAX_DB_TIME.
static std::string convertToDatabaseTime(const time_t cltt,
const uint32_t valid_lifetime);
/// @brief Converts time stamp from the database to a time_t
///
/// @param db_time_val timestamp to be converted. This value
/// is expected to be the number of seconds since the epoch
/// expressed as base-10 integer string.
/// @return Converted timestamp as time_t value.
static time_t convertFromDatabaseTime(const std::string& db_time_val);
/// @brief Gets a pointer to the raw column value in a result set row
///
/// Given a result set, row, and column return a const char* pointer to
/// the data value in the result set. The pointer is valid as long as
/// the result set has not been freed. It may point to text or binary
/// data depending on how query was structured. You should not attempt
/// to free this pointer.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
const char* getRawColumnValue(PGresult*& r, const int row,
const size_t col) const;
/// @brief Fetches boolean text ('t' or 'f') as a bool.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
bool &value) const;
/// @brief Fetches an integer text column as a uint32_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
uint32_t &value) const;
/// @brief Fetches an integer text column as a int32_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
int32_t &value) const;
/// @brief Fetches an integer text column as a uint8_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
uint8_t &value) const;
/// @brief Converts a column in a row in a result set to a binary bytes
///
/// Method is used to convert columns stored as BYTEA into a buffer of
/// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] buffer pre-allocated buffer to which the converted bytes
/// will be stored.
/// @param buffer_size size of the output buffer
/// @param[out] bytes_converted number of bytes converted
/// value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void convertFromBytea(PGresult*& r, const int row, const size_t col,
uint8_t* buffer, const size_t buffer_size,
size_t &bytes_converted) const;
/// @brief Returns column label given a column number
std::string getColumnLabel(const size_t column) const;
protected:
/// @brief Stores text labels for columns, currently only used for
/// logging and errors.
std::vector<std::string>column_labels_;
};
}; // end of isc::dhcp namespace
}; // end of isc namespace
#endif // PGSQL_EXCHANGE_MGR_H
......@@ -208,66 +208,14 @@ PgSqlTaggedStatement tagged_statements[] = {
namespace isc {
namespace dhcp {
const int PsqlBindArray::TEXT_FMT = 0;
const int PsqlBindArray::BINARY_FMT = 1;
const char* PsqlBindArray::TRUE_STR = "TRUE";
const char* PsqlBindArray::FALSE_STR = "FALSE";
void PsqlBindArray::add(const char* value) {
values_.push_back(value);
lengths_.push_back(strlen(value));
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::string& value) {
values_.push_back(value.c_str());
lengths_.push_back(value.size());
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::vector<uint8_t>& data) {
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
lengths_.push_back(data.size());
formats_.push_back(BINARY_FMT);
}
void PsqlBindArray::add(const bool& value) {
add(value ? TRUE_STR : FALSE_STR);
}
std::string PsqlBindArray::toText() {
std::ostringstream stream;
for (int i = 0; i < values_.size(); ++i) {
stream << i << " : ";
if (formats_[i] == TEXT_FMT) {
stream << "\"" << values_[i] << "\"" << std::endl;
} else {
const char *data = values_[i];
if (lengths_[i] == 0) {
stream << "empty" << std::endl;
} else {
stream << "0x";
for (int i = 0; i < lengths_[i]; ++i) {
stream << setfill('0') << setw(2) << setbase(16)
<< static_cast<unsigned int>(data[i]);
}
stream << std::endl;
}
}
}
return (stream.str());
}
/// @brief Base class for marshalling leases to and from PostgreSQL.
///
/// Provides the common functionality to set up binding information between
/// lease objects in the program and their database representation in the
/// database.
class PgSqlLeaseExchange {
class PgSqlLeaseExchange : public PgSqlExchange {
public:
PgSqlLeaseExchange()
: addr_str_(""), valid_lifetime_(0), valid_lft_str_(""),
expire_(0), expire_str_(""), subnet_id_(0), subnet_id_str_(""),
......@@ -277,282 +225,7 @@ public:
virtual ~PgSqlLeaseExchange(){}
/// @brief Converts time_t value to a text representation in local time.
///
/// @param input_time A time_t value representing time.
/// @return std::string containing stringified time.
static std::string
convertToDatabaseTime(const time_t input_time) {
struct tm tinfo;
char buffer[20];
localtime_r(&input_time, &tinfo);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
return (std::string(buffer));
}
/// @brief Converts lease expiration time to a text representation in
/// local time.
///
/// The expiration time is calculated as a sum of the cltt (client last
/// transmit time) and the valid lifetime.
///
/// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
/// table columns using this value should be typed as TIMESTAMP WITH
/// TIME ZONE. For such columns PostgreSQL assumes input strings without
/// timezones should be treated as in local time and are converted to UTC
/// when stored. Likewise, these columns are automatically adjusted
/// upon retrieval unless fetched via "extract(epoch from <column>))".
///
/// @param cltt Client last transmit time
/// @param valid_lifetime Valid lifetime
///
/// @return std::string containing the stringified time
/// @throw isc::BadValue if the sum of the calculated expiration time is
/// greater than the value of @c DataSource::MAX_DB_TIME.
static std::string
convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime) {
// 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);
// It has been observed that the PostgreSQL doesn't deal well with the
// timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
// beginning of the epoch (around year 2038). The value is often
// stored in the database but it is invalid when read back (overflow?).
// Hence, the maximum timestamp value is restricted here.
if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
}
return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
}
/// @brief Converts time stamp from the database to a time_t
///
/// @param db_time_val timestamp to be converted. This value
/// is expected to be the number of seconds since the epoch
/// expressed as base-10 integer string.
/// @return Converted timestamp as time_t value.
static time_t convertFromDatabaseTime(const std::string& db_time_val) {
// Convert string time value to time_t
try {
return (boost::lexical_cast<time_t>(db_time_val));
} catch (const std::exception& ex) {
isc_throw(BadValue, "Database time value is invalid: "
<< db_time_val);
}
}
/// @brief Gets a pointer to the raw column value in a result set row
///
/// Given a result set, row, and column return a const char* pointer to
/// the data value in the result set. The pointer is valid as long as
/// the result set has not been freed. It may point to text or binary
/// data depending on how query was structured. You should not attempt
/// to free this pointer.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
const char* getRawColumnValue(PGresult*& r, const int row,
const size_t col) const {
const char* value = PQgetvalue(r, row, col);
if (!value) {
isc_throw(DbOperationError, "getRawColumnValue no data for :"
<< getColumnLabel(col) << " row:" << row);
}
return (value);
}