lmdb-safe/lmdb-safe.hh

636 lines
15 KiB
C++
Raw Normal View History

#pragma once
2018-12-07 13:52:17 +01:00
#include <lmdb.h>
#include <iostream>
#include <fstream>
#include <set>
2018-12-07 18:17:03 +01:00
#include <map>
#include <thread>
#include <memory>
2018-12-10 14:51:02 +01:00
#include <string>
#include <string.h>
2018-12-08 14:08:26 +01:00
#include <mutex>
#include <vector>
#include <algorithm>
2018-12-07 13:52:17 +01:00
// apple compiler somehow has string_view even in c++11!
#if __cplusplus < 201703L && !defined(__APPLE__)
2018-12-28 22:02:29 +01:00
#include <boost/version.hpp>
2018-12-28 19:14:00 +01:00
#if BOOST_VERSION > 105400
#include <boost/utility/string_view.hpp>
using boost::string_view;
#else
2018-12-28 19:14:00 +01:00
#include <boost/utility/string_ref.hpp>
using string_view = boost::string_ref;
#endif
#else // C++17
using std::string_view;
#endif
2018-12-07 13:52:17 +01:00
/* open issues:
*
* - missing convenience functions (string_view, string)
*/
/*
The error strategy. Anything that "should never happen" turns into an exception. But things like 'duplicate entry' or 'no such key' are for you to deal with.
*/
/*
Thread safety: we are as safe as lmdb. You can talk to MDBEnv from as many threads as you want
*/
2018-12-08 14:08:26 +01:00
/** MDBDbi is our only 'value type' object, as 1) a dbi is actually an integer
and 2) per LMDB documentation, we never close it. */
2018-12-07 13:52:17 +01:00
class MDBDbi
{
public:
MDBDbi()
{
d_dbi = -1;
}
explicit MDBDbi(MDB_env* env, MDB_txn* txn, string_view dbname, int flags);
2018-12-07 13:52:17 +01:00
operator const MDB_dbi&() const
{
return d_dbi;
}
MDB_dbi d_dbi;
};
class MDBRWTransaction;
class MDBROTransaction;
class MDBEnv
{
public:
MDBEnv(const char* fname, int flags, int mode);
2018-12-07 13:52:17 +01:00
~MDBEnv()
{
// Only a single thread may call this function. All transactions, databases, and cursors must already be closed before calling this function
mdb_env_close(d_env);
// but, elsewhere, docs say database handles do not need to be closed?
}
MDBDbi openDB(const string_view dbname, int flags);
2018-12-07 13:52:17 +01:00
MDBRWTransaction getRWTransaction();
MDBROTransaction getROTransaction();
operator MDB_env*& ()
{
return d_env;
}
MDB_env* d_env;
2018-12-08 14:08:26 +01:00
int getRWTX();
void incRWTX();
void decRWTX();
int getROTX();
void incROTX();
void decROTX();
private:
std::mutex d_openmut;
std::mutex d_countmutex;
2018-12-07 18:17:03 +01:00
std::map<std::thread::id, int> d_RWtransactionsOut;
std::map<std::thread::id, int> d_ROtransactionsOut;
2018-12-07 13:52:17 +01:00
};
std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode);
2018-12-07 18:17:03 +01:00
2018-12-10 14:51:02 +01:00
struct MDBOutVal
{
operator MDB_val&()
{
return d_mdbval;
}
template <class T,
typename std::enable_if<std::is_arithmetic<T>::value,
2019-01-04 20:51:04 +01:00
T>::type* = nullptr> const
2018-12-10 14:51:02 +01:00
T get()
{
T ret;
if(d_mdbval.mv_size != sizeof(T))
throw std::runtime_error("MDB data has wrong length for type");
2018-12-10 14:51:02 +01:00
memcpy(&ret, d_mdbval.mv_data, sizeof(T));
return ret;
}
template <class T,
typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr>
2019-01-04 20:51:04 +01:00
T get() const;
2018-12-10 14:51:02 +01:00
template<class T>
2019-01-04 20:51:04 +01:00
T get_struct() const
2018-12-10 14:51:02 +01:00
{
T ret;
if(d_mdbval.mv_size != sizeof(T))
throw std::runtime_error("MDB data has wrong length for type");
2018-12-10 14:51:02 +01:00
memcpy(&ret, d_mdbval.mv_data, sizeof(T));
return ret;
}
2019-06-24 11:16:03 +02:00
template<class T>
const T* get_struct_ptr() const
{
if(d_mdbval.mv_size != sizeof(T))
throw std::runtime_error("MDB data has wrong length for type");
return reinterpret_cast<const T*>(d_mdbval.mv_data);
}
2018-12-10 14:51:02 +01:00
MDB_val d_mdbval;
};
2019-01-04 20:51:04 +01:00
template<> inline std::string MDBOutVal::get<std::string>() const
2018-12-10 14:51:02 +01:00
{
return std::string((char*)d_mdbval.mv_data, d_mdbval.mv_size);
}
2019-01-04 20:51:04 +01:00
template<> inline string_view MDBOutVal::get<string_view>() const
2018-12-10 14:51:02 +01:00
{
return string_view((char*)d_mdbval.mv_data, d_mdbval.mv_size);
2018-12-10 14:51:02 +01:00
}
class MDBInVal
{
public:
MDBInVal(const MDBOutVal& rhs)
{
d_mdbval = rhs.d_mdbval;
}
2018-12-15 21:06:47 +01:00
2018-12-10 14:51:02 +01:00
template <class T,
typename std::enable_if<std::is_arithmetic<T>::value,
T>::type* = nullptr>
MDBInVal(T i)
{
memcpy(&d_memory[0], &i, sizeof(i));
d_mdbval.mv_size = sizeof(T);
d_mdbval.mv_data = d_memory;;
}
MDBInVal(const char* s)
{
d_mdbval.mv_size = strlen(s);
d_mdbval.mv_data = (void*)s;
}
MDBInVal(const string_view& v)
{
d_mdbval.mv_size = v.size();
d_mdbval.mv_data = (void*)&v[0];
}
MDBInVal(const std::string& v)
2018-12-10 15:02:36 +01:00
{
d_mdbval.mv_size = v.size();
d_mdbval.mv_data = (void*)&v[0];
}
2018-12-10 14:51:02 +01:00
template<typename T>
static MDBInVal fromStruct(const T& t)
{
MDBInVal ret;
ret.d_mdbval.mv_size = sizeof(T);
ret.d_mdbval.mv_data = (void*)&t;
return ret;
}
operator MDB_val&()
{
return d_mdbval;
}
MDB_val d_mdbval;
private:
MDBInVal(){}
char d_memory[sizeof(double)];
};
2018-12-07 13:52:17 +01:00
class MDBROCursor;
class MDBROTransaction
{
protected:
MDBROTransaction(MDBEnv *parent, MDB_txn *txn);
private:
static MDB_txn *openROTransaction(MDBEnv *env, MDB_txn *parent, int flags=0);
MDBEnv* d_parent;
std::unique_ptr<std::vector<MDBROCursor*>> d_cursors;
protected:
MDB_txn* d_txn;
void closeROCursors();
2018-12-07 13:52:17 +01:00
public:
explicit MDBROTransaction(MDBEnv* parent, int flags=0);
2018-12-07 13:52:17 +01:00
MDBROTransaction(const MDBROTransaction& src) = delete;
MDBROTransaction &operator=(const MDBROTransaction& src) = delete;
2018-12-07 13:52:17 +01:00
MDBROTransaction(MDBROTransaction&& rhs) noexcept:
d_parent(rhs.d_parent),
d_cursors(std::move(rhs.d_cursors)),
d_txn(rhs.d_txn)
2018-12-07 13:52:17 +01:00
{
rhs.d_parent = nullptr;
rhs.d_txn = nullptr;
2018-12-07 13:52:17 +01:00
}
MDBROTransaction &operator=(MDBROTransaction &&rhs) noexcept
2018-12-07 13:52:17 +01:00
{
if (d_txn) {
abort();
}
d_parent = rhs.d_parent;
d_txn = rhs.d_txn;
d_cursors = std::move(d_cursors);
rhs.d_txn = nullptr;
rhs.d_parent = nullptr;
return *this;
2018-12-07 13:52:17 +01:00
}
/* ensure that we cannot move from subclasses, because that would be massively
* unsafe. */
template<typename T, typename _ = typename std::enable_if<std::is_base_of<MDBROTransaction, T>::value>::type>
MDBROTransaction(T&& rhs) = delete;
virtual ~MDBROTransaction();
virtual void abort();
virtual void commit();
2018-12-07 13:52:17 +01:00
2018-12-10 14:51:02 +01:00
int get(MDB_dbi dbi, const MDBInVal& key, MDBOutVal& val)
2018-12-07 13:52:17 +01:00
{
2018-12-08 14:08:26 +01:00
if(!d_txn)
throw std::runtime_error("Attempt to use a closed RO transaction for get");
2018-12-10 14:51:02 +01:00
int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval),
const_cast<MDB_val*>(&val.d_mdbval));
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc)));
return rc;
2018-12-07 13:52:17 +01:00
}
2018-12-10 14:51:02 +01:00
int get(MDB_dbi dbi, const MDBInVal& key, string_view& val)
{
MDBOutVal out;
int rc = get(dbi, key, out);
if(!rc)
val = out.get<string_view>();
2018-12-10 14:51:02 +01:00
return rc;
}
2018-12-07 13:52:17 +01:00
// this is something you can do, readonly
MDBDbi openDB(string_view dbname, int flags)
2018-12-07 13:52:17 +01:00
{
2018-12-27 17:49:41 +01:00
return MDBDbi( d_parent->d_env, d_txn, dbname, flags);
2018-12-07 13:52:17 +01:00
}
MDBROCursor getCursor(const MDBDbi&);
MDBROCursor getROCursor(const MDBDbi&);
2018-12-07 14:16:21 +01:00
operator MDB_txn*()
2018-12-07 13:52:17 +01:00
{
return d_txn;
2018-12-07 13:52:17 +01:00
}
inline operator bool() const {
2018-12-07 13:52:17 +01:00
return d_txn;
}
inline MDBEnv &environment()
{
return *d_parent;
}
2018-12-07 13:52:17 +01:00
};
/*
A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends. It can be reused with mdb_cursor_renew() before finally closing it.
"If the parent transaction commits, the cursor must not be used again."
*/
template<class Transaction, class T>
class MDBGenCursor
2018-12-07 13:52:17 +01:00
{
private:
std::vector<T*> *d_registry;
MDB_cursor* d_cursor;
public:
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)
{
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();
}
2018-12-07 13:52:17 +01:00
public:
2018-12-10 14:51:02 +01:00
int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
2018-12-07 13:52:17 +01:00
{
int rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("Unable to get from cursor: " + std::string(mdb_strerror(rc)));
return rc;
2018-12-07 13:52:17 +01:00
}
2018-12-08 14:08:26 +01:00
int find(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data)
{
key.d_mdbval = in.d_mdbval;
int rc=mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("Unable to find from cursor: " + std::string(mdb_strerror(rc)));
return rc;
}
int lower_bound(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data)
{
key.d_mdbval = in.d_mdbval;
int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET_RANGE);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("Unable to lower_bound from cursor: " + std::string(mdb_strerror(rc)));
return rc;
}
int nextprev(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
{
int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("Unable to prevnext from cursor: " + std::string(mdb_strerror(rc)));
return rc;
}
int next(MDBOutVal& key, MDBOutVal& data)
{
return nextprev(key, data, MDB_NEXT);
}
int prev(MDBOutVal& key, MDBOutVal& data)
{
return nextprev(key, data, MDB_PREV);
}
int currentlast(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
{
int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("Unable to next from cursor: " + std::string(mdb_strerror(rc)));
return rc;
}
int current(MDBOutVal& key, MDBOutVal& data)
{
return currentlast(key, data, MDB_GET_CURRENT);
}
int last(MDBOutVal& key, MDBOutVal& data)
{
return currentlast(key, data, MDB_LAST);
}
2019-01-09 10:30:59 +01:00
int first(MDBOutVal& key, MDBOutVal& data)
{
return currentlast(key, data, MDB_FIRST);
}
operator MDB_cursor*()
{
return d_cursor;
}
operator bool() const
{
return d_cursor;
}
void close()
{
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<MDBROTransaction, MDBROCursor>
{
public:
using MDBGenCursor<MDBROTransaction, MDBROCursor>::MDBGenCursor;
MDBROCursor(const MDBROCursor &src) = delete;
MDBROCursor(MDBROCursor &&src) = default;
MDBROCursor &operator=(const MDBROCursor &src) = delete;
MDBROCursor &operator=(MDBROCursor &&src) = default;
~MDBROCursor() = default;
2018-12-07 13:52:17 +01:00
};
class MDBRWCursor;
class MDBRWTransaction: public MDBROTransaction
2018-12-07 13:52:17 +01:00
{
private:
static MDB_txn *openRWTransaction(MDBEnv* env, MDB_txn *parent, int flags);
private:
std::unique_ptr<std::vector<MDBRWCursor*>> d_rw_cursors;
void closeRWCursors();
inline void closeRORWCursors() {
closeROCursors();
closeRWCursors();
}
2018-12-07 13:52:17 +01:00
public:
explicit MDBRWTransaction(MDBEnv* parent, int flags=0);
2018-12-07 13:52:17 +01:00
MDBRWTransaction(MDBRWTransaction&& rhs) noexcept:
MDBROTransaction(std::move(static_cast<MDBROTransaction&>(rhs))),
d_rw_cursors(std::move(rhs.d_rw_cursors))
2018-12-07 13:52:17 +01:00
{
2018-12-07 13:52:17 +01:00
}
MDBRWTransaction &operator=(MDBRWTransaction&& rhs) noexcept
2018-12-07 13:52:17 +01:00
{
MDBROTransaction::operator=(std::move(static_cast<MDBROTransaction&>(rhs)));
d_rw_cursors = std::move(rhs.d_rw_cursors);
2018-12-07 13:52:17 +01:00
return *this;
}
~MDBRWTransaction() override;
2018-12-07 13:52:17 +01:00
void commit() override;
void abort() override;
2018-12-07 13:52:17 +01:00
2018-12-09 14:37:08 +01:00
void clear(MDB_dbi dbi);
2018-12-10 15:02:36 +01:00
void put(MDB_dbi dbi, const MDBInVal& key, const MDBInVal& val, int flags=0)
2018-12-07 13:52:17 +01:00
{
2018-12-08 14:08:26 +01:00
if(!d_txn)
throw std::runtime_error("Attempt to use a closed RW transaction for put");
2018-12-07 13:52:17 +01:00
int rc;
2018-12-10 15:02:36 +01:00
if((rc=mdb_put(d_txn, dbi,
const_cast<MDB_val*>(&key.d_mdbval),
const_cast<MDB_val*>(&val.d_mdbval), flags)))
2018-12-07 13:52:17 +01:00
throw std::runtime_error("putting data: " + std::string(mdb_strerror(rc)));
}
int del(MDBDbi& dbi, const MDBInVal& key, const MDBInVal& val)
2018-12-10 15:02:36 +01:00
{
int rc;
rc=mdb_del(d_txn, dbi, (MDB_val*)&key.d_mdbval, (MDB_val*)&val.d_mdbval);
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("deleting data: " + std::string(mdb_strerror(rc)));
return rc;
2018-12-10 15:02:36 +01:00
}
2018-12-07 13:52:17 +01:00
int del(MDBDbi& dbi, const MDBInVal& key)
2018-12-07 13:52:17 +01:00
{
int rc;
rc=mdb_del(d_txn, dbi, (MDB_val*)&key.d_mdbval, 0);
2018-12-07 13:52:17 +01:00
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("deleting data: " + std::string(mdb_strerror(rc)));
return rc;
}
2018-12-10 14:51:02 +01:00
int get(MDBDbi& dbi, const MDBInVal& key, MDBOutVal& val)
2018-12-07 13:52:17 +01:00
{
2018-12-08 14:08:26 +01:00
if(!d_txn)
throw std::runtime_error("Attempt to use a closed RW transaction for get");
2018-12-08 14:08:26 +01:00
2018-12-10 14:51:02 +01:00
int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval),
const_cast<MDB_val*>(&val.d_mdbval));
if(rc && rc != MDB_NOTFOUND)
throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc)));
return rc;
2018-12-07 13:52:17 +01:00
}
int get(MDBDbi& dbi, const MDBInVal& key, string_view& val)
2018-12-10 14:51:02 +01:00
{
MDBOutVal out;
int rc = get(dbi, key, out);
if(!rc)
val = out.get<string_view>();
2018-12-10 14:51:02 +01:00
return rc;
}
2018-12-07 13:52:17 +01:00
2018-12-27 17:49:41 +01:00
MDBDbi openDB(string_view dbname, int flags)
2018-12-07 13:52:17 +01:00
{
return MDBDbi(environment().d_env, d_txn, dbname, flags);
2018-12-07 13:52:17 +01:00
}
MDBRWCursor getRWCursor(const MDBDbi&);
2018-12-07 13:52:17 +01:00
MDBRWCursor getCursor(const MDBDbi&);
};
/* "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, MDBRWCursor>
2018-12-07 13:52:17 +01:00
{
public:
using MDBGenCursor<MDBRWTransaction, MDBRWCursor>::MDBGenCursor;
MDBRWCursor(const MDBRWCursor &src) = default;
MDBRWCursor(MDBRWCursor &&src) = default;
MDBRWCursor &operator=(const MDBRWCursor &src) = default;
MDBRWCursor &operator=(MDBRWCursor &&src) = default;
~MDBRWCursor() = default;
void put(const MDBOutVal& key, const MDBInVal& data)
2018-12-07 13:52:17 +01:00
{
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)));
2018-12-07 13:52:17 +01:00
}
2018-12-10 17:42:25 +01:00
2018-12-10 15:02:36 +01:00
int put(const MDBOutVal& key, const MDBOutVal& data, int flags=0)
2018-12-07 13:52:17 +01:00
{
// XXX check errors
return mdb_cursor_put(*this,
2018-12-10 15:02:36 +01:00
const_cast<MDB_val*>(&key.d_mdbval),
const_cast<MDB_val*>(&data.d_mdbval), flags);
2018-12-07 13:52:17 +01:00
}
int del(int flags=0)
2018-12-07 13:52:17 +01:00
{
return mdb_cursor_del(*this, flags);
2018-12-07 13:52:17 +01:00
}
2018-12-07 13:52:17 +01:00
};