1
0
Fork 0
forked from blue/lmdbal
lmdbal/src/cursorcommon.cpp
2025-01-05 18:39:36 +02:00

366 lines
12 KiB
C++

/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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);
}