mirror of
https://github.com/getml/sqlgen.git
synced 2026-01-05 00:49:58 -06:00
Support for CREATE TABLE in sqlite
This commit is contained in:
@@ -22,11 +22,14 @@ struct Connection {
|
||||
|
||||
/// Executes a statement. Note that in order for the statement to take effect,
|
||||
/// you must call .commit() afterwards.
|
||||
virtual Result<Nothing> execute(const dynamic::Statement& _stmt) = 0;
|
||||
virtual Result<Nothing> execute(const std::string& _sql) = 0;
|
||||
|
||||
/// Reads the results of a SelectFrom statement.
|
||||
virtual Result<Ref<Iterator>> read(const dynamic::SelectFrom& _query) = 0;
|
||||
|
||||
/// Transpiles a statement to a particular SQL dialect.
|
||||
virtual std::string to_sql(const dynamic::Statement& _stmt) = 0;
|
||||
|
||||
/// Starts the write operation.
|
||||
virtual Result<Nothing> start_write(const dynamic::Insert& _stmt) = 0;
|
||||
|
||||
|
||||
6
include/sqlgen/internal/collect.hpp
Normal file
6
include/sqlgen/internal/collect.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef SQLGEN_INTERNAL_COLLECT_HPP_
|
||||
#define SQLGEN_INTERNAL_COLLECT_HPP_
|
||||
|
||||
namespace sqlgen::internal::collect {}
|
||||
|
||||
#endif
|
||||
20
include/sqlgen/internal/collect/vector.hpp
Normal file
20
include/sqlgen/internal/collect/vector.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef SQLGEN_INTERNAL_COLLECT_VECTOR_HPP_
|
||||
#define SQLGEN_INTERNAL_COLLECT_VECTOR_HPP_
|
||||
|
||||
#include <ranges>
|
||||
|
||||
namespace sqlgen::internal::collect {
|
||||
|
||||
template <class RangeType>
|
||||
auto vector(RangeType _r) {
|
||||
using T = std::ranges::range_value_t<RangeType>;
|
||||
std::vector<T> res;
|
||||
for (auto e : _r) {
|
||||
res.emplace_back(std::move(e));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace sqlgen::internal::collect
|
||||
|
||||
#endif
|
||||
24
include/sqlgen/internal/strings/strings.hpp
Normal file
24
include/sqlgen/internal/strings/strings.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef SQLGEN_INTERNAL_STRINGS_STRINGS_HPP_
|
||||
#define SQLGEN_INTERNAL_STRINGS_STRINGS_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sqlgen::internal::strings {
|
||||
|
||||
char to_lower(const char ch);
|
||||
|
||||
char to_upper(const char ch);
|
||||
|
||||
std::string join(const std::string& _delimiter,
|
||||
const std::vector<std::string>& _strings);
|
||||
|
||||
std::string replace_all(const std::string& _str, const std::string& _from,
|
||||
const std::string& _to);
|
||||
|
||||
std::vector<std::string> split(const std::string& _str,
|
||||
const std::string& _delimiter);
|
||||
|
||||
} // namespace sqlgen::internal::strings
|
||||
|
||||
#endif
|
||||
7
include/sqlgen/sqlite.hpp
Normal file
7
include/sqlgen/sqlite.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef SQLGEN_SQLITE_HPP_
|
||||
#define SQLGEN_SQLITE_HPP_
|
||||
|
||||
#include "../sqlgen.hpp"
|
||||
#include "sqlite/connect.hpp"
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SQLGEN_SQLITE3_CONNECTION_HPP_
|
||||
#define SQLGEN_SQLITE3_CONNECTION_HPP_
|
||||
#ifndef SQLGEN_SQLITE_CONNECTION_HPP_
|
||||
#define SQLGEN_SQLITE_CONNECTION_HPP_
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../Connection.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../dynamic/Column.hpp"
|
||||
#include "../dynamic/Statement.hpp"
|
||||
|
||||
namespace sqlgen::sqlite {
|
||||
@@ -30,11 +31,9 @@ class Connection : public sqlgen::Connection {
|
||||
|
||||
~Connection();
|
||||
|
||||
Result<Nothing> commit() final { return exec("COMMIT;"); }
|
||||
Result<Nothing> commit() final { return execute("COMMIT;"); }
|
||||
|
||||
Result<Nothing> execute(const dynamic::Statement& _stmt) final {
|
||||
return exec(to_sql(_stmt));
|
||||
}
|
||||
Result<Nothing> execute(const std::string& _sql) noexcept final;
|
||||
|
||||
Connection& operator=(const Connection& _other) = delete;
|
||||
|
||||
@@ -44,6 +43,8 @@ class Connection : public sqlgen::Connection {
|
||||
return error("TODO");
|
||||
}
|
||||
|
||||
std::string to_sql(const dynamic::Statement& _stmt) noexcept final;
|
||||
|
||||
Result<Nothing> start_write(const dynamic::Insert& _stmt) final {
|
||||
return error("TODO");
|
||||
}
|
||||
@@ -56,18 +57,22 @@ class Connection : public sqlgen::Connection {
|
||||
}
|
||||
|
||||
private:
|
||||
/// Transforms a dynamic::Column to an SQL string that defines the column in a
|
||||
/// CREATE TABLE statement.
|
||||
std::string column_to_sql_definition(const dynamic::Column& _col) noexcept;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Expresses the properies as SQL.
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept;
|
||||
|
||||
/// Expresses the type as SQL.
|
||||
std::string type_to_sql(const dynamic::Type& _type) noexcept;
|
||||
|
||||
private:
|
||||
/// The underlying sqlite3 connection.
|
||||
sqlite3* conn_;
|
||||
|
||||
16
include/sqlgen/sqlite/connect.hpp
Normal file
16
include/sqlgen/sqlite/connect.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef SQLGEN_SQLITE_CONNECT_HPP_
|
||||
#define SQLGEN_SQLITE_CONNECT_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace sqlgen::sqlite {
|
||||
|
||||
inline auto connect(const std::string& _fname = ":memory:") {
|
||||
return Connection::make(_fname);
|
||||
}
|
||||
|
||||
} // namespace sqlgen::sqlite
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
#include "sqlgen/internal/strings/strings.cpp"
|
||||
|
||||
58
src/sqlgen/internal/strings/strings.cpp
Normal file
58
src/sqlgen/internal/strings/strings.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "sqlgen/internal/strings/strings.hpp"
|
||||
|
||||
namespace sqlgen::internal::strings {
|
||||
|
||||
char to_lower(const char ch) {
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
return ch + ('a' - 'A');
|
||||
} else {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
char to_upper(const char ch) {
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
return ch + ('A' - 'a');
|
||||
} else {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
std::string join(const std::string& _delimiter,
|
||||
const std::vector<std::string>& _strings) {
|
||||
if (_strings.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
auto res = _strings[0];
|
||||
for (size_t i = 1; i < _strings.size(); ++i) {
|
||||
res += _delimiter + _strings[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string replace_all(const std::string& _str, const std::string& _from,
|
||||
const std::string& _to) {
|
||||
auto str = _str;
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(_from, pos)) != std::string::npos) {
|
||||
str.replace(pos, _from.length(), _to);
|
||||
pos += _to.length();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& _str,
|
||||
const std::string& _delimiter) {
|
||||
auto str = _str;
|
||||
size_t pos = 0;
|
||||
std::vector<std::string> result;
|
||||
while ((pos = str.find(_delimiter)) != std::string::npos) {
|
||||
result.emplace_back(str.substr(0, pos));
|
||||
str.erase(0, pos + _delimiter.length());
|
||||
}
|
||||
result.emplace_back(std::move(str));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sqlgen::internal::strings
|
||||
@@ -1,7 +1,12 @@
|
||||
#include "sqlgen/sqlite/Connection.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#include "sqlgen/internal/collect/vector.hpp"
|
||||
#include "sqlgen/internal/strings/strings.hpp"
|
||||
|
||||
namespace sqlgen::sqlite {
|
||||
|
||||
Connection::~Connection() {
|
||||
@@ -10,8 +15,21 @@ Connection::~Connection() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string Connection::column_to_sql_definition(
|
||||
const dynamic::Column& _col) noexcept {
|
||||
return "\"" + _col.name + "\"" + " " + type_to_sql(_col.type) +
|
||||
properties_to_sql(
|
||||
_col.type.visit([](const auto& _t) { return _t.properties; }));
|
||||
}
|
||||
|
||||
std::string Connection::create_table_to_sql(
|
||||
const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto col_to_sql = [&](const auto& _col) {
|
||||
return column_to_sql_definition(_col);
|
||||
};
|
||||
|
||||
std::stringstream stream;
|
||||
stream << "CREATE TABLE ";
|
||||
if (_stmt.table.schema) {
|
||||
@@ -21,9 +39,12 @@ std::string Connection::create_table_to_sql(
|
||||
if (_stmt.if_not_exists) {
|
||||
stream << "IF NOT EXISTS ";
|
||||
}
|
||||
stream << "(";
|
||||
|
||||
stream << "(";
|
||||
stream << internal::strings::join(
|
||||
", ", internal::collect::vector(_stmt.columns | transform(col_to_sql)));
|
||||
stream << ");";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
@@ -36,7 +57,7 @@ rfl::Result<Ref<sqlgen::Connection>> Connection::make(
|
||||
}
|
||||
}
|
||||
|
||||
Result<Nothing> Connection::exec(const std::string& _sql) noexcept {
|
||||
Result<Nothing> Connection::execute(const std::string& _sql) noexcept {
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(conn_, _sql.c_str(), nullptr, nullptr, &errmsg);
|
||||
if (errmsg) {
|
||||
@@ -57,6 +78,12 @@ sqlite3* Connection::make_conn(const std::string& _fname) {
|
||||
return conn;
|
||||
}
|
||||
|
||||
std::string Connection::properties_to_sql(
|
||||
const dynamic::types::Properties& _p) noexcept {
|
||||
return std::string(_p.primary ? " PRIMARY KEY" : "") +
|
||||
std::string(_p.nullable ? "" : " NOT NULL");
|
||||
}
|
||||
|
||||
Connection& Connection::operator=(Connection&& _other) {
|
||||
if (this == &_other) {
|
||||
return *this;
|
||||
@@ -78,4 +105,32 @@ std::string Connection::to_sql(const dynamic::Statement& _stmt) noexcept {
|
||||
});
|
||||
}
|
||||
|
||||
std::string Connection::type_to_sql(const dynamic::Type& _type) noexcept {
|
||||
return _type.visit([](const auto _t) -> std::string {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
if constexpr (std::is_same_v<T, dynamic::types::Boolean> ||
|
||||
std::is_same_v<T, dynamic::types::Int8> ||
|
||||
std::is_same_v<T, dynamic::types::Int16> ||
|
||||
std::is_same_v<T, dynamic::types::Int32> ||
|
||||
std::is_same_v<T, dynamic::types::Int64> ||
|
||||
std::is_same_v<T, dynamic::types::UInt8> ||
|
||||
std::is_same_v<T, dynamic::types::UInt16> ||
|
||||
std::is_same_v<T, dynamic::types::UInt32> ||
|
||||
std::is_same_v<T, dynamic::types::UInt64>) {
|
||||
return "INTEGER";
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
|
||||
std::is_same_v<T, dynamic::types::Float64>) {
|
||||
return "REAL";
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown> ||
|
||||
std::is_same_v<T, dynamic::types::Text> ||
|
||||
std::is_same_v<T, dynamic::types::VarChar> ||
|
||||
std::is_same_v<T, dynamic::types::Timestamp> ||
|
||||
std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
|
||||
return "TEXT";
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sqlgen::sqlite
|
||||
|
||||
@@ -22,3 +22,7 @@ target_link_libraries(
|
||||
|
||||
find_package(GTest)
|
||||
gtest_discover_tests(sqlgen-tests)
|
||||
|
||||
if(SQLGEN_SQLITE3)
|
||||
add_subdirectory(sqlite)
|
||||
endif()
|
||||
|
||||
19
tests/sqlite/CMakeLists.txt
Normal file
19
tests/sqlite/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
project(sqlgen-sqlite-tests)
|
||||
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp")
|
||||
|
||||
add_executable(
|
||||
sqlgen-sqlite-tests
|
||||
${SOURCES}
|
||||
)
|
||||
target_precompile_headers(sqlgen-sqlite-tests PRIVATE [["sqlgen.hpp"]] <iostream> <string> <functional> <gtest/gtest.h>)
|
||||
|
||||
|
||||
target_link_libraries(
|
||||
sqlgen-sqlite-tests
|
||||
PRIVATE
|
||||
"${SQLGEN_GTEST_LIB}"
|
||||
)
|
||||
|
||||
find_package(GTest)
|
||||
gtest_discover_tests(sqlgen-sqlite-tests)
|
||||
24
tests/sqlite/test_to_create_table.cpp
Normal file
24
tests/sqlite/test_to_create_table.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/parsing/to_create_table.hpp>
|
||||
#include <sqlgen/sqlite.hpp>
|
||||
|
||||
namespace test_to_create_table {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(sqlite, test_to_create_table) {
|
||||
const auto create_table_stmt = sqlgen::parsing::to_create_table<TestTable>();
|
||||
const auto conn = sqlgen::sqlite::connect().value();
|
||||
const auto expected =
|
||||
R"(CREATE TABLE "TestTable" IF NOT EXISTS ("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" INTEGER PRIMARY KEY NOT NULL, "nullable" TEXT);)";
|
||||
|
||||
EXPECT_EQ(conn->to_sql(create_table_stmt), expected);
|
||||
}
|
||||
} // namespace test_to_create_table
|
||||
Reference in New Issue
Block a user