mirror of
https://github.com/getml/sqlgen.git
synced 2026-02-17 06:08:39 -06:00
Started adding the sqlite connection
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
14
include/sqlgen/dynamic/Statement.hpp
Normal file
14
include/sqlgen/dynamic/Statement.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
78
include/sqlgen/sqlite/Connection.hpp
Normal file
78
include/sqlgen/sqlite/Connection.hpp
Normal 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
|
||||
81
src/sqlgen/sqlite/Connection.cpp
Normal file
81
src/sqlgen/sqlite/Connection.cpp
Normal 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
1
src/sqlgen_sqlite.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "sqlgen/sqlite/Connection.cpp"
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user