Started adding the sqlite connection

This commit is contained in:
Dr. Patrick Urbanke
2025-04-01 08:17:35 +02:00
parent dc578df313
commit 9b40b301fc
12 changed files with 200 additions and 19 deletions

View File

@@ -12,13 +12,8 @@ if (NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 20)
endif()
set(SQLGEN_USE_VCPKG_DEFAULT ON)
if(SQLGEN_BUILD_BENCHMARKS)
# TODO
endif()
option(SQLGEN_USE_VCPKG "Use VCPKG to download and build dependencies" ${SQLGEN_USE_VCPKG_DEFAULT})
if (SQLGEN_USE_VCPKG)
@@ -49,6 +44,14 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
if (SQLGEN_SQLITE3)
list(APPEND SQLGEN_SOURCES src/sqlgen_sqlite.cpp)
if (NOT TARGET unofficial-sqlite3)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
endif()
target_link_libraries(sqlgen PUBLIC unofficial::sqlite3::sqlite3)
endif()
find_package(reflectcpp CONFIG REQUIRED)
target_link_libraries(sqlgen PUBLIC reflectcpp::reflectcpp)

View File

@@ -7,7 +7,7 @@ sqlgen is closely integrated with our sister project [reflect-cpp](https://githu
## Simple example
Here is how you create a simple sqlite3 database
Here is how you create a simple sqlite database
and insert some data:
```cpp
@@ -24,7 +24,7 @@ const auto people = std::vector<People>({
.last_name = "Simpson",
.age = 45}});
const auto conn = sqlgen::sqlite3::connect("example.db");
const auto conn = sqlgen::sqlite::connect("example.db");
// Will automatically create a table called 'People'
// with the columns 'first_name', 'last_name' and 'age',
@@ -43,7 +43,7 @@ and print the results as a JSON:
#include <rfl/json.hpp> // reflect-cpp
#include <sqlgen/sqlite.hpp>
const auto conn = sqlgen::sqlite3::connect("example.db");
const auto conn = sqlgen::sqlite::connect("example.db");
const sqlgen::Result<std::vector<People>> result =
sqlgen::read<std::vector<People>>(conn);

View File

@@ -8,9 +8,9 @@
#include "Iterator.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "dynamic/CreateTable.hpp"
#include "dynamic/Insert.hpp"
#include "dynamic/SelectFrom.hpp"
#include "dynamic/Statement.hpp"
namespace sqlgen {
@@ -22,8 +22,7 @@ struct Connection {
/// Executes a statement. Note that in order for the statement to take effect,
/// you must call .commit() afterwards.
/// TODO: Abstract away the different statements using rfl::TaggedUnion.
virtual Result<Nothing> execute(const dynamic::CreateTable& _stmt) = 0;
virtual Result<Nothing> execute(const dynamic::Statement& _stmt) = 0;
/// Reads the results of a SelectFrom statement.
virtual Result<Ref<Iterator>> read(const dynamic::SelectFrom& _query) = 0;

View File

@@ -2,6 +2,7 @@
#define SQLGEN_RESULT_HPP_
#include <rfl.hpp>
#include <string>
namespace sqlgen {
@@ -12,6 +13,8 @@ using Nothing = rfl::Nothing;
template <class T>
using Result = rfl::Result<T>;
inline auto error(const std::string& _msg) { return rfl::error(_msg); }
}; // namespace sqlgen
#endif

View File

@@ -1,6 +1,7 @@
#ifndef SQLGEN_DYNAMIC_COLUMN_HPP_
#define SQLGEN_DYNAMIC_COLUMN_HPP_
#include <optional>
#include <string>
#include <vector>
@@ -9,9 +10,9 @@
namespace sqlgen::dynamic {
struct Column {
std::string alias;
std::string name;
Type type;
std::optional<std::string> alias;
std::string name;
Type type;
};
} // namespace sqlgen::dynamic

View File

@@ -0,0 +1,14 @@
#ifndef SQLGEN_DYNAMIC_STATEMENT_HPP_
#define SQLGEN_DYNAMIC_STATEMENT_HPP_
#include <rfl.hpp>
#include "CreateTable.hpp"
namespace sqlgen::dynamic {
using Statement = rfl::TaggedUnion<"stmt", CreateTable>;
} // namespace sqlgen::dynamic
#endif

View File

@@ -9,9 +9,9 @@
namespace sqlgen::dynamic {
struct Table {
std::string alias;
std::optional<std::string> alias;
std::string name;
std::string schema;
std::optional<std::string> schema;
};
} // namespace sqlgen::dynamic

View File

@@ -1,6 +1,7 @@
#ifndef SQLGEN_PARSING_GET_SCHEMA_HPP_
#define SQLGEN_PARSING_GET_SCHEMA_HPP_
#include <optional>
#include <rfl.hpp>
#include <type_traits>
@@ -9,13 +10,13 @@
namespace sqlgen::parsing {
template <class T>
std::string get_schema() noexcept {
std::optional<std::string> get_schema() noexcept {
using Type = std::remove_cvref_t<T>;
if constexpr (has_schema<Type>) {
using LiteralType = typename Type::schema;
return LiteralType().str();
} else {
return "";
return std::nullopt;
}
}

View File

@@ -0,0 +1,78 @@
#ifndef SQLGEN_SQLITE3_CONNECTION_HPP_
#define SQLGEN_SQLITE3_CONNECTION_HPP_
#include <sqlite3.h>
#include <memory>
#include <rfl.hpp>
#include <stdexcept>
#include <string>
#include "../Connection.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../dynamic/Statement.hpp"
namespace sqlgen::sqlite {
class Connection : public sqlgen::Connection {
public:
Connection(const std::string& _fname) : conn_(make_conn(_fname)) {}
Connection(const Connection& _other) = delete;
Connection(Connection&& _other) : conn_(_other.conn_) {
_other.conn_ = nullptr;
}
static rfl::Result<Ref<sqlgen::Connection>> make(
const std::string& _fname) noexcept;
~Connection();
Result<Nothing> commit() final { return exec("COMMIT;"); }
Result<Nothing> execute(const dynamic::Statement& _stmt) final {
return exec(to_sql(_stmt));
}
Connection& operator=(const Connection& _other) = delete;
Connection& operator=(Connection&& _other);
Result<Ref<Iterator>> read(const dynamic::SelectFrom& _query) final {
return error("TODO");
}
Result<Nothing> start_write(const dynamic::Insert& _stmt) final {
return error("TODO");
}
Result<Nothing> end_write() final { return error("TODO"); }
Result<Nothing> write(
const std::vector<std::vector<std::optional<std::string>>>& _data) final {
return error("TODO");
}
private:
/// Transforms a CreateTable Statement to an SQL string.
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
/// Wrapper around sqlite3_exec.
Result<Nothing> exec(const std::string& _sql) noexcept;
/// Transforms a Statement to an SQL string.
std::string to_sql(const dynamic::Statement& _stmt) noexcept;
/// Generates the underlying connection.
static sqlite3* make_conn(const std::string& _fname);
private:
/// The underlying sqlite3 connection.
sqlite3* conn_;
};
} // namespace sqlgen::sqlite
#endif

View File

@@ -0,0 +1,81 @@
#include "sqlgen/sqlite/Connection.hpp"
#include <sstream>
namespace sqlgen::sqlite {
Connection::~Connection() {
if (conn_) {
sqlite3_close(conn_);
}
}
std::string Connection::create_table_to_sql(
const dynamic::CreateTable& _stmt) noexcept {
std::stringstream stream;
stream << "CREATE TABLE ";
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
}
stream << "\"" << _stmt.table.name << "\" ";
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << "(";
stream << ");";
return stream.str();
}
rfl::Result<Ref<sqlgen::Connection>> Connection::make(
const std::string& _fname) noexcept {
try {
return Ref<sqlgen::Connection>(Ref<Connection>::make(_fname));
} catch (std::exception& e) {
return error(e.what());
}
}
Result<Nothing> Connection::exec(const std::string& _sql) noexcept {
char* errmsg = nullptr;
sqlite3_exec(conn_, _sql.c_str(), nullptr, nullptr, &errmsg);
if (errmsg) {
const auto err = error(errmsg);
sqlite3_free(errmsg);
return err;
}
return Nothing{};
}
sqlite3* Connection::make_conn(const std::string& _fname) {
sqlite3* conn = nullptr;
const auto err = sqlite3_open(_fname.c_str(), &conn);
if (err) {
throw std::runtime_error("Can't open database: " +
std::string(sqlite3_errmsg(conn)));
}
return conn;
}
Connection& Connection::operator=(Connection&& _other) {
if (this == &_other) {
return *this;
}
sqlite3_close(conn_);
conn_ = _other.conn_;
_other.conn_ = nullptr;
return *this;
}
std::string Connection::to_sql(const dynamic::Statement& _stmt) noexcept {
return _stmt.visit([&](const auto& _s) -> std::string {
using S = std::remove_cvref_t<decltype(_s)>;
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
return create_table_to_sql(_s);
} else {
static_assert(rfl::always_false_v<S>, "Unsupported type.");
}
});
}
} // namespace sqlgen::sqlite

1
src/sqlgen_sqlite.cpp Normal file
View File

@@ -0,0 +1 @@
#include "sqlgen/sqlite/Connection.cpp"

View File

@@ -17,7 +17,7 @@ TEST(general, test_to_create_table) {
const auto create_table_stmt = sqlgen::parsing::to_create_table<TestTable>();
const std::string expected =
R"({"table":{"alias":"","name":"TestTable","schema":""},"columns":[{"alias":"","name":"field1","type":{"type":"Text","properties":{"primary":false,"nullable":false}}},{"alias":"","name":"field2","type":{"type":"Int32","properties":{"primary":false,"nullable":false}}},{"alias":"","name":"id","type":{"type":"UInt32","properties":{"primary":true,"nullable":false}}},{"alias":"","name":"nullable","type":{"type":"Text","properties":{"primary":false,"nullable":true}}}],"if_not_exists":true})";
R"({"table":{"name":"TestTable"},"columns":[{"name":"field1","type":{"type":"Text","properties":{"primary":false,"nullable":false}}},{"name":"field2","type":{"type":"Int32","properties":{"primary":false,"nullable":false}}},{"name":"id","type":{"type":"UInt32","properties":{"primary":true,"nullable":false}}},{"name":"nullable","type":{"type":"Text","properties":{"primary":false,"nullable":true}}}],"if_not_exists":true})";
const auto json_str = rfl::json::write(create_table_stmt);