add tests, prefix_range

This commit is contained in:
bert hubert 2018-12-28 16:33:20 +01:00
parent 59b3b602fa
commit b3eb2a03cb
5 changed files with 193 additions and 8 deletions

View File

@ -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

View File

@ -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<typename T>
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<typename KeyType>
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<std::string>(), d_t);
}
else
serFromString(d_id.get<std::string>(), 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<std::string>(), d_t);
}
std::function<bool(const MDBOutVal&)> 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<std::string>().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<int N>
std::pair<iter_t,eiter_t> prefix_range(const typename std::tuple_element<N, tuple_t>::type::type& key)
{
typename Parent::cursor_t cursor = d_parent.d_txn->getCursor(std::get<N>(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;
};

42
lmdb-view.cc Normal file
View File

@ -0,0 +1,42 @@
#include "lmdb-safe.hh"
#include <iostream>
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<string>();
if(key.d_mdbval.mv_size == 4)
cout << " " << key.get<uint32_t>();
cout<<": " << val.get<std::string>();
cout << "\n";
++count;
}
cout <<count<<endl;
}
int main(int argc, char** argv)
{
MDBEnv env(argv[1], MDB_RDONLY | MDB_NOSUBDIR, 0600);
auto main = env.openDB("", 0);
auto txn = env.getROTransaction();
auto cursor = txn.getCursor(main);
MDBOutVal key, val;
if(cursor.get(key, val, MDB_FIRST)) {
cout << "Database is empty" <<endl;
}
do {
cout << key.get<string>() << endl;
countDB(env, txn, key.get<string>());
} while(!cursor.get(key, val, MDB_NEXT));
}

30
test-basic.cc Normal file
View File

@ -0,0 +1,30 @@
#define CATCH_CONFIG_MAIN
#include <iostream>
#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<std::string>() == "hot");
txn.abort();
auto rotxn = env.getROTransaction();
REQUIRE(rotxn.get(main, "lmdb", out) == MDB_NOTFOUND);
}

61
typed-test.cc Normal file
View File

@ -0,0 +1,61 @@
#include <iostream>
#include "catch2/catch.hpp"
#include "lmdb-typed.hh"
using namespace std;
struct Member
{
std::string firstName;
std::string lastName;
};
template<class Archive>
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<Member,
index_on<Member, string, &Member::firstName>,
index_on<Member, string, &Member::lastName> > 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<std::string> names;
for(auto& iter = range.first; iter != range.second; ++iter) {
names.push_back(iter->firstName);
}
REQUIRE(names == vector<std::string>{"bert", "bertus"});
auto range2 = txn.prefix_range<0>("nosuchperson");
REQUIRE(!(range2.first == range2.second));
txn.abort();
}