/* * 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 "base.h" #include "exceptions.h" #include "storage.h" #define UNUSED(x) (void)(x) LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), size(mapSize), environment(), storages(), transactions(new Transactions()) {} LMDBAL::Base::~Base() { close(); delete transactions; for (const std::pair& pair : storages) delete pair.second; } void LMDBAL::Base::close() { if (opened) { for (LMDBAL::TransactionID id : *transactions) abortTransaction(id, emptyName); for (const std::pair& pair : storages) { iStorage* storage = pair.second; mdb_dbi_close(environment, storage->dbi); } mdb_env_close(environment); transactions->clear(); opened = false; } } /** * Almost every LMDBAL::Base require it to be opened, this function opens it. It laso creates the directory for the database if it was an initial launch */ void LMDBAL::Base::open() { if (!opened) { mdb_env_create(&environment); QString path = createDirectory(); mdb_env_set_maxdbs(environment, storages.size()); mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { iStorage* storage = pair.second; int rc = storage->createStorage(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); } commitPrivateTransaction(txn, emptyName); opened = true; } } bool LMDBAL::Base::removeDirectory() { if (opened) throw Opened(name, "remove database directory"); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + getName(); QDir cache(path); if (cache.exists()) return cache.removeRecursively(); else return true; } QString LMDBAL::Base::createDirectory() { if (opened) throw Opened(name, "create database directory"); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + getName(); QDir cache(path); if (!cache.exists()) { bool res = cache.mkpath(path); if (!res) throw Directory(path.toStdString()); } return path; } QString LMDBAL::Base::getName() const { return QString::fromStdString(name);} bool LMDBAL::Base::ready() const { return opened;} void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { int rc = pair.second->drop(txn); if (rc) throw Unknown(name, mdb_strerror(rc), pair.first); } commitPrivateTransaction(txn, emptyName); } LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { return beginReadOnlyTransaction(emptyName);} LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const { return beginTransaction(emptyName);} void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { return abortTransaction(id, emptyName);} void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) const { return commitTransaction(id, emptyName);} LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); transactions->emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, true); return txn; } LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); transactions->emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, false); return txn; } void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); Transactions::iterator itr = transactions->find(id); if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to abort transaction: transaction was not found", storageName); abortPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionAborted(id); transactions->erase(itr); } void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); Transactions::iterator itr = transactions->find(id); if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to commit transaction: transaction was not found", storageName); commitPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionCommited(id); transactions->erase(itr); } LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); if (rc) { mdb_txn_abort(txn); throw Unknown(name, mdb_strerror(rc), storageName); } return txn; } LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, 0, &txn); if (rc) { mdb_txn_abort(txn); throw Unknown(name, mdb_strerror(rc), storageName); } return txn; } void LMDBAL::Base::abortPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { UNUSED(storageName); mdb_txn_abort(id); } void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { int rc = mdb_txn_commit(id); if (rc != MDB_SUCCESS) throw Unknown(name, mdb_strerror(rc), storageName); }