From 388ed523733b1044d27d57eeabc81d74a1ed3025 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 23 Jul 2024 12:03:09 +0200 Subject: [PATCH 1/3] Added missing `node_tuple` specialization for bitwise not --- dev/node_tuple.h | 3 +++ include/sqlite_orm/sqlite_orm.h | 3 +++ tests/static_tests/node_tuple.cpp | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/dev/node_tuple.h b/dev/node_tuple.h index 1cf4729e..d82825af 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -199,6 +199,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + template struct node_tuple, void> : node_tuple_for {}; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 07207af3..9adac345 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -24197,6 +24197,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + template struct node_tuple, void> : node_tuple_for {}; diff --git a/tests/static_tests/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index 59b4e6c9..9f10491d 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -209,6 +209,24 @@ TEST_CASE("Node tuple") { using AssignTuple = node_tuple_t>; static_assert(is_same>::value, "assign_t"); } + SECTION("bitwise operator") { + using namespace internal; + + using BitwiseNotTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + + using BitwiseShiftLeftTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + + using BitwiseShiftRightTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + + using BitwiseAndTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + + using BitwiseOrTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + } SECTION("columns") { auto cols = columns(&User::id, &User::name); using Cols = decltype(cols); From 766861e8cb70358cfd1429e6f9ac1deb705d617f Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 23 Jul 2024 12:30:25 +0200 Subject: [PATCH 2/3] C++ operator overloads for bitwise operators The bitwise operators were missing C++ operator equivalents. Also: * Added serialization unit tests for bitwise operators * Added missing column result type unit tests for bitwise operators * Added missing serialization unit tests for logical not * Improved parenthesizing logical and bitwise "not" expressions --- dev/conditions.h | 10 +- dev/core_functions.h | 52 ++++++++ dev/expression.h | 14 ++- dev/statement_serializer.h | 41 ++++-- include/sqlite_orm/sqlite_orm.h | 118 +++++++++++++++--- tests/CMakeLists.txt | 6 +- .../{bitwise.cpp => bitwise_operators.cpp} | 24 ++-- .../bitwise_operators.cpp | 71 +++++++++++ .../logical_operators.cpp | 14 +++ .../schema/trigger.cpp | 2 +- tests/static_tests/column_result_t.cpp | 7 +- tests/static_tests/operators_adl.cpp | 11 ++ 12 files changed, 327 insertions(+), 43 deletions(-) rename tests/operators/{bitwise.cpp => bitwise_operators.cpp} (50%) create mode 100644 tests/statement_serializer_tests/bitwise_operators.cpp diff --git a/dev/conditions.h b/dev/conditions.h index d7ac8b93..7ee3dcff 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -111,9 +111,11 @@ namespace sqlite_orm { */ template struct negated_condition_t : condition_t, negated_condition_string { - C c; + using argument_type = C; - constexpr negated_condition_t(C c_) : c(std::move(c_)) {} + argument_type c; + + constexpr negated_condition_t(argument_type arg) : c(std::move(arg)) {} }; /** @@ -153,7 +155,7 @@ namespace sqlite_orm { * Result of and operator */ template - struct and_condition_t : binary_condition { + struct and_condition_t : binary_condition, negatable_t { using super = binary_condition; using super::super; @@ -169,7 +171,7 @@ namespace sqlite_orm { * Result of or operator */ template - struct or_condition_t : binary_condition { + struct or_condition_t : binary_condition, negatable_t { using super = binary_condition; using super::super; diff --git a/dev/core_functions.h b/dev/core_functions.h index bed10999..3c4136b5 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -2095,6 +2095,58 @@ namespace sqlite_orm { constexpr mod_t, unwrap_expression_t> operator%(L l, R r) { return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } + + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + constexpr bitwise_not_t> operator~(T arg) { + return {get_from_expression(std::forward(arg))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_shift_left_t, unwrap_expression_t> operator<<(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_shift_right_t, unwrap_expression_t> operator>>(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_and_t, unwrap_expression_t> operator&(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_or_t, unwrap_expression_t> operator|(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } } template diff --git a/dev/expression.h b/dev/expression.h index d40b4c63..39a86aec 100644 --- a/dev/expression.h +++ b/dev/expression.h @@ -68,15 +68,25 @@ namespace sqlite_orm { is_operator_argument_v::value>> = true; template - constexpr T get_from_expression(T value) { + constexpr T get_from_expression(T&& value) { return std::move(value); } template - constexpr T get_from_expression(expression_t expression) { + constexpr const T& get_from_expression(const T& value) { + return value; + } + + template + constexpr T get_from_expression(expression_t&& expression) { return std::move(expression.value); } + template + constexpr const T& get_from_expression(const expression_t& expression) { + return expression.value; + } + template using unwrap_expression_t = decltype(get_from_expression(std::declval())); } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index fd3da122..60cf5586 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -35,6 +35,7 @@ #include "type_printer.h" #include "field_printer.h" #include "literal.h" +#include "expression.h" #include "table_name_collector.h" #include "column_names_getter.h" #include "cte_column_names_collector.h" @@ -721,11 +722,23 @@ namespace sqlite_orm { using statement_type = bitwise_not_t; template - std::string operator()(const statement_type& statement, const Ctx& context) const { + std::string operator()(const statement_type& expression, const Ctx& context) const { + // subqueries should always use parentheses in binary expressions + auto subCtx = context; + subCtx.use_parentheses = true; + // parentheses for sub-trees to ensure the order of precedence + constexpr bool parenthesize = is_binary_condition::value || + is_binary_operator::value; + std::stringstream ss; - ss << statement.serialize() << " "; - auto cString = serialize(statement.argument, context); - ss << " (" << cString << " )"; + ss << expression.serialize(); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << "("; + } + ss << serialize(get_from_expression(expression.argument), subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << ")"; + } return ss.str(); } }; @@ -735,11 +748,23 @@ namespace sqlite_orm { using statement_type = negated_condition_t; template - std::string operator()(const statement_type& c, const Ctx& context) const { + std::string operator()(const statement_type& expression, const Ctx& context) const { + // subqueries should always use parentheses in binary expressions + auto subCtx = context; + subCtx.use_parentheses = true; + // parentheses for sub-trees to ensure the order of precedence + constexpr bool parenthesize = is_binary_condition::value || + is_binary_operator::value; + std::stringstream ss; - ss << static_cast(c) << " "; - auto cString = serialize(c.c, context); - ss << " (" << cString << " )"; + ss << static_cast(expression) << " "; + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << "("; + } + ss << serialize(get_from_expression(expression.c), subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << ")"; + } return ss.str(); } }; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 9adac345..ff4507de 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -4717,15 +4717,25 @@ namespace sqlite_orm { is_operator_argument_v::value>> = true; template - constexpr T get_from_expression(T value) { + constexpr T get_from_expression(T&& value) { return std::move(value); } template - constexpr T get_from_expression(expression_t expression) { + constexpr const T& get_from_expression(const T& value) { + return value; + } + + template + constexpr T get_from_expression(expression_t&& expression) { return std::move(expression.value); } + template + constexpr const T& get_from_expression(const expression_t& expression) { + return expression.value; + } + template using unwrap_expression_t = decltype(get_from_expression(std::declval())); } @@ -4850,9 +4860,11 @@ namespace sqlite_orm { */ template struct negated_condition_t : condition_t, negated_condition_string { - C c; + using argument_type = C; + + argument_type c; - constexpr negated_condition_t(C c_) : c(std::move(c_)) {} + constexpr negated_condition_t(argument_type arg) : c(std::move(arg)) {} }; /** @@ -4892,7 +4904,7 @@ namespace sqlite_orm { * Result of and operator */ template - struct and_condition_t : binary_condition { + struct and_condition_t : binary_condition, negatable_t { using super = binary_condition; using super::super; @@ -4908,7 +4920,7 @@ namespace sqlite_orm { * Result of or operator */ template - struct or_condition_t : binary_condition { + struct or_condition_t : binary_condition, negatable_t { using super = binary_condition; using super::super; @@ -8133,6 +8145,58 @@ namespace sqlite_orm { constexpr mod_t, unwrap_expression_t> operator%(L l, R r) { return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } + + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + constexpr bitwise_not_t> operator~(T arg) { + return {get_from_expression(std::forward(arg))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_shift_left_t, unwrap_expression_t> operator<<(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_shift_right_t, unwrap_expression_t> operator>>(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_and_t, unwrap_expression_t> operator&(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + constexpr bitwise_or_t, unwrap_expression_t> operator|(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } } template @@ -18463,6 +18527,8 @@ namespace sqlite_orm { // #include "literal.h" +// #include "expression.h" + // #include "table_name_collector.h" // #include "column_names_getter.h" @@ -19879,11 +19945,23 @@ namespace sqlite_orm { using statement_type = bitwise_not_t; template - std::string operator()(const statement_type& statement, const Ctx& context) const { + std::string operator()(const statement_type& expression, const Ctx& context) const { + // subqueries should always use parentheses in binary expressions + auto subCtx = context; + subCtx.use_parentheses = true; + // parentheses for sub-trees to ensure the order of precedence + constexpr bool parenthesize = is_binary_condition::value || + is_binary_operator::value; + std::stringstream ss; - ss << statement.serialize() << " "; - auto cString = serialize(statement.argument, context); - ss << " (" << cString << " )"; + ss << expression.serialize(); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << "("; + } + ss << serialize(get_from_expression(expression.argument), subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << ")"; + } return ss.str(); } }; @@ -19893,11 +19971,23 @@ namespace sqlite_orm { using statement_type = negated_condition_t; template - std::string operator()(const statement_type& c, const Ctx& context) const { + std::string operator()(const statement_type& expression, const Ctx& context) const { + // subqueries should always use parentheses in binary expressions + auto subCtx = context; + subCtx.use_parentheses = true; + // parentheses for sub-trees to ensure the order of precedence + constexpr bool parenthesize = is_binary_condition::value || + is_binary_operator::value; + std::stringstream ss; - ss << static_cast(c) << " "; - auto cString = serialize(c.c, context); - ss << " (" << cString << " )"; + ss << static_cast(expression) << " "; + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << "("; + } + ss << serialize(get_from_expression(expression.c), subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesize) { + ss << ")"; + } return ss.str(); } }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index db24c65f..d3ebf450 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,15 +57,15 @@ add_executable(unit_tests built_in_functions_tests/math_functions.cpp user_defined_functions.cpp constraints/composite_key.cpp - operators/arithmetic_operators.cpp operators/like.cpp operators/glob.cpp operators/in.cpp operators/cast.cpp operators/is_null.cpp - operators/not_operator.cpp - operators/bitwise.cpp + operators/arithmetic_operators.cpp + operators/bitwise_operators.cpp operators/binary_operators.cpp + operators/not_operator.cpp prepared_statement_tests/select.cpp prepared_statement_tests/get_all.cpp prepared_statement_tests/get_all_pointer.cpp diff --git a/tests/operators/bitwise.cpp b/tests/operators/bitwise_operators.cpp similarity index 50% rename from tests/operators/bitwise.cpp rename to tests/operators/bitwise_operators.cpp index 7492a2c1..c6524155 100644 --- a/tests/operators/bitwise.cpp +++ b/tests/operators/bitwise_operators.cpp @@ -18,46 +18,50 @@ TEST_CASE("bitwise operators") { storage.sync_schema(); { - auto rows = storage.select(bitwise_or(60, 13)); + auto rows = storage.select(union_(select(bitwise_or(60, 13)), select(c(60) | 13))); REQUIRE(rows == std::vector{61}); } { - auto rows = storage.select(bitwise_and(60, 13)); + auto rows = storage.select(union_(select(bitwise_and(60, 13)), select(c(60) & 13))); REQUIRE(rows == std::vector{12}); } { - auto rows = storage.select(bitwise_shift_left(60, 2)); + auto rows = storage.select(union_(select(bitwise_shift_left(60, 2)), select(c(60) << 2))); REQUIRE(rows == std::vector{240}); } { - auto rows = storage.select(bitwise_shift_right(60, 2)); + auto rows = storage.select(union_(select(bitwise_shift_right(60, 2)), select(c(60) >> 2))); REQUIRE(rows == std::vector{15}); } { - auto rows = storage.select(bitwise_not(60)); + auto rows = storage.select(union_(select(bitwise_not(60)), select(~c(60)))); REQUIRE(rows == std::vector{-61}); } storage.insert(Entry{60, 13}); { - auto rows = storage.select(bitwise_or(&Entry::lhs, &Entry::rhs)); + auto rows = + storage.select(union_(select(bitwise_or(&Entry::lhs, &Entry::rhs)), select(c(&Entry::lhs) | &Entry::rhs))); REQUIRE(rows == std::vector{61}); } { - auto rows = storage.select(bitwise_and(&Entry::lhs, &Entry::rhs)); + auto rows = + storage.select(union_(select(bitwise_and(&Entry::lhs, &Entry::rhs)), select(c(&Entry::lhs) & &Entry::rhs))); REQUIRE(rows == std::vector{12}); } storage.remove_all(); storage.insert(Entry{60, 2}); { - auto rows = storage.select(bitwise_shift_left(&Entry::lhs, &Entry::rhs)); + auto rows = storage.select( + union_(select(bitwise_shift_left(&Entry::lhs, &Entry::rhs)), select(c(&Entry::lhs) << &Entry::rhs))); REQUIRE(rows == std::vector{240}); } { - auto rows = storage.select(bitwise_shift_right(&Entry::lhs, &Entry::rhs)); + auto rows = storage.select( + union_(select(bitwise_shift_right(&Entry::lhs, &Entry::rhs)), select(c(&Entry::lhs) >> &Entry::rhs))); REQUIRE(rows == std::vector{15}); } { - auto rows = storage.select(bitwise_not(&Entry::lhs)); + auto rows = storage.select(union_(select(bitwise_not(&Entry::lhs)), select(~c(&Entry::lhs)))); REQUIRE(rows == std::vector{-61}); } } diff --git a/tests/statement_serializer_tests/bitwise_operators.cpp b/tests/statement_serializer_tests/bitwise_operators.cpp new file mode 100644 index 00000000..b5b119c9 --- /dev/null +++ b/tests/statement_serializer_tests/bitwise_operators.cpp @@ -0,0 +1,71 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("statement_serializer bitwise operators") { + internal::db_objects_tuple<> storage; + internal::serializer_context> context{storage}; + std::string value; + decltype(value) expected; + SECTION("bitwise_or") { + SECTION("func") { + value = serialize(bitwise_or(3, 5), context); + } + SECTION("operator") { + value = serialize(c(3) | 5, context); + } + expected = "3 | 5"; + } + SECTION("bitwise_and") { + SECTION("func") { + value = serialize(bitwise_and(5, -9), context); + } + SECTION("operator") { + value = serialize(c(5) & -9, context); + } + expected = "5 & -9"; + } + SECTION("bitwise_shift_left") { + SECTION("func") { + value = serialize(bitwise_shift_left(10, 1), context); + } + SECTION("operator") { + value = serialize(c(10) << 1, context); + } + expected = "10 << 1"; + } + SECTION("bitwise_shift_right") { + SECTION("func") { + value = serialize(bitwise_shift_right(10, 2), context); + } + SECTION("operator") { + value = serialize(c(10) >> 2, context); + } + expected = "10 >> 2"; + } + SECTION("bitwise_not") { + SECTION("func") { + value = serialize(bitwise_not(20), context); + } + SECTION("operator") { + value = serialize(~c(20), context); + } + expected = "~20"; + } + SECTION("parentheses keeping order of precedence") { + SECTION("1") { + value = serialize(c(4) << 5 << 3, context); + expected = "(4 << 5) << 3"; + } + SECTION("2") { + value = serialize(4 << (c(5) << 3), context); + expected = "4 << (5 << 3)"; + } + SECTION("3") { + value = serialize(4 | ~c(5) & 3 | 1, context); + expected = "(4 | (~5 & 3)) | 1"; + } + } + REQUIRE(value == expected); +} diff --git a/tests/statement_serializer_tests/logical_operators.cpp b/tests/statement_serializer_tests/logical_operators.cpp index 312f4c29..00fdae9c 100644 --- a/tests/statement_serializer_tests/logical_operators.cpp +++ b/tests/statement_serializer_tests/logical_operators.cpp @@ -63,6 +63,20 @@ TEST_CASE("statement_serializer logical operators") { expected = R"(("id" = 5) OR ("name" = 'Ariana'))"; } } + SECTION("not") { + SECTION("simple") { + SECTION("operator") { + stringValue = serialize(!c(20), context); + } + expected = "NOT 20"; + } + SECTION("complex") { + SECTION("operator") { + stringValue = serialize(!(c(20) and 1), context); + } + expected = "NOT (20 AND 1)"; + } + } SECTION("in") { SECTION("static in") { auto inValue = c(&User::id).in(1, 2, 3); diff --git a/tests/statement_serializer_tests/schema/trigger.cpp b/tests/statement_serializer_tests/schema/trigger.cpp index 28c9d00f..99d66980 100644 --- a/tests/statement_serializer_tests/schema/trigger.cpp +++ b/tests/statement_serializer_tests/schema/trigger.cpp @@ -37,7 +37,7 @@ TEST_CASE("statement_serializer trigger") { value = serialize(expression, context); expected = R"(CREATE TRIGGER IF NOT EXISTS "validate_email_before_insert_leads" BEFORE INSERT ON "leads" BEGIN SELECT )" - R"(CASE WHEN NOT (NEW."email" LIKE '%_@__%.__%' ) THEN RAISE(ABORT, 'Invalid email address') END; END)"; + R"(CASE WHEN NOT NEW."email" LIKE '%_@__%.__%' THEN RAISE(ABORT, 'Invalid email address') END; END)"; } REQUIRE(value == expected); } diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 0a914afa..25b19491 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -105,11 +105,16 @@ TEST_CASE("column_result_of_t") { runTest(c(&User::id) / 5); runTest(mod(&User::id, 5)); runTest(c(&User::id) % 5); + runTest(bitwise_not(&User::id)); + runTest(~c(&User::id)); runTest(bitwise_shift_left(&User::id, 4)); + runTest(c(&User::id) << 4); runTest(bitwise_shift_right(&User::id, 4)); + runTest(c(&User::id) >> 4); runTest(bitwise_and(&User::id, 4)); + runTest(c(&User::id) & 4); runTest(bitwise_or(&User::id, 4)); - runTest(bitwise_not(&User::id)); + runTest(c(&User::id) | 4); runTest(rowid()); runTest(oid()); runTest(_rowid_()); diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index 74c5df20..cdfdb63c 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -15,6 +15,11 @@ using sqlite_orm::get; using sqlite_orm::or_; using sqlite_orm::internal::and_condition_t; using sqlite_orm::internal::binary_operator; +using sqlite_orm::internal::bitwise_and_t; +using sqlite_orm::internal::bitwise_not_t; +using sqlite_orm::internal::bitwise_or_t; +using sqlite_orm::internal::bitwise_shift_left_t; +using sqlite_orm::internal::bitwise_shift_right_t; using sqlite_orm::internal::greater_or_equal_t; using sqlite_orm::internal::greater_than_t; using sqlite_orm::internal::is_equal_t; @@ -106,6 +111,12 @@ void runTests(E expression) { // conc_t + condition_t yield or_condition_t STATIC_REQUIRE(is_specialization_of_v); STATIC_REQUIRE(is_specialization_of_v); + + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v> expression), binary_operator>); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); } TEST_CASE("inline namespace literals expressions") { From dd2040efdfbc3c71d337f21db57bd832357a1031 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 23 Jul 2024 18:22:57 +0200 Subject: [PATCH 3/3] Added the "unary minus" operator --- dev/column_result.h | 5 ++ dev/core_functions.h | 8 ++++ dev/node_tuple.h | 3 ++ dev/operators.h | 25 +++++++++- dev/statement_serializer.h | 7 ++- include/sqlite_orm/sqlite_orm.h | 48 +++++++++++++++++-- .../arithmetic_operators.cpp | 17 +++++-- tests/static_tests/column_result_t.cpp | 2 + tests/static_tests/node_tuple.cpp | 17 ++++--- tests/static_tests/operators_adl.cpp | 5 ++ 10 files changed, 120 insertions(+), 17 deletions(-) diff --git a/dev/column_result.h b/dev/column_result.h index 6bf9ad7d..725546b3 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -149,6 +149,11 @@ namespace sqlite_orm { using type = std::string; }; + template + struct column_result_t, void> { + using type = double; + }; + template struct column_result_t, void> { using type = double; diff --git a/dev/core_functions.h b/dev/core_functions.h index 3c4136b5..918135ca 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -2041,6 +2041,14 @@ namespace sqlite_orm { // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + constexpr unary_minus_t> operator-(T arg) { + return {get_from_expression(std::forward(arg))}; + } + template, diff --git a/dev/node_tuple.h b/dev/node_tuple.h index d82825af..e551b04b 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -199,6 +199,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + template struct node_tuple, void> : node_tuple {}; diff --git a/dev/operators.h b/dev/operators.h index 85d0e349..d656a0ec 100644 --- a/dev/operators.h +++ b/dev/operators.h @@ -41,6 +41,24 @@ namespace sqlite_orm { template using conc_t = binary_operator; + struct unary_minus_string { + serialize_result_type serialize() const { + return "-"; + } + }; + + /** + * Result of unary minus - operator + */ + template + struct unary_minus_t : unary_minus_string, arithmetic_t, negatable_t { + using argument_type = T; + + argument_type argument; + + unary_minus_t(argument_type argument_) : argument(std::move(argument_)) {} + }; + struct add_string { serialize_result_type serialize() const { return "+"; @@ -60,7 +78,7 @@ namespace sqlite_orm { }; /** - * Result of substitute - operator + * Result of substraction - operator */ template using sub_t = binary_operator; @@ -201,6 +219,11 @@ namespace sqlite_orm { return {std::move(l), std::move(r)}; } + template + constexpr internal::unary_minus_t minus(T t) { + return {std::move(t)}; + } + /** * Public interface for + operator. Example: `select(add(&User::age, 100));` => SELECT age + 100 FROM users */ diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index 60cf5586..c616d906 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -718,8 +718,11 @@ namespace sqlite_orm { }; template - struct statement_serializer, void> { - using statement_type = bitwise_not_t; + struct statement_serializer< + T, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = T; template std::string operator()(const statement_type& expression, const Ctx& context) const { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ff4507de..dc32f84f 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -4168,6 +4168,24 @@ namespace sqlite_orm { template using conc_t = binary_operator; + struct unary_minus_string { + serialize_result_type serialize() const { + return "-"; + } + }; + + /** + * Result of unary minus - operator + */ + template + struct unary_minus_t : unary_minus_string, arithmetic_t, negatable_t { + using argument_type = T; + + argument_type argument; + + unary_minus_t(argument_type argument_) : argument(std::move(argument_)) {} + }; + struct add_string { serialize_result_type serialize() const { return "+"; @@ -4187,7 +4205,7 @@ namespace sqlite_orm { }; /** - * Result of substitute - operator + * Result of substraction - operator */ template using sub_t = binary_operator; @@ -4328,6 +4346,11 @@ namespace sqlite_orm { return {std::move(l), std::move(r)}; } + template + constexpr internal::unary_minus_t minus(T t) { + return {std::move(t)}; + } + /** * Public interface for + operator. Example: `select(add(&User::age, 100));` => SELECT age + 100 FROM users */ @@ -8091,6 +8114,14 @@ namespace sqlite_orm { // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + constexpr unary_minus_t> operator-(T arg) { + return {get_from_expression(std::forward(arg))}; + } + template, @@ -11474,6 +11505,11 @@ namespace sqlite_orm { using type = std::string; }; + template + struct column_result_t, void> { + using type = double; + }; + template struct column_result_t, void> { using type = double; @@ -19941,8 +19977,11 @@ namespace sqlite_orm { }; template - struct statement_serializer, void> { - using statement_type = bitwise_not_t; + struct statement_serializer< + T, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = T; template std::string operator()(const statement_type& expression, const Ctx& context) const { @@ -24287,6 +24326,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + template struct node_tuple, void> : node_tuple {}; diff --git a/tests/statement_serializer_tests/arithmetic_operators.cpp b/tests/statement_serializer_tests/arithmetic_operators.cpp index 764b062b..842f9ec2 100644 --- a/tests/statement_serializer_tests/arithmetic_operators.cpp +++ b/tests/statement_serializer_tests/arithmetic_operators.cpp @@ -8,6 +8,15 @@ TEST_CASE("statement_serializer arithmetic operators") { internal::serializer_context> context{storage}; std::string value; decltype(value) expected; + SECTION("unary minus") { + SECTION("func") { + value = serialize(minus(20), context); + } + SECTION("operator") { + value = serialize(-c(20), context); + } + expected = "-20"; + } SECTION("add") { SECTION("func") { value = serialize(add(3, 5), context); @@ -55,12 +64,12 @@ TEST_CASE("statement_serializer arithmetic operators") { } SECTION("parentheses keeping order of precedence") { SECTION("1") { - value = serialize(c(4) + 5 + 3, context); - expected = "(4 + 5) + 3"; + value = serialize(c(4) + 5 + -c(3), context); + expected = "(4 + 5) + -3"; } SECTION("2") { - value = serialize(4 + (c(5) + 3), context); - expected = "4 + (5 + 3)"; + value = serialize(4 + -(c(5) + 3), context); + expected = "4 + -(5 + 3)"; } SECTION("3") { value = serialize(4 + c(5) * 3 + 1, context); diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 25b19491..cbfef385 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -95,6 +95,8 @@ TEST_CASE("column_result_of_t") { runTest(all(&User::name)); runTest(conc(&User::name, &User::id)); runTest(c(&User::name) || &User::id); + runTest(minus(&User::id)); + runTest(-c(&User::id)); runTest(add(&User::id, 5)); runTest(c(&User::id) + 5); runTest(sub(&User::id, 5)); diff --git a/tests/static_tests/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index 9f10491d..4a9a07bc 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -189,25 +189,28 @@ TEST_CASE("Node tuple") { using namespace internal; using CondTuple = node_tuple_t>; - static_assert(is_same>::value, "conc_t"); + STATIC_REQUIRE(is_same>::value); + + using MinusTuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); using AddTuple = node_tuple_t>; - static_assert(is_same>::value, "add_t"); + STATIC_REQUIRE(is_same>::value); using SubTuple = node_tuple_t>; - static_assert(is_same>::value, "sub_t"); + STATIC_REQUIRE(is_same>::value); using MulTuple = node_tuple_t>; - static_assert(is_same>::value, "mul_t"); + STATIC_REQUIRE(is_same>::value); using DivTuple = node_tuple_t>; - static_assert(is_same>::value, "div_t"); + STATIC_REQUIRE(is_same>::value); using ModTuple = node_tuple_t>; - static_assert(is_same>::value, "mod_t"); + STATIC_REQUIRE(is_same>::value); using AssignTuple = node_tuple_t>; - static_assert(is_same>::value, "assign_t"); + STATIC_REQUIRE(is_same>::value); } SECTION("bitwise operator") { using namespace internal; diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index cdfdb63c..111358ab 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -28,6 +28,7 @@ using sqlite_orm::internal::less_or_equal_t; using sqlite_orm::internal::less_than_t; using sqlite_orm::internal::negated_condition_t; using sqlite_orm::internal::or_condition_t; +using sqlite_orm::internal::unary_minus_t; using sqlite_orm::polyfill::is_specialization_of_v; template @@ -68,6 +69,10 @@ void runTests(E expression) { STATIC_REQUIRE(is_specialization_of_v); STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); STATIC_REQUIRE(is_specialization_of_v); STATIC_REQUIRE(is_specialization_of_v);