From bfb1d007ad8f07adbb4528fc564b9c62f0bc7ad3 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 25 Dec 2024 19:19:32 +0200 Subject: [PATCH] Cursors refactoring part one --- README.md | 4 +- src/CMakeLists.txt | 7 +- src/base.cpp | 20 +- src/base.h | 10 +- src/cache.hpp | 48 ++-- src/cursor.h | 45 +--- src/cursor.hpp | 333 ++---------------------- src/cursorcommon.cpp | 342 +++++++++++++++++++++++++ src/cursorcommon.h | 90 +++++++ src/icursor.h | 32 --- src/storage.h | 108 +------- src/storage.hpp | 84 +----- src/{storage.cpp => storagecommon.cpp} | 106 ++++---- src/storagecommon.h | 139 ++++++++++ src/storagecommon.hpp | 99 +++++++ src/transaction.cpp | 4 +- src/transaction.h | 14 +- test/cachetransaction.cpp | 8 +- test/storagetransaction.cpp | 8 +- 19 files changed, 824 insertions(+), 677 deletions(-) create mode 100644 src/cursorcommon.cpp create mode 100644 src/cursorcommon.h delete mode 100644 src/icursor.h rename src/{storage.cpp => storagecommon.cpp} (78%) create mode 100644 src/storagecommon.h create mode 100644 src/storagecommon.hpp diff --git a/README.md b/README.md index 6046d65..1226357 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # LMDBAL - Lightning Memory Data Base Abstraction Level [![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) -[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt5/) -[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt6/) +[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square&label=lmdbal-qt5)](https://aur.archlinux.org/packages/lmdbal-qt5/) +[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square&label=lmdbal-qt6)](https://aur.archlinux.org/packages/lmdbal-qt6/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) [![Documentation](https://img.shields.io/badge/Documentation-HTML-green)](https://macaw.me/lmdbal/doc/html) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a717e80..9c53f6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,9 @@ set(SOURCES exceptions.cpp - storage.cpp + storagecommon.cpp base.cpp transaction.cpp + cursorcommon.cpp ) set(HEADERS @@ -10,13 +11,15 @@ set(HEADERS exceptions.h storage.h storage.hpp + storagecommon.h + storagecommon.hpp cursor.h cursor.hpp + cursorcommon.h cache.h cache.hpp operators.hpp transaction.h - icursor.h ) target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES}) diff --git a/src/base.cpp b/src/base.cpp index ce28975..cac914b 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -52,7 +52,7 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): LMDBAL::Base::~Base() { close(); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) delete pair.second; } @@ -71,7 +71,7 @@ void LMDBAL::Base::close() { pair.second->reset(); } - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->close(); mdb_env_close(environment); @@ -99,8 +99,8 @@ void LMDBAL::Base::open() { mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); TransactionID txn = beginPrivateTransaction(emptyName); - for (const std::pair& pair : storages) { - iStorage* storage = pair.second; + for (const std::pair& pair : storages) { + StorageCommon* storage = pair.second; int rc = storage->open(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); @@ -200,7 +200,7 @@ void LMDBAL::Base::drop() { throw Closed("drop", name); TransactionID txn = beginPrivateTransaction(emptyName); - for (const std::pair& pair : storages) { + for (const std::pair& pair : storages) { int rc = pair.second->drop(txn); if (rc != MDB_SUCCESS) { abortPrivateTransaction(txn, emptyName); @@ -209,7 +209,7 @@ void LMDBAL::Base::drop() { } commitPrivateTransaction(txn, emptyName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->handleDrop(); } @@ -306,7 +306,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionStarted(txn, true); return txn; @@ -331,7 +331,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionStarted(txn, false); return txn; @@ -356,7 +356,7 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& throw Closed("abortTransaction", name, storageName); abortPrivateTransaction(id, storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionAborted(id); } @@ -379,7 +379,7 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string throw Closed("abortTransaction", name, storageName); commitPrivateTransaction(id, storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionCommited(id); } diff --git a/src/base.h b/src/base.h index de61ad7..a4b60a7 100644 --- a/src/base.h +++ b/src/base.h @@ -34,7 +34,7 @@ namespace LMDBAL { -class iStorage; +class StorageCommon; class Transaction; class WriteTransaction; @@ -51,7 +51,7 @@ typedef MDB_txn* TransactionID; /**<\brief I'm going to typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32_t*/ class Base { - friend class iStorage; + friend class StorageCommon; friend class Transaction; friend class WriteTransaction; public: @@ -84,7 +84,7 @@ public: LMDBAL::Cache* getCache(const std::string& storageName); private: - typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ + typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ typedef std::map Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ void commitTransaction(TransactionID id); @@ -136,7 +136,7 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, throw Opened(name, "add storage " + storageName); Storage* storage = new Storage(this, storageName, duplicates); - std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)storage)); + std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)storage)); if (!pair.second) throw StorageDuplicate(name, storageName); @@ -164,7 +164,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { throw Opened(name, "add cache " + storageName); Cache* cache = new Cache(this, storageName, false); - std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)cache)); + std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)cache)); if (!pair.second) throw StorageDuplicate(name, storageName); diff --git a/src/cache.hpp b/src/cache.hpp index 29af6d0..6dd8c54 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -63,10 +63,10 @@ LMDBAL::Cache::~Cache() { template void LMDBAL::Cache::addRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::addRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::addRecordMethodName); if (cache->count(key) > 0) - iStorage::throwDuplicate(iStorage::toString(key)); + StorageCommon::throwDuplicate(StorageCommon::toString(key)); Storage::addRecord(key, value); handleAddRecord(key, value); @@ -75,7 +75,7 @@ void LMDBAL::Cache::addRecord(const K& key, const V& value) { template void LMDBAL::Cache::addRecord(const K& key, const V& value, TransactionID txn) { if (cache->count(key) > 0) - iStorage::throwDuplicate(iStorage::toString(key)); + StorageCommon::throwDuplicate(StorageCommon::toString(key)); Storage::addRecord(key, value, txn); @@ -96,7 +96,7 @@ void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { template bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::forceRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::forceRecordMethodName); bool added = Storage::forceRecord(key, value); handleForceRecord(key, value, added); @@ -136,18 +136,18 @@ void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool a template void LMDBAL::Cache::changeRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::changeRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::changeRecordMethodName); if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::changeRecord(key, value); itr->second = value; } else { if (abscent->count(key) > 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::changeRecord(key, value); @@ -169,12 +169,12 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value, Transaction if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::changeRecord(key, value, txn); } else { if (abscent->count(key) > 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::changeRecord(key, value, txn); @@ -207,7 +207,7 @@ void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { template V LMDBAL::Cache::getRecord(const K& key) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::getRecordMethodName); V value; Cache::getRecord(key, value); @@ -216,7 +216,7 @@ V LMDBAL::Cache::getRecord(const K& key) const { template void LMDBAL::Cache::getRecord(const K& key, V& out) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::getRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) { @@ -225,7 +225,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { } if (mode == Mode::full || abscent->count(key) != 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::getRecord(key, out); @@ -269,7 +269,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } break; case Operation::remove: - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); break; case Operation::change: if (static_cast*>(entry.second)->first == key) { @@ -285,7 +285,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } break; case Operation::drop: - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); break; case Operation::replace: { std::map* newMap = static_cast*>(entry.second); @@ -294,7 +294,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con out = vitr->second; return; } else { - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); } } break; @@ -322,7 +322,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } if (mode == Mode::full || abscent->count(key) != 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::getRecord(key, out, txn); @@ -352,7 +352,7 @@ void LMDBAL::Cache::discoveredRecord(const K& key, const V& value, Transac template bool LMDBAL::Cache::checkRecord(const K& key) const { - iStorage::ensureOpened(iStorage::checkRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::checkRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) @@ -457,7 +457,7 @@ void LMDBAL::Cache::appendToCache(const K& key, const V& value) const { template std::map LMDBAL::Cache::readAll() const { - iStorage::ensureOpened(iStorage::readAllMethodName); + StorageCommon::ensureOpened(StorageCommon::readAllMethodName); if (mode != Mode::full) { //there is a room for optimization mode = Mode::full; //I can read and deserialize only those values @@ -471,7 +471,7 @@ std::map LMDBAL::Cache::readAll() const { template void LMDBAL::Cache::readAll(std::map& out) const { - iStorage::ensureOpened(iStorage::readAllMethodName); + StorageCommon::ensureOpened(StorageCommon::readAllMethodName); if (mode != Mode::full) { //there is a room for optimization mode = Mode::full; //I can read and deserialize only those values @@ -642,7 +642,7 @@ void LMDBAL::Cache::handleAddRecords(const std::map& data, bool over template void LMDBAL::Cache::removeRecord(const K& key) { - iStorage::ensureOpened(iStorage::removeRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::removeRecordMethodName); bool noKey = false; if (mode != Mode::full) @@ -651,7 +651,7 @@ void LMDBAL::Cache::removeRecord(const K& key) { noKey = abscent->count(key) > 0; if (noKey) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::removeRecord(key); handleRemoveRecord(key); @@ -666,7 +666,7 @@ void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { noKey = abscent->count(key) > 0; if (noKey) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::removeRecord(key, txn); @@ -781,8 +781,8 @@ void LMDBAL::Cache::handleMode() const { template int LMDBAL::Cache::drop(const WriteTransaction& transaction) { - iStorage::ensureOpened(iStorage::dropMethodName); - TransactionID txn = iStorage::extractTransactionId(transaction, iStorage::dropMethodName); + StorageCommon::ensureOpened(StorageCommon::dropMethodName); + TransactionID txn = StorageCommon::extractTransactionId(transaction, StorageCommon::dropMethodName); int res = Storage::drop(txn); if (res != MDB_SUCCESS) diff --git a/src/cursor.h b/src/cursor.h index 65a947d..eef7cf1 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -24,20 +24,13 @@ #include "base.h" #include "storage.h" #include "transaction.h" -#include "icursor.h" +#include "cursorcommon.h" namespace LMDBAL { template -class Cursor : public iCursor { +class Cursor : public CursorCommon { friend class Storage; -private: - enum State { /*** parent); @@ -48,14 +41,6 @@ public: Cursor& operator = (const Cursor& other) = delete; Cursor& operator = (Cursor&& other); - void open(); - void open(const Transaction& transaction); - void renew(); - void renew(const Transaction& transaction); - void close(); - bool opened() const; - bool empty() const; - void drop(); std::pair first(); @@ -72,33 +57,7 @@ public: void current(K& key, V& value) const; private: - virtual void terminated() override; - void dropped(); - void freed(); void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; - -private: - Storage* storage; - MDB_cursor* cursor; - State state; - uint32_t id; - - inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ - inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ - inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions*/ - inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions*/ - inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/ - inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/ - inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/ - inline static const std::string setMethodName = "set"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/ - inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/ - inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions*/ - inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions*/ - inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions*/ }; }; diff --git a/src/cursor.hpp b/src/cursor.hpp index 875b764..ed68e8f 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -41,8 +41,6 @@ * You are not supposed to instantiate or destory instances of this class yourself! */ -static uint32_t idCounter = 0; - /** * \brief Creates a cursor * @@ -50,12 +48,9 @@ static uint32_t idCounter = 0; */ template LMDBAL::Cursor::Cursor(Storage* parent): - storage(parent), - cursor(nullptr), - state(closed), - id(++idCounter) + CursorCommon(parent) { - storage->cursors[id] = this; + parent->cursors[id] = this; } /** @@ -65,10 +60,7 @@ LMDBAL::Cursor::Cursor(Storage* parent): */ template LMDBAL::Cursor::Cursor(): - storage(nullptr), - cursor(nullptr), - state(closed), - id(0) + CursorCommon() {} /** @@ -76,64 +68,39 @@ LMDBAL::Cursor::Cursor(): */ template LMDBAL::Cursor::Cursor(Cursor&& other): - storage(other.storage), - cursor(other.cursor), - state(other.state), - id(other.id) + CursorCommon(std::move(other)) { - other.terminated(); - if (id != 0) - storage->cursors[id] = this; - - if (state == openedPublic) - storage->attachCursorToTransaction(id, cursor, this); - - - other.freed(); + if (!empty()) + static_cast*>(storage)->cursors[id] = this; } /** - * \brief A private function that turns cursor into an empty one + * \brief Move assignment operator * - * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. - * Those cursors will become empty, and can't be used anymore + * Transfers other cursor into this one */ template LMDBAL::Cursor& LMDBAL::Cursor::operator = (Cursor&& other) { - terminated(); + if (!empty() && other.empty()) + static_cast*>(storage)->cursors.erase(id); - if (id != 0) - storage->cursors.erase(id); + CursorCommon::operator=(std::move(other)); - storage = other.storage; - cursor = other.cursor; - state = other.state; - id = other.id; - - if (id != 0) { - other.freed(); - other.state = closed; - - storage->cursors[id] = this; - - if (state == openedPublic) - storage->attachCursorToTransaction(id, cursor, this); - } + if (!empty()) + static_cast*>(storage)->cursors[id] = this; return *this; } /** - * \brief Destroys a cursor + * \brief Destroys this cursor * * If the cursor wasn't properly closed - it's going to be upon destruction */ template LMDBAL::Cursor::~Cursor () { - close(); - if (id != 0) - storage->cursors.erase(id); + static_cast*>(storage)->cursors.erase(id); } /** @@ -147,253 +114,9 @@ void LMDBAL::Cursor::drop () { close(); if (id != 0) - storage->cursors.erase(id); + static_cast*>(storage)->cursors.erase(id); - freed(); -} - -/** - * \brief A private method that turns cursor into an empty one - * - * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors. - * Those cursors will become empty, and can't be used anymore - */ -template -void LMDBAL::Cursor::dropped () { - terminated(); - freed(); -} - -/** - * \brief A private method that turns cursor into an empty one (submethod) - * - * This function is called from LMDBAL::Storage, when the cursor is getting destoryed. - * Those cursors will become empty, and can't be used anymore - */ -template -void LMDBAL::Cursor::freed () { - cursor = nullptr; - storage = nullptr; - id = 0; -} - -/** - * \brief Returns true if the cursor is empty - * - * Empty cursors can't be used, they can be only targets of move operations - */ -template -bool LMDBAL::Cursor::empty () const { - return id == 0; -} - -/** - * \brief A private function the storage owning this cursor will call to inform this cursor that the thansaction needs to be aborted - */ -template -void LMDBAL::Cursor::terminated () { - switch (state) { - case openedPublic: - storage->_mdbCursorClose(cursor); - state = closed; - break; - case openedPrivate: - storage->closeCursorTransaction(cursor, true); - state = closed; - break; - default: - break; - } -} - -/** - * \brief Opens the cursor for operations. - * - * This is a normal way to start the sequence of operations with the cursor. - * This variant of the function creates a read only transaction just for this cursor - * - * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! - * It will do nothing to a cursor that was already opened (no matter what way). - * - * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::open () { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - switch (state) { - case closed: - storage->openCursorTransaction(&cursor, false); - state = openedPrivate; - break; - default: - break; - } -} - -/** - * \brief Opens the cursor for operations. - * - * This is a normal way to start the sequence of operations with the cursor. - * This variant of the function uses for queries a transaction you have obtained somewhere else. - * - * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! - * It will do nothing to a cursor that was already opened (no matter what way). - * - * \param[in] transaction - a transaction, can be read only - * - * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb - * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::open (const Transaction& transaction) { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(openCursorMethodName); - TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); - switch (state) { - case closed: { - int result = storage->_mdbCursorOpen(txn, &cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - state = openedPublic; - } break; - default: - break; - } -} - -/** - * \brief Renews a cursor - * - * This function aborts current transaction if the cursor was opened with it's own transaction - * (does not mess up if the transaction was public), - * creates new private transaction and rebinds this cursor to it. - * - * Theoretically you could call this method if your public transaction was aborted (or commited) - * but you wish to continue to keep working with your cursor. - * Or if you just want to rebind your cursor to a new private transaction. - * - * This function does nothing if the cursor is closed - * - * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::renew () { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(renewCursorMethodName); - switch (state) { - case openedPrivate: - storage->closeCursorTransaction(cursor, false); - storage->openCursorTransaction(&cursor, true); - break; - case openedPublic: - storage->disconnectCursorFromTransaction(id, cursor); - storage->openCursorTransaction(&cursor, true); - state = openedPrivate; - break; - default: - break; - } -} - -/** - * \brief Renews a cursor - * - * This function aborts current transaction if the cursor was opened with it's own transaction - * (does not mess up if the transaction was public), - * and rebinds this cursor to a passed new transaction. - * - * Theoretically you could call this method if your previous public transaction was aborted (or commited) - * but you wish to continue to keep working with your cursor. - * Or if you just want to rebind your cursor to another public transaction. - * - * This function does nothing if the cursor is closed - * - * \param[in] transaction - a transaction you wish this cursor to be bound to - * - * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb - * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::renew (const Transaction& transaction) { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(renewCursorMethodName); - TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); - switch (state) { - case openedPrivate: { - storage->closeCursorTransaction(cursor, false); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - state = openedPublic; - } break; - case openedPublic: { - storage->disconnectCursorFromTransaction(id, cursor); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - } break; - default: - break; - } -} - -/** - * \brief Termiates a sequence of operations with the cursor - * - * This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries. - * The state of the cursor is lost after calling this method, some inner resorce is freed. - * - * If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction. - * - * This function does nothing on a closed cursor. - */ -template -void LMDBAL::Cursor::close () { - switch (state) { - case openedPublic: - storage->disconnectCursorFromTransaction(id, cursor); - storage->_mdbCursorClose(cursor); - - state = closed; - break; - case openedPrivate: - storage->closeCursorTransaction(cursor, true); - - state = closed; - break; - default: - break; - } -} - -/** - * \brief Tells if the cursor is open - */ -template -bool LMDBAL::Cursor::opened () const { - return state != closed; + reset(); } /** @@ -614,16 +337,16 @@ std::pair LMDBAL::Cursor::current () const { template bool LMDBAL::Cursor::set (const K& key) { if (state == closed) - storage->throwCursorNotReady(setMethodName); + static_cast*>(storage)->throwCursorNotReady(setMethodName); - MDB_val mdbKey = storage->keySerializer.setData(key); - int result = storage->_mdbCursorSet(cursor, mdbKey); + MDB_val mdbKey = static_cast*>(storage)->keySerializer.setData(key); + int result = static_cast*>(storage)->_mdbCursorSet(handle, mdbKey); if (result == MDB_SUCCESS) return true; else if (result == MDB_NOTFOUND) return false; - storage->throwUnknown(result); + static_cast*>(storage)->throwUnknown(result); return false; //unreachable, just to suppress the warning } @@ -651,18 +374,18 @@ void LMDBAL::Cursor::operateCursorRead( const std::string& operationName ) const { if (state == closed) - storage->throwCursorNotReady(methodName); + static_cast*>(storage)->throwCursorNotReady(methodName); MDB_val mdbKey, mdbValue; - int result = storage->_mdbCursorGet(cursor, mdbKey, mdbValue, operation); + int result = static_cast*>(storage)->_mdbCursorGet(handle, mdbKey, mdbValue, operation); if (result != MDB_SUCCESS) - storage->throwNotFoundOrUnknown(result, operationName); + static_cast*>(storage)->throwNotFoundOrUnknown(result, operationName); - storage->keySerializer.deserialize(mdbKey, key); - storage->valueSerializer.deserialize(mdbValue, value); + static_cast*>(storage)->keySerializer.deserialize(mdbKey, key); + static_cast*>(storage)->valueSerializer.deserialize(mdbValue, value); if (state == openedPrivate) - storage->discoveredRecord(key, value); + static_cast*>(storage)->discoveredRecord(key, value); else - storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor)); + static_cast*>(storage)->discoveredRecord(key, value, static_cast*>(storage)->_mdbCursorTxn(handle)); } diff --git a/src/cursorcommon.cpp b/src/cursorcommon.cpp new file mode 100644 index 0000000..bfa77f0 --- /dev/null +++ b/src/cursorcommon.cpp @@ -0,0 +1,342 @@ +/* + * 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 "cursorcommon.h" + +/** + * \class LMDBAL::CursorCommon + * \brief An object to manage cursor internals and state. + * + * Cursors are owned by the storage, they die with the storage. + * They also get closed if the storage is closed (if you close by the database for example) + * + * You can obtain an instance of this class calling LMDBAL::Storage::createCursor() + * and destory it calling LMDBAL::Storage::destoryCursor() at any time, LMDBAL::Base doesn't necessarily need to be opened. + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + +#include "storagecommon.h" + +static uint32_t idCounter = 0; + +/** + * \brief Creates a empty class + */ +LMDBAL::CursorCommon::CursorCommon (): + id(0), + state(closed), + handle(nullptr), + storage(nullptr) +{} + +/** + * \brief Creates a cursor + * + * \param[in] _storage a storage that created this cursor + */ +LMDBAL::CursorCommon::CursorCommon (StorageCommon* _storage): + id(++idCounter), + state(closed), + handle(nullptr), + storage(_storage) +{} + +/** + * \brief Moves other cursor into this class + * + * \param[in] other other instance that is being moved + */ +LMDBAL::CursorCommon::CursorCommon (CursorCommon&& other): + id(other.id), + state(other.state), + handle(other.handle), + storage(other.storage) +{ + other.dropped(); + + if (state == openedPublic) + storage->attachCursorToTransaction(id, handle, this); +} + +/** + * \brief Destroys this cursor + * + * If the cursor wasn't properly closed - it's going to be upon destruction + */ +LMDBAL::CursorCommon::~CursorCommon () noexcept { + close(); +} + +/** + * \brief Move assignment operator + * + * Transfers other cursor into this one + */ +LMDBAL::CursorCommon& LMDBAL::CursorCommon::operator = (CursorCommon&& other) { + terminated(); + + id = other.id; + state = other.state; + handle = other.handle; + storage = other.storage; + + other.reset(); + + if (state == openedPublic) + storage->attachCursorToTransaction(id, handle, this); + + return *this; +} + +/** + * \brief A private method that turns cursor into an empty one + * + * This method is called from LMDBAL::Storage, when the cursor is getting destoryed. + * After this method cursors will become empty, and can't be used anymore + */ +void LMDBAL::CursorCommon::reset () { + id = 0; + state = closed; + handle = nullptr; + storage = nullptr; +} + +/** + * \brief A private method that turns cursor into an empty one + * + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors. + * Those cursors will become empty, and can't be used anymore + */ +void LMDBAL::CursorCommon::dropped () { + terminated(); + reset(); +} + +/** + * \brief A private function called to inform the cursor he has been terminated + * + * Is expected to be called from transaction, database, storage or move constructor + */ +void LMDBAL::CursorCommon::terminated () { + switch (state) { + case openedPublic: + storage->_mdbCursorClose(handle); + state = closed; + break; + case openedPrivate: + storage->closeCursorTransaction(handle, true); + state = closed; + break; + default: + break; + } +} + +/** + * \brief Termiates a sequence of operations with the cursor + * + * This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries. + * The state of the cursor is lost after calling this method, some inner resorce is freed. + * + * If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction. + * + * This function does nothing on a closed cursor. + */ +void LMDBAL::CursorCommon::close () { + switch (state) { + case openedPublic: + storage->disconnectCursorFromTransaction(id, handle); + storage->_mdbCursorClose(handle); + + state = closed; + break; + case openedPrivate: + storage->closeCursorTransaction(handle, true); + + state = closed; + break; + default: + break; + } +} + +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function creates a read only transaction just for this cursor + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::open () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + switch (state) { + case closed: + storage->openCursorTransaction(&handle, false); + state = openedPrivate; + break; + default: + break; + } +} + +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function uses for queries a transaction you have obtained somewhere else. + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \param[in] transaction - a transaction, can be read only + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::open (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(openCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); + switch (state) { + case closed: { + int result = storage->_mdbCursorOpen(txn, &handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + state = openedPublic; + } break; + default: + break; + } +} + +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * creates new private transaction and rebinds this cursor to it. + * + * Theoretically you could call this method if your public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to a new private transaction. + * + * This function does nothing if the cursor is closed + * + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::renew () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(renewCursorMethodName); + switch (state) { + case openedPrivate: + storage->closeCursorTransaction(handle, false); + storage->openCursorTransaction(&handle, true); + break; + case openedPublic: + storage->disconnectCursorFromTransaction(id, handle); + storage->openCursorTransaction(&handle, true); + state = openedPrivate; + break; + default: + break; + } +} + +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * and rebinds this cursor to a passed new transaction. + * + * Theoretically you could call this method if your previous public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to another public transaction. + * + * This function does nothing if the cursor is closed + * + * \param[in] transaction - a transaction you wish this cursor to be bound to + * + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::renew (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(renewCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); + switch (state) { + case openedPrivate: { + storage->closeCursorTransaction(handle, false); + int result = storage->_mdbCursorRenew(txn, handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + state = openedPublic; + } break; + case openedPublic: { + storage->disconnectCursorFromTransaction(id, handle); + int result = storage->_mdbCursorRenew(txn, handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + } break; + default: + break; + } +} + +/** + * \brief Returns true if the cursor is empty + * + * Empty cursors can't be used, they can be only targets of move operations + */ +bool LMDBAL::CursorCommon::empty () const { + return id == 0; +} + +/** + * \brief Tells if the cursor is open + */ +bool LMDBAL::CursorCommon::opened () const { + return state != closed; +} diff --git a/src/cursorcommon.h b/src/cursorcommon.h new file mode 100644 index 0000000..fee4e8b --- /dev/null +++ b/src/cursorcommon.h @@ -0,0 +1,90 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +#include + +namespace LMDBAL { + +class Transaction; +class StorageCommon; + +class CursorCommon { + friend class Transaction; +protected: + enum State { /** - * - * 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 . - */ - -#pragma once - -namespace LMDBAL { - -class Transaction; - -class iCursor { - friend class Transaction; -protected: - virtual ~iCursor() {} - virtual void terminated() = 0; -}; - -} diff --git a/src/storage.h b/src/storage.h index 99141e0..844beb2 100644 --- a/src/storage.h +++ b/src/storage.h @@ -25,6 +25,7 @@ #include "serializer.h" #include "cursor.h" #include "transaction.h" +#include "storagecommon.h" class BaseTest; class DuplicatesTest; @@ -33,111 +34,8 @@ class StorageCursorTest; namespace LMDBAL { -class iStorage { - friend class Base; -public: -protected: - iStorage(Base* parent, const std::string& name, bool duplicates = false); - virtual ~iStorage(); - - /** - * \brief A private virtual function I need to open each storage in the database - * - * \param[in] transaction - lmdb transaction to call mdb_dbi_open - * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code - */ - virtual int open(MDB_txn * transaction) = 0; - virtual void close(); - virtual void handleDrop(); - - bool isDBOpened() const; - const std::string& dbName() const; - - void ensureOpened(const std::string& methodName) const; - void throwDuplicateOrUnknown(int rc, const std::string& key) const; - void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; - void throwNotFoundOrUnknown(int rc, const std::string& key) const; - void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; - void throwUnknown(int rc, TransactionID txn) const; - void throwUnknown(int rc) const; - void throwUnknown(const std::string& message) const; - void throwDuplicate(const std::string& key) const; - void throwNotFound(const std::string& key) const; - void throwCursorNotReady(const std::string& method) const; - - TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const; - TransactionID beginReadOnlyTransaction() const; - TransactionID beginTransaction() const; - void commitTransaction(TransactionID id); - void abortTransaction(TransactionID id) const; - virtual void transactionStarted(TransactionID txn, bool readOnly) const; - virtual void transactionCommited(TransactionID txn); - virtual void transactionAborted(TransactionID txn) const; - virtual int drop(TransactionID transaction); - virtual SizeType count(TransactionID txn) const; - - void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; - void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; - void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const; - void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; - - int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); - - int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); - int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const; - int _mdbDel(MDB_txn* txn, MDB_val& key); - int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data); - - int _mdbStat(MDB_txn* txn, MDB_stat& stat) const; - int _mdbFlags(MDB_txn* txn, uint32_t& flags) const; - - int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const; - void _mdbCursorClose(MDB_cursor* cursor) const; - int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const; - int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const; - int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0); - int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0); - - int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const; - MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const; - -public: - virtual void drop(); - virtual int drop(const WriteTransaction& txn); - virtual SizeType count() const; - virtual SizeType count(const Transaction& txn) const; - -protected: - MDB_dbi dbi; /**<\brief lmdb storage handle*/ - Base* db; /**<\brief parent database pointer (borrowed)*/ - const std::string name; /**<\brief this storage name*/ - const bool duplicates; /**<\brief true if storage supports duplicates*/ - - inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ - inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ - inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ - inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ - inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/ - -protected: - template - int makeStorage(MDB_txn* transaction, bool duplicates = false); - - template - static std::string toString(const T& value); -}; - template -class Storage : public iStorage { +class Storage : public StorageCommon { friend class ::BaseTest; friend class ::DuplicatesTest; friend class ::CacheCursorTest; @@ -165,7 +63,7 @@ protected: virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); public: - using iStorage::drop; + using StorageCommon::drop; virtual void addRecord(const K& key, const V& value); virtual void addRecord(const K& key, const V& value, const WriteTransaction& txn); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change diff --git a/src/storage.hpp b/src/storage.hpp index e56e91a..7f89fc2 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -46,7 +46,7 @@ */ template LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool duplicates): - iStorage(parent, name, duplicates), + StorageCommon(parent, name, duplicates), keySerializer(), valueSerializer(), cursors() @@ -1015,7 +1015,7 @@ void LMDBAL::Storage::close() { for (const std::pair*>& pair : cursors) pair.second->terminated(); - iStorage::close(); + StorageCommon::close(); } /** @@ -1051,7 +1051,7 @@ void LMDBAL::Storage::destroyCursor(LMDBAL::Cursor& cursor) { cursor.close(); cursors.erase(itr); - cursor.freed(); + cursor.reset(); } /** @@ -1103,81 +1103,3 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans UNUSED(value); UNUSED(txn); } - -/** - * \brief A functiion to actually open MDB_dbi storage - * - * \tparam K type of keys in opening storage - * - * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! - * \param[in] duplicates - true if key duplicates are allowed (false by default) - * - * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code - * - * This is a way to optimise database using MDB_INTEGERKEY flag, - * when the key is actually kind of an integer - * This infrastructure also allowes us to customize mdb_dbi_open call in the future - */ -template -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) { - unsigned int flags = MDB_CREATE; - if constexpr (std::is_integral::value) - flags |= MDB_INTEGERKEY; - - if (duplicates) { - flags |= MDB_DUPSORT; - - if constexpr (std::is_scalar::value) - flags |= MDB_DUPFIXED; - - if constexpr ( - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode - flags |= MDB_INTEGERDUP; - } - - return _mdbOpen(transaction, flags); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * This function is mainly used in exceptions, to report which key was duplicated or not found. - * You can define your own specializations to this function in case std::to_string doesn't cover your case - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template -inline std::string LMDBAL::iStorage::toString(const T& value) { - return std::to_string(value); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * QString spectialization - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template<> -inline std::string LMDBAL::iStorage::toString(const QString& value) { - return value.toStdString(); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * std::string spectialization - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template<> -inline std::string LMDBAL::iStorage::toString(const std::string& value) { - return value; -} diff --git a/src/storage.cpp b/src/storagecommon.cpp similarity index 78% rename from src/storage.cpp rename to src/storagecommon.cpp index 4359987..298f48d 100644 --- a/src/storage.cpp +++ b/src/storagecommon.cpp @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -#include "storage.h" +#include "storagecommon.h" + +#include "cursorcommon.h" #define UNUSED(x) (void)(x) @@ -37,7 +39,7 @@ * \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): +LMDBAL::StorageCommon::StorageCommon(Base* parent, const std::string& name, bool duplicates): dbi(), db(parent), name(name), @@ -47,12 +49,12 @@ LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicate /** * \brief Destroys a storage interface */ -LMDBAL::iStorage::~iStorage() {} +LMDBAL::StorageCommon::~StorageCommon () {} /** * \brief A private virtual function to close each storage in the database */ -void LMDBAL::iStorage::close() { +void LMDBAL::StorageCommon::close() { mdb_dbi_close(db->environment, dbi); } @@ -67,7 +69,7 @@ void LMDBAL::iStorage::close() { * * \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 { +LMDBAL::TransactionID LMDBAL::StorageCommon::extractTransactionId(const Transaction& txn, const std::string& action) const { if (!txn.isActive()) throw TransactionTerminated(db->name, name, action); @@ -82,11 +84,11 @@ LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ -void LMDBAL::iStorage::drop() { +void LMDBAL::StorageCommon::drop() { ensureOpened(dropMethodName); TransactionID txn = beginTransaction(); - int rc = iStorage::drop(txn); + int rc = StorageCommon::drop(txn); if (rc != MDB_SUCCESS) { abortTransaction(txn); throw Unknown(db->name, mdb_strerror(rc), name); @@ -104,7 +106,7 @@ void LMDBAL::iStorage::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) { +int LMDBAL::StorageCommon::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } @@ -118,7 +120,7 @@ int LMDBAL::iStorage::drop(TransactionID transaction) { * * \exception LMDBAL::TransactionTerminated thrown if the transaction was not active */ -int LMDBAL::iStorage::drop(const WriteTransaction& txn) { +int LMDBAL::StorageCommon::drop(const WriteTransaction& txn) { ensureOpened(dropMethodName); return drop(extractTransactionId(txn, dropMethodName)); } @@ -130,7 +132,7 @@ int LMDBAL::iStorage::drop(const WriteTransaction& txn) { * * \exception LMDBAL::Closed thrown if the database was closed */ -void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { +void LMDBAL::StorageCommon::ensureOpened(const std::string& methodName) const { if (!isDBOpened()) throw Closed(methodName, db->name, name); } @@ -143,7 +145,7 @@ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ -LMDBAL::SizeType LMDBAL::iStorage::count() const { +LMDBAL::SizeType LMDBAL::StorageCommon::count() const { ensureOpened(countMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -168,7 +170,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ -void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const { +void LMDBAL::StorageCommon::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const { TransactionID txnID = _mdbCursorTxn(cursorHandle); Transaction* txn = db->transactions.at(txnID); txn->cursors[cursorId] = pointer; @@ -186,7 +188,7 @@ void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction */ -void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor, bool renew) const { +void LMDBAL::StorageCommon::openCursorTransaction (MDB_cursor** cursor, bool renew) const { ensureOpened(openCursorTransactionMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -210,7 +212,7 @@ void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor, bool renew) c * \param[in] cursor - cursor handle * \param[in] closeCursor - true if the cursor should also get closed, false if you wish to leave it open */ -void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const { +void LMDBAL::StorageCommon::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const { TransactionID txn = _mdbCursorTxn(cursor); if (closeCursor) _mdbCursorClose(cursor); @@ -228,7 +230,7 @@ void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor, bool closeCur * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ -void LMDBAL::iStorage::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { +void LMDBAL::StorageCommon::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { TransactionID txnID = _mdbCursorTxn(cursorHandle); Transaction* txn = db->transactions.at(txnID); txn->cursors.erase(cursorId); @@ -243,7 +245,7 @@ void LMDBAL::iStorage::disconnectCursorFromTransaction (uint32_t cursorId, MDB_c * * \exception LMDBAL::Unknown thrown if something unexpected happened */ -LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { +LMDBAL::SizeType LMDBAL::StorageCommon::count(TransactionID txn) const { MDB_stat stat; int rc = mdb_stat(txn, dbi, &stat); if (rc != MDB_SUCCESS) @@ -262,7 +264,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { * \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 { +LMDBAL::SizeType LMDBAL::StorageCommon::count(const Transaction& txn) const { ensureOpened(countMethodName); return count(extractTransactionId(txn, countMethodName)); } @@ -279,7 +281,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const { * \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 { +void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); throwDuplicateOrUnknown(rc, key); } @@ -296,7 +298,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const * \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 { +void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { abortTransaction(txn); throwNotFoundOrUnknown(rc, key); } @@ -312,7 +314,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, * \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 { +void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, const std::string& key) const { if (rc == MDB_KEYEXIST) throwDuplicate(key); else @@ -330,7 +332,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) c * \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 { +void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, const std::string& key) const { if (rc == MDB_NOTFOUND) throwNotFound(key); else @@ -347,7 +349,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) co * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { +void LMDBAL::StorageCommon::throwUnknown(int rc, LMDBAL::TransactionID txn) const { abortTransaction(txn); throwUnknown(rc); } @@ -359,7 +361,7 @@ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { * * \returns database name */ -const std::string & LMDBAL::iStorage::dbName() const { +const std::string & LMDBAL::StorageCommon::dbName() const { return db->name;} /** @@ -369,7 +371,7 @@ const std::string & LMDBAL::iStorage::dbName() const { * * \returns true if database is ipened, false otherwise */ -bool LMDBAL::iStorage::isDBOpened() const { +bool LMDBAL::StorageCommon::isDBOpened() const { return db->opened;} /** @@ -381,7 +383,7 @@ bool LMDBAL::iStorage::isDBOpened() const { * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(int rc) const { +void LMDBAL::StorageCommon::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} /** @@ -393,7 +395,7 @@ void LMDBAL::iStorage::throwUnknown(int rc) const { * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(const std::string& message) const { +void LMDBAL::StorageCommon::throwUnknown(const std::string& message) const { throw Unknown(db->name, message, name);} /** @@ -405,7 +407,7 @@ void LMDBAL::iStorage::throwUnknown(const std::string& message) const { * * \exception LMDBAL::Exist thrown everytime */ -void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { +void LMDBAL::StorageCommon::throwDuplicate(const std::string& key) const { throw Exist(key, db->name, name);} /** @@ -417,7 +419,7 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { * * \exception LMDBAL::NotFound thrown everytime */ -void LMDBAL::iStorage::throwNotFound(const std::string& key) const { +void LMDBAL::StorageCommon::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} /** @@ -429,7 +431,7 @@ void LMDBAL::iStorage::throwNotFound(const std::string& key) const { * * \exception LMDBAL::CursorNotReady thrown everytime */ -void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { +void LMDBAL::StorageCommon::throwCursorNotReady(const std::string& method) const { throw CursorNotReady(method, db->name, name);} /** @@ -439,7 +441,7 @@ void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { * * \returns read only transaction */ -LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { +LMDBAL::TransactionID LMDBAL::StorageCommon::beginReadOnlyTransaction() const { return db->beginPrivateReadOnlyTransaction(name);} /** @@ -449,7 +451,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { * * \returns read only transaction */ -LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { +LMDBAL::TransactionID LMDBAL::StorageCommon::beginTransaction() const { return db->beginPrivateTransaction(name);} /** @@ -457,7 +459,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { * * 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 { +void LMDBAL::StorageCommon::abortTransaction(LMDBAL::TransactionID id) const { db->abortPrivateTransaction(id, name);} /** @@ -467,7 +469,7 @@ void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { * * \exception LMDBAL::Unknown thrown if something unexpected happened */ -void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { +void LMDBAL::StorageCommon::commitTransaction(LMDBAL::TransactionID id) { db->commitPrivateTransaction(id, name);} /** @@ -482,7 +484,7 @@ void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { * \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 { +void LMDBAL::StorageCommon::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { UNUSED(txn); UNUSED(readOnly); } @@ -498,7 +500,7 @@ void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOn * * \param[in] txn - ID of started transaction */ -void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { +void LMDBAL::StorageCommon::transactionCommited(LMDBAL::TransactionID txn) { UNUSED(txn);} /** @@ -512,7 +514,7 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { * * \param[in] txn - ID of started transaction */ -void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { +void LMDBAL::StorageCommon::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} /** @@ -521,65 +523,65 @@ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { * 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() {} +void LMDBAL::StorageCommon::handleDrop() {} -int LMDBAL::iStorage::_mdbOpen(MDB_txn *txn, unsigned int flags) { +int LMDBAL::StorageCommon::_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) { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_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) { +int LMDBAL::StorageCommon::_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) { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { return mdb_cursor_open(txn, dbi, cursor); } -void LMDBAL::iStorage::_mdbCursorClose(MDB_cursor *cursor) const { +void LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_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) { +int LMDBAL::StorageCommon::_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) { +int LMDBAL::StorageCommon::_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 { +int LMDBAL::StorageCommon::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const { return mdb_cursor_renew(txn, cursor); } -MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const { +MDB_txn* LMDBAL::StorageCommon::_mdbCursorTxn(MDB_cursor* cursor) const { return mdb_cursor_txn(cursor); } diff --git a/src/storagecommon.h b/src/storagecommon.h new file mode 100644 index 0000000..367ef62 --- /dev/null +++ b/src/storagecommon.h @@ -0,0 +1,139 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +#include + +#include "base.h" +#include "transaction.h" + +namespace LMDBAL { + +class CursorCommon; + +class StorageCommon { + friend class Base; + friend class CursorCommon; +public: +protected: + StorageCommon(Base* parent, const std::string& name, bool duplicates = false); + virtual ~ StorageCommon(); + + /** + * \brief A private virtual function I need to open each storage in the database + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + */ + virtual int open(MDB_txn * transaction) = 0; + virtual void close(); + virtual void handleDrop(); + + bool isDBOpened() const; + const std::string& dbName() const; + + void ensureOpened(const std::string& methodName) const; + void throwDuplicateOrUnknown(int rc, const std::string& key) const; + void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwUnknown(int rc, TransactionID txn) const; + void throwUnknown(int rc) const; + void throwUnknown(const std::string& message) const; + void throwDuplicate(const std::string& key) const; + void throwNotFound(const std::string& key) const; + void throwCursorNotReady(const std::string& method) const; + + TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const; + TransactionID beginReadOnlyTransaction() const; + TransactionID beginTransaction() const; + void commitTransaction(TransactionID id); + void abortTransaction(TransactionID id) const; + virtual void transactionStarted(TransactionID txn, bool readOnly) const; + virtual void transactionCommited(TransactionID txn); + virtual void transactionAborted(TransactionID txn) const; + virtual int drop(TransactionID transaction); + virtual SizeType count(TransactionID txn) const; + + void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; + void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; + void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const; + void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; + + int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); + + int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); + int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const; + int _mdbDel(MDB_txn* txn, MDB_val& key); + int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data); + + int _mdbStat(MDB_txn* txn, MDB_stat& stat) const; + int _mdbFlags(MDB_txn* txn, uint32_t& flags) const; + + int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const; + void _mdbCursorClose(MDB_cursor* cursor) const; + int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const; + int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const; + int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0); + int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0); + + int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const; + MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const; + +public: + virtual void drop(); + virtual int drop(const WriteTransaction& txn); + virtual SizeType count() const; + virtual SizeType count(const Transaction& txn) const; + +protected: + MDB_dbi dbi; /**<\brief lmdb storage handle*/ + Base* db; /**<\brief parent database pointer (borrowed)*/ + const std::string name; /**<\brief this storage name*/ + const bool duplicates; /**<\brief true if storage supports duplicates*/ + + inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ + inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ + inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ + inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ + inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/ + +protected: + template + int makeStorage(MDB_txn* transaction, bool duplicates = false); + + template + static std::string toString(const T& value); +}; + +} + +#include "storagecommon.hpp" diff --git a/src/storagecommon.hpp b/src/storagecommon.hpp new file mode 100644 index 0000000..c32ae6f --- /dev/null +++ b/src/storagecommon.hpp @@ -0,0 +1,99 @@ +/* + * 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 . + */ + +#pragma once + +#include "storagecommon.h" + +/** + * \brief A functiion to actually open MDB_dbi storage + * + * \tparam K type of keys in opening storage + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! + * \param[in] duplicates - true if key duplicates are allowed (false by default) + * + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + * + * This is a way to optimise database using MDB_INTEGERKEY flag, + * when the key is actually kind of an integer + * This infrastructure also allowes us to customize mdb_dbi_open call in the future + */ +template +inline int LMDBAL::StorageCommon::makeStorage(MDB_txn* transaction, bool duplicates) { + unsigned int flags = MDB_CREATE; + if constexpr (std::is_integral::value) + flags |= MDB_INTEGERKEY; + + if (duplicates) { + flags |= MDB_DUPSORT; + + if constexpr (std::is_scalar::value) + flags |= MDB_DUPFIXED; + + if constexpr ( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode + flags |= MDB_INTEGERDUP; + } + + return _mdbOpen(transaction, flags); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * This function is mainly used in exceptions, to report which key was duplicated or not found. + * You can define your own specializations to this function in case std::to_string doesn't cover your case + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template +inline std::string LMDBAL::StorageCommon::toString(const T& value) { + return std::to_string(value); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * QString spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template<> +inline std::string LMDBAL::StorageCommon::toString(const QString& value) { + return value.toStdString(); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * std::string spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template<> +inline std::string LMDBAL::StorageCommon::toString(const std::string& value) { + return value; +} diff --git a/src/transaction.cpp b/src/transaction.cpp index d2e6a05..f92e7e8 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -18,6 +18,8 @@ #include "transaction.h" +#include "cursorcommon.h" + /** * \class LMDBAL::Transaction * \brief Public read only transaction @@ -131,7 +133,7 @@ void LMDBAL::Transaction::reset() { * \brief Closes attached curors; */ void LMDBAL::Transaction::closeCursors () { - for (const std::pair& pair : cursors) + for (const std::pair& pair : cursors) pair.second->terminated(); } diff --git a/src/transaction.h b/src/transaction.h index f37dab0..0fb28e3 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -19,14 +19,14 @@ #pragma once #include "base.h" -#include "icursor.h" namespace LMDBAL { -class iStorage; +class StorageCommon; +class CursorCommon; class Transaction { friend class Base; - friend class iStorage; + friend class StorageCommon; public: explicit Transaction(); explicit Transaction(Transaction&& other); @@ -44,10 +44,10 @@ protected: void closeCursors(); protected: - TransactionID txn; /**<\brief Transaction inner handler*/ - bool active; /**<\brief Transaction state*/ - const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ - std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ + std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ }; class WriteTransaction : public Transaction { diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index 0fc259d..a2c0e42 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -182,7 +182,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -208,7 +208,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "adding first transaction value" << std::endl; c1->addRecord(5, 812, txn1); @@ -235,7 +235,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -256,7 +256,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "parent thread woke up" << std::endl; std::cout << "adding value from parent thread" << std::endl; diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 237a7c6..d4b9089 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -181,7 +181,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -207,7 +207,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "adding first transaction value" << std::endl; t1->addRecord(5, 812, txn1); @@ -234,7 +234,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -255,7 +255,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "parent thread woke up" << std::endl; std::cout << "adding value from parent thread" << std::endl;