diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c18426..ea1dcaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ endif () add_executable(storage main.cpp exception.cpp + exceptions.cpp table.cpp database.cpp ) diff --git a/database.cpp b/database.cpp index 0e9344c..0b10e11 100644 --- a/database.cpp +++ b/database.cpp @@ -89,10 +89,11 @@ QString Core::DataBase::getName() const template Core::DataBase::Table* Core::DataBase::addTable(const QString& p_name) { + std::string nm = p_name.toStdString(); if (opened) { - throw Core::DataBase::Opened(name); + throw Core::DataBase::Opened(name, nm); } - Core::DataBase::Table* table = new Core::DataBase::Table(); - tables.insert(std::make_pair(p_name.toStdString(), table)); + Core::DataBase::Table* table = new Core::DataBase::Table(nm, this); + tables.insert(std::make_pair(nm, table)); return table; } diff --git a/database.h b/database.h index a407531..6bb25fd 100644 --- a/database.h +++ b/database.h @@ -50,9 +50,7 @@ public: class Closed; class Opened; class NotFound; - class Empty; class Exist; - class NoAvatar; class Unknown; private: diff --git a/exceptions.cpp b/exceptions.cpp new file mode 100644 index 0000000..0c0c7d5 --- /dev/null +++ b/exceptions.cpp @@ -0,0 +1,105 @@ +// Squawk messenger. +// Copyright (C) 2019 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 "exceptions.h" + +Core::DataBase::Directory::Directory(const std::string& p_path): + Exception(), + path(p_path) {} + +std::string Core::DataBase::Directory::getMessage() const { + return "Can't create directory for database at " + path;} + +Core::DataBase::Closed::Closed( + const std::string& p_operation, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + operation(p_operation), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::Closed::getMessage() const { + return "An attempt to perform operation " + operation + + " on closed database " + dbName + + " on table " + tableName; +} + +Core::DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_tableName): + Exception(), + dbName(p_dbName), + tableName(p_tableName) {} + + +std::string Core::DataBase::Opened::getMessage() const { + return "An attempt to add table " + tableName + + " to the database " + dbName + + " but it's can't be done because the DataBase is already opened." + + " Please add all tables before opening DataBase."; +} + +Core::DataBase::NotFound::NotFound( + const std::string& p_key, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + key(p_key), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::NotFound::getMessage() const { + return "Element for id " + key + " wasn't found " + + " in database " + dbName + + " in table " + tableName;} + +Core::DataBase::Exist::Exist( + const std::string& p_key, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + key(p_key), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::Exist::getMessage() const { + return "An attempt to insert element with key " + key + + " to database " + dbName + + " to table " + tableName + + " but it already has an element with given id"; +} + +Core::DataBase::Unknown::Unknown( + const std::string& p_dbName, + const std::string& message, + const std::optional& p_tableName +): + Exception(), + dbName(p_dbName), + tableName(p_tableName), + msg(message) {} + +std::string Core::DataBase::Unknown::getMessage() const +{ + std::string result = "Unknown error in database " + dbName; + if (tableName.has_value()) { + result += " in table " + tableName.value(); + } + result += ": " + msg; + return result; +} diff --git a/exceptions.h b/exceptions.h index 82a0fce..7e2bd34 100644 --- a/exceptions.h +++ b/exceptions.h @@ -24,82 +24,64 @@ namespace Core { class DataBase::Directory: public Utils::Exception { public: - Directory(const std::string& p_path):Exception(), path(p_path){} + Directory(const std::string& path); - std::string getMessage() const{return "Can't create directory for database at " + path;} + std::string getMessage() const; private: std::string path; }; class DataBase::Closed : public Utils::Exception { public: - Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} + Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} + std::string getMessage() const; private: std::string operation; - std::string account; + std::string dbName; + std::string tableName; }; class DataBase::Opened : public Utils::Exception { public: - Opened(const std::string& tn):Exception(), tableName(tn){} + Opened(const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to add table " + tableName + " but it's impossible to do if the DataBase is already opened";} + std::string getMessage() const; private: + std::string dbName; std::string tableName; }; class DataBase::NotFound : public Utils::Exception { public: - NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} + NotFound(const std::string& key, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} + std::string getMessage() const; private: std::string key; - std::string account; -}; - -class DataBase::Empty : public Utils::Exception { -public: - Empty(const std::string& acc):Exception(), account(acc){} - - std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} -private: - std::string account; + std::string dbName; + std::string tableName; }; class DataBase::Exist : public Utils::Exception { public: - Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} + Exist(const std::string& key, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} + std::string getMessage() const; private: - std::string account; std::string key; -}; - -class DataBase::NoAvatar : public Utils::Exception { -public: - NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ - if (resource.size() == 0) { - resource = "for himself"; - } - } - - std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} -private: - std::string element; - std::string resource; + std::string dbName; + std::string tableName; }; class DataBase::Unknown : public Utils::Exception { public: - Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} + Unknown(const std::string& dbName, const std::string& message, const std::optional& tableName = std::nullopt); - std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} + std::string getMessage() const; private: - std::string account; + std::string dbName; + std::optional tableName; std::string msg; }; } diff --git a/main.cpp b/main.cpp index 5d0f628..3375807 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,17 @@ #include -#include +#include "database.h" +#include "table.h" int main(int argc, char **argv) { - std::cout << "Hello, world!" << std::endl; + + Core::DataBase base("test1"); + Core::DataBase::Table* table1 = base.addTable("table1"); + Core::DataBase::Table* table2 = base.addTable("table2"); + + base.open(); + + table1->addRecord(1, 2); + return 0; } diff --git a/table.cpp b/table.cpp index adc2265..17b6e03 100644 --- a/table.cpp +++ b/table.cpp @@ -16,9 +16,10 @@ #include "table.h" -Core::DataBase::_Table::_Table(DataBase* parent): +Core::DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): dbi(), - db(parent) + db(parent), + name(p_name) { } diff --git a/table.h b/table.h index 8a39d05..cdde697 100644 --- a/table.h +++ b/table.h @@ -23,19 +23,20 @@ namespace Core { class DataBase::_Table { public: - _Table(DataBase* parent); + _Table(const std::string& name, DataBase* parent); virtual ~_Table(); public: MDB_dbi dbi; DataBase* db; + const std::string name; }; template class DataBase::Table : private DataBase::_Table { friend class DataBase; private: - Table(DataBase* parent); + Table(const std::string& name, DataBase* parent); ~Table() override; public: @@ -47,6 +48,16 @@ public: } +MDB_val& operator << (MDB_val& data, const QString& value); +MDB_val& operator >> (MDB_val& data, QString& value); + +MDB_val& operator << (MDB_val& data, const uint32_t& value); +MDB_val& operator >> (MDB_val& data, uint32_t& value); + +namespace std { + std::string to_string(const QString& str); +} + #include "table.hpp" #endif // CORE_TABLE_H diff --git a/table.hpp b/table.hpp index d900940..8ccbca0 100644 --- a/table.hpp +++ b/table.hpp @@ -18,22 +18,27 @@ #define CORE_TABLE_HPP #include "table.h" +#include "exceptions.h" +template +Core::DataBase::Table::Table(const std::string& p_name, Core::DataBase* parent): + _Table(p_name, parent) +{ +} + +template +Core::DataBase::Table::~Table() { +} template void Core::DataBase::Table::addRecord(const K& key, const V& value) { if (!db->opened) { - throw Closed("addRecord", db->name); + throw Closed("addRecord", db->name, name); } - QByteArray ba; - QDataStream ds(&ba, QIODevice::WriteOnly); - ds << value; MDB_val lmdbKey, lmdbData; lmdbKey << key; - - lmdbData.mv_size = ba.size(); - lmdbData.mv_data = (uint8_t*)ba.data(); + lmdbData << value; MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -41,13 +46,117 @@ void Core::DataBase::Table::addRecord(const K& key, const V& value) { if (rc != 0) { mdb_txn_abort(txn); if (rc == MDB_KEYEXIST) { - throw Exist(db->name, std::to_string(key)); + throw Exist(std::to_string(key), db->name, name); } else { - throw Unknown(db->name, mdb_strerror(rc)); + throw Unknown(db->name, mdb_strerror(rc), name); } } else { mdb_txn_commit(txn); } } + + +template +void Core::DataBase::Table::changeRecord(const K& key, const V& value) { + if (!db->opened) { + throw Closed("changeRecord", db->name, name); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + lmdbData << value; + MDB_txn *txn; + mdb_txn_begin(db->environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + mdb_txn_commit(txn); + } +} + +template +V Core::DataBase::Table::getRecord(const K& key) const { + if (!db->opened) { + throw Closed("getRecord", db->name, name); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), db->name, name); + } else { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + V value; + lmdbData >> value; + mdb_txn_abort(txn); + + return value; + } +} + +template +void Core::DataBase::Table::removeRecord(const K& key) { + if (!db->opened) { + throw Closed("removeRecord", db->name, name); + } + + MDB_val lmdbKey; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(db->environment, NULL, 0, &txn); + rc = mdb_del(txn, dbi, &lmdbKey, NULL); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), db->name, name); + } else { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + mdb_txn_commit(txn); + } +} + +MDB_val& operator << (MDB_val& data, const QString& value) { + QByteArray ba = value.toUtf8(); + data.mv_size = ba.size(); + data.mv_data = ba.data(); + return data; +} +MDB_val& operator >> (MDB_val& data, QString& value) { + value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); + return data; +} + +MDB_val& operator << (MDB_val& data, const uint32_t& value) { + data.mv_size = 4; + data.mv_data = &value; + return data; +} +MDB_val& operator >> (MDB_val& data, uint32_t& value) { + std::memcpy(&value, data.mv_data, data.mv_size); + return data; +} + +std::string std::to_string(const QString& str) { + return str.toStdString(); +} + #endif //CORE_TABLE_HPP