forked from blue/lmdbal
366 lines
12 KiB
C++
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);
|
|
}
|