From 9f362147d354aa6bddcf04effdea3d637bbb803b Mon Sep 17 00:00:00 2001 From: indev Date: Thu, 8 Oct 2020 20:35:03 +0700 Subject: [PATCH 1/2] Add typed interface for KV store --- include/ppconsul/kv.h | 46 +++++++++++++ tests/kv/kv_tests.cpp | 151 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 191 insertions(+), 6 deletions(-) diff --git a/include/ppconsul/kv.h b/include/ppconsul/kv.h index 68e9f04..90525cc 100644 --- a/include/ppconsul/kv.h +++ b/include/ppconsul/kv.h @@ -9,6 +9,7 @@ #include "ppconsul/consul.h" #include "ppconsul/helpers.h" #include "ppconsul/types.h" +#include #include #include #include @@ -28,6 +29,14 @@ namespace ppconsul { namespace kv { std::string key; std::string value; std::string session; + + template + T get() const + { + if (!valid()) + throw std::runtime_error("KeyValue invalid"); + return boost::lexical_cast(value); + } }; namespace txn_ops { @@ -264,6 +273,9 @@ namespace ppconsul { namespace kv { template> std::string get(const std::string& key, const std::string& defaultValue, const Params&... params) const; + template> + T get(const std::string& key, const T& defaultValue, const Params&... params) const; + // Returns invalid KeyValue (i.e. !kv.valid()) if key does not exist. // Result contains both headers and data. // Allowed parameters: @@ -395,6 +407,12 @@ namespace ppconsul { namespace kv { throw UpdateError(key); } + template> + void set(const std::string& key, const T& value, const Params&... params) const + { + set(key, boost::lexical_cast(value), params...); + } + // Compare and set (CAS operation). Returns true if value was successfully set and false otherwise. // Allowed parameters: // - groups::put @@ -408,6 +426,12 @@ namespace ppconsul { namespace kv { kw::cas = expectedIndex, params...)); } + template> + bool compareSet(const std::string& key, uint64_t expectedIndex, const T& value, const Params&... params) const + { + return compareSet(key, expectedIndex, boost::lexical_cast(value), params...); + } + // Compare and erase (CAS operation). bool compareErase(const std::string& key, uint64_t expectedIndex) const { @@ -429,6 +453,12 @@ namespace ppconsul { namespace kv { kw::acquire = session, params...)); } + template> + bool lock(const std::string& key, const std::string& session, const T& value, const Params&... params) const + { + return lock(key, session, boost::lexical_cast(value), params...); + } + // Acquire the lock. Returns true if the lock is acquired and value is set, returns false otherwise. // Allowed parameters: // - groups::put @@ -442,6 +472,12 @@ namespace ppconsul { namespace kv { kw::release = session, params...)); } + template> + bool unlock(const std::string& key, const std::string& session, const T& value, const Params&... params) const + { + return unlock(key, session, value, params...); + } + // Perform a series of operations as a transaction. // A KeyValue element is appended to the result for each operation except for: // - txn_ops::Erase @@ -528,6 +564,16 @@ namespace ppconsul { namespace kv { return std::move(kv.data().value); } + template + T Kv::get(const std::string& key, const T& defaultValue, const Params&... params) const + { + const auto kv = item(withHeaders, key, params...); + if (!kv.data().valid()) + return defaultValue; + else + return kv.data().template get(); + } + template Response> Kv::items(WithHeaders, const std::string& keyPrefix, const Params&... params) const { diff --git a/tests/kv/kv_tests.cpp b/tests/kv/kv_tests.cpp index 693db04..bc61f7b 100644 --- a/tests/kv/kv_tests.cpp +++ b/tests/kv/kv_tests.cpp @@ -11,15 +11,38 @@ #include "test_consul.h" #include #include +#include using namespace ppconsul::kv; using ppconsul::StringList; - namespace { auto const Non_Existing_Key = "6DD1E923-71E6-4448-A0B7-57B5F32690E7"; + + struct LexicalCastable + { + int number = 0; + std::string word; + }; + + std::ostream & operator<<(std::ostream & out, const LexicalCastable& value) + { + return out << value.number << " " << value.word; + } + + std::istream & operator>>(std::istream & in, LexicalCastable& value) + { + in >> value.number; + if((in.flags() & std::ios_base::skipws) == 0) + { + char whitespace; + in >> whitespace; + } + return in >> value.word; + } + } TEST_CASE("kv.invalid KeyValue", "[consul][kv]") @@ -60,6 +83,29 @@ TEST_CASE("kv.valid KeyValue", "[consul][kv]") CHECK(v.value == "some value"); CHECK(v.session == "some session"); } + +TEST_CASE("kv.get KeyValue", "[consul][kv]") +{ + KeyValue v; + v.modifyIndex = 42; + REQUIRE(v.valid()); + + v.value = "-11"; + CHECK(v.get() == -11); + + v.value = "4.20"; + CHECK(v.get() == 4.20f); + + v.value = "1"; + CHECK(v.get()); + + v.value = "0"; + CHECK(!v.get()); + + v.value = "123 lexical_castable"; + CHECK(v.get().number == 123); + CHECK(v.get().word == "lexical_castable"); +} TEST_CASE("kv.erase and count", "[consul][kv]") { @@ -131,8 +177,10 @@ TEST_CASE("kv.get", "[consul][kv][headers]") kv.set("key3", "value3"); kv.set("other/Key1", "other/Value1"); kv.set("other/Key2", "other/Value2"); + kv.set("nonstring/Key1", 111111); + kv.set("nonstring/Key2", LexicalCastable{123, "lexical_castable"}); - REQUIRE(kv.size() == 5); + REQUIRE(kv.size() == 7); SECTION("valid") { @@ -141,6 +189,8 @@ TEST_CASE("kv.get", "[consul][kv][headers]") CHECK(kv.item("key3").valid()); CHECK(kv.item("other/Key1").valid()); CHECK(kv.item("other/Key2").valid()); + CHECK(kv.item("nonstring/Key1").valid()); + CHECK(kv.item("nonstring/Key2").valid()); } SECTION("get nonexisting item") @@ -176,6 +226,33 @@ TEST_CASE("kv.get", "[consul][kv][headers]") CHECK(kv.get("key1", "some default value") == "value1"); } + SECTION("get (nonstring)") + { + KeyValue v = kv.item("nonstring/Key2"); + + REQUIRE(v.valid()); + + CHECK(v.createIndex); + CHECK(v.modifyIndex); + CHECK(!v.lockIndex); + CHECK(!v.flags); + CHECK(v.key == "nonstring/Key2"); + CHECK(v.value == "123 lexical_castable"); + CHECK(v.session == ""); + CHECK(v.get().number == 123); + CHECK(v.get().word == "lexical_castable"); + + CHECK(kv.get("nonstring/Key1", 0) == 111111); + CHECK(kv.get(Non_Existing_Key, 100) == 100); + + CHECK(kv.get(Non_Existing_Key, true)); + + CHECK(kv.get("nonstring/Key2", LexicalCastable{100, "default"}).number == 123); + CHECK(kv.get("nonstring/Key2", LexicalCastable{100, "default"}).word == "lexical_castable"); + CHECK(kv.get(Non_Existing_Key, LexicalCastable{100, "default"}).number == 100); + CHECK(kv.get(Non_Existing_Key, LexicalCastable{100, "default"}).word == "default"); + } + SECTION("get item with headers") { ppconsul::Response v0 = kv.item(ppconsul::withHeaders, Non_Existing_Key); @@ -208,8 +285,9 @@ TEST_CASE("kv.get", "[consul][kv][headers]") SECTION("get items") { CHECK(kv.items(Non_Existing_Key).size() == 0); - CHECK(kv.items().size() == 5); + CHECK(kv.items().size() == 7); CHECK(kv.items("other/Key").size() == 2); + CHECK(kv.items("nonstring/Key").size() == 2); std::vector v = kv.items("key"); @@ -271,8 +349,9 @@ TEST_CASE("kv.get", "[consul][kv][headers]") CHECK(kv.subKeys(Non_Existing_Key, "/") == StringList()); CHECK(kv.keys("key") == StringList({"key1", "key2", "key3"})); CHECK(kv.keys("other/Key") == StringList({ "other/Key1", "other/Key2" })); - CHECK(kv.subKeys("", "/") == StringList({ "key1", "key2", "key3", "other/" })); - CHECK(kv.subKeys("", "e") == StringList({ "ke", "othe" })); + CHECK(kv.keys("nonstring/Key") == StringList({ "nonstring/Key1", "nonstring/Key2" })); + CHECK(kv.subKeys("", "/") == StringList({ "key1", "key2", "key3", "nonstring/", "other/" })); + CHECK(kv.subKeys("", "e") == StringList({ "ke", "nonstring/Ke", "othe" })); } SECTION("get keys with headers") @@ -299,7 +378,7 @@ TEST_CASE("kv.get", "[consul][kv][headers]") CHECK(v2.headers().lastContact() == std::chrono::milliseconds(0)); ppconsul::Response v3 = kv.subKeys(ppconsul::withHeaders, "", "/"); - CHECK(v3.data() == StringList({ "key1", "key2", "key3", "other/" })); + CHECK(v3.data() == StringList({ "key1", "key2", "key3", "nonstring/", "other/" })); CHECK(v3.headers().valid()); CHECK(v3.headers().index()); CHECK(v3.headers().knownLeader()); @@ -374,6 +453,7 @@ TEST_CASE("kv.compareSet", "[consul][kv]") SECTION("init without cas") { kv.set("key1", "value1"); + kv.set("nonstring/Key1", 111111); { KeyValue v = kv.item("key1"); @@ -391,6 +471,10 @@ TEST_CASE("kv.compareSet", "[consul][kv]") REQUIRE(kv.item("key1").value == "value1"); REQUIRE(kv.item("key1").flags == 0); + REQUIRE(kv.item("nonstring/Key1").valid()); + REQUIRE(kv.item("nonstring/Key1").get() == 111111); + REQUIRE(kv.item("nonstring/Key1").flags == 0); + SECTION("change with cas wrong") { REQUIRE(!kv.compareSet("key1", 0, "value2")); @@ -399,6 +483,14 @@ TEST_CASE("kv.compareSet", "[consul][kv]") CHECK(kv.item("key1").flags == 0); } + SECTION("change with cas wrong (nonstring)") + { + REQUIRE(!kv.compareSet("nonstring/Key1", 0, 222222)); + CHECK(kv.item("nonstring/Key1").valid()); + CHECK(kv.item("nonstring/Key1").get() == 111111); + CHECK(kv.item("nonstring/Key1").flags == 0); + } + SECTION("change with cas right") { auto cas = kv.item("key1").modifyIndex; @@ -415,6 +507,23 @@ TEST_CASE("kv.compareSet", "[consul][kv]") CHECK(v.key == "key1"); CHECK(v.session == ""); } + + SECTION("change with cas right (nonstring)") + { + auto cas = kv.item("nonstring/Key1").modifyIndex; + + REQUIRE(kv.compareSet("nonstring/Key1", cas, 222222)); + + KeyValue v = kv.item("nonstring/Key1"); + REQUIRE(v.valid()); + CHECK(v.get() == 222222); + CHECK(v.createIndex); + CHECK(v.modifyIndex); + CHECK(!v.lockIndex); + CHECK(v.flags == 0); + CHECK(v.key == "nonstring/Key1"); + CHECK(v.session == ""); + } } SECTION("init with cas") @@ -803,6 +912,20 @@ TEST_CASE("kv.lock_unlock", "[consul][kv][session]") REQUIRE(v); REQUIRE(v.session == ""); REQUIRE(v.value == "test2"); + + REQUIRE(kv.lock("nonstring/Key1", session1, 222222)); + + v = kv.item("nonstring/Key1"); + REQUIRE(v); + REQUIRE(v.session == session1); + REQUIRE(v.get() == 222222); + + REQUIRE(kv.unlock("nonstring/Key1", session1, 333333)); + + v = kv.item("nonstring/Key1"); + REQUIRE(v); + REQUIRE(v.session == ""); + REQUIRE(v.get() == 333333); } SECTION("lock-unlock already locked") @@ -821,6 +944,22 @@ TEST_CASE("kv.lock_unlock", "[consul][kv][session]") REQUIRE(v); REQUIRE(v.session == session1); REQUIRE(v.value == "test1"); + + REQUIRE(kv.lock("nonstring/Key1", session1, 111111)); + REQUIRE(!kv.lock("nonstring/Key1", session2, 222222)); + + v = kv.item("nonstring/Key1"); + REQUIRE(v); + REQUIRE(v.session == session1); + REQUIRE(v.get() == 111111); + + REQUIRE(!kv.unlock("nonstring/Key1", session2, 333333)); + + v = kv.item("nonstring/Key1"); + REQUIRE(v); + REQUIRE(v.session == session1); + REQUIRE(v.get() == 111111); + REQUIRE(v.value == "test1"); } // TODO: add more tests From ca4d193e62e71a76ae3ec3ab0a5714bbee78da18 Mon Sep 17 00:00:00 2001 From: indev Date: Mon, 12 Oct 2020 14:19:01 +0700 Subject: [PATCH 2/2] Add missing lexical_cast for typed unlock method --- include/ppconsul/kv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ppconsul/kv.h b/include/ppconsul/kv.h index 90525cc..4018349 100644 --- a/include/ppconsul/kv.h +++ b/include/ppconsul/kv.h @@ -475,7 +475,7 @@ namespace ppconsul { namespace kv { template> bool unlock(const std::string& key, const std::string& session, const T& value, const Params&... params) const { - return unlock(key, session, value, params...); + return unlock(key, session, boost::lexical_cast(value), params...); } // Perform a series of operations as a transaction.