Merge pull request #6 from horazont/feature/nested-transactions
Implement support for nesting transactions
This commit is contained in:
commit
469de3c0e2
24
README.md
24
README.md
|
@ -87,16 +87,16 @@ transaction is aborted automatically. To commit or abort, use `commit()` or
|
|||
`abort()`, after which going out of scope has no further effect.
|
||||
|
||||
```
|
||||
txn.put(dbi, "lmdb", "great");
|
||||
txn->put(dbi, "lmdb", "great");
|
||||
|
||||
string_view data;
|
||||
if(!txn.get(dbi, "lmdb", data)) {
|
||||
if(!txn->get(dbi, "lmdb", data)) {
|
||||
cout<< "Within RW transaction, found that lmdb = " << data <<endl;
|
||||
}
|
||||
else
|
||||
cout<<"Found nothing" << endl;
|
||||
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
```
|
||||
|
||||
LMDB is so fast because it does not copy data unless it really needs to.
|
||||
|
@ -129,8 +129,8 @@ For example, to store `double` values for 64 bit IDs:
|
|||
auto txn = env->getRWTransaction();
|
||||
uint64_t id=12345678901;
|
||||
double score=3.14159;
|
||||
txn.put(dbi, id, score);
|
||||
txn.commit();
|
||||
txn->put(dbi, id, score);
|
||||
txn->commit();
|
||||
```
|
||||
|
||||
Behind the scenes, the `id` and `score` values are wrapped by `MDBInVal`
|
||||
|
@ -142,7 +142,7 @@ works similary:
|
|||
uint64_t id=12345678901;
|
||||
MDBOutValue val;
|
||||
|
||||
txn.get(dbi, id, val);
|
||||
txn->get(dbi, id, val);
|
||||
|
||||
cout << "Score: " << val.get<double>() << "\n";
|
||||
```
|
||||
|
@ -170,10 +170,10 @@ struct Coordinate
|
|||
|
||||
C c{12.0, 13.0};
|
||||
|
||||
txn.put(dbi, MDBInVal::fromStruct(c), 12.0);
|
||||
txn->put(dbi, MDBInVal::fromStruct(c), 12.0);
|
||||
|
||||
MDBOutVal res;
|
||||
txn.get(dbi, MDBInVal::fromStruct(c), res);
|
||||
txn->get(dbi, MDBInVal::fromStruct(c), res);
|
||||
|
||||
auto c1 = res.get_struct<Coordinate>();
|
||||
```
|
||||
|
@ -193,7 +193,7 @@ calls to mdb.
|
|||
This is the usual opening sequence.
|
||||
|
||||
```
|
||||
auto cursor=txn.getCursor(dbi);
|
||||
auto cursor=txn->getCursor(dbi);
|
||||
MDBOutVal key, data;
|
||||
int count=0;
|
||||
cout<<"Counting records.. "; cout.flush();
|
||||
|
@ -212,7 +212,7 @@ records in under a second (!).
|
|||
|
||||
```
|
||||
cout<<"Clearing records.. "; cout.flush();
|
||||
mdb_drop(txn, dbi, 0); // clear records
|
||||
mdb_drop(*txn, dbi, 0); // clear records
|
||||
cout<<"Done!"<<endl;
|
||||
```
|
||||
|
||||
|
@ -224,11 +224,11 @@ native `mdb_drop` function which we did not wrap. This is possible because
|
|||
```
|
||||
cout << "Adding "<<limit<<" values .. "; cout.flush();
|
||||
for(unsigned int n = 0 ; n < limit; ++n) {
|
||||
txn.put(dbi, n, n);
|
||||
txn->put(dbi, n, n);
|
||||
}
|
||||
cout <<"Done!"<<endl;
|
||||
cout <<"Calling commit.. "; cout.flush();
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
cout<<"Done!"<<endl;
|
||||
```
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ void checkLMDB(MDBEnv* env, MDBDbi dbi)
|
|||
{
|
||||
auto rotxn = env->getROTransaction();
|
||||
MDBOutVal data;
|
||||
if(!rotxn.get(dbi, "lmdb", data)) {
|
||||
if(!rotxn->get(dbi, "lmdb", data)) {
|
||||
cout<< "Outside RW transaction, found that lmdb = " << data.get<string_view>() <<endl;
|
||||
}
|
||||
else
|
||||
|
@ -18,11 +18,11 @@ int main()
|
|||
auto dbi = env->openDB("example", MDB_CREATE);
|
||||
|
||||
auto txn = env->getRWTransaction();
|
||||
mdb_drop(txn, dbi, 0);
|
||||
txn.put(dbi, "lmdb", "great");
|
||||
mdb_drop(*txn, dbi, 0);
|
||||
txn->put(dbi, "lmdb", "great");
|
||||
|
||||
MDBOutVal data;
|
||||
if(!txn.get(dbi, "lmdb", data)) {
|
||||
if(!txn->get(dbi, "lmdb", data)) {
|
||||
cout<< "Within RW transaction, found that lmdb = " << data.get<string_view>() <<endl;
|
||||
}
|
||||
else
|
||||
|
@ -31,12 +31,12 @@ int main()
|
|||
std::thread elsewhere(checkLMDB, env.get(), dbi);
|
||||
elsewhere.join();
|
||||
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
|
||||
cout<<"Committed data"<<endl;
|
||||
|
||||
checkLMDB(env.get(), dbi);
|
||||
txn = env->getRWTransaction();
|
||||
mdb_drop(txn, dbi, 0);
|
||||
txn.commit();
|
||||
mdb_drop(*txn, dbi, 0);
|
||||
txn->commit();
|
||||
}
|
||||
|
|
188
lmdb-safe.cc
188
lmdb-safe.cc
|
@ -139,53 +139,107 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
|
|||
|
||||
if(!(envflags & MDB_RDONLY)) {
|
||||
auto rwt = getRWTransaction();
|
||||
MDBDbi ret = rwt.openDB(dbname, flags);
|
||||
rwt.commit();
|
||||
MDBDbi ret = rwt->openDB(dbname, flags);
|
||||
rwt->commit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
MDBDbi ret;
|
||||
{
|
||||
auto rwt = getROTransaction();
|
||||
ret = rwt.openDB(dbname, flags);
|
||||
ret = rwt->openDB(dbname, flags);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
MDBRWTransaction::MDBRWTransaction(MDBEnv* parent, int flags) : d_parent(parent)
|
||||
MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn):
|
||||
MDBROTransactionImpl(parent, txn)
|
||||
|
||||
{
|
||||
if(d_parent->getROTX() || d_parent->getRWTX())
|
||||
|
||||
}
|
||||
|
||||
MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, int flags)
|
||||
{
|
||||
MDB_txn *result;
|
||||
if(env->getROTX() || env->getRWTX())
|
||||
throw std::runtime_error("Duplicate RW transaction");
|
||||
|
||||
for(int tries =0 ; tries < 3; ++tries) { // it might happen twice, who knows
|
||||
if(int rc=mdb_txn_begin(d_parent->d_env, 0, flags, &d_txn)) {
|
||||
if(int rc=mdb_txn_begin(env->d_env, parent, flags, &result)) {
|
||||
if(rc == MDB_MAP_RESIZED && tries < 2) {
|
||||
// "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED.
|
||||
// call mdb_env_set_mapsize with a size of zero to adopt the new size."
|
||||
mdb_env_set_mapsize(d_parent->d_env, 0);
|
||||
mdb_env_set_mapsize(env->d_env, 0);
|
||||
continue;
|
||||
}
|
||||
throw std::runtime_error("Unable to start RW transaction: "+std::string(mdb_strerror(rc)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
d_parent->incRWTX();
|
||||
env->incRWTX();
|
||||
return result;
|
||||
}
|
||||
|
||||
MDBROTransaction::MDBROTransaction(MDBEnv* parent, int flags) : d_parent(parent)
|
||||
MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv* parent, int flags):
|
||||
MDBRWTransactionImpl(parent, openRWTransaction(parent, nullptr, flags))
|
||||
{
|
||||
if(d_parent->getRWTX())
|
||||
}
|
||||
|
||||
MDBRWTransactionImpl::~MDBRWTransactionImpl()
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void MDBRWTransactionImpl::commit()
|
||||
{
|
||||
closeRORWCursors();
|
||||
if (!d_txn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(int rc = mdb_txn_commit(d_txn)) {
|
||||
throw std::runtime_error("committing: " + std::string(mdb_strerror(rc)));
|
||||
}
|
||||
environment().decRWTX();
|
||||
d_txn = nullptr;
|
||||
}
|
||||
|
||||
void MDBRWTransactionImpl::abort()
|
||||
{
|
||||
closeRORWCursors();
|
||||
if (!d_txn) {
|
||||
return;
|
||||
}
|
||||
|
||||
mdb_txn_abort(d_txn);
|
||||
// prevent the RO destructor from cleaning up the transaction itself
|
||||
environment().decRWTX();
|
||||
d_txn = nullptr;
|
||||
}
|
||||
|
||||
MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn):
|
||||
d_parent(parent),
|
||||
d_cursors(),
|
||||
d_txn(txn)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MDB_txn *MDBROTransactionImpl::openROTransaction(MDBEnv *env, MDB_txn *parent, int flags)
|
||||
{
|
||||
if(env->getRWTX())
|
||||
throw std::runtime_error("Duplicate RO transaction");
|
||||
|
||||
/*
|
||||
A transaction and its cursors must only be used by a single thread, and a thread may only have a single transaction at a time. If MDB_NOTLS is in use, this does not apply to read-only transactions. */
|
||||
|
||||
MDB_txn *result = nullptr;
|
||||
for(int tries =0 ; tries < 3; ++tries) { // it might happen twice, who knows
|
||||
if(int rc=mdb_txn_begin(d_parent->d_env, 0, MDB_RDONLY | flags, &d_txn)) {
|
||||
if(int rc=mdb_txn_begin(env->d_env, parent, MDB_RDONLY | flags, &result)) {
|
||||
if(rc == MDB_MAP_RESIZED && tries < 2) {
|
||||
// "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED.
|
||||
// call mdb_env_set_mapsize with a size of zero to adopt the new size."
|
||||
mdb_env_set_mapsize(d_parent->d_env, 0);
|
||||
mdb_env_set_mapsize(env->d_env, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -193,43 +247,125 @@ MDBROTransaction::MDBROTransaction(MDBEnv* parent, int flags) : d_parent(parent)
|
|||
}
|
||||
break;
|
||||
}
|
||||
d_parent->incROTX();
|
||||
env->incROTX();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MDBROTransactionImpl::closeROCursors()
|
||||
{
|
||||
// we need to move the vector away to ensure that the cursors don’t mess with our iteration.
|
||||
std::vector<MDBROCursor*> buf;
|
||||
std::swap(d_cursors, buf);
|
||||
for (auto &cursor: buf) {
|
||||
cursor->close();
|
||||
}
|
||||
}
|
||||
|
||||
MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, int flags):
|
||||
MDBROTransactionImpl(parent, openROTransaction(parent, nullptr, flags))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MDBROTransactionImpl::~MDBROTransactionImpl()
|
||||
{
|
||||
// this is safe because C++ will not call overrides of virtual methods in destructors.
|
||||
commit();
|
||||
}
|
||||
|
||||
void MDBROTransactionImpl::abort()
|
||||
{
|
||||
closeROCursors();
|
||||
// if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
|
||||
if (d_txn) {
|
||||
d_parent->decROTX();
|
||||
mdb_txn_abort(d_txn); // this appears to work better than abort for r/o database opening
|
||||
d_txn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MDBROTransactionImpl::commit()
|
||||
{
|
||||
closeROCursors();
|
||||
// if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
|
||||
if (d_txn) {
|
||||
d_parent->decROTX();
|
||||
mdb_txn_commit(d_txn); // this appears to work better than abort for r/o database opening
|
||||
d_txn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MDBRWTransaction::clear(MDB_dbi dbi)
|
||||
void MDBRWTransactionImpl::clear(MDB_dbi dbi)
|
||||
{
|
||||
if(int rc = mdb_drop(d_txn, dbi, 0)) {
|
||||
throw runtime_error("Error clearing database: " + MDBError(rc));
|
||||
}
|
||||
}
|
||||
|
||||
MDBRWCursor MDBRWTransaction::getCursor(const MDBDbi& dbi)
|
||||
MDBRWCursor MDBRWTransactionImpl::getRWCursor(const MDBDbi& dbi)
|
||||
{
|
||||
return MDBRWCursor(this, dbi);
|
||||
MDB_cursor *cursor;
|
||||
int rc= mdb_cursor_open(d_txn, dbi, &cursor);
|
||||
if(rc) {
|
||||
throw std::runtime_error("Error creating RO cursor: "+std::string(mdb_strerror(rc)));
|
||||
}
|
||||
return MDBRWCursor(d_rw_cursors, cursor);
|
||||
}
|
||||
|
||||
MDBRWCursor MDBRWTransactionImpl::getCursor(const MDBDbi &dbi)
|
||||
{
|
||||
return getRWCursor(dbi);
|
||||
}
|
||||
|
||||
MDBRWTransaction MDBRWTransactionImpl::getRWTransaction()
|
||||
{
|
||||
MDB_txn *txn;
|
||||
if (int rc = mdb_txn_begin(environment(), *this, 0, &txn)) {
|
||||
throw std::runtime_error(std::string("failed to start child transaction: ")+mdb_strerror(rc));
|
||||
}
|
||||
// we need to increase the counter here because commit/abort on the child transaction will decrease it
|
||||
environment().incRWTX();
|
||||
return MDBRWTransaction(new MDBRWTransactionImpl(&environment(), txn));
|
||||
}
|
||||
|
||||
MDBROTransaction MDBRWTransactionImpl::getROTransaction()
|
||||
{
|
||||
return std::move(getRWTransaction());
|
||||
}
|
||||
|
||||
MDBROTransaction MDBEnv::getROTransaction()
|
||||
{
|
||||
return MDBROTransaction(this);
|
||||
return MDBROTransaction(new MDBROTransactionImpl(this));
|
||||
}
|
||||
MDBRWTransaction MDBEnv::getRWTransaction()
|
||||
{
|
||||
return MDBRWTransaction(this);
|
||||
return MDBRWTransaction(new MDBRWTransactionImpl(this));
|
||||
}
|
||||
|
||||
|
||||
void MDBRWTransaction::closeCursors()
|
||||
void MDBRWTransactionImpl::closeRWCursors()
|
||||
{
|
||||
for(auto& c : d_cursors)
|
||||
c->close();
|
||||
d_cursors.clear();
|
||||
decltype(d_rw_cursors) buf;
|
||||
std::swap(d_rw_cursors, buf);
|
||||
for (auto &cursor: buf) {
|
||||
cursor->close();
|
||||
}
|
||||
}
|
||||
|
||||
MDBROCursor MDBROTransaction::getCursor(const MDBDbi& dbi)
|
||||
MDBROCursor MDBROTransactionImpl::getCursor(const MDBDbi& dbi)
|
||||
{
|
||||
return MDBROCursor(this, dbi);
|
||||
return getROCursor(dbi);
|
||||
}
|
||||
|
||||
|
||||
MDBROCursor MDBROTransactionImpl::getROCursor(const MDBDbi &dbi)
|
||||
{
|
||||
MDB_cursor *cursor;
|
||||
int rc= mdb_cursor_open(d_txn, dbi, &cursor);
|
||||
if(rc) {
|
||||
throw std::runtime_error("Error creating RO cursor: "+std::string(mdb_strerror(rc)));
|
||||
}
|
||||
return MDBROCursor(d_cursors, cursor);
|
||||
}
|
||||
|
|
354
lmdb-safe.hh
354
lmdb-safe.hh
|
@ -9,6 +9,8 @@
|
|||
#include <string>
|
||||
#include <string.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// apple compiler somehow has string_view even in c++11!
|
||||
#if __cplusplus < 201703L && !defined(__APPLE__)
|
||||
|
@ -57,8 +59,11 @@ public:
|
|||
MDB_dbi d_dbi;
|
||||
};
|
||||
|
||||
class MDBRWTransaction;
|
||||
class MDBROTransaction;
|
||||
class MDBRWTransactionImpl;
|
||||
class MDBROTransactionImpl;
|
||||
|
||||
using MDBROTransaction = std::unique_ptr<MDBROTransactionImpl>;
|
||||
using MDBRWTransaction = std::unique_ptr<MDBRWTransactionImpl>;
|
||||
|
||||
class MDBEnv
|
||||
{
|
||||
|
@ -220,35 +225,36 @@ private:
|
|||
|
||||
class MDBROCursor;
|
||||
|
||||
class MDBROTransaction
|
||||
class MDBROTransactionImpl
|
||||
{
|
||||
protected:
|
||||
MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn);
|
||||
|
||||
private:
|
||||
static MDB_txn *openROTransaction(MDBEnv *env, MDB_txn *parent, int flags=0);
|
||||
|
||||
MDBEnv* d_parent;
|
||||
std::vector<MDBROCursor*> d_cursors;
|
||||
|
||||
protected:
|
||||
MDB_txn* d_txn;
|
||||
|
||||
void closeROCursors();
|
||||
|
||||
public:
|
||||
explicit MDBROTransaction(MDBEnv* parent, int flags=0);
|
||||
explicit MDBROTransactionImpl(MDBEnv* parent, int flags=0);
|
||||
|
||||
MDBROTransaction(MDBROTransaction&& rhs)
|
||||
{
|
||||
d_parent = rhs.d_parent;
|
||||
d_txn = rhs.d_txn;
|
||||
rhs.d_parent = 0;
|
||||
rhs.d_txn = 0;
|
||||
}
|
||||
MDBROTransactionImpl(const MDBROTransactionImpl& src) = delete;
|
||||
MDBROTransactionImpl &operator=(const MDBROTransactionImpl& src) = delete;
|
||||
|
||||
void reset()
|
||||
{
|
||||
// this does not free cursors
|
||||
mdb_txn_reset(d_txn);
|
||||
d_parent->decROTX();
|
||||
}
|
||||
// The move constructor/operator cannot be made safe due to Object Slicing with MDBRWTransaction.
|
||||
MDBROTransactionImpl(MDBROTransactionImpl&& rhs) = delete;
|
||||
MDBROTransactionImpl &operator=(MDBROTransactionImpl &&rhs) = delete;
|
||||
|
||||
void renew()
|
||||
{
|
||||
if(d_parent->getROTX())
|
||||
throw std::runtime_error("Duplicate RO transaction");
|
||||
if(int rc = mdb_txn_renew(d_txn))
|
||||
throw std::runtime_error("Renewing RO transaction: "+std::string(mdb_strerror(rc)));
|
||||
d_parent->incROTX();
|
||||
}
|
||||
|
||||
virtual ~MDBROTransactionImpl();
|
||||
|
||||
virtual void abort();
|
||||
virtual void commit();
|
||||
|
||||
int get(MDB_dbi dbi, const MDBInVal& key, MDBOutVal& val)
|
||||
{
|
||||
|
@ -280,22 +286,21 @@ public:
|
|||
}
|
||||
|
||||
MDBROCursor getCursor(const MDBDbi&);
|
||||
MDBROCursor getROCursor(const MDBDbi&);
|
||||
|
||||
~MDBROTransaction()
|
||||
{
|
||||
if(d_txn) {
|
||||
d_parent->decROTX();
|
||||
mdb_txn_commit(d_txn); // this appears to work better than abort for r/o database opening
|
||||
}
|
||||
}
|
||||
|
||||
operator MDB_txn*&()
|
||||
operator MDB_txn*()
|
||||
{
|
||||
return d_txn;
|
||||
}
|
||||
|
||||
MDBEnv* d_parent;
|
||||
MDB_txn* d_txn;
|
||||
|
||||
inline operator bool() const {
|
||||
return d_txn;
|
||||
}
|
||||
|
||||
inline MDBEnv &environment()
|
||||
{
|
||||
return *d_parent;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -304,12 +309,75 @@ public:
|
|||
"If the parent transaction commits, the cursor must not be used again."
|
||||
*/
|
||||
|
||||
template<class Transaction>
|
||||
template<class Transaction, class T>
|
||||
class MDBGenCursor
|
||||
{
|
||||
private:
|
||||
std::vector<T*> *d_registry;
|
||||
MDB_cursor* d_cursor;
|
||||
|
||||
public:
|
||||
MDBGenCursor():
|
||||
d_registry(nullptr),
|
||||
d_cursor(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MDBGenCursor(std::vector<T*> ®istry, MDB_cursor *cursor):
|
||||
d_registry(®istry),
|
||||
d_cursor(cursor)
|
||||
{
|
||||
registry.emplace_back(static_cast<T*>(this));
|
||||
}
|
||||
|
||||
private:
|
||||
void move_from(MDBGenCursor *src)
|
||||
{
|
||||
if (!d_registry) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = std::find(d_registry->begin(),
|
||||
d_registry->end(),
|
||||
src);
|
||||
if (iter != d_registry->end()) {
|
||||
*iter = static_cast<T*>(this);
|
||||
} else {
|
||||
d_registry->emplace_back(static_cast<T*>(this));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
MDBGenCursor(const MDBGenCursor &src) = delete;
|
||||
|
||||
MDBGenCursor(MDBGenCursor &&src) noexcept:
|
||||
d_registry(src.d_registry),
|
||||
d_cursor(src.d_cursor)
|
||||
{
|
||||
move_from(&src);
|
||||
src.d_registry = nullptr;
|
||||
src.d_cursor = nullptr;
|
||||
}
|
||||
|
||||
MDBGenCursor &operator=(const MDBGenCursor &src) = delete;
|
||||
|
||||
MDBGenCursor &operator=(MDBGenCursor &&src) noexcept
|
||||
{
|
||||
d_registry = src.d_registry;
|
||||
d_cursor = src.d_cursor;
|
||||
move_from(&src);
|
||||
src.d_registry = nullptr;
|
||||
src.d_cursor = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MDBGenCursor()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
public:
|
||||
MDBGenCursor(Transaction *t) : d_parent(t)
|
||||
{}
|
||||
int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
|
||||
{
|
||||
int rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
|
||||
|
@ -377,101 +445,78 @@ public:
|
|||
return currentlast(key, data, MDB_FIRST);
|
||||
}
|
||||
|
||||
operator MDB_cursor*&()
|
||||
operator MDB_cursor*()
|
||||
{
|
||||
return d_cursor;
|
||||
}
|
||||
|
||||
MDB_cursor* d_cursor;
|
||||
Transaction* d_parent;
|
||||
};
|
||||
|
||||
class MDBROCursor : public MDBGenCursor<MDBROTransaction>
|
||||
{
|
||||
public:
|
||||
MDBROCursor(MDBROTransaction* parent, const MDB_dbi& dbi) : MDBGenCursor<MDBROTransaction>(parent)
|
||||
operator bool() const
|
||||
{
|
||||
int rc= mdb_cursor_open(d_parent->d_txn, dbi, &d_cursor);
|
||||
if(rc) {
|
||||
throw std::runtime_error("Error creating RO cursor: "+std::string(mdb_strerror(rc)));
|
||||
}
|
||||
}
|
||||
MDBROCursor(MDBROCursor&& rhs) : MDBGenCursor<MDBROTransaction>(rhs.d_parent)
|
||||
{
|
||||
d_cursor = rhs.d_cursor;
|
||||
rhs.d_cursor=0;
|
||||
return d_cursor;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
mdb_cursor_close(d_cursor);
|
||||
d_cursor=0;
|
||||
}
|
||||
|
||||
~MDBROCursor()
|
||||
{
|
||||
if(d_cursor)
|
||||
if (d_registry) {
|
||||
auto iter = std::find(d_registry->begin(),
|
||||
d_registry->end(),
|
||||
static_cast<T*>(this));
|
||||
if (iter != d_registry->end()) {
|
||||
d_registry->erase(iter);
|
||||
}
|
||||
d_registry = nullptr;
|
||||
}
|
||||
if (d_cursor) {
|
||||
mdb_cursor_close(d_cursor);
|
||||
d_cursor = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MDBROCursor : public MDBGenCursor<MDBROTransactionImpl, MDBROCursor>
|
||||
{
|
||||
public:
|
||||
MDBROCursor() = default;
|
||||
using MDBGenCursor<MDBROTransactionImpl, MDBROCursor>::MDBGenCursor;
|
||||
MDBROCursor(const MDBROCursor &src) = delete;
|
||||
MDBROCursor(MDBROCursor &&src) = default;
|
||||
MDBROCursor &operator=(const MDBROCursor &src) = delete;
|
||||
MDBROCursor &operator=(MDBROCursor &&src) = default;
|
||||
~MDBROCursor() = default;
|
||||
|
||||
};
|
||||
|
||||
class MDBRWCursor;
|
||||
|
||||
class MDBRWTransaction
|
||||
class MDBRWTransactionImpl: public MDBROTransactionImpl
|
||||
{
|
||||
protected:
|
||||
MDBRWTransactionImpl(MDBEnv* parent, MDB_txn* txn);
|
||||
|
||||
private:
|
||||
static MDB_txn *openRWTransaction(MDBEnv* env, MDB_txn *parent, int flags);
|
||||
|
||||
private:
|
||||
std::vector<MDBRWCursor*> d_rw_cursors;
|
||||
|
||||
void closeRWCursors();
|
||||
inline void closeRORWCursors() {
|
||||
closeROCursors();
|
||||
closeRWCursors();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit MDBRWTransaction(MDBEnv* parent, int flags=0);
|
||||
explicit MDBRWTransactionImpl(MDBEnv* parent, int flags=0);
|
||||
|
||||
MDBRWTransaction(MDBRWTransaction&& rhs)
|
||||
{
|
||||
d_parent = rhs.d_parent;
|
||||
d_txn = rhs.d_txn;
|
||||
rhs.d_parent = 0;
|
||||
rhs.d_txn = 0;
|
||||
}
|
||||
MDBRWTransactionImpl(const MDBRWTransactionImpl& rhs) = delete;
|
||||
MDBRWTransactionImpl(MDBRWTransactionImpl&& rhs) = delete;
|
||||
MDBRWTransactionImpl &operator=(const MDBRWTransactionImpl& rhs) = delete;
|
||||
MDBRWTransactionImpl &operator=(MDBRWTransactionImpl&& rhs) = delete;
|
||||
|
||||
MDBRWTransaction& operator=(MDBRWTransaction&& rhs)
|
||||
{
|
||||
if(d_txn)
|
||||
abort();
|
||||
|
||||
d_parent = rhs.d_parent;
|
||||
d_txn = rhs.d_txn;
|
||||
rhs.d_parent = 0;
|
||||
rhs.d_txn = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MDBRWTransaction()
|
||||
{
|
||||
if(d_txn) {
|
||||
d_parent->decRWTX();
|
||||
closeCursors();
|
||||
mdb_txn_abort(d_txn); // XXX check response?
|
||||
}
|
||||
}
|
||||
void closeCursors();
|
||||
~MDBRWTransactionImpl() override;
|
||||
|
||||
void commit()
|
||||
{
|
||||
closeCursors();
|
||||
if(int rc = mdb_txn_commit(d_txn)) {
|
||||
throw std::runtime_error("committing: " + std::string(mdb_strerror(rc)));
|
||||
}
|
||||
d_parent->decRWTX();
|
||||
|
||||
d_txn=0;
|
||||
}
|
||||
|
||||
void abort()
|
||||
{
|
||||
closeCursors();
|
||||
mdb_txn_abort(d_txn); // XXX check error?
|
||||
d_txn = 0;
|
||||
d_parent->decRWTX();
|
||||
}
|
||||
void commit() override;
|
||||
void abort() override;
|
||||
|
||||
void clear(MDB_dbi dbi);
|
||||
|
||||
|
@ -529,79 +574,35 @@ public:
|
|||
|
||||
MDBDbi openDB(string_view dbname, int flags)
|
||||
{
|
||||
return MDBDbi(d_parent->d_env, d_txn, dbname, flags);
|
||||
return MDBDbi(environment().d_env, d_txn, dbname, flags);
|
||||
}
|
||||
|
||||
MDBRWCursor getRWCursor(const MDBDbi&);
|
||||
MDBRWCursor getCursor(const MDBDbi&);
|
||||
|
||||
void reportCursor(MDBRWCursor* child)
|
||||
{
|
||||
d_cursors.insert(child);
|
||||
}
|
||||
void unreportCursor(MDBRWCursor* child)
|
||||
{
|
||||
d_cursors.erase(child);
|
||||
}
|
||||
|
||||
void reportCursorMove(MDBRWCursor* from, MDBRWCursor* to)
|
||||
{
|
||||
d_cursors.erase(from);
|
||||
d_cursors.insert(to);
|
||||
}
|
||||
|
||||
operator MDB_txn*&()
|
||||
{
|
||||
return d_txn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::set<MDBRWCursor*> d_cursors;
|
||||
MDBEnv* d_parent;
|
||||
MDB_txn* d_txn;
|
||||
MDBRWTransaction getRWTransaction();
|
||||
MDBROTransaction getROTransaction();
|
||||
};
|
||||
|
||||
/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends"
|
||||
This is a problem for us since it may means we are closing the cursor twice, which is bad
|
||||
*/
|
||||
class MDBRWCursor : public MDBGenCursor<MDBRWTransaction>
|
||||
class MDBRWCursor : public MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>
|
||||
{
|
||||
public:
|
||||
MDBRWCursor(MDBRWTransaction* parent, const MDB_dbi& dbi) : MDBGenCursor<MDBRWTransaction>(parent)
|
||||
{
|
||||
int rc= mdb_cursor_open(d_parent->d_txn, dbi, &d_cursor);
|
||||
if(rc) {
|
||||
throw std::runtime_error("Error creating RW cursor: "+std::string(mdb_strerror(rc)));
|
||||
}
|
||||
d_parent->reportCursor(this);
|
||||
}
|
||||
MDBRWCursor(MDBRWCursor&& rhs) : MDBGenCursor<MDBRWTransaction>(rhs.d_parent)
|
||||
{
|
||||
d_cursor = rhs.d_cursor;
|
||||
rhs.d_cursor=0;
|
||||
d_parent->reportCursorMove(&rhs, this);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if(d_cursor)
|
||||
mdb_cursor_close(d_cursor);
|
||||
d_cursor=0;
|
||||
}
|
||||
|
||||
~MDBRWCursor()
|
||||
{
|
||||
if(d_cursor)
|
||||
mdb_cursor_close(d_cursor);
|
||||
d_parent->unreportCursor(this);
|
||||
}
|
||||
|
||||
MDBRWCursor() = default;
|
||||
using MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>::MDBGenCursor;
|
||||
MDBRWCursor(const MDBRWCursor &src) = delete;
|
||||
MDBRWCursor(MDBRWCursor &&src) = default;
|
||||
MDBRWCursor &operator=(const MDBRWCursor &src) = delete;
|
||||
MDBRWCursor &operator=(MDBRWCursor &&src) = default;
|
||||
~MDBRWCursor() = default;
|
||||
|
||||
void put(const MDBOutVal& key, const MDBInVal& data)
|
||||
{
|
||||
int rc = mdb_cursor_put(d_cursor,
|
||||
const_cast<MDB_val*>(&key.d_mdbval),
|
||||
const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT);
|
||||
int rc = mdb_cursor_put(*this,
|
||||
const_cast<MDB_val*>(&key.d_mdbval),
|
||||
const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT);
|
||||
if(rc)
|
||||
throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(rc)));
|
||||
}
|
||||
|
@ -610,14 +611,15 @@ public:
|
|||
int put(const MDBOutVal& key, const MDBOutVal& data, int flags=0)
|
||||
{
|
||||
// XXX check errors
|
||||
return mdb_cursor_put(d_cursor,
|
||||
return mdb_cursor_put(*this,
|
||||
const_cast<MDB_val*>(&key.d_mdbval),
|
||||
const_cast<MDB_val*>(&data.d_mdbval), flags);
|
||||
}
|
||||
|
||||
int del(int flags=0)
|
||||
{
|
||||
return mdb_cursor_del(d_cursor, flags);
|
||||
return mdb_cursor_del(*this, flags);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
|
||||
{
|
||||
auto cursor = txn.getCursor(dbi);
|
||||
auto cursor = txn->getRWCursor(dbi);
|
||||
MDBOutVal maxidval, maxcontent;
|
||||
unsigned int maxid{0};
|
||||
if(!cursor.get(maxidval, maxcontent, MDB_LAST)) {
|
||||
|
|
|
@ -101,12 +101,12 @@ struct LMDBIndexOps
|
|||
explicit LMDBIndexOps(Parent* parent) : d_parent(parent){}
|
||||
void put(MDBRWTransaction& txn, const Class& t, uint32_t id, int flags=0)
|
||||
{
|
||||
txn.put(d_idx, keyConv(d_parent->getMember(t)), id, flags);
|
||||
txn->put(d_idx, keyConv(d_parent->getMember(t)), id, flags);
|
||||
}
|
||||
|
||||
void del(MDBRWTransaction& txn, const Class& t, uint32_t id)
|
||||
{
|
||||
if(int rc = txn.del(d_idx, keyConv(d_parent->getMember(t)), id)) {
|
||||
if(int rc = txn->del(d_idx, keyConv(d_parent->getMember(t)), id)) {
|
||||
throw std::runtime_error("Error deleting from index: " + std::string(mdb_strerror(rc)));
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ public:
|
|||
uint32_t size()
|
||||
{
|
||||
MDB_stat stat;
|
||||
mdb_stat(*d_parent.d_txn, d_parent.d_parent->d_main, &stat);
|
||||
mdb_stat(**d_parent.d_txn, d_parent.d_parent->d_main, &stat);
|
||||
return stat.ms_entries;
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,7 @@ public:
|
|||
uint32_t size()
|
||||
{
|
||||
MDB_stat stat;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@ public:
|
|||
bool get(uint32_t id, T& t)
|
||||
{
|
||||
MDBOutVal data;
|
||||
if(d_parent.d_txn->get(d_parent.d_parent->d_main, id, data))
|
||||
if((*d_parent.d_txn)->get(d_parent.d_parent->d_main, id, data))
|
||||
return false;
|
||||
|
||||
serFromString(data.get<std::string>(), t);
|
||||
|
@ -234,7 +234,7 @@ public:
|
|||
uint32_t get(const typename std::tuple_element<N, tuple_t>::type::type& key, T& out)
|
||||
{
|
||||
MDBOutVal id;
|
||||
if(!d_parent.d_txn->get(std::get<N>(d_parent.d_parent->d_tuple).d_idx, keyConv(key), id)) {
|
||||
if(!(*d_parent.d_txn)->get(std::get<N>(d_parent.d_parent->d_tuple).d_idx, keyConv(key), id)) {
|
||||
if(get(id.get<uint32_t>(), out))
|
||||
return id.get<uint32_t>();
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ public:
|
|||
template<int N>
|
||||
uint32_t cardinality()
|
||||
{
|
||||
auto cursor = d_parent.d_txn->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
|
||||
auto cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
|
||||
bool first = true;
|
||||
MDBOutVal key, data;
|
||||
uint32_t count = 0;
|
||||
|
@ -284,7 +284,7 @@ public:
|
|||
}
|
||||
|
||||
if(d_on_index) {
|
||||
if(d_parent->d_txn->get(d_parent->d_parent->d_main, d_id, d_data))
|
||||
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);
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ public:
|
|||
}
|
||||
|
||||
if(d_on_index) {
|
||||
if(d_parent->d_txn->get(d_parent->d_parent->d_main, d_id, d_data))
|
||||
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);
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ public:
|
|||
}
|
||||
else {
|
||||
if(d_on_index) {
|
||||
if(d_parent->d_txn->get(d_parent->d_parent->d_main, d_id, data))
|
||||
if((*d_parent->d_txn)->get(d_parent->d_parent->d_main, d_id, data))
|
||||
throw std::runtime_error("Missing id field");
|
||||
if(filter && !filter(data))
|
||||
goto next;
|
||||
|
@ -419,7 +419,7 @@ public:
|
|||
template<int N>
|
||||
iter_t genbegin(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);
|
||||
|
||||
MDBOutVal out, id;
|
||||
|
||||
|
@ -445,7 +445,7 @@ public:
|
|||
|
||||
iter_t begin()
|
||||
{
|
||||
typename Parent::cursor_t cursor = d_parent.d_txn->getCursor(d_parent.d_parent->d_main);
|
||||
typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(d_parent.d_parent->d_main);
|
||||
|
||||
MDBOutVal out, id;
|
||||
|
||||
|
@ -466,7 +466,7 @@ public:
|
|||
template<int N>
|
||||
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);
|
||||
|
||||
std::string keystr = keyConv(key);
|
||||
MDBInVal in(keystr);
|
||||
|
@ -498,7 +498,7 @@ public:
|
|||
template<int N>
|
||||
std::pair<iter_t,eiter_t> equal_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);
|
||||
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);
|
||||
|
@ -517,7 +517,7 @@ public:
|
|||
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);
|
||||
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);
|
||||
|
@ -595,7 +595,7 @@ public:
|
|||
id = MDBGetMaxID(*d_txn, d_parent->d_main) + 1;
|
||||
flags = MDB_APPEND;
|
||||
}
|
||||
d_txn->put(d_parent->d_main, id, serToString(t), flags);
|
||||
(*d_txn)->put(d_parent->d_main, id, serToString(t), flags);
|
||||
|
||||
#define insertMacro(N) std::get<N>(d_parent->d_tuple).put(*d_txn, t, id);
|
||||
insertMacro(0);
|
||||
|
@ -626,14 +626,14 @@ public:
|
|||
if(!this->get(id, t))
|
||||
return;
|
||||
|
||||
d_txn->del(d_parent->d_main, id);
|
||||
(*d_txn)->del(d_parent->d_main, id);
|
||||
clearIndex(id, t);
|
||||
}
|
||||
|
||||
//! clear database & indexes (by hand!)
|
||||
void clear()
|
||||
{
|
||||
auto cursor = d_txn->getCursor(d_parent->d_main);
|
||||
auto cursor = (*d_txn)->getRWCursor(d_parent->d_main);
|
||||
bool first = true;
|
||||
MDBOutVal key, data;
|
||||
while(!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT)) {
|
||||
|
@ -648,13 +648,13 @@ public:
|
|||
//! commit this transaction
|
||||
void commit()
|
||||
{
|
||||
d_txn->commit();
|
||||
(*d_txn)->commit();
|
||||
}
|
||||
|
||||
//! abort this transaction
|
||||
void abort()
|
||||
{
|
||||
d_txn->abort();
|
||||
(*d_txn)->abort();
|
||||
}
|
||||
|
||||
typedef MDBRWCursor cursor_t;
|
||||
|
|
|
@ -18,7 +18,7 @@ static void closeTest()
|
|||
|
||||
auto txn = env->getROTransaction();
|
||||
for(auto& d : {&main, &dbi, &hyc}) {
|
||||
auto rocursor = txn.getCursor(*d);
|
||||
auto rocursor = txn->getCursor(*d);
|
||||
MDBOutVal key, data;
|
||||
if(rocursor.get(key, data, MDB_FIRST))
|
||||
continue;
|
||||
|
@ -41,8 +41,8 @@ try
|
|||
for(int n=0; n < 15; ++n) {
|
||||
auto txn = env->getRWTransaction();
|
||||
int val = n + 1000*tid;
|
||||
txn.put(dbi, val, val);
|
||||
txn.commit();
|
||||
txn->put(dbi, val, val);
|
||||
txn->commit();
|
||||
cout << "Done with transaction "<<n<<" in thread " << tid<<endl;
|
||||
}
|
||||
cout<<"Done with thread "<<tid<<endl;
|
||||
|
@ -62,7 +62,7 @@ try
|
|||
auto txn = env->getROTransaction();
|
||||
int val = n + 1000*tid;
|
||||
MDBOutVal res;
|
||||
if(txn.get(dbi, val, res)) {
|
||||
if(txn->get(dbi, val, res)) {
|
||||
throw std::runtime_error("no record");
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,9 @@ void doFill()
|
|||
auto txn = env->getRWTransaction();
|
||||
for(int j=0; j < 1000000; ++j) {
|
||||
MDBInVal mv(n*1000000+j);
|
||||
txn.put(dbi, mv, mv, 0);
|
||||
txn->put(dbi, mv, mv, 0);
|
||||
}
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
}
|
||||
cout<<"Done filling"<<endl;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ void doMeasure()
|
|||
for(int j=0; j < 1000000; ++j) {
|
||||
MDBInVal mv(n*1000000+j);
|
||||
MDBOutVal res;
|
||||
if(!txn.get(dbi, mv, res))
|
||||
if(!txn->get(dbi, mv, res))
|
||||
++count;
|
||||
}
|
||||
cout<<count<<" ";
|
||||
|
|
|
@ -5,8 +5,8 @@ using namespace std;
|
|||
|
||||
void countDB(MDBEnv& env, MDBROTransaction& txn, const std::string& dbname)
|
||||
{
|
||||
auto db = txn.openDB(dbname, 0);
|
||||
auto cursor = txn.getCursor(db);
|
||||
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)) {
|
||||
|
@ -27,7 +27,7 @@ int main(int argc, char** argv)
|
|||
auto main = env.openDB("", 0);
|
||||
auto txn = env.getROTransaction();
|
||||
|
||||
auto cursor = txn.getCursor(main);
|
||||
auto cursor = txn->getCursor(main);
|
||||
|
||||
MDBOutVal key, val;
|
||||
if(cursor.get(key, val, MDB_FIRST)) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "lmdb-safe.hh"
|
||||
using namespace std;
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
unlink("./multi");
|
||||
|
@ -8,26 +10,26 @@ int main()
|
|||
auto dbi = env->openDB("qnames", MDB_DUPSORT | MDB_CREATE);
|
||||
|
||||
auto txn = env->getRWTransaction();
|
||||
txn.clear(dbi);
|
||||
txn->clear(dbi);
|
||||
|
||||
txn.put(dbi, "bdb", "old");
|
||||
txn.put(dbi, "lmdb", "hot");
|
||||
txn.put(dbi, "lmdb", "fast");
|
||||
txn.put(dbi, "lmdb", "zooms");
|
||||
txn.put(dbi, "lmdb", "c");
|
||||
txn.put(dbi, "mdb", "old name");
|
||||
txn->put(dbi, "bdb", "old");
|
||||
txn->put(dbi, "lmdb", "hot");
|
||||
txn->put(dbi, "lmdb", "fast");
|
||||
txn->put(dbi, "lmdb", "zooms");
|
||||
txn->put(dbi, "lmdb", "c");
|
||||
txn->put(dbi, "mdb", "old name");
|
||||
|
||||
string_view v1;
|
||||
if(!txn.get(dbi, "mdb", v1)) {
|
||||
if(!txn->get(dbi, "mdb", v1)) {
|
||||
cout<<v1<<endl;
|
||||
}
|
||||
else {
|
||||
cout << "found nothing" << endl;
|
||||
}
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
|
||||
txn = env->getRWTransaction();
|
||||
auto cursor = txn.getCursor(dbi);
|
||||
auto cursor = txn->getRWCursor(dbi);
|
||||
|
||||
MDBOutVal key, data;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ struct Record
|
|||
|
||||
static unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
|
||||
{
|
||||
auto cursor = txn.getCursor(dbi);
|
||||
auto cursor = txn->getRWCursor(dbi);
|
||||
MDBOutVal maxidval, maxcontent;
|
||||
unsigned int maxid{0};
|
||||
if(!cursor.get(maxidval, maxcontent, MDB_LAST)) {
|
||||
|
@ -42,9 +42,9 @@ static void store(MDBRWTransaction& txn, MDBDbi& records, MDBDbi& domainidx, MDB
|
|||
boost::archive::binary_oarchive oa(oss,boost::archive::no_header );
|
||||
oa << r;
|
||||
|
||||
txn.put(records, r.id, oss.str(), MDB_APPEND);
|
||||
txn.put(domainidx, r.domain_id, r.id);
|
||||
txn.put(nameidx, r.name, r.id);
|
||||
txn->put(records, r.id, oss.str(), MDB_APPEND);
|
||||
txn->put(domainidx, r.domain_id, r.id);
|
||||
txn->put(nameidx, r.name, r.id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,12 +122,12 @@ int main(int argc, char** argv)
|
|||
store(txn, records, domainidx, nameidx, r);
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
|
||||
auto rotxn = env->getROTransaction();
|
||||
auto rotxn2 = env->getROTransaction();
|
||||
|
||||
auto rocursor = rotxn.getCursor(nameidx);
|
||||
auto rocursor = rotxn->getCursor(nameidx);
|
||||
|
||||
MDBOutVal data;
|
||||
int count = 0;
|
||||
|
@ -143,7 +143,7 @@ int main(int argc, char** argv)
|
|||
cout<<"Got something: id="<<id<<endl;
|
||||
MDBOutVal record;
|
||||
|
||||
if(!rotxn.get(records, data, record)) {
|
||||
if(!rotxn->get(records, data, record)) {
|
||||
Record test;
|
||||
stringstream istr{record.get<string>()};
|
||||
boost::archive::binary_iarchive oi(istr,boost::archive::no_header );
|
||||
|
|
|
@ -10,42 +10,42 @@ int main(int argc, char** argv)
|
|||
MDBInVal key("counter");
|
||||
|
||||
auto rwtxn = env->getRWTransaction();
|
||||
rwtxn.put(main, "counter", "1234");
|
||||
rwtxn.put(main, MDBInVal::fromStruct(std::make_pair(12,13)), "hoi dan 12,13");
|
||||
rwtxn->put(main, "counter", "1234");
|
||||
rwtxn->put(main, MDBInVal::fromStruct(std::make_pair(12,13)), "hoi dan 12,13");
|
||||
|
||||
rwtxn.put(main, MDBInVal::fromStruct(std::make_pair(14,15)),
|
||||
MDBInVal::fromStruct(std::make_pair(20,23)));
|
||||
rwtxn->put(main, MDBInVal::fromStruct(std::make_pair(14,15)),
|
||||
MDBInVal::fromStruct(std::make_pair(20,23)));
|
||||
|
||||
|
||||
MDBOutVal out;
|
||||
if(!rwtxn.get(main, MDBInVal::fromStruct(std::make_pair(12,13)), out))
|
||||
if(!rwtxn->get(main, MDBInVal::fromStruct(std::make_pair(12,13)), out))
|
||||
cout << "Got: " << out.get<string_view>() << endl;
|
||||
else
|
||||
cout << "Got nothing!1"<<endl;
|
||||
|
||||
if(!rwtxn.get(main, MDBInVal::fromStruct(std::make_pair(14,15)), out)) {
|
||||
if(!rwtxn->get(main, MDBInVal::fromStruct(std::make_pair(14,15)), out)) {
|
||||
auto res = out.get_struct<pair<int,int>>();
|
||||
cout << "Got: " << res.first<<", "<<res.second << endl;
|
||||
}
|
||||
else
|
||||
cout << "Got nothing!1"<<endl;
|
||||
|
||||
rwtxn.put(main, 12.12, 7.3);
|
||||
if(!rwtxn.get(main, 12.12, out)) {
|
||||
rwtxn->put(main, 12.12, 7.3);
|
||||
if(!rwtxn->get(main, 12.12, out)) {
|
||||
cout<<"Got: "<< out.get<double>() <<endl;
|
||||
}
|
||||
else
|
||||
cout << "Got nothing!1"<<endl;
|
||||
|
||||
|
||||
rwtxn.commit();
|
||||
rwtxn->commit();
|
||||
return 0;
|
||||
|
||||
if(argc==1) {
|
||||
for(;;) {
|
||||
auto rotxn = env->getROTransaction();
|
||||
MDBOutVal data;
|
||||
if(!rotxn.get(main, key, data)) {
|
||||
if(!rotxn->get(main, key, data)) {
|
||||
cout<<"Counter is "<<data.get<unsigned int>() << endl;
|
||||
cout <<data.get<string>() << endl;
|
||||
cout<<data.get<string_view>() << endl;
|
||||
|
@ -73,10 +73,10 @@ int main(int argc, char** argv)
|
|||
cout<<"Did resize"<<endl;
|
||||
}
|
||||
auto txn = env->getRWTransaction();
|
||||
txn.put(main, key, MDBInVal(n));
|
||||
txn->put(main, key, MDBInVal(n));
|
||||
for(int k=0; k < 100; ++k)
|
||||
txn.put(main, MDBInVal(n+1000*k), MDBInVal(n+1000*k));
|
||||
txn.commit();
|
||||
txn->put(main, MDBInVal(n+1000*k), MDBInVal(n+1000*k));
|
||||
txn->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ int main(int argc, char** argv)
|
|||
limit = atoi(argv[1]);
|
||||
|
||||
cout<<"Counting records.. "; cout.flush();
|
||||
auto cursor=txn.getCursor(dbi);
|
||||
auto cursor = txn->getCursor(dbi);
|
||||
MDBOutVal key, data;
|
||||
int count=0;
|
||||
while(!cursor.get(key, data, count ? MDB_NEXT : MDB_FIRST)) {
|
||||
|
@ -40,15 +40,15 @@ int main(int argc, char** argv)
|
|||
cout<<"Have "<<count<<"!"<<endl;
|
||||
|
||||
cout<<"Clearing records.. "; cout.flush();
|
||||
mdb_drop(txn, dbi, 0); // clear records
|
||||
mdb_drop(*txn, dbi, 0); // clear records
|
||||
cout<<"Done!"<<endl;
|
||||
|
||||
cout << "Adding "<<limit<<" values .. "; cout.flush();
|
||||
for(unsigned long n = 0 ; n < limit; ++n) {
|
||||
txn.put(dbi, n, n, MDB_APPEND);
|
||||
txn->put(dbi, n, n, MDB_APPEND);
|
||||
}
|
||||
cout <<"Done!"<<endl;
|
||||
cout <<"Calling commit.. "; cout.flush();
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
cout<<"Done!"<<endl;
|
||||
}
|
||||
|
|
283
test-basic.cc
283
test-basic.cc
|
@ -13,20 +13,20 @@ TEST_CASE("Most basic tests", "[mostbasic]") {
|
|||
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) == MDB_NOTFOUND);
|
||||
|
||||
REQUIRE(txn.get(main, "lmdb", out) == 0);
|
||||
txn->put(main, "lmdb", "hot");
|
||||
|
||||
REQUIRE(txn->get(main, "lmdb", out) == 0);
|
||||
REQUIRE(out.get<std::string>() == "hot");
|
||||
txn.abort();
|
||||
txn->abort();
|
||||
|
||||
auto rotxn = env.getROTransaction();
|
||||
REQUIRE(rotxn.get(main, "lmdb", out) == MDB_NOTFOUND);
|
||||
REQUIRE(rotxn->get(main, "lmdb", out) == MDB_NOTFOUND);
|
||||
}
|
||||
|
||||
TEST_CASE("Range tests", "[range]") {
|
||||
|
@ -36,20 +36,20 @@ TEST_CASE("Range tests", "[range]") {
|
|||
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, "bert", "hubert");
|
||||
txn.put(main, "bertt", "1975");
|
||||
txn.put(main, "berthubert", "lmdb");
|
||||
txn.put(main, "bert1", "one");
|
||||
txn.put(main, "beru", "not");
|
||||
|
||||
REQUIRE(txn->get(main, "lmdb", out) == MDB_NOTFOUND);
|
||||
|
||||
txn->put(main, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
|
||||
{
|
||||
auto cursor = txn.getCursor(main);
|
||||
auto cursor = txn->getCursor(main);
|
||||
MDBInVal bert("bert");
|
||||
MDBOutVal key, val;
|
||||
REQUIRE(cursor.lower_bound(bert, key, val) == 0);
|
||||
|
@ -64,14 +64,14 @@ TEST_CASE("Range tests", "[range]") {
|
|||
REQUIRE(key.get<string>() == "berthubert");
|
||||
REQUIRE(val.get<string>() == "lmdb");
|
||||
|
||||
REQUIRE(cursor.lower_bound("kees", key, val) == MDB_NOTFOUND);
|
||||
REQUIRE(cursor.lower_bound("kees", key, val) == MDB_NOTFOUND);
|
||||
|
||||
txn.commit();
|
||||
txn->commit();
|
||||
}
|
||||
|
||||
|
||||
auto rotxn = env.getROTransaction();
|
||||
{
|
||||
auto cursor = rotxn.getCursor(main);
|
||||
auto cursor = rotxn->getCursor(main);
|
||||
MDBInVal bert("bert");
|
||||
MDBOutVal key, val;
|
||||
REQUIRE(cursor.lower_bound(bert, key, val) == 0);
|
||||
|
@ -86,7 +86,244 @@ TEST_CASE("Range tests", "[range]") {
|
|||
REQUIRE(key.get<string>() == "berthubert");
|
||||
REQUIRE(val.get<string>() == "lmdb");
|
||||
|
||||
REQUIRE(cursor.lower_bound("kees", key, val) == MDB_NOTFOUND);
|
||||
REQUIRE(cursor.lower_bound("kees", key, val) == MDB_NOTFOUND);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("moving transactions")
|
||||
{
|
||||
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, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
|
||||
auto cursor = txn->getCursor(main);
|
||||
auto txn2 = std::move(txn);
|
||||
{
|
||||
auto cursor2 = std::move(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("transaction inheritance and moving")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
MDBRWCursor cursor;
|
||||
{
|
||||
MDBRWTransaction txn = env.getRWTransaction();
|
||||
MDBOutVal out;
|
||||
|
||||
REQUIRE(txn->get(main, "lmdb", out) == MDB_NOTFOUND);
|
||||
|
||||
// lets just keep this cursor to ensure that it invalidates
|
||||
cursor = txn->getRWCursor(main);
|
||||
txn->put(main, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
|
||||
MDBROTransaction ro_txn(std::move(txn));
|
||||
// despite being moved to an ro_txn (which normally commits instead of
|
||||
// aborting by default)
|
||||
}
|
||||
|
||||
CHECK(!cursor);
|
||||
}
|
||||
|
||||
TEST_CASE("nested RW transactions", "[transactions]")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
REQUIRE(1);
|
||||
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
/* bootstrap some data */
|
||||
{
|
||||
auto txn = env.getRWTransaction();
|
||||
txn->put(main, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
txn->commit();
|
||||
}
|
||||
|
||||
auto main_txn = env.getRWTransaction();
|
||||
main_txn->del(main, "bertt", "1975");
|
||||
|
||||
MDBOutVal dummy{};
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
|
||||
{
|
||||
auto sub_txn = main_txn->getRWTransaction();
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
|
||||
sub_txn->del(main, "berthubert", "lmdb");
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
}
|
||||
|
||||
/* check that subtransaction got rolled back */
|
||||
CHECK(main_txn->get(main, "berthubert", dummy) == 0);
|
||||
/* and that the main changes are still there */
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
|
||||
{
|
||||
auto sub_txn = main_txn->getRWTransaction();
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
|
||||
sub_txn->del(main, "berthubert", "lmdb");
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
/* this time for real! */
|
||||
sub_txn->commit();
|
||||
}
|
||||
|
||||
CHECK(main_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("nesting RW -> RO", "[transactions]")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
REQUIRE(1);
|
||||
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
/* bootstrap some data */
|
||||
{
|
||||
auto txn = env.getRWTransaction();
|
||||
txn->put(main, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
txn->commit();
|
||||
}
|
||||
|
||||
auto main_txn = env.getRWTransaction();
|
||||
main_txn->del(main, "bertt", "1975");
|
||||
|
||||
MDBOutVal dummy{};
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
|
||||
{
|
||||
MDBROTransaction sub_txn = main_txn->getROTransaction();
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
|
||||
}
|
||||
|
||||
/* check that subtransaction got rolled back */
|
||||
CHECK(main_txn->get(main, "berthubert", dummy) == 0);
|
||||
/* and that the main changes are still there */
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
|
||||
{
|
||||
auto sub_txn = main_txn->getRWTransaction();
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
|
||||
sub_txn->del(main, "berthubert", "lmdb");
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
{
|
||||
MDBROTransaction sub_sub_txn = sub_txn->getROTransaction();
|
||||
CHECK(sub_sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
}
|
||||
/* this time for real! */
|
||||
sub_txn->commit();
|
||||
}
|
||||
|
||||
CHECK(main_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
}
|
||||
|
||||
TEST_CASE("try to nest twice", "[transactions]")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
REQUIRE(1);
|
||||
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
/* bootstrap some data */
|
||||
{
|
||||
auto txn = env.getRWTransaction();
|
||||
txn->put(main, "bert", "hubert");
|
||||
txn->put(main, "bertt", "1975");
|
||||
txn->put(main, "berthubert", "lmdb");
|
||||
txn->put(main, "bert1", "one");
|
||||
txn->put(main, "beru", "not");
|
||||
txn->commit();
|
||||
}
|
||||
|
||||
auto main_txn = env.getRWTransaction();
|
||||
main_txn->del(main, "bertt", "1975");
|
||||
|
||||
MDBOutVal dummy{};
|
||||
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
|
||||
|
||||
{
|
||||
auto sub_txn = main_txn->getRWTransaction();
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
|
||||
sub_txn->del(main, "berthubert", "lmdb");
|
||||
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
main_txn->getRWTransaction(),
|
||||
std::runtime_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("transaction counter correctness for RW->RW nesting")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
REQUIRE(1);
|
||||
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
{
|
||||
auto txn = env.getRWTransaction();
|
||||
auto sub_txn = txn->getRWTransaction();
|
||||
}
|
||||
|
||||
CHECK_NOTHROW(env.getRWTransaction());
|
||||
CHECK_NOTHROW(env.getROTransaction());
|
||||
}
|
||||
|
||||
TEST_CASE("transaction counter correctness for RW->RO nesting")
|
||||
{
|
||||
unlink("./tests");
|
||||
|
||||
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
|
||||
REQUIRE(1);
|
||||
|
||||
MDBDbi main = env.openDB("", MDB_CREATE);
|
||||
|
||||
{
|
||||
auto txn = env.getRWTransaction();
|
||||
auto sub_txn = txn->getROTransaction();
|
||||
}
|
||||
|
||||
CHECK_NOTHROW(env.getRWTransaction());
|
||||
CHECK_NOTHROW(env.getROTransaction());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue