added sqlite connection class (progress)

This commit is contained in:
Sascha Kuehl 2023-11-07 23:07:49 +01:00
parent f4f5c00eec
commit 715f4dff8f
21 changed files with 645 additions and 73 deletions

View File

@ -2,15 +2,24 @@ cmake_minimum_required(VERSION 3.26)
project(query)
set(CMAKE_CXX_STANDARD 17)
include_directories(${PROJECT_SOURCE_DIR}/include)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(ODBC REQUIRED)
find_package(SQLite3 REQUIRED)
message(STATUS "Found SQLite3 ${SQLite3_VERSION}")
message(STATUS "Adding SQLite3 include directory: ${SQLite3_INCLUDE_DIRS}")
message(STATUS "Adding SQLite3 libs: ${SQLite3_LIBRARIES}")
message(STATUS "Common flags ${CMAKE_CXX_FLAGS}")
message(STATUS "Debug flags ${CMAKE_CXX_FLAGS_DEBUG}")
message(STATUS "Relase flags ${CMAKE_CXX_FLAGS_RELEASE}")
message(STATUS "Linker flags ${CMAKE_EXE_LINKER_FLAGS}")
add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(backends)
add_executable(query main.cpp
include/matador/sql/connection_impl.hpp
include/matador/sql/connection_info.hpp)
add_executable(query main.cpp)
target_link_libraries(query matador)

1
backends/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(sqlite)

View File

@ -0,0 +1,17 @@
set(HEADER
include/sqlite_connection.hpp
include/sqlite_error.hpp
)
set(SOURCES
src/sqlite_connection.cpp
src/sqlite_error.cpp
)
add_library(matador-sqlite SHARED ${SOURCES} ${HEADER})
target_include_directories(matador-sqlite PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/backends/sqlite/include
${SQLite3_INCLUDE_DIRS})
target_link_libraries(matador-sqlite matador ${SQLite3_LIBRARIES})

View File

@ -0,0 +1,26 @@
#ifndef QUERY_SQLITE_CONNECTION_HPP
#define QUERY_SQLITE_CONNECTION_HPP
#include "matador/sql/connection_impl.hpp"
#include <sqlite3.h>
namespace matador::backends::sqlite {
class sqlite_connection : public matador::sql::connection_impl
{
public:
explicit sqlite_connection(sql::connection_info info);
void open() override;
void close() override;
bool is_open() override;
void execute(const std::string &stmt) override;
void prepare(const std::string &stmt) override;
private:
sqlite3 *sqlite_db_{};
};
}
#endif //QUERY_SQLITE_CONNECTION_HPP

View File

@ -0,0 +1,15 @@
#ifndef QUERY_SQLITE_ERROR_HPP
#define QUERY_SQLITE_ERROR_HPP
#include <string>
struct sqlite3;
namespace matador::backends::sqlite {
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source);
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql);
}
#endif //QUERY_SQLITE_ERROR_HPP

View File

@ -0,0 +1,46 @@
#include "sqlite_connection.hpp"
#include "sqlite_error.hpp"
#include <utility>
namespace matador::backends::sqlite {
sqlite_connection::sqlite_connection(sql::connection_info info)
: connection_impl(std::move(info)) {
}
void sqlite_connection::open()
{
const auto ret = sqlite3_open(info().database.c_str(), &sqlite_db_);
if (ret != SQLITE_OK) {
throw_sqlite_error(ret, sqlite_db_, "open");
}
}
void sqlite_connection::close()
{
int ret = sqlite3_close(sqlite_db_);
throw_sqlite_error(ret, sqlite_db_, "close");
sqlite_db_ = nullptr;
}
bool sqlite_connection::is_open()
{
return false;
}
void sqlite_connection::execute(const std::string &stmt)
{
}
void sqlite_connection::prepare(const std::string &stmt)
{
}
}

View File

@ -0,0 +1,28 @@
#include "sqlite_error.hpp"
#include <stdexcept>
#include <sstream>
#include <sqlite3.h>
namespace matador::backends::sqlite {
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source)
{
if (ec != SQLITE_OK) {
std::stringstream msg;
msg << "sqlite error (" << source << "): " << sqlite3_errmsg(db);
throw std::logic_error(msg.str());
}
}
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql)
{
if (ec != SQLITE_OK) {
std::stringstream msg;
msg << "sqlite error (" << source << ", sql: " << sql << "): " << sqlite3_errmsg(db);
throw std::logic_error(msg.str());
}
}
}

View File

@ -1,7 +1,7 @@
#ifndef QUERY_CONNECTION_HPP
#define QUERY_CONNECTION_HPP
#include "matador/sql/connection_intermediates.hpp"
#include "matador/sql/query_intermediates.hpp"
#include "matador/sql/dialect.hpp"
#include "matador/sql/query_builder.hpp"
@ -12,20 +12,21 @@ namespace matador::sql {
class connection
{
public:
connection();
query_create_intermediate create();
query_drop_intermediate drop();
query_select_intermediate select(std::initializer_list<std::string> column_names);
query_insert_intermediate insert();
query_update_intermediate update(const std::string &table);
query_delete_intermediate remove();
connection() = default;
explicit connection(std::string dns);
void open();
void close();
[[nodiscard]] bool is_open() const;
[[nodiscard]] const std::string& dns() const;
query_result<record> fetch(const std::string &sql);
std::pair<size_t, std::string> execute(const std::string &sql);
private:
dialect dialect_;
query_builder query_;
std::string dns_;
bool is_open_{false};
};
}

View File

@ -0,0 +1,107 @@
#ifndef QUERY_CONNECTION_POOL_HPP
#define QUERY_CONNECTION_POOL_HPP
#include <utility>
#include <queue>
#include <mutex>
#include <string>
#include <unordered_set>
namespace matador::sql {
template < class Connection >
class connection_pool;
template < class Connection >
class connection_ptr
{
public:
connection_ptr(Connection *c, connection_pool<Connection> &pool)
: connection_(c), pool_(pool) {}
~connection_ptr();
connection_ptr(const connection_ptr &) = delete;
connection_ptr& operator=(const connection_ptr &) = delete;
connection_ptr(connection_ptr &&) noexcept = default;
connection_ptr& operator=(connection_ptr &&) noexcept = default;
Connection* operator->() { return connection_; }
Connection& operator*() { return *connection_; }
[[nodiscard]] bool valid() const { return connection_ != nullptr; }
private:
friend class connection_pool<Connection>;
Connection *connection_;
connection_pool<Connection> &pool_;
};
template < class Connection >
class connection_pool
{
public:
connection_pool(const std::string &db, unsigned int count) {
connection_repo_.reserve(count);
for (auto i = 0U; i < count; ++i) {
connection_repo_.emplace_back(db + std::to_string(i+1));
idle_connections_.push(&connection_repo_.back());
idle_connections_.back()->open();
}
}
connection_ptr<Connection> acquire() {
std::lock_guard<std::mutex> guard(mutex_);
if (idle_connections_.empty()) {
return {nullptr, *this};
}
auto ptr = idle_connections_.front();
idle_connections_.pop();
inuse_connections_.insert(ptr);
return { ptr, *this };
}
void release(Connection *c) {
if (c == nullptr) {
return;
}
std::lock_guard<std::mutex> guard(mutex_);
if (inuse_connections_.erase(c) > 0) {
idle_connections_.push(c);
}
}
void release(connection_ptr<Connection> &c) {
release(c.connection_);
c.connection_ = nullptr;
}
std::size_t size() const { return connection_repo_.size(); }
std::size_t idle() const {
std::lock_guard<std::mutex> guard(mutex_);
return idle_connections_.size();
}
std::size_t inuse() const {
std::lock_guard<std::mutex> guard(mutex_);
return inuse_connections_.size();
}
private:
mutable std::mutex mutex_;
std::vector<Connection> connection_repo_;
using pointer = Connection*;
using connections = std::queue<pointer>;
using connection_set = std::unordered_set<pointer>;
connections idle_connections_;
connection_set inuse_connections_;
};
template<class Connection>
connection_ptr<Connection>::~connection_ptr() {
pool_.release(connection_);
}
}
#endif //QUERY_CONNECTION_POOL_HPP

View File

@ -1,5 +1,5 @@
#ifndef QUERY_CONNECTION_INTERMEDIATES_HPP
#define QUERY_CONNECTION_INTERMEDIATES_HPP
#ifndef QUERY_QUERY_INTERMEDIATES_HPP
#define QUERY_QUERY_INTERMEDIATES_HPP
#include "matador/sql/column.hpp"
#include "matador/sql/key_value_pair.hpp"
@ -11,20 +11,20 @@
namespace matador::sql {
class basic_condition;
class connection;
class session;
class query_builder;
class query_intermediate
{
public:
query_intermediate(connection &db, query_builder &query);
query_intermediate(session &db, query_builder &query);
protected:
connection& db();
session& db();
query_builder& query();
private:
connection &db_;
session &db_;
query_builder &query_;
};
@ -195,4 +195,4 @@ public:
};
}
#endif //QUERY_CONNECTION_INTERMEDIATES_HPP
#endif //QUERY_QUERY_INTERMEDIATES_HPP

View File

@ -0,0 +1,31 @@
#ifndef QUERY_SESSION_HPP
#define QUERY_SESSION_HPP
#include "matador/sql/connection.hpp"
#include "matador/sql/connection_pool.hpp"
namespace matador::sql {
class session
{
public:
explicit session(connection_pool<connection> &pool);
query_create_intermediate create();
query_drop_intermediate drop();
query_select_intermediate select(std::initializer_list<std::string> column_names);
query_insert_intermediate insert();
query_update_intermediate update(const std::string &table);
query_delete_intermediate remove();
query_result<record> fetch(const std::string &sql);
std::pair<size_t, std::string> execute(const std::string &sql);
private:
connection_pool<connection> &pool_;
dialect dialect_;
query_builder query_;
};
}
#endif //QUERY_SESSION_HPP

View File

@ -0,0 +1,107 @@
#ifndef QUERY_LIBRARY_HPP
#define QUERY_LIBRARY_HPP
#include <string>
#if defined(_MSC_VER) || defined(__MINGW32__)
#define _WINSOCKAPI_
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace matador::utils {
#ifndef MATADOR_DOXYGEN_DOC
#if defined(_MSC_VER) || defined(__MINGW32__)
typedef FARPROC func_ptr;
#else
typedef void *func_ptr;
#endif
#endif /* MATADOR_DOXYGEN_DOC */
/**
* @class library
* @brief Helps to load and unload
* external libraries.
*
* This class represents a loader and un-loader
* for an external library. The path to the library
* is given by a string.
* It can be loaded and unloaded. Furthermore it
* provides a method to get a pointer to an function
* exported by the library. The function is identified
* by a name.
*/
class library
{
public:
/**
* Create a unspecified library serializable
*/
library() = default;
/**
* Create a library for the given lib path
*
* @param lib The path to the library to map.
*/
explicit library(std::string lib);
~library();
/**
* Load the underlying library.
* If the library path is invalid
* or the library cannot be loaded
* false is returned.
*
* @return True on successful library loading.
*/
bool load();
/**
* Sets the library (and subsequently close and
* overwrite an already instantiated library) and
* load the underlying library. If the library
* path is invalid or the library cannot be loaded
* false is returned.
*
* @param lib The path to the library to load.
* @return True on successful library loading.
*/
bool load(const std::string &lib);
/**
* Unload an loaded library. If
* the library isn't loaded or an
* error occurred while unloading
* false is returned.
*
* @return True on successful unloading.
*/
bool unload();
/**
* Returns the pointer to the exported
* function of the library identified
* by the given name.
*
* @param f The name of the exported function.
* @return The pointer to the function.
*/
[[nodiscard]] func_ptr function(const std::string &f) const;
private:
std::string lib_;
#if defined(_WIN32) || defined(__MINGW32__)
HMODULE handle_ = nullptr;
#else
void *handle_ = nullptr;
#endif
};
}
#endif //QUERY_LIBRARY_HPP

View File

@ -5,10 +5,11 @@ set(SQL_SOURCES
sql/key_value_pair.cpp
sql/basic_condition.cpp
sql/connection.cpp
sql/connection_intermediates.cpp
sql/query_intermediates.cpp
sql/record.cpp
sql/connection_info.cpp
sql/connection_impl.cpp)
sql/connection_impl.cpp
sql/session.cpp)
set(SQL_HEADER
../include/matador/sql/dialect.hpp
@ -19,22 +20,27 @@ set(SQL_HEADER
../include/matador/sql/basic_condition.hpp
../include/matador/sql/condition.hpp
../include/matador/sql/connection.hpp
../include/matador/sql/connection_intermediates.hpp
../include/matador/sql/query_intermediates.hpp
../include/matador/sql/result.hpp
../include/matador/sql/record.hpp
../include/matador/sql/query_result.hpp
../include/matador/sql/connection_impl.hpp
../include/matador/sql/connection_info.hpp)
../include/matador/sql/connection_info.hpp
../include/matador/sql/connection_pool.hpp
../include/matador/sql/session.hpp)
set(UTILS_HEADER
../include/matador/utils/field_attributes.hpp
../include/matador/utils/string.hpp
../include/matador/utils/constraints.hpp)
../include/matador/utils/constraints.hpp
../include/matador/utils/library.hpp)
set(UTILS_SOURCES
utils/field_attributes.cpp
utils/string.cpp
sql/condition.cpp)
sql/condition.cpp
utils/library.cpp)
add_library(matador STATIC ${SQL_SOURCES} ${SQL_HEADER} ${UTILS_SOURCES} ${UTILS_HEADER})
set_target_properties(matador PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(matador PUBLIC ${PROJECT_SOURCE_DIR}/include)
#set_target_properties(matador PROPERTIES LINKER_LANGUAGE CXX)

View File

@ -1,39 +1,30 @@
#include <utility>
#include "matador/sql/connection.hpp"
namespace matador::sql {
connection::connection(std::string dns)
: dns_(std::move(dns)) {}
connection::connection()
: query_(dialect_) {}
query_create_intermediate connection::create()
void connection::open()
{
return query_create_intermediate{*this, query_.create()};
is_open_ = true;
}
query_drop_intermediate connection::drop()
void connection::close()
{
return query_drop_intermediate{*this, query_.drop()};
is_open_ = false;
}
query_select_intermediate connection::select(std::initializer_list<std::string> column_names)
bool connection::is_open() const
{
return query_select_intermediate{*this, query_.select(column_names)};
return is_open_;
}
query_insert_intermediate connection::insert()
const std::string &connection::dns() const
{
return query_insert_intermediate{*this, query_.insert()};
}
query_update_intermediate connection::update(const std::string &table)
{
return query_update_intermediate{*this, query_.update(table)};
}
query_delete_intermediate connection::remove()
{
return query_delete_intermediate{*this, query_.remove()};
return dns_;
}
query_result<record> connection::fetch(const std::string &sql)

View File

@ -1,9 +1,9 @@
#include "matador/sql/connection_intermediates.hpp"
#include "matador/sql/connection.hpp"
#include "matador/sql/query_intermediates.hpp"
#include "matador/sql/session.hpp"
#include "matador/sql/query_builder.hpp"
namespace matador::sql {
connection &query_intermediate::db()
session &query_intermediate::db()
{
return db_;
}
@ -23,7 +23,7 @@ record query_select_finish::fetch_one()
return db().fetch(query().compile()).begin();
}
query_intermediate::query_intermediate(connection &db, query_builder &query)
query_intermediate::query_intermediate(session &db, query_builder &query)
: db_(db), query_(query) {}
query_offset_intermediate query_order_direction_intermediate::offset(size_t offset)

56
src/sql/session.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "matador/sql/session.hpp"
#include <stdexcept>
namespace matador::sql {
session::session(connection_pool<connection> &pool)
: pool_(pool)
, query_(dialect_) {}
query_create_intermediate session::create()
{
return query_create_intermediate{*this, query_.create()};
}
query_drop_intermediate session::drop()
{
return query_drop_intermediate{*this, query_.drop()};
}
query_select_intermediate session::select(std::initializer_list<std::string> column_names)
{
return query_select_intermediate{*this, query_.select(column_names)};
}
query_insert_intermediate session::insert()
{
return query_insert_intermediate{*this, query_.insert()};
}
query_update_intermediate session::update(const std::string &table)
{
return query_update_intermediate{*this, query_.update(table)};
}
query_delete_intermediate session::remove()
{
return query_delete_intermediate{*this, query_.remove()};
}
query_result<record> session::fetch(const std::string &sql) {
auto c = pool_.acquire();
if (!c.valid()) {
throw std::logic_error("no database connection available");
}
return c->fetch(sql);
}
std::pair<size_t, std::string> session::execute(const std::string &sql) {
auto c = pool_.acquire();
if (!c.valid()) {
throw std::logic_error("no database connection available");
}
return c->execute(sql);
}
}

70
src/utils/library.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "matador/utils/library.hpp"
namespace matador::utils {
library::library(std::string lib)
: lib_(std::move(lib))
{}
library::~library()
{
unload();
}
bool library::load()
{
#if defined(_MSC_VER) || defined(__MINGW32__)
handle_ = LoadLibrary((lib_ + ".dll").c_str());
#elif defined(__APPLE__)
handle_ = dlopen(std::string("lib" + lib_ + ".dylib").c_str(), RTLD_LAZY);
#else
handle_ = dlopen(std::string("./lib" + lib_ + ".so").c_str(), RTLD_LAZY);
#endif
if (!handle_) {
#if defined(_MSC_VER) || defined(__MINGW32__)
DWORD errorMessageID = ::GetLastError();
#else
// TODO: handle win32 and linux error
fprintf(stdout, "dlopen error: %s", dlerror());
#endif
return false;
}
return true;
}
bool library::load(const std::string &lib)
{
if (handle_) {
return false;
}
lib_ = lib;
return load();
}
bool library::unload()
{
bool ret(true);
if (handle_) {
#if defined(_MSC_VER) || defined(__MINGW32__)
ret = FreeLibrary(handle_) == TRUE;
#else
ret = dlclose(handle_) == 0;
#endif
if (!ret) {
} else {
handle_ = nullptr;
}
}
return ret;
}
func_ptr library::function(const std::string &f) const
{
#if defined(_MSC_VER) || defined(__MINGW32__)
return GetProcAddress(handle_, f.c_str());
#else
return dlsym(handle_, f.c_str());
#endif
}
}

View File

@ -9,7 +9,9 @@ FetchContent_Declare(
FetchContent_MakeAvailable(Catch2)
add_executable(tests builder.cpp
connection.cpp
record.cpp)
session.cpp
record.cpp
connection_pool.cpp
query.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain matador)
target_include_directories(tests PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include)

42
test/connection_pool.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/connection_pool.hpp>
using namespace matador::sql;
TEST_CASE("Create connection pool", "[connection pool]") {
using pool_t = connection_pool<connection>;
pool_t pool("db", 4);
REQUIRE(pool.size() == 4);
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
auto ptr = pool.acquire();
REQUIRE(ptr.valid());
REQUIRE(ptr->is_open());
REQUIRE(!ptr->dns().empty());
REQUIRE(pool.idle() == 3);
REQUIRE(pool.inuse() == 1);
pool.release(ptr);
REQUIRE(!ptr.valid());
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
{
auto ptr2 = pool.acquire();
REQUIRE(ptr2.valid());
REQUIRE(ptr2->is_open());
REQUIRE(!ptr2->dns().empty());
REQUIRE(pool.idle() == 3);
REQUIRE(pool.inuse() == 1);
}
REQUIRE(pool.idle() == 4);
REQUIRE(pool.inuse() == 0);
}

9
test/query.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <catch2/catch_test_macros.hpp>
#include "catch2/generators/catch_generators.hpp"
TEST_CASE("Query test", "[query]") {
auto dns = GENERATE(as<std::string>{}, "mssql", "sqlite", "postgres" );
INFO(dns);
REQUIRE(!dns.empty());
}

View File

@ -2,14 +2,15 @@
#include <matador/sql/column.hpp>
#include <matador/sql/condition.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/session.hpp>
using namespace matador::sql;
TEST_CASE("Execute create table statement", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
const auto res = c.create()
const auto res = s.create()
.table("person", {
make_pk_column<unsigned long>("id"),
make_column<std::string>("name", 255),
@ -21,9 +22,10 @@ TEST_CASE("Execute create table statement", "[connection]") {
}
TEST_CASE("Execute drop table statement", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
const auto res = c.drop()
const auto res = s.drop()
.table("person")
.execute();
@ -31,9 +33,10 @@ TEST_CASE("Execute drop table statement", "[connection]") {
}
TEST_CASE("Execute select statement with where clause", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.select({"id", "name", "color"})
auto res = s.select({"id", "name", "color"})
.from("person")
.where("id"_col == 8)
.fetch_all();
@ -42,9 +45,10 @@ TEST_CASE("Execute select statement with where clause", "[connection]") {
}
TEST_CASE("Execute select statement with order by", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.select({"id", "name", "color"})
auto res = s.select({"id", "name", "color"})
.from("person")
.where("id"_col == 8)
.order_by("name").desc()
@ -54,9 +58,10 @@ TEST_CASE("Execute select statement with order by", "[connection]") {
}
TEST_CASE("Execute select statement with group by and order by", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.select({"id", "name", "color"})
auto res = s.select({"id", "name", "color"})
.from("person")
.where("id"_col == 8)
.group_by("color")
@ -67,9 +72,10 @@ TEST_CASE("Execute select statement with group by and order by", "[connection]")
}
TEST_CASE("Execute insert statement", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.insert()
auto res = s.insert()
.into("person", {"id", "name", "color"})
.values({7, "george", "green"})
.execute();
@ -78,9 +84,10 @@ TEST_CASE("Execute insert statement", "[connection]") {
}
TEST_CASE("Execute update statement", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.update("person")
auto res = s.update("person")
.set({
{"name", "george"},
{"color", "green"}
@ -92,9 +99,10 @@ TEST_CASE("Execute update statement", "[connection]") {
}
TEST_CASE("Execute delete statement", "[connection]") {
connection c;
connection_pool<connection> pool("dns", 4);
session s(pool);
auto res = c.remove()
auto res = s.remove()
.from("person", "p")
.where("id"_col == 9)
.execute();