Merge pull request #6 from horazont/feature/nested-transactions

Implement support for nesting transactions
This commit is contained in:
bert hubert 2019-11-20 21:38:58 +01:00 committed by GitHub
commit 469de3c0e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 686 additions and 309 deletions

View File

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

View File

@ -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();
}

View File

@ -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 dont 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);
}

View File

@ -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*> &registry, MDB_cursor *cursor):
d_registry(&registry),
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);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
}
}

View File

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

View File

@ -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());
}