#pragma once #include "./lmdb-safe.hh" #include #include #include namespace LMDBSafe { /* Open issues: What is an error? What is an exception? could id=0 be magic? ('no such id') yes Perhaps use the separate index concept from multi_index perhaps get eiter to be of same type so for(auto& a : x) works make it more value "like" with unique_ptr */ /** Return the highest ID used in a database. Returns 0 for an empty DB. This makes us start everything at ID=1, which might make it possible to treat id 0 as special */ LMDB_SAFE_EXPORT unsigned int MDBGetMaxID(MDBRWTransaction &txn, MDBDbi &dbi); /** This is the serialization interface. You need to define your these functions for the types you'd like to store. */ template std::string serToString(const T &t); template void serFromString(string_view str, T &ret); // define some "shortcuts" (to avoid full-blown serialization stuff for trivial cases) template <> inline std::string serToString(const std::string_view &t) { return std::string(t); } template <> inline std::string serToString(const std::string &t) { return t; } template <> inline std::string serToString(const std::uint8_t &t) { return std::string(reinterpret_cast(&t), sizeof(t)); } template <> inline std::string serToString(const std::uint16_t &t) { auto str = std::string(sizeof(t), '\0'); CppUtilities::LE::getBytes(t, str.data()); return str; } template <> inline std::string serToString(const std::uint32_t &t) { auto str = std::string(sizeof(t), '\0'); CppUtilities::LE::getBytes(t, str.data()); return str; } template <> inline std::string serToString(const std::uint64_t &t) { auto str = std::string(sizeof(t), '\0'); CppUtilities::LE::getBytes(t, str.data()); return str; } template <> inline void serFromString(string_view str, std::string &ret) { ret = std::string(str); } template <> inline void serFromString<>(string_view str, std::uint8_t &ret) { if (str.size() != sizeof(ret)) { throw CppUtilities::ConversionException(CppUtilities::argsToString("value not 8-bit, got ", str.size(), " bytes instead")); } ret = static_cast(*str.data()); } template <> inline void serFromString<>(string_view str, std::uint16_t &ret) { if (str.size() != sizeof(ret)) { throw CppUtilities::ConversionException(CppUtilities::argsToString("value not 16-bit, got ", str.size(), " bytes instead")); } ret = CppUtilities::LE::toUInt16(str.data()); } template <> inline void serFromString<>(string_view str, std::uint32_t &ret) { if (str.size() != sizeof(ret)) { throw CppUtilities::ConversionException(CppUtilities::argsToString("value not 32-bit, got ", str.size(), " bytes instead")); } ret = CppUtilities::LE::toUInt32(str.data()); } template <> inline void serFromString<>(string_view str, std::uint64_t &ret) { if (str.size() != sizeof(ret)) { throw CppUtilities::ConversionException(CppUtilities::argsToString("value not 64-bit, got ", str.size(), " bytes instead")); } ret = CppUtilities::LE::toUInt64(str.data()); } /** This is the serialization interface for keys. You need to define your these functions for the types you'd like to use as keys. */ template inline std::string keyConv(const T &t); template ::value, T>::type * = nullptr> inline string_view keyConv(const T &t) { return string_view(reinterpret_cast(&t), sizeof(t)); } template ::value, T>::type * = nullptr> inline string_view keyConv(const T &t) { return t; } template ::value, T>::type * = nullptr> inline string_view keyConv(string_view t) { return t; } /*! * \brief The LMDBIndexOps struct implements index operations, but only the operations that * are broadcast to all indexes. * * Specifically, to deal with databases with less than the maximum number of interfaces, this * only includes calls that should be ignored for empty indexes. * * This class only needs methods that must happen for all indexes at once. So specifically, *not* * size or get. People ask for those themselves, and should no do that on indexes that * don't exist. */ template struct LMDB_SAFE_EXPORT LMDBIndexOps { explicit LMDBIndexOps(Parent *parent) : d_parent(parent) { } void put(MDBRWTransaction &txn, const Class &t, std::uint32_t id, unsigned int flags = 0) { txn->put(d_idx, keyConv(d_parent->getMember(t)), id, flags); } void del(MDBRWTransaction &txn, const Class &t, std::uint32_t id) { if (const auto rc = txn->del(d_idx, keyConv(d_parent->getMember(t)), id)) { throw LMDBError("Error deleting from index: ", rc); } } void clear(MDBRWTransaction &txn) { if (const auto rc = mdb_drop(*txn, d_idx, 0)) { throw LMDBError("Error clearing index: ", rc); } } void openDB(std::shared_ptr &env, string_view str, unsigned int flags) { d_idx = env->openDB(str, flags); } MDBDbi d_idx; Parent *d_parent; }; /** This is an index on a field in a struct, it derives from the LMDBIndexOps */ template struct index_on : LMDBIndexOps> { index_on() : LMDBIndexOps>(this) { } static Type getMember(const Class &c) { return c.*PtrToMember; } typedef Type type; }; /** This is a calculated index */ template struct index_on_function : LMDBIndexOps> { index_on_function() : LMDBIndexOps>(this) { } static Type getMember(const Class &c) { Func f; return f(c); } typedef Type type; }; /*! * \brief The TypedDBI class is the main class. * \tparam T Specifies the type to store within the database. * \tparam I Specifies an index, should be an instantiation of index_on. */ template class LMDB_SAFE_EXPORT TypedDBI { public: // declare tuple for indexes using tuple_t = std::tuple; template using index_t = typename std::tuple_element_t::type; private: tuple_t d_tuple; template struct IndexIterator { static inline void apply(Tuple &tuple, auto &&func) { IndexIterator::apply(tuple, std::forward(func)); func(std::get(tuple)); } }; template struct IndexIterator { static inline void apply(Tuple &tuple, auto &&func) { func(std::get<0>(tuple)); } }; template struct IndexIterator { static inline void apply(Tuple &tuple, auto &&func) { CPP_UTILITIES_UNUSED(tuple) CPP_UTILITIES_UNUSED(func) } }; void forEachIndex(auto &&func) { IndexIterator>::apply(d_tuple, std::forward(func)); } public: TypedDBI(std::shared_ptr env, string_view name) : d_env(env) , d_name(name) { d_main = d_env->openDB(name, MDB_CREATE | MDB_INTEGERKEY); std::size_t index = 0; forEachIndex([&](auto &&i) { i.openDB(d_env, CppUtilities::argsToString(name, '_', index++), MDB_CREATE | MDB_DUPFIXED | MDB_DUPSORT); }); } // We support readonly and rw transactions. Here we put the Readonly operations // which get sourced by both kinds of transactions template struct ReadonlyOperations { ReadonlyOperations(Parent &parent) : d_parent(parent) { } //! Number of entries in main database std::size_t size() { MDB_stat stat; mdb_stat(**d_parent.d_txn, d_parent.d_parent->d_main, &stat); return stat.ms_entries; } //! Number of entries in the various indexes - should be the same template std::size_t size() { MDB_stat stat; mdb_stat(**d_parent.d_txn, std::get(d_parent.d_parent->d_tuple).d_idx, &stat); return stat.ms_entries; } //! Get item with id, from main table directly bool get(std::uint32_t id, T &t) { MDBOutVal data; if ((*d_parent.d_txn)->get(d_parent.d_parent->d_main, id, data)) return false; serFromString(data.get(), t); return true; } //! Get item through index N, then via the main database template std::uint32_t get(const index_t &key, T &out) { MDBOutVal id; if (!(*d_parent.d_txn)->get(std::get(d_parent.d_parent->d_tuple).d_idx, keyConv(key), id)) { if (get(id.get(), out)) return id.get(); } return 0; } //! Cardinality of index N template std::uint32_t cardinality() { auto cursor = (*d_parent.d_txn)->getCursor(std::get(d_parent.d_parent->d_tuple).d_idx); bool first = true; MDBOutVal key, data; std::uint32_t count = 0; while (!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT_NODUP)) { ++count; first = false; } return count; } //! End iderator type struct eiter_t { }; //! Store the object as immediate member of iter_t (as opposed to using an std::unique_ptr or std::shared_ptr) template struct DirectStorage { }; // can be on main, or on an index // when on main, return data directly // when on index, indirect // we can be limited to one key, or iterate over entire database // iter requires you to put the cursor in the right place first! template