diff --git a/README.md b/README.md index d082263..08c6350 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,17 @@ and print the results as a JSON: const auto conn = sqlgen::postgres::connect(credentials); -const sqlgen::Result> result = - sqlgen::read>(conn); +using namespace sqlgen; + +// Query that returns the 100 youngest children. +const auto get_children = sqlgen::read> | + where("age"_c < 18) | + order_by("age"_c) | + limit(100); + +// Actually executes the query. +// Returns sqlgen::Result> +const auto result = get_children(conn); if (result) { std::cout << rfl::json::write(*result) << std::endl; diff --git a/include/sqlgen/col.hpp b/include/sqlgen/col.hpp index cc8c438..9fc996c 100644 --- a/include/sqlgen/col.hpp +++ b/include/sqlgen/col.hpp @@ -25,6 +25,11 @@ struct Col { template const auto col = Col<_name>{}; +template +auto operator"" _c() { + return Col<_name>{}; +} + template auto operator==(const Col<_name1>&, const Col<_name2>&) { diff --git a/include/sqlgen/order_by.hpp b/include/sqlgen/order_by.hpp index 6f24792..dc2c2cb 100644 --- a/include/sqlgen/order_by.hpp +++ b/include/sqlgen/order_by.hpp @@ -21,7 +21,7 @@ auto operator|(const Read& _r, "You cannot call order_by twice (but you can order by more " "than one column)."); static_assert(std::is_same_v, - "Limit must be called after order_by."); + "You cannot call limit(...) must be called before order_by."); static_assert(sizeof...(ColTypes) != 0, "You must assign at least one column to order by."); return Read + std::string condition_to_sql_impl( + const ConditionType& _condition) const noexcept; + std::string create_table_to_sql( const dynamic::CreateTable& _stmt) const noexcept; @@ -89,6 +99,51 @@ class Connection : public sqlgen::Connection { 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/transpilation/Value.hpp b/include/sqlgen/transpilation/Value.hpp index b12cc7c..ad8315d 100644 --- a/include/sqlgen/transpilation/Value.hpp +++ b/include/sqlgen/transpilation/Value.hpp @@ -1,6 +1,7 @@ #ifndef SQLGEN_TRANSPILATION_VALUE_HPP_ #define SQLGEN_TRANSPILATION_VALUE_HPP_ +#include #include namespace sqlgen::transpilation { @@ -15,6 +16,9 @@ auto make_value(T&& _t) { return Value>{.val = _t}; } +inline auto make_value(const char* _str) { + return Value{.val = _str}; +} } // namespace sqlgen::transpilation #endif diff --git a/include/sqlgen/transpilation/to_condition.hpp b/include/sqlgen/transpilation/to_condition.hpp index aed6b72..d362176 100644 --- a/include/sqlgen/transpilation/to_condition.hpp +++ b/include/sqlgen/transpilation/to_condition.hpp @@ -10,6 +10,7 @@ #include "../dynamic/Condition.hpp" #include "Condition.hpp" #include "conditions.hpp" +#include "to_value.hpp" namespace sqlgen::transpilation { @@ -25,8 +26,7 @@ struct ToCondition> { template struct ToCondition> { - dynamic::Condition operator()( - const conditions::And& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{ .val = dynamic::Condition::And{ .cond1 = Ref::make( @@ -40,8 +40,7 @@ struct ToCondition> { template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::Equal, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::Equal{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -49,11 +48,20 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::Equal{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::GreaterEqual, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::GreaterEqual{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -61,11 +69,20 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::GreaterEqual{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::GreaterThan, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::GreaterThan{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -73,11 +90,20 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::GreaterThan{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::LesserEqual, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::LesserEqual{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -85,11 +111,20 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::LesserEqual{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::LesserThan, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::LesserThan{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -97,11 +132,20 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::LesserThan{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition, Col<_name2>>> { - dynamic::Condition operator()( - const conditions::NotEqual, Col<_name2>>& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::NotEqual{ .op1 = dynamic::Column{.name = _name1.str()}, .op2 = dynamic::Column{.name = _name2.str()}, @@ -109,10 +153,19 @@ struct ToCondition, Col<_name2>>> { } }; +template +struct ToCondition, Value>> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::NotEqual{ + .op1 = dynamic::Column{.name = _name.str()}, + .op2 = to_value(_cond.op2.val), + }}; + } +}; + template struct ToCondition> { - dynamic::Condition operator()( - const conditions::Or& _cond) const { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{ .val = dynamic::Condition::Or{ .cond1 = Ref::make( diff --git a/include/sqlgen/transpilation/to_value.hpp b/include/sqlgen/transpilation/to_value.hpp new file mode 100644 index 0000000..4f0f36e --- /dev/null +++ b/include/sqlgen/transpilation/to_value.hpp @@ -0,0 +1,35 @@ +#ifndef SQLGEN_TRANSPILATION_TO_VALUE_HPP_ +#define SQLGEN_TRANSPILATION_TO_VALUE_HPP_ + +#include +#include + +#include "../dynamic/Value.hpp" +#include "Value.hpp" +#include "has_reflection_method.hpp" + +namespace sqlgen::transpilation { + +template +dynamic::Value to_value(const T& _t) { + using Type = std::remove_cvref_t; + if constexpr (std::is_floating_point_v) { + return dynamic::Float{.val = static_cast(_t)}; + + } else if constexpr (std::is_integral_v) { + return dynamic::Integer{.val = static_cast(_t)}; + + } else if constexpr (std::is_convertible_v) { + return dynamic::String{.val = std::string(_t)}; + + } else if constexpr (has_reflection_method) { + return to_value(_t.reflection()); + + } else { + static_assert(rfl::always_false_v, "Unsupported type"); + } +} + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/where.hpp b/include/sqlgen/where.hpp index ae5bca8..703e436 100644 --- a/include/sqlgen/where.hpp +++ b/include/sqlgen/where.hpp @@ -23,9 +23,9 @@ auto operator|(const Read& _r, "You cannot call where(...) twice (but you can apply more " "than one condition by combining them with && or ||)."); static_assert(std::is_same_v, - "You must call order_by(...) after where(...)."); + "You cannot call order_by(...) before where(...)."); static_assert(std::is_same_v, - "You must call limit(...) after where(...)."); + "You cannot call limit(...) before where(...)."); return Read{ .where_ = _where.condition}; } diff --git a/src/sqlgen/sqlite/Connection.cpp b/src/sqlgen/sqlite/Connection.cpp index 0f1661e..13da643 100644 --- a/src/sqlgen/sqlite/Connection.cpp +++ b/src/sqlgen/sqlite/Connection.cpp @@ -143,8 +143,6 @@ std::string Connection::properties_to_sql( Result> Connection::read(const dynamic::SelectFrom& _query) { const auto sql = to_sql(_query); - std::cout << sql << std::endl; - sqlite3_stmt* p_stmt = nullptr; sqlite3_prepare(conn_.get(), /* Database handle */ diff --git a/tests/sqlite/test_limit.cpp b/tests/sqlite/test_limit.cpp index 940429c..85b22f5 100644 --- a/tests/sqlite/test_limit.cpp +++ b/tests/sqlite/test_limit.cpp @@ -33,7 +33,7 @@ TEST(sqlite, test_limit) { using namespace sqlgen; const auto query = - sqlgen::read> | order_by(col<"age">) | limit(2); + sqlgen::read> | order_by("age"_c) | limit(2); const auto people2 = query(conn).value(); diff --git a/tests/sqlite/test_order_by.cpp b/tests/sqlite/test_order_by.cpp index f0e2a80..1b605bf 100644 --- a/tests/sqlite/test_order_by.cpp +++ b/tests/sqlite/test_order_by.cpp @@ -33,7 +33,7 @@ TEST(sqlite, test_order_by) { using namespace sqlgen; const auto query = sqlgen::read> | - order_by(col<"age">, col<"first_name">.desc()); + order_by("age"_c, "first_name"_c.desc()); const auto people2 = query(conn).value(); diff --git a/tests/sqlite/test_where.cpp b/tests/sqlite/test_where.cpp index dba2d02..57dbc1a 100644 --- a/tests/sqlite/test_where.cpp +++ b/tests/sqlite/test_where.cpp @@ -32,15 +32,14 @@ TEST(sqlite, test_where) { using namespace sqlgen; - const auto query = - sqlgen::read> | - where(col<"first_name"> != col<"last_name"> or col<"id"> != col<"age">) | - order_by(col<"age">, col<"first_name">.desc()); + const auto query = sqlgen::read> | + where("age"_c < 18 and "first_name"_c != "Hugo") | + order_by("age"_c); const auto people2 = query(conn).value(); const std::string expected = - R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":4,"first_name":"Hugo","last_name":"Simpson","age":10},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":0,"first_name":"Homer","last_name":"Simpson","age":45}])"; + R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])"; EXPECT_EQ(rfl::json::write(people2), expected); }