/* * 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) attachToTransaction(); } /** * \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) attachToTransaction(); 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: disconnectFromTransaction(); 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); transaction.cursors[id] = 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: disconnectFromTransaction(); 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); transaction.cursors[id] = this; state = openedPublic; } break; case openedPublic: { disconnectFromTransaction(); int result = storage->_mdbCursorRenew(txn, handle); if (result != MDB_SUCCESS) storage->throwUnknown(result); transaction.cursors[id] = 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; } /** * \brief Links cursor to the transaction it has been opened with * * Cursor must be opened by a public transaction * * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ void LMDBAL::CursorCommon::attachToTransaction () { Transaction* txn = storage->getTransactionForCursor(handle); txn->cursors[id] = this; } /** * \brief Disconnects cursor from the transaction it has been opened with * * Cursor must be still opened by a public transaction * * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ void LMDBAL::CursorCommon::disconnectFromTransaction () { Transaction* txn = storage->getTransactionForCursor(handle); txn->cursors.erase(id); }