]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2381] Added support for JSON columns
authorThomas Markwalder <tmark@isc.org>
Fri, 17 Dec 2021 18:12:31 +0000 (13:12 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 21 Dec 2021 18:30:14 +0000 (13:30 -0500)
src/lib/pgsql/pgsql_exchange.*
    PsqlBindArray::add(const ElementPtr& value)
    PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
                                  const size_t col, ElementPtr& value)

src/lib/pgsql/tests/pgsql_basics.h
src/lib/pgsql/tests/pgsql_exchange_unittest.cc
    TEST_F(PgSqlBasicsTest, jsonTest) - new test

src/lib/pgsql/pgsql_exchange.cc
src/lib/pgsql/pgsql_exchange.h
src/lib/pgsql/tests/pgsql_basics.h
src/lib/pgsql/tests/pgsql_exchange_unittest.cc

index 8f62158dea0c11087bc0dac298123964dc6b6b6b..e3db14022bd528331a97f3048ffcfe1848de10fd 100644 (file)
@@ -17,6 +17,7 @@
 #include <vector>
 
 using namespace isc::util;
+using namespace isc::data;
 
 namespace isc {
 namespace db {
@@ -217,6 +218,17 @@ PsqlBindArray::addTimestamp() {
     addTempString(PgSqlExchange::convertToDatabaseTime(now));
 }
 
+void
+PsqlBindArray::add(const ElementPtr& value) {
+    if (!value) {
+        addNull();
+        return;
+    }
+
+    std::ostringstream ss;
+    value->toJSON(ss);
+    addTempString(ss.str());
+}
 
 std::string
 PsqlBindArray::toText() const {
@@ -375,6 +387,19 @@ PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
     PgSqlExchange::convertFromDatabaseTime(db_time_val, value);
 }
 
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+                              const size_t col, ElementPtr& value) {
+    const char* data = getRawColumnValue(r, row, col);
+    try {
+        value = Element::fromJSON(data);
+    } catch (const std::exception& ex) {
+        isc_throw(DbOperationError, "Cannot convert data: " << data
+                  << " for: " << getColumnLabel(r, col) << " row:" << row
+                  << " : " << ex.what());
+    }
+}
+
 isc::asiolink::IOAddress
 PgSqlExchange::getInetValue4(const PgSqlResult& r, const int row,
                             const size_t col) {
index 53650c364583bbbfe95c5aee5dc0c108cda567fc..af1dde0954e8d44f408fe65d9f96968dc3d504a7 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <database/database_connection.h>
+#include <cc/data.h>
 #include <util/triplet.h>
 #include <util/boost_time_utils.h>
 #include <exceptions/exceptions.h>
@@ -406,6 +407,15 @@ struct PsqlBindArray {
     /// Precision is seconds.
     void addTimestamp();
 
+    /// @brief Adds an ElementPtr to the bind array
+    ///
+    /// Adds a TEXT_FMT value to the end of the bind array containing
+    /// the JSON text output by given ElementPtr::toJSON().
+    ///
+    /// @param value ElementPtr containing Element tree to add.
+    /// @throw DbOperationError if value is NULL.
+    void add(const data::ElementPtr& value);
+
     /// @brief Dumps the contents of the array to a string.
     /// @return std::string containing the dump
     std::string toText() const;
@@ -685,6 +695,17 @@ public:
     static void getColumnValue(const PgSqlResult& r, const int row,
                                const size_t col, boost::posix_time::ptime& value);
 
+    /// @brief Fetches a JSON column as an ElementPtr.
+    ///
+    /// @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
+    ///
+    /// @throw  DbOperationError if the value cannot be fetched or is
+    /// invalid.
+    static void getColumnValue(const PgSqlResult& r, const int row,
+                               const size_t col, data::ElementPtr& value);
+
     /// @brief Converts a column in a row in a result set to a binary bytes
     ///
     /// Method is used to convert columns stored as BYTEA into a buffer of
index 1e0efc28ff9dbfe1bb073ee5452ac05f35e7f9d2..47ed578ee51358c84da499688ae2fb2a279c494e 100644 (file)
@@ -52,6 +52,7 @@ public:
         VARCHAR_COL,
         INET_COL,
         FLOAT_COL,
+        JSON_COL,
         NUM_BASIC_COLS
     };
     /// @brief Constructor
@@ -81,6 +82,7 @@ public:
         expectedColNames_[VARCHAR_COL] = "varchar_col";
         expectedColNames_[INET_COL] = "inet_col";
         expectedColNames_[FLOAT_COL] = "float_col";
+        expectedColNames_[JSON_COL] = "json_col";
 
         destroySchema();
         createSchema();
@@ -129,7 +131,8 @@ public:
             "    timestamp_col TIMESTAMP WITH TIME ZONE, "
             "    varchar_col VARCHAR(255), "
             "    inet_col INET, "
-            "    float_col FLOAT "
+            "    float_col FLOAT, "
+            "    json_col JSON "
             "); ";
 
         PgSqlResult r(PQexec(*conn_, sql));
@@ -213,7 +216,7 @@ public:
             "   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"
+            "   varchar_col, inet_col, float_col, json_col"
             " FROM basics";
 
         runSql(r, sql, PGRES_TUPLES_OK, line);
index 906b9c8d8c7bcf2e6268e8c5bd3c451682b85511..f3e98c3abbd2a2acca89e6ba971fe77f5e18c5fd 100644 (file)
@@ -20,6 +20,7 @@
 #include <vector>
 
 using namespace isc;
+using namespace isc::data;
 using namespace isc::db;
 using namespace isc::db::test;
 using namespace isc::util;
@@ -81,6 +82,22 @@ TEST(PsqlBindArray, addDataTest) {
 
         // Add an empty string.
         b.add(std::string(""));
+
+        // Add a v4 address.
+        asiolink::IOAddress addr4("192.168.1.1");
+        b.addInet4(addr4);
+
+        // Add a v6 address.
+        asiolink::IOAddress addr6("3001::1");
+        b.addInet6(addr6);
+
+        // Add a double.  Not sure how portably reliable this test will be.
+        double dbl = 2.0;
+        b.add(dbl);
+
+        // Add a JSON.
+        ElementPtr elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+        b.add(elems);
     }
 
     // We've left bind scope, everything should be intact.
@@ -96,7 +113,11 @@ TEST(PsqlBindArray, addDataTest) {
         "8 : \"3221360418\"\n"
         "9 : \"3001::1\"\n"
         "10 : 0x0102030405060708090a\n"
-        "11 : empty\n";
+        "11 : empty\n"
+        "12 : \"192.168.1.1\"\n"
+        "13 : \"3001::1\"\n"
+        "14 : \"2\"\n"
+        "15 : \"{ \"foo\": \"bar\" }\"\n";
 
     EXPECT_EQ(expected, b.toText());
 }
@@ -165,7 +186,6 @@ TEST(PsqlBindArray, addOptionalString) {
     EXPECT_EQ(expected, b.toText());
 }
 
-
 /// @brief Verifies the ability to add Optional booleans to
 /// the bind array.
 TEST(PsqlBindArray, addOptionalBool) {
@@ -1202,4 +1222,59 @@ TEST_F(PgSqlBasicsTest, floatTest) {
     ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, FLOAT_COL));
 }
 
+/// @brief Verify that we can read and write JSON columns.
+TEST_F(PgSqlBasicsTest, jsonTest) {
+    // Create a prepared statement for inserting a SMALLINT
+    const char* st_name = "json_insert";
+    PgSqlTaggedStatement statement[] = {
+        { 1, { OID_TEXT }, st_name,
+          "INSERT INTO BASICS (json_col) values (cast($1 as json))" }
+    };
+
+    ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+    // Build our reference list of reference values
+    std::vector<ElementPtr>elem_ptrs;
+    ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"one\":1 }")));
+    ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"two\":\"two\" }")));
+
+    // Insert a row for each reference value
+    PsqlBindArrayPtr bind_array;
+    PgSqlResultPtr r;
+    for (int i = 0; i < elem_ptrs.size(); ++i) {
+        bind_array.reset(new PsqlBindArray());
+        bind_array->add(elem_ptrs[i]);
+        RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+    }
+
+    // Fetch the newly inserted rows.
+    FETCH_ROWS(r, elem_ptrs.size());
+
+    // Iterate over the rows, verifying each value against its reference
+    int row = 0;
+    for ( ; row  < elem_ptrs.size(); ++row ) {
+        // Verify the column is not null.
+        ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, JSON_COL));
+
+        // Fetch and verify the column value
+        ElementPtr fetched_ptr;
+        ASSERT_NO_THROW_LOG(PgSqlExchange::getColumnValue(*r, row, JSON_COL, fetched_ptr));
+        EXPECT_TRUE(fetched_ptr->equals(*(elem_ptrs[row])));
+    }
+
+    // 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, JSON_COL));
+}
+
 }; // namespace