add a lot of comments for future ahu
This commit is contained in:
parent
bb981d6d31
commit
654e782b5f
|
@ -1,4 +1,6 @@
|
||||||
#include "lmdb-typed.hh"
|
#include "lmdb-typed.hh"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
|
unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
|
||||||
|
@ -44,7 +46,8 @@ struct compound
|
||||||
std::string operator()(const DNSResourceRecord& rr)
|
std::string operator()(const DNSResourceRecord& rr)
|
||||||
{
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
ret.assign((char*)&rr.domain_id, 4);
|
uint32_t id = htonl(rr.domain_id);
|
||||||
|
ret.assign((char*)&id, 4);
|
||||||
ret.append(rr.ordername);
|
ret.append(rr.ordername);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,7 @@ int main()
|
||||||
}
|
}
|
||||||
cout<<"Iterating over name 'powerdns.com': "<<endl;
|
cout<<"Iterating over name 'powerdns.com': "<<endl;
|
||||||
auto range = rotxn.equal_range<0>("powerdns.com");
|
auto range = rotxn.equal_range<0>("powerdns.com");
|
||||||
for(auto& iter = range.first; iter != range.second; ++iter)
|
for(auto iter = std::move(range.first); iter != range.second; ++iter)
|
||||||
{
|
{
|
||||||
cout << iter->qname << " " << iter->qtype << " " <<iter->content <<endl;
|
cout << iter->qname << " " << iter->qtype << " " <<iter->content <<endl;
|
||||||
}
|
}
|
||||||
|
@ -168,13 +171,13 @@ int main()
|
||||||
}
|
}
|
||||||
|
|
||||||
compound c;
|
compound c;
|
||||||
rr3.ordername = "www";
|
rr3.ordername = "vvv";
|
||||||
rr3.domain_id = 10;
|
rr3.domain_id = 10;
|
||||||
auto iter = txn.find<2>(c(rr3));
|
auto iter = txn.lower_bound<2>(c(rr3));
|
||||||
cout <<"Found using compound index: "<<iter->qname<< " # " <<iter->ordername<<endl;
|
cout <<"Found for '"<<rr3.ordername<<"' using compound index: "<<iter->qname<< " # '" <<iter->ordername<< "'"<<endl;
|
||||||
for(int n =0 ; n < 4; ++n) {
|
for(int n =0 ; n < 4; ++n) {
|
||||||
--iter;
|
--iter;
|
||||||
cout <<"Found PREV using compound index: "<<iter->qname<< " # " <<iter->ordername<<endl;
|
cout <<"Found PREV using compound index: "<<iter->qname<< " # '" <<iter->ordername<<"'"<<endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
cout<<"Going to iterate over the name powerdns.com!"<<endl;
|
cout<<"Going to iterate over the name powerdns.com!"<<endl;
|
||||||
|
|
150
lmdb-typed.hh
150
lmdb-typed.hh
|
@ -20,22 +20,25 @@ using std::endl;
|
||||||
Everything should go into a namespace
|
Everything should go into a namespace
|
||||||
What is an error? What is an exception?
|
What is an error? What is an exception?
|
||||||
could id=0 be magic? ('no such id')
|
could id=0 be magic? ('no such id')
|
||||||
Composite keys (powerdns needs them)
|
|
||||||
functional keys
|
|
||||||
Insert? Put? Naming matters
|
Insert? Put? Naming matters
|
||||||
Is boost the best serializer?
|
Is boost the best serializer?
|
||||||
rename find to equal_range
|
|
||||||
Perhaps use the separate index concept from multi_index
|
Perhaps use the separate index concept from multi_index
|
||||||
A dump function would be nice (typed)
|
A dump function would be nice (typed)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/** Return the highest ID used in a database. Returns 0 for an empty DB.
|
||||||
|
This makes us start everything at ID=1, which might make it possible to
|
||||||
|
treat id 0 as special
|
||||||
|
*/
|
||||||
unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi);
|
unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi);
|
||||||
|
|
||||||
|
/** This is our serialization interface.
|
||||||
|
We could/should templatize this so you could pick something else
|
||||||
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::string serToString(const T& t)
|
std::string serToString(const T& t)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string serial_str;
|
std::string serial_str;
|
||||||
boost::iostreams::back_insert_device<std::string> inserter(serial_str);
|
boost::iostreams::back_insert_device<std::string> inserter(serial_str);
|
||||||
boost::iostreams::stream<boost::iostreams::back_insert_device<std::string> > s(inserter);
|
boost::iostreams::stream<boost::iostreams::back_insert_device<std::string> > s(inserter);
|
||||||
|
@ -54,47 +57,15 @@ void serFromString(const std::string& str, T& ret)
|
||||||
oi >> ret;
|
oi >> ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** This is a struct that implements index operations, but
|
||||||
|
only the operations that are broadcast to all indexes.
|
||||||
|
Specifically, to deal with databases with less than the maximum
|
||||||
|
number of interfaces, this only includes calls that should be
|
||||||
|
ignored for empty indexes.
|
||||||
|
|
||||||
/* This is for storing a struct that has to be found using several
|
this only needs methods that must happen for all indexes at once
|
||||||
of its fields.
|
so specifically, not size<t> or get<t>, people ask for those themselves, and
|
||||||
|
should no do that on indexes that don't exist */
|
||||||
We want a typed lmdb database that we can only:
|
|
||||||
* insert such structs
|
|
||||||
* remove them
|
|
||||||
* mutate them
|
|
||||||
|
|
||||||
All while maintaining indexes on insert, removal and mutation.
|
|
||||||
|
|
||||||
struct DNSResourceRecord
|
|
||||||
{
|
|
||||||
string qname; // index
|
|
||||||
string qtype;
|
|
||||||
uint32_t domain_id; // index
|
|
||||||
string content;
|
|
||||||
string ordername; // index
|
|
||||||
bool auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedDBI<DNSResourceRecord,
|
|
||||||
DNSResourceRecord::qname,
|
|
||||||
DNSResourceRecord::domain_id,
|
|
||||||
DNSResourceRecord::ordername> tdbi;
|
|
||||||
|
|
||||||
DNSResourceRecord rr;
|
|
||||||
uint32_t id = tdbi.insert(rr); // inserts, creates three index items
|
|
||||||
|
|
||||||
tdbi.modify(id, [](auto& rr) { rr.auth=false; });
|
|
||||||
|
|
||||||
DNSResourceRecord retrr;
|
|
||||||
uint32_t id = tdbi.get<1>(qname, retrr);
|
|
||||||
|
|
||||||
// this checks for changes and updates indexes if need be
|
|
||||||
tdbi.modify(id, [](auto& rr) { rr.ordername="blah"; });
|
|
||||||
*/
|
|
||||||
|
|
||||||
// this only needs methods that must happen for all indexes at once
|
|
||||||
// so specifically, not size<t> or get<t>, people ask for those themselves, and
|
|
||||||
// should no do that on indexes that don't exist
|
|
||||||
|
|
||||||
template<class Class,typename Type, typename Parent>
|
template<class Class,typename Type, typename Parent>
|
||||||
struct LMDBIndexOps
|
struct LMDBIndexOps
|
||||||
|
@ -118,6 +89,8 @@ struct LMDBIndexOps
|
||||||
Parent* d_parent;
|
Parent* d_parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** This is an index on a field in a struct, it derives from the LMDBIndexOps */
|
||||||
|
|
||||||
template<class Class,typename Type,Type Class::*PtrToMember>
|
template<class Class,typename Type,Type Class::*PtrToMember>
|
||||||
struct index_on : LMDBIndexOps<Class, Type, index_on<Class, Type, PtrToMember>>
|
struct index_on : LMDBIndexOps<Class, Type, index_on<Class, Type, PtrToMember>>
|
||||||
{
|
{
|
||||||
|
@ -131,6 +104,7 @@ struct index_on : LMDBIndexOps<Class, Type, index_on<Class, Type, PtrToMember>>
|
||||||
typedef Type type;
|
typedef Type type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** This is a calculated index */
|
||||||
template<class Class, typename Type, class Func>
|
template<class Class, typename Type, class Func>
|
||||||
struct index_on_function : LMDBIndexOps<Class, Type, index_on_function<Class, Type, Func> >
|
struct index_on_function : LMDBIndexOps<Class, Type, index_on_function<Class, Type, Func> >
|
||||||
{
|
{
|
||||||
|
@ -145,6 +119,7 @@ struct index_on_function : LMDBIndexOps<Class, Type, index_on_function<Class, Ty
|
||||||
typedef Type type;
|
typedef Type type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** nop index, so we can fill our N indexes, even if you don't use them all */
|
||||||
struct nullindex_t
|
struct nullindex_t
|
||||||
{
|
{
|
||||||
template<typename Class>
|
template<typename Class>
|
||||||
|
@ -161,6 +136,7 @@ struct nullindex_t
|
||||||
typedef uint32_t type; // dummy
|
typedef uint32_t type; // dummy
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** The main class. Templatized only on the indexes and typename right now */
|
||||||
template<typename T, class I1=nullindex_t, class I2=nullindex_t, class I3 = nullindex_t, class I4 = nullindex_t>
|
template<typename T, class I1=nullindex_t, class I2=nullindex_t, class I3 = nullindex_t, class I4 = nullindex_t>
|
||||||
class TypedDBI
|
class TypedDBI
|
||||||
{
|
{
|
||||||
|
@ -169,7 +145,10 @@ public:
|
||||||
: d_env(env), d_name(name)
|
: d_env(env), d_name(name)
|
||||||
{
|
{
|
||||||
d_main = d_env->openDB(name, MDB_CREATE | MDB_INTEGERKEY);
|
d_main = d_env->openDB(name, MDB_CREATE | MDB_INTEGERKEY);
|
||||||
|
|
||||||
|
// now you might be tempted to go all MPL on this so we can get rid of the
|
||||||
|
// ugly macro. I'm not very receptive to that idea since it will make things
|
||||||
|
// EVEN uglier.
|
||||||
#define openMacro(N) std::get<N>(d_tuple).openDB(d_env, std::string(name)+"_"#N, MDB_CREATE | MDB_DUPFIXED | MDB_DUPSORT);
|
#define openMacro(N) std::get<N>(d_tuple).openDB(d_env, std::string(name)+"_"#N, MDB_CREATE | MDB_DUPFIXED | MDB_DUPSORT);
|
||||||
openMacro(0);
|
openMacro(0);
|
||||||
openMacro(1);
|
openMacro(1);
|
||||||
|
@ -179,15 +158,19 @@ public:
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we get a lot of our smarts from this tuple, it enables get<0> etc
|
||||||
typedef std::tuple<I1, I2, I3, I4> tuple_t;
|
typedef std::tuple<I1, I2, I3, I4> tuple_t;
|
||||||
tuple_t d_tuple;
|
tuple_t d_tuple;
|
||||||
|
|
||||||
|
// We support readonly and rw transactions. Here we put the Readonly operations
|
||||||
|
// which get sourced by both kinds of transactions
|
||||||
template<class Parent>
|
template<class Parent>
|
||||||
struct ReadonlyOperations
|
struct ReadonlyOperations
|
||||||
{
|
{
|
||||||
ReadonlyOperations(Parent& parent) : d_parent(parent)
|
ReadonlyOperations(Parent& parent) : d_parent(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
//! Number of entries in main database
|
||||||
uint32_t size()
|
uint32_t size()
|
||||||
{
|
{
|
||||||
MDB_stat stat;
|
MDB_stat stat;
|
||||||
|
@ -195,6 +178,7 @@ public:
|
||||||
return stat.ms_entries;
|
return stat.ms_entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Number of entries in the various indexes - should be the same
|
||||||
template<int N>
|
template<int N>
|
||||||
uint32_t size()
|
uint32_t size()
|
||||||
{
|
{
|
||||||
|
@ -202,7 +186,8 @@ public:
|
||||||
mdb_stat(d_parent.d_txn, std::get<N>(d_parent.d_parent->d_tuple).d_idx, &stat);
|
mdb_stat(d_parent.d_txn, std::get<N>(d_parent.d_parent->d_tuple).d_idx, &stat);
|
||||||
return stat.ms_entries;
|
return stat.ms_entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get item with id, from main table directly
|
||||||
bool get(uint32_t id, T& t)
|
bool get(uint32_t id, T& t)
|
||||||
{
|
{
|
||||||
MDBOutVal data;
|
MDBOutVal data;
|
||||||
|
@ -213,6 +198,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get item through index N, then via the main database
|
||||||
template<int N>
|
template<int N>
|
||||||
uint32_t get(const typename std::tuple_element<N, tuple_t>::type::type& key, T& out)
|
uint32_t get(const typename std::tuple_element<N, tuple_t>::type::type& key, T& out)
|
||||||
{
|
{
|
||||||
|
@ -222,7 +208,7 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Cardinality of index N
|
||||||
template<int N>
|
template<int N>
|
||||||
uint32_t cardinality()
|
uint32_t cardinality()
|
||||||
{
|
{
|
||||||
|
@ -237,21 +223,22 @@ public:
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! End iderator type
|
||||||
struct eiter_t
|
struct eiter_t
|
||||||
{};
|
{};
|
||||||
|
|
||||||
// can be on main, or on an index
|
// can be on main, or on an index
|
||||||
// when on main, return data directly
|
// when on main, return data directly
|
||||||
// when on index, indirect
|
// when on index, indirect
|
||||||
// we can be limited to one key, or iterate over entire table
|
// we can be limited to one key, or iterate over entire database
|
||||||
//
|
// iter requires you to put the cursor in the right place first!
|
||||||
struct iter_t
|
struct iter_t
|
||||||
{
|
{
|
||||||
explicit iter_t(Parent* parent, typename Parent::cursor_t&& cursor, bool on_index, bool one_key, bool end=false) :
|
explicit iter_t(Parent* parent, typename Parent::cursor_t&& cursor, bool on_index, bool one_key, bool end=false) :
|
||||||
d_parent(parent),
|
d_parent(parent),
|
||||||
d_cursor(std::move(cursor)),
|
d_cursor(std::move(cursor)),
|
||||||
d_on_index(on_index),
|
d_on_index(on_index), // is this an iterator on main database or on index?
|
||||||
d_one_key(one_key),
|
d_one_key(one_key), // should we stop at end of key? (equal range)
|
||||||
d_end(end)
|
d_end(end)
|
||||||
{
|
{
|
||||||
if(d_end)
|
if(d_end)
|
||||||
|
@ -291,7 +278,8 @@ public:
|
||||||
{
|
{
|
||||||
return &d_t;
|
return &d_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implements generic ++ or --
|
||||||
iter_t& genoperator(MDB_cursor_op dupop, MDB_cursor_op op)
|
iter_t& genoperator(MDB_cursor_op dupop, MDB_cursor_op op)
|
||||||
{
|
{
|
||||||
MDBOutVal data;
|
MDBOutVal data;
|
||||||
|
@ -320,7 +308,8 @@ public:
|
||||||
{
|
{
|
||||||
return genoperator(MDB_PREV_DUP, MDB_PREV);
|
return genoperator(MDB_PREV_DUP, MDB_PREV);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get ID this iterator points to
|
||||||
uint32_t getID()
|
uint32_t getID()
|
||||||
{
|
{
|
||||||
if(d_on_index)
|
if(d_on_index)
|
||||||
|
@ -328,9 +317,12 @@ public:
|
||||||
else
|
else
|
||||||
return d_key.get<uint32_t>();
|
return d_key.get<uint32_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transaction we are part of
|
||||||
Parent* d_parent;
|
Parent* d_parent;
|
||||||
typename Parent::cursor_t d_cursor;
|
typename Parent::cursor_t d_cursor;
|
||||||
|
|
||||||
|
// gcc complains if I don't zero-init these, which is worrying XXX
|
||||||
MDBOutVal d_key{0,0}, d_data{0,0}, d_id{0,0};
|
MDBOutVal d_key{0,0}, d_data{0,0}, d_id{0,0};
|
||||||
bool d_on_index;
|
bool d_on_index;
|
||||||
bool d_one_key;
|
bool d_one_key;
|
||||||
|
@ -367,9 +359,14 @@ public:
|
||||||
return iter_t{&d_parent, std::move(cursor), false, false};
|
return iter_t{&d_parent, std::move(cursor), false, false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
eiter_t end()
|
||||||
|
{
|
||||||
|
return eiter_t();
|
||||||
|
}
|
||||||
|
|
||||||
|
// basis for find, lower_bound
|
||||||
template<int N>
|
template<int N>
|
||||||
iter_t find(const typename std::tuple_element<N, tuple_t>::type::type& key)
|
iter_t genfind(const typename std::tuple_element<N, tuple_t>::type::type& key, MDB_cursor_op op)
|
||||||
{
|
{
|
||||||
typename Parent::cursor_t cursor = d_parent.d_txn.getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
|
typename Parent::cursor_t cursor = d_parent.d_txn.getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
|
||||||
|
|
||||||
|
@ -377,7 +374,7 @@ public:
|
||||||
MDBOutVal out, id;
|
MDBOutVal out, id;
|
||||||
out.d_mdbval = in.d_mdbval;
|
out.d_mdbval = in.d_mdbval;
|
||||||
|
|
||||||
if(cursor.get(out, id, MDB_SET)) {
|
if(cursor.get(out, id, op)) {
|
||||||
// on_index, one_key, end
|
// on_index, one_key, end
|
||||||
return iter_t{&d_parent, std::move(cursor), true, false, true};
|
return iter_t{&d_parent, std::move(cursor), true, false, true};
|
||||||
}
|
}
|
||||||
|
@ -385,6 +382,20 @@ public:
|
||||||
return iter_t{&d_parent, std::move(cursor), true, false};
|
return iter_t{&d_parent, std::move(cursor), true, false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<int N>
|
||||||
|
iter_t find(const typename std::tuple_element<N, tuple_t>::type::type& key)
|
||||||
|
{
|
||||||
|
return genfind<N>(key, MDB_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N>
|
||||||
|
iter_t lower_bound(const typename std::tuple_element<N, tuple_t>::type::type& key)
|
||||||
|
{
|
||||||
|
return genfind<N>(key, MDB_SET_RANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! equal range - could possibly be expressed through genfind
|
||||||
template<int N>
|
template<int N>
|
||||||
std::pair<iter_t,eiter_t> equal_range(const typename std::tuple_element<N, tuple_t>::type::type& key)
|
std::pair<iter_t,eiter_t> equal_range(const typename std::tuple_element<N, tuple_t>::type::type& key)
|
||||||
{
|
{
|
||||||
|
@ -402,12 +413,6 @@ public:
|
||||||
return {iter_t{&d_parent, std::move(cursor), true, true}, eiter_t()};
|
return {iter_t{&d_parent, std::move(cursor), true, true}, eiter_t()};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
eiter_t end()
|
|
||||||
{
|
|
||||||
return eiter_t();
|
|
||||||
}
|
|
||||||
|
|
||||||
Parent& d_parent;
|
Parent& d_parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -446,7 +451,7 @@ public:
|
||||||
rhs.d_parent = 0;
|
rhs.d_parent = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert something, with possibly a specific id
|
||||||
uint32_t insert(const T& t, uint32_t id=0)
|
uint32_t insert(const T& t, uint32_t id=0)
|
||||||
{
|
{
|
||||||
if(!id)
|
if(!id)
|
||||||
|
@ -463,6 +468,7 @@ public:
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modify an item 'in place', plus update indexes
|
||||||
void modify(uint32_t id, std::function<void(T&)> func)
|
void modify(uint32_t id, std::function<void(T&)> func)
|
||||||
{
|
{
|
||||||
T t;
|
T t;
|
||||||
|
@ -474,7 +480,7 @@ public:
|
||||||
insert(t, id);
|
insert(t, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! delete an item, and from indexes
|
||||||
void del(uint32_t id)
|
void del(uint32_t id)
|
||||||
{
|
{
|
||||||
T t;
|
T t;
|
||||||
|
@ -485,6 +491,7 @@ public:
|
||||||
clearIndex(id, t);
|
clearIndex(id, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! clear database & indexes (by hand!)
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
auto cursor = d_txn.getCursor(d_parent->d_main);
|
auto cursor = d_txn.getCursor(d_parent->d_main);
|
||||||
|
@ -498,22 +505,23 @@ public:
|
||||||
cursor.del();
|
cursor.del();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! commit this transaction
|
||||||
void commit()
|
void commit()
|
||||||
{
|
{
|
||||||
d_txn.commit();
|
d_txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! abort this transaction
|
||||||
void abort()
|
void abort()
|
||||||
{
|
{
|
||||||
d_txn.abort();
|
d_txn.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
typedef MDBRWCursor cursor_t;
|
typedef MDBRWCursor cursor_t;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// clear this ID from all indexes
|
||||||
void clearIndex(uint32_t id, const T& t)
|
void clearIndex(uint32_t id, const T& t)
|
||||||
{
|
{
|
||||||
#define clearMacro(N) std::get<N>(d_parent->d_tuple).del(d_txn, t, id);
|
#define clearMacro(N) std::get<N>(d_parent->d_tuple).del(d_txn, t, id);
|
||||||
|
@ -528,12 +536,14 @@ public:
|
||||||
TypedDBI* d_parent;
|
TypedDBI* d_parent;
|
||||||
MDBRWTransaction d_txn;
|
MDBRWTransaction d_txn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Get an RW transaction
|
||||||
RWTransaction getRWTransaction()
|
RWTransaction getRWTransaction()
|
||||||
{
|
{
|
||||||
return RWTransaction(this);
|
return RWTransaction(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get an RO transaction
|
||||||
ROTransaction getROTransaction()
|
ROTransaction getROTransaction()
|
||||||
{
|
{
|
||||||
return ROTransaction(this);
|
return ROTransaction(this);
|
||||||
|
|
Loading…
Reference in New Issue