]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#95] Postgresql v4 backend now supports global parameters
authorThomas Markwalder <tmark@isc.org>
Fri, 28 Jan 2022 12:45:31 +0000 (07:45 -0500)
committerRazvan Becheriu <razvan@isc.org>
Wed, 9 Feb 2022 14:43:15 +0000 (16:43 +0200)
    Added support for global paramters and new convenience
    class, PgSqlResultRowWorker

src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.cc
    PgSqlConfigBackendDHCPv4Impl::getGlobalParameter4()
    PgSqlConfigBackendDHCPv4Impl::createUpdateGlobalParameter4()
    - implemented

src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc
    PgSqlConfigBackendImpl::getRecentAuditEntries()
    PgSqlConfigBackendImpl::getServers()
    - now uses PgSqlResultRowWorker

    PgSqlConfigBackendImpl::getGlobalParameters()
    - implemented

src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc
    TEST_F(PgSqlConfigBackendDHCPv4Test, getAndDeleteAllServersTest)
    TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteGlobalParameter4Test)
    TEST_F(PgSqlConfigBackendDHCPv4Test, globalParameters4WithServerTagsTest)
    TEST_F(PgSqlConfigBackendDHCPv4Test, getAllGlobalParameters4Test)
    TEST_F(PgSqlConfigBackendDHCPv4Test, getModifiedGlobalParameters4Test)
    TEST_F(PgSqlConfigBackendDHCPv4Test, nullKeyErrorTest)
    - new tests

src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
    GenericConfigBackendDHCPv4Test::nullKeyErrorTest()
    - only checks exception type, since messages between backends
    are different

src/lib/pgsql/pgsql_exchange.*
    PgSqlExchange::convertFromBytea()
    - new function outputs a vector

    PgSqlExchange::getTripletValue()
    PgSqlExchange::getTripletValue()
    - new functions

    PgSqlResultRowWorker
    - new convenience class for accessing columns in a
    result set row

src/lib/pgsql/tests/pgsql_basics.cc
    Added new columns to basics table

src/lib/pgsql/tests/pgsql_exchange_unittest.cc
    TEST_F(PgSqlBasicsTest, tripleTest)
    TEST_F(PgSqlBasicsTest, resultRowWorker)
    - new tests

src/hooks/dhcp/pgsql_cb/pgsql_cb_dhcp4.cc
src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc
src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
src/lib/pgsql/pgsql_exchange.cc
src/lib/pgsql/pgsql_exchange.h
src/lib/pgsql/tests/pgsql_basics.cc
src/lib/pgsql/tests/pgsql_basics.h
src/lib/pgsql/tests/pgsql_exchange_unittest.cc

index 515e7e19e2cb6426979114d7fe96e5b209afbe3c..f1a8cec7a535453ca29e0ef638a155f433d62dc7 100644 (file)
@@ -196,19 +196,76 @@ public:
     ///
     /// @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.
     ///
index 734204d87d82c4c8694a3ef69c7a2defa9f1ab4f..262d2fd800a6b386d3d96dd04a31418e3394e31c 100644 (file)
@@ -172,33 +172,28 @@ PgSqlConfigBackendImpl::getRecentAuditEntries(const int index,
         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.
@@ -256,10 +251,86 @@ PgSqlConfigBackendImpl::getLastInsertId(const int index, const std::string& tabl
 }
 
 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
@@ -485,22 +556,20 @@ PgSqlConfigBackendImpl::getServers(const int index,
     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.
index 5c1d842911fd2cd5bb5d92f66a7039561256f7bd..3bfe3ebd24f649edafd9b42dabb9f15d5ae2f7b2 100644 (file)
@@ -133,6 +133,29 @@ TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteServer) {
     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.
index ca3a8a4bb7a619b85a878f99d61d280569478d8e..10f59c6ff4c3385334180ec9986a824ba3afa701 100644 (file)
@@ -1014,18 +1014,8 @@ GenericConfigBackendDHCPv4Test::nullKeyErrorTest() {
     // 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
index f2c25b4b65ea131044919487a4584e658c736c66..c20af57a2bff9150d5e578e6bd3821a7cceaa422 100644 (file)
@@ -521,6 +521,70 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
     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));
@@ -559,5 +623,108 @@ PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
     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
index 37c74790442f0f484efc747facd4962da7d93e82..90999ca88b90ca38429a42417c8108b9265f334e 100644 (file)
@@ -756,6 +756,59 @@ public:
                                  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
@@ -770,6 +823,164 @@ protected:
     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
 
index c0dcb0d8f71adbb645d15712d38163389bd1c5ec..1c4850adc9ee792831660d50053fea890f9b5946 100644 (file)
@@ -42,9 +42,12 @@ PgSqlBasicsTest::PgSqlBasicsTest() : expected_col_names_(NUM_BASIC_COLS) {
     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();
@@ -78,9 +81,12 @@ PgSqlBasicsTest::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));
@@ -129,7 +135,8 @@ PgSqlBasicsTest::fetchRows(PgSqlResultPtr& r, int exp_rows, int line) {
         "   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);
index 3366a1604fbda28cb1907653f8b905fc52b7b057..02c5184f9393ec5ed4df51129a722d83cb9bde43 100644 (file)
@@ -51,9 +51,12 @@ public:
         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
     };
 
index d8e8eb7fa4de1b2c70edf3d725f6049833a3d26e..5166c0de51ce132d46cf9d511fce036756b80bf6 100644 (file)
@@ -1093,12 +1093,12 @@ TEST(PsqlBindArray, popBackTest) {
 /// 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]));
@@ -1127,10 +1127,10 @@ TEST_F(PgSqlBasicsTest, inetTest4) {
     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]);
     }
 
@@ -1140,7 +1140,7 @@ TEST_F(PgSqlBasicsTest, inetTest4) {
     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
@@ -1155,17 +1155,17 @@ TEST_F(PgSqlBasicsTest, inetTest4) {
     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]));
@@ -1191,11 +1191,11 @@ TEST_F(PgSqlBasicsTest, inetTest6) {
     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]);
     }
 
@@ -1211,7 +1211,7 @@ TEST_F(PgSqlBasicsTest, inetTest6) {
     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
@@ -1324,4 +1324,207 @@ TEST_F(PgSqlBasicsTest, jsonTest) {
     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