From 91e993ffdd11a7be37bcddbc40da3af7603c0504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Thu, 23 Oct 2025 23:30:58 +0200 Subject: [PATCH] Make sure we can use boolean values in conditions; fixes #74 (#75) --- include/sqlgen/dynamic/Condition.hpp | 11 ++-- include/sqlgen/dynamic/Value.hpp | 8 ++- include/sqlgen/transpilation/to_value.hpp | 3 + src/sqlgen/mysql/to_sql.cpp | 4 ++ src/sqlgen/postgres/to_sql.cpp | 7 +++ src/sqlgen/sqlite/to_sql.cpp | 4 ++ tests/mysql/test_boolean_conditions.cpp | 63 ++++++++++++++++++++ tests/mysql/test_boolean_update.cpp | 67 ++++++++++++++++++++++ tests/postgres/test_boolean_conditions.cpp | 63 ++++++++++++++++++++ tests/postgres/test_boolean_update.cpp | 67 ++++++++++++++++++++++ tests/sqlite/test_boolean_conditions.cpp | 54 +++++++++++++++++ tests/sqlite/test_boolean_update.cpp | 59 +++++++++++++++++++ 12 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 tests/mysql/test_boolean_conditions.cpp create mode 100644 tests/mysql/test_boolean_update.cpp create mode 100644 tests/postgres/test_boolean_conditions.cpp create mode 100644 tests/postgres/test_boolean_update.cpp create mode 100644 tests/sqlite/test_boolean_conditions.cpp create mode 100644 tests/sqlite/test_boolean_update.cpp diff --git a/include/sqlgen/dynamic/Condition.hpp b/include/sqlgen/dynamic/Condition.hpp index 999d795..999cdf9 100644 --- a/include/sqlgen/dynamic/Condition.hpp +++ b/include/sqlgen/dynamic/Condition.hpp @@ -4,7 +4,6 @@ #include #include "../Ref.hpp" -#include "Column.hpp" #include "ColumnOrValue.hpp" #include "Operation.hpp" @@ -16,6 +15,10 @@ struct Condition { Ref cond2; }; + struct BooleanColumnOrValue { + ColumnOrValue col_or_val; + }; + struct Equal { Operation op1; Operation op2; @@ -84,9 +87,9 @@ struct Condition { }; using ReflectionType = - rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, In, - IsNull, IsNotNull, LesserEqual, LesserThan, Like, Not, - NotEqual, NotIn, NotLike, Or>; + rfl::TaggedUnion<"what", And, BooleanColumnOrValue, Equal, GreaterEqual, + GreaterThan, In, IsNull, IsNotNull, LesserEqual, + LesserThan, Like, Not, NotEqual, NotIn, NotLike, Or>; const ReflectionType& reflection() const { return val; } diff --git a/include/sqlgen/dynamic/Value.hpp b/include/sqlgen/dynamic/Value.hpp index f5cc9c0..586159b 100644 --- a/include/sqlgen/dynamic/Value.hpp +++ b/include/sqlgen/dynamic/Value.hpp @@ -13,6 +13,10 @@ struct Duration { int64_t val; }; +struct Boolean { + bool val; +}; + struct Float { double val; }; @@ -30,8 +34,8 @@ struct Timestamp { }; struct Value { - using ReflectionType = - rfl::TaggedUnion<"type", Duration, Float, Integer, String, Timestamp>; + using ReflectionType = rfl::TaggedUnion<"type", Duration, Boolean, Float, + Integer, String, Timestamp>; const auto& reflection() const { return val; } ReflectionType val; }; diff --git a/include/sqlgen/transpilation/to_value.hpp b/include/sqlgen/transpilation/to_value.hpp index 55758a9..5ca5c55 100644 --- a/include/sqlgen/transpilation/to_value.hpp +++ b/include/sqlgen/transpilation/to_value.hpp @@ -20,6 +20,9 @@ struct ToValue { if constexpr (std::is_floating_point_v) { return dynamic::Value{dynamic::Float{.val = static_cast(_t)}}; + } else if constexpr (std::is_same_v) { + return dynamic::Value{dynamic::Boolean{.val = _t}}; + } else if constexpr (std::is_integral_v) { return dynamic::Value{dynamic::Integer{.val = static_cast(_t)}}; diff --git a/src/sqlgen/mysql/to_sql.cpp b/src/sqlgen/mysql/to_sql.cpp index f10c941..8799cc4 100644 --- a/src/sqlgen/mysql/to_sql.cpp +++ b/src/sqlgen/mysql/to_sql.cpp @@ -219,6 +219,10 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << "(" << condition_to_sql(*_condition.cond1) << ") AND (" << condition_to_sql(*_condition.cond2) << ")"; + } else if constexpr (std::is_same_v< + C, dynamic::Condition::BooleanColumnOrValue>) { + stream << column_or_value_to_sql(_condition.col_or_val); + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op1) << " = " << operation_to_sql(_condition.op2); diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 0eda2aa..ab491ac 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -135,6 +135,9 @@ std::string column_or_value_to_sql( } else if constexpr (std::is_same_v) { return "to_timestamp(" + std::to_string(_v.seconds_since_unix) + ")"; + } else if constexpr (std::is_same_v) { + return _v.val ? "TRUE" : "FALSE"; + } else { return std::to_string(_v.val); } @@ -171,6 +174,10 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << "(" << condition_to_sql(*_condition.cond1) << ") AND (" << condition_to_sql(*_condition.cond2) << ")"; + } else if constexpr (std::is_same_v< + C, dynamic::Condition::BooleanColumnOrValue>) { + stream << column_or_value_to_sql(_condition.col_or_val); + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op1) << " = " << operation_to_sql(_condition.op2); diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index 371e5ba..e4fd737 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -179,6 +179,10 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << "(" << condition_to_sql(*_condition.cond1) << ") AND (" << condition_to_sql(*_condition.cond2) << ")"; + } else if constexpr (std::is_same_v< + C, dynamic::Condition::BooleanColumnOrValue>) { + stream << column_or_value_to_sql(_condition.col_or_val); + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op1) << " = " << operation_to_sql(_condition.op2); diff --git a/tests/mysql/test_boolean_conditions.cpp b/tests/mysql/test_boolean_conditions.cpp new file mode 100644 index 0000000..8efc394 --- /dev/null +++ b/tests/mysql/test_boolean_conditions.cpp @@ -0,0 +1,63 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_conditions { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(mysql, test_boolean_conditions) { + const auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = + mysql::connect(credentials).and_then(drop | if_exists); + + const auto homer = + sqlgen::write(conn, people1) + .and_then(sqlgen::read | where("has_children"_c == true) | + order_by("id"_c)) + .value(); + + const auto json1 = rfl::json::write(people1.at(0)); + const auto json2 = rfl::json::write(homer); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_conditions + +#endif diff --git a/tests/mysql/test_boolean_update.cpp b/tests/mysql/test_boolean_update.cpp new file mode 100644 index 0000000..d3d313b --- /dev/null +++ b/tests/mysql/test_boolean_update.cpp @@ -0,0 +1,67 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_update { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(mysql, test_boolean_update) { + auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = + mysql::connect(credentials).and_then(drop | if_exists); + + const auto people2 = + sqlgen::write(conn, people1) + .and_then(update("has_children"_c.set(false)) | + where("has_children"_c == true)) + .and_then(sqlgen::read> | + where("has_children"_c == false) | order_by("id"_c)) + .value(); + + people1.at(0).has_children = false; + + const auto json1 = rfl::json::write(people1); + const auto json2 = rfl::json::write(people2); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_update + +#endif diff --git a/tests/postgres/test_boolean_conditions.cpp b/tests/postgres/test_boolean_conditions.cpp new file mode 100644 index 0000000..f0b4c6b --- /dev/null +++ b/tests/postgres/test_boolean_conditions.cpp @@ -0,0 +1,63 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_conditions { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(postgres, test_boolean_conditions) { + const auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = + postgres::connect(credentials).and_then(drop | if_exists); + + const auto homer = + sqlgen::write(conn, people1) + .and_then(sqlgen::read | where("has_children"_c == true) | + order_by("id"_c)) + .value(); + + const auto json1 = rfl::json::write(people1.at(0)); + const auto json2 = rfl::json::write(homer); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_conditions + +#endif diff --git a/tests/postgres/test_boolean_update.cpp b/tests/postgres/test_boolean_update.cpp new file mode 100644 index 0000000..69bdfb8 --- /dev/null +++ b/tests/postgres/test_boolean_update.cpp @@ -0,0 +1,67 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_update { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(postgres, test_boolean_update) { + auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = + postgres::connect(credentials).and_then(drop | if_exists); + + const auto people2 = + sqlgen::write(conn, people1) + .and_then(update("has_children"_c.set(false)) | + where("has_children"_c == true)) + .and_then(sqlgen::read> | + where("has_children"_c == false) | order_by("id"_c)) + .value(); + + people1.at(0).has_children = false; + + const auto json1 = rfl::json::write(people1); + const auto json2 = rfl::json::write(people2); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_update + +#endif diff --git a/tests/sqlite/test_boolean_conditions.cpp b/tests/sqlite/test_boolean_conditions.cpp new file mode 100644 index 0000000..5502403 --- /dev/null +++ b/tests/sqlite/test_boolean_conditions.cpp @@ -0,0 +1,54 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_conditions { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(sqlite, test_boolean_conditions) { + const auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto homer = + sqlite::connect() + .and_then(sqlgen::write(people1)) + .and_then(sqlgen::read | where("has_children"_c == true) | + order_by("id"_c)) + .value(); + + const auto json1 = rfl::json::write(people1.at(0)); + const auto json2 = rfl::json::write(homer); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_conditions + diff --git a/tests/sqlite/test_boolean_update.cpp b/tests/sqlite/test_boolean_update.cpp new file mode 100644 index 0000000..bd5ad97 --- /dev/null +++ b/tests/sqlite/test_boolean_update.cpp @@ -0,0 +1,59 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_boolean_update { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + bool has_children; +}; + +TEST(sqlite, test_boolean_update) { + auto people1 = std::vector({Person{.id = 0, + .first_name = "Homer", + .last_name = "Simpson", + .has_children = true}, + Person{.id = 1, + .first_name = "Bart", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 2, + .first_name = "Lisa", + .last_name = "Simpson", + .has_children = false}, + Person{.id = 3, + .first_name = "Maggie", + .last_name = "Simpson", + .has_children = false}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = sqlite::connect(); + + const auto people2 = + sqlgen::write(conn, people1) + .and_then(update("has_children"_c.set(false)) | + where("has_children"_c == true)) + .and_then(sqlgen::read> | + where("has_children"_c == false) | order_by("id"_c)) + .value(); + + people1.at(0).has_children = false; + + const auto json1 = rfl::json::write(people1); + const auto json2 = rfl::json::write(people2); + + EXPECT_EQ(json1, json2); +} + +} // namespace test_boolean_update +