#pragma once #include "./global.h" #include #include #include #include #include #include #include #include #include #include #include /*! * \brief The LMDBSafe namespace contains all classes/types contained by the lmdb-safe and * lmdb-typed libraries. * \remarks * - 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. */ namespace LMDBSafe { // apple compiler somehow has string_view even in c++11! #ifdef __cpp_lib_string_view using std::string_view; #else #include #if BOOST_VERSION > 105400 #include using boost::string_view; #else #include using string_view = boost::string_ref; #endif #endif /*! * \brief The LMDBError class is thrown when an error happens. */ class LMDB_SAFE_EXPORT LMDBError : public std::runtime_error { public: explicit LMDBError(const std::string &error) noexcept : std::runtime_error(error) , ec(0) { } explicit LMDBError(const std::string &context, int error) noexcept : std::runtime_error(context + mdb_strerror(error)) , ec(error) { } const int ec; }; /*! * \brief The MDBDbi class is our only 'value type' object as 1) a dbi is actually an integer * and 2) per LMDB documentation, we never close it. */ class LMDB_SAFE_EXPORT MDBDbi { public: MDBDbi() { d_dbi = std::numeric_limits::max(); } explicit MDBDbi(MDB_env *env, MDB_txn *txn, string_view dbname, unsigned int flags); operator const MDB_dbi &() const { return d_dbi; } MDB_dbi d_dbi; }; class MDBRWTransactionImpl; class MDBROTransactionImpl; using MDBROTransaction = std::unique_ptr; using MDBRWTransaction = std::unique_ptr; /*! * \brief The MDBEnv class is a handle to an MDB environment. */ class LMDB_SAFE_EXPORT MDBEnv { public: MDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs = 10); /*! * \brief Closes the MDB environment. * \remarks Only a single thread may call this function. All transactions, databases, and cursors must already be closed * before calling this function. */ ~MDBEnv() { mdb_env_close(d_env); } MDBDbi openDB(const string_view dbname, unsigned int flags); MDBRWTransaction getRWTransaction(); MDBROTransaction getROTransaction(); operator MDB_env *&() { return d_env; } MDB_env *d_env; int getRWTX(); void incRWTX(); void decRWTX(); int getROTX(); void incROTX(); void decROTX(); private: std::mutex d_openmut; std::mutex d_countmutex; std::map d_RWtransactionsOut; std::map d_ROtransactionsOut; }; /*! * \brief Opens an MDB environment for the specified database file. */ LMDB_SAFE_EXPORT std::shared_ptr getMDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs = 128); /*! * \brief The MDBOutVal struct is the handle to an MDB value used as output. */ struct LMDB_SAFE_EXPORT MDBOutVal { operator MDB_val &() { return d_mdbval; } template ::value, T>::type * = nullptr> T get() const { T ret; if (d_mdbval.mv_size != sizeof(T)) throw LMDBError("MDB data has wrong length for type"); memcpy(&ret, d_mdbval.mv_data, sizeof(T)); return ret; } template ::value, T>::type * = nullptr> T get() const; template T get_struct() const { T ret; if (d_mdbval.mv_size != sizeof(T)) throw LMDBError("MDB data has wrong length for type"); memcpy(&ret, d_mdbval.mv_data, sizeof(T)); return ret; } template const T *get_struct_ptr() const { if (d_mdbval.mv_size != sizeof(T)) throw LMDBError("MDB data has wrong length for type"); return reinterpret_cast(d_mdbval.mv_data); } MDB_val d_mdbval; }; template <> inline std::string MDBOutVal::get() const { return std::string(static_cast(d_mdbval.mv_data), d_mdbval.mv_size); } template <> inline string_view MDBOutVal::get() const { return string_view(static_cast(d_mdbval.mv_data), d_mdbval.mv_size); } /*! * \brief The MDBInVal struct is the handle to an MDB value used as input. */ class LMDB_SAFE_EXPORT MDBInVal { public: MDBInVal(const MDBOutVal &rhs) { d_mdbval = rhs.d_mdbval; } template ::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 = static_cast(const_cast(s)); } MDBInVal(string_view v) { d_mdbval.mv_size = v.size(); d_mdbval.mv_data = static_cast(const_cast(v.data())); } MDBInVal(const std::string &v) { d_mdbval.mv_size = v.size(); d_mdbval.mv_data = static_cast(const_cast(v.data())); } template static MDBInVal fromStruct(const T &t) { MDBInVal ret; ret.d_mdbval.mv_size = sizeof(T); ret.d_mdbval.mv_data = static_cast(&const_cast(t)); return ret; } operator MDB_val &() { return d_mdbval; } MDB_val d_mdbval; private: MDBInVal() { } char d_memory[sizeof(double)]; }; class MDBROCursor; /*! * \brief The MDBROTransactionImpl class wraps read operations. */ class LMDB_SAFE_EXPORT MDBROTransactionImpl { protected: MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn); private: static MDB_txn *openROTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags = 0); MDBEnv *d_parent; std::vector d_cursors; protected: MDB_txn *d_txn; void closeROCursors(); public: explicit MDBROTransactionImpl(MDBEnv *parent, unsigned int flags = 0); MDBROTransactionImpl(const MDBROTransactionImpl &src) = delete; MDBROTransactionImpl &operator=(const MDBROTransactionImpl &src) = delete; // The move constructor/operator cannot be made safe due to Object Slicing with MDBRWTransaction. MDBROTransactionImpl(MDBROTransactionImpl &&rhs) = delete; MDBROTransactionImpl &operator=(MDBROTransactionImpl &&rhs) = delete; virtual ~MDBROTransactionImpl(); virtual void abort(); virtual void commit(); int get(MDB_dbi dbi, const MDBInVal &key, MDBOutVal &val) { if (!d_txn) throw LMDBError("Attempt to use a closed RO transaction for get"); const auto rc = mdb_get(d_txn, dbi, const_cast(&key.d_mdbval), &val.d_mdbval); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Getting data: ", rc); return rc; } int get(MDB_dbi dbi, const MDBInVal &key, string_view &val) { MDBOutVal out; int rc = get(dbi, key, out); if (!rc) val = out.get(); return rc; } // this is something you can do, readonly MDBDbi openDB(string_view dbname, unsigned int flags) { return MDBDbi(d_parent->d_env, d_txn, dbname, flags); } MDBROCursor getCursor(const MDBDbi &); MDBROCursor getROCursor(const MDBDbi &); operator MDB_txn *() { return d_txn; } inline operator bool() const { return d_txn; } inline MDBEnv &environment() { return *d_parent; } }; /*! * \brief The MDBGenCursor class represents a MDB_cursor handle. * \remarks * - 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 MDBGenCursor { private: std::vector *d_registry; MDB_cursor *d_cursor; public: MDBGenCursor() : d_registry(nullptr) , d_cursor(nullptr) { } MDBGenCursor(std::vector ®istry, MDB_cursor *cursor) : d_registry(®istry) , d_cursor(cursor) { registry.emplace_back(static_cast(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(this); } else { d_registry->emplace_back(static_cast(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: int get(MDBOutVal &key, MDBOutVal &data, MDB_cursor_op op) { const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Unable to get from cursor: ", rc); return rc; } int find(const MDBInVal &in, MDBOutVal &key, MDBOutVal &data) { key.d_mdbval = in.d_mdbval; const auto rc = mdb_cursor_get(d_cursor, const_cast(&key.d_mdbval), &data.d_mdbval, MDB_SET); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Unable to find from cursor: ", rc); return rc; } int lower_bound(const MDBInVal &in, MDBOutVal &key, MDBOutVal &data) { key.d_mdbval = in.d_mdbval; const auto rc = mdb_cursor_get(d_cursor, const_cast(&key.d_mdbval), &data.d_mdbval, MDB_SET_RANGE); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Unable to lower_bound from cursor: ", rc); return rc; } int nextprev(MDBOutVal &key, MDBOutVal &data, MDB_cursor_op op) { const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Unable to prevnext from cursor: ", 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) { const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Unable to next from cursor: ", 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); } 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(this)); if (iter != d_registry->end()) { d_registry->erase(iter); } d_registry = nullptr; } if (d_cursor) { mdb_cursor_close(d_cursor); d_cursor = nullptr; } } }; /*! * \brief The MDBROCursor class represents a read-only cursor. */ class LMDB_SAFE_EXPORT MDBROCursor : public MDBGenCursor { public: MDBROCursor() = default; using MDBGenCursor::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; /*! * \brief The MDBRWTransactionImpl class wraps write operations. */ class LMDB_SAFE_EXPORT MDBRWTransactionImpl : public MDBROTransactionImpl { protected: MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn); private: static MDB_txn *openRWTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags); private: std::vector d_rw_cursors; void closeRWCursors(); inline void closeRORWCursors() { closeROCursors(); closeRWCursors(); } public: explicit MDBRWTransactionImpl(MDBEnv *parent, unsigned int flags = 0); MDBRWTransactionImpl(const MDBRWTransactionImpl &rhs) = delete; MDBRWTransactionImpl(MDBRWTransactionImpl &&rhs) = delete; MDBRWTransactionImpl &operator=(const MDBRWTransactionImpl &rhs) = delete; MDBRWTransactionImpl &operator=(MDBRWTransactionImpl &&rhs) = delete; ~MDBRWTransactionImpl() override; void commit() override; void abort() override; void clear(MDB_dbi dbi); void put(MDB_dbi dbi, const MDBInVal &key, const MDBInVal &val, unsigned int flags = 0) { if (!d_txn) throw LMDBError("Attempt to use a closed RW transaction for put"); if (const auto rc = mdb_put(d_txn, dbi, const_cast(&key.d_mdbval), const_cast(&val.d_mdbval), flags)) throw LMDBError("Putting data: ", rc); } int del(MDBDbi &dbi, const MDBInVal &key, const MDBInVal &val) { const auto rc = mdb_del(d_txn, dbi, const_cast(&key.d_mdbval), const_cast(&val.d_mdbval)); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Deleting data: ", rc); return rc; } int del(MDBDbi &dbi, const MDBInVal &key) { const auto rc = mdb_del(d_txn, dbi, const_cast(&key.d_mdbval), 0); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Deleting data: ", rc); return rc; } int get(MDBDbi &dbi, const MDBInVal &key, MDBOutVal &val) { if (!d_txn) throw LMDBError("Attempt to use a closed RW transaction for get"); const auto rc = mdb_get(d_txn, dbi, const_cast(&key.d_mdbval), &val.d_mdbval); if (rc && rc != MDB_NOTFOUND) throw LMDBError("Getting data: ", rc); return rc; } int get(MDBDbi &dbi, const MDBInVal &key, string_view &val) { MDBOutVal out; const auto rc = get(dbi, key, out); if (!rc) val = out.get(); return rc; } MDBDbi openDB(string_view dbname, unsigned int flags) { return MDBDbi(environment().d_env, d_txn, dbname, flags); } MDBRWCursor getRWCursor(const MDBDbi &); MDBRWCursor getCursor(const MDBDbi &); MDBRWTransaction getRWTransaction(); MDBROTransaction getROTransaction(); }; /*! * \brief The MDBRWCursor class implements RW operations based on MDBGenCursor. * \remarks * - "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 LMDB_SAFE_EXPORT MDBRWCursor : public MDBGenCursor { public: MDBRWCursor() = default; using MDBGenCursor::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) { if (const auto rc = mdb_cursor_put(*this, const_cast(&key.d_mdbval), const_cast(&data.d_mdbval), MDB_CURRENT)) throw LMDBError("Putting data via mdb_cursor_put: ", rc); } void put(const MDBOutVal &key, const MDBOutVal &data, unsigned int flags = 0) { if (const auto rc = mdb_cursor_put(*this, const_cast(&key.d_mdbval), const_cast(&data.d_mdbval), flags)) throw LMDBError("Putting data via mdb_cursor_put: ", rc); } void del(unsigned int flags = 0) { if (const auto rc = mdb_cursor_del(*this, flags)) throw LMDBError("Deleting data via mdb_cursor_del: ", rc); } }; } // namespace LMDBSafe