From b3eb2a03cbfb3b23e056888d39e441f7b6feb105 Mon Sep 17 00:00:00 2001 From: bert hubert Date: Fri, 28 Dec 2018 16:33:20 +0100 Subject: [PATCH] add tests, prefix_range --- Makefile | 4 ++-- lmdb-typed.hh | 64 ++++++++++++++++++++++++++++++++++++++++++++++----- lmdb-view.cc | 42 +++++++++++++++++++++++++++++++++ test-basic.cc | 30 ++++++++++++++++++++++++ typed-test.cc | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 lmdb-view.cc create mode 100644 test-basic.cc create mode 100644 typed-test.cc diff --git a/Makefile b/Makefile index b412a6b..e0660c7 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ CXXFLAGS:= $(CXXVERSIONFLAG) -Wall -O2 -MMD -MP -ggdb -pthread $(INCLUDES) -Iext CFLAGS:= -Wall -O2 -MMD -MP -ggdb PROGRAMS = lmdb-various basic-example scale-example multi-example rel-example \ - resize-example typed-example test-basic lmdb-view + resize-example typed-example testrunner lmdb-view all: $(PROGRAMS) @@ -17,7 +17,7 @@ clean: -include *.d -test-basic: test-basic.o lmdb-safe.o +testrunner: test-basic.o typed-test.o lmdb-safe.o lmdb-typed.o -lboost_serialization g++ $(CXXVERSIONFLAG) $^ -o $@ -pthread $(LIBS) lmdb-various: lmdb-various.o lmdb-safe.o diff --git a/lmdb-typed.hh b/lmdb-typed.hh index 8870bfa..366a942 100644 --- a/lmdb-typed.hh +++ b/lmdb-typed.hh @@ -20,11 +20,12 @@ using std::endl; Everything should go into a namespace What is an error? What is an exception? could id=0 be magic? ('no such id') + yes Is boost the best serializer? + good default Perhaps use the separate index concept from multi_index - A dump function would be nice (typed) - including indexes perhaps get eiter to be of same type so for(auto& a : x) works + make it more value "like" with unique_ptr */ @@ -35,7 +36,7 @@ using std::endl; unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi); /** This is our serialization interface. - We could/should templatize this so you could pick something else + You can define your own serToString for your type if you know better */ template std::string serToString(const T& t) @@ -66,10 +67,10 @@ void serFromString(const std::string& str, T& ret) */ } +// mini-serializer for keys. Again, you can override template std::string keyConv(const KeyType& t); - template<> inline std::string keyConv(const std::string& t) { @@ -276,6 +277,32 @@ public: d_on_index(on_index), // is this an iterator on main database or on index? d_one_key(one_key), // should we stop at end of key? (equal range) d_end(end) + { + if(d_end) + return; + d_prefix.clear(); + + if(d_cursor.get(d_key, d_id, MDB_GET_CURRENT)) { + d_end = true; + return; + } + + if(d_on_index) { + if(d_parent->d_txn->get(d_parent->d_parent->d_main, d_id, d_data)) + throw std::runtime_error("Missing id in constructor"); + serFromString(d_data.get(), d_t); + } + else + serFromString(d_id.get(), d_t); + } + + explicit iter_t(Parent* parent, typename Parent::cursor_t&& cursor, const std::string& prefix) : + d_parent(parent), + d_cursor(std::move(cursor)), + d_on_index(true), // is this an iterator on main database or on index? + d_one_key(false), + d_prefix(prefix), + d_end(false) { if(d_end) return; @@ -293,6 +320,7 @@ public: else serFromString(d_id.get(), d_t); } + std::function filter; void del() @@ -300,12 +328,12 @@ public: d_cursor.del(); } - bool operator!=(const eiter_t& rhs) + bool operator!=(const eiter_t& rhs) const { return !d_end; } - bool operator==(const eiter_t& rhs) + bool operator==(const eiter_t& rhs) const { return d_end; } @@ -333,6 +361,9 @@ public: else if(rc) { throw std::runtime_error("in genoperator, " + std::string(mdb_strerror(rc))); } + else if(!d_prefix.empty() && d_key.get().rfind(d_prefix, 0)!=0) { + d_end = true; + } else { if(d_on_index) { if(d_parent->d_txn->get(d_parent->d_parent->d_main, d_id, data)) @@ -378,6 +409,7 @@ public: MDBOutVal d_key{0,0}, d_data{0,0}, d_id{0,0}; bool d_on_index; bool d_one_key; + std::string d_prefix; bool d_end{false}; T d_t; }; @@ -479,6 +511,26 @@ public: return {iter_t{&d_parent, std::move(cursor), true, true}, eiter_t()}; }; + //! equal range - could possibly be expressed through genfind + template + std::pair prefix_range(const typename std::tuple_element::type::type& key) + { + typename Parent::cursor_t cursor = d_parent.d_txn->getCursor(std::get(d_parent.d_parent->d_tuple).d_idx); + + std::string keyString=keyConv(key); + MDBInVal in(keyString); + MDBOutVal out, id; + out.d_mdbval = in.d_mdbval; + + if(cursor.get(out, id, MDB_SET_RANGE)) { + // on_index, one_key, end + return {iter_t{&d_parent, std::move(cursor), true, true, true}, eiter_t()}; + } + + return {iter_t(&d_parent, std::move(cursor), keyString), eiter_t()}; + }; + + Parent& d_parent; }; diff --git a/lmdb-view.cc b/lmdb-view.cc new file mode 100644 index 0000000..434fa7c --- /dev/null +++ b/lmdb-view.cc @@ -0,0 +1,42 @@ +#include "lmdb-safe.hh" +#include + +using namespace std; + +void countDB(MDBEnv& env, MDBROTransaction& txn, const std::string& dbname) +{ + auto db = txn.openDB(dbname, 0); + auto cursor = txn.getCursor(db); + uint32_t count = 0; + MDBOutVal key, val; + while(!cursor.get(key, val, count ? MDB_NEXT : MDB_FIRST)) { + cout << key.get(); + if(key.d_mdbval.mv_size == 4) + cout << " " << key.get(); + cout<<": " << val.get(); + cout << "\n"; + ++count; + + } + cout <() << endl; + countDB(env, txn, key.get()); + } while(!cursor.get(key, val, MDB_NEXT)); + + +} diff --git a/test-basic.cc b/test-basic.cc new file mode 100644 index 0000000..4da1a7c --- /dev/null +++ b/test-basic.cc @@ -0,0 +1,30 @@ +#define CATCH_CONFIG_MAIN + +#include +#include "catch2/catch.hpp" +#include "lmdb-safe.hh" + +using namespace std; + +TEST_CASE("Most basic tests", "[mostbasic]") { + unlink("./tests"); + + MDBEnv env("./tests", MDB_NOSUBDIR, 0600); + REQUIRE(1); + + MDBDbi main = env.openDB("", MDB_CREATE); + + auto txn = env.getRWTransaction(); + MDBOutVal out; + + REQUIRE(txn.get(main, "lmdb", out) == MDB_NOTFOUND); + + txn.put(main, "lmdb", "hot"); + + REQUIRE(txn.get(main, "lmdb", out) == 0); + REQUIRE(out.get() == "hot"); + txn.abort(); + + auto rotxn = env.getROTransaction(); + REQUIRE(rotxn.get(main, "lmdb", out) == MDB_NOTFOUND); +} diff --git a/typed-test.cc b/typed-test.cc new file mode 100644 index 0000000..49bbc13 --- /dev/null +++ b/typed-test.cc @@ -0,0 +1,61 @@ +#include + +#include "catch2/catch.hpp" +#include "lmdb-typed.hh" + +using namespace std; + +struct Member +{ + std::string firstName; + std::string lastName; +}; + +template +void serialize(Archive & ar, Member& g, const unsigned int version) +{ + ar & g.firstName & g.lastName; +} + + + +TEST_CASE("Basic typed tests", "[basictyped]") { + unlink("./tests-typed"); + typedef TypedDBI, + index_on > tmembers_t; + + auto tmembers = tmembers_t(getMDBEnv("./tests-typed", MDB_CREATE | MDB_NOSUBDIR, 0600), "members"); + + REQUIRE(1); + + auto txn = tmembers.getRWTransaction(); + Member m{"bert", "hubert"}; + txn.put(m); + m.firstName="bertus"; + m.lastName = "testperson"; + txn.put(m); + + m.firstName = "other"; + txn.put(m); + + Member out; + REQUIRE(txn.get(1,out)); + REQUIRE(out.firstName == "bert"); + REQUIRE(txn.get(2,out)); + REQUIRE(out.lastName == "testperson"); + + REQUIRE(!txn.get(4,out)); + + auto range = txn.prefix_range<0>("bert"); + vector names; + for(auto& iter = range.first; iter != range.second; ++iter) { + names.push_back(iter->firstName); + } + REQUIRE(names == vector{"bert", "bertus"}); + + auto range2 = txn.prefix_range<0>("nosuchperson"); + REQUIRE(!(range2.first == range2.second)); + + txn.abort(); +}