diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b0d5ad..7d88adf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,8 @@ set(HEADERS exceptions.h storage.h storage.hpp + cursor.h + cursor.hpp cache.h cache.hpp serializer.h diff --git a/src/base.cpp b/src/base.cpp index bb34bf1..04040fc 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -67,13 +67,12 @@ LMDBAL::Base::~Base() { */ void LMDBAL::Base::close() { if (opened) { - for (LMDBAL::TransactionID id : *transactions) + for (const LMDBAL::TransactionID id : *transactions) abortTransaction(id, emptyName); - for (const std::pair& pair : storages) { - iStorage* storage = pair.second; - mdb_dbi_close(environment, storage->dbi); - } + for (const std::pair& pair : storages) + pair.second->close(); + mdb_env_close(environment); transactions->clear(); opened = false; @@ -101,7 +100,7 @@ void LMDBAL::Base::open() { TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { iStorage* storage = pair.second; - int rc = storage->createStorage(txn); + int rc = storage->open(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); } diff --git a/src/cache.h b/src/cache.h index 0f4f814..e6342cf 100644 --- a/src/cache.h +++ b/src/cache.h @@ -105,15 +105,13 @@ protected: * \brief Cache mode * * Sometimes we have a complete information about the content of the database, - * and we don't even need to refer to lmdb to fetch or check for data. + * and we don't even need to call lmdb to fetch or check for data. * This member actually shows what is the current state, more about them you can read in Enum descriptions - * - * It's done with pointer to comply some of the const method limitations which would modify this state eventually */ - Mode* mode; + mutable Mode mode; std::map* cache; /**<\brief Cached data*/ std::set* abscent; /**<\brief Set of keys that are definitely not in the cache*/ - SizeType* sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/ + mutable SizeType sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/ TransactionCache* transactionCache; /**<\brief All changes made under under uncommited transactions*/ }; diff --git a/src/cache.hpp b/src/cache.hpp index 18d8843..6563d60 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -45,15 +45,11 @@ template LMDBAL::Cache::Cache(const std::string& _name, Base* parent): Storage(_name, parent), - mode(new Mode), + mode(Mode::nothing), cache(new std::map()), abscent(new std::set()), - sizeDifference(new uint32_t), - transactionCache(new TransactionCache) -{ - *mode = Mode::nothing; - *sizeDifference = 0; -} + sizeDifference(0), + transactionCache(new TransactionCache) {} /** * \brief Destroys a cache @@ -61,8 +57,6 @@ LMDBAL::Cache::Cache(const std::string& _name, Base* parent): template LMDBAL::Cache::~Cache() { delete transactionCache; - delete sizeDifference; - delete mode; delete cache; delete abscent; } @@ -98,7 +92,7 @@ template void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { cache->insert(std::make_pair(key, value)); - if (*mode != Mode::full) + if (mode != Mode::full) abscent->erase(key); } @@ -129,7 +123,7 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value, TransactionI template void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool added) { - if (*mode == Mode::full) { + if (mode == Mode::full) { (*cache)[key] = value; } else { if (added) @@ -148,7 +142,7 @@ template void LMDBAL::Cache::changeRecord(const K& key, const V& value) { iStorage::ensureOpened(iStorage::changeRecordMethodName); - if (*mode == Mode::full) { + if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) iStorage::throwNotFound(iStorage::toString(key)); @@ -178,7 +172,7 @@ template void LMDBAL::Cache::changeRecord(const K& key, const V& value, TransactionID txn) { iStorage::ensureOpened(iStorage::changeRecordMethodName); - if (*mode == Mode::full) { + if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) iStorage::throwNotFound(iStorage::toString(key)); @@ -205,7 +199,7 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value, Transaction template void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { - if (*mode == Mode::full) { + if (mode == Mode::full) { cache->at(key) = value; } else { typename std::pair::iterator, bool> res = @@ -236,7 +230,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { return; } - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) iStorage::throwNotFound(iStorage::toString(key)); try { @@ -245,7 +239,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { handleMode(); return; } catch (const NotFound& error) { - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); throw error; @@ -338,7 +332,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con return; } - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) iStorage::throwNotFound(iStorage::toString(key)); try { @@ -349,7 +343,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } return; } catch (const NotFound& error) { - if (! currentTransaction && *mode != Mode::full) + if (!currentTransaction && mode != Mode::full) abscent->insert(key); throw error; @@ -364,7 +358,7 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { if (itr != cache->end()) return true; - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) return false; try { @@ -373,7 +367,7 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { handleMode(); return true; } catch (const NotFound& error) { - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); return false; @@ -439,7 +433,7 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { if (itr != cache->end()) return true; - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) return false; try { @@ -450,7 +444,7 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { } return true; } catch (const NotFound& error) { - if (!currentTransaction && *mode != Mode::full) + if (!currentTransaction && mode != Mode::full) abscent->insert(key); return false; @@ -461,11 +455,11 @@ template std::map LMDBAL::Cache::readAll() const { iStorage::ensureOpened(iStorage::readAllMethodName); - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values *cache = Storage::readAll(); //that are missing in the cache abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } return *cache; @@ -475,12 +469,12 @@ template void LMDBAL::Cache::readAll(std::map& out) const { iStorage::ensureOpened(iStorage::readAllMethodName); - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values Storage::readAll(out); //that are missing in the cache *cache = out; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -501,7 +495,7 @@ void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const typename TransactionCache::iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { Queue& queue = tc->second; - if (*mode != Mode::full) { + if (mode != Mode::full) { out = *cache; for (typename Queue::const_iterator i = queue.begin(), end = queue.end(); i != end; ++i) { @@ -552,12 +546,12 @@ void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const queue.emplace_back(Operation::replace, new std::map(out)); //I can as well erase all previous cache entries } } else { - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values Storage::readAll(out); //that are missing in the cache *cache = out; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } } @@ -567,10 +561,10 @@ void LMDBAL::Cache::replaceAll(const std::map& data) { Storage::replaceAll(data); *cache = data; - if (*mode != Mode::full) { - *mode = Mode::full; + if (mode != Mode::full) { + mode = Mode::full; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -591,10 +585,10 @@ void LMDBAL::Cache::handleReplaceAll(std::map* data) { delete cache; cache = data; - if (*mode != Mode::full) { - *mode = Mode::full; + if (mode != Mode::full) { + mode = Mode::full; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -622,9 +616,8 @@ LMDBAL::SizeType LMDBAL::Cache::addRecords(const std::map& data, Tra template void LMDBAL::Cache::handleAddRecords(const std::map& data, bool overwrite, SizeType newSize) { - Mode& m = *mode; - if (m == Mode::nothing) - m = Mode::size; + if (mode == Mode::nothing) + mode = Mode::size; std::map& c = *cache; std::set& a = *abscent; @@ -633,15 +626,15 @@ void LMDBAL::Cache::handleAddRecords(const std::map& data, bool over if (!res.second) { if (overwrite) res.first->second = pair.second; - } else if (m != Mode::full) { + } else if (mode != Mode::full) { a.erase(pair.first); } } - if (m != Mode::full) { - *sizeDifference = newSize - c.size(); - if (*sizeDifference == 0) { - *mode = Mode::full; + if (mode != Mode::full) { + sizeDifference = newSize - c.size(); + if (sizeDifference == 0) { + mode = Mode::full; abscent->clear(); } } @@ -652,7 +645,7 @@ void LMDBAL::Cache::removeRecord(const K& key) { iStorage::ensureOpened(iStorage::removeRecordMethodName); bool noKey = false; - if (*mode != Mode::full) + if (mode != Mode::full) noKey = cache->count(key) == 0; else noKey = abscent->count(key) > 0; @@ -669,7 +662,7 @@ void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { iStorage::ensureOpened(iStorage::removeRecordMethodName); bool noKey = false; - if (*mode != Mode::full) + if (mode != Mode::full) noKey = cache->count(key) == 0; else noKey = abscent->count(key) > 0; @@ -689,27 +682,27 @@ void LMDBAL::Cache::handleRemoveRecord(const K& key) { if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease handleMode(); - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); } template uint32_t LMDBAL::Cache::count() const { - switch (*mode) { + switch (mode) { case Mode::nothing: { uint32_t sz = Storage::count(); - *sizeDifference = sz - cache->size(); + sizeDifference = sz - cache->size(); if (sz == 0) { - *mode = Mode::full; + mode = Mode::full; abscent->clear(); } else { - *mode = Mode::size; + mode = Mode::size; } return sz; } case Mode::size: - return cache->size() + *sizeDifference; + return cache->size() + sizeDifference; case Mode::full: return cache->size(); default: @@ -755,22 +748,22 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { } } - switch (*mode) { + switch (mode) { case Mode::nothing: { uint32_t sz = Storage::count(txn); if (!currentTransaction) { - *sizeDifference = sz - cache->size(); + sizeDifference = sz - cache->size(); if (sz == 0) { - *mode = Mode::full; + mode = Mode::full; abscent->clear(); } else { - *mode = Mode::size; + mode = Mode::size; } } return sz; } case Mode::size: - return cache->size() + *sizeDifference + diff; + return cache->size() + sizeDifference + diff; case Mode::full: return cache->size() + diff; default: @@ -780,10 +773,10 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { template void LMDBAL::Cache::handleMode() const { - if (*mode == Mode::size) { - --(*sizeDifference); - if (*sizeDifference == 0) { - *mode = Mode::full; + if (mode == Mode::size) { + --sizeDifference; + if (sizeDifference == 0) { + mode = Mode::full; abscent->clear(); } } @@ -804,8 +797,8 @@ template void LMDBAL::Cache::handleDrop() { cache->clear(); abscent->clear(); - *mode = Mode::full; - *sizeDifference = 0; + mode = Mode::full; + sizeDifference = 0; } template diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 0000000..7322248 --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +#ifndef LMDBAL_CURSOR_H +#define LMDBAL_CURSOR_H + +#include "base.h" +#include "storage.h" + +namespace LMDBAL { + +template +class Cursor { + friend class Storage; +private: + Cursor(Storage* parent); + ~Cursor(); + +public: + void open(); + void close(); + +private: + void terminated(); + +private: + Storage* storage; +}; + +}; + + +#include "cursor.hpp" + +#endif //LMDBAL_CURSOR_H diff --git a/src/cursor.hpp b/src/cursor.hpp new file mode 100644 index 0000000..68f8497 --- /dev/null +++ b/src/cursor.hpp @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef LMDBAL_CURSOR_HPP +#define LMDBAL_CURSOR_HPP + +#include "cursor.h" + +template +LMDBAL::Cursor::Cursor(Storage* parent): + storage(parent) +{ + +} + +template +LMDBAL::Cursor::~Cursor () { + +} + +template +void LMDBAL::Cursor::terminated () { + +} + +#endif //LMDBAL_CURSOR_HPP diff --git a/src/storage.cpp b/src/storage.cpp index 8737b6f..88a1f1f 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -42,6 +42,14 @@ LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): */ LMDBAL::iStorage::~iStorage() {} +/** + * \brief A private virtual function I need to close each storage in the database + */ +void LMDBAL::iStorage::close() { + mdb_dbi_close(db->environment, dbi); +} + + /** * \brief Drops content of a storage interface * diff --git a/src/storage.h b/src/storage.h index 8045d25..e52667b 100644 --- a/src/storage.h +++ b/src/storage.h @@ -21,6 +21,7 @@ #include "base.h" #include "serializer.h" +#include "cursor.h" namespace LMDBAL { @@ -34,7 +35,8 @@ protected: iStorage(const std::string& name, Base* parent); virtual ~iStorage(); - virtual int createStorage(MDB_txn * transaction) = 0; + virtual int open(MDB_txn * transaction) = 0; + virtual void close(); bool isDBOpened() const; const std::string& dbName() const; @@ -89,9 +91,13 @@ protected: static std::string toString(const T& value); }; +// template +// class Cursor; + template class Storage : public iStorage { friend class Base; + friend class Cursor; protected: Storage(const std::string& name, Base* parent); ~Storage() override; @@ -121,11 +127,16 @@ public: virtual uint32_t addRecords(const std::map& data, bool overwrite = false); virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); -protected: - Serializer* keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ - Serializer* valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ + Cursor* createCursor(); + void destroyCursor(Cursor* cursor); - int createStorage(MDB_txn* transaction) override; +protected: + mutable Serializer keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ + mutable Serializer valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ + std::set*> cursors; /**<\brief a set of cursors that has been created under this storage*/ + + int open(MDB_txn* transaction) override; + void close() override; }; } diff --git a/src/storage.hpp b/src/storage.hpp index 2dfdf1c..f14fa5e 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -45,18 +45,16 @@ template LMDBAL::Storage::Storage(const std::string& _name, Base* parent): iStorage(_name, parent), - keySerializer(new Serializer()), - valueSerializer(new Serializer()) + keySerializer(), + valueSerializer(), + cursors() {} /** * \brief Destroys a storage */ template -LMDBAL::Storage::~Storage() { - delete valueSerializer; - delete keySerializer; -} +LMDBAL::Storage::~Storage() {} /** * \brief Adds a key-value record to the storage @@ -104,8 +102,8 @@ template void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(addRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData = valueSerializer.setData(value); int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) @@ -158,7 +156,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio ensureOpened(forceRecordMethodName); bool added; - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); @@ -174,7 +172,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio throwUnknown(rc); } - lmdbData = valueSerializer->setData(value); + lmdbData = valueSerializer.setData(value); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != MDB_SUCCESS) throwUnknown(rc); @@ -234,12 +232,12 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti if (rc != MDB_SUCCESS) throwUnknown(rc); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_val lmdbData = valueSerializer.setData(value); rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) @@ -339,14 +337,14 @@ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { ensureOpened(getRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); - valueSerializer->deserialize(lmdbData, value); + valueSerializer.deserialize(lmdbData, value); } /** @@ -392,7 +390,7 @@ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { ensureOpened(checkRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); @@ -495,9 +493,9 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); while (rc == MDB_SUCCESS) { K key; - keySerializer->deserialize(lmdbKey, key); + keySerializer.deserialize(lmdbKey, key); V& value = result[key]; - valueSerializer->deserialize(lmdbData, value); + valueSerializer.deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } mdb_cursor_close(cursor); @@ -553,8 +551,8 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID MDB_val lmdbKey, lmdbData; for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); + lmdbKey = keySerializer.setData(pair.first); + lmdbData = valueSerializer.setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here? if (rc != MDB_SUCCESS) @@ -612,8 +610,8 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti MDB_val lmdbKey, lmdbData; int rc; for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); + lmdbKey = keySerializer.setData(pair.first); + lmdbData = valueSerializer.setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); if (rc == MDB_KEYEXIST) @@ -675,7 +673,7 @@ template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { ensureOpened(removeRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); int rc = mdb_del(txn, dbi, &lmdbKey, NULL); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); @@ -688,10 +686,35 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code */ template -int LMDBAL::Storage::createStorage(MDB_txn* transaction) { +int LMDBAL::Storage::open(MDB_txn* transaction) { return makeStorage(transaction); } +/** + * \brief A private virtual function I need to close each storage in the database + */ +template +void LMDBAL::Storage::close() { + for (Cursor* cursor : cursors) + cursor->terminated(); + + iStorage::close(); +} + +template +LMDBAL::Cursor* LMDBAL::Storage::createCursor() { + Cursor* cursor = new Cursor(this); + cursors.insert(cursor); + + return cursor; +} + +template +void LMDBAL::Storage::destroyCursor(Cursor* cursor) { + cursors.erase(cursor); + delete cursor; +} + /** * \brief A functiion to actually open MDB_dbi storage *