From: Thomas Markwalder Date: Wed, 18 May 2016 11:32:42 +0000 (-0400) Subject: [4276] Created new base class, PgSqlExchange X-Git-Tag: trac4106_update_base~12^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09e4931b63d61986f6056d69f4b7dc7284071863;p=thirdparty%2Fkea.git [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.* --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 06a7fe09f3..6a3b10fb02 100755 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -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 diff --git a/src/lib/dhcpsrv/pgsql_exchange.cc b/src/lib/dhcpsrv/pgsql_exchange.cc new file mode 100644 index 0000000000..69f8bb8cb3 --- /dev/null +++ b/src/lib/dhcpsrv/pgsql_exchange.cc @@ -0,0 +1,222 @@ +// 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 + +#include + +#include +#include +#include + +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& data) { + values_.push_back(reinterpret_cast(&(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(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(cltt) + + static_cast(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(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(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(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(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(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 diff --git a/src/lib/dhcpsrv/pgsql_exchange.h b/src/lib/dhcpsrv/pgsql_exchange.h new file mode 100644 index 0000000000..ec36d370dd --- /dev/null +++ b/src/lib/dhcpsrv/pgsql_exchange.h @@ -0,0 +1,243 @@ +// 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 + +#include + +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 values_; + /// @brief Vector of data lengths for each value. + std::vector 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 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& 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 ))". + /// + /// @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::vectorcolumn_labels_; +}; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // PGSQL_EXCHANGE_MGR_H diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index a407ae7310..3bae6d9e7f 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -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& data) { - values_.push_back(reinterpret_cast(&(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(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 ))". - /// - /// @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(cltt) + - static_cast(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(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(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); - } - - /// @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 { - 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'"); - } - } - - /// @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 { - const char* data = getRawColumnValue(r, row, col); - try { - value = boost::lexical_cast(data); - } catch (const std::exception& ex) { - isc_throw(DbOperationError, "Invalid uint32_t data: " << data - << " for: " << getColumnLabel(col) << " row:" << row - << " : " << ex.what()); - } - } - - /// @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 { - const char* data = getRawColumnValue(r, row, col); - try { - value = boost::lexical_cast(data); - } catch (const std::exception& ex) { - isc_throw(DbOperationError, "Invalid int32_t data: " << data - << " for: " << getColumnLabel(col) << " row:" << row - << " : " << ex.what()); - } - } - - /// @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 { - 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(data); - } catch (const std::exception& ex) { - isc_throw(DbOperationError, "Invalid uint8_t data: " << data - << " for: " << getColumnLabel(col) << " row:" << row - << " : " << ex.what()); - } - } - - /// @brief Fetches an integer text column as a Lease6::Type - /// - /// @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, - Lease6::Type& value) const { - uint32_t raw_value = 0; - getColumnValue(r, row , col, raw_value); - switch (raw_value) { - case Lease6::TYPE_NA: - value = Lease6::TYPE_NA; - break; - - case Lease6::TYPE_TA: - value = Lease6::TYPE_TA; - break; - - case Lease6::TYPE_PD: - value = Lease6::TYPE_PD; - break; - - default: - isc_throw(DbOperationError, "Invalid lease type: " << raw_value - << " for: " << getColumnLabel(col) << " row:" << row); - } - } - - /// @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 { - - // 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); - } - - /// @brief Returns column label given a column number - std::string getColumnLabel(const size_t column) const { - if (column > column_labels_.size()) { - ostringstream os; - os << "Unknown column:" << column; - return (os.str()); - } - - return (column_labels_[column]); - } - protected: - /// @brief Stores text labels for columns, currently only used for - /// logging and errors. - std::vectorcolumn_labels_; - /// @brief Common Instance members used for binding and conversion //@{ std::string addr_str_; @@ -905,7 +578,7 @@ public: getColumnValue(r, row , PREF_LIFETIME_COL, pref_lifetime_); - getColumnValue(r, row, LEASE_TYPE_COL, lease_type_); + getLeaseTypeColumnValue(r, row, LEASE_TYPE_COL, lease_type_); getColumnValue(r, row , IAID_COL, iaid_u_.ival_); @@ -933,6 +606,35 @@ public: } } + /// @brief Fetches an integer text column as a Lease6::Type + /// + /// @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 + /// + /// Note we depart from overloading getColumnValue to avoid ambiguity + /// with base class methods for integers. + /// + /// @throw DbOperationError if the value cannot be fetched or is + /// invalid. + void getLeaseTypeColumnValue(PGresult*& r, const int row, const size_t col, + Lease6::Type& value) const { + uint32_t raw_value = 0; + getColumnValue(r, row , col, raw_value); + switch (raw_value) { + case Lease6::TYPE_NA: + case Lease6::TYPE_TA: + case Lease6::TYPE_PD: + value = static_cast(raw_value); + break; + + default: + isc_throw(DbOperationError, "Invalid lease type: " << raw_value + << " for: " << getColumnLabel(col) << " row:" << row); + } + } + /// @brief Converts a column in a row in a result set into IPv6 address. /// /// @param r the result set containing the query results @@ -993,7 +695,16 @@ PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters) : LeaseMgr(), exchange4_(new PgSqlLease4Exchange()), exchange6_(new PgSqlLease6Exchange()), conn_(parameters) { conn_.openDatabase(); - prepareStatements(); + int i = 0; + for( ; tagged_statements[i].text != NULL ; ++i) { + conn_.prepareStatement(tagged_statements[i]); + } + + // Just in case somebody foo-barred things + if (i != NUM_STATEMENTS) { + isc_throw(DbOpenError, "Number of statements prepared: " << i + << " does not match expected count:" << NUM_STATEMENTS); + } } PgSqlLeaseMgr::~PgSqlLeaseMgr() { @@ -1008,13 +719,6 @@ PgSqlLeaseMgr::getDBVersion() { return (tmp.str()); } -void -PgSqlLeaseMgr::prepareStatements() { - for(int i = 0; tagged_statements[i].text != NULL; ++ i) { - conn_.prepareStatement(tagged_statements[i]); - } -} - bool PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex, PsqlBindArray& bind_array) { diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index d84f4fe8c4..2457b1c1f0 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -19,86 +20,6 @@ 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 values_; - /// @brief Vector of data lengths for each value. - std::vector 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 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& 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(); -}; - // Forward definitions (needed for shared_ptr definitions) // See pgsql_lease_mgr.cc file for actual class definitions class PgSqlLease4Exchange; @@ -469,26 +390,6 @@ public: private: - /// @brief Prepare statements - /// - /// Creates the prepared statements for all of the SQL statements used - /// by the PostgreSQL backend. - /// - /// @throw isc::dhcp::DbOperationError An operation on the open database has - /// failed. - /// @throw isc::InvalidParameter 'index' is not valid for the vector. This - /// represents an internal error within the code. - void prepareStatements(); - - /// @brief Open Database - /// - /// Opens the database using the information supplied in the parameters - /// passed to the constructor. - /// - /// @throw NoDatabaseName Mandatory database name not given - /// @throw DbOpenError Error opening the database - void openDatabase(); - /// @brief Add Lease Common Code /// /// This method performs the common actions for both flavours (V4 and V6)