///
/// @return Pointer to the retrieved value or null if such parameter
/// doesn't exist.
- StampedValuePtr getGlobalParameter4(const ServerSelector& /* server_selector */,
- const std::string& /* name */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ StampedValuePtr getGlobalParameter4(const ServerSelector& server_selector,
+ const std::string& name) {
+ StampedValueCollection parameters;
+
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ PsqlBindArray in_bindings;
+ in_bindings.addTempString(tag.get());
+ in_bindings.add(name);
+
+ getGlobalParameters(GET_GLOBAL_PARAMETER4, in_bindings, parameters);
+ }
+
+ return (parameters.empty() ? StampedValuePtr() : *parameters.begin());
}
/// @brief Sends query to insert or update global parameter.
///
/// @param server_selector Server selector.
/// @param value StampedValue describing the parameter to create/update.
- void createUpdateGlobalParameter4(const db::ServerSelector& /* server_selector */,
- const StampedValuePtr& /* value */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
- }
+ void createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const StampedValuePtr& value) {
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "creating or updating global parameter");
+
+ PsqlBindArray in_bindings;
+ in_bindings.addTempString(value->getName());
+ in_bindings.addTempString(value->getValue());
+ in_bindings.add(value->getType()),
+ in_bindings.addTimestamp(value->getModificationTime()),
+ in_bindings.addTempString(tag);
+ in_bindings.addTempString(value->getName());
+
+ PgSqlTransaction transaction(conn_);
+
+ // Create scoped audit revision. As long as this instance exists
+ // no new audit revisions are created in any subsequent calls.
+ ScopedAuditRevision audit_revision(this,
+ PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ server_selector, "global parameter set",
+ false);
+
+ // Try to update the existing row.
+ if (updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::UPDATE_GLOBAL_PARAMETER4,
+ in_bindings) == 0) {
+
+ // No such parameter found, so let's insert it. We have to adjust the
+ // bindings collection to match the prepared statement for insert.
+ in_bindings.popBack();
+ in_bindings.popBack();
+
+ insertQuery(PgSqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4,
+ in_bindings);
+
+ // Successfully inserted global parameter. Now, we have to associate it
+ // with the server tag.
+ PsqlBindArray attach_bindings;
+ uint64_t pid = getLastInsertId4("dhcp4_global_parameter", "id");
+ attach_bindings.add(pid); // id of newly inserted global.
+ attach_bindings.add(value->getModificationTime());
+ attachElementToServers(PgSqlConfigBackendDHCPv4Impl::INSERT_GLOBAL_PARAMETER4_SERVER,
+ server_selector, attach_bindings);
+ }
+
+ transaction.commit();
+ }
/// @brief Sends query to the database to retrieve multiple subnets.
///
selectQuery(index, in_bindings,
[&audit_entries] (PgSqlResult& r, int row) {
// Extract the column values for r[row].
+ // Create a worker for the row.
+ PgSqlResultRowWorker worker(r, row);
// Get the object type. Column 0 is the entry ID which
// we don't need here.
- std::string object_type;
- PgSqlExchange::getColumnValue(r, row, 1, object_type);
+ std::string object_type = worker.getString(1);
// Get the object ID.
- uint64_t object_id;
- PgSqlExchange::getColumnValue(r, row, 2, object_id);
+ uint64_t object_id = worker.getBigInt(2);
// Get the modification type.
- uint8_t mod_typ_int;
- PgSqlExchange::getColumnValue(r, row, 3, mod_typ_int);
AuditEntry::ModificationType mod_type =
- static_cast<AuditEntry::ModificationType>(mod_typ_int);
+ static_cast<AuditEntry::ModificationType>(worker.getSmallInt(3));
// Get the modification time.
- boost::posix_time::ptime mod_time;
- PgSqlExchange::getColumnValue(r, row, 4, mod_time);
+ boost::posix_time::ptime mod_time = worker.getTimestamp(4);
// Get the revision ID.
- uint64_t revision_id;
- PgSqlExchange::getColumnValue(r, row, 5, revision_id);
+ uint64_t revision_id = worker.getBigInt(5);;
// Get the revision log message.
- std::string log_message;
- PgSqlExchange::getColumnValue(r, row, 6, log_message);
+ std::string log_message = worker.getString(6);
// Create new audit entry and add it to the collection of received
// entries.
}
void
-PgSqlConfigBackendImpl::getGlobalParameters(const int /* index */,
- const PsqlBindArray& /* in_bindings */,
- StampedValueCollection& /* parameters */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+PgSqlConfigBackendImpl::getGlobalParameters(const int index,
+ const PsqlBindArray& in_bindings,
+ StampedValueCollection& parameters) {
+ // The following parameters from the dhcp[46]_global_parameter table are
+ // returned per row:
+ // - id
+ // - parameter name
+ // - parameter value
+ // - parameter type
+ // - modification timestamp
+
+ StampedValuePtr last_param;
+ StampedValueCollection local_parameters;
+ selectQuery(index, in_bindings,
+ [&local_parameters, &last_param](PgSqlResult& r, int row) {
+ // Extract the column values for r[row].
+ // Create a worker for the row.
+ PgSqlResultRowWorker worker(r, row);
+
+ // Get parameter ID.
+ uint64_t id = worker.getBigInt(0);
+
+ // If we're starting or if this is new parameter being processed...
+ if (!last_param || (last_param->getId() != id)) {
+ // Create the parameter instance.
+
+ // Get parameter name.
+ std::string name = worker.getString(1);
+ if (!name.empty()) {
+ // Fetch the value.
+ std::string value = worker.getString(2);
+
+ // Fetch the type.
+ Element::types ptype = static_cast<Element::types>(worker.getSmallInt(3));
+
+ // Create the parameter.
+ last_param = StampedValue::create(name, value, ptype);
+
+ // Set the id.
+ last_param->setId(id);
+
+ // Get and set the modification time.
+ boost::posix_time::ptime mod_time = worker.getTimestamp(4);
+ last_param->setModificationTime(mod_time);
+
+ // server_tag
+ std::string server_tag_str = worker.getString(5);
+ last_param->setServerTag(server_tag_str);
+
+ // If we're fetching parameters for a given server (explicit server
+ // tag is provided), it takes precedence over the same parameter
+ // specified for all servers. Therefore, we check if the given
+ // parameter already exists and belongs to 'all'.
+ ServerTag last_param_server_tag(server_tag_str);
+ auto& index = local_parameters.get<StampedValueNameIndexTag>();
+ auto existing = index.find(name);
+ if (existing != index.end()) {
+ // This parameter was already fetched. Let's check if we should
+ // replace it or not.
+ if (!last_param_server_tag.amAll() && (*existing)->hasAllServerTag()) {
+ // Replace parameter specified for 'all' with the one associated
+ // with the particular server tag.
+ local_parameters.replace(existing, last_param);
+ return;
+ }
+ }
+
+ // If there is no such parameter yet or the existing parameter
+ // belongs to a different server and the inserted parameter is
+ // not for all servers.
+ if ((existing == index.end()) ||
+ (!(*existing)->hasServerTag(last_param_server_tag) &&
+ !last_param_server_tag.amAll())) {
+ local_parameters.insert(last_param);
+ }
+ }
+ }
+ });
+
+ parameters.insert(local_parameters.begin(), local_parameters.end());
}
OptionDefinitionPtr
selectQuery(index, in_bindings,
[&servers, &last_server](PgSqlResult& r, int row) {
// Extract the column values for r[row].
+ // Create a worker for the row.
+ PgSqlResultRowWorker worker(r, row);
// Get the server ID.
- uint64_t id;
- PgSqlExchange::getColumnValue(r, row, 0, id);
+ uint64_t id = worker.getBigInt(0);
// Get the server tag.
- std::string tag;
- PgSqlExchange::getColumnValue(r, row, 1, tag);
+ std::string tag = worker.getString(1);
// Get the description.
- std::string description;
- PgSqlExchange::getColumnValue(r, row, 2, description);
+ std::string description = worker.getString(2);
// Get the modification time.
- boost::posix_time::ptime mod_time;
- PgSqlExchange::getColumnValue(r, row, 3, mod_time);
+ boost::posix_time::ptime mod_time = worker.getTimestamp(3);
if (!last_server || (last_server->getId() != id)) {
// Create the server instance.
createUpdateDeleteServerTest();
}
+TEST_F(PgSqlConfigBackendDHCPv4Test, getAndDeleteAllServersTest) {
+ getAndDeleteAllServersTest();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteGlobalParameter4Test) {
+ createUpdateDeleteGlobalParameter4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, globalParameters4WithServerTagsTest) {
+ globalParameters4WithServerTagsTest();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, getAllGlobalParameters4Test) {
+ getAllGlobalParameters4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, getModifiedGlobalParameters4Test) {
+ getModifiedGlobalParameters4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, nullKeyErrorTest) {
+ nullKeyErrorTest();
+}
/// @brief Test fixture for verifying database connection loss-recovery
/// behavior.
// Create a global parameter (it should work with any object type).
StampedValuePtr global_parameter = StampedValue::create("global", "value");
- // Try to insert it and associate with non-existing server.
- std::string msg;
- try {
- cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
- global_parameter);
- msg = "got no exception";
- } catch (const NullKeyError& ex) {
- msg = ex.what();
- } catch (const std::exception&) {
- msg = "got another exception";
- }
- EXPECT_EQ("server 'server1' does not exist", msg);
+ ASSERT_THROW (cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter), NullKeyError);
}
void
PQfreemem(bytes);
}
+void
+PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row, const size_t col,
+ std::vector<uint8_t>& value) {
+ // Returns converted bytes in a dynamically allocated buffer, and
+ // sets bytes_converted.
+ size_t bytes_converted = 0;
+ 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(r, col) << " row:" << row);
+ }
+
+ // Copy from the allocated buffer to caller's buffer the free up
+ // the allocated buffer.
+ if (bytes_converted) {
+ value.assign(bytes, bytes + bytes_converted);
+ } else {
+ value.clear();
+ }
+
+ // Free the PostgreSQL buffer.
+ PQfreemem(bytes);
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t col) {
+ uint32_t col_value;
+ if (isColumnNull(r, row, col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ getColumnValue(r, row, col, col_value);
+ return (Triplet<uint32_t>(col_value));
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ if (isColumnNull(r, row, def_col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ uint32_t value;
+ getColumnValue(r, row, def_col, value);
+
+ uint32_t min_value = value;
+ if (!isColumnNull(r, row, min_col)) {
+ getColumnValue(r, row, min_col, min_value);
+ }
+
+ uint32_t max_value = value;
+ if (!isColumnNull(r, row, max_col)) {
+ getColumnValue(r, row, max_col, max_value);
+ }
+
+ return (Triplet<uint32_t>(min_value, value, max_value));
+}
+
std::string
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
return (r.getColumnLabel(column));
return (stream.str());
}
+PgSqlResultRowWorker::PgSqlResultRowWorker(const PgSqlResult& r, const int row)
+ : r_(r), row_(row) {
+ // Validate the desired row.
+ r.rowCheck(row);
+}
+
+bool
+PgSqlResultRowWorker::isColumnNull(const size_t col) {
+ return (PgSqlExchange::isColumnNull(r_, row_, col));
+}
+
+std::string
+PgSqlResultRowWorker::getString(const size_t col) {
+ std::string tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+bool
+PgSqlResultRowWorker::getBool(const size_t col) {
+ bool tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+double
+PgSqlResultRowWorker::getDouble(const size_t col) {
+ double tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+const char*
+PgSqlResultRowWorker::getRawColumnValue(const size_t col) {
+ return(PgSqlExchange::getRawColumnValue(r_, row_, col));
+}
+
+uint64_t
+PgSqlResultRowWorker::getBigInt(const size_t col) {
+ uint64_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint32_t
+PgSqlResultRowWorker::getInt(const size_t col) {
+ uint32_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint16_t
+PgSqlResultRowWorker::getSmallInt(const size_t col) {
+ uint16_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+void
+PgSqlResultRowWorker::getBytes(const size_t col, std::vector<uint8_t>& value) {
+ PgSqlExchange::convertFromBytea(r_, row_, col, value);
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet4(const size_t col) {
+ return(PgSqlExchange::getInetValue4(r_, row_, col));
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet6(const size_t col) {
+ return(PgSqlExchange::getInetValue6(r_, row_, col));
+}
+
+boost::posix_time::ptime
+PgSqlResultRowWorker::getTimestamp(const size_t col) {
+ boost::posix_time::ptime value;
+ getColumnValue(col, value);
+ return (value);
+};
+
+data::ElementPtr
+PgSqlResultRowWorker::getJSON(const size_t col) {
+ data::ElementPtr value;
+ getColumnValue(col, value);
+ return(value);
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t col) {
+ return(PgSqlExchange::getTripletValue(r_, row_, col));
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ return(PgSqlExchange::getTripletValue(r_, row_, def_col, min_col, max_col));
+}
+
+std::string
+PgSqlResultRowWorker::dumpRow() {
+ return(PgSqlExchange::dumpRow(r_, row_));
+}
+
} // end of isc::db namespace
} // end of isc namespace
const size_t buffer_size,
size_t &bytes_converted);
+ /// @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 vectory 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] value vector to receive the converted bytes
+ /// value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void convertFromBytea(const PgSqlResult& r, const int row,
+ const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @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 If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @param[out] value Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minium value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @param[out] value Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
/// @brief Diagnostic tool which dumps the Result row contents as a string
///
/// @param r the result set containing the query results
std::vector<std::string> columns_;
};
+/// @brief Convenience class which facilitates fetching column values
+/// from a result set row.
+class PgSqlResultRowWorker {
+public:
+ /// @brief Constructor
+ ///
+ /// @param r result set containing the fetched rows of data.
+ /// @param row zero-based index of the desired row, (e.g.
+ /// 0 .. n - 1 where n = number of rows in r)
+ /// @throw DbOperationError if row value is invalid.
+ PgSqlResultRowWorker(const PgSqlResult& r, const int row);
+
+ /// @brief Indicates whether or not the given column value is null.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return true if the value is null, false otherwise.
+ bool isColumnNull(const size_t col);
+
+ /// @brief Fetches the column value as a string.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return std::string containing the column value.
+ std::string getString(const size_t col);
+
+ /// @brief Fetches the boolean value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return bool containing the column value.
+ bool getBool(const size_t col);
+
+ /// @brief Fetches the floating point value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return double containing the column value.
+ double getDouble(const size_t col);
+
+ /// @brief Gets a pointer to the raw column value in a result set row
+ ///
+ /// Given a column return a const char* pointer to the data value in
+ /// the result set row. The pointer is valid as long as the underlying
+ /// 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 col the column number within the row
+ ///
+ /// @return a const char* pointer to the column's raw data
+ const char* getRawColumnValue(const size_t col);
+
+ /// @brief Fetches the uint64_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint64_t containing the column value
+ uint64_t getBigInt(const size_t col);
+
+ /// @brief Fetches the uint32_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint32_t containing the column value
+ uint32_t getInt(const size_t col);
+
+ /// @brief Fetches the uint16_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint16_t containing the column value
+ uint16_t getSmallInt(const size_t col);
+
+ /// @brief Fetches binary data at the given column into a vector.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value vector to receive the fetched data.
+ void getBytes(const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches the v4 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv4 address.
+ isc::asiolink::IOAddress getInet4(const size_t col);
+
+ /// @brief Fetches the v6 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv6 address.
+ isc::asiolink::IOAddress getInet6(const size_t col);
+
+ /// @brief Fetches a text column as the given value type
+ ///
+ /// Uses boost::lexicalcast to convert the text column value into
+ /// a value of type T.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ template<typename T>
+ void getColumnValue(const size_t col, T& value) {
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ }
+
+ /// @brief Fetches a timestamp column as a ptime.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value ptime parameter to receive the converted timestamp
+ boost::posix_time::ptime getTimestamp(const size_t col);
+
+ /// @brief Fetches a JSON column as an ElementPtr.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value ElementPtr parameter to receive the column value
+ data::ElementPtr getJSON(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @param col the column number within the row If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @param[out] value Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minium value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @param[out] value Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
+ /// @brief Diagnostic tool which dumps the Result row contents as a string
+ ///
+ /// @return A string depiction of the row contents.
+ std::string dumpRow();
+
+private:
+ /// @brief Result set containing the row.
+ const PgSqlResult& r_;
+ /// @brief Index of the desired row.
+ size_t row_;
+};
+
+/// @brief Pointer to a result row worker.
+typedef boost::shared_ptr<PgSqlResultRowWorker> PgSqlResultRowWorkerPtr;
+
} // end of isc::db namespace
} // end of isc namespace
expected_col_names_[TEXT_COL] = "text_col";
expected_col_names_[TIMESTAMP_COL] = "timestamp_col";
expected_col_names_[VARCHAR_COL] = "varchar_col";
- expected_col_names_[INET_COL] = "inet_col";
+ expected_col_names_[INET4_COL] = "inet4_col";
expected_col_names_[FLOAT_COL] = "float_col";
expected_col_names_[JSON_COL] = "json_col";
+ expected_col_names_[MIN_INT_COL] = "min_int_col";
+ expected_col_names_[MAX_INT_COL] = "max_int_col";
+ expected_col_names_[INET6_COL] = "inet6_col";
destroySchema();
createSchema();
" text_col TEXT, "
" timestamp_col TIMESTAMP WITH TIME ZONE, "
" varchar_col VARCHAR(255), "
- " inet_col INET, "
+ " inet4_col INET, "
" float_col FLOAT, "
- " json_col JSON "
+ " json_col JSON,"
+ " min_int_col INT, "
+ " max_int_col INT, "
+ " inet6_col INET "
"); ";
PgSqlResult r(PQexec(*conn_, sql));
" id, bool_col, bytea_col, bigint_col, smallint_col, "
" int_col, text_col,"
" extract(epoch from timestamp_col)::bigint as timestamp_col,"
- " varchar_col, inet_col, float_col, json_col"
+ " varchar_col, inet4_col, float_col, json_col,"
+ " min_int_col, max_int_col, inet6_col"
" FROM basics";
runSql(r, sql, PGRES_TUPLES_OK, line);
TEXT_COL,
TIMESTAMP_COL,
VARCHAR_COL,
- INET_COL,
+ INET4_COL,
FLOAT_COL,
JSON_COL,
+ MIN_INT_COL,
+ MAX_INT_COL,
+ INET6_COL,
NUM_BASIC_COLS
};
/// using INET columns.
TEST_F(PgSqlBasicsTest, inetTest4) {
// Create a prepared statement for inserting a SMALLINT
- const char* st_name = "inet_insert";
+ const char* st_name = "inet4_insert";
PgSqlTaggedStatement statement[] = {
{ 1, { OID_TEXT }, st_name,
- "INSERT INTO BASICS (inet_col) values (cast($1 as inet))" },
+ "INSERT INTO BASICS (inet4_col) values (cast($1 as inet))" },
{ 1, { OID_TEXT }, "check_where",
- "select * from BASICS where inet_col = cast($1 as inet)" }
+ "select * from BASICS where inet4_col = cast($1 as inet)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
asiolink::IOAddress fetched_inet("0.0.0.0");
for ( ; row < inets.size(); ++row ) {
// Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET_COL));
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET4_COL));
// Fetch and verify the column value
- ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, row, INET_COL));
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, row, INET4_COL));
EXPECT_EQ(fetched_inet, inets[row]);
}
bind_array->addInet4(inets[1]);
RUN_PREP(r, statement[1], bind_array, PGRES_TUPLES_OK);
ASSERT_EQ(r->getRows(),1);
- ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, 0, INET_COL));
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, 0, INET4_COL));
EXPECT_EQ(fetched_inet, inets[1]);
// Clean out the table
FETCH_ROWS(r, 1);
// Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET_COL));
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET4_COL));
}
/// @brief Verify that we can read and write IPv6 addresses
/// using INET columns.
TEST_F(PgSqlBasicsTest, inetTest6) {
// Create a prepared statement for inserting a SMALLINT
- const char* st_name = "inet_insert";
+ const char* st_name = "inet6_insert";
PgSqlTaggedStatement statement[] = {
{ 1, { OID_TEXT }, st_name,
- "INSERT INTO BASICS (inet_col) values (cast($1 as inet))" }
+ "INSERT INTO BASICS (inet6_col) values (cast($1 as inet))" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
int row = 0;
for ( ; row < inets.size(); ++row ) {
// Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET_COL));
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET6_COL));
// Fetch and verify the column value
asiolink::IOAddress fetched_inet("::");
- ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, row, INET_COL));
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, row, INET6_COL));
EXPECT_EQ(fetched_inet, inets[row]);
}
FETCH_ROWS(r, 1);
// Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET_COL));
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET6_COL));
}
/// @brief Verify that we can read and write floats
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, JSON_COL));
}
+/// @brief Verify that we can read and write integer Triplets.
+TEST_F(PgSqlBasicsTest, tripleTest) {
+ // Create a prepared statement for inserting a SMALLINT
+ const char* st_name = "triplets_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 3, { OID_INT4, OID_INT4, OID_INT4 }, st_name,
+ "INSERT INTO BASICS (int_col, min_int_col, max_int_col) values ($1, $2, $3)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<Triplet<uint32_t>> triplets;
+ triplets.push_back(Triplet<uint32_t>()); // def column is null
+ triplets.push_back(Triplet<uint32_t>(10)); // only default column
+ triplets.push_back(Triplet<uint32_t>(5,10,15)); // all three columns
+ triplets.push_back(Triplet<uint32_t>(10,10,15)); // min column is null
+ triplets.push_back(Triplet<uint32_t>(5,10,10)); // max column is null
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (auto triplet : triplets) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(triplet);
+ bind_array->addMin(triplet);
+ bind_array->addMax(triplet);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, triplets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for (auto expected : triplets) {
+ Triplet<uint32_t> fetched;
+ // First we test making a triplet only with default value column.
+ ASSERT_NO_THROW_LOG(fetched = PgSqlExchange::getTripletValue(*r, row, INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ }
+
+ // Now test making a triplet with all three columns.
+ ASSERT_NO_THROW_LOG(
+ fetched = PgSqlExchange::getTripletValue(*r, row,
+ INT_COL, MIN_INT_COL, MAX_INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ EXPECT_EQ(fetched.getMin(), expected.getMin());
+ EXPECT_EQ(fetched.getMax(), expected.getMax());
+ }
+
+ ++row;
+ }
+
+ // Clean out the table
+ WIPE_ROWS(r);
+}
+
+/// @brief Verify PgResultRowWorker operations.
+TEST_F(PgSqlBasicsTest, resultRowWorker) {
+ // Create a prepared statement for inserting a SMALLINT
+ const char* st_name = "row_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 14,
+ {
+ OID_BOOL,
+ OID_BYTEA,
+ OID_INT8,
+ OID_INT2,
+ OID_INT4,
+ OID_TEXT,
+ OID_TIMESTAMP,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_INT4,
+ OID_INT4,
+ }, st_name,
+ "INSERT INTO BASICS ("
+ " bool_col, "
+ " bytea_col, "
+ " bigint_col, "
+ " smallint_col, "
+ " int_col, "
+ " text_col, "
+ " timestamp_col, "
+ " varchar_col, "
+ " inet4_col, "
+ " float_col, "
+ " json_col, "
+ " min_int_col, "
+ " max_int_col, "
+ " inet6_col) "
+ " VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, cast($9 as inet), "
+ " cast($10 as float), cast($11 as json), $12, $13, $14)"
+ }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Create a bind array of input values.
+ PsqlBindArrayPtr b(new PsqlBindArray());
+
+ bool exp_bool(true);
+ b->add(exp_bool);
+
+ std::vector<uint8_t> exp_bytes({ 0x01, 0x02, 0x03, 0x04});
+ b->add(exp_bytes);
+
+ uint64_t exp_bigint = 9876;
+ b->add(exp_bigint);
+
+ uint16_t exp_smallint = 12;
+ b->add(exp_smallint);
+
+ uint32_t exp_int = 345;
+ b->add(exp_int);
+
+ std::string exp_text = "just some string";
+ b->add(exp_text);
+
+ time_duration duration = hours(7) + minutes(45) + seconds(9);
+ ptime exp_timestamp(date(2021, Jul, 18), duration);
+ b->addTimestamp(exp_timestamp);
+
+ std::string exp_varchar = "really just a string";
+ b->add(exp_varchar);
+
+ asiolink::IOAddress exp_inet4("192.168.1.35");
+ b->addInet4(exp_inet4);
+
+ double exp_double(2.5);
+ b->add(exp_double);
+
+ ElementPtr exp_elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+ b->add(exp_elems);
+
+ uint32_t exp_min = 100;
+ b->add(exp_min);
+
+ uint32_t exp_max = 500;
+ b->add(exp_max);
+
+ asiolink::IOAddress exp_inet6("3001::77");
+ b->addInet6(exp_inet6);
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], b, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Create a row worker.
+ PgSqlResultRowWorkerPtr worker;
+
+ // Creating the row worker for the first (and only) row should succeed.
+ ASSERT_NO_THROW_LOG(worker.reset(new PgSqlResultRowWorker(*r, 0)));
+
+ // Now let's test all the getters.
+ EXPECT_EQ(exp_bool, worker->getBool(BOOL_COL));
+
+ std::vector<uint8_t> fetched_bytes;
+ ASSERT_NO_THROW_LOG(worker->getBytes(BYTEA_COL, fetched_bytes));
+ EXPECT_EQ(exp_bytes, fetched_bytes);
+
+ EXPECT_EQ(exp_bigint, worker->getBigInt(BIGINT_COL));
+ EXPECT_EQ(exp_smallint, worker->getSmallInt(SMALLINT_COL));
+ EXPECT_EQ(exp_int, worker->getInt(INT_COL));
+ EXPECT_EQ(exp_text, worker->getString(TEXT_COL));
+ EXPECT_EQ(exp_timestamp, worker->getTimestamp(TIMESTAMP_COL));
+ EXPECT_EQ(exp_varchar, worker->getString(VARCHAR_COL));
+ EXPECT_EQ(exp_inet4, worker->getInet4(INET4_COL));
+ EXPECT_EQ(exp_double, worker->getDouble(FLOAT_COL));
+ EXPECT_EQ(*exp_elems, *(worker->getJSON(JSON_COL)));
+ EXPECT_EQ(exp_min, worker->getInt(MIN_INT_COL));
+ EXPECT_EQ(exp_max, worker->getInt(MAX_INT_COL));
+ EXPECT_EQ(exp_inet6, worker->getInet6(INET6_COL));
+
+ // Get a triplet using int_col as the sole value.
+ Triplet<uint32_t>fetched_triplet = worker->getTriplet(5);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+
+ // Get a triplet using int_col, min_col, and max_col values.
+ fetched_triplet = worker->getTriplet(INT_COL, MIN_INT_COL, MAX_INT_COL);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+ EXPECT_EQ(exp_min, fetched_triplet.getMin());
+ EXPECT_EQ(exp_max, fetched_triplet.getMax());
+
+ // Attempting to access an invalid row should throw.
+ ASSERT_THROW_MSG(worker.reset(new PgSqlResultRowWorker(*r, 1)),
+ DbOperationError, "row: 1, out of range: 0..1");
+}
+
+
}; // namespace