From b2f4192a4f9f69667ec283bed71b0755389dce7e Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 9 May 2025 18:17:51 +0200 Subject: [PATCH] Added to_sql --- include/sqlgen.hpp | 2 + include/sqlgen/CreateTable.hpp | 15 +++++++ include/sqlgen/Insert.hpp | 15 +++++++ include/sqlgen/postgres.hpp | 1 + include/sqlgen/postgres/Connection.hpp | 4 +- include/sqlgen/postgres/to_sql.hpp | 14 ++++++- include/sqlgen/sqlite/Connection.hpp | 2 +- include/sqlgen/sqlite/to_sql.hpp | 13 +++++- include/sqlgen/transpilation/to_sql.hpp | 48 ++++++++++++++++++++++ src/sqlgen/postgres/Connection.cpp | 2 +- src/sqlgen/postgres/to_sql.cpp | 2 +- src/sqlgen/sqlite/Connection.cpp | 4 +- src/sqlgen/sqlite/to_sql.cpp | 2 +- tests/postgres/test_create_table_dry.cpp | 23 +++++++++++ tests/postgres/test_insert_dry.cpp | 24 +++++++++++ tests/postgres/test_to_select_from.cpp | 30 -------------- tests/postgres/test_to_select_from_dry.cpp | 24 +++++++++++ tests/postgres/test_where_dry.cpp | 29 +++++++++++++ 18 files changed, 214 insertions(+), 40 deletions(-) create mode 100644 include/sqlgen/CreateTable.hpp create mode 100644 include/sqlgen/Insert.hpp create mode 100644 include/sqlgen/transpilation/to_sql.hpp create mode 100644 tests/postgres/test_create_table_dry.cpp create mode 100644 tests/postgres/test_insert_dry.cpp delete mode 100644 tests/postgres/test_to_select_from.cpp create mode 100644 tests/postgres/test_to_select_from_dry.cpp create mode 100644 tests/postgres/test_where_dry.cpp diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index 2921460..d51595b 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -2,7 +2,9 @@ #define SQLGEN_HPP_ #include "sqlgen/Connection.hpp" +#include "sqlgen/CreateTable.hpp" #include "sqlgen/Flatten.hpp" +#include "sqlgen/Insert.hpp" #include "sqlgen/Iterator.hpp" #include "sqlgen/IteratorBase.hpp" #include "sqlgen/Literal.hpp" diff --git a/include/sqlgen/CreateTable.hpp b/include/sqlgen/CreateTable.hpp new file mode 100644 index 0000000..1f59291 --- /dev/null +++ b/include/sqlgen/CreateTable.hpp @@ -0,0 +1,15 @@ +#ifndef SQLGEN_CREATETABLE_HPP_ +#define SQLGEN_CREATETABLE_HPP_ + +#include + +namespace sqlgen { + +/// Helper class for to_sql. +template +struct CreateTable {}; + +}; // namespace sqlgen + +#endif + diff --git a/include/sqlgen/Insert.hpp b/include/sqlgen/Insert.hpp new file mode 100644 index 0000000..481ed1d --- /dev/null +++ b/include/sqlgen/Insert.hpp @@ -0,0 +1,15 @@ +#ifndef SQLGEN_INSERT_HPP_ +#define SQLGEN_INSERT_HPP_ + +#include + +namespace sqlgen { + +/// Helper class for to_sql. +template +struct Insert {}; + +}; // namespace sqlgen + +#endif + diff --git a/include/sqlgen/postgres.hpp b/include/sqlgen/postgres.hpp index 5ee513b..1ff83b4 100644 --- a/include/sqlgen/postgres.hpp +++ b/include/sqlgen/postgres.hpp @@ -4,5 +4,6 @@ #include "../sqlgen.hpp" #include "postgres/Credentials.hpp" #include "postgres/connect.hpp" +#include "postgres/to_sql.hpp" #endif diff --git a/include/sqlgen/postgres/Connection.hpp b/include/sqlgen/postgres/Connection.hpp index 2a9824e..46f76b8 100644 --- a/include/sqlgen/postgres/Connection.hpp +++ b/include/sqlgen/postgres/Connection.hpp @@ -41,11 +41,11 @@ class Connection : public sqlgen::Connection { Result> read(const dynamic::SelectFrom& _query) final; std::string to_sql(const dynamic::Statement& _stmt) noexcept final { - return postgres::to_sql(_stmt); + return postgres::to_sql_impl(_stmt); } Result start_write(const dynamic::Insert& _stmt) final { - return execute(to_sql(_stmt)); + return execute(postgres::to_sql_impl(_stmt)); } Result end_write() final; diff --git a/include/sqlgen/postgres/to_sql.hpp b/include/sqlgen/postgres/to_sql.hpp index 2323405..da663b4 100644 --- a/include/sqlgen/postgres/to_sql.hpp +++ b/include/sqlgen/postgres/to_sql.hpp @@ -2,13 +2,25 @@ #define SQLGEN_POSTGRES_TO_SQL_HPP_ #include +#include #include "../dynamic/Statement.hpp" +#include "../transpilation/to_sql.hpp" namespace sqlgen::postgres { /// Transpiles a dynamic general SQL statement to the postgres dialect. -std::string to_sql(const dynamic::Statement& _stmt) noexcept; +std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept; + +/// Transpiles any SQL statement to the postgres dialect. +template +std::string to_sql(const T& _t) noexcept { + if constexpr (std::is_same_v, dynamic::Statement>) { + return to_sql_impl(_t); + } else { + return to_sql_impl(transpilation::to_sql(_t)); + } +} } // namespace sqlgen::postgres diff --git a/include/sqlgen/sqlite/Connection.hpp b/include/sqlgen/sqlite/Connection.hpp index 562807a..0ba0851 100644 --- a/include/sqlgen/sqlite/Connection.hpp +++ b/include/sqlgen/sqlite/Connection.hpp @@ -37,7 +37,7 @@ class Connection : public sqlgen::Connection { Result> read(const dynamic::SelectFrom& _query) final; std::string to_sql(const dynamic::Statement& _stmt) noexcept final { - return sqlite::to_sql(_stmt); + return sqlite::to_sql_impl(_stmt); } Result start_write(const dynamic::Insert& _stmt) final; diff --git a/include/sqlgen/sqlite/to_sql.hpp b/include/sqlgen/sqlite/to_sql.hpp index 2fd5079..9959e30 100644 --- a/include/sqlgen/sqlite/to_sql.hpp +++ b/include/sqlgen/sqlite/to_sql.hpp @@ -4,11 +4,22 @@ #include #include "../dynamic/Statement.hpp" +#include "../transpilation/to_sql.hpp" namespace sqlgen::sqlite { /// Transpiles a dynamic general SQL statement to the sqlite dialect. -std::string to_sql(const dynamic::Statement& _stmt) noexcept; +std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept; + +/// Transpiles any SQL statement to the sqlite dialect. +template +std::string to_sql(const T& _t) noexcept { + if constexpr (std::is_same_v, dynamic::Statement>) { + return to_sql_impl(_t); + } else { + return to_sql_impl(transpilation::to_sql(_t)); + } +} } // namespace sqlgen::sqlite diff --git a/include/sqlgen/transpilation/to_sql.hpp b/include/sqlgen/transpilation/to_sql.hpp new file mode 100644 index 0000000..d6c67ca --- /dev/null +++ b/include/sqlgen/transpilation/to_sql.hpp @@ -0,0 +1,48 @@ +#ifndef SQLGEN_TRANSPILATION_TO_SQL_HPP_ +#define SQLGEN_TRANSPILATION_TO_SQL_HPP_ + +#include + +#include "../CreateTable.hpp" +#include "../Insert.hpp" +#include "../dynamic/Statement.hpp" +#include "../read.hpp" +#include "to_create_table.hpp" +#include "to_insert.hpp" +#include "to_select_from.hpp" +#include "value_t.hpp" + +namespace sqlgen::transpilation { + +template +struct ToSQL; + +template +struct ToSQL> { + dynamic::Statement operator()(const auto&) const { + return to_create_table(); + } +}; + +template +struct ToSQL> { + dynamic::Statement operator()(const auto&) const { return to_insert(); } +}; + +template +struct ToSQL> { + dynamic::Statement operator()(const auto& _read) const { + return to_select_from, WhereType, OrderByType, + LimitType>(_read.where_, _read.limit_); + } +}; + +template +dynamic::Statement to_sql(const T& _t) { + return ToSQL>{}(_t); +} + +} // namespace sqlgen::transpilation + +#endif diff --git a/src/sqlgen/postgres/Connection.cpp b/src/sqlgen/postgres/Connection.cpp index d5cedd6..bfa741b 100644 --- a/src/sqlgen/postgres/Connection.cpp +++ b/src/sqlgen/postgres/Connection.cpp @@ -46,7 +46,7 @@ typename Connection::ConnPtr Connection::make_conn( } Result> Connection::read(const dynamic::SelectFrom& _query) { - const auto sql = postgres::to_sql(_query); + const auto sql = postgres::to_sql_impl(_query); try { return Ref(Ref::make(sql, conn_)); } catch (std::exception& e) { diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 08a770c..3706d74 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -223,7 +223,7 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept { return stream.str(); } -std::string to_sql(const dynamic::Statement& _stmt) noexcept { +std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { return _stmt.visit([&](const auto& _s) -> std::string { using S = std::remove_cvref_t; if constexpr (std::is_same_v) { diff --git a/src/sqlgen/sqlite/Connection.cpp b/src/sqlgen/sqlite/Connection.cpp index 969e60f..b6ac28d 100644 --- a/src/sqlgen/sqlite/Connection.cpp +++ b/src/sqlgen/sqlite/Connection.cpp @@ -42,7 +42,7 @@ typename Connection::ConnPtr Connection::make_conn(const std::string& _fname) { } Result> Connection::read(const dynamic::SelectFrom& _query) { - const auto sql = to_sql(_query); + const auto sql = to_sql_impl(_query); sqlite3_stmt* p_stmt = nullptr; @@ -70,7 +70,7 @@ Result Connection::start_write(const dynamic::Insert& _stmt) { ".end_write() before you can start another."); } - const auto sql = to_sql(_stmt); + const auto sql = to_sql_impl(_stmt); sqlite3_stmt* p_stmt = nullptr; diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index d89cd79..167a20f 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -210,7 +210,7 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept { return stream.str(); } -std::string to_sql(const dynamic::Statement& _stmt) noexcept { +std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { return _stmt.visit([&](const auto& _s) -> std::string { using S = std::remove_cvref_t; if constexpr (std::is_same_v) { diff --git a/tests/postgres/test_create_table_dry.cpp b/tests/postgres/test_create_table_dry.cpp new file mode 100644 index 0000000..b2705cc --- /dev/null +++ b/tests/postgres/test_create_table_dry.cpp @@ -0,0 +1,23 @@ +#include + +#include +#include + +namespace test_create_table_dry { + +struct TestTable { + std::string field1; + int32_t field2; + sqlgen::PrimaryKey id; + std::optional nullable; +}; + +TEST(postgres, test_create_table_dry) { + const auto query = sqlgen::CreateTable{}; + + const auto expected = + R"(CREATE TABLE IF NOT EXISTS "TestTable" ("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" INTEGER NOT NULL, "nullable" TEXT, PRIMARY KEY ("id"));)"; + + EXPECT_EQ(sqlgen::postgres::to_sql(query), expected); +} +} // namespace test_create_table_dry diff --git a/tests/postgres/test_insert_dry.cpp b/tests/postgres/test_insert_dry.cpp new file mode 100644 index 0000000..4ee7335 --- /dev/null +++ b/tests/postgres/test_insert_dry.cpp @@ -0,0 +1,24 @@ +#include + +#include +#include + +namespace test_insert_dry { + +struct TestTable { + std::string field1; + int32_t field2; + sqlgen::PrimaryKey id; + std::optional nullable; +}; + +TEST(postgres, test_insert_dry) { + const auto query = sqlgen::Insert{}; + + const auto expected = + "COPY \"public\".\"TestTable\"(\"field1\", \"field2\", \"id\", " + "\"nullable\") FROM STDIN WITH DELIMITER '\t' NULL '\e' QUOTE '\a';"; + + EXPECT_EQ(sqlgen::postgres::to_sql(query), expected); +} +} // namespace test_insert_dry diff --git a/tests/postgres/test_to_select_from.cpp b/tests/postgres/test_to_select_from.cpp deleted file mode 100644 index 1aadbe1..0000000 --- a/tests/postgres/test_to_select_from.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#include -#include -#include - -namespace test_to_select_from { - -struct TestTable { - std::string field1; - int32_t field2; - sqlgen::PrimaryKey id; - std::optional nullable; -}; - -TEST(postgres, test_to_select_from) { - const auto select_from_stmt = - sqlgen::transpilation::to_select_from(); - const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", - .password = "postgres", - .host = "localhost", - .dbname = "postgres", - .port = 5432}; - const auto conn = sqlgen::postgres::connect(credentials).value(); - const auto expected = - R"(SELECT "field1", "field2", "id", "nullable" FROM "TestTable";)"; - - EXPECT_EQ(conn->to_sql(select_from_stmt), expected); -} -} // namespace test_to_select_from diff --git a/tests/postgres/test_to_select_from_dry.cpp b/tests/postgres/test_to_select_from_dry.cpp new file mode 100644 index 0000000..18a818d --- /dev/null +++ b/tests/postgres/test_to_select_from_dry.cpp @@ -0,0 +1,24 @@ +#include + +#include +#include +#include + +namespace test_to_select_from_dry { + +struct TestTable { + std::string field1; + int32_t field2; + sqlgen::PrimaryKey id; + std::optional nullable; +}; + +TEST(postgres, test_to_select_from_dry) { + const auto query = sqlgen::read>; + + const auto expected = + R"(SELECT "field1", "field2", "id", "nullable" FROM "TestTable";)"; + + EXPECT_EQ(sqlgen::postgres::to_sql(query), expected); +} +} // namespace test_to_select_from_dry diff --git a/tests/postgres/test_where_dry.cpp b/tests/postgres/test_where_dry.cpp new file mode 100644 index 0000000..284099d --- /dev/null +++ b/tests/postgres/test_where_dry.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include +#include + +namespace test_where_dry { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_where_dry) { + using namespace sqlgen; + + const auto query = sqlgen::read> | + where("age"_c < 18 and "first_name"_c != "Hugo") | + order_by("age"_c); + + const auto expected = + R"(SELECT "id", "first_name", "last_name", "age" FROM "Person" WHERE ("age" < 18) AND ("first_name" != 'Hugo') ORDER BY "age";)"; + + EXPECT_EQ(sqlgen::postgres::to_sql(query), expected); +} + +} // namespace test_where_dry