From d840654ff1d4921bac2d91ca623662bf8b9dc5b5 Mon Sep 17 00:00:00 2001 From: Yevgeniy Zakharov Date: Wed, 11 Oct 2023 20:30:48 +0600 Subject: [PATCH] added explicit null and not null --- README.md | 2 +- dev/constraints.h | 19 +++++ dev/serializing_util.h | 21 ++++-- dev/statement_serializer.h | 32 +++++++-- include/sqlite_orm/sqlite_orm.h | 72 ++++++++++++++++--- tests/CMakeLists.txt | 2 + .../column_constraints/not_null.cpp | 12 ++++ .../column_constraints/null.cpp | 12 ++++ .../schema/column.cpp | 38 ++++++++-- 9 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 tests/statement_serializer_tests/column_constraints/not_null.cpp create mode 100644 tests/statement_serializer_tests/column_constraints/null.cpp diff --git a/README.md b/README.md index 8dea9b25a..03650d645 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ auto storage = make_storage("db.sqlite", make_column("name", &UserType::name, default_value("name_placeholder")))); ``` -Too easy isn't it? You do not have to specify mapped type explicitly - it is deduced from your member pointers you pass during making a column (for example: `&User::id`). To create a column you have to pass two arguments at least: its name in the table and your mapped class member pointer. You can also add extra arguments to tell your storage about column's constraints like `primary_key`, `autoincrement`, `default_value`, `unique` or `generated_always_as` (order isn't important; `not_null` is deduced from type automatically). +Too easy isn't it? You do not have to specify mapped type explicitly - it is deduced from your member pointers you pass during making a column (for example: `&User::id`). To create a column you have to pass two arguments at least: its name in the table and your mapped class member pointer. You can also add extra arguments to tell your storage about column's constraints like `primary_key`, `autoincrement`, `default_value`, `unique` or `generated_always_as` (order isn't important; `not_null`/`null` are deduced from type automatically but can be added manually if you wish with `null()` and `not_null()`). More details about making storage can be found in [tutorial](https://github.com/fnc12/sqlite_orm/wiki/Making-a-storage). diff --git a/dev/constraints.h b/dev/constraints.h index ff38bfd8a..0495c713d 100644 --- a/dev/constraints.h +++ b/dev/constraints.h @@ -412,6 +412,9 @@ namespace sqlite_orm { } }; + struct null_t {}; + + struct not_null_t {}; } namespace internal { @@ -440,6 +443,12 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + template + using is_null = std::is_same; + + template + using is_not_null = std::is_same; + /** * PRIMARY KEY INSERTABLE traits. */ @@ -458,6 +467,8 @@ namespace sqlite_orm { using is_constraint = mpl::instantiate, check_if, + check_if, + check_if, check_if_is_template, check_if_is_template, check_if_is_template, @@ -536,4 +547,12 @@ namespace sqlite_orm { internal::check_t check(T t) { return {std::move(t)}; } + + inline internal::null_t null() { + return {}; + } + + inline internal::not_null_t not_null() { + return {}; + } } diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 08be5f6ab..3a5123158 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -366,11 +366,24 @@ namespace sqlite_orm { const bool& isNotNull = get<2>(tpl); auto& context = get<3>(tpl); - iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { - ss << serialize(constraint, context) << ' '; + auto first = true; + constexpr std::array sep = {" ", ""}; + iterate_tuple(column.constraints, [&ss, &context, &first, &sep](auto& constraint) { + ss << sep[std::exchange(first, false)]; + ss << serialize(constraint, context); }); - if(isNotNull) { - ss << "NOT NULL"; + using ConstraintsTuple = decltype(column.constraints); + constexpr bool constraintsHaveNullConstraint = + mpl::invoke_t, ConstraintsTuple>::value; + constexpr bool constraintsHaveNotNullConstraint = + mpl::invoke_t, ConstraintsTuple>::value; + if(!constraintsHaveNullConstraint && !constraintsHaveNotNullConstraint) { + ss << sep[std::exchange(first, false)]; + if(isNotNull) { + ss << "NOT NULL"; + } else { + ss << "NULL"; + } } return ss; diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index 06de3ba2e..18dd73402 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -886,6 +886,26 @@ namespace sqlite_orm { } }; + template<> + struct statement_serializer { + using statement_type = null_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + return "NULL"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_null_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + return "NOT NULL"; + } + }; + template struct statement_serializer, void> { using statement_type = primary_key_t; @@ -1031,13 +1051,11 @@ namespace sqlite_orm { if(!context.skip_types_and_constraints) { ss << " " << type_printer>().print(); const bool columnIsNotNull = column.is_not_null(); - auto constraintsTuple = streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - columnIsNotNull, - context); - if(std::tuple_size::value > 0) { - ss << " " << constraintsTuple; - } + ss << " " + << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + columnIsNotNull, + context); } return ss.str(); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 58443eb54..3cc9d8b21 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1698,6 +1698,9 @@ namespace sqlite_orm { } }; + struct null_t {}; + + struct not_null_t {}; } namespace internal { @@ -1726,6 +1729,12 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + template + using is_null = std::is_same; + + template + using is_not_null = std::is_same; + /** * PRIMARY KEY INSERTABLE traits. */ @@ -1744,6 +1753,8 @@ namespace sqlite_orm { using is_constraint = mpl::instantiate, check_if, + check_if, + check_if, check_if_is_template, check_if_is_template, check_if_is_template, @@ -1822,6 +1833,14 @@ namespace sqlite_orm { internal::check_t check(T t) { return {std::move(t)}; } + + inline internal::null_t null() { + return {}; + } + + inline internal::not_null_t not_null() { + return {}; + } } #pragma once @@ -13663,11 +13682,24 @@ namespace sqlite_orm { const bool& isNotNull = get<2>(tpl); auto& context = get<3>(tpl); - iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { - ss << serialize(constraint, context) << ' '; + auto first = true; + constexpr std::array sep = {" ", ""}; + iterate_tuple(column.constraints, [&ss, &context, &first, &sep](auto& constraint) { + ss << sep[std::exchange(first, false)]; + ss << serialize(constraint, context); }); - if(isNotNull) { - ss << "NOT NULL"; + using ConstraintsTuple = decltype(column.constraints); + constexpr bool constraintsHaveNullConstraint = + mpl::invoke_t, ConstraintsTuple>::value; + constexpr bool constraintsHaveNotNullConstraint = + mpl::invoke_t, ConstraintsTuple>::value; + if(!constraintsHaveNullConstraint && !constraintsHaveNotNullConstraint) { + ss << sep[std::exchange(first, false)]; + if(isNotNull) { + ss << "NOT NULL"; + } else { + ss << "NULL"; + } } return ss; @@ -16433,6 +16465,26 @@ namespace sqlite_orm { } }; + template<> + struct statement_serializer { + using statement_type = null_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + return "NULL"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_null_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + return "NOT NULL"; + } + }; + template struct statement_serializer, void> { using statement_type = primary_key_t; @@ -16578,13 +16630,11 @@ namespace sqlite_orm { if(!context.skip_types_and_constraints) { ss << " " << type_printer>().print(); const bool columnIsNotNull = column.is_not_null(); - auto constraintsTuple = streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - columnIsNotNull, - context); - if(std::tuple_size::value > 0) { - ss << " " << constraintsTuple; - } + ss << " " + << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + columnIsNotNull, + context); } return ss.str(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 771fa518f..ff037642b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,6 +87,8 @@ add_executable(unit_tests statement_serializer_tests/column_constraints/primary_key.cpp statement_serializer_tests/column_constraints/unique.cpp statement_serializer_tests/column_constraints/check.cpp + statement_serializer_tests/column_constraints/null.cpp + statement_serializer_tests/column_constraints/not_null.cpp statement_serializer_tests/bindables.cpp statement_serializer_tests/ast/upsert_clause.cpp statement_serializer_tests/ast/excluded.cpp diff --git a/tests/statement_serializer_tests/column_constraints/not_null.cpp b/tests/statement_serializer_tests/column_constraints/not_null.cpp new file mode 100644 index 000000000..93074de43 --- /dev/null +++ b/tests/statement_serializer_tests/column_constraints/not_null.cpp @@ -0,0 +1,12 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("statement_serializer not null") { + internal::db_objects_tuple<> storage; + internal::serializer_context> context{storage}; + auto statement = not_null(); + auto value = serialize(statement, context); + REQUIRE(value == "NOT NULL"); +} diff --git a/tests/statement_serializer_tests/column_constraints/null.cpp b/tests/statement_serializer_tests/column_constraints/null.cpp new file mode 100644 index 000000000..12b67ae60 --- /dev/null +++ b/tests/statement_serializer_tests/column_constraints/null.cpp @@ -0,0 +1,12 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("statement_serializer null") { + internal::db_objects_tuple<> storage; + internal::serializer_context> context{storage}; + auto statement = null(); + auto value = serialize(statement, context); + REQUIRE(value == "NULL"); +} diff --git a/tests/statement_serializer_tests/schema/column.cpp b/tests/statement_serializer_tests/schema/column.cpp index 78eae98fd..4e27fc710 100644 --- a/tests/statement_serializer_tests/schema/column.cpp +++ b/tests/statement_serializer_tests/schema/column.cpp @@ -15,20 +15,50 @@ TEST_CASE("statement_serializer column") { std::string expected; SECTION("with types and constraints") { context.skip_types_and_constraints = false; - SECTION("id INTEGER NOT NULL") { + SECTION("id INTEGER (implicit) NOT NULL") { auto column = make_column("id", &User::id); value = serialize(column, context); expected = "\"id\" INTEGER NOT NULL"; } - SECTION("name TEXT NOT NULL") { + SECTION("id INTEGER (explicit) NOT NULL") { + auto column = make_column("id", &User::id, not_null()); + value = serialize(column, context); + expected = "\"id\" INTEGER NOT NULL"; + } + SECTION("id INTEGER (explicit) NULL") { + auto column = make_column("id", &User::id, null()); + value = serialize(column, context); + expected = "\"id\" INTEGER NULL"; + } + SECTION("name TEXT (implicit) NOT NULL") { auto column = make_column("name", &User::name); value = serialize(column, context); expected = "\"name\" TEXT NOT NULL"; } - SECTION("nullable text") { + SECTION("name TEXT (explicit) NOT NULL") { + auto column = make_column("name", &User::name, not_null()); + value = serialize(column, context); + expected = "\"name\" TEXT NOT NULL"; + } + SECTION("name TEXT (explicit) NULL") { + auto column = make_column("name", &User::name, null()); + value = serialize(column, context); + expected = "\"name\" TEXT NULL"; + } + SECTION("nullable text (implicit) NULL") { auto column = make_column("nullable_text", &User::nullableText); value = serialize(column, context); - expected = "\"nullable_text\" TEXT "; + expected = "\"nullable_text\" TEXT NULL"; + } + SECTION("nullable text (explicit) NOT NULL") { + auto column = make_column("nullable_text", &User::nullableText, not_null()); + value = serialize(column, context); + expected = "\"nullable_text\" TEXT NOT NULL"; + } + SECTION("nullable text (explicit) NULL") { + auto column = make_column("nullable_text", &User::nullableText, null()); + value = serialize(column, context); + expected = "\"nullable_text\" TEXT NULL"; } } SECTION("without types and constraints") {