some tuning, specializations, basic testing
This commit is contained in:
parent
5f90a21fe6
commit
047f96b54a
@ -4,6 +4,11 @@ project(storage VERSION 0.0.1 LANGUAGES CXX)
|
|||||||
|
|
||||||
cmake_policy(SET CMP0076 NEW)
|
cmake_policy(SET CMP0076 NEW)
|
||||||
cmake_policy(SET CMP0079 NEW)
|
cmake_policy(SET CMP0079 NEW)
|
||||||
|
|
||||||
|
option(BUILD_STATIC "Builds library as static library" ON)
|
||||||
|
option(BUILD_TESTS "Builds tests" ON)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
@ -22,14 +27,36 @@ if (NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_executable(storage
|
set(SOURCES
|
||||||
main.cpp
|
|
||||||
exception.cpp
|
|
||||||
exceptions.cpp
|
exceptions.cpp
|
||||||
table.cpp
|
table.cpp
|
||||||
database.cpp
|
database.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(HEADERS
|
||||||
|
database.h
|
||||||
|
exceptions.h
|
||||||
|
table.h
|
||||||
|
table.hpp
|
||||||
|
serializer.h
|
||||||
|
serializer.hpp
|
||||||
|
serializer_uint8.hpp
|
||||||
|
serializer_uint16.hpp
|
||||||
|
serializer_uint32.hpp
|
||||||
|
serializer_uint64.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (BUILD_STATIC)
|
||||||
|
add_library(storage STATIC ${SOURCES})
|
||||||
|
else ()
|
||||||
|
add_library(storage SHARED ${SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (BUILD_TESTS)
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set_target_properties(storage PROPERTIES PUBLIC_HEADER "${HEADERS}")
|
||||||
|
|
||||||
target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR})
|
target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR})
|
||||||
target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
|
target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
|
||||||
@ -41,4 +68,9 @@ target_link_libraries(storage
|
|||||||
)
|
)
|
||||||
target_link_libraries(storage PRIVATE lmdb)
|
target_link_libraries(storage PRIVATE lmdb)
|
||||||
|
|
||||||
install(TARGETS storage RUNTIME DESTINATION bin)
|
install(TARGETS storage
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/storage
|
||||||
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/storage
|
||||||
|
)
|
||||||
|
|
||||||
|
20
database.cpp
20
database.cpp
@ -82,9 +82,29 @@ void DataBase::open()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataBase::removeDirectory()
|
||||||
|
{
|
||||||
|
if (opened) {
|
||||||
|
throw Opened(name, "");
|
||||||
|
}
|
||||||
|
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||||
|
path += "/" + getName();
|
||||||
|
QDir cache(path);
|
||||||
|
|
||||||
|
if (cache.exists()) {
|
||||||
|
return cache.removeRecursively();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString DataBase::getName() const
|
QString DataBase::getName() const
|
||||||
{
|
{
|
||||||
return QString::fromStdString(name);
|
return QString::fromStdString(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataBase::ready() const
|
||||||
|
{
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
22
database.h
22
database.h
@ -39,13 +39,19 @@ public:
|
|||||||
|
|
||||||
void open();
|
void open();
|
||||||
void close();
|
void close();
|
||||||
|
bool ready() const;
|
||||||
|
bool removeDirectory();
|
||||||
QString getName() const;
|
QString getName() const;
|
||||||
|
|
||||||
template <class K, class V>
|
template <class K, class V>
|
||||||
Table<K, V>* addTable(const QString& name);
|
Table<K, V>* addTable(const std::string& name);
|
||||||
|
|
||||||
|
template <class K, class V>
|
||||||
|
Table<K, V>* getTable(const std::string& name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//exceptions
|
//exceptions
|
||||||
|
class Exception;
|
||||||
class Directory;
|
class Directory;
|
||||||
class Closed;
|
class Closed;
|
||||||
class Opened;
|
class Opened;
|
||||||
@ -64,14 +70,18 @@ private:
|
|||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
|
|
||||||
template <class K, class V>
|
template <class K, class V>
|
||||||
DataBase::Table<K, V>* DataBase::addTable(const QString& p_name) {
|
DataBase::Table<K, V>* DataBase::addTable(const std::string& p_name) {
|
||||||
std::string nm = p_name.toStdString();
|
|
||||||
if (opened) {
|
if (opened) {
|
||||||
throw Opened(name, nm);
|
throw Opened(name, p_name);
|
||||||
}
|
}
|
||||||
DataBase::Table<K, V>* table = new DataBase::Table<K, V>(nm, this);
|
DataBase::Table<K, V>* table = new DataBase::Table<K, V>(p_name, this);
|
||||||
tables.insert(std::make_pair(nm, (_Table*)table));
|
tables.insert(std::make_pair(p_name, (_Table*)table));
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class K, class V>
|
||||||
|
DataBase::Table<K, V>* DataBase::getTable(const std::string& p_name) {
|
||||||
|
return static_cast<DataBase::Table<K, V>*>(tables.at(p_name));
|
||||||
|
}
|
||||||
|
|
||||||
#endif // CORE_DATABASE_H
|
#endif // CORE_DATABASE_H
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Squawk messenger.
|
|
||||||
* Copyright (C) 2019 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 "exception.h"
|
|
||||||
|
|
||||||
Utils::Exception::Exception()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::Exception::~Exception()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Utils::Exception::what() const noexcept( true )
|
|
||||||
{
|
|
||||||
std::string* msg = new std::string(getMessage());
|
|
||||||
return msg->c_str();
|
|
||||||
}
|
|
40
exception.h
40
exception.h
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Squawk messenger.
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef EXCEPTION_H
|
|
||||||
#define EXCEPTION_H
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace Utils
|
|
||||||
{
|
|
||||||
class Exception:
|
|
||||||
public std::exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Exception();
|
|
||||||
virtual ~Exception();
|
|
||||||
|
|
||||||
virtual std::string getMessage() const = 0;
|
|
||||||
|
|
||||||
const char* what() const noexcept( true );
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // EXCEPTION_H
|
|
@ -16,6 +16,18 @@
|
|||||||
|
|
||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
|
|
||||||
|
DataBase::Exception::Exception():
|
||||||
|
std::exception()
|
||||||
|
{}
|
||||||
|
|
||||||
|
DataBase::Exception::~Exception() {}
|
||||||
|
|
||||||
|
const char* DataBase::Exception::what() const noexcept( true )
|
||||||
|
{
|
||||||
|
std::string* msg = new std::string(getMessage());
|
||||||
|
return msg->c_str();
|
||||||
|
}
|
||||||
|
|
||||||
DataBase::Directory::Directory(const std::string& p_path):
|
DataBase::Directory::Directory(const std::string& p_path):
|
||||||
Exception(),
|
Exception(),
|
||||||
path(p_path) {}
|
path(p_path) {}
|
||||||
|
26
exceptions.h
26
exceptions.h
@ -17,10 +17,22 @@
|
|||||||
#ifndef CORE_DATABASE_EXCEPTIONS_H
|
#ifndef CORE_DATABASE_EXCEPTIONS_H
|
||||||
#define CORE_DATABASE_EXCEPTIONS_H
|
#define CORE_DATABASE_EXCEPTIONS_H
|
||||||
|
|
||||||
#include "exception.h"
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
|
||||||
class DataBase::Directory: public Utils::Exception {
|
class DataBase::Exception : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Exception();
|
||||||
|
virtual ~Exception();
|
||||||
|
|
||||||
|
virtual std::string getMessage() const = 0;
|
||||||
|
|
||||||
|
const char* what() const noexcept( true );
|
||||||
|
};
|
||||||
|
|
||||||
|
class DataBase::Directory: public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
Directory(const std::string& path);
|
Directory(const std::string& path);
|
||||||
|
|
||||||
@ -29,7 +41,7 @@ private:
|
|||||||
std::string path;
|
std::string path;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBase::Closed : public Utils::Exception {
|
class DataBase::Closed : public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName);
|
Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName);
|
||||||
|
|
||||||
@ -40,7 +52,7 @@ private:
|
|||||||
std::string tableName;
|
std::string tableName;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBase::Opened : public Utils::Exception {
|
class DataBase::Opened : public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
Opened(const std::string& dbName, const std::string& tableName);
|
Opened(const std::string& dbName, const std::string& tableName);
|
||||||
|
|
||||||
@ -50,7 +62,7 @@ private:
|
|||||||
std::string tableName;
|
std::string tableName;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBase::NotFound : public Utils::Exception {
|
class DataBase::NotFound : public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
NotFound(const std::string& key, const std::string& dbName, const std::string& tableName);
|
NotFound(const std::string& key, const std::string& dbName, const std::string& tableName);
|
||||||
|
|
||||||
@ -61,7 +73,7 @@ private:
|
|||||||
std::string tableName;
|
std::string tableName;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBase::Exist : public Utils::Exception {
|
class DataBase::Exist : public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
Exist(const std::string& key, const std::string& dbName, const std::string& tableName);
|
Exist(const std::string& key, const std::string& dbName, const std::string& tableName);
|
||||||
|
|
||||||
@ -72,7 +84,7 @@ private:
|
|||||||
std::string tableName;
|
std::string tableName;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBase::Unknown : public Utils::Exception {
|
class DataBase::Unknown : public DataBase::Exception {
|
||||||
public:
|
public:
|
||||||
Unknown(const std::string& dbName, const std::string& message, const std::optional<std::string>& tableName = std::nullopt);
|
Unknown(const std::string& dbName, const std::string& message, const std::optional<std::string>& tableName = std::nullopt);
|
||||||
|
|
||||||
|
34
main.cpp
34
main.cpp
@ -1,34 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "database.h"
|
|
||||||
#include "table.h"
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
|
|
||||||
DataBase base("test1");
|
|
||||||
DataBase::Table<uint32_t, uint32_t>* table1 = base.addTable<uint32_t, uint32_t>("table1");
|
|
||||||
DataBase::Table<QString, QString>* table2 = base.addTable<QString, QString>("table2");
|
|
||||||
|
|
||||||
base.open();
|
|
||||||
|
|
||||||
try {
|
|
||||||
table1->addRecord(1, 2);
|
|
||||||
} catch (const DataBase::Exist& error) {
|
|
||||||
std::cout << error.getMessage() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t rec1 = table1->getRecord(1);
|
|
||||||
std::cout << "table1 record under 1 is " << rec1 << std::endl;
|
|
||||||
|
|
||||||
try {
|
|
||||||
table2->addRecord("hello", "world");
|
|
||||||
} catch (const DataBase::Exist& error) {
|
|
||||||
std::cout << error.getMessage() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString rec2 = table2->getRecord("hello");
|
|
||||||
std::cout << "table2 record under hello is " << rec2.toStdString() << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -47,6 +47,9 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
#include "serializer.hpp"
|
#include "serializer.hpp"
|
||||||
|
#include "serializer_uint64.hpp"
|
||||||
#include "serializer_uint32.hpp"
|
#include "serializer_uint32.hpp"
|
||||||
|
#include "serializer_uint16.hpp"
|
||||||
|
#include "serializer_uint8.hpp"
|
||||||
|
|
||||||
#endif // CORE_DATABASE_SERIALIZER_H
|
#endif // CORE_DATABASE_SERIALIZER_H
|
||||||
|
52
serializer_uint16.hpp
Normal file
52
serializer_uint16.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Squawk messenger.
|
||||||
|
// Copyright (C) 2019 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CORE_DATABASE_SERIALIZER_UINT16_HPP
|
||||||
|
#define CORE_DATABASE_SERIALIZER_UINT16_HPP
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class DataBase::Serializer<uint16_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Serializer():value(0) {};
|
||||||
|
Serializer(const uint16_t& p_value):value(p_value) {};
|
||||||
|
~Serializer() {};
|
||||||
|
|
||||||
|
uint16_t deserialize(const MDB_val& data) {
|
||||||
|
std::memcpy(&value, data.mv_data, 2);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
MDB_val setData(const uint16_t& data) {
|
||||||
|
value = data;
|
||||||
|
return getData();
|
||||||
|
};
|
||||||
|
MDB_val getData() {
|
||||||
|
MDB_val result;
|
||||||
|
result.mv_data = &value,
|
||||||
|
result.mv_size = 2;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
void clear() {}; //not possible;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CORE_DATABASE_SERIALIZER_UINT16_HPP
|
||||||
|
|
||||||
|
|
51
serializer_uint64.hpp
Normal file
51
serializer_uint64.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Squawk messenger.
|
||||||
|
// Copyright (C) 2019 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CORE_DATABASE_SERIALIZER_UINT64_HPP
|
||||||
|
#define CORE_DATABASE_SERIALIZER_UINT64_HPP
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class DataBase::Serializer<uint64_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Serializer():value(0) {};
|
||||||
|
Serializer(const uint64_t& p_value):value(p_value) {};
|
||||||
|
~Serializer() {};
|
||||||
|
|
||||||
|
uint64_t deserialize(const MDB_val& data) {
|
||||||
|
std::memcpy(&value, data.mv_data, 8);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
MDB_val setData(const uint64_t& data) {
|
||||||
|
value = data;
|
||||||
|
return getData();
|
||||||
|
};
|
||||||
|
MDB_val getData() {
|
||||||
|
MDB_val result;
|
||||||
|
result.mv_data = &value,
|
||||||
|
result.mv_size = 8;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
void clear() {}; //not possible;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CORE_DATABASE_SERIALIZER_UINT64_HPP
|
||||||
|
|
53
serializer_uint8.hpp
Normal file
53
serializer_uint8.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Squawk messenger.
|
||||||
|
// Copyright (C) 2019 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CORE_DATABASE_SERIALIZER_UINT8_HPP
|
||||||
|
#define CORE_DATABASE_SERIALIZER_UINT8_HPP
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class DataBase::Serializer<uint8_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Serializer():value(0) {};
|
||||||
|
Serializer(const uint8_t& p_value):value(p_value) {};
|
||||||
|
~Serializer() {};
|
||||||
|
|
||||||
|
uint8_t deserialize(const MDB_val& data) {
|
||||||
|
std::memcpy(&value, data.mv_data, 1);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
MDB_val setData(const uint8_t& data) {
|
||||||
|
value = data;
|
||||||
|
return getData();
|
||||||
|
};
|
||||||
|
MDB_val getData() {
|
||||||
|
MDB_val result;
|
||||||
|
result.mv_data = &value,
|
||||||
|
result.mv_size = 1;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
void clear() {}; //not possible;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CORE_DATABASE_SERIALIZER_UINT8_HPP
|
||||||
|
|
||||||
|
|
||||||
|
|
158
storage.h
158
storage.h
@ -1,158 +0,0 @@
|
|||||||
/*
|
|
||||||
* Squawk messenger.
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CORE_STORAGE_H
|
|
||||||
#define CORE_STORAGE_H
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <lmdb.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include "exception.h"
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo write docs
|
|
||||||
*/
|
|
||||||
template <class K, class V>
|
|
||||||
class Storage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Storage(const QString& name);
|
|
||||||
~Storage();
|
|
||||||
|
|
||||||
void open();
|
|
||||||
void close();
|
|
||||||
|
|
||||||
void addRecord(const K& key, const V& value);
|
|
||||||
void changeRecord(const K& key, const V& value);
|
|
||||||
void removeRecord(const K& key);
|
|
||||||
V getRecord(const K& key) const;
|
|
||||||
QString getName() const;
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString name;
|
|
||||||
bool opened;
|
|
||||||
MDB_env* environment;
|
|
||||||
MDB_dbi base;
|
|
||||||
|
|
||||||
public:
|
|
||||||
class Directory:
|
|
||||||
public Utils::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Directory(const std::string& p_path):Exception(), path(p_path){}
|
|
||||||
|
|
||||||
std::string getMessage() const{return "Can't create directory for database at " + path;}
|
|
||||||
private:
|
|
||||||
std::string path;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Closed:
|
|
||||||
public Utils::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){}
|
|
||||||
|
|
||||||
std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;}
|
|
||||||
private:
|
|
||||||
std::string operation;
|
|
||||||
std::string account;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NotFound:
|
|
||||||
public Utils::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){}
|
|
||||||
|
|
||||||
std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;}
|
|
||||||
private:
|
|
||||||
std::string key;
|
|
||||||
std::string account;
|
|
||||||
};
|
|
||||||
|
|
||||||
class 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Exist:
|
|
||||||
public Utils::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){}
|
|
||||||
|
|
||||||
std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";}
|
|
||||||
private:
|
|
||||||
std::string account;
|
|
||||||
std::string key;
|
|
||||||
};
|
|
||||||
|
|
||||||
class 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Unknown:
|
|
||||||
public Utils::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){}
|
|
||||||
|
|
||||||
std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;}
|
|
||||||
private:
|
|
||||||
std::string account;
|
|
||||||
std::string msg;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MDB_val& operator << (MDB_val& data, QString& value);
|
|
||||||
MDB_val& operator >> (MDB_val& data, QString& value);
|
|
||||||
|
|
||||||
MDB_val& operator << (MDB_val& data, uint32_t& value);
|
|
||||||
MDB_val& operator >> (MDB_val& data, uint32_t& value);
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
std::string to_string(const QString& str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "storage.hpp"
|
|
||||||
|
|
||||||
#endif // CORE_STORAGE_H
|
|
226
storage.hpp
226
storage.hpp
@ -1,226 +0,0 @@
|
|||||||
/*
|
|
||||||
* Squawk messenger.
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
#ifndef CORE_STORAGE_HPP
|
|
||||||
#define CORE_STORAGE_HPP
|
|
||||||
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QDir>
|
|
||||||
|
|
||||||
#include "storage.h"
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
Core::Storage<K, V>::Storage(const QString& p_name):
|
|
||||||
name(p_name),
|
|
||||||
opened(false),
|
|
||||||
environment(),
|
|
||||||
base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
Core::Storage<K, V>::~Storage()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
void Core::Storage<K, V>::open()
|
|
||||||
{
|
|
||||||
if (!opened) {
|
|
||||||
mdb_env_create(&environment);
|
|
||||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
|
||||||
path += "/" + name;
|
|
||||||
QDir cache(path);
|
|
||||||
|
|
||||||
if (!cache.exists()) {
|
|
||||||
bool res = cache.mkpath(path);
|
|
||||||
if (!res) {
|
|
||||||
throw Directory(path.toStdString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mdb_env_set_maxdbs(environment, 1);
|
|
||||||
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
|
|
||||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
|
||||||
|
|
||||||
MDB_txn *txn;
|
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
|
||||||
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
|
|
||||||
mdb_txn_commit(txn);
|
|
||||||
opened = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
void Core::Storage<K, V>::close()
|
|
||||||
{
|
|
||||||
if (opened) {
|
|
||||||
mdb_dbi_close(environment, base);
|
|
||||||
mdb_env_close(environment);
|
|
||||||
opened = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
void Core::Storage<K, V>::addRecord(const K& key, const V& value)
|
|
||||||
{
|
|
||||||
if (!opened) {
|
|
||||||
throw Closed("addRecord", name.toStdString());
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
MDB_txn *txn;
|
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
|
||||||
int rc;
|
|
||||||
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
|
|
||||||
if (rc != 0) {
|
|
||||||
mdb_txn_abort(txn);
|
|
||||||
if (rc == MDB_KEYEXIST) {
|
|
||||||
throw Exist(name.toStdString(), std::to_string(key));
|
|
||||||
} else {
|
|
||||||
throw Unknown(name.toStdString(), mdb_strerror(rc));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mdb_txn_commit(txn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
void Core::Storage<K, V>::changeRecord(const K& key, const V& value)
|
|
||||||
{
|
|
||||||
if (!opened) {
|
|
||||||
throw Closed("changeRecord", name.toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
MDB_txn *txn;
|
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
|
||||||
int rc;
|
|
||||||
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
|
|
||||||
if (rc != 0) {
|
|
||||||
mdb_txn_abort(txn);
|
|
||||||
if (rc) {
|
|
||||||
throw Unknown(name.toStdString(), mdb_strerror(rc));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mdb_txn_commit(txn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
V Core::Storage<K, V>::getRecord(const K& key) const
|
|
||||||
{
|
|
||||||
if (!opened) {
|
|
||||||
throw Closed("addElement", name.toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
MDB_val lmdbKey, lmdbData;
|
|
||||||
lmdbKey << key;
|
|
||||||
|
|
||||||
MDB_txn *txn;
|
|
||||||
int rc;
|
|
||||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
|
||||||
rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
|
|
||||||
if (rc) {
|
|
||||||
mdb_txn_abort(txn);
|
|
||||||
if (rc == MDB_NOTFOUND) {
|
|
||||||
throw NotFound(std::to_string(key), name.toStdString());
|
|
||||||
} else {
|
|
||||||
throw Unknown(name.toStdString(), mdb_strerror(rc));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
|
||||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
|
||||||
V value;
|
|
||||||
ds >> value;
|
|
||||||
mdb_txn_abort(txn);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
void Core::Storage<K, V>::removeRecord(const K& key)
|
|
||||||
{
|
|
||||||
if (!opened) {
|
|
||||||
throw Closed("addElement", name.toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
MDB_val lmdbKey;
|
|
||||||
lmdbKey << key;
|
|
||||||
|
|
||||||
MDB_txn *txn;
|
|
||||||
int rc;
|
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
|
||||||
rc = mdb_del(txn, base, &lmdbKey, NULL);
|
|
||||||
if (rc) {
|
|
||||||
mdb_txn_abort(txn);
|
|
||||||
if (rc == MDB_NOTFOUND) {
|
|
||||||
throw NotFound(std::to_string(key), name.toStdString());
|
|
||||||
} else {
|
|
||||||
throw Unknown(name.toStdString(), mdb_strerror(rc));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mdb_txn_commit(txn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class K, class V>
|
|
||||||
QString Core::Storage<K, V>::getName() const {
|
|
||||||
return name;}
|
|
||||||
|
|
||||||
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, 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_STORAGE_HPP
|
|
20
table.hpp
20
table.hpp
@ -144,11 +144,26 @@ inline int DataBase::_Table::makeTable(MDB_txn* transaction) {
|
|||||||
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi);
|
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int DataBase::_Table::makeTable<uint64_t>(MDB_txn* transaction) {
|
||||||
|
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi);
|
||||||
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline int DataBase::_Table::makeTable<uint32_t>(MDB_txn* transaction) {
|
inline int DataBase::_Table::makeTable<uint32_t>(MDB_txn* transaction) {
|
||||||
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi);
|
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int DataBase::_Table::makeTable<uint16_t>(MDB_txn* transaction) {
|
||||||
|
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int DataBase::_Table::makeTable<uint8_t>(MDB_txn* transaction) {
|
||||||
|
return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi);
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
inline std::string DataBase::_Table::toString(const T& value) {
|
inline std::string DataBase::_Table::toString(const T& value) {
|
||||||
return std::to_string(value);
|
return std::to_string(value);
|
||||||
@ -159,4 +174,9 @@ inline std::string DataBase::_Table::toString(const QString& value) {
|
|||||||
return value.toStdString();
|
return value.toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline std::string DataBase::_Table::toString(const std::string& value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
#endif //CORE_TABLE_HPP
|
#endif //CORE_TABLE_HPP
|
||||||
|
22
test/CMakeLists.txt
Normal file
22
test/CMakeLists.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
enable_testing()
|
||||||
|
find_package(GTest REQUIRED)
|
||||||
|
include_directories(${GTEST_INCLUDE_DIR})
|
||||||
|
|
||||||
|
add_executable(runUnitTests
|
||||||
|
basic.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(runUnitTests PRIVATE -fPIC)
|
||||||
|
|
||||||
|
target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR})
|
||||||
|
target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
|
||||||
|
target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
runUnitTests
|
||||||
|
GTest::gtest_main
|
||||||
|
storage
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(runUnitTests)
|
66
test/basic.cpp
Normal file
66
test/basic.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "database.h"
|
||||||
|
#include "table.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class DataBaseTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
DataBaseTest():
|
||||||
|
::testing::Test(),
|
||||||
|
t1(db->getTable<uint32_t, uint32_t>("table1")),
|
||||||
|
t2(db->getTable<QString, QString>("table2")) {}
|
||||||
|
|
||||||
|
~DataBaseTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetUpTestSuite() {
|
||||||
|
if (db == nullptr) {
|
||||||
|
db = new DataBase("testBase");
|
||||||
|
db->addTable<uint32_t, uint32_t>("table1");
|
||||||
|
db->addTable<QString, QString>("table2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TearDownTestSuite() {
|
||||||
|
db->close();
|
||||||
|
db->removeDirectory();
|
||||||
|
delete db;
|
||||||
|
db = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DataBase* db;
|
||||||
|
|
||||||
|
DataBase::Table<uint32_t, uint32_t>* t1;
|
||||||
|
DataBase::Table<QString, QString>* t2;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DataBase* DataBaseTest::db = nullptr;
|
||||||
|
|
||||||
|
TEST_F(DataBaseTest, RemovingDirectory) {
|
||||||
|
EXPECT_EQ(db->removeDirectory(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DataBaseTest, OpeningDatabase) {
|
||||||
|
db->open();
|
||||||
|
EXPECT_EQ(db->ready(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DataBaseTest, AddingIntegerKey) {
|
||||||
|
EXPECT_EQ(db->ready(), true);
|
||||||
|
t1->addRecord(1, 2);
|
||||||
|
EXPECT_EQ(t1->getRecord(1), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DataBaseTest, AddingQStringKey) {
|
||||||
|
EXPECT_EQ(db->ready(), true);
|
||||||
|
t2->addRecord("hello", "world");
|
||||||
|
EXPECT_EQ(t2->getRecord("hello"), "world");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DataBaseTest, ClosingDatabase) {
|
||||||
|
db->close();
|
||||||
|
EXPECT_EQ(db->ready(), false);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user