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. `abort()`, after which going out of scope has no further effect.
``` ```
txn.put(dbi, "lmdb", "great"); txn->put(dbi, "lmdb", "great");
string_view data; string_view data;
if(!txn.get(dbi, "lmdb", data)) { if(!txn->get(dbi, "lmdb", data)) {
cout<< "Within RW transaction, found that lmdb = " << data <<endl; cout<< "Within RW transaction, found that lmdb = " << data <<endl;
} }
else else
cout<<"Found nothing" << endl; cout<<"Found nothing" << endl;
txn.commit(); txn->commit();
``` ```
LMDB is so fast because it does not copy data unless it really needs to. 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(); auto txn = env->getRWTransaction();
uint64_t id=12345678901; uint64_t id=12345678901;
double score=3.14159; double score=3.14159;
txn.put(dbi, id, score); txn->put(dbi, id, score);
txn.commit(); txn->commit();
``` ```
Behind the scenes, the `id` and `score` values are wrapped by `MDBInVal` Behind the scenes, the `id` and `score` values are wrapped by `MDBInVal`
@ -142,7 +142,7 @@ works similary:
uint64_t id=12345678901; uint64_t id=12345678901;
MDBOutValue val; MDBOutValue val;
txn.get(dbi, id, val); txn->get(dbi, id, val);
cout << "Score: " << val.get<double>() << "\n"; cout << "Score: " << val.get<double>() << "\n";
``` ```
@ -170,10 +170,10 @@ struct Coordinate
C c{12.0, 13.0}; C c{12.0, 13.0};
txn.put(dbi, MDBInVal::fromStruct(c), 12.0); txn->put(dbi, MDBInVal::fromStruct(c), 12.0);
MDBOutVal res; MDBOutVal res;
txn.get(dbi, MDBInVal::fromStruct(c), res); txn->get(dbi, MDBInVal::fromStruct(c), res);
auto c1 = res.get_struct<Coordinate>(); auto c1 = res.get_struct<Coordinate>();
``` ```
@ -193,7 +193,7 @@ calls to mdb.
This is the usual opening sequence. This is the usual opening sequence.
``` ```
auto cursor=txn.getCursor(dbi); auto cursor=txn->getCursor(dbi);
MDBOutVal key, data; MDBOutVal key, data;
int count=0; int count=0;
cout<<"Counting records.. "; cout.flush(); cout<<"Counting records.. "; cout.flush();
@ -212,7 +212,7 @@ records in under a second (!).
``` ```
cout<<"Clearing records.. "; cout.flush(); cout<<"Clearing records.. "; cout.flush();
mdb_drop(txn, dbi, 0); // clear records mdb_drop(*txn, dbi, 0); // clear records
cout<<"Done!"<<endl; 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(); cout << "Adding "<<limit<<" values .. "; cout.flush();
for(unsigned int n = 0 ; n < limit; ++n) { for(unsigned int n = 0 ; n < limit; ++n) {
txn.put(dbi, n, n); txn->put(dbi, n, n);
} }
cout <<"Done!"<<endl; cout <<"Done!"<<endl;
cout <<"Calling commit.. "; cout.flush(); cout <<"Calling commit.. "; cout.flush();
txn.commit(); txn->commit();
cout<<"Done!"<<endl; cout<<"Done!"<<endl;
``` ```

View File

@ -5,7 +5,7 @@ void checkLMDB(MDBEnv* env, MDBDbi dbi)
{ {
auto rotxn = env->getROTransaction(); auto rotxn = env->getROTransaction();
MDBOutVal data; 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; cout<< "Outside RW transaction, found that lmdb = " << data.get<string_view>() <<endl;
} }
else else
@ -18,11 +18,11 @@ int main()
auto dbi = env->openDB("example", MDB_CREATE); auto dbi = env->openDB("example", MDB_CREATE);
auto txn = env->getRWTransaction(); auto txn = env->getRWTransaction();
mdb_drop(txn, dbi, 0); mdb_drop(*txn, dbi, 0);
txn.put(dbi, "lmdb", "great"); txn->put(dbi, "lmdb", "great");
MDBOutVal data; 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; cout<< "Within RW transaction, found that lmdb = " << data.get<string_view>() <<endl;
} }
else else
@ -31,12 +31,12 @@ int main()
std::thread elsewhere(checkLMDB, env.get(), dbi); std::thread elsewhere(checkLMDB, env.get(), dbi);
elsewhere.join(); elsewhere.join();
txn.commit(); txn->commit();
cout<<"Committed data"<<endl; cout<<"Committed data"<<endl;
checkLMDB(env.get(), dbi); checkLMDB(env.get(), dbi);
txn = env->getRWTransaction(); txn = env->getRWTransaction();
mdb_drop(txn, dbi, 0); mdb_drop(*txn, dbi, 0);
txn.commit(); txn->commit();
} }

View File

@ -139,53 +139,107 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
if(!(envflags & MDB_RDONLY)) { if(!(envflags & MDB_RDONLY)) {
auto rwt = getRWTransaction(); auto rwt = getRWTransaction();
MDBDbi ret = rwt.openDB(dbname, flags); MDBDbi ret = rwt->openDB(dbname, flags);
rwt.commit(); rwt->commit();
return ret; return ret;
} }
MDBDbi ret; MDBDbi ret;
{ {
auto rwt = getROTransaction(); auto rwt = getROTransaction();
ret = rwt.openDB(dbname, flags); ret = rwt->openDB(dbname, flags);
} }
return ret; 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"); throw std::runtime_error("Duplicate RW transaction");
for(int tries =0 ; tries < 3; ++tries) { // it might happen twice, who knows 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(rc == MDB_MAP_RESIZED && tries < 2) {
// "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED. // "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." // 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; continue;
} }
throw std::runtime_error("Unable to start RW transaction: "+std::string(mdb_strerror(rc))); throw std::runtime_error("Unable to start RW transaction: "+std::string(mdb_strerror(rc)));
} }
break; 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"); 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. */ 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 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(rc == MDB_MAP_RESIZED && tries < 2) {
// "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED. // "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." // 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; continue;
} }
@ -193,43 +247,125 @@ MDBROTransaction::MDBROTransaction(MDBEnv* parent, int flags) : d_parent(parent)
} }
break; 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)) { if(int rc = mdb_drop(d_txn, dbi, 0)) {
throw runtime_error("Error clearing database: " + MDBError(rc)); 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() MDBROTransaction MDBEnv::getROTransaction()
{ {
return MDBROTransaction(this); return MDBROTransaction(new MDBROTransactionImpl(this));
} }
MDBRWTransaction MDBEnv::getRWTransaction() MDBRWTransaction MDBEnv::getRWTransaction()
{ {
return MDBRWTransaction(this); return MDBRWTransaction(new MDBRWTransactionImpl(this));
} }
void MDBRWTransaction::closeCursors() void MDBRWTransactionImpl::closeRWCursors()
{ {
for(auto& c : d_cursors) decltype(d_rw_cursors) buf;
c->close(); std::swap(d_rw_cursors, buf);
d_cursors.clear(); 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>
#include <string.h> #include <string.h>
#include <mutex> #include <mutex>
#include <vector>
#include <algorithm>
// apple compiler somehow has string_view even in c++11! // apple compiler somehow has string_view even in c++11!
#if __cplusplus < 201703L && !defined(__APPLE__) #if __cplusplus < 201703L && !defined(__APPLE__)
@ -57,8 +59,11 @@ public:
MDB_dbi d_dbi; MDB_dbi d_dbi;
}; };
class MDBRWTransaction; class MDBRWTransactionImpl;
class MDBROTransaction; class MDBROTransactionImpl;
using MDBROTransaction = std::unique_ptr<MDBROTransactionImpl>;
using MDBRWTransaction = std::unique_ptr<MDBRWTransactionImpl>;
class MDBEnv class MDBEnv
{ {
@ -220,35 +225,36 @@ private:
class MDBROCursor; 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: public:
explicit MDBROTransaction(MDBEnv* parent, int flags=0); explicit MDBROTransactionImpl(MDBEnv* parent, int flags=0);
MDBROTransaction(MDBROTransaction&& rhs) MDBROTransactionImpl(const MDBROTransactionImpl& src) = delete;
{ MDBROTransactionImpl &operator=(const MDBROTransactionImpl& src) = delete;
d_parent = rhs.d_parent;
d_txn = rhs.d_txn;
rhs.d_parent = 0;
rhs.d_txn = 0;
}
void reset() // The move constructor/operator cannot be made safe due to Object Slicing with MDBRWTransaction.
{ MDBROTransactionImpl(MDBROTransactionImpl&& rhs) = delete;
// this does not free cursors MDBROTransactionImpl &operator=(MDBROTransactionImpl &&rhs) = delete;
mdb_txn_reset(d_txn);
d_parent->decROTX();
}
void renew() virtual ~MDBROTransactionImpl();
{
if(d_parent->getROTX()) virtual void abort();
throw std::runtime_error("Duplicate RO transaction"); virtual void commit();
if(int rc = mdb_txn_renew(d_txn))
throw std::runtime_error("Renewing RO transaction: "+std::string(mdb_strerror(rc)));
d_parent->incROTX();
}
int get(MDB_dbi dbi, const MDBInVal& key, MDBOutVal& val) int get(MDB_dbi dbi, const MDBInVal& key, MDBOutVal& val)
{ {
@ -280,22 +286,21 @@ public:
} }
MDBROCursor getCursor(const MDBDbi&); MDBROCursor getCursor(const MDBDbi&);
MDBROCursor getROCursor(const MDBDbi&);
~MDBROTransaction() operator MDB_txn*()
{
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*&()
{ {
return d_txn; return d_txn;
} }
MDBEnv* d_parent; inline operator bool() const {
MDB_txn* d_txn; 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." "If the parent transaction commits, the cursor must not be used again."
*/ */
template<class Transaction> template<class Transaction, class T>
class MDBGenCursor 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: public:
MDBGenCursor(Transaction *t) : d_parent(t)
{}
int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op) int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
{ {
int rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, 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); return currentlast(key, data, MDB_FIRST);
} }
operator MDB_cursor*&() operator MDB_cursor*()
{ {
return d_cursor; return d_cursor;
} }
MDB_cursor* d_cursor; operator bool() const
Transaction* d_parent;
};
class MDBROCursor : public MDBGenCursor<MDBROTransaction>
{
public:
MDBROCursor(MDBROTransaction* parent, const MDB_dbi& dbi) : MDBGenCursor<MDBROTransaction>(parent)
{ {
int rc= mdb_cursor_open(d_parent->d_txn, dbi, &d_cursor); return 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;
} }
void close() void close()
{ {
mdb_cursor_close(d_cursor); if (d_registry) {
d_cursor=0; auto iter = std::find(d_registry->begin(),
} d_registry->end(),
static_cast<T*>(this));
~MDBROCursor() if (iter != d_registry->end()) {
{ d_registry->erase(iter);
if(d_cursor) }
d_registry = nullptr;
}
if (d_cursor) {
mdb_cursor_close(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 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: public:
explicit MDBRWTransaction(MDBEnv* parent, int flags=0); explicit MDBRWTransactionImpl(MDBEnv* parent, int flags=0);
MDBRWTransaction(MDBRWTransaction&& rhs) MDBRWTransactionImpl(const MDBRWTransactionImpl& rhs) = delete;
{ MDBRWTransactionImpl(MDBRWTransactionImpl&& rhs) = delete;
d_parent = rhs.d_parent; MDBRWTransactionImpl &operator=(const MDBRWTransactionImpl& rhs) = delete;
d_txn = rhs.d_txn; MDBRWTransactionImpl &operator=(MDBRWTransactionImpl&& rhs) = delete;
rhs.d_parent = 0;
rhs.d_txn = 0;
}
MDBRWTransaction& operator=(MDBRWTransaction&& rhs) ~MDBRWTransactionImpl() override;
{
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();
void commit() void commit() override;
{ void abort() override;
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 clear(MDB_dbi dbi); void clear(MDB_dbi dbi);
@ -529,79 +574,35 @@ public:
MDBDbi openDB(string_view dbname, int flags) 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&); MDBRWCursor getCursor(const MDBDbi&);
void reportCursor(MDBRWCursor* child) MDBRWTransaction getRWTransaction();
{ MDBROTransaction getROTransaction();
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;
}; };
/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends" /* "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 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: public:
MDBRWCursor(MDBRWTransaction* parent, const MDB_dbi& dbi) : MDBGenCursor<MDBRWTransaction>(parent) MDBRWCursor() = default;
{ using MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>::MDBGenCursor;
int rc= mdb_cursor_open(d_parent->d_txn, dbi, &d_cursor); MDBRWCursor(const MDBRWCursor &src) = delete;
if(rc) { MDBRWCursor(MDBRWCursor &&src) = default;
throw std::runtime_error("Error creating RW cursor: "+std::string(mdb_strerror(rc))); MDBRWCursor &operator=(const MDBRWCursor &src) = delete;
} MDBRWCursor &operator=(MDBRWCursor &&src) = default;
d_parent->reportCursor(this); ~MDBRWCursor() = default;
}
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);
}
void put(const MDBOutVal& key, const MDBInVal& data) void put(const MDBOutVal& key, const MDBInVal& data)
{ {
int rc = mdb_cursor_put(d_cursor, int rc = mdb_cursor_put(*this,
const_cast<MDB_val*>(&key.d_mdbval), const_cast<MDB_val*>(&key.d_mdbval),
const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT); const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT);
if(rc) if(rc)
throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(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) int put(const MDBOutVal& key, const MDBOutVal& data, int flags=0)
{ {
// XXX check errors // 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*>(&key.d_mdbval),
const_cast<MDB_val*>(&data.d_mdbval), flags); const_cast<MDB_val*>(&data.d_mdbval), flags);
} }
int del(int flags=0) 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) unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
{ {
auto cursor = txn.getCursor(dbi); auto cursor = txn->getRWCursor(dbi);
MDBOutVal maxidval, maxcontent; MDBOutVal maxidval, maxcontent;
unsigned int maxid{0}; unsigned int maxid{0};
if(!cursor.get(maxidval, maxcontent, MDB_LAST)) { if(!cursor.get(maxidval, maxcontent, MDB_LAST)) {

View File

@ -101,12 +101,12 @@ struct LMDBIndexOps
explicit LMDBIndexOps(Parent* parent) : d_parent(parent){} explicit LMDBIndexOps(Parent* parent) : d_parent(parent){}
void put(MDBRWTransaction& txn, const Class& t, uint32_t id, int flags=0) 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) 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))); throw std::runtime_error("Error deleting from index: " + std::string(mdb_strerror(rc)));
} }
} }
@ -205,7 +205,7 @@ public:
uint32_t size() uint32_t size()
{ {
MDB_stat stat; 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; return stat.ms_entries;
} }
@ -214,7 +214,7 @@ public:
uint32_t size() uint32_t size()
{ {
MDB_stat stat; 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; return stat.ms_entries;
} }
@ -222,7 +222,7 @@ public:
bool get(uint32_t id, T& t) bool get(uint32_t id, T& t)
{ {
MDBOutVal data; 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; return false;
serFromString(data.get<std::string>(), t); 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) uint32_t get(const typename std::tuple_element<N, tuple_t>::type::type& key, T& out)
{ {
MDBOutVal id; 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)) if(get(id.get<uint32_t>(), out))
return id.get<uint32_t>(); return id.get<uint32_t>();
} }
@ -245,7 +245,7 @@ public:
template<int N> template<int N>
uint32_t cardinality() 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; bool first = true;
MDBOutVal key, data; MDBOutVal key, data;
uint32_t count = 0; uint32_t count = 0;
@ -284,7 +284,7 @@ public:
} }
if(d_on_index) { 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"); throw std::runtime_error("Missing id in constructor");
serFromString(d_data.get<std::string>(), d_t); serFromString(d_data.get<std::string>(), d_t);
} }
@ -309,7 +309,7 @@ public:
} }
if(d_on_index) { 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"); throw std::runtime_error("Missing id in constructor");
serFromString(d_data.get<std::string>(), d_t); serFromString(d_data.get<std::string>(), d_t);
} }
@ -362,7 +362,7 @@ public:
} }
else { else {
if(d_on_index) { 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"); throw std::runtime_error("Missing id field");
if(filter && !filter(data)) if(filter && !filter(data))
goto next; goto next;
@ -419,7 +419,7 @@ public:
template<int N> template<int N>
iter_t genbegin(MDB_cursor_op op) 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; MDBOutVal out, id;
@ -445,7 +445,7 @@ public:
iter_t begin() 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; MDBOutVal out, id;
@ -466,7 +466,7 @@ public:
template<int N> template<int N>
iter_t genfind(const typename std::tuple_element<N, tuple_t>::type::type& key, MDB_cursor_op op) 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); std::string keystr = keyConv(key);
MDBInVal in(keystr); MDBInVal in(keystr);
@ -498,7 +498,7 @@ public:
template<int N> template<int N>
std::pair<iter_t,eiter_t> equal_range(const typename std::tuple_element<N, tuple_t>::type::type& key) std::pair<iter_t,eiter_t> equal_range(const typename std::tuple_element<N, tuple_t>::type::type& key)
{ {
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); std::string keyString=keyConv(key);
MDBInVal in(keyString); MDBInVal in(keyString);
@ -517,7 +517,7 @@ public:
template<int N> template<int N>
std::pair<iter_t,eiter_t> prefix_range(const typename std::tuple_element<N, tuple_t>::type::type& key) 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); std::string keyString=keyConv(key);
MDBInVal in(keyString); MDBInVal in(keyString);
@ -595,7 +595,7 @@ public:
id = MDBGetMaxID(*d_txn, d_parent->d_main) + 1; id = MDBGetMaxID(*d_txn, d_parent->d_main) + 1;
flags = MDB_APPEND; 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); #define insertMacro(N) std::get<N>(d_parent->d_tuple).put(*d_txn, t, id);
insertMacro(0); insertMacro(0);
@ -626,14 +626,14 @@ public:
if(!this->get(id, t)) if(!this->get(id, t))
return; return;
d_txn->del(d_parent->d_main, id); (*d_txn)->del(d_parent->d_main, id);
clearIndex(id, t); clearIndex(id, t);
} }
//! clear database & indexes (by hand!) //! clear database & indexes (by hand!)
void clear() void clear()
{ {
auto cursor = d_txn->getCursor(d_parent->d_main); auto cursor = (*d_txn)->getRWCursor(d_parent->d_main);
bool first = true; bool first = true;
MDBOutVal key, data; MDBOutVal key, data;
while(!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT)) { while(!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT)) {
@ -648,13 +648,13 @@ public:
//! commit this transaction //! commit this transaction
void commit() void commit()
{ {
d_txn->commit(); (*d_txn)->commit();
} }
//! abort this transaction //! abort this transaction
void abort() void abort()
{ {
d_txn->abort(); (*d_txn)->abort();
} }
typedef MDBRWCursor cursor_t; typedef MDBRWCursor cursor_t;

View File

@ -18,7 +18,7 @@ static void closeTest()
auto txn = env->getROTransaction(); auto txn = env->getROTransaction();
for(auto& d : {&main, &dbi, &hyc}) { for(auto& d : {&main, &dbi, &hyc}) {
auto rocursor = txn.getCursor(*d); auto rocursor = txn->getCursor(*d);
MDBOutVal key, data; MDBOutVal key, data;
if(rocursor.get(key, data, MDB_FIRST)) if(rocursor.get(key, data, MDB_FIRST))
continue; continue;
@ -41,8 +41,8 @@ try
for(int n=0; n < 15; ++n) { for(int n=0; n < 15; ++n) {
auto txn = env->getRWTransaction(); auto txn = env->getRWTransaction();
int val = n + 1000*tid; int val = n + 1000*tid;
txn.put(dbi, val, val); txn->put(dbi, val, val);
txn.commit(); txn->commit();
cout << "Done with transaction "<<n<<" in thread " << tid<<endl; cout << "Done with transaction "<<n<<" in thread " << tid<<endl;
} }
cout<<"Done with thread "<<tid<<endl; cout<<"Done with thread "<<tid<<endl;
@ -62,7 +62,7 @@ try
auto txn = env->getROTransaction(); auto txn = env->getROTransaction();
int val = n + 1000*tid; int val = n + 1000*tid;
MDBOutVal res; MDBOutVal res;
if(txn.get(dbi, val, res)) { if(txn->get(dbi, val, res)) {
throw std::runtime_error("no record"); throw std::runtime_error("no record");
} }
@ -85,9 +85,9 @@ void doFill()
auto txn = env->getRWTransaction(); auto txn = env->getRWTransaction();
for(int j=0; j < 1000000; ++j) { for(int j=0; j < 1000000; ++j) {
MDBInVal mv(n*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; cout<<"Done filling"<<endl;
} }
@ -104,7 +104,7 @@ void doMeasure()
for(int j=0; j < 1000000; ++j) { for(int j=0; j < 1000000; ++j) {
MDBInVal mv(n*1000000+j); MDBInVal mv(n*1000000+j);
MDBOutVal res; MDBOutVal res;
if(!txn.get(dbi, mv, res)) if(!txn->get(dbi, mv, res))
++count; ++count;
} }
cout<<count<<" "; cout<<count<<" ";

View File

@ -5,8 +5,8 @@ using namespace std;
void countDB(MDBEnv& env, MDBROTransaction& txn, const std::string& dbname) void countDB(MDBEnv& env, MDBROTransaction& txn, const std::string& dbname)
{ {
auto db = txn.openDB(dbname, 0); auto db = txn->openDB(dbname, 0);
auto cursor = txn.getCursor(db); auto cursor = txn->getCursor(db);
uint32_t count = 0; uint32_t count = 0;
MDBOutVal key, val; MDBOutVal key, val;
while(!cursor.get(key, val, count ? MDB_NEXT : MDB_FIRST)) { 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 main = env.openDB("", 0);
auto txn = env.getROTransaction(); auto txn = env.getROTransaction();
auto cursor = txn.getCursor(main); auto cursor = txn->getCursor(main);
MDBOutVal key, val; MDBOutVal key, val;
if(cursor.get(key, val, MDB_FIRST)) { if(cursor.get(key, val, MDB_FIRST)) {

View File

@ -1,6 +1,8 @@
#include "lmdb-safe.hh" #include "lmdb-safe.hh"
using namespace std; using namespace std;
#include <unistd.h>
int main() int main()
{ {
unlink("./multi"); unlink("./multi");
@ -8,26 +10,26 @@ int main()
auto dbi = env->openDB("qnames", MDB_DUPSORT | MDB_CREATE); auto dbi = env->openDB("qnames", MDB_DUPSORT | MDB_CREATE);
auto txn = env->getRWTransaction(); auto txn = env->getRWTransaction();
txn.clear(dbi); txn->clear(dbi);
txn.put(dbi, "bdb", "old"); txn->put(dbi, "bdb", "old");
txn.put(dbi, "lmdb", "hot"); txn->put(dbi, "lmdb", "hot");
txn.put(dbi, "lmdb", "fast"); txn->put(dbi, "lmdb", "fast");
txn.put(dbi, "lmdb", "zooms"); txn->put(dbi, "lmdb", "zooms");
txn.put(dbi, "lmdb", "c"); txn->put(dbi, "lmdb", "c");
txn.put(dbi, "mdb", "old name"); txn->put(dbi, "mdb", "old name");
string_view v1; string_view v1;
if(!txn.get(dbi, "mdb", v1)) { if(!txn->get(dbi, "mdb", v1)) {
cout<<v1<<endl; cout<<v1<<endl;
} }
else { else {
cout << "found nothing" << endl; cout << "found nothing" << endl;
} }
txn.commit(); txn->commit();
txn = env->getRWTransaction(); txn = env->getRWTransaction();
auto cursor = txn.getCursor(dbi); auto cursor = txn->getRWCursor(dbi);
MDBOutVal key, data; MDBOutVal key, data;

View File

@ -27,7 +27,7 @@ struct Record
static unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi) static unsigned int getMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
{ {
auto cursor = txn.getCursor(dbi); auto cursor = txn->getRWCursor(dbi);
MDBOutVal maxidval, maxcontent; MDBOutVal maxidval, maxcontent;
unsigned int maxid{0}; unsigned int maxid{0};
if(!cursor.get(maxidval, maxcontent, MDB_LAST)) { 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 ); boost::archive::binary_oarchive oa(oss,boost::archive::no_header );
oa << r; oa << r;
txn.put(records, r.id, oss.str(), MDB_APPEND); txn->put(records, r.id, oss.str(), MDB_APPEND);
txn.put(domainidx, r.domain_id, r.id); txn->put(domainidx, r.domain_id, r.id);
txn.put(nameidx, r.name, 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); store(txn, records, domainidx, nameidx, r);
} }
txn.commit(); txn->commit();
auto rotxn = env->getROTransaction(); auto rotxn = env->getROTransaction();
auto rotxn2 = env->getROTransaction(); auto rotxn2 = env->getROTransaction();
auto rocursor = rotxn.getCursor(nameidx); auto rocursor = rotxn->getCursor(nameidx);
MDBOutVal data; MDBOutVal data;
int count = 0; int count = 0;
@ -143,7 +143,7 @@ int main(int argc, char** argv)
cout<<"Got something: id="<<id<<endl; cout<<"Got something: id="<<id<<endl;
MDBOutVal record; MDBOutVal record;
if(!rotxn.get(records, data, record)) { if(!rotxn->get(records, data, record)) {
Record test; Record test;
stringstream istr{record.get<string>()}; stringstream istr{record.get<string>()};
boost::archive::binary_iarchive oi(istr,boost::archive::no_header ); 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"); MDBInVal key("counter");
auto rwtxn = env->getRWTransaction(); auto rwtxn = env->getRWTransaction();
rwtxn.put(main, "counter", "1234"); 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(12,13)), "hoi dan 12,13");
rwtxn.put(main, MDBInVal::fromStruct(std::make_pair(14,15)), rwtxn->put(main, MDBInVal::fromStruct(std::make_pair(14,15)),
MDBInVal::fromStruct(std::make_pair(20,23))); MDBInVal::fromStruct(std::make_pair(20,23)));
MDBOutVal out; 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; cout << "Got: " << out.get<string_view>() << endl;
else else
cout << "Got nothing!1"<<endl; 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>>(); auto res = out.get_struct<pair<int,int>>();
cout << "Got: " << res.first<<", "<<res.second << endl; cout << "Got: " << res.first<<", "<<res.second << endl;
} }
else else
cout << "Got nothing!1"<<endl; cout << "Got nothing!1"<<endl;
rwtxn.put(main, 12.12, 7.3); rwtxn->put(main, 12.12, 7.3);
if(!rwtxn.get(main, 12.12, out)) { if(!rwtxn->get(main, 12.12, out)) {
cout<<"Got: "<< out.get<double>() <<endl; cout<<"Got: "<< out.get<double>() <<endl;
} }
else else
cout << "Got nothing!1"<<endl; cout << "Got nothing!1"<<endl;
rwtxn.commit(); rwtxn->commit();
return 0; return 0;
if(argc==1) { if(argc==1) {
for(;;) { for(;;) {
auto rotxn = env->getROTransaction(); auto rotxn = env->getROTransaction();
MDBOutVal data; MDBOutVal data;
if(!rotxn.get(main, key, data)) { if(!rotxn->get(main, key, data)) {
cout<<"Counter is "<<data.get<unsigned int>() << endl; cout<<"Counter is "<<data.get<unsigned int>() << endl;
cout <<data.get<string>() << endl; cout <<data.get<string>() << endl;
cout<<data.get<string_view>() << endl; cout<<data.get<string_view>() << endl;
@ -73,10 +73,10 @@ int main(int argc, char** argv)
cout<<"Did resize"<<endl; cout<<"Did resize"<<endl;
} }
auto txn = env->getRWTransaction(); auto txn = env->getRWTransaction();
txn.put(main, key, MDBInVal(n)); txn->put(main, key, MDBInVal(n));
for(int k=0; k < 100; ++k) for(int k=0; k < 100; ++k)
txn.put(main, MDBInVal(n+1000*k), MDBInVal(n+1000*k)); txn->put(main, MDBInVal(n+1000*k), MDBInVal(n+1000*k));
txn.commit(); txn->commit();
} }
} }
} }

View File

@ -28,7 +28,7 @@ int main(int argc, char** argv)
limit = atoi(argv[1]); limit = atoi(argv[1]);
cout<<"Counting records.. "; cout.flush(); cout<<"Counting records.. "; cout.flush();
auto cursor=txn.getCursor(dbi); auto cursor = txn->getCursor(dbi);
MDBOutVal key, data; MDBOutVal key, data;
int count=0; int count=0;
while(!cursor.get(key, data, count ? MDB_NEXT : MDB_FIRST)) { 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<<"Have "<<count<<"!"<<endl;
cout<<"Clearing records.. "; cout.flush(); cout<<"Clearing records.. "; cout.flush();
mdb_drop(txn, dbi, 0); // clear records mdb_drop(*txn, dbi, 0); // clear records
cout<<"Done!"<<endl; cout<<"Done!"<<endl;
cout << "Adding "<<limit<<" values .. "; cout.flush(); cout << "Adding "<<limit<<" values .. "; cout.flush();
for(unsigned long n = 0 ; n < limit; ++n) { 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 <<"Done!"<<endl;
cout <<"Calling commit.. "; cout.flush(); cout <<"Calling commit.. "; cout.flush();
txn.commit(); txn->commit();
cout<<"Done!"<<endl; cout<<"Done!"<<endl;
} }

View File

@ -13,20 +13,20 @@ TEST_CASE("Most basic tests", "[mostbasic]") {
REQUIRE(1); REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE); MDBDbi main = env.openDB("", MDB_CREATE);
auto txn = env.getRWTransaction(); auto txn = env.getRWTransaction();
MDBOutVal out; MDBOutVal out;
REQUIRE(txn.get(main, "lmdb", out) == MDB_NOTFOUND); REQUIRE(txn->get(main, "lmdb", out) == MDB_NOTFOUND);
txn.put(main, "lmdb", "hot");
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"); REQUIRE(out.get<std::string>() == "hot");
txn.abort(); txn->abort();
auto rotxn = env.getROTransaction(); 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]") { TEST_CASE("Range tests", "[range]") {
@ -36,20 +36,20 @@ TEST_CASE("Range tests", "[range]") {
REQUIRE(1); REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE); MDBDbi main = env.openDB("", MDB_CREATE);
auto txn = env.getRWTransaction(); auto txn = env.getRWTransaction();
MDBOutVal out; MDBOutVal out;
REQUIRE(txn.get(main, "lmdb", out) == MDB_NOTFOUND); REQUIRE(txn->get(main, "lmdb", out) == MDB_NOTFOUND);
txn.put(main, "bert", "hubert"); txn->put(main, "bert", "hubert");
txn.put(main, "bertt", "1975"); txn->put(main, "bertt", "1975");
txn.put(main, "berthubert", "lmdb"); txn->put(main, "berthubert", "lmdb");
txn.put(main, "bert1", "one"); txn->put(main, "bert1", "one");
txn.put(main, "beru", "not"); txn->put(main, "beru", "not");
{ {
auto cursor = txn.getCursor(main); auto cursor = txn->getCursor(main);
MDBInVal bert("bert"); MDBInVal bert("bert");
MDBOutVal key, val; MDBOutVal key, val;
REQUIRE(cursor.lower_bound(bert, key, val) == 0); REQUIRE(cursor.lower_bound(bert, key, val) == 0);
@ -64,14 +64,14 @@ TEST_CASE("Range tests", "[range]") {
REQUIRE(key.get<string>() == "berthubert"); REQUIRE(key.get<string>() == "berthubert");
REQUIRE(val.get<string>() == "lmdb"); 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 rotxn = env.getROTransaction();
{ {
auto cursor = rotxn.getCursor(main); auto cursor = rotxn->getCursor(main);
MDBInVal bert("bert"); MDBInVal bert("bert");
MDBOutVal key, val; MDBOutVal key, val;
REQUIRE(cursor.lower_bound(bert, key, val) == 0); REQUIRE(cursor.lower_bound(bert, key, val) == 0);
@ -86,7 +86,244 @@ TEST_CASE("Range tests", "[range]") {
REQUIRE(key.get<string>() == "berthubert"); REQUIRE(key.get<string>() == "berthubert");
REQUIRE(val.get<string>() == "lmdb"); 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());
} }