From 45c1a52177394f83ddd2d72499da6d60014c4970 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: Sun, 28 Sep 2025 22:28:48 +0300 Subject: [PATCH] Added support for IN and NOT IN; resolves #53 (#54) --- docs/col.md | 51 +++++++++++ include/sqlgen/col.hpp | 14 +++ include/sqlgen/dynamic/Condition.hpp | 16 +++- include/sqlgen/transpilation/conditions.hpp | 58 +++++++++++++ include/sqlgen/transpilation/string_t.hpp | 28 ++++++ include/sqlgen/transpilation/to_condition.hpp | 85 +++++++++++++++++++ src/sqlgen/mysql/to_sql.cpp | 18 ++++ src/sqlgen/postgres/to_sql.cpp | 18 ++++ src/sqlgen/sqlite/to_sql.cpp | 18 ++++ tests/mysql/test_in.cpp | 56 ++++++++++++ tests/mysql/test_in_vec.cpp | 57 +++++++++++++ tests/mysql/test_not_in.cpp | 56 ++++++++++++ tests/mysql/test_not_in_vec.cpp | 57 +++++++++++++ tests/postgres/test_in.cpp | 56 ++++++++++++ tests/postgres/test_in_vec.cpp | 57 +++++++++++++ tests/postgres/test_not_in.cpp | 56 ++++++++++++ tests/postgres/test_not_in_vec.cpp | 57 +++++++++++++ tests/sqlite/test_in.cpp | 48 +++++++++++ tests/sqlite/test_in_vec.cpp | 49 +++++++++++ tests/sqlite/test_not_in.cpp | 48 +++++++++++ tests/sqlite/test_not_in_vec.cpp | 49 +++++++++++ 21 files changed, 949 insertions(+), 3 deletions(-) create mode 100644 include/sqlgen/transpilation/string_t.hpp create mode 100644 tests/mysql/test_in.cpp create mode 100644 tests/mysql/test_in_vec.cpp create mode 100644 tests/mysql/test_not_in.cpp create mode 100644 tests/mysql/test_not_in_vec.cpp create mode 100644 tests/postgres/test_in.cpp create mode 100644 tests/postgres/test_in_vec.cpp create mode 100644 tests/postgres/test_not_in.cpp create mode 100644 tests/postgres/test_not_in_vec.cpp create mode 100644 tests/sqlite/test_in.cpp create mode 100644 tests/sqlite/test_in_vec.cpp create mode 100644 tests/sqlite/test_not_in.cpp create mode 100644 tests/sqlite/test_not_in_vec.cpp diff --git a/docs/col.md b/docs/col.md index b21cacb..7c40b87 100644 --- a/docs/col.md +++ b/docs/col.md @@ -113,6 +113,57 @@ FROM "Person" WHERE "first_name" NOT LIKE 'H%'; ``` +#### IN and NOT IN Operations + +Use IN and NOT IN to check if a column value matches any value in a list: + +```cpp +using namespace sqlgen; +using namespace sqlgen::literals; + +// Find people with specific first names (variadic arguments) +const auto query1 = read> | + where("first_name"_c.in("Bart", "Lisa", "Maggie")); + +// Find people NOT with specific first names (variadic arguments) +const auto query2 = read> | + where("first_name"_c.not_in("Homer", "Hugo")); + +// Find people with specific first names (using vector) +const auto names = std::vector({"Bart", "Lisa", "Maggie"}); +const auto query3 = read> | + where("first_name"_c.in(names)); + +// Find people NOT with specific first names (using vector) +const auto excluded_names = std::vector({"Homer", "Hugo"}); +const auto query4 = read> | + where("first_name"_c.not_in(excluded_names)); +``` + +This generates SQL like: + +```sql +-- For query1 +SELECT "id", "first_name", "last_name", "age" +FROM "Person" +WHERE "first_name" IN ('Bart', 'Lisa', 'Maggie'); + +-- For query2 +SELECT "id", "first_name", "last_name", "age" +FROM "Person" +WHERE "first_name" NOT IN ('Homer', 'Hugo'); + +-- For query3 +SELECT "id", "first_name", "last_name", "age" +FROM "Person" +WHERE "first_name" IN ('Bart', 'Lisa', 'Maggie'); + +-- For query4 +SELECT "id", "first_name", "last_name", "age" +FROM "Person" +WHERE "first_name" NOT IN ('Homer', 'Hugo'); +``` + #### Ordering Specify column ordering in queries: diff --git a/include/sqlgen/col.hpp b/include/sqlgen/col.hpp index b344389..97b9075 100644 --- a/include/sqlgen/col.hpp +++ b/include/sqlgen/col.hpp @@ -40,6 +40,13 @@ struct Col { /// Returns the column name. std::string name() const noexcept { return Name().str(); } + /// Returns an IN condition. + template + auto in(const Ts&... _ts) const noexcept { + return transpilation::make_condition(transpilation::conditions::in( + transpilation::Col<_name, _alias>{}, _ts...)); + } + /// Returns an IS NULL condition. auto is_null() const noexcept { return transpilation::make_condition(transpilation::conditions::is_null( @@ -64,6 +71,13 @@ struct Col { transpilation::Col<_name, _alias>{}, _pattern)); } + /// Returns a NOT IN condition. + template + auto not_in(const Ts&... _ts) const noexcept { + return transpilation::make_condition(transpilation::conditions::not_in( + transpilation::Col<_name, _alias>{}, _ts...)); + } + /// Returns a SET clause in an UPDATE statement. template auto set(const T& _to) const noexcept { diff --git a/include/sqlgen/dynamic/Condition.hpp b/include/sqlgen/dynamic/Condition.hpp index 75ac4d0..999d795 100644 --- a/include/sqlgen/dynamic/Condition.hpp +++ b/include/sqlgen/dynamic/Condition.hpp @@ -31,6 +31,11 @@ struct Condition { Operation op2; }; + struct In { + Operation op; + std::vector patterns; + }; + struct IsNotNull { Operation op; }; @@ -68,15 +73,20 @@ struct Condition { dynamic::Value pattern; }; + struct NotIn { + Operation op; + std::vector patterns; + }; + struct Or { Ref cond1; Ref cond2; }; using ReflectionType = - rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, IsNull, - IsNotNull, LesserEqual, LesserThan, Like, Not, NotEqual, - NotLike, Or>; + rfl::TaggedUnion<"what", And, 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/transpilation/conditions.hpp b/include/sqlgen/transpilation/conditions.hpp index f9eb438..6fdb95a 100644 --- a/include/sqlgen/transpilation/conditions.hpp +++ b/include/sqlgen/transpilation/conditions.hpp @@ -1,6 +1,10 @@ #ifndef SQLGEN_TRANSPILATION_CONDITIONS_HPP_ #define SQLGEN_TRANSPILATION_CONDITIONS_HPP_ +#include + +#include "string_t.hpp" + namespace sqlgen::transpilation::conditions { template @@ -53,6 +57,33 @@ auto greater_than(const OpType1& _op1, const OpType2& _op2) { std::remove_cvref_t>{.op1 = _op1, .op2 = _op2}; } +template +struct In { + using ResultType = bool; + + OpType op; + rfl::Tuple patterns; +}; + +template +auto in(const OpType& _op, const Ts&... _ts) { + return In...>{ + .op = _op, .patterns = rfl::Tuple...>(_ts...)}; +} + +template +struct InVec { + using ResultType = bool; + + OpType op; + std::vector patterns; +}; + +template +auto in(const OpType& _op, const std::vector& _patterns) { + return InVec{.op = _op, .patterns = _patterns}; +} + template struct IsNull { using ResultType = bool; @@ -139,6 +170,33 @@ auto not_equal(const OpType1& _op1, const OpType2& _op2) { .op1 = _op1, .op2 = _op2}; } +template +struct NotIn { + using ResultType = bool; + + OpType op; + rfl::Tuple patterns; +}; + +template +auto not_in(const OpType& _op, const Ts&... _ts) { + return NotIn...>{ + .op = _op, .patterns = rfl::Tuple...>(_ts...)}; +} + +template +struct NotInVec { + using ResultType = bool; + + OpType op; + std::vector patterns; +}; + +template +auto not_in(const OpType& _op, const std::vector& _patterns) { + return NotInVec{.op = _op, .patterns = _patterns}; +} + template struct NotLike { using ResultType = bool; diff --git a/include/sqlgen/transpilation/string_t.hpp b/include/sqlgen/transpilation/string_t.hpp new file mode 100644 index 0000000..c137d8a --- /dev/null +++ b/include/sqlgen/transpilation/string_t.hpp @@ -0,0 +1,28 @@ +#ifndef SQLGEN_TRANSPILATION_STRINGT_HPP_ +#define SQLGEN_TRANSPILATION_STRINGT_HPP_ + +#include +#include + +namespace sqlgen::transpilation { + +template +struct StringType; + +template +struct StringType { + using Type = T; +}; + +template + requires std::is_convertible_v +struct StringType { + using Type = std::string; +}; + +template +using string_t = typename StringType>::Type; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/to_condition.hpp b/include/sqlgen/transpilation/to_condition.hpp index 3b6ed7a..e5fb089 100644 --- a/include/sqlgen/transpilation/to_condition.hpp +++ b/include/sqlgen/transpilation/to_condition.hpp @@ -3,17 +3,20 @@ #include #include +#include #include #include #include "../Ref.hpp" #include "../Result.hpp" #include "../dynamic/Condition.hpp" +#include "../internal/collect/vector.hpp" #include "Condition.hpp" #include "all_columns_exist.hpp" #include "conditions.hpp" #include "is_timestamp.hpp" #include "make_field.hpp" +#include "remove_nullable_t.hpp" #include "to_transpilation_type.hpp" #include "underlying_t.hpp" @@ -141,6 +144,47 @@ struct ToCondition> { } }; +template +struct ToCondition> { + using UnderlyingT = remove_nullable_t>; + + static constexpr bool is_equality_comparable = + (true && ... && std::equality_comparable_with); + + static_assert(is_equality_comparable, "Must be equality comparable."); + + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{ + .val = dynamic::Condition::In{.op = make_field(_cond.op).val, + .patterns = rfl::apply( + [](const auto&... _p) { + return std::vector( + {to_value(_p)...}); + }, + _cond.patterns)}}; + } +}; + +template +struct ToCondition> { + using UnderlyingT = remove_nullable_t>; + + static constexpr bool is_equality_comparable = + std::equality_comparable_with; + + static_assert(is_equality_comparable, "Must be equality comparable."); + + dynamic::Condition operator()(const auto& _cond) const { + using namespace std::ranges::views; + return dynamic::Condition{ + .val = dynamic::Condition::In{ + .op = make_field(_cond.op).val, + .patterns = sqlgen::internal::collect::vector( + _cond.patterns | + transform([](const auto& _v) { return to_value(_v); }))}}; + } +}; + template struct ToCondition> { dynamic::Condition operator()(const auto& _cond) const { @@ -198,6 +242,47 @@ struct ToCondition> { } }; +template +struct ToCondition> { + using UnderlyingT = remove_nullable_t>; + + static constexpr bool is_equality_comparable = + (true && ... && std::equality_comparable_with); + + static_assert(is_equality_comparable, "Must be equality comparable."); + + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{.val = dynamic::Condition::NotIn{ + .op = make_field(_cond.op).val, + .patterns = rfl::apply( + [](const auto&... _p) { + return std::vector( + {to_value(_p)...}); + }, + _cond.patterns)}}; + } +}; + +template +struct ToCondition> { + using UnderlyingT = remove_nullable_t>; + + static constexpr bool is_equality_comparable = + std::equality_comparable_with; + + static_assert(is_equality_comparable, "Must be equality comparable."); + + dynamic::Condition operator()(const auto& _cond) const { + using namespace std::ranges::views; + return dynamic::Condition{ + .val = dynamic::Condition::NotIn{ + .op = make_field(_cond.op).val, + .patterns = sqlgen::internal::collect::vector( + _cond.patterns | + transform([](const auto& _v) { return to_value(_v); }))}}; + } +}; + template struct ToCondition> { dynamic::Condition operator()(const auto& _cond) const { diff --git a/src/sqlgen/mysql/to_sql.cpp b/src/sqlgen/mysql/to_sql.cpp index 6a390e6..f10c941 100644 --- a/src/sqlgen/mysql/to_sql.cpp +++ b/src/sqlgen/mysql/to_sql.cpp @@ -209,6 +209,8 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { template std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { + using namespace std::ranges::views; + using C = std::remove_cvref_t; std::stringstream stream; @@ -229,6 +231,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << operation_to_sql(_condition.op1) << " > " << operation_to_sql(_condition.op2); + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op) << " IS NULL"; @@ -262,6 +272,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << "(" << condition_to_sql(*_condition.cond1) << ") OR (" << condition_to_sql(*_condition.cond2) << ")"; + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " NOT IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 5595d93..c479ead 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -161,6 +161,8 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { template std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { + using namespace std::ranges::views; + using C = std::remove_cvref_t; std::stringstream stream; @@ -181,6 +183,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << operation_to_sql(_condition.op1) << " > " << operation_to_sql(_condition.op2); + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op) << " IS NULL"; @@ -210,6 +220,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << operation_to_sql(_condition.op) << " NOT LIKE " << column_or_value_to_sql(_condition.pattern); + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " NOT IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else if constexpr (std::is_same_v) { stream << "(" << condition_to_sql(*_condition.cond1) << ") OR (" << condition_to_sql(*_condition.cond2) << ")"; diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index 4802233..371e5ba 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -169,6 +169,8 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { template std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { + using namespace std::ranges::views; + using C = std::remove_cvref_t; std::stringstream stream; @@ -189,6 +191,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << operation_to_sql(_condition.op1) << " > " << operation_to_sql(_condition.op2); + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else if constexpr (std::is_same_v) { stream << operation_to_sql(_condition.op) << " IS NULL"; @@ -218,6 +228,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { stream << operation_to_sql(_condition.op) << " NOT LIKE " << column_or_value_to_sql(_condition.pattern); + } else if constexpr (std::is_same_v) { + stream << operation_to_sql(_condition.op) << " NOT IN (" + << internal::strings::join( + ", ", + internal::collect::vector(_condition.patterns | + transform(column_or_value_to_sql))) + << ")"; + } else if constexpr (std::is_same_v) { stream << "(" << condition_to_sql(*_condition.cond1) << ") OR (" << condition_to_sql(*_condition.cond2) << ")"; diff --git a/tests/mysql/test_in.cpp b/tests/mysql/test_in.cpp new file mode 100644 index 0000000..a9a73e0 --- /dev/null +++ b/tests/mysql/test_in.cpp @@ -0,0 +1,56 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(mysql, test_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + mysql::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in("Bart", "Lisa", "Maggie")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in + +#endif diff --git a/tests/mysql/test_in_vec.cpp b/tests/mysql/test_in_vec.cpp new file mode 100644 index 0000000..5be0906 --- /dev/null +++ b/tests/mysql/test_in_vec.cpp @@ -0,0 +1,57 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(mysql, test_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + mysql::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in( + std::vector({"Bart", "Lisa", "Maggie"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in_vec + +#endif diff --git a/tests/mysql/test_not_in.cpp b/tests/mysql/test_not_in.cpp new file mode 100644 index 0000000..af0d47f --- /dev/null +++ b/tests/mysql/test_not_in.cpp @@ -0,0 +1,56 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(mysql, test_not_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + mysql::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in("Homer", "Hugo")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in + +#endif diff --git a/tests/mysql/test_not_in_vec.cpp b/tests/mysql/test_not_in_vec.cpp new file mode 100644 index 0000000..3328a25 --- /dev/null +++ b/tests/mysql/test_not_in_vec.cpp @@ -0,0 +1,57 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(mysql, test_not_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + mysql::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in( + std::vector({"Homer", "Hugo"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in_vec + +#endif diff --git a/tests/postgres/test_in.cpp b/tests/postgres/test_in.cpp new file mode 100644 index 0000000..b48a0cd --- /dev/null +++ b/tests/postgres/test_in.cpp @@ -0,0 +1,56 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in("Bart", "Lisa", "Maggie")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in + +#endif diff --git a/tests/postgres/test_in_vec.cpp b/tests/postgres/test_in_vec.cpp new file mode 100644 index 0000000..d579ead --- /dev/null +++ b/tests/postgres/test_in_vec.cpp @@ -0,0 +1,57 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in( + std::vector({"Bart", "Lisa", "Maggie"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in_vec + +#endif diff --git a/tests/postgres/test_not_in.cpp b/tests/postgres/test_not_in.cpp new file mode 100644 index 0000000..12ee883 --- /dev/null +++ b/tests/postgres/test_not_in.cpp @@ -0,0 +1,56 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_not_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in("Homer", "Hugo")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in + +#endif diff --git a/tests/postgres/test_not_in_vec.cpp b/tests/postgres/test_not_in_vec.cpp new file mode 100644 index 0000000..2b52a56 --- /dev/null +++ b/tests/postgres/test_not_in_vec.cpp @@ -0,0 +1,57 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_not_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in( + std::vector({"Homer", "Hugo"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in_vec + +#endif diff --git a/tests/sqlite/test_in.cpp b/tests/sqlite/test_in.cpp new file mode 100644 index 0000000..8e1b9ed --- /dev/null +++ b/tests/sqlite/test_in.cpp @@ -0,0 +1,48 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in("Bart", "Lisa", "Maggie")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in + diff --git a/tests/sqlite/test_in_vec.cpp b/tests/sqlite/test_in_vec.cpp new file mode 100644 index 0000000..4bfa9af --- /dev/null +++ b/tests/sqlite/test_in_vec.cpp @@ -0,0 +1,49 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.in( + std::vector({"Bart", "Lisa", "Maggie"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_in_vec + diff --git a/tests/sqlite/test_not_in.cpp b/tests/sqlite/test_not_in.cpp new file mode 100644 index 0000000..ed5a2cb --- /dev/null +++ b/tests/sqlite/test_not_in.cpp @@ -0,0 +1,48 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_not_in) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in("Homer", "Hugo")) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in + diff --git a/tests/sqlite/test_not_in_vec.cpp b/tests/sqlite/test_not_in_vec.cpp new file mode 100644 index 0000000..05ee3ab --- /dev/null +++ b/tests/sqlite/test_not_in_vec.cpp @@ -0,0 +1,49 @@ + +#include + +#include +#include +#include +#include +#include + +namespace test_not_in_vec { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_not_in_vec) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto people2 = + sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(sqlgen::read> | + where("first_name"_c.not_in( + std::vector({"Homer", "Hugo"}))) | + order_by("age"_c)) + .value(); + + const std::string expected1 = + 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), expected1); +} + +} // namespace test_not_in_vec +