/* * LMDB Abstraction Layer. * Copyright (C) 2023 Yury Gubich * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "storage.h" #define UNUSED(x) (void)(x) /** * \class LMDBAL::iStorage * * \brief Storage interface * * This is a interface-like class, it's designed to be an inner database interface to * be used as a polymorphic entity, and provide protected interaction with the database * from the heirs code */ /** * \brief Constructs a storage interface * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage * \param[in] duplicates - true if key duplicates are allowed (false by default) */ LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates): dbi(), db(parent), name(name), duplicates(duplicates) {} /** * \brief Destroys a storage interface */ LMDBAL::iStorage::~iStorage() {} /** * \brief A private virtual function I need to close each storage in the database */ void LMDBAL::iStorage::close() { mdb_dbi_close(db->environment, dbi); } /** * \brief Checks if the transaction is still active, returns inner TransactionID * * This method is for internal usage only * * \param[in] txn - a transaction, you want to extract ID from * \param[in] action - a description of what you're going to do, in case of exception the error description will be verbose * \returns - Transaction inner TransactionID * * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& txn, const std::string& action) const { if (!txn.isActive()) throw TransactionTerminated(db->name, name, action); return txn.txn; } /** * \brief Drops content of a storage interface * * Designed to drop storage content * * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); TransactionID txn = beginTransaction(); int rc = iStorage::drop(txn); if (rc != MDB_SUCCESS) { abortTransaction(txn); throw Unknown(db->name, mdb_strerror(rc), name); } db->commitTransaction(txn); handleDrop(); } /** * \brief Drops content of a storage interface (transaction variant) * * Just performs content drop * * \param[in] transaction - transaction ID, must be writable transaction! * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise */ int LMDBAL::iStorage::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } /** * \brief Drops content of a storage interface (public transaction variant) * * Just performs content drop * * \param[in] txn - transaction ID, must be writable transaction! * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise * * \exception LMDBAL::TransactionTerminated thrown if the transaction was not active */ int LMDBAL::iStorage::drop(const WriteTransaction& txn) { ensureOpened(dropMethodName); return drop(extractTransactionId(txn, dropMethodName)); } /** * \brief Helper function, thows exception if the database is not opened * * \param[in] methodName - name of the method this function is called from, just for display in std::exception::what() message * * \exception LMDBAL::Closed thrown if the database was closed */ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { if (!isDBOpened()) throw Closed(methodName, db->name, name); } /** * \brief Storage size * * \returns amount of records in the storage * * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ LMDBAL::SizeType LMDBAL::iStorage::count() const { ensureOpened(countMethodName); TransactionID txn = beginReadOnlyTransaction(); SizeType amount; try { amount = count(txn); } catch (...) { abortTransaction(txn); throw; } abortTransaction(txn); return amount; } /** * \brief Storage size (private transaction variant) * * \param[in] txn - transaction ID, can be read-only transaction * \returns amount of records in the storage * * \exception LMDBAL::Unknown thrown if something unexpected happened */ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { MDB_stat stat; int rc = mdb_stat(txn, dbi, &stat); if (rc != MDB_SUCCESS) throw Unknown(db->name, mdb_strerror(rc), name); return stat.ms_entries; } /** * \brief Storage size (public transaction variant) * * \param[in] txn - transaction, can be read-only transaction * \returns amount of records in the storage * * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const { ensureOpened(countMethodName); return count(extractTransactionId(txn, countMethodName)); } /** * \brief Throws LMDBAL::Exist or LMDBAL::Unknown (transaction vairiant) * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * \param[in] txn - transaction ID to be aborted, any transaction * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST */ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); throwDuplicateOrUnknown(rc, key); } /** * \brief Throws LMDBAL::NotFound or LMDBAL::Unknown (transaction vairiant) * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * \param[in] txn - transaction ID to be aborted, any transaction * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND */ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { abortTransaction(txn); throwNotFoundOrUnknown(rc, key); } /** * \brief Throws LMDBAL::Exist or LMDBAL::Unknown * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST */ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) const { if (rc == MDB_KEYEXIST) throwDuplicate(key); else throwUnknown(rc); } /** * \brief Throws LMDBAL::NotFound or LMDBAL::Unknown (transaction variant) * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND */ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) const { if (rc == MDB_NOTFOUND) throwNotFound(key); else throwUnknown(rc); } /** * \brief Throws LMDBAL::Unknown (transaction vairiant) * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * \param[in] txn - transaction ID to be aborted, any transaction * * \exception LMDBAL::Unknown thrown everytime */ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { abortTransaction(txn); throwUnknown(rc); } /** * \brief Database name * * Ment to be used in heirs, to provide some sort of interface to acces to some of the database information * * \returns database name */ const std::string & LMDBAL::iStorage::dbName() const { return db->name;} /** * \brief Is database opened * * Ment to be used in heirs, to provide some sort of interface to acces to some of the database information * * \returns true if database is ipened, false otherwise */ bool LMDBAL::iStorage::isDBOpened() const { return db->opened;} /** * \brief Throws LMDBAL::Unknown * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] rc - result of lmdb low level operation * * \exception LMDBAL::Unknown thrown everytime */ void LMDBAL::iStorage::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} /** * \brief Throws LMDBAL::Unknown * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] message - a message you wish to appear in the exception reason * * \exception LMDBAL::Unknown thrown everytime */ void LMDBAL::iStorage::throwUnknown(const std::string& message) const { throw Unknown(db->name, message, name);} /** * \brief Throws LMDBAL::Exist * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::Exist thrown everytime */ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { throw Exist(key, db->name, name);} /** * \brief Throws LMDBAL::NotFound * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] key - requested key string representation, just to show in std::exception::what() message * * \exception LMDBAL::NotFound thrown everytime */ void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} /** * \brief Throws LMDBAL::CursorNotReady * * Helper function ment to be used in heirs and reduce the code a bit * * \param[in] method - called cursor method name, just to show in std::exception::what() message * * \exception LMDBAL::CursorNotReady thrown everytime */ void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { throw CursorNotReady(method, db->name, name);} /** * \brief Begins read-only transaction * * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message * * \returns read only transaction */ LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { return db->beginPrivateReadOnlyTransaction(name);} /** * \brief Begins writable transaction * * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message * * \returns read only transaction */ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { return db->beginPrivateTransaction(name);} /** * \brief Aborts transaction * * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message */ void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { db->abortPrivateTransaction(id, name);} /** * \brief Commits transaction * * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message * * \exception LMDBAL::Unknown thrown if something unexpected happened */ void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { db->commitPrivateTransaction(id, name);} /** * \brief called on beginning of public transaction * * This function is called on every storage of the database * when user calls LMDBAL::Base::beginTransaction() or LMDBAL::Base::beginReadOnlyTransaction() * * This function is met to be reimplemented in heirs * if the heir code requires some transaction custom handling * * \param[in] txn - ID of started transaction * \param[in] readOnly - true if transaction is read-only, false otherwise */ void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { UNUSED(txn); UNUSED(readOnly); } /** * \brief called on commitment of public transaction * * This function is called on every storage of the database * when user calls LMDBAL::Base::commitTransaction(LMDBAL::TransactionID) * * This function is met to be reimplemented in heirs * if the heir code requires some transaction custom handling * * \param[in] txn - ID of started transaction */ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { UNUSED(txn);} /** * \brief called on abortion of public transaction * * This function is called on every storage of the database * when user calls LMDBAL::Base::abortTransaction(LMDBAL::TransactionID) * * This function is met to be reimplemented in heirs * if the heir code requires some transaction custom handling * * \param[in] txn - ID of started transaction */ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} /** * \brief A method where database additionally handles drop * * It's a protected method that is called to optimise drop process * after the transaction is commited. Used just for optimisations. */ void LMDBAL::iStorage::handleDrop() {} int LMDBAL::iStorage::_mdbOpen(MDB_txn *txn, unsigned int flags) { return mdb_dbi_open(txn, name.c_str(), flags, &dbi); } int LMDBAL::iStorage::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) { return mdb_put(txn, dbi, &key, &data, flags); } int LMDBAL::iStorage::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const { return mdb_get(txn, dbi, &key, &data); } int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key) { return mdb_del(txn, dbi, &key, NULL); } int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) { return mdb_del(txn, dbi, &key, &data); } int LMDBAL::iStorage::_mdbStat(MDB_txn* txn, MDB_stat& stat) const { return mdb_stat(txn, dbi, &stat); } int LMDBAL::iStorage::_mdbFlags(MDB_txn* txn, uint32_t& flags) const { return mdb_dbi_flags(txn, dbi, &flags); } int LMDBAL::iStorage::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { return mdb_cursor_open(txn, dbi, cursor); } void LMDBAL::iStorage::_mdbCursorClose(MDB_cursor *cursor) const { mdb_cursor_close(cursor); } int LMDBAL::iStorage::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const { return mdb_cursor_get(cursor, &key, &data, operation); } int LMDBAL::iStorage::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const { return mdb_cursor_get(cursor, &key, NULL, MDB_SET); } int LMDBAL::iStorage::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) { return mdb_cursor_del(cursor, flags); } int LMDBAL::iStorage::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) { return mdb_cursor_put(cursor, &key, &data, flags); } int LMDBAL::iStorage::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const { return mdb_cursor_renew(txn, cursor); } MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const { return mdb_cursor_txn(cursor); }