/* * 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_STORAGE_HPP #define LMDBAL_STORAGE_HPP #include "storage.h" #include "exceptions.h" /** * \class LMDBAL::Storage * \brief This is a basic key value storage. * * \tparam K type of the keys of the storage * \tparam V type of the values of the storage * * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&) * if the database is yet closed and you're defining the storages you're going to need. * Or you can call LMDBAL::Base::getStorage(const std::string&) if the database is opened and you didn't save a pointer to the storage * * You are not supposed to instantiate or destory instances of this class yourself! */ template LMDBAL::Storage::Storage(const std::string& p_name, Base* parent): iStorage(p_name, parent), keySerializer(new Serializer()), valueSerializer(new Serializer()) {} template LMDBAL::Storage::~Storage() { delete valueSerializer; delete keySerializer; } /** * \brief Adds a key-value record to the storage * * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown * * \param[in] key key of the record * \param[in] value value of the record * * \exception LMDBAL::Exist thrown if the storage already has a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); TransactionID txn = beginTransaction(); try { Storage::addRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); } /** * \brief Adds a key-value record to the storage (transaction variant) * * This function schedules an addition of a key-value record, but doesn't immidiately adds it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown * * \param[in] key key of the record * \param[in] value value of the record * \param[in] txn transaction ID, needs to be a writable transaction! * * \exception LMDBAL::Exist thrown if the storage already has a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ 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); int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) throwDuplicateOrUnknown(rc, toString(key)); } /** * \brief Adds a key-value record to the storage, overwrites if it already exists * \param[in] key key of the record * \param[in] value value of the record * \returns true if the record was added, false otherwise * * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { ensureOpened(forceRecordMethodName); TransactionID txn = beginTransaction(); bool added; try { added = Storage::forceRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); return added; } /** * \brief Adds a key-value record to the storage, overwrites if it already exists (transaction variant) * * This function schedules an addition of a key-value record, but doesn't immidiately adds it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::Base::commitTransaction(). * * \param[in] key key of the record * \param[in] value value of the record * \param[in] txn transaction ID, needs to be a writable transaction! * \returns true if the record was added, false otherwise * * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(forceRecordMethodName); bool added; MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); switch (rc) { case MDB_SUCCESS: added = false; break; case MDB_NOTFOUND: added = true; break; default: added = false; throwUnknown(rc); } lmdbData = valueSerializer->setData(value); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != MDB_SUCCESS) throwUnknown(rc); return added; } /** * \brief Changes key-value record to the storage. * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record * \param[in] value new value of the record * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value) { ensureOpened(changeRecordMethodName); TransactionID txn = beginTransaction(); try { Storage::changeRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); } /** * \brief Changes key-value record to the storage (transaction variant) * * This function schedules a modification of a key-value record, but doesn't immidiately changes it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record * \param[in] value new value of the record * \param[in] txn transaction ID, needs to be a writable transaction! * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(changeRecordMethodName); MDB_cursor* cursor; int rc = mdb_cursor_open(txn, dbi, &cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); 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); rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); } /** * \brief Gets the record from the database * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record you look for * \returns the value from the storage * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); V value; Storage::getRecord(key, value); return value; } /** * \brief Gets the record from the database (reference variant) * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record you look for * \param[out] value the value from the storage * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::getRecord(const K& key, V& value) const { ensureOpened(getRecordMethodName); TransactionID txn = beginReadOnlyTransaction(); try { Storage::getRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; } abortTransaction(txn); } /** * \brief Gets the record from the database (transaction variant) * * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record you look for * \param[in] txn transaction ID, can be read only transaction * \returns the value from the storage * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { ensureOpened(getRecordMethodName); V value; Storage::getRecord(key, value, txn); return value; } /** * \brief Gets the record from the database (transaction, reference variant) * * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * \param[in] key key of the record you look for * \param[out] value the value from the storage * \param[in] txn transaction ID, can be read only transaction * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { ensureOpened(getRecordMethodName); 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); } /** * \brief Chechs if storage has value * * \param[in] key key of the record you look for * \returns true if there was a record with given key, false otherwise * * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::checkRecord(const K& key) const { ensureOpened(checkRecordMethodName); TransactionID txn = beginReadOnlyTransaction(); bool result; try { result = Storage::checkRecord(key, txn); } catch (...) { abortTransaction(txn); throw; } abortTransaction(txn); return result; } /** * \brief Chechs if storage has value (transaction variant) * * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * * \param[in] key key of the record you look for * \param[in] txn transaction ID, can be read only transaction * \returns true if there was a record with given key, false otherwise * * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { ensureOpened(checkRecordMethodName); MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc == MDB_SUCCESS) return true; if (rc != MDB_NOTFOUND) throwUnknown(rc); return false; } template std::map LMDBAL::Storage::readAll() const { ensureOpened(readAllMethodName); std::map result; Storage::readAll(result); return result; } template void LMDBAL::Storage::readAll(std::map& result) const { ensureOpened(readAllMethodName); TransactionID txn = beginReadOnlyTransaction(); try { Storage::readAll(result, txn); } catch (...) { abortTransaction(txn); throw; } abortTransaction(txn); } template std::map LMDBAL::Storage::readAll(TransactionID txn) const { ensureOpened(readAllMethodName); std::map result; Storage::readAll(result, txn); return result; } template void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) const { ensureOpened(readAllMethodName); MDB_cursor* cursor; MDB_val lmdbKey, lmdbData; int rc = mdb_cursor_open(txn, dbi, &cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); while (rc == MDB_SUCCESS) { K key; keySerializer->deserialize(lmdbKey, key); V& value = result[key]; valueSerializer->deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } mdb_cursor_close(cursor); if (rc != MDB_NOTFOUND) throwUnknown(rc); } template void LMDBAL::Storage::replaceAll(const std::map& data) { ensureOpened(replaceAllMethodName); TransactionID txn = beginTransaction(); try { Storage::replaceAll(data, txn); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); } template void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID txn) { ensureOpened(replaceAllMethodName); int rc = drop(txn); if (rc != MDB_SUCCESS) throwUnknown(rc); MDB_val lmdbKey, lmdbData; for (const std::pair& pair : data) { lmdbKey = keySerializer->setData(pair.first); lmdbData = valueSerializer->setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) throwUnknown(rc); } } template uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool overwrite) { ensureOpened(addRecordsMethodName); TransactionID txn = beginTransaction(); uint32_t amount; try { amount = Storage::addRecords(data, txn, overwrite); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); return amount; } template uint32_t LMDBAL::Storage::addRecords(const std::map& data, TransactionID txn, bool overwrite) { ensureOpened(addRecordsMethodName); MDB_val lmdbKey, lmdbData; int rc; for (const std::pair& pair : data) { 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) throwDuplicate(toString(pair.first)); if (rc != MDB_SUCCESS) throwUnknown(rc); } MDB_stat stat; rc = mdb_stat(txn, dbi, &stat); if (rc != MDB_SUCCESS) throwUnknown(rc); return stat.ms_entries; } template void LMDBAL::Storage::removeRecord(const K& key) { ensureOpened(removeRecordMethodName); TransactionID txn = beginTransaction(); try { Storage::removeRecord(key, txn); } catch (...) { abortTransaction(txn); throw; } commitTransaction(txn); } template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { ensureOpened(removeRecordMethodName); MDB_val lmdbKey = keySerializer->setData(key); int rc = mdb_del(txn, dbi, &lmdbKey, NULL); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); } template int LMDBAL::Storage::createStorage(MDB_txn* transaction) { return makeTable(transaction); } template inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template inline std::string LMDBAL::iStorage::toString(const T& value) { return std::to_string(value); } template<> inline std::string LMDBAL::iStorage::toString(const QString& value) { return value.toStdString(); } template<> inline std::string LMDBAL::iStorage::toString(const std::string& value) { return value; } #endif //LMDBAL_STORAGE_HPP