From: Marcin Siodelski Date: Fri, 1 Mar 2019 14:06:25 +0000 (+0100) Subject: [#489,!250] Extend MySQL backend to store floating point values. X-Git-Tag: 430-configure-location-of-datadir_base~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=108a86cbc5c8b223cff0ca3e0ff15c79d219aa0c;p=thirdparty%2Fkea.git [#489,!250] Extend MySQL backend to store floating point values. --- diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc index eb7cfd065a..5a2b4d0070 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc @@ -275,7 +275,10 @@ public: MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option: user_context MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // option: shared_network_name MySqlBinding::createInteger(), // option: pool_id - MySqlBinding::createTimestamp() //option: modification_ts + MySqlBinding::createTimestamp(), //option: modification_ts + MySqlBinding::createInteger(), // calculate_tee_times + MySqlBinding::createInteger(), // t1_percent + MySqlBinding::createInteger() // t2_percent }; uint64_t last_pool_id = 0; @@ -358,8 +361,7 @@ public: // match_client_id if (!out_bindings[8]->amNull()) { - last_subnet->setMatchClientId(static_cast - (out_bindings[8]->getInteger())); + last_subnet->setMatchClientId(out_bindings[8]->getBool()); } // modification_ts @@ -423,6 +425,21 @@ public: last_subnet->setContext(user_context); } + // calculate_tee_times + if (!out_bindings[49]->amNull()) { + last_subnet->setCalculateTeeTimes(out_bindings[49]->getBool()); + } + + // t1_percent + if (!out_bindings[50]->amNull()) { + last_subnet->setT1Percent(out_bindings[50]->getFloat()); + } + + // t2_percent + if (!out_bindings[51]->amNull()) { + last_subnet->setT2Percent(out_bindings[51]->getFloat()); + } + // Subnet ready. Add it to the list. subnets.push_back(last_subnet); } @@ -778,7 +795,10 @@ public: MySqlBinding::condCreateString(subnet->getSname()), shared_network_binding, createInputContextBinding(subnet), - createBinding(subnet->getValid()) + createBinding(subnet->getValid()), + MySqlBinding::condCreateBool(subnet->getCalculateTeeTimes()), + MySqlBinding::condCreateFloat(subnet->getT1Percent()), + MySqlBinding::condCreateFloat(subnet->getT2Percent()) }; MySqlTransaction transaction(conn_); @@ -1014,8 +1034,7 @@ public: // match_client_id if (!out_bindings[4]->amNull()) { - last_network->setMatchClientId(static_cast - (out_bindings[4]->getInteger())); + last_network->setMatchClientId(out_bindings[4]->getBool()); } // modification_ts @@ -1305,7 +1324,7 @@ public: createOptionValueBinding(option), MySqlBinding::condCreateString(option->formatted_value_), MySqlBinding::condCreateString(option->space_name_), - MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createBool(option->persistent_), MySqlBinding::createNull(), MySqlBinding::createNull(), MySqlBinding::createInteger(0), @@ -1367,7 +1386,7 @@ public: createOptionValueBinding(option), MySqlBinding::condCreateString(option->formatted_value_), MySqlBinding::condCreateString(option->space_name_), - MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createBool(option->persistent_), MySqlBinding::createNull(), MySqlBinding::createInteger(static_cast(subnet_id)), MySqlBinding::createInteger(1), @@ -1465,7 +1484,7 @@ public: createOptionValueBinding(option), MySqlBinding::condCreateString(option->formatted_value_), MySqlBinding::condCreateString(option->space_name_), - MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createBool(option->persistent_), MySqlBinding::createNull(), MySqlBinding::createNull(), MySqlBinding::createInteger(5), @@ -1531,7 +1550,7 @@ public: createOptionValueBinding(option), MySqlBinding::condCreateString(option->formatted_value_), MySqlBinding::condCreateString(option->space_name_), - MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createBool(option->persistent_), MySqlBinding::createNull(), MySqlBinding::createNull(), MySqlBinding::createInteger(4), @@ -1607,7 +1626,7 @@ public: "dhcp4" : option_def->getOptionSpaceName()), MySqlBinding::createInteger(static_cast(option_def->getType())), MySqlBinding::createTimestamp(option_def->getModificationTime()), - MySqlBinding::createInteger(static_cast(option_def->getArrayType())), + MySqlBinding::createBool(option_def->getArrayType()), MySqlBinding::createString(option_def->getEncapsulatedSpace()), record_types_binding, createInputContextBinding(option_def) @@ -2018,9 +2037,12 @@ TaggedStatementArray tagged_statements = { { " server_hostname," " shared_network_name," " user_context," - " valid_lifetime" + " valid_lifetime," + " calculate_tee_times," + " t1_percent," + " t2_percent" ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," - "?, ?, ?, ?, ?, ?, ?, ?)" }, + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" }, // Insert association of the subnet with a server. { MySqlConfigBackendDHCPv4Impl::INSERT_SUBNET4_SERVER, @@ -2101,7 +2123,10 @@ TaggedStatementArray tagged_statements = { { " server_hostname = ?," " shared_network_name = ?," " user_context = ?," - " valid_lifetime = ? " + " valid_lifetime = ?," + " calculate_tee_times = ?," + " t1_percent = ?," + " t2_percent = ? " "WHERE subnet_id = ?" }, // Update existing shared network. diff --git a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h index f23f2d79ff..f3c0a3a496 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h +++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h @@ -98,7 +98,10 @@ namespace { " o.user_context," \ " o.shared_network_name," \ " o.pool_id," \ - " o.modification_ts " \ + " o.modification_ts," \ + " s.calculate_tee_times," \ + " s.t1_percent," \ + " s.t2_percent " \ "FROM dhcp4_subnet AS s " \ "INNER JOIN dhcp4_subnet_server AS a " \ " ON s.subnet_id = a.subnet_id " \ diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc index 4bcf68b73f..01703835cc 100644 --- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc @@ -108,6 +108,9 @@ public: subnet->setSname("server-hostname"); subnet->setContext(user_context); subnet->setValid(555555); + subnet->setCalculateTeeTimes(true); + subnet->setT1Percent(0.345); + subnet->setT2Percent(0.444); Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"))); subnet->addPool(pool1); diff --git a/src/lib/mysql/mysql_binding.cc b/src/lib/mysql/mysql_binding.cc index fbde51c7fe..bd708ec1d0 100644 --- a/src/lib/mysql/mysql_binding.cc +++ b/src/lib/mysql/mysql_binding.cc @@ -64,6 +64,17 @@ MySqlBinding::getBlobOrDefault(const std::vector& default_value) const return (getBlob()); } +float +MySqlBinding::getFloat() const { + // It may seem a bit weird that we use getInteger template method + // for getting a floating point value. However, the getInteger method + // seems to be generic enough to support it. If we were to redo the + // API of this class we would probably introduce a getNumericValue + // method instead of getInteger. However, we already have getInteger + // used in many places so we should stick to it. + return (getInteger()); +} + ptime MySqlBinding::getTimestamp() const { // Make sure the binding type is timestamp. @@ -109,6 +120,22 @@ MySqlBinding::createBlob(const unsigned long length) { return (binding); } +MySqlBindingPtr +MySqlBinding::createFloat(const float value) { + // It may seem a bit weird that we use createInteger template method + // for setting a floating point value. However, the setInteger method + // seems to be generic enough to support it. If we were to redo the + // API of this class we would probably introduce a createNumericValue + // method instead of createInteger. However, we already have createInteger + // used in many places so we should stick to it. + return (createInteger(value)); +} + +MySqlBindingPtr +MySqlBinding::createBool(const bool value) { + return (createInteger(static_cast(value))); +} + MySqlBindingPtr MySqlBinding::condCreateBool(const util::Optional& value) { if (value.unspecified()) { diff --git a/src/lib/mysql/mysql_binding.h b/src/lib/mysql/mysql_binding.h index 82f388e894..f46f45f623 100644 --- a/src/lib/mysql/mysql_binding.h +++ b/src/lib/mysql/mysql_binding.h @@ -125,6 +125,13 @@ struct MySqlBindingTraits { static const bool am_unsigned = true; }; +template<> +struct MySqlBindingTraits { + static const enum_field_types column_type = MYSQL_TYPE_FLOAT; + static const size_t length = 4; + static const bool am_unsigned = false; +}; + /// @brief Forward declaration of @c MySqlBinding class. class MySqlBinding; @@ -272,6 +279,30 @@ public: return (getInteger()); } + /// @brief Returns float value held in the binding. + /// + /// Call @c MySqlBinding::amNull to verify that the value is not + /// null prior to calling this method. + /// + /// @throw InvalidOperation if the value is NULL or the binding + /// type does not match the template parameter. + /// + /// @return Float value. + float getFloat() const; + + /// @brief Returns boolean value held in the binding. + /// + /// Call @c MySqlBinding::amNull to verify that the value is not + /// null prior to calling this method. + /// + /// @throw InvalidOperation if the value is NULL or the binding + /// type is not uint8_t. + /// + /// @return Boolean value. + bool getBool() const { + return (static_cast(getInteger())); + } + /// @brief Returns timestamp value held in the binding. /// /// Call @c MySqlBinding::amNull to verify that the value is not @@ -395,6 +426,35 @@ public: return (value.unspecified() ? createNull() : createInteger(value.get())); } + /// @brief Creates binding having a float type for sending data. + /// + /// @param value Float value to be sent to the database. + /// + /// @return Pointer to the created binding. + static MySqlBindingPtr createFloat(const float value); + + /// @Conditionally creates binding of float type for sending data if + /// provided value is specified. + /// + /// @tparam T Floating point type to be converted to float. + /// + /// @param value Value to be stored in the database as float. + /// + /// @return Pointer to the created binding. + template + static MySqlBindingPtr condCreateFloat(const util::Optional& value) { + return (value.unspecified() ? createNull() : + createInteger (static_cast(value.get()))); + } + + /// @brief Creates binding having a bool type for sending data. + /// + /// @param value Boolean value to be sent to the database. + /// + /// @return Pointer to the created binding holding an @c uint8_t + /// value representing the boolean value. + static MySqlBindingPtr createBool(const bool value); + /// @brief Conditionally creates binding of @c uint8_t type representing /// a boolean value if provided value is specified. /// diff --git a/src/lib/mysql/tests/mysql_binding_unittest.cc b/src/lib/mysql/tests/mysql_binding_unittest.cc index 1a412cb816..f75f83e065 100644 --- a/src/lib/mysql/tests/mysql_binding_unittest.cc +++ b/src/lib/mysql/tests/mysql_binding_unittest.cc @@ -14,6 +14,7 @@ #include #include +using namespace isc; using namespace isc::asiolink; using namespace isc::data; using namespace isc::db; @@ -30,7 +31,8 @@ TEST(MySqlBindingTest, defaultString) { EXPECT_EQ("bar", binding->getStringOrDefault("foo")); } -// This test verifies that null binding is created for unspecified string. +// This test verifies that null binding is created for unspecified string +// and the string binding is created for a specified string. TEST(MySqlBindingTest, conditionalString) { auto binding = MySqlBinding::condCreateString(Optional()); EXPECT_TRUE(binding->amNull()); @@ -40,6 +42,19 @@ TEST(MySqlBindingTest, conditionalString) { EXPECT_EQ("foo", binding->getString()); } +// This test verifies that an error is thrown upon an attempt to use +// invalid accessor for a string binding. +TEST(MySqlBindingTest, stringTypeMismatch) { + auto binding = MySqlBinding::createString("foo"); + EXPECT_NO_THROW(static_cast(binding->getString())); + + EXPECT_THROW(static_cast(binding->getBlob()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getFloat()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBool()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getTimestamp()), InvalidOperation); +} + // This test verifies that null JSON is returned if the string binding // is null, JSON value is returned when string value is valid JSON and // that exception is thrown if the string is not a valid JSON. @@ -68,6 +83,20 @@ TEST(MySqlBindingTest, defaultBlob) { EXPECT_EQ(blob, binding->getBlobOrDefault(default_blob)); } +// This test verifies that an error is thrown upon an attempt to use +// invalid accessor for a blob binding. +TEST(MySqlBindingTest, blobTypeMismatch) { + std::vector blob(10, 1); + auto binding = MySqlBinding::createBlob(blob.begin(), blob.end()); + EXPECT_NO_THROW(static_cast(binding->getBlob())); + + EXPECT_THROW(static_cast(binding->getString()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getFloat()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBool()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getTimestamp()), InvalidOperation); +} + // This test verifies that default number is returned if binding is null. TEST(MySqlBindingTest, defaultInteger) { auto binding = MySqlBinding::createNull(); @@ -77,7 +106,8 @@ TEST(MySqlBindingTest, defaultInteger) { EXPECT_EQ(1024, binding->getIntegerOrDefault(123)); } -// This test verifies that null binding is created for unspecified number. +// This test verifies that null binding is created for unspecified number +// and the integer binding is created for a specified number. TEST(MySqlBindingTest, conditionalInteger) { auto binding = MySqlBinding::condCreateInteger(Optional()); EXPECT_TRUE(binding->amNull()); @@ -87,7 +117,49 @@ TEST(MySqlBindingTest, conditionalInteger) { EXPECT_EQ(1, binding->getInteger()); } +// This test verifies that an error is thrown upon an attempt to use +// invalid accessor for an integer binding. +TEST(MySqlBindingTest, integerTypeMismatch) { + auto binding = MySqlBinding::createInteger(123); + EXPECT_NO_THROW(static_cast(binding->getInteger())); + + EXPECT_THROW(static_cast(binding->getString()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBlob()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getFloat()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBool()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getTimestamp()), InvalidOperation); +} + +// This test verifies that null binding is created for unspecified floating +// point value and the float binding is created for the specified value. +TEST(MySqlBindingTest, conditionalFloat) { + auto binding = MySqlBinding::condCreateFloat(Optional()); + EXPECT_TRUE(binding->amNull()); + + binding = MySqlBinding::condCreateFloat(1.567f); + ASSERT_FALSE(binding->amNull()); + EXPECT_EQ(1.567f, binding->getFloat()); +} + +// This test verifies that an error is thrown upon an attempt to use +// invalid accessor for a float binding. +TEST(MySqlBindingTest, floatTypeMismatch) { + auto binding = MySqlBinding::createFloat(123.123f); + EXPECT_NO_THROW(static_cast(binding->getFloat())); + + EXPECT_THROW(static_cast(binding->getString()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBlob()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBool()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getTimestamp()), InvalidOperation); +} + // This test verifies that null binding is created for unspecified boolean +// value and the uint8_t binding is created for a specified boolean // value. TEST(MySqlBindingTest, conditionalBoolean) { auto binding = MySqlBinding::condCreateBool(Optional()); @@ -95,14 +167,30 @@ TEST(MySqlBindingTest, conditionalBoolean) { binding = MySqlBinding::condCreateBool(false); ASSERT_FALSE(binding->amNull()); - EXPECT_EQ(0, binding->getInteger()); + EXPECT_FALSE(binding->getBool()); binding = MySqlBinding::condCreateBool(true); ASSERT_FALSE(binding->amNull()); - EXPECT_NE(binding->getInteger(), 0); + EXPECT_TRUE(binding->getBool()); +} + +// This test verifies that an error is thrown upon an attempt to use +// invalid accessor for a float binding. +TEST(MySqlBindingTest, booleanTypeMismatch) { + auto binding = MySqlBinding::createBool(false); + EXPECT_NO_THROW(static_cast(binding->getBool())); + EXPECT_NO_THROW(static_cast(binding->getInteger())); + + EXPECT_THROW(static_cast(binding->getString()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getBlob()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getInteger()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getFloat()), InvalidOperation); + EXPECT_THROW(static_cast(binding->getTimestamp()), InvalidOperation); } -// This test verifies that null binding is created for unspecified address. +// This test verifies that null binding is created for unspecified address +// and the uint32_t binding is created for the specified address. TEST(MySqlBindingTest, conditionalIPv4Address) { auto binding = MySqlBinding::condCreateIPv4Address(Optional()); EXPECT_TRUE(binding->amNull()); diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql index d8076313e5..b88dee9579 100644 --- a/src/share/database/scripts/mysql/dhcpdb_create.mysql +++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql @@ -1343,24 +1343,45 @@ ALTER TABLE dhcp6_options MODIFY COLUMN modification_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE dhcp4_subnet + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp4_subnet MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; ALTER TABLE dhcp4_subnet MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL; +ALTER TABLE dhcp4_shared_network + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp4_shared_network MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; ALTER TABLE dhcp4_shared_network MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL; +ALTER TABLE dhcp6_subnet + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp6_subnet MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; +ALTER TABLE dhcp6_shared_network + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp6_shared_network MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; + -- ----------------------------------------------------- -- Make sure that constraints on the 7.0 schema tables -- have appropriate referential actions. All tables diff --git a/src/share/database/scripts/mysql/upgrade_7.0_to_8.0.sh.in b/src/share/database/scripts/mysql/upgrade_7.0_to_8.0.sh.in index b3516d5567..3be2a7bad0 100644 --- a/src/share/database/scripts/mysql/upgrade_7.0_to_8.0.sh.in +++ b/src/share/database/scripts/mysql/upgrade_7.0_to_8.0.sh.in @@ -26,21 +26,41 @@ ALTER TABLE dhcp6_options MODIFY COLUMN modification_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE dhcp4_subnet + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp4_subnet MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; ALTER TABLE dhcp4_subnet MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL; +ALTER TABLE dhcp4_shared_network + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp4_shared_network MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; ALTER TABLE dhcp4_shared_network MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL; +ALTER TABLE dhcp6_subnet + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp6_subnet MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL; +ALTER TABLE dhcp6_shared_network + ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL, + ADD COLUMN t1_percent FLOAT DEFAULT NULL, + ADD COLUMN t2_percent FLOAT DEFAULT NULL; + ALTER TABLE dhcp6_shared_network MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;