diff --git a/include/sqlgen/postgres/Connection.hpp b/include/sqlgen/postgres/Connection.hpp index 43b3b10..2a9824e 100644 --- a/include/sqlgen/postgres/Connection.hpp +++ b/include/sqlgen/postgres/Connection.hpp @@ -16,6 +16,7 @@ #include "../dynamic/Statement.hpp" #include "Credentials.hpp" #include "exec.hpp" +#include "to_sql.hpp" namespace sqlgen::postgres { @@ -37,11 +38,11 @@ class Connection : public sqlgen::Connection { return exec(conn_, _sql).transform([](auto&&) { return Nothing{}; }); } - Result> read(const dynamic::SelectFrom& _query) final { - return error("TODO"); - } + Result> read(const dynamic::SelectFrom& _query) final; - std::string to_sql(const dynamic::Statement& _stmt) noexcept final; + std::string to_sql(const dynamic::Statement& _stmt) noexcept final { + return postgres::to_sql(_stmt); + } Result start_write(const dynamic::Insert& _stmt) final { return execute(to_sql(_stmt)); @@ -53,97 +54,17 @@ class Connection : public sqlgen::Connection { const std::vector>>& _data) final; private: - std::string add_not_null_if_necessary( - const dynamic::types::Properties& _p) const noexcept; - - std::string column_or_value_to_sql( - const dynamic::ColumnOrValue& _col) const noexcept; - - std::string column_to_sql_definition( - const dynamic::Column& _col) const noexcept; - - std::string condition_to_sql( - const dynamic::Condition& _condition) const noexcept; - - template - std::string condition_to_sql_impl( - const ConditionType& _condition) const noexcept; - - std::string create_table_to_sql( - const dynamic::CreateTable& _stmt) const noexcept; - - static std::string get_name(const dynamic::Column& _col) { return _col.name; } - - std::vector get_primary_keys( - const dynamic::CreateTable& _stmt) const noexcept; - - std::string insert_to_sql(const dynamic::Insert& _stmt) const noexcept; - static ConnPtr make_conn(const std::string& _conn_str); - std::string select_from_to_sql( - const dynamic::SelectFrom& _stmt) const noexcept; - std::string to_buffer( const std::vector>& _line) const noexcept; - std::string type_to_sql(const dynamic::Type& _type) const noexcept; - - static std::string wrap_in_quotes(const std::string& _name) noexcept { - return "\"" + _name + "\""; - } - private: ConnPtr conn_; Credentials credentials_; }; -template -std::string Connection::condition_to_sql_impl( - const ConditionType& _condition) const noexcept { - using C = std::remove_cvref_t; - std::stringstream stream; - - if constexpr (std::is_same_v) { - stream << "(" << condition_to_sql(*_condition.cond1) << ") AND (" - << condition_to_sql(*_condition.cond2) << ")"; - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " = " - << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " >= " << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " > " - << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " != " << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " <= " << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " < " - << column_or_value_to_sql(_condition.op2); - - } else if constexpr (std::is_same_v) { - stream << "(" << condition_to_sql(*_condition.cond1) << ") OR (" - << condition_to_sql(*_condition.cond2) << ")"; - - } else { - static_assert(rfl::always_false_v, "Not all cases where covered."); - } - - return stream.str(); -} - } // namespace sqlgen::postgres #endif diff --git a/include/sqlgen/postgres/to_sql.hpp b/include/sqlgen/postgres/to_sql.hpp new file mode 100644 index 0000000..2323405 --- /dev/null +++ b/include/sqlgen/postgres/to_sql.hpp @@ -0,0 +1,15 @@ +#ifndef SQLGEN_POSTGRES_TO_SQL_HPP_ +#define SQLGEN_POSTGRES_TO_SQL_HPP_ + +#include + +#include "../dynamic/Statement.hpp" + +namespace sqlgen::postgres { + +/// Transpiles a dynamic general SQL statement to the postgres dialect. +std::string to_sql(const dynamic::Statement& _stmt) noexcept; + +} // namespace sqlgen::postgres + +#endif diff --git a/src/sqlgen/postgres/Connection.cpp b/src/sqlgen/postgres/Connection.cpp index c2eabd4..d5cedd6 100644 --- a/src/sqlgen/postgres/Connection.cpp +++ b/src/sqlgen/postgres/Connection.cpp @@ -7,84 +7,10 @@ #include "sqlgen/internal/collect/vector.hpp" #include "sqlgen/internal/strings/strings.hpp" +#include "sqlgen/postgres/Iterator.hpp" namespace sqlgen::postgres { -std::string Connection::add_not_null_if_necessary( - const dynamic::types::Properties& _p) const noexcept { - return std::string(_p.nullable ? "" : " NOT NULL"); -} - -std::string Connection::column_or_value_to_sql( - const dynamic::ColumnOrValue& _col) const noexcept { - const auto handle_value = [](const auto& _v) -> std::string { - using Type = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return "'" + _v.val + "'"; - } else { - return std::to_string(_v.val); - } - }; - - return _col.visit([&](const auto& _c) -> std::string { - using Type = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return wrap_in_quotes(_c.name); - } else { - return _c.visit(handle_value); - } - }); -} - -std::string Connection::condition_to_sql( - const dynamic::Condition& _cond) const noexcept { - return _cond.val.visit( - [&](const auto& _c) { return condition_to_sql_impl(_c); }); -} - -std::string Connection::column_to_sql_definition( - const dynamic::Column& _col) const noexcept { - return wrap_in_quotes(_col.name) + " " + type_to_sql(_col.type) + - add_not_null_if_necessary( - _col.type.visit([](const auto& _t) { return _t.properties; })); -} - -std::string Connection::create_table_to_sql( - const dynamic::CreateTable& _stmt) const 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.if_not_exists) { - stream << "IF NOT EXISTS "; - } - - if (_stmt.table.schema) { - stream << wrap_in_quotes(*_stmt.table.schema) << "."; - } - stream << wrap_in_quotes(_stmt.table.name) << " "; - - stream << "("; - stream << internal::strings::join( - ", ", internal::collect::vector(_stmt.columns | transform(col_to_sql))); - - const auto primary_keys = get_primary_keys(_stmt); - - if (primary_keys.size() != 0) { - stream << ", PRIMARY KEY (" << internal::strings::join(", ", primary_keys) - << ")"; - } - - stream << ");"; - - return stream.str(); -} - Result Connection::end_write() { if (PQputCopyEnd(conn_.get(), NULL) == -1) { return error(PQerrorMessage(conn_.get())); @@ -96,32 +22,6 @@ Result Connection::end_write() { return Nothing{}; } -std::vector Connection::get_primary_keys( - const dynamic::CreateTable& _stmt) const noexcept { - using namespace std::ranges::views; - - const auto is_primary_key = [](const auto& _col) -> bool { - return _col.type.visit( - [](const auto& _t) -> bool { return _t.properties.primary; }); - }; - - return internal::collect::vector(_stmt.columns | filter(is_primary_key) | - transform(get_name) | - transform(wrap_in_quotes)); -} - -std::string Connection::insert_to_sql( - const dynamic::Insert& _stmt) const noexcept { - using namespace std::ranges::views; - const auto schema = wrap_in_quotes(_stmt.table.schema.value_or("public")); - const auto table = wrap_in_quotes(_stmt.table.name); - const auto colnames = internal::strings::join( - ", ", - internal::collect::vector(_stmt.columns | transform(wrap_in_quotes))); - return "COPY " + schema + "." + table + "(" + colnames + - ") FROM STDIN WITH DELIMITER '\t' NULL '\e' QUOTE '\a';"; -} - rfl::Result> Connection::make( const Credentials& _credentials) noexcept { try { @@ -145,44 +45,13 @@ typename Connection::ConnPtr Connection::make_conn( return ConnPtr::make(std::shared_ptr(raw_ptr, &PQfinish)).value(); } -std::string Connection::select_from_to_sql( - const dynamic::SelectFrom& _stmt) const noexcept { - using namespace std::ranges::views; - - const auto order_by_to_str = [](const auto& _w) -> std::string { - return "\"" + _w.column.name + "\"" + (_w.desc ? " DESC" : ""); - }; - - std::stringstream stream; - - stream << "SELECT "; - stream << internal::strings::join( - ", ", internal::collect::vector(_stmt.columns | transform(get_name) | - transform(wrap_in_quotes))); - stream << " FROM "; - if (_stmt.table.schema) { - stream << wrap_in_quotes(*_stmt.table.schema) << "."; +Result> Connection::read(const dynamic::SelectFrom& _query) { + const auto sql = postgres::to_sql(_query); + try { + return Ref(Ref::make(sql, conn_)); + } catch (std::exception& e) { + return error(e.what()); } - stream << wrap_in_quotes(_stmt.table.name); - - if (_stmt.where) { - stream << " WHERE " << condition_to_sql(*_stmt.where); - } - - if (_stmt.order_by) { - stream << " ORDER BY " - << internal::strings::join( - ", ", internal::collect::vector(_stmt.order_by->columns | - transform(order_by_to_str))); - } - - if (_stmt.limit) { - stream << " LIMIT " << _stmt.limit->val; - } - - stream << ";"; - - return stream.str(); } std::string Connection::to_buffer( @@ -205,57 +74,6 @@ std::string Connection::to_buffer( "\n"; } -std::string Connection::to_sql(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) { - return create_table_to_sql(_s); - } else if constexpr (std::is_same_v) { - return insert_to_sql(_s); - } else if constexpr (std::is_same_v) { - return select_from_to_sql(_s); - } else { - static_assert(rfl::always_false_v, "Unsupported type."); - } - }); -} - -std::string Connection::type_to_sql(const dynamic::Type& _type) const noexcept { - return _type.visit([](const auto _t) -> std::string { - using T = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return "BOOLEAN"; - } else if constexpr (std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v) { - return "SMALLINT"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - return "INTEGER"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - return "BIGINT"; - } else if constexpr (std::is_same_v) { - return "REAL"; - } else if constexpr (std::is_same_v) { - return "DOUBLE PRECISION"; - } else if constexpr (std::is_same_v) { - return "TEXT"; - } else if constexpr (std::is_same_v) { - return "VARCHAR(" + std::to_string(_t.length) + ")"; - } else if constexpr (std::is_same_v) { - return "TIMESTAMP"; - } else if constexpr (std::is_same_v) { - return "TIMESTAMP WITH TIME ZONE"; - } else if constexpr (std::is_same_v) { - return "TEXT"; - } else { - static_assert(rfl::always_false_v, "Not all cases were covered."); - } - }); -} - Result Connection::write( const std::vector>>& _data) { for (const auto& line : _data) { diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp new file mode 100644 index 0000000..08a770c --- /dev/null +++ b/src/sqlgen/postgres/to_sql.cpp @@ -0,0 +1,277 @@ +#include "sqlgen/postgres/to_sql.hpp" + +#include +#include +#include +#include + +#include "sqlgen/internal/collect/vector.hpp" +#include "sqlgen/internal/strings/strings.hpp" + +namespace sqlgen::postgres { + +std::string add_not_null_if_necessary( + const dynamic::types::Properties& _p) noexcept; + +std::string column_or_value_to_sql(const dynamic::ColumnOrValue& _col) noexcept; + +std::string condition_to_sql(const dynamic::Condition& _cond) noexcept; + +template +std::string condition_to_sql_impl(const ConditionType& _condition) noexcept; + +std::string column_to_sql_definition(const dynamic::Column& _col) noexcept; + +std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept; + +std::vector get_primary_keys( + const dynamic::CreateTable& _stmt) noexcept; + +std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept; + +std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept; + +std::string type_to_sql(const dynamic::Type& _type) noexcept; + +// ---------------------------------------------------------------------------- + +inline std::string get_name(const dynamic::Column& _col) { return _col.name; } + +inline std::string wrap_in_quotes(const std::string& _name) noexcept { + return "\"" + _name + "\""; +} + +// ---------------------------------------------------------------------------- + +std::string add_not_null_if_necessary( + const dynamic::types::Properties& _p) noexcept { + return std::string(_p.nullable ? "" : " NOT NULL"); +} + +std::string column_or_value_to_sql( + const dynamic::ColumnOrValue& _col) noexcept { + const auto handle_value = [](const auto& _v) -> std::string { + using Type = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return "'" + _v.val + "'"; + } else { + return std::to_string(_v.val); + } + }; + + return _col.visit([&](const auto& _c) -> std::string { + using Type = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return wrap_in_quotes(_c.name); + } else { + return _c.visit(handle_value); + } + }); +} + +std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { + return _cond.val.visit( + [&](const auto& _c) { return condition_to_sql_impl(_c); }); +} + +template +std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { + using C = std::remove_cvref_t; + std::stringstream stream; + + if constexpr (std::is_same_v) { + stream << "(" << condition_to_sql(*_condition.cond1) << ") AND (" + << condition_to_sql(*_condition.cond2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) << " = " + << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) + << " >= " << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) << " > " + << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) + << " != " << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) + << " <= " << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_condition.op1) << " < " + << column_or_value_to_sql(_condition.op2); + + } else if constexpr (std::is_same_v) { + stream << "(" << condition_to_sql(*_condition.cond1) << ") OR (" + << condition_to_sql(*_condition.cond2) << ")"; + + } else { + static_assert(rfl::always_false_v, "Not all cases where covered."); + } + + return stream.str(); +} + +std::string column_to_sql_definition(const dynamic::Column& _col) noexcept { + return wrap_in_quotes(_col.name) + " " + type_to_sql(_col.type) + + add_not_null_if_necessary( + _col.type.visit([](const auto& _t) { return _t.properties; })); +} + +std::string 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.if_not_exists) { + stream << "IF NOT EXISTS "; + } + + if (_stmt.table.schema) { + stream << wrap_in_quotes(*_stmt.table.schema) << "."; + } + stream << wrap_in_quotes(_stmt.table.name) << " "; + + stream << "("; + stream << internal::strings::join( + ", ", internal::collect::vector(_stmt.columns | transform(col_to_sql))); + + const auto primary_keys = get_primary_keys(_stmt); + + if (primary_keys.size() != 0) { + stream << ", PRIMARY KEY (" << internal::strings::join(", ", primary_keys) + << ")"; + } + + stream << ");"; + + return stream.str(); +} + +std::vector get_primary_keys( + const dynamic::CreateTable& _stmt) noexcept { + using namespace std::ranges::views; + + const auto is_primary_key = [](const auto& _col) -> bool { + return _col.type.visit( + [](const auto& _t) -> bool { return _t.properties.primary; }); + }; + + return internal::collect::vector(_stmt.columns | filter(is_primary_key) | + transform(get_name) | + transform(wrap_in_quotes)); +} + +std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept { + using namespace std::ranges::views; + const auto schema = wrap_in_quotes(_stmt.table.schema.value_or("public")); + const auto table = wrap_in_quotes(_stmt.table.name); + const auto colnames = internal::strings::join( + ", ", + internal::collect::vector(_stmt.columns | transform(wrap_in_quotes))); + return "COPY " + schema + "." + table + "(" + colnames + + ") FROM STDIN WITH DELIMITER '\t' NULL '\e' QUOTE '\a';"; +} + +std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept { + using namespace std::ranges::views; + + const auto order_by_to_str = [](const auto& _w) -> std::string { + return "\"" + _w.column.name + "\"" + (_w.desc ? " DESC" : ""); + }; + + std::stringstream stream; + + stream << "SELECT "; + stream << internal::strings::join( + ", ", internal::collect::vector(_stmt.columns | transform(get_name) | + transform(wrap_in_quotes))); + stream << " FROM "; + if (_stmt.table.schema) { + stream << wrap_in_quotes(*_stmt.table.schema) << "."; + } + stream << wrap_in_quotes(_stmt.table.name); + + if (_stmt.where) { + stream << " WHERE " << condition_to_sql(*_stmt.where); + } + + if (_stmt.order_by) { + stream << " ORDER BY " + << internal::strings::join( + ", ", internal::collect::vector(_stmt.order_by->columns | + transform(order_by_to_str))); + } + + if (_stmt.limit) { + stream << " LIMIT " << _stmt.limit->val; + } + + stream << ";"; + + return stream.str(); +} + +std::string to_sql(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) { + return create_table_to_sql(_s); + } else if constexpr (std::is_same_v) { + return insert_to_sql(_s); + } else if constexpr (std::is_same_v) { + return select_from_to_sql(_s); + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + }); +} + +std::string type_to_sql(const dynamic::Type& _type) noexcept { + return _type.visit([](const auto _t) -> std::string { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return "BOOLEAN"; + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + return "SMALLINT"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + return "INTEGER"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + return "BIGINT"; + } else if constexpr (std::is_same_v) { + return "REAL"; + } else if constexpr (std::is_same_v) { + return "DOUBLE PRECISION"; + } else if constexpr (std::is_same_v) { + return "TEXT"; + } else if constexpr (std::is_same_v) { + return "VARCHAR(" + std::to_string(_t.length) + ")"; + } else if constexpr (std::is_same_v) { + return "TIMESTAMP"; + } else if constexpr (std::is_same_v) { + return "TIMESTAMP WITH TIME ZONE"; + } else if constexpr (std::is_same_v) { + return "TEXT"; + } else { + static_assert(rfl::always_false_v, "Not all cases were covered."); + } + }); +} + +} // namespace sqlgen::postgres diff --git a/src/sqlgen_postgres.cpp b/src/sqlgen_postgres.cpp index 3bb9cab..a3c0de3 100644 --- a/src/sqlgen_postgres.cpp +++ b/src/sqlgen_postgres.cpp @@ -1,3 +1,4 @@ #include "sqlgen/postgres/Connection.cpp" #include "sqlgen/postgres/Iterator.cpp" #include "sqlgen/postgres/exec.cpp" +#include "sqlgen/postgres/to_sql.cpp"