mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 04:57:52 +00:00
[4277] Addressed bulk of review comments
src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc - Added PgSqlBasicsTest test fixture class and tests which exercise all of the PostgreSQL data types we currently use with round-trip database writes and reads src/lib/dhcpsrv/pgsql_connection.cc src/lib/dhcpsrv/pgsql_connection.h - Moved PgSqlResult function impls from .h - Added exception safe implementation of getColumnLabel() to PgSqlResult src/lib/dhcpsrv/pgsql_exchange.cc src/lib/dhcpsrv/pgsql_exchange.h - PsqlBindArray::add() variants which accept raw pointers now throw if the pointer is NULL - PgSqlExchange::getColumnLabel() is now a wrapper around PgSqlResult method src/lib/dhcpsrv/pgsql_host_data_source.h src/lib/dhcpsrv/pgsql_host_data_source.cc - Commentary clean up src/lib/dhcpsrv/pgsql_lease_mgr.cc - Commentary clean up
This commit is contained in:
parent
3d40522867
commit
15b51b6229
@ -35,6 +35,59 @@ const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
|
|||||||
|
|
||||||
const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
|
const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
|
||||||
|
|
||||||
|
PgSqlResult::PgSqlResult(PGresult *result)
|
||||||
|
: result_(result), rows_(0), cols_(0) {
|
||||||
|
if (!result) {
|
||||||
|
isc_throw (BadValue, "PgSqlResult result pointer cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
rows_ = PQntuples(result);
|
||||||
|
cols_ = PQnfields(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PgSqlResult::rowCheck(int row) const {
|
||||||
|
if (row < 0 || row >= rows_) {
|
||||||
|
isc_throw (DbOperationError, "row: " << row
|
||||||
|
<< ", out of range: 0.." << rows_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PgSqlResult::~PgSqlResult() {
|
||||||
|
if (result_) {
|
||||||
|
PQclear(result_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PgSqlResult::colCheck(int col) const {
|
||||||
|
if (col < 0 || col >= cols_) {
|
||||||
|
isc_throw (DbOperationError, "col: " << col
|
||||||
|
<< ", out of range: 0.." << cols_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PgSqlResult::rowColCheck(int row, int col) const {
|
||||||
|
rowCheck(row);
|
||||||
|
colCheck(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
PgSqlResult::getColumnLabel(const int col) const {
|
||||||
|
const char* label = NULL;
|
||||||
|
try {
|
||||||
|
colCheck(col);
|
||||||
|
label = PQfname(result_, col);
|
||||||
|
} catch (...) {
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Unknown column:" << col;
|
||||||
|
return (os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (label);
|
||||||
|
}
|
||||||
|
|
||||||
PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
|
PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
|
||||||
: conn_(conn), committed_(false) {
|
: conn_(conn), committed_(false) {
|
||||||
conn_.startTransaction();
|
conn_.startTransaction();
|
||||||
|
@ -28,7 +28,6 @@ const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 32;
|
|||||||
/// Each statement is associated with an index, which is used to reference the
|
/// Each statement is associated with an index, which is used to reference the
|
||||||
/// associated prepared statement.
|
/// associated prepared statement.
|
||||||
struct PgSqlTaggedStatement {
|
struct PgSqlTaggedStatement {
|
||||||
|
|
||||||
/// Number of parameters for a given query
|
/// Number of parameters for a given query
|
||||||
int nbparams;
|
int nbparams;
|
||||||
|
|
||||||
@ -48,16 +47,16 @@ struct PgSqlTaggedStatement {
|
|||||||
|
|
||||||
/// @brief Constants for PostgreSQL data types
|
/// @brief Constants for PostgreSQL data types
|
||||||
/// This are defined by PostreSQL in <catalog/pg_type.h>, but including
|
/// This are defined by PostreSQL in <catalog/pg_type.h>, but including
|
||||||
/// this file is extrordinarily convoluted, so we'll use these to fill-in.
|
/// this file is extraordinarily convoluted, so we'll use these to fill-in.
|
||||||
const size_t OID_NONE = 0; // PostgreSQL infers proper type
|
const size_t OID_NONE = 0; // PostgreSQL infers proper type
|
||||||
const size_t OID_BOOL = 16;
|
const size_t OID_BOOL = 16;
|
||||||
const size_t OID_BYTEA = 17;
|
const size_t OID_BYTEA = 17;
|
||||||
const size_t OID_INT8 = 20; // 8 byte int
|
const size_t OID_INT8 = 20; // 8 byte int
|
||||||
const size_t OID_INT4 = 23; // 4 byte int
|
|
||||||
const size_t OID_INT2 = 21; // 2 byte int
|
const size_t OID_INT2 = 21; // 2 byte int
|
||||||
|
const size_t OID_INT4 = 23; // 4 byte int
|
||||||
const size_t OID_TEXT = 25;
|
const size_t OID_TEXT = 25;
|
||||||
const size_t OID_TIMESTAMP = 1114;
|
|
||||||
const size_t OID_VARCHAR = 1043;
|
const size_t OID_VARCHAR = 1043;
|
||||||
|
const size_t OID_TIMESTAMP = 1114;
|
||||||
|
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
@ -85,23 +84,12 @@ public:
|
|||||||
/// Store the pointer to the result set to being fetched. Set row
|
/// Store the pointer to the result set to being fetched. Set row
|
||||||
/// and column counts for convenience.
|
/// and column counts for convenience.
|
||||||
///
|
///
|
||||||
PgSqlResult(PGresult *result) : result_(result), rows_(0), cols_(0) {
|
PgSqlResult(PGresult *result);
|
||||||
if (!result) {
|
|
||||||
isc_throw (BadValue, "PgSqlResult result pointer cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
rows_ = PQntuples(result);
|
|
||||||
cols_ = PQnfields(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Destructor
|
/// @brief Destructor
|
||||||
///
|
///
|
||||||
/// Frees the result set
|
/// Frees the result set
|
||||||
~PgSqlResult() {
|
~PgSqlResult();
|
||||||
if (result_) {
|
|
||||||
PQclear(result_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Returns the number of rows in the result set.
|
/// @brief Returns the number of rows in the result set.
|
||||||
int getRows() const {
|
int getRows() const {
|
||||||
@ -117,35 +105,34 @@ public:
|
|||||||
///
|
///
|
||||||
/// @param row index to range check
|
/// @param row index to range check
|
||||||
///
|
///
|
||||||
/// @throw throws DbOperationError if the row index is out of range
|
/// @throw DbOperationError if the row index is out of range
|
||||||
void rowCheck(int row) const {
|
void rowCheck(int row) const;
|
||||||
if (row >= rows_) {
|
|
||||||
isc_throw (DbOperationError, "row: " << row << ", out of range: 0.." << rows_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Determines if a column index is valid
|
/// @brief Determines if a column index is valid
|
||||||
///
|
///
|
||||||
/// @param col index to range check
|
/// @param col index to range check
|
||||||
///
|
///
|
||||||
/// @throw throws DbOperationError if the column index is out of range
|
/// @throw DbOperationError if the column index is out of range
|
||||||
void colCheck(int col) const {
|
void colCheck(int col) const;
|
||||||
if (col >= cols_) {
|
|
||||||
isc_throw (DbOperationError, "col: " << col << ", out of range: 0.." << cols_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Determines if both a row and column index are valid
|
/// @brief Determines if both a row and column index are valid
|
||||||
///
|
///
|
||||||
/// @param row index to range check
|
/// @param row index to range check
|
||||||
/// @param col index to range check
|
/// @param col index to range check
|
||||||
///
|
///
|
||||||
/// @throw throws DbOperationError if either the row or column index
|
/// @throw DbOperationError if either the row or column index
|
||||||
/// is out of range
|
/// is out of range
|
||||||
void rowColCheck(int row, int col) const {
|
void rowColCheck(int row, int col) const;
|
||||||
rowCheck(row);
|
|
||||||
colCheck(col);
|
/// @brief Fetches the name of the column in a result set
|
||||||
}
|
///
|
||||||
|
/// Returns the column name of the column from the result set.
|
||||||
|
/// If the column index is out of range it will return the
|
||||||
|
/// string "Unknown column:<index>"
|
||||||
|
///
|
||||||
|
/// @param col index of the column name to fetch
|
||||||
|
/// @return string containing the name of the column
|
||||||
|
std::string getColumnLabel(const int col) const;
|
||||||
|
|
||||||
/// @brief Conversion Operator
|
/// @brief Conversion Operator
|
||||||
///
|
///
|
||||||
@ -275,7 +262,7 @@ public:
|
|||||||
/// @brief Commits transaction.
|
/// @brief Commits transaction.
|
||||||
///
|
///
|
||||||
/// Commits all changes made during the transaction by executing the
|
/// Commits all changes made during the transaction by executing the
|
||||||
/// SQL statement: "COMMIT">
|
/// SQL statement: "COMMIT"
|
||||||
///
|
///
|
||||||
/// @throw DbOperationError if statement execution fails
|
/// @throw DbOperationError if statement execution fails
|
||||||
void commit();
|
void commit();
|
||||||
@ -410,8 +397,6 @@ public:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}; // end of isc::dhcp namespace
|
}; // end of isc::dhcp namespace
|
||||||
}; // end of isc namespace
|
}; // end of isc namespace
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ const char* PsqlBindArray::TRUE_STR = "TRUE";
|
|||||||
const char* PsqlBindArray::FALSE_STR = "FALSE";
|
const char* PsqlBindArray::FALSE_STR = "FALSE";
|
||||||
|
|
||||||
void PsqlBindArray::add(const char* value) {
|
void PsqlBindArray::add(const char* value) {
|
||||||
|
if (!value) {
|
||||||
|
isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
|
||||||
|
}
|
||||||
|
|
||||||
values_.push_back(value);
|
values_.push_back(value);
|
||||||
lengths_.push_back(strlen(value));
|
lengths_.push_back(strlen(value));
|
||||||
formats_.push_back(TEXT_FMT);
|
formats_.push_back(TEXT_FMT);
|
||||||
@ -39,6 +43,10 @@ void PsqlBindArray::add(const std::vector<uint8_t>& data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PsqlBindArray::add(const uint8_t* data, const size_t len) {
|
void PsqlBindArray::add(const uint8_t* data, const size_t len) {
|
||||||
|
if (!data) {
|
||||||
|
isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
|
||||||
|
}
|
||||||
|
|
||||||
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
|
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
|
||||||
lengths_.push_back(len);
|
lengths_.push_back(len);
|
||||||
formats_.push_back(BINARY_FMT);
|
formats_.push_back(BINARY_FMT);
|
||||||
@ -70,7 +78,11 @@ void PsqlBindArray::addNull(const int format) {
|
|||||||
formats_.push_back(format);
|
formats_.push_back(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eventually this could replace add(std::string&) ?
|
/// @todo Eventually this could replace add(std::string&)? This would mean
|
||||||
|
/// all bound strings would be internally copies rather than perhaps belonging
|
||||||
|
/// to the originating object such as Host::hostname_. One the one hand it
|
||||||
|
/// would make all strings handled one-way only, on the other hand it would
|
||||||
|
/// mean duplicating strings where it isn't strictly necessary.
|
||||||
void PsqlBindArray::addTempString(const std::string& str) {
|
void PsqlBindArray::addTempString(const std::string& str) {
|
||||||
bound_strs_.push_back(ConstStringPtr(new std::string(str)));
|
bound_strs_.push_back(ConstStringPtr(new std::string(str)));
|
||||||
PsqlBindArray::add((bound_strs_.back())->c_str());
|
PsqlBindArray::add((bound_strs_.back())->c_str());
|
||||||
@ -246,15 +258,7 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
|
|||||||
|
|
||||||
std::string
|
std::string
|
||||||
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
|
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
|
||||||
r.colCheck(column);
|
return (r.getColumnLabel(column));
|
||||||
const char* label = PQfname(r, column);
|
|
||||||
if (!label) {
|
|
||||||
std::ostringstream os;
|
|
||||||
os << "Unknown column:" << column;
|
|
||||||
return (os.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
@ -264,7 +268,7 @@ PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
|
|||||||
int columns = r.getCols();
|
int columns = r.getCols();
|
||||||
for (int col = 0; col < columns; ++col) {
|
for (int col = 0; col < columns; ++col) {
|
||||||
const char* val = getRawColumnValue(r, row, col);
|
const char* val = getRawColumnValue(r, row, col);
|
||||||
std::string name = getColumnLabel(r, col);
|
std::string name = r.getColumnLabel(col);
|
||||||
int format = PQfformat(r, col);
|
int format = PQfformat(r, col);
|
||||||
|
|
||||||
stream << col << " " << name << " : " ;
|
stream << col << " " << name << " : " ;
|
||||||
|
@ -82,6 +82,7 @@ struct PsqlBindArray {
|
|||||||
/// remains in scope until the bind array has been discarded.
|
/// remains in scope until the bind array has been discarded.
|
||||||
///
|
///
|
||||||
/// @param value char array containing the null-terminated text to add.
|
/// @param value char array containing the null-terminated text to add.
|
||||||
|
/// @throw DbOperationError if value is NULL.
|
||||||
void add(const char* value);
|
void add(const char* value);
|
||||||
|
|
||||||
/// @brief Adds an string value to the bind array
|
/// @brief Adds an string value to the bind array
|
||||||
@ -113,6 +114,7 @@ struct PsqlBindArray {
|
|||||||
///
|
///
|
||||||
/// @param data buffer of binary data.
|
/// @param data buffer of binary data.
|
||||||
/// @param len number of bytes of data in buffer
|
/// @param len number of bytes of data in buffer
|
||||||
|
/// @throw DbOperationError if data is NULL.
|
||||||
void add(const uint8_t* data, const size_t len);
|
void add(const uint8_t* data, const size_t len);
|
||||||
|
|
||||||
/// @brief Adds a boolean value to the bind array.
|
/// @brief Adds a boolean value to the bind array.
|
||||||
@ -159,7 +161,7 @@ struct PsqlBindArray {
|
|||||||
/// @brief Binds a the given string to the bind array.
|
/// @brief Binds a the given string to the bind array.
|
||||||
///
|
///
|
||||||
/// Prior to added the The given string the vector of exchange values,
|
/// Prior to added the The given string the vector of exchange values,
|
||||||
/// it duplicated as a ConstStringPtr and saved internally. This garauntees
|
/// it duplicated as a ConstStringPtr and saved internally. This guarantees
|
||||||
/// the string remains in scope until the PsqlBindArray is destroyed,
|
/// the string remains in scope until the PsqlBindArray is destroyed,
|
||||||
/// without the caller maintaining the string values.
|
/// without the caller maintaining the string values.
|
||||||
///
|
///
|
||||||
@ -262,7 +264,15 @@ public:
|
|||||||
static const char* getRawColumnValue(const PgSqlResult& r, const int row,
|
static const char* getRawColumnValue(const PgSqlResult& r, const int row,
|
||||||
const size_t col);
|
const size_t col);
|
||||||
|
|
||||||
/// @todo
|
/// @brief Fetches the name of the column in a result set
|
||||||
|
///
|
||||||
|
/// Returns the column name of the column from the result set.
|
||||||
|
/// If the column index is out of range it will return the
|
||||||
|
/// string "Unknown column:<index>". Note this is NOT from the
|
||||||
|
/// list of columns defined in the exchange.
|
||||||
|
///
|
||||||
|
/// @param col index of the column name to fetch
|
||||||
|
/// @return string containing the name of the column
|
||||||
static std::string getColumnLabel(const PgSqlResult& r, const size_t col);
|
static std::string getColumnLabel(const PgSqlResult& r, const size_t col);
|
||||||
|
|
||||||
/// @brief Fetches text column value as a string
|
/// @brief Fetches text column value as a string
|
||||||
@ -319,6 +329,8 @@ public:
|
|||||||
/// @param r the result set containing the query results
|
/// @param r the result set containing the query results
|
||||||
/// @param row the row number within the result set
|
/// @param row the row number within the result set
|
||||||
/// @param col the column number within the row
|
/// @param col the column number within the row
|
||||||
|
///
|
||||||
|
/// @return True if the column values in the row is NULL, false otherwise.
|
||||||
static bool isColumnNull(const PgSqlResult& r, const int row,
|
static bool isColumnNull(const PgSqlResult& r, const int row,
|
||||||
const size_t col);
|
const size_t col);
|
||||||
|
|
||||||
@ -372,6 +384,8 @@ public:
|
|||||||
///
|
///
|
||||||
/// @param r the result set containing the query results
|
/// @param r the result set containing the query results
|
||||||
/// @param row the row number within the result set
|
/// @param row the row number within the result set
|
||||||
|
///
|
||||||
|
/// @return A string depiction of the row contents.
|
||||||
static std::string dumpRow(const PgSqlResult& r, int row);
|
static std::string dumpRow(const PgSqlResult& r, int row);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -34,6 +34,8 @@ using namespace std;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// @brief Maximum length of option value.
|
/// @brief Maximum length of option value.
|
||||||
|
/// The maximum size of the raw option data that may be read from the
|
||||||
|
/// database.
|
||||||
const size_t OPTION_VALUE_MAX_LEN = 4096;
|
const size_t OPTION_VALUE_MAX_LEN = 4096;
|
||||||
|
|
||||||
/// @brief Numeric value representing last supported identifier.
|
/// @brief Numeric value representing last supported identifier.
|
||||||
@ -106,7 +108,7 @@ public:
|
|||||||
/// @brief Reinitializes state information
|
/// @brief Reinitializes state information
|
||||||
///
|
///
|
||||||
/// This function should be called in between statement executions.
|
/// This function should be called in between statement executions.
|
||||||
/// Deriving classes should inovke this method as well as be reset
|
/// Deriving classes should invoke this method as well as be reset
|
||||||
/// all of their own stateful values.
|
/// all of their own stateful values.
|
||||||
virtual void clear() {
|
virtual void clear() {
|
||||||
host_.reset();
|
host_.reset();
|
||||||
@ -375,13 +377,30 @@ private:
|
|||||||
/// @brief Creates instance of the currently processed option.
|
/// @brief Creates instance of the currently processed option.
|
||||||
///
|
///
|
||||||
/// This method detects if the currently processed option is a new
|
/// This method detects if the currently processed option is a new
|
||||||
/// instance. It makes it determination by comparing the identifier
|
/// instance. It makes its determination by comparing the identifier
|
||||||
/// of the currently processed option, with the most recently processed
|
/// of the currently processed option, with the most recently processed
|
||||||
/// option. If the current value is greater than the id of the recently
|
/// option. If the current value is greater than the id of the recently
|
||||||
/// processed option it is assumed that the processed row holds new
|
/// processed option it is assumed that the processed row holds new
|
||||||
/// option information. In such case the option instance is created and
|
/// option information. In such case the option instance is created and
|
||||||
/// inserted into the configuration passed as argument.
|
/// inserted into the configuration passed as argument.
|
||||||
///
|
///
|
||||||
|
/// This logic is necessary to deal with result sets made from multiple
|
||||||
|
/// left joins which contain duplicated data. For instance queries
|
||||||
|
/// returning both v4 and v6 options for a host would generate result
|
||||||
|
/// sets similar to this:
|
||||||
|
/// @code
|
||||||
|
///
|
||||||
|
/// row 0: host-1 v4-opt-1 v6-opt-1
|
||||||
|
/// row 1: host-1 v4-opt-1 v6-opt-2
|
||||||
|
/// row 2: host-1 v4-opt-1 v6-opt-3
|
||||||
|
/// row 4: host-1 v4-opt-2 v6-opt-1
|
||||||
|
/// row 5: host-1 v4-opt-2 v6-opt-2
|
||||||
|
/// row 6: host-1 v4-opt-2 v6-opt-3
|
||||||
|
/// row 7: host-2 v4-opt-1 v6-opt-1
|
||||||
|
/// row 8: host-2 v4-opt-2 v6-opt-1
|
||||||
|
/// :
|
||||||
|
/// @endcode
|
||||||
|
///
|
||||||
/// @param cfg Pointer to the configuration object into which new
|
/// @param cfg Pointer to the configuration object into which new
|
||||||
/// option instances should be inserted.
|
/// option instances should be inserted.
|
||||||
/// @param r result set containing one or more rows from a dhcp
|
/// @param r result set containing one or more rows from a dhcp
|
||||||
@ -421,8 +440,6 @@ private:
|
|||||||
sizeof(value), value_len);
|
sizeof(value), value_len);
|
||||||
|
|
||||||
// formatted_value: TEXT
|
// formatted_value: TEXT
|
||||||
// @todo Should we attempt to enforce max value of 8K?
|
|
||||||
// If so, we should declare this VARCHAR[8K] in the table
|
|
||||||
std::string formatted_value;
|
std::string formatted_value;
|
||||||
PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
|
PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
|
||||||
formatted_value);
|
formatted_value);
|
||||||
@ -596,7 +613,7 @@ public:
|
|||||||
/// @brief Clears state information
|
/// @brief Clears state information
|
||||||
///
|
///
|
||||||
/// This function should be called in between statement executions.
|
/// This function should be called in between statement executions.
|
||||||
/// Deriving classes should inovke this method as well as be reset
|
/// Deriving classes should invoke this method as well as be reset
|
||||||
/// all of their own stateful values.
|
/// all of their own stateful values.
|
||||||
virtual void clear() {
|
virtual void clear() {
|
||||||
PgSqlHostExchange::clear();
|
PgSqlHostExchange::clear();
|
||||||
@ -611,10 +628,11 @@ public:
|
|||||||
|
|
||||||
/// @brief Processes the current row.
|
/// @brief Processes the current row.
|
||||||
///
|
///
|
||||||
/// The processed row includes both host information and DHCP option
|
/// The fetched row includes both host information and DHCP option
|
||||||
/// information. Because used SELECT query use LEFT JOIN clause, the
|
/// information. Because the SELECT queries use one or more LEFT JOIN
|
||||||
/// some rows contain duplicated host or options entries. This method
|
/// clauses, the result set may contain duplicated host or options
|
||||||
/// detects duplicated information and discards such entries.
|
/// entries. This method detects duplicated information and discards such
|
||||||
|
/// entries.
|
||||||
///
|
///
|
||||||
/// @param [out] hosts Container holding parsed hosts and options.
|
/// @param [out] hosts Container holding parsed hosts and options.
|
||||||
virtual void processRowData(ConstHostCollection& hosts,
|
virtual void processRowData(ConstHostCollection& hosts,
|
||||||
@ -685,7 +703,7 @@ private:
|
|||||||
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
|
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
|
||||||
///
|
///
|
||||||
/// This class extends the @ref PgSqlHostWithOptionsExchange class with the
|
/// This class extends the @ref PgSqlHostWithOptionsExchange class with the
|
||||||
/// mechanisms to retrieve IPv6 reservations. This class is used in sitations
|
/// mechanisms to retrieve IPv6 reservations. This class is used in situations
|
||||||
/// when it is desired to retrieve DHCPv6 specific information about the host
|
/// when it is desired to retrieve DHCPv6 specific information about the host
|
||||||
/// (DHCPv6 options and reservations), or entire information about the host
|
/// (DHCPv6 options and reservations), or entire information about the host
|
||||||
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
|
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
|
||||||
@ -727,7 +745,7 @@ public:
|
|||||||
/// @brief Reinitializes state information
|
/// @brief Reinitializes state information
|
||||||
///
|
///
|
||||||
/// This function should be called in between statement executions.
|
/// This function should be called in between statement executions.
|
||||||
/// Deriving classes should inovke this method as well as be reset
|
/// Deriving classes should invoke this method as well as be reset
|
||||||
/// all of their own stateful values.
|
/// all of their own stateful values.
|
||||||
void clear() {
|
void clear() {
|
||||||
PgSqlHostWithOptionsExchange::clear();
|
PgSqlHostWithOptionsExchange::clear();
|
||||||
@ -921,7 +939,7 @@ public:
|
|||||||
bind_array->add(resv.getPrefixLen());
|
bind_array->add(resv.getPrefixLen());
|
||||||
|
|
||||||
// type: SMALLINT NOT NULL
|
// type: SMALLINT NOT NULL
|
||||||
// See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
|
// See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
|
||||||
uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
|
uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
|
||||||
bind_array->add(type);
|
bind_array->add(type);
|
||||||
|
|
||||||
@ -1122,7 +1140,7 @@ public:
|
|||||||
/// of a single row with one column, the value of the primary key.
|
/// of a single row with one column, the value of the primary key.
|
||||||
/// Defaults to false.
|
/// Defaults to false.
|
||||||
///
|
///
|
||||||
/// @returns 0 if return_last_id is false, otherwise it returns the
|
/// @return 0 if return_last_id is false, otherwise it returns the
|
||||||
/// the value in the result set in the first col of the first row.
|
/// the value in the result set in the first col of the first row.
|
||||||
///
|
///
|
||||||
/// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
|
/// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
|
||||||
@ -1245,8 +1263,9 @@ public:
|
|||||||
/// @brief Prepared MySQL statements used by the backend to insert and
|
/// @brief Prepared MySQL statements used by the backend to insert and
|
||||||
/// retrieve hosts from the database.
|
/// retrieve hosts from the database.
|
||||||
PgSqlTaggedStatement tagged_statements[] = {
|
PgSqlTaggedStatement tagged_statements[] = {
|
||||||
|
// PgSqlHostDataSourceImpl::INSERT_HOST
|
||||||
// Inserts a host into the 'hosts' table. Returns the inserted host id.
|
// Inserts a host into the 'hosts' table. Returns the inserted host id.
|
||||||
{8, // PgSqlHostDataSourceImpl::INSERT_HOST,
|
{8,
|
||||||
{ OID_BYTEA, OID_INT2,
|
{ OID_BYTEA, OID_INT2,
|
||||||
OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
|
OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
|
||||||
OID_VARCHAR, OID_VARCHAR },
|
OID_VARCHAR, OID_VARCHAR },
|
||||||
@ -1257,8 +1276,9 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
|
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//PgSqlHostDataSourceImpl::INSERT_V6_RESRV
|
||||||
// Inserts a single IPv6 reservation into 'reservations' table.
|
// Inserts a single IPv6 reservation into 'reservations' table.
|
||||||
{5, //PgSqlHostDataSourceImpl::INSERT_V6_RESRV,
|
{5,
|
||||||
{ OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
|
{ OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
|
||||||
"insert_v6_resrv",
|
"insert_v6_resrv",
|
||||||
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
|
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
|
||||||
@ -1266,9 +1286,10 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"VALUES ($1, $2, $3, $4, $5)"
|
"VALUES ($1, $2, $3, $4, $5)"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
|
||||||
// Inserts a single DHCPv4 option into 'dhcp4_options' table.
|
// Inserts a single DHCPv4 option into 'dhcp4_options' table.
|
||||||
// Using fixed scope_id = 3, which associates an option with host.
|
// Using fixed scope_id = 3, which associates an option with host.
|
||||||
{6, // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
|
{6,
|
||||||
{ OID_INT2, OID_BYTEA, OID_TEXT,
|
{ OID_INT2, OID_BYTEA, OID_TEXT,
|
||||||
OID_VARCHAR, OID_BOOL, OID_INT8},
|
OID_VARCHAR, OID_BOOL, OID_INT8},
|
||||||
"insert_v4_host_option",
|
"insert_v4_host_option",
|
||||||
@ -1277,9 +1298,10 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"VALUES ($1, $2, $3, $4, $5, $6, 3)"
|
"VALUES ($1, $2, $3, $4, $5, $6, 3)"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
|
||||||
// Inserts a single DHCPv6 option into 'dhcp6_options' table.
|
// Inserts a single DHCPv6 option into 'dhcp6_options' table.
|
||||||
// Using fixed scope_id = 3, which associates an option with host.
|
// Using fixed scope_id = 3, which associates an option with host.
|
||||||
{6, // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
|
{6,
|
||||||
{ OID_INT2, OID_BYTEA, OID_TEXT,
|
{ OID_INT2, OID_BYTEA, OID_TEXT,
|
||||||
OID_VARCHAR, OID_BOOL, OID_INT8},
|
OID_VARCHAR, OID_BOOL, OID_INT8},
|
||||||
"insert_v6_host_option",
|
"insert_v6_host_option",
|
||||||
@ -1288,11 +1310,12 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"VALUES ($1, $2, $3, $4, $5, $6, 3)"
|
"VALUES ($1, $2, $3, $4, $5, $6, 3)"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PgSqlHostDataSourceImpl::GET_HOST_DHCPID
|
||||||
// Retrieves host information, IPv6 reservations and both DHCPv4 and
|
// Retrieves host information, IPv6 reservations and both DHCPv4 and
|
||||||
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
|
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
|
||||||
// to retrieve information from 4 different tables using a single query.
|
// to retrieve information from 4 different tables using a single query.
|
||||||
// Hence, this query returns multiple rows for a single host.
|
// Hence, this query returns multiple rows for a single host.
|
||||||
{2, // PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
|
{2,
|
||||||
{ OID_BYTEA, OID_INT2 },
|
{ OID_BYTEA, OID_INT2 },
|
||||||
"get_host_dhcpid",
|
"get_host_dhcpid",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
||||||
@ -1311,10 +1334,11 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
|
"ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PgSqlHostDataSourceImpl::GET_HOST_ADDR
|
||||||
// Retrieves host information along with the DHCPv4 options associated with
|
// Retrieves host information along with the DHCPv4 options associated with
|
||||||
// it. Left joining the dhcp4_options table results in multiple rows being
|
// it. Left joining the dhcp4_options table results in multiple rows being
|
||||||
// returned for the same host. The host is retrieved by IPv4 address.
|
// returned for the same host. The host is retrieved by IPv4 address.
|
||||||
{ 1, // PgSqlHostDataSourceImpl::GET_HOST_ADDR,
|
{1,
|
||||||
{ OID_INT8 }, "get_host_addr",
|
{ OID_INT8 }, "get_host_addr",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
||||||
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
|
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
|
||||||
@ -1326,10 +1350,11 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o.option_id"
|
"ORDER BY h.host_id, o.option_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
|
||||||
// Retrieves host information and DHCPv4 options using subnet identifier
|
// Retrieves host information and DHCPv4 options using subnet identifier
|
||||||
// and client's identifier. Left joining the dhcp4_options table results in
|
// and client's identifier. Left joining the dhcp4_options table results in
|
||||||
// multiple rows being returned for the same host.
|
// multiple rows being returned for the same host.
|
||||||
{ 3, //PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
|
{3,
|
||||||
{ OID_INT4, OID_INT2, OID_BYTEA },
|
{ OID_INT4, OID_INT2, OID_BYTEA },
|
||||||
"get_host_subid4_dhcpid",
|
"get_host_subid4_dhcpid",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
||||||
@ -1343,10 +1368,11 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o.option_id"
|
"ORDER BY h.host_id, o.option_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
|
||||||
// Retrieves host information, IPv6 reservations and DHCPv6 options
|
// Retrieves host information, IPv6 reservations and DHCPv6 options
|
||||||
// associated with a host. The number of rows returned is a multiplication
|
// associated with a host. The number of rows returned is a multiplication
|
||||||
// of number of IPv6 reservations and DHCPv6 options.
|
// of number of IPv6 reservations and DHCPv6 options.
|
||||||
{3, //PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
|
{3,
|
||||||
{ OID_INT4, OID_INT2, OID_BYTEA },
|
{ OID_INT4, OID_INT2, OID_BYTEA },
|
||||||
"get_host_subid6_dhcpid",
|
"get_host_subid6_dhcpid",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, "
|
"SELECT h.host_id, h.dhcp_identifier, "
|
||||||
@ -1364,11 +1390,12 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o.option_id, r.reservation_id"
|
"ORDER BY h.host_id, o.option_id, r.reservation_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
|
||||||
// Retrieves host information and DHCPv4 options for the host using subnet
|
// Retrieves host information and DHCPv4 options for the host using subnet
|
||||||
// identifier and IPv4 reservation. Left joining the dhcp4_options table
|
// identifier and IPv4 reservation. Left joining the dhcp4_options table
|
||||||
// results in multiple rows being returned for the host. The number of
|
// results in multiple rows being returned for the host. The number of
|
||||||
// rows depends on the number of options defined for the host.
|
// rows depends on the number of options defined for the host.
|
||||||
{ 2, //PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
|
{2,
|
||||||
{ OID_INT4, OID_INT8 },
|
{ OID_INT4, OID_INT8 },
|
||||||
"get_host_subid_addr",
|
"get_host_subid_addr",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
|
||||||
@ -1381,13 +1408,14 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o.option_id"
|
"ORDER BY h.host_id, o.option_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// PgSqlHostDataSourceImpl::GET_HOST_PREFIX
|
||||||
// Retrieves host information, IPv6 reservations and DHCPv6 options
|
// Retrieves host information, IPv6 reservations and DHCPv6 options
|
||||||
// associated with a host using prefix and prefix length. This query
|
// associated with a host using prefix and prefix length. This query
|
||||||
// returns host information for a single host. However, multiple rows
|
// returns host information for a single host. However, multiple rows
|
||||||
// are returned due to left joining IPv6 reservations and DHCPv6 options.
|
// are returned due to left joining IPv6 reservations and DHCPv6 options.
|
||||||
// The number of rows returned is multiplication of number of existing
|
// The number of rows returned is multiplication of number of existing
|
||||||
// IPv6 reservations and DHCPv6 options.
|
// IPv6 reservations and DHCPv6 options.
|
||||||
{2, // PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
|
{2,
|
||||||
{ OID_VARCHAR, OID_INT2 },
|
{ OID_VARCHAR, OID_INT2 },
|
||||||
"get_host_prefix",
|
"get_host_prefix",
|
||||||
"SELECT h.host_id, h.dhcp_identifier, "
|
"SELECT h.host_id, h.dhcp_identifier, "
|
||||||
@ -1407,8 +1435,9 @@ PgSqlTaggedStatement tagged_statements[] = {
|
|||||||
"ORDER BY h.host_id, o.option_id, r.reservation_id"
|
"ORDER BY h.host_id, o.option_id, r.reservation_id"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//PgSqlHostDataSourceImpl::GET_VERSION
|
||||||
// Retrieves MySQL schema version.
|
// Retrieves MySQL schema version.
|
||||||
{ 0, //PgSqlHostDataSourceImpl::GET_VERSION,
|
{0,
|
||||||
{ OID_NONE },
|
{ OID_NONE },
|
||||||
"get_version",
|
"get_version",
|
||||||
"SELECT version, minor FROM schema_version"
|
"SELECT version, minor FROM schema_version"
|
||||||
@ -1460,13 +1489,13 @@ PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex,
|
|||||||
int s = PQresultStatus(r);
|
int s = PQresultStatus(r);
|
||||||
|
|
||||||
if (s != PGRES_COMMAND_OK) {
|
if (s != PGRES_COMMAND_OK) {
|
||||||
// Failure: check for the special case of duplicate entry. If this is
|
// Failure: check for the special case of duplicate entry.
|
||||||
// the case, we return false to indicate that the row was not added.
|
|
||||||
// Otherwise we throw an exception.
|
|
||||||
if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
|
if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
|
||||||
isc_throw(DuplicateEntry, "Database duplicate entry error");
|
isc_throw(DuplicateEntry, "Database duplicate entry error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connection determines if the error is fatal or not, and
|
||||||
|
// throws the appropriate exception
|
||||||
conn_.checkStatementError(r, tagged_statements[stindex]);
|
conn_.checkStatementError(r, tagged_statements[stindex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1748,6 +1777,11 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id,
|
|||||||
ConstHostPtr
|
ConstHostPtr
|
||||||
PgSqlHostDataSource::get4(const SubnetID& subnet_id,
|
PgSqlHostDataSource::get4(const SubnetID& subnet_id,
|
||||||
const asiolink::IOAddress& address) const {
|
const asiolink::IOAddress& address) const {
|
||||||
|
if (!address.isV4()) {
|
||||||
|
isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
|
||||||
|
" wrong address type, address supplied is an IPv6 address");
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the WHERE clause value
|
// Set up the WHERE clause value
|
||||||
PsqlBindArrayPtr bind_array(new PsqlBindArray());
|
PsqlBindArrayPtr bind_array(new PsqlBindArray());
|
||||||
|
|
||||||
|
@ -22,6 +22,16 @@ class PgSqlHostDataSourceImpl;
|
|||||||
/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
|
/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
|
||||||
/// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
|
/// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
|
||||||
/// database is available and that the Kea schema has been created within it.
|
/// database is available and that the Kea schema has been created within it.
|
||||||
|
///
|
||||||
|
/// Reservations are uniquely identified by identifier type and value. Currently
|
||||||
|
/// The currently supported values are defined in @ref Host::IdentifierType
|
||||||
|
/// as well as in host_identifier_table:
|
||||||
|
///
|
||||||
|
/// - IDENT_HWADDR
|
||||||
|
/// - IDENT_DUID
|
||||||
|
/// - IDENT_CIRCUIT_ID
|
||||||
|
/// - IDENT_CLIENT_ID
|
||||||
|
///
|
||||||
class PgSqlHostDataSource: public BaseHostDataSource {
|
class PgSqlHostDataSource: public BaseHostDataSource {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -50,6 +60,8 @@ public:
|
|||||||
PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
|
PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
|
||||||
|
|
||||||
/// @brief Virtual destructor.
|
/// @brief Virtual destructor.
|
||||||
|
/// Frees database resources and closes the database connection through
|
||||||
|
/// the destruction of member impl_.
|
||||||
virtual ~PgSqlHostDataSource();
|
virtual ~PgSqlHostDataSource();
|
||||||
|
|
||||||
/// @brief Return all hosts for the specified HW address or DUID.
|
/// @brief Return all hosts for the specified HW address or DUID.
|
||||||
@ -145,13 +157,11 @@ public:
|
|||||||
/// if this address is not reserved for some other host and do not allocate
|
/// if this address is not reserved for some other host and do not allocate
|
||||||
/// this address if reservation is present.
|
/// this address if reservation is present.
|
||||||
///
|
///
|
||||||
/// Implementations of this method should guard against invalid addresses,
|
|
||||||
/// such as IPv6 address.
|
|
||||||
///
|
|
||||||
/// @param subnet_id Subnet identifier.
|
/// @param subnet_id Subnet identifier.
|
||||||
/// @param address reserved IPv4 address.
|
/// @param address reserved IPv4 address.
|
||||||
///
|
///
|
||||||
/// @return Const @c Host object using a specified IPv4 address.
|
/// @return Const @c Host object using a specified IPv4 address.
|
||||||
|
/// @throw BadValue is given an IPv6 address
|
||||||
virtual ConstHostPtr
|
virtual ConstHostPtr
|
||||||
get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
|
get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
|
||||||
|
|
||||||
@ -198,30 +208,51 @@ public:
|
|||||||
|
|
||||||
/// @brief Adds a new host to the collection.
|
/// @brief Adds a new host to the collection.
|
||||||
///
|
///
|
||||||
/// The implementations of this method should guard against duplicate
|
/// The method will insert the given host and all of its children (v4
|
||||||
/// reservations for the same host, where possible. For example, when the
|
/// options, v6 options, and v6 reservations) into the database. It
|
||||||
/// reservation for the same HW address and subnet id is added twice, the
|
/// relies on constraints defined as part of the PostgreSQL schema to
|
||||||
/// addHost method should throw an DuplicateEntry exception. Note, that
|
/// defend against duplicate entries and to ensure referential
|
||||||
/// usually it is impossible to guard against adding duplicated host, where
|
/// integrity.
|
||||||
/// one instance is identified by HW address, another one by DUID.
|
///
|
||||||
|
/// Violation of any of these constraints for a host will result in a
|
||||||
|
/// DuplicateEntry exception:
|
||||||
|
///
|
||||||
|
/// -# IPV4_ADDRESS and DHCP4_SUBNET_ID combination must be unique
|
||||||
|
/// -# DHCP ID, DHCP ID TYPE, and DHCP4_SUBNET_ID combination must be unique
|
||||||
|
/// -# DHCP ID, DHCP ID TYPE, and DHCP6_SUBNET_ID combination must be unique
|
||||||
|
///
|
||||||
|
/// In addition, violating the following referential contraints will
|
||||||
|
/// a DbOperationError exception:
|
||||||
|
///
|
||||||
|
/// -# DHCP ID TYPE must be defined in the HOST_IDENTIFIER_TYPE table
|
||||||
|
/// -# For DHCP4 Options:
|
||||||
|
/// -# HOST_ID must exist with HOSTS
|
||||||
|
/// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
|
||||||
|
/// -# For DHCP6 Options:
|
||||||
|
/// -# HOST_ID must exist with HOSTS
|
||||||
|
/// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
|
||||||
|
/// -# For IPV6 Reservations:
|
||||||
|
/// -# HOST_ID must exist with HOSTS
|
||||||
|
/// -# Address and Prefix Length must be unique (DuplicateEntry)
|
||||||
///
|
///
|
||||||
/// @param host Pointer to the new @c Host object being added.
|
/// @param host Pointer to the new @c Host object being added.
|
||||||
|
/// @throw DuplicateEntry or DbOperationError dependent on the constraint
|
||||||
|
/// violation
|
||||||
virtual void add(const HostPtr& host);
|
virtual void add(const HostPtr& host);
|
||||||
|
|
||||||
/// @brief Return backend type
|
/// @brief Return backend type
|
||||||
///
|
///
|
||||||
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
|
/// Returns the type of database as the string "postgresql". This is
|
||||||
|
/// same value as used for configuration purposes.
|
||||||
///
|
///
|
||||||
/// @return Type of the backend.
|
/// @return Type of the backend.
|
||||||
virtual std::string getType() const {
|
virtual std::string getType() const {
|
||||||
return (std::string("postgresql"));
|
return (std::string("postgresql"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Returns backend name.
|
/// @brief Returns the name of the open database
|
||||||
///
|
///
|
||||||
/// Each backend have specific name.
|
/// @return String containing the name of the database
|
||||||
///
|
|
||||||
/// @return "mysql".
|
|
||||||
virtual std::string getName() const;
|
virtual std::string getName() const;
|
||||||
|
|
||||||
/// @brief Returns description of the backend.
|
/// @brief Returns description of the backend.
|
||||||
|
@ -26,7 +26,8 @@ using namespace std;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source columns
|
/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source
|
||||||
|
/// columns. This is coverd by tickets #3557, #4530, and PR#9.
|
||||||
|
|
||||||
/// @brief Catalog of all the SQL statements currently supported. Note
|
/// @brief Catalog of all the SQL statements currently supported. Note
|
||||||
/// that the order columns appear in statement body must match the order they
|
/// that the order columns appear in statement body must match the order they
|
||||||
|
@ -6,69 +6,21 @@
|
|||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <dhcpsrv/pgsql_connection.h>
|
||||||
#include <dhcpsrv/pgsql_exchange.h>
|
#include <dhcpsrv/pgsql_exchange.h>
|
||||||
|
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace isc;
|
using namespace isc;
|
||||||
using namespace isc::dhcp;
|
using namespace isc::dhcp;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// @brief Converts a time_t into a string matching our Postgres input format
|
|
||||||
///
|
|
||||||
/// @param time_val Time value to convert
|
|
||||||
/// @retrun A string containing the converted time
|
|
||||||
std::string timeToDbString(const time_t time_val) {
|
|
||||||
struct tm tinfo;
|
|
||||||
char buffer[20];
|
|
||||||
|
|
||||||
localtime_r(&time_val, &tinfo);
|
|
||||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
|
|
||||||
return(std::string(buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Basic checks on time conversion functions in PgSqlExchange
|
|
||||||
/// We input timestamps as date/time strings and we output them as
|
|
||||||
/// an integer string of seconds since the epoch. There is no meangingful
|
|
||||||
/// way to test them round-trip without Postgres involved.
|
|
||||||
TEST(PgSqlExchangeTest, convertTimeTest) {
|
|
||||||
// Get a reference time and time string
|
|
||||||
time_t ref_time;
|
|
||||||
time(&ref_time);
|
|
||||||
|
|
||||||
std::string ref_time_str(timeToDbString(ref_time));
|
|
||||||
|
|
||||||
// Verify convertToDatabaseTime gives us the expected localtime string
|
|
||||||
std::string time_str = PgSqlExchange::convertToDatabaseTime(ref_time);
|
|
||||||
EXPECT_EQ(time_str, ref_time_str);
|
|
||||||
|
|
||||||
// Verify convertToDatabaseTime with valid_lifetime = 0 gives us the
|
|
||||||
// expected localtime string
|
|
||||||
time_str = PgSqlExchange::convertToDatabaseTime(ref_time, 0);
|
|
||||||
EXPECT_EQ(time_str, ref_time_str);
|
|
||||||
|
|
||||||
// Verify we can add time by adding a day.
|
|
||||||
ref_time_str = timeToDbString(ref_time + (24*3600));
|
|
||||||
ASSERT_NO_THROW(time_str = PgSqlExchange::convertToDatabaseTime(ref_time,
|
|
||||||
24*3600));
|
|
||||||
EXPECT_EQ(time_str, ref_time_str);
|
|
||||||
|
|
||||||
// Verify too large of a value is detected.
|
|
||||||
ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(DatabaseConnection::
|
|
||||||
MAX_DB_TIME - 1,
|
|
||||||
24*3600),
|
|
||||||
isc::BadValue);
|
|
||||||
|
|
||||||
// Make sure Conversion "from" database time functions
|
|
||||||
std::string ref_secs_str = boost::lexical_cast<std::string>(ref_time);
|
|
||||||
time_t from_time = PgSqlExchange::convertFromDatabaseTime(ref_secs_str);
|
|
||||||
from_time = PgSqlExchange::convertFromDatabaseTime(ref_secs_str);
|
|
||||||
EXPECT_EQ(ref_time, from_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Verifies the ability to add various data types to
|
/// @brief Verifies the ability to add various data types to
|
||||||
/// the bind array.
|
/// the bind array.
|
||||||
TEST(PsqlBindArray, addDataTest) {
|
TEST(PsqlBindArray, addDataTest) {
|
||||||
@ -138,5 +90,840 @@ TEST(PsqlBindArray, addDataTest) {
|
|||||||
EXPECT_EQ(expected, b.toText());
|
EXPECT_EQ(expected, b.toText());
|
||||||
}
|
}
|
||||||
|
|
||||||
}; // namespace
|
/// @brief Defines a pointer to a PgSqlConnection
|
||||||
|
typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
|
||||||
|
/// @brief Defines a pointer to a PgSqlResult
|
||||||
|
typedef boost::shared_ptr<PgSqlResult> PgSqlResultPtr;
|
||||||
|
|
||||||
|
/// @brief Fixture for exercising basic PostgreSQL operations and data types
|
||||||
|
///
|
||||||
|
/// This class is intended to be used to verify basic operations and to
|
||||||
|
/// verify that each PostgreSQL data type currently used by Kea, can be
|
||||||
|
/// correctly written to and read from PostgreSQL. Rather than use tables
|
||||||
|
/// that belong to Kea the schema proper, it creates its own. Currently it
|
||||||
|
/// consists of a single table, called "basics" which contains one column for
|
||||||
|
/// each of the supported data types.
|
||||||
|
///
|
||||||
|
/// It creates the schema during construction, deletes it upon destruction, and
|
||||||
|
/// provides functions for executing SQL statements, executing prepared
|
||||||
|
/// statements, fetching all rows in the table, and deleting all the rows in
|
||||||
|
/// the table.
|
||||||
|
class PgSqlBasicsTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
/// @brief Column index for each column
|
||||||
|
enum BasicColIndex {
|
||||||
|
ID_COL,
|
||||||
|
BOOL_COL,
|
||||||
|
BYTEA_COL,
|
||||||
|
BIGINT_COL,
|
||||||
|
SMALLINT_COL,
|
||||||
|
INT_COL,
|
||||||
|
TEXT_COL,
|
||||||
|
TIMESTAMP_COL,
|
||||||
|
VARCHAR_COL,
|
||||||
|
NUM_BASIC_COLS
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Constructor
|
||||||
|
///
|
||||||
|
/// Creates the database connection, opens the database, and destroys
|
||||||
|
/// the table (if present) and then recreates it.
|
||||||
|
PgSqlBasicsTest() : expectedColNames_(NUM_BASIC_COLS) {
|
||||||
|
// Create database connection parameter list
|
||||||
|
PgSqlConnection::ParameterMap params;
|
||||||
|
params["name"] = "keatest";
|
||||||
|
params["user"] = "keatest";
|
||||||
|
params["password"] = "keatest";
|
||||||
|
|
||||||
|
// Create and open the database connection
|
||||||
|
conn_.reset(new PgSqlConnection(params));
|
||||||
|
conn_->openDatabase();
|
||||||
|
|
||||||
|
// Create the list of expected column names
|
||||||
|
expectedColNames_[ID_COL] = "id";
|
||||||
|
expectedColNames_[BOOL_COL] = "bool_col";
|
||||||
|
expectedColNames_[BYTEA_COL] = "bytea_col";
|
||||||
|
expectedColNames_[BIGINT_COL] = "bigint_col";
|
||||||
|
expectedColNames_[SMALLINT_COL] = "smallint_col";
|
||||||
|
expectedColNames_[INT_COL] = "int_col";
|
||||||
|
expectedColNames_[TEXT_COL] = "text_col";
|
||||||
|
expectedColNames_[TIMESTAMP_COL] = "timestamp_col";
|
||||||
|
expectedColNames_[VARCHAR_COL] = "varchar_col";
|
||||||
|
|
||||||
|
destroySchema();
|
||||||
|
createSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Destructor
|
||||||
|
///
|
||||||
|
/// Destroys the table. The database resources are freed and the connection
|
||||||
|
/// closed by the destruction of conn_.
|
||||||
|
virtual ~PgSqlBasicsTest () {
|
||||||
|
destroySchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Gets the expected name of the column for a given column index
|
||||||
|
///
|
||||||
|
/// Returns the name of column as we expect it to be when the column is
|
||||||
|
/// fetched from the database.
|
||||||
|
///
|
||||||
|
/// @param col index of the desired column
|
||||||
|
///
|
||||||
|
/// @return string containing the column name
|
||||||
|
///
|
||||||
|
/// @throw BadValue if the index is out of range
|
||||||
|
const std::string& expectedColumnName(int col) {
|
||||||
|
if (col < 0 || col >= NUM_BASIC_COLS) {
|
||||||
|
isc_throw(BadValue,
|
||||||
|
"definedColunName: invalid column value" << col);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (expectedColNames_[col]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Creates the basics table
|
||||||
|
/// Asserts if the creation step fails
|
||||||
|
void createSchema() {
|
||||||
|
// One column for OID type, plus an auto-increment
|
||||||
|
const char* sql =
|
||||||
|
"CREATE TABLE basics ( "
|
||||||
|
" id SERIAL PRIMARY KEY NOT NULL, "
|
||||||
|
" bool_col BOOLEAN, "
|
||||||
|
" bytea_col BYTEA, "
|
||||||
|
" bigint_col BIGINT, "
|
||||||
|
" smallint_col SMALLINT, "
|
||||||
|
" int_col INT, "
|
||||||
|
" text_col TEXT, "
|
||||||
|
" timestamp_col TIMESTAMP WITH TIME ZONE, "
|
||||||
|
" varchar_col VARCHAR(255) "
|
||||||
|
"); ";
|
||||||
|
|
||||||
|
PgSqlResult r(PQexec(*conn_, sql));
|
||||||
|
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
|
||||||
|
<< " create basics table failed: " << PQerrorMessage(*conn_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Destroys the basics table
|
||||||
|
/// Asserts if the destruction fails
|
||||||
|
void destroySchema() {
|
||||||
|
if (conn_) {
|
||||||
|
PgSqlResult r(PQexec(*conn_, "DROP TABLE IF EXISTS basics;"));
|
||||||
|
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
|
||||||
|
<< " drop basics table failed: " << PQerrorMessage(*conn_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Executes a SQL statement and tests for an expected outcome
|
||||||
|
///
|
||||||
|
/// @param r pointer which will contain the result set returned by the
|
||||||
|
/// statment's execution.
|
||||||
|
/// @param sql string containing the SQL statement text. Note that
|
||||||
|
/// PostgreSQL supports executing text which contains more than one SQL
|
||||||
|
/// statement separated by semicolons.
|
||||||
|
/// @param exp_outcome expected status value returned with within the
|
||||||
|
/// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
|
||||||
|
/// @lineno line number from where the call was invoked
|
||||||
|
///
|
||||||
|
/// Asserts if the result set status does not equal the expected outcome.
|
||||||
|
void runSql(PgSqlResultPtr& r, const std::string sql, int exp_outcome,
|
||||||
|
int lineno) {
|
||||||
|
r.reset(new PgSqlResult(PQexec(*conn_, sql.c_str())));
|
||||||
|
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
|
||||||
|
<< " runSql at line: " << lineno << " failed, sql:[" << sql
|
||||||
|
<< "]\n reason: " << PQerrorMessage(*conn_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Executes a SQL statement and tests for an expected outcome
|
||||||
|
///
|
||||||
|
/// @param r pointer which will contain the result set returned by the
|
||||||
|
/// statment's execution.
|
||||||
|
/// @param statement statement descriptor of the prepared statement
|
||||||
|
/// to execute.
|
||||||
|
/// @param bind_array bind array containing the input values to submit
|
||||||
|
/// along with the statement
|
||||||
|
/// @param exp_outcome expected status value returned with within the
|
||||||
|
/// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
|
||||||
|
/// @lineno line number from where the call was invoked
|
||||||
|
///
|
||||||
|
/// Asserts if the result set status does not equal the expected outcome.
|
||||||
|
void runPreparedStatement(PgSqlResultPtr& r,
|
||||||
|
PgSqlTaggedStatement& statement,
|
||||||
|
PsqlBindArrayPtr bind_array, int exp_outcome,
|
||||||
|
int lineno) {
|
||||||
|
r.reset(new PgSqlResult(PQexecPrepared(*conn_, statement.name,
|
||||||
|
statement.nbparams,
|
||||||
|
&bind_array->values_[0],
|
||||||
|
&bind_array->lengths_[0],
|
||||||
|
&bind_array->formats_[0], 0)));
|
||||||
|
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
|
||||||
|
<< " runPreparedStatement at line: " << lineno
|
||||||
|
<< " statement name:[" << statement.name
|
||||||
|
<< "]\n reason: " << PQerrorMessage(*conn_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Fetches all of the rows currently in the table
|
||||||
|
///
|
||||||
|
/// Executes a select statement which returns all of the rows in the
|
||||||
|
/// basics table, in their order of insertion. Each row contains all
|
||||||
|
/// of the defined columns, in the order they are defined.
|
||||||
|
///
|
||||||
|
/// @param r pointer which will contain the result set returned by the
|
||||||
|
/// statment's execution.
|
||||||
|
/// @param exp_rows expected number of rows fetched. (This can be 0).
|
||||||
|
/// @lineno line number from where the call was invoked
|
||||||
|
///
|
||||||
|
/// Asserts if the result set status does not equal the expected outcome.
|
||||||
|
void fetchRows(PgSqlResultPtr& r, int exp_rows, int line) {
|
||||||
|
std::string sql =
|
||||||
|
"SELECT"
|
||||||
|
" id, bool_col, bytea_col, bigint_col, smallint_col, "
|
||||||
|
" int_col, text_col,"
|
||||||
|
" extract(epoch from timestamp_col)::bigint as timestamp_col,"
|
||||||
|
" varchar_col FROM basics";
|
||||||
|
|
||||||
|
runSql(r, sql, PGRES_TUPLES_OK, line);
|
||||||
|
ASSERT_EQ(r->getRows(), exp_rows) << "fetch at line: " << line
|
||||||
|
<< " wrong row count, expected: " << exp_rows
|
||||||
|
<< " , have: " << r->getRows();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Database connection
|
||||||
|
PgSqlConnectionPtr conn_;
|
||||||
|
|
||||||
|
/// @brief List of column names as we expect them to be in fetched rows
|
||||||
|
std::vector<std::string> expectedColNames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Macros defined to ease passing invocation line number for output tracing
|
||||||
|
// (Yes I could have used scoped tracing but that's so ugly in code...)
|
||||||
|
#define RUN_SQL(a,b,c) (runSql(a,b,c, __LINE__))
|
||||||
|
#define RUN_PREP(a,b,c,d) (runPreparedStatement(a,b,c,d, __LINE__))
|
||||||
|
#define FETCH_ROWS(a,b) (fetchRows(a,b,__LINE__))
|
||||||
|
#define WIPE_ROWS(a) (RUN_SQL(a, "DELETE FROM BASICS", PGRES_COMMAND_OK))
|
||||||
|
|
||||||
|
/// @brief Verifies that PgResultSet row and colum meta-data is correct
|
||||||
|
TEST_F(PgSqlBasicsTest, rowColumnBasics) {
|
||||||
|
// We fetch the table contents, which at this point should be no rows.
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
FETCH_ROWS(r, 0);
|
||||||
|
|
||||||
|
// Column meta-data is deteremined by the select statement and is
|
||||||
|
// present whether or not any rows were returned.
|
||||||
|
EXPECT_EQ(r->getCols(), NUM_BASIC_COLS);
|
||||||
|
|
||||||
|
// Negative indexes should be out of range. We test negative values
|
||||||
|
// as PostgreSQL functions accept column values as type int.
|
||||||
|
EXPECT_THROW(r->colCheck(-1), DbOperationError);
|
||||||
|
|
||||||
|
// Iterate over the column indexes verifying:
|
||||||
|
// 1. the column is valid
|
||||||
|
// 2. the result set column name matches the expected column name
|
||||||
|
for (int i = 0; i < NUM_BASIC_COLS; i++) {
|
||||||
|
EXPECT_NO_THROW(r->colCheck(i));
|
||||||
|
EXPECT_EQ(r->getColumnLabel(i), expectedColumnName(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify above range column value is detected.
|
||||||
|
EXPECT_THROW(r->colCheck(NUM_BASIC_COLS), DbOperationError);
|
||||||
|
|
||||||
|
// Verify the fetching a column label for out of range columns
|
||||||
|
// do NOT throw.
|
||||||
|
std::string label;
|
||||||
|
ASSERT_NO_THROW(label = r->getColumnLabel(-1));
|
||||||
|
EXPECT_EQ(label, "Unknown column:-1");
|
||||||
|
ASSERT_NO_THROW(label = r->getColumnLabel(NUM_BASIC_COLS));
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Unknown column:" << NUM_BASIC_COLS;
|
||||||
|
EXPECT_EQ(label, os.str());
|
||||||
|
|
||||||
|
// Verify row count and checking. With an empty result set all values of
|
||||||
|
// row are invalid.
|
||||||
|
EXPECT_EQ(r->getRows(), 0);
|
||||||
|
EXPECT_THROW(r->rowCheck(-1), DbOperationError);
|
||||||
|
EXPECT_THROW(r->rowCheck(0), DbOperationError);
|
||||||
|
EXPECT_THROW(r->rowCheck(1), DbOperationError);
|
||||||
|
|
||||||
|
// Verify Row-column check will always fail with an empty result set.
|
||||||
|
EXPECT_THROW(r->rowColCheck(-1, 1), DbOperationError);
|
||||||
|
EXPECT_THROW(r->rowColCheck(0, 1), DbOperationError);
|
||||||
|
EXPECT_THROW(r->rowColCheck(1, 1), DbOperationError);
|
||||||
|
|
||||||
|
// Insert three minimal rows. We don't really care about column content
|
||||||
|
// for this test.
|
||||||
|
int num_rows = 3;
|
||||||
|
for (int i = 0; i < num_rows; i++) {
|
||||||
|
RUN_SQL(r, "INSERT INTO basics (bool_col) VALUES ('t')",
|
||||||
|
PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly created rows.
|
||||||
|
FETCH_ROWS(r, num_rows);
|
||||||
|
|
||||||
|
// Verify we row count and checking
|
||||||
|
EXPECT_EQ(r->getRows(), num_rows);
|
||||||
|
EXPECT_THROW(r->rowCheck(-1), DbOperationError);
|
||||||
|
|
||||||
|
// Iterate over the row count, verifying that expected rows are valid
|
||||||
|
for (int i = 0; i < num_rows; i++) {
|
||||||
|
EXPECT_NO_THROW(r->rowCheck(i));
|
||||||
|
EXPECT_NO_THROW(r->rowColCheck(i, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify an above range row is detected.
|
||||||
|
EXPECT_THROW(r->rowCheck(num_rows), DbOperationError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write BOOL columns
|
||||||
|
TEST_F(PgSqlBasicsTest, boolTest) {
|
||||||
|
// Create a prepared statement for inserting bool_col
|
||||||
|
const char* st_name = "bool_insert";
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{1, { OID_BOOL }, st_name,
|
||||||
|
"INSERT INTO BASICS (bool_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
bool bools[] = { true, false };
|
||||||
|
PsqlBindArrayPtr bind_array(new PsqlBindArray());
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
|
||||||
|
// Insert bool rows
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(bools[i]);
|
||||||
|
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, 2);
|
||||||
|
|
||||||
|
// Verify the fetched bool values are what we expect.
|
||||||
|
bool fetched_bool;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < 2; ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BOOL_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_bool = !bools[row];
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BOOL_COL,
|
||||||
|
fetched_bool));
|
||||||
|
EXPECT_EQ(fetched_bool, bools[row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, 1, fetched_bool),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL boolean
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
|
||||||
|
// Run the insert with the bind array.
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted row.
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write BYTEA columns
|
||||||
|
TEST_F(PgSqlBasicsTest, byteaTest) {
|
||||||
|
const char* st_name = "bytea_insert";
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{1, { OID_BYTEA }, st_name,
|
||||||
|
"INSERT INTO BASICS (bytea_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
const uint8_t bytes[] = {
|
||||||
|
0x01, 0x02, 0x03, 0x04
|
||||||
|
};
|
||||||
|
std::vector<uint8_t> vbytes(bytes, bytes + sizeof(bytes));
|
||||||
|
|
||||||
|
// Verify we can insert bytea from a vector
|
||||||
|
PsqlBindArrayPtr bind_array(new PsqlBindArray());
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
bind_array->add(vbytes);
|
||||||
|
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Verify we can insert bytea from a buffer.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(bytes, sizeof(bytes));
|
||||||
|
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
int num_rows = 2;
|
||||||
|
FETCH_ROWS(r, num_rows);
|
||||||
|
|
||||||
|
uint8_t fetched_bytes[sizeof(bytes)];
|
||||||
|
size_t byte_count;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < num_rows; ++row) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BYTEA_COL));
|
||||||
|
|
||||||
|
// Extract the data into a correctly sized buffer
|
||||||
|
memset(fetched_bytes, 0, sizeof(fetched_bytes));
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
|
||||||
|
fetched_bytes,
|
||||||
|
sizeof(fetched_bytes),
|
||||||
|
byte_count));
|
||||||
|
|
||||||
|
// Verify the data is correct
|
||||||
|
ASSERT_EQ(byte_count, sizeof(bytes));
|
||||||
|
for (int i = 0; i < sizeof(bytes); i++) {
|
||||||
|
ASSERT_EQ(bytes[i], fetched_bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
|
||||||
|
fetched_bytes,
|
||||||
|
sizeof(fetched_bytes),
|
||||||
|
byte_count),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Verify that too small of a buffer throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
|
||||||
|
fetched_bytes,
|
||||||
|
sizeof(fetched_bytes) - 1,
|
||||||
|
byte_count),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL for a bytea column
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull(PsqlBindArray::BINARY_FMT);
|
||||||
|
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted row.
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BYTEA_COL));
|
||||||
|
|
||||||
|
// Verify that fetching a NULL bytea, returns 0 byte count
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
|
||||||
|
fetched_bytes,
|
||||||
|
sizeof(fetched_bytes),
|
||||||
|
byte_count));
|
||||||
|
EXPECT_EQ(byte_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write BIGINT columns
|
||||||
|
TEST_F(PgSqlBasicsTest, bigIntTest) {
|
||||||
|
// Create a prepared statement for inserting BIGINT
|
||||||
|
const char* st_name = "bigint_insert";
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_INT8 }, st_name,
|
||||||
|
"INSERT INTO BASICS (bigint_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Build our reference list of reference values
|
||||||
|
std::vector<int64_t> ints;
|
||||||
|
ints.push_back(-1);
|
||||||
|
ints.push_back(0);
|
||||||
|
ints.push_back(0x7fffffffffffffff);
|
||||||
|
ints.push_back(0xffffffffffffffff);
|
||||||
|
|
||||||
|
// Insert a row for each reference value
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
for (int i = 0; i < ints.size(); ++i) {
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ints[i]);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, ints.size());
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying each value against its reference
|
||||||
|
int64_t fetched_int;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < ints.size(); ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BIGINT_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_int = 777;
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
|
||||||
|
fetched_int));
|
||||||
|
EXPECT_EQ(fetched_int, ints[row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
|
||||||
|
fetched_int),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted row.
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BIGINT_COL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write SMALLINT columns
|
||||||
|
TEST_F(PgSqlBasicsTest, smallIntTest) {
|
||||||
|
// Create a prepared statement for inserting a SMALLINT
|
||||||
|
const char* st_name = "smallint_insert";
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_INT2 }, st_name,
|
||||||
|
"INSERT INTO BASICS (smallint_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Build our reference list of reference values
|
||||||
|
std::vector<int16_t>ints;
|
||||||
|
ints.push_back(-1);
|
||||||
|
ints.push_back(0);
|
||||||
|
ints.push_back(0x7fff);
|
||||||
|
ints.push_back(0xffff);
|
||||||
|
|
||||||
|
// Insert a row for each reference value
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
for (int i = 0; i < ints.size(); ++i) {
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ints[i]);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, ints.size());
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying each value against its reference
|
||||||
|
int16_t fetched_int;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < ints.size(); ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, SMALLINT_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_int = 777;
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
|
||||||
|
fetched_int));
|
||||||
|
EXPECT_EQ(fetched_int, ints[row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
|
||||||
|
fetched_int),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted row.
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, SMALLINT_COL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write INT columns
|
||||||
|
TEST_F(PgSqlBasicsTest, intTest) {
|
||||||
|
// Create a prepared statement for inserting an INT
|
||||||
|
const char* st_name = "int_insert";
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_INT4 }, st_name,
|
||||||
|
"INSERT INTO BASICS (int_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Build our reference list of reference values
|
||||||
|
std::vector<int32_t> ints;
|
||||||
|
ints.push_back(-1);
|
||||||
|
ints.push_back(0);
|
||||||
|
ints.push_back(0x7fffffff);
|
||||||
|
ints.push_back(0xffffffff);
|
||||||
|
|
||||||
|
// Insert a row for each reference value
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
for (int i = 0; i < ints.size(); ++i) {
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ints[i]);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, ints.size());
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying each value against its reference
|
||||||
|
int32_t fetched_int;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < ints.size(); ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INT_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_int = 777;
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL,
|
||||||
|
fetched_int));
|
||||||
|
EXPECT_EQ(fetched_int, ints[row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL, fetched_int),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INT_COL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write TEXT columns
|
||||||
|
TEST_F(PgSqlBasicsTest, textTest) {
|
||||||
|
// Create a prepared statement for inserting TEXT
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_TEXT }, "text_insert",
|
||||||
|
"INSERT INTO BASICS (text_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Our reference string.
|
||||||
|
std::string ref_string = "This is a text string";
|
||||||
|
|
||||||
|
// Insert the reference from std::string
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ref_string);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Insert the reference from a buffer
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ref_string.c_str());
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, 2);
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying the value against the reference
|
||||||
|
std::string fetched_str;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < 2; ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TEXT_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_str = "";
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL,
|
||||||
|
fetched_str));
|
||||||
|
EXPECT_EQ(fetched_str, ref_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL, fetched_str),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted row.
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TEXT_COL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write VARCHAR columns
|
||||||
|
TEST_F(PgSqlBasicsTest, varcharTest) {
|
||||||
|
// Create a prepared statement for inserting a VARCHAR
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_VARCHAR }, "varchar_insert",
|
||||||
|
"INSERT INTO BASICS (varchar_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Our reference string.
|
||||||
|
std::string ref_string = "This is a varchar string";
|
||||||
|
|
||||||
|
// Insert the reference from std::string
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ref_string);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Insert the reference from a buffer
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(ref_string.c_str());
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, 2);
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying the value against the reference
|
||||||
|
std::string fetched_str;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < 2; ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, VARCHAR_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_str = "";
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
|
||||||
|
fetched_str));
|
||||||
|
EXPECT_EQ(fetched_str, ref_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
|
||||||
|
fetched_str),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, VARCHAR_COL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Verify that we can read and write TIMESTAMP columns
|
||||||
|
TEST_F(PgSqlBasicsTest, timeStampTest) {
|
||||||
|
// Create a prepared statement for inserting a TIMESTAMP
|
||||||
|
PgSqlTaggedStatement statement[] = {
|
||||||
|
{ 1, { OID_TIMESTAMP }, "timestamp_insert",
|
||||||
|
"INSERT INTO BASICS (timestamp_col) values ($1)" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
|
||||||
|
|
||||||
|
// Build our list of reference times
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
std::vector<time_t> times;
|
||||||
|
times.push_back(now);
|
||||||
|
times.push_back(DatabaseConnection::MAX_DB_TIME);
|
||||||
|
// Note on a 32-bit OS this value is really -1. PosgreSQL will store it
|
||||||
|
// and return it intact.
|
||||||
|
times.push_back(0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Insert a row for each reference value
|
||||||
|
PsqlBindArrayPtr bind_array;
|
||||||
|
PgSqlResultPtr r;
|
||||||
|
std::string time_str;
|
||||||
|
for (int i = 0; i < times.size(); ++i) {
|
||||||
|
// Timestamps are inserted as strings so convert them first
|
||||||
|
ASSERT_NO_THROW(time_str =
|
||||||
|
PgSqlExchange::convertToDatabaseTime(times[i]));
|
||||||
|
|
||||||
|
// Add it to the bind array and insert it
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(time_str);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a row with ref time plus one day
|
||||||
|
times.push_back(now + 24*3600);
|
||||||
|
ASSERT_NO_THROW(time_str =
|
||||||
|
PgSqlExchange::convertToDatabaseTime(times[0], 24*3600));
|
||||||
|
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->add(time_str);
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows.
|
||||||
|
FETCH_ROWS(r, times.size());
|
||||||
|
|
||||||
|
// Iterate over the rows, verifying the value against its reference
|
||||||
|
std::string fetched_str;
|
||||||
|
int row = 0;
|
||||||
|
for ( ; row < times.size(); ++row ) {
|
||||||
|
// Verify the column is not null.
|
||||||
|
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TIMESTAMP_COL));
|
||||||
|
|
||||||
|
// Fetch and verify the column value
|
||||||
|
fetched_str = "";
|
||||||
|
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
|
||||||
|
fetched_str));
|
||||||
|
|
||||||
|
time_t fetched_time;
|
||||||
|
ASSERT_NO_THROW(fetched_time =
|
||||||
|
PgSqlExchange::convertFromDatabaseTime(fetched_str));
|
||||||
|
EXPECT_EQ(fetched_time, times[row]) << " row: " << row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we here, verify that bad row throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
|
||||||
|
fetched_str),
|
||||||
|
DbOperationError);
|
||||||
|
|
||||||
|
// Clean out the table
|
||||||
|
WIPE_ROWS(r);
|
||||||
|
|
||||||
|
// Verify we can insert a NULL value.
|
||||||
|
bind_array.reset(new PsqlBindArray());
|
||||||
|
bind_array->addNull();
|
||||||
|
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
|
||||||
|
|
||||||
|
// Fetch the newly inserted rows
|
||||||
|
FETCH_ROWS(r, 1);
|
||||||
|
|
||||||
|
// Verify the column is null.
|
||||||
|
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TIMESTAMP_COL));
|
||||||
|
|
||||||
|
// Verify exceeding max time throws
|
||||||
|
ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(times[0],
|
||||||
|
DatabaseConnection::
|
||||||
|
MAX_DB_TIME),
|
||||||
|
BadValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace
|
||||||
|
Loading…
x
Reference in New Issue
Block a user