diff --git a/.clang-format b/.clang-format index f4bc58fcd..b5b9729f9 100644 --- a/.clang-format +++ b/.clang-format @@ -114,12 +114,12 @@ SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 +AttributeMacros: [SQLITE_ORM_CPP_LIKELY, SQLITE_ORM_CPP_UNLIKELY] StatementMacros: - __pragma - _Pragma - Q_UNUSED - QT_REQUIRE_VERSION -TabWidth: 8 +TabWidth: 4 UseTab: Never ... - diff --git a/.github/workflows/clang-format-lint.yaml b/.github/workflows/clang-format-lint.yaml index 3916bbbff..49fdc4db4 100644 --- a/.github/workflows/clang-format-lint.yaml +++ b/.github/workflows/clang-format-lint.yaml @@ -9,6 +9,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: clang-format lint - uses: DoozyX/clang-format-lint-action@v0.15 + uses: DoozyX/clang-format-lint-action@v0.16.2 with: - clangFormatVersion: 15 + clangFormatVersion: 16 diff --git a/.gitignore b/.gitignore index f5cff1a34..1876c6a58 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ cmake-build-debug/ .idea/ /compile +build diff --git a/README.md b/README.md index 9fbe23488..850aeb62a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ SQLite ORM light header only library for modern C++. Please read the license pre * **No raw string queries** * **Intuitive syntax** * **Comfortable interface - one code line per single query** -* **Built with modern C++14 features (no macros and external scripts)** +* **Built with modern C++14/C++17/C++20 features (no macros and external scripts)** * **CRUD support** * **Pure select query support** * **Prepared statements support** @@ -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). @@ -686,7 +686,7 @@ storage.transaction([&] () mutable { // mutable keyword allows make non-cons The second way guarantees that `commit` or `rollback` will be called. You can use either way. -Trancations are useful with `changes` sqlite function that returns number of rows modified. +Transactions are useful with `changes` sqlite function that returns number of rows modified. ```c++ storage.transaction([&] () mutable { diff --git a/appveyor.yml b/appveyor.yml index 55bed4074..ffc1cc5f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,7 +46,8 @@ environment: CC: clang CXX: clang++ SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON" - cmake_build_parallel: --parallel + # clang was stuck with a parallel build + cmake_build_parallel: "" - job_name: gcc, C++17 appveyor_build_worker_image: Ubuntu @@ -107,12 +108,12 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2023.01.09 + git fetch --tags && git checkout 2024.06.15 cd %APPVEYOR_BUILD_FOLDER% C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install set VCPKG_DEFAULT_TRIPLET=%platform%-windows - vcpkg install sqlite3 + vcpkg install sqlite3[core,dbstat,math,json1,fts5,soundex] rem The Visual Studio 2017 build worker image comes with CMake 3.16 only, and sqlite_orm will build the Catch2 dependency from source if not "%appveyor_build_worker_image%"=="Visual Studio 2017" (vcpkg install catch2) before_build: @@ -140,11 +141,11 @@ for: install: - |- pushd $HOME/vcpkg - git fetch --tags && git checkout 2023.01.09 + git fetch --tags && git checkout 2024.06.15 popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets - vcpkg install sqlite3 catch2 --overlay-triplets=vcpkg/triplets + vcpkg install sqlite3[core,dbstat,math,json1,fts5,soundex] catch2 --overlay-triplets=vcpkg/triplets before_build: - |- mkdir compile @@ -168,10 +169,10 @@ for: # using custom vcpkg triplets for building and linking dynamic dependent libraries install: - |- - git clone --depth 1 --branch 2023.01.09 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2024.06.15 https://github.com/microsoft/vcpkg.git $HOME/vcpkg $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets - vcpkg install sqlite3 catch2 --overlay-triplets=vcpkg/triplets + vcpkg install sqlite3[core,dbstat,math,json1,fts5,soundex] catch2 --overlay-triplets=vcpkg/triplets before_build: - |- mkdir compile diff --git a/dev/alias.h b/dev/alias.h index eb5aad0b5..822b41be5 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -1,19 +1,29 @@ #pragma once -#include // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const -#include // std::index_sequence, std::make_index_sequence +#include // std::enable_if, std::is_same, std::conditional +#include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream -#include // std::copy_n +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#endif -#include "functional/cxx_universal.h" // ::size_t #include "functional/cxx_type_traits_polyfill.h" +#include "functional/mpl/conditional.h" +#include "functional/cstring_literal.h" #include "type_traits.h" #include "alias_traits.h" +#include "table_type_of.h" +#include "tags.h" +#include "column_pointer.h" namespace sqlite_orm { namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + inline constexpr bool is_operator_argument_v>> = true; +#endif /** * This is a common built-in class used for character based table aliases. @@ -40,8 +50,19 @@ namespace sqlite_orm { column_type column; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = + true; + + struct basic_table; + /* * Encapsulates extracting the alias identifier of a non-alias. + * + * `extract()` always returns the empty string. + * `as_alias()` is used in contexts where a table might be aliased, and the empty string is returned. + * `as_qualifier()` is used in contexts where a table might be aliased, and the given table's name is returned. */ template struct alias_extractor { @@ -52,13 +73,19 @@ namespace sqlite_orm { static std::string as_alias() { return {}; } + + template + static const std::string& as_qualifier(const X& table) { + return table.name; + } }; /* * Encapsulates extracting the alias identifier of an alias. * - * `extract()` always returns the alias identifier. - * `as_alias()` is used in contexts where a table is aliased. + * `extract()` always returns the alias identifier or CTE moniker. + * `as_alias()` is used in contexts where a recordset is aliased, and the alias identifier is returned. + * `as_qualifier()` is used in contexts where a table is aliased, and the alias identifier is returned. */ template struct alias_extractor> { @@ -73,6 +100,20 @@ namespace sqlite_orm { static std::string as_alias() { return alias_extractor::extract(); } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // for CTE monikers -> empty + template, A> = true> + static std::string as_alias() { + return {}; + } +#endif + + // for regular table aliases -> alias identifier + template = true> + static std::string as_qualifier(const basic_table&) { + return alias_extractor::extract(); + } }; /** @@ -87,7 +128,7 @@ namespace sqlite_orm { }; /** - * This is a common built-in class used for custom single-character column aliases. + * Built-in column alias. * For convenience there exist type aliases `colalias_a`, `colalias_b`, ... * The easiest way to create a column alias is using `"xyz"_col`. */ @@ -103,20 +144,190 @@ namespace sqlite_orm { using type = T; alias_holder() = default; + // CTE feature needs it to implicitly convert a column alias to an alias_holder; see `cte()` factory function + alias_holder(const T&) noexcept {} + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct recordset_alias_builder { + template + [[nodiscard]] consteval recordset_alias for_() const { + return {}; + } + + template + [[nodiscard]] consteval auto for_() const { + using T = std::remove_const_t; + return recordset_alias{}; + } }; +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + SQLITE_ORM_CONSTEVAL auto n_to_colalias() { + constexpr column_alias<'1' + n % 10, C...> colalias{}; + if constexpr(n > 10) { + return n_to_colalias(); + } else { + return colalias; + } + } + + template + inline constexpr bool is_builtin_numeric_column_alias_v = false; + template + inline constexpr bool is_builtin_numeric_column_alias_v> = ((C >= '0' && C <= '9') && ...); +#endif + } + + /** + * Using a column pointer, create a column reference to an aliased table column. + * + * Example: + * using als = alias_u; + * select(alias_column(column(&User::id))) + */ + template, + polyfill::negation>>>::value, + bool> = true> + constexpr auto alias_column(C field) { + using namespace ::sqlite_orm::internal; + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + return alias_column_t{std::move(field)}; + } + + /** + * Using an object member field, create a column reference to an aliased table column. + * + * @note The object member pointer can be from a derived class without explicitly forming a column pointer. + * + * Example: + * using als = alias_u; + * select(alias_column(&User::id)) + */ + template, + polyfill::negation>>>::value, + bool> = true> + constexpr auto alias_column(F O::*field) { + using namespace ::sqlite_orm::internal; + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + using C1 = + mpl::conditional_t::value, F O::*, column_pointer>; + return alias_column_t{C1{field}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a column reference to an aliased table column. + * + * @note An object member pointer can be from a derived class without explicitly forming a column pointer. + * + * Example: + * constexpr orm_table_alias auto als = "u"_alias.for_(); + * select(alias_column(&User::id)) + */ + template + requires(!orm_cte_moniker>) + constexpr auto alias_column(C field) { + using namespace ::sqlite_orm::internal; + using A = decltype(als); + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + if constexpr(is_column_pointer_v) { + return alias_column_t{std::move(field)}; + } else if constexpr(std::is_same_v, aliased_type>) { + return alias_column_t{field}; + } else { + // wrap in column_pointer + using C1 = column_pointer; + return alias_column_t{{field}}; + } + } + + /** + * Create a column reference to an aliased table column. + * + * @note An object member pointer can be from a derived class without explicitly forming a column pointer. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + * + * Example: + * constexpr auto als = "u"_alias.for_(); + * select(als->*&User::id) + */ + template + requires(!orm_cte_moniker>) + constexpr auto operator->*(const A& /*tableAlias*/, F field) { + return alias_column(std::move(field)); + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Create a column reference to an aliased CTE column. + */ + template, internal::is_cte_moniker>>, + bool> = true> + constexpr auto alias_column(C c) { + using namespace internal; + using cte_moniker_t = type_t; + + if constexpr(is_column_pointer_v) { + static_assert(std::is_same, cte_moniker_t>::value, + "Column pointer must match aliased CTE"); + return alias_column_t{c}; + } else { + auto cp = column(c); + return alias_column_t{std::move(cp)}; + } + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a column reference to an aliased CTE column. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + */ + template + requires(orm_cte_moniker>) + constexpr auto operator->*(const A& /*tableAlias*/, C c) { + return alias_column(std::move(c)); } /** - * @return column with table alias attached. Place it instead of a column statement in case you need to specify a - * column with table alias prefix like 'a.column'. + * Create a column reference to an aliased CTE column. */ - template, bool> = true> - internal::alias_column_t alias_column(C c) { - using aliased_type = internal::type_t; - static_assert(std::is_same, aliased_type>::value, - "Column must be from aliased table"); - return {c}; + template + requires(orm_cte_moniker>) + constexpr auto alias_column(C c) { + using A = std::remove_const_t; + return alias_column(std::move(c)); } +#endif +#endif /** * Alias a column expression. @@ -126,11 +337,48 @@ namespace sqlite_orm { return {std::move(expression)}; } - template = true> - internal::alias_holder get() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Alias a column expression. + */ + template + auto as(E expression) { + return internal::as_t{std::move(expression)}; + } + + /** + * Alias a column expression. + */ + template + internal::as_t operator>>=(E expression, const A&) { + return {std::move(expression)}; + } +#else + /** + * Alias a column expression. + */ + template = true> + internal::as_t operator>>=(E expression, const A&) { + return {std::move(expression)}; + } +#endif + + /** + * Wrap a column alias in an alias holder. + */ + template + internal::alias_holder get() { + static_assert(internal::is_column_alias_v, ""); return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto get() { + return internal::alias_holder{}; + } +#endif + template using alias_a = internal::recordset_alias; template @@ -193,4 +441,52 @@ namespace sqlite_orm { using colalias_g = internal::column_alias<'g'>; using colalias_h = internal::column_alias<'h'>; using colalias_i = internal::column_alias<'i'>; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Create a table alias. + * + * Examples: + * constexpr orm_table_alias auto z_alias = alias<'z'>.for_(); + */ + template + inline constexpr internal::recordset_alias_builder alias{}; + + inline namespace literals { + /** @short Create a table alias. + * + * Examples: + * constexpr orm_table_alias auto z_alias = "z"_alias.for_(); + */ + template + [[nodiscard]] consteval auto operator"" _alias() { + return internal::explode_into( + std::make_index_sequence{}); + } + + /** @short Create a column alias. + * column_alias<'a'[, ...]> from a string literal. + * E.g. "a"_col, "b"_col + */ + template + [[nodiscard]] consteval auto operator"" _col() { + return internal::explode_into(std::make_index_sequence{}); + } + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + inline namespace literals { + /** + * column_alias<'1'[, ...]> from a numeric literal. + * E.g. 1_colalias, 2_colalias + */ + template + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _colalias() { + // numeric identifiers are used for automatically assigning implicit aliases to unaliased column expressions, + // which start at "1". + static_assert(std::array{Chars...}[0] > '0'); + return internal::column_alias{}; + } + } +#endif } diff --git a/dev/alias_traits.h b/dev/alias_traits.h index fae7966ca..5d64de530 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -1,51 +1,125 @@ -#pragma once - -#include // std::remove_const, std::is_base_of, std::is_same - -#include "functional/cxx_universal.h" -#include "functional/cxx_type_traits_polyfill.h" -#include "type_traits.h" - -namespace sqlite_orm { - - /** @short Base class for a custom table alias, column alias or expression alias. - */ - struct alias_tag {}; - - namespace internal { - - template - SQLITE_ORM_INLINE_VAR constexpr bool is_alias_v = std::is_base_of::value; - - template - using is_alias = polyfill::bool_constant>; - - /** @short Alias of a column in a record set, see `orm_column_alias`. - */ - template - SQLITE_ORM_INLINE_VAR constexpr bool is_column_alias_v = - polyfill::conjunction_v, polyfill::negation>>; - - template - using is_column_alias = is_alias; - - /** @short Alias of any type of record set, see `orm_recordset_alias`. - */ - template - SQLITE_ORM_INLINE_VAR constexpr bool is_recordset_alias_v = - polyfill::conjunction_v, polyfill::is_detected>; - - template - using is_recordset_alias = polyfill::bool_constant>; - - /** @short Alias of a concrete table, see `orm_table_alias`. - */ - template - SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction_v< - is_recordset_alias, - polyfill::negation, std::remove_const_t>>>; - - template - using is_table_alias = polyfill::bool_constant>; - } -} +#pragma once + +#include // std::is_base_of, std::is_same +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif + +#include "functional/cxx_universal.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "type_traits.h" +#include "table_reference.h" + +namespace sqlite_orm { + + /** @short Base class for a custom table alias, column alias or expression alias. + */ + struct alias_tag {}; + + namespace internal { + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_alias_v = std::is_base_of::value; + + template + struct is_alias : polyfill::bool_constant> {}; + + /** @short Alias of a column in a record set, see `orm_column_alias`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_column_alias_v = + polyfill::conjunction, polyfill::negation>>::value; + + template + struct is_column_alias : is_alias {}; + + /** @short Alias of any type of record set, see `orm_recordset_alias`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_recordset_alias_v = + polyfill::conjunction, polyfill::is_detected>::value; + + template + struct is_recordset_alias : polyfill::bool_constant> {}; + + /** @short Alias of a concrete table, see `orm_table_alias`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction< + is_recordset_alias, + polyfill::negation, std::remove_const_t>>>::value; + + template + struct is_table_alias : polyfill::bool_constant> {}; + + /** @short Moniker of a CTE, see `orm_cte_moniker`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_cte_moniker_v = +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + polyfill::conjunction_v, + std::is_same, std::remove_const_t>>; +#else + false; +#endif + + template + using is_cte_moniker = polyfill::bool_constant>; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept orm_alias = std::derived_from; + + /** @short Specifies that a type is an alias of a column in a record set. + * + * A column alias has the following traits: + * - is derived from `alias_tag` + * - must not have a nested `type` typename + */ + template + concept orm_column_alias = (orm_alias && !orm_names_type); + + /** @short Specifies that a type is an alias of any type of record set. + * + * A record set alias has the following traits: + * - is derived from `alias_tag`. + * - has a nested `type` typename, which refers to a mapped object. + */ + template + concept orm_recordset_alias = (orm_alias && orm_names_type); + + /** @short Specifies that a type is an alias of a concrete table. + * + * A concrete table alias has the following traits: + * - is derived from `alias_tag`. + * - has a `type` typename, which refers to another mapped object (i.e. doesn't refer to itself). + */ + template + concept orm_table_alias = (orm_recordset_alias && !std::same_as>); + + /** @short Moniker of a CTE. + * + * A CTE moniker has the following traits: + * - is derived from `alias_tag`. + * - has a `type` typename, which refers to itself. + */ + template + concept orm_cte_moniker = (orm_recordset_alias && std::same_as>); + + /** @short Specifies that a type refers to a mapped table (possibly aliased). + */ + template + concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + + /** @short Specifies that a type refers to a recordset. + */ + template + concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + + /** @short Specifies that a type is a mapped recordset (table reference). + */ + template + concept orm_mapped_recordset = (orm_table_reference || orm_cte_moniker); +#endif +} diff --git a/dev/arg_values.h b/dev/arg_values.h index 9060460b0..515a1b12f 100644 --- a/dev/arg_values.h +++ b/dev/arg_values.h @@ -6,6 +6,8 @@ namespace sqlite_orm { + /** @short Wrapper around a dynamically typed value object. + */ struct arg_value { arg_value() : arg_value(nullptr) {} @@ -14,7 +16,8 @@ namespace sqlite_orm { template T get() const { - return row_extractor().extract(this->value); + const auto rowExtractor = internal::boxed_value_extractor(); + return rowExtractor.extract(this->value); } bool is_null() const { diff --git a/dev/arithmetic_tag.h b/dev/arithmetic_tag.h index 86a714c64..26570fff6 100644 --- a/dev/arithmetic_tag.h +++ b/dev/arithmetic_tag.h @@ -1,5 +1,7 @@ #pragma once -#include +#include // std::is_integral + +#include "functional/mpl/conditional.h" namespace sqlite_orm { @@ -12,9 +14,9 @@ namespace sqlite_orm { template using arithmetic_tag_t = - std::conditional_t::value, + mpl::conditional_t::value, // Integer class - std::conditional_t, + mpl::conditional_t, // Floating-point class real_tag>; } diff --git a/dev/ast/group_by.h b/dev/ast/group_by.h index 0ad6bd31a..966dc1550 100644 --- a/dev/ast/group_by.h +++ b/dev/ast/group_by.h @@ -36,20 +36,6 @@ namespace sqlite_orm { template using is_group_by = polyfill::disjunction, polyfill::is_specialization_of>; - - /** - * HAVING holder. - * T is having argument type. - */ - template - struct having_t { - using expression_type = T; - - expression_type expression; - }; - - template - using is_having = polyfill::is_specialization_of; } /** @@ -57,18 +43,7 @@ namespace sqlite_orm { * Example: storage.get_all(group_by(&Employee::name)) */ template - internal::group_by_t group_by(Args&&... args) { - return {std::make_tuple(std::forward(args)...)}; - } - - /** - * [Deprecation notice]: this function is deprecated and will be removed in v1.9. Please use `group_by(...).having(...)` instead. - * - * HAVING(expression). - * Example: storage.get_all(group_by(&Employee::name), having(greater_than(count(&Employee::name), 2))); - */ - template - [[deprecated("Use group_by(...).having(...) instead")]] internal::having_t having(T expression) { - return {std::move(expression)}; + internal::group_by_t group_by(Args... args) { + return {{std::forward(args)...}}; } } diff --git a/dev/ast/match.h b/dev/ast/match.h new file mode 100644 index 000000000..5500e2820 --- /dev/null +++ b/dev/ast/match.h @@ -0,0 +1,21 @@ +#pragma once + +namespace sqlite_orm { + namespace internal { + + template + struct match_t { + using mapped_type = T; + using argument_type = X; + + argument_type argument; + + match_t(argument_type argument) : argument(std::move(argument)) {} + }; + } + + template + internal::match_t match(X argument) { + return {std::move(argument)}; + } +} diff --git a/dev/ast/rank.h b/dev/ast/rank.h new file mode 100644 index 000000000..e71ec8723 --- /dev/null +++ b/dev/ast/rank.h @@ -0,0 +1,11 @@ +#pragma once + +namespace sqlite_orm { + namespace internal { + struct rank_t {}; + } + + inline internal::rank_t rank() { + return {}; + } +} diff --git a/dev/ast/set.h b/dev/ast/set.h index 0146acbc4..e5e66f2ea 100644 --- a/dev/ast/set.h +++ b/dev/ast/set.h @@ -6,6 +6,7 @@ #include // std::stringstream #include // std::false_type, std::true_type +#include "../tuple_helper/tuple_traits.h" #include "../table_name_collector.h" namespace sqlite_orm { diff --git a/dev/ast/special_keywords.h b/dev/ast/special_keywords.h new file mode 100644 index 000000000..f553d123b --- /dev/null +++ b/dev/ast/special_keywords.h @@ -0,0 +1,21 @@ +#pragma once + +namespace sqlite_orm { + namespace internal { + struct current_time_t {}; + struct current_date_t {}; + struct current_timestamp_t {}; + } + + inline internal::current_time_t current_time() { + return {}; + } + + inline internal::current_date_t current_date() { + return {}; + } + + inline internal::current_timestamp_t current_timestamp() { + return {}; + } +} \ No newline at end of file diff --git a/dev/ast/upsert_clause.h b/dev/ast/upsert_clause.h index 3a3b26948..287abaace 100644 --- a/dev/ast/upsert_clause.h +++ b/dev/ast/upsert_clause.h @@ -38,13 +38,18 @@ namespace sqlite_orm { actions_tuple actions; }; +#endif template - using is_upsert_clause = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_upsert_clause_v = +#if SQLITE_VERSION_NUMBER >= 3024000 + polyfill::is_specialization_of::value; #else - template - struct is_upsert_clause : polyfill::bool_constant {}; + false; #endif + + template + using is_upsert_clause = polyfill::bool_constant>; } #if SQLITE_VERSION_NUMBER >= 3024000 @@ -62,7 +67,7 @@ namespace sqlite_orm { */ template internal::conflict_target on_conflict(Args... args) { - return {std::tuple(std::forward(args)...)}; + return {{std::forward(args)...}}; } #endif } diff --git a/dev/ast/where.h b/dev/ast/where.h index 93e1572a7..29ca50021 100644 --- a/dev/ast/where.h +++ b/dev/ast/where.h @@ -31,10 +31,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_where_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_where_v = polyfill::is_specialization_of::value; template - using is_where = polyfill::bool_constant>; + struct is_where : polyfill::bool_constant> {}; } /** diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index 5dcc9bc5b..7e166566e 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -20,6 +20,7 @@ #include "ast/group_by.h" #include "ast/exists.h" #include "ast/set.h" +#include "ast/match.h" namespace sqlite_orm { @@ -85,6 +86,16 @@ namespace sqlite_orm { } }; + template + struct ast_iterator, void> { + using node_type = match_t; + + template + void operator()(const node_type& node, L& lambda) const { + iterate_ast(node.argument, lambda); + } + }; + template struct ast_iterator, void> { using node_type = group_by_t; @@ -95,6 +106,19 @@ namespace sqlite_orm { } }; + template + struct ast_iterator, void> { + using node_type = highlight_t; + + template + void operator()(const node_type& expression, L& lambda) const { + lambda(expression); + iterate_ast(expression.argument0, lambda); + iterate_ast(expression.argument1, lambda); + iterate_ast(expression.argument2, lambda); + } + }; + template struct ast_iterator, void> { using node_type = excluded_t; @@ -126,30 +150,31 @@ namespace sqlite_orm { }; template - struct ast_iterator> { + struct ast_iterator< + T, + std::enable_if_t, is_binary_operator>::value>> { using node_type = T; template - void operator()(const node_type& binaryCondition, L& lambda) const { - iterate_ast(binaryCondition.l, lambda); - iterate_ast(binaryCondition.r, lambda); + void operator()(const node_type& node, L& lambda) const { + iterate_ast(node.lhs, lambda); + iterate_ast(node.rhs, lambda); } }; - template - struct ast_iterator, void> { - using node_type = binary_operator; + template + struct ast_iterator, void> { + using node_type = is_equal_with_table_t; template - void operator()(const node_type& binaryOperator, C& lambda) const { - iterate_ast(binaryOperator.lhs, lambda); - iterate_ast(binaryOperator.rhs, lambda); + void operator()(const node_type& node, C& lambda) const { + iterate_ast(node.rhs, lambda); } }; - template - struct ast_iterator, void> { - using node_type = columns_t; + template + struct ast_iterator, is_struct>::value>> { + using node_type = C; template void operator()(const node_type& cols, L& lambda) const { @@ -201,14 +226,36 @@ namespace sqlite_orm { } }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct ast_iterator> { + using node_type = CTE; + + template + void operator()(const node_type& c, L& lambda) const { + iterate_ast(c.subselect, lambda); + } + }; + + template + struct ast_iterator> { + using node_type = With; + + template + void operator()(const node_type& c, L& lambda) const { + iterate_ast(c.cte, lambda); + iterate_ast(c.expression, lambda); + } + }; +#endif + template struct ast_iterator> { using node_type = T; template void operator()(const node_type& c, L& lambda) const { - iterate_ast(c.left, lambda); - iterate_ast(c.right, lambda); + iterate_ast(c.compound, lambda); } }; @@ -349,16 +396,6 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = having_t; - - template - void operator()(const node_type& node, L& lambda) const { - iterate_ast(node.expression, lambda); - } - }; - template struct ast_iterator, void> { using node_type = cast_t; @@ -456,13 +493,13 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = function_call; + template + struct ast_iterator, void> { + using node_type = function_call; template void operator()(const node_type& f, L& lambda) const { - iterate_ast(f.args, lambda); + iterate_ast(f.callArgs, lambda); } }; @@ -520,7 +557,7 @@ namespace sqlite_orm { // note: not strictly necessary as there's no binding support for USING; // we provide it nevertheless, in line with on_t. template - struct ast_iterator>> { + struct ast_iterator::value>> { using node_type = T; template @@ -649,9 +686,9 @@ namespace sqlite_orm { */ template struct ast_iterator, - polyfill::is_specialization_of, - is_column_alias>>> { + std::enable_if_t, + polyfill::is_specialization_of, + is_column_alias>::value>> { using node_type = T; template diff --git a/dev/carray.h b/dev/carray.h index 4e7188792..f0c7ab73a 100644 --- a/dev/carray.h +++ b/dev/carray.h @@ -6,24 +6,80 @@ * Hence we make it only available for compilers supporting inline variables. */ +#if SQLITE_VERSION_NUMBER >= 3020000 #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED -#include // std::integral_constant #include // std::move +#ifndef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::integral_constant +#endif +#endif +#endif -#include "functional/cxx_universal.h" #include "pointer_value.h" +#if SQLITE_VERSION_NUMBER >= 3020000 +#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED namespace sqlite_orm { - inline constexpr const char carray_pvt_name[] = "carray"; - using carray_pvt = std::integral_constant; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + inline constexpr orm_pointer_type auto carray_pointer_tag = "carray"_pointer_type; + // [Deprecation notice] This type is deprecated and will be removed in v1.10. Use the inline variable `carray_pointer_tag` instead. + using carray_pvt [[deprecated]] = decltype("carray"_pointer_type); + + template + using carray_pointer_arg = pointer_arg_t; + template + using carray_pointer_binding = pointer_binding_t; + template + using static_carray_pointer_binding = static_pointer_binding_t; + + /** + * Wrap a pointer of type 'carray' and its deleter function for binding it to a statement. + * + * Unless the deleter yields a nullptr 'xDestroy' function the ownership of the pointed-to-object + * is transferred to the pointer binding, which will delete it through + * the deleter when the statement finishes. + */ + template + carray_pointer_binding bind_carray_pointer(P* p, D d) noexcept { + return bind_pointer(p, std::move(d)); + } + + template + static_carray_pointer_binding

bind_carray_pointer_statically(P* p) noexcept { + return bind_pointer_statically(p); + } + + /** + * Wrap a pointer of type 'carray' for binding it to a statement. + * + * Note: 'Static' means that ownership of the pointed-to-object won't be transferred + * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. + */ + template + [[deprecated("Use the better named function `bind_carray_pointer(...)`")]] carray_pointer_binding + bindable_carray_pointer(P* p, D d) noexcept { + return bind_pointer(p, std::move(d)); + } + + template + [[deprecated( + "Use the better named function `bind_carray_pointer_statically(...)` ")]] static_carray_pointer_binding

+ statically_bindable_carray_pointer(P* p) noexcept { + return bind_pointer_statically(p); + } +#else + inline constexpr const char carray_pointer_name[] = "carray"; + using carray_pointer_type = std::integral_constant; + // [Deprecation notice] This type is deprecated and will be removed in v1.10. Use the alias type `carray_pointer_type` instead. + using carray_pvt [[deprecated]] = carray_pointer_type; template - using carray_pointer_arg = pointer_arg; + using carray_pointer_arg = pointer_arg; template - using carray_pointer_binding = pointer_binding; + using carray_pointer_binding = pointer_binding; template - using static_carray_pointer_binding = static_pointer_binding; + using static_carray_pointer_binding = static_pointer_binding; /** * Wrap a pointer of type 'carray' and its deleter function for binding it to a statement. @@ -33,8 +89,8 @@ namespace sqlite_orm { * the deleter when the statement finishes. */ template - auto bindable_carray_pointer(P* p, D d) noexcept -> pointer_binding { - return bindable_pointer(p, std::move(d)); + carray_pointer_binding bind_carray_pointer(P* p, D d) noexcept { + return bind_pointer(p, std::move(d)); } /** @@ -44,12 +100,26 @@ namespace sqlite_orm { * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. */ template - auto statically_bindable_carray_pointer(P* p) noexcept -> static_pointer_binding { - return statically_bindable_pointer(p); + static_carray_pointer_binding

bind_carray_pointer_statically(P* p) noexcept { + return bind_pointer_statically(p); } + template + [[deprecated("Use the better named function `bind_carray_pointer(...)`")]] carray_pointer_binding + bindable_carray_pointer(P* p, D d) noexcept { + return bind_carray_pointer(p, std::move(d)); + } + + template + [[deprecated( + "Use the better named function `bind_carray_pointer_statically(...)` ")]] static_carray_pointer_binding

+ statically_bindable_carray_pointer(P* p) noexcept { + return bind_carray_pointer_statically(p); + } +#endif + /** - * Generalized form of the 'remember' SQL function that is a pass-through for values + * Base for a generalized form of the 'remember' SQL function that is a pass-through for values * (it returns its argument unchanged using move semantics) but also saves the * value that is passed through into a bound variable. */ @@ -61,10 +131,6 @@ namespace sqlite_orm { } return std::move(value); } - - static constexpr const char* name() { - return "note_value"; - } }; /** @@ -77,3 +143,4 @@ namespace sqlite_orm { }; } #endif +#endif diff --git a/dev/column_expression.h b/dev/column_expression.h new file mode 100644 index 000000000..e0d099257 --- /dev/null +++ b/dev/column_expression.h @@ -0,0 +1,108 @@ +#pragma once + +#include // std::enable_if, std::is_same, std::decay, std::is_arithmetic +#include // std::tuple +#include // std::reference_wrapper + +#include "functional/cxx_type_traits_polyfill.h" +#include "tuple_helper/tuple_transformer.h" +#include "type_traits.h" +#include "select_constraints.h" +#include "alias.h" +#include "storage_traits.h" + +namespace sqlite_orm { + + namespace internal { + + template + struct column_expression_type; + + /** + * Obains the expressions that form the columns of a subselect statement. + */ + template + using column_expression_of_t = typename column_expression_type::type; + + /** + * Identity. + */ + template + struct column_expression_type { + using type = E; + }; + + /** + * Resolve column alias. + * as_t -> as_t + */ + template + struct column_expression_type> { + using type = as_t, column_expression_of_t>>; + }; + + /** + * Resolve reference wrapper. + * reference_wrapper -> T& + */ + template + struct column_expression_type, void> + : std::add_lvalue_reference> {}; + + // No CTE for object expression. + template + struct column_expression_type, void> { + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + }; + + /** + * Resolve all columns of a mapped object or CTE. + * asterisk_t -> tuple + */ + template + struct column_expression_type, + std::enable_if_t>, + is_cte_moniker>::value>> + : storage_traits::storage_mapped_column_expressions {}; + + template + struct add_column_alias { + template + using apply_t = alias_column_t; + }; + /** + * Resolve all columns of an aliased object. + * asterisk_t -> tuple...> + */ + template + struct column_expression_type, match_if> + : tuple_transformer>::type, + add_column_alias::template apply_t> {}; + + /** + * Resolve multiple columns. + * columns_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + + /** + * Resolve multiple columns. + * struct_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + + /** + * Resolve column(s) of subselect. + * select_t -> ColExpr, tuple + */ + template + struct column_expression_type> : column_expression_type {}; + } +} diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index b35a4e359..1792dad73 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -21,8 +21,8 @@ namespace sqlite_orm { namespace internal { - template - std::string serialize(const T&, const serializer_context&); + template + auto serialize(const T& t, const C& context); template std::vector& collect_table_column_names(std::vector& collectedExpressions, @@ -30,11 +30,11 @@ namespace sqlite_orm { const Ctx& context) { if(definedOrder) { auto& table = pick_table>(context.db_objects); - collectedExpressions.reserve(collectedExpressions.size() + table.count_columns_amount()); + collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); table.for_each_column([qualified = !context.skip_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { - if(is_alias_v) { + if(is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + "." + quote_identifier(column.name)); } else if(qualified) { @@ -46,7 +46,7 @@ namespace sqlite_orm { }); } else { collectedExpressions.reserve(collectedExpressions.size() + 1); - if(is_alias_v) { + if(is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); } else if(!context.skip_table_name) { const basic_table& table = pick_table>(context.db_objects); @@ -71,9 +71,9 @@ namespace sqlite_orm { if(columnExpression.empty()) { throw std::system_error{orm_error_code::column_not_found}; } - collectedExpressions.reserve(collectedExpressions.size() + 1); - collectedExpressions.push_back(std::move(columnExpression)); - return collectedExpressions; + this->collectedExpressions.reserve(this->collectedExpressions.size() + 1); + this->collectedExpressions.push_back(std::move(columnExpression)); + return this->collectedExpressions; } template @@ -83,27 +83,40 @@ namespace sqlite_orm { template std::vector& operator()(const asterisk_t& expression, const Ctx& context) { - return collect_table_column_names(collectedExpressions, expression.defined_order, context); + return collect_table_column_names(this->collectedExpressions, expression.defined_order, context); } template std::vector& operator()(const object_t& expression, const Ctx& context) { - return collect_table_column_names(collectedExpressions, expression.defined_order, context); + return collect_table_column_names(this->collectedExpressions, expression.defined_order, context); } template std::vector& operator()(const columns_t& cols, const Ctx& context) { - collectedExpressions.reserve(collectedExpressions.size() + cols.count); + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); + iterate_tuple(cols.columns, [this, &context](auto& colExpr) { + (*this)(colExpr, context); + }); + // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); + } + return this->collectedExpressions; + } + + template + std::vector& operator()(const struct_t& cols, const Ctx& context) { + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); iterate_tuple(cols.columns, [this, &context](auto& colExpr) { (*this)(colExpr, context); }); // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order - if(mpl::instantiate, - typename columns_t::columns_type>::value && - collectedExpressions.capacity() > collectedExpressions.size()) { - collectedExpressions.shrink_to_fit(); + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); } - return collectedExpressions; + return this->collectedExpressions; } std::vector collectedExpressions; diff --git a/dev/column_pointer.h b/dev/column_pointer.h index 1fd06e7c5..c2b6b75bd 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -1,62 +1,162 @@ -#pragma once - -#include // std::string -#include // std::move - -#include "functional/cxx_core_features.h" -#include "conditions.h" -#include "alias_traits.h" - -namespace sqlite_orm { - namespace internal { - /** - * This class is used to store explicit mapped type T and its column descriptor (member pointer/getter/setter). - * Is useful when mapped type is derived from other type and base class has members mapped to a storage. - */ - template - struct column_pointer { - using self = column_pointer; - using type = T; - using field_type = F; - - field_type field; - - template - is_equal_t operator==(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - is_not_equal_t operator!=(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - lesser_than_t operator<(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - lesser_or_equal_t operator<=(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - greater_than_t operator>(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - greater_or_equal_t operator>=(R rhs) const { - return {*this, std::move(rhs)}; - } - }; - - template - SQLITE_ORM_INLINE_VAR constexpr bool is_column_pointer_v = polyfill::is_specialization_of_v; - - template - using is_column_pointer = polyfill::bool_constant>; - - } -} +#pragma once + +#include // std::enable_if, std::is_convertible +#include // std::move + +#include "functional/cxx_core_features.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "type_traits.h" +#include "table_reference.h" +#include "alias_traits.h" +#include "tags.h" + +namespace sqlite_orm { + namespace internal { + /** + * This class is used to store explicit mapped type T and its column descriptor (member pointer/getter/setter). + * Is useful when mapped type is derived from other type and base class has members mapped to a storage. + */ + template + struct column_pointer { + using type = T; + using field_type = F; + + field_type field; + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_column_pointer_v = + polyfill::is_specialization_of::value; + + template + struct is_column_pointer : polyfill::bool_constant> {}; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_operator_argument_v::value>> = + true; + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct alias_holder; +#endif + } + + /** + * Explicitly refer to a column, used in contexts + * where the automatic object mapping deduction needs to be overridden. + * + * Example: + * struct BaseType : { int64 id; }; + * struct MyType : BaseType { ... }; + * storage.select(column(&BaseType::id)); + */ + template = true> + constexpr internal::column_pointer column(F Base::*field) { + static_assert(std::is_convertible::value, "Field must be from derived class"); + return {field}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column. + */ + template + constexpr auto column(F O::*field) { + return column>(field); + } + + // Intentionally place pointer-to-member operator for table references in the internal namespace + // to facilitate ADL (Argument Dependent Lookup) + namespace internal { + /** + * Explicitly refer to a column. + */ + template + constexpr auto operator->*(const R& /*table*/, F O::*field) { + return column(field); + } + } + + /** + * Make a table reference. + */ + template + requires(!orm_recordset_alias) + consteval internal::table_reference column() { + return {}; + } + + /** + * Make a table reference. + */ + template + requires(!orm_recordset_alias) + consteval internal::table_reference c() { + return {}; + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Explicitly refer to a column alias mapped into a CTE or subquery. + * + * Example: + * struct Object { ... }; + * using cte_1 = decltype(1_ctealias); + * storage.with(cte()(select(&Object::id)), select(column(&Object::id))); + * storage.with(cte()(select(&Object::id)), select(column(1_colalias))); + * storage.with(cte()(select(as(&Object::id))), select(column(colalias_a{}))); + * storage.with(cte(colalias_a{})(select(&Object::id)), select(column(colalias_a{}))); + * storage.with(cte()(select(as(&Object::id))), select(column(get()))); + */ + template = true> + constexpr auto column(F field) { + using namespace ::sqlite_orm::internal; + + static_assert(is_cte_moniker_v, "`Moniker' must be a CTE moniker"); + + if constexpr(polyfill::is_specialization_of_v) { + static_assert(is_column_alias_v>); + return column_pointer{{}}; + } else if constexpr(is_column_alias_v) { + return column_pointer>{{}}; + } else { + return column_pointer{std::move(field)}; + } + (void)field; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column mapped into a CTE or subquery. + * + * Example: + * struct Object { ... }; + * storage.with(cte<"z"_cte>()(select(&Object::id)), select(column<"z"_cte>(&Object::id))); + * storage.with(cte<"z"_cte>()(select(&Object::id)), select(column<"z"_cte>(1_colalias))); + */ + template + constexpr auto column(F field) { + using Moniker = std::remove_const_t; + return column(std::forward(field)); + } + + /** + * Explicitly refer to a column mapped into a CTE or subquery. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + * + * Example: + * struct Object { ... }; + * using cte_1 = decltype(1_ctealias); + * storage.with(cte()(select(&Object::id)), select(1_ctealias->*&Object::id)); + * storage.with(cte()(select(&Object::id)), select(1_ctealias->*1_colalias)); + */ + template + constexpr auto operator->*(const Moniker& /*moniker*/, F field) { + return column(std::forward(field)); + } +#endif +#endif +} \ No newline at end of file diff --git a/dev/column_result.h b/dev/column_result.h index b5d2fd745..6ab442763 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -1,13 +1,16 @@ #pragma once #include // std::enable_if, std::is_same, std::decay, std::is_arithmetic, std::is_base_of -#include // std::tuple #include // std::reference_wrapper -#include "functional/cxx_universal.h" +#include "functional/cxx_universal.h" // ::nullptr_t +#include "functional/cxx_type_traits_polyfill.h" +#include "functional/mpl.h" #include "tuple_helper/tuple_traits.h" #include "tuple_helper/tuple_fy.h" #include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_transformer.h" +#include "tuple_helper/same_or_void.h" #include "type_traits.h" #include "member_traits/member_traits.h" #include "mapped_type_proxy.h" @@ -15,9 +18,12 @@ #include "select_constraints.h" #include "operators.h" #include "rowid.h" +#include "column_result_proxy.h" #include "alias.h" +#include "cte_types.h" #include "storage_traits.h" #include "function.h" +#include "ast/special_keywords.h" namespace sqlite_orm { @@ -36,11 +42,23 @@ namespace sqlite_orm { * SFINAE - sfinae argument */ template - struct column_result_t; + struct column_result_t { +#ifdef __FUNCTION__ + // produce an error message that reveals `T` and `DBOs` + static constexpr bool reveal() { + static_assert(polyfill::always_false_v, "T not found in DBOs - " __FUNCTION__); + } + static constexpr bool trigger = reveal(); +#endif + }; template using column_result_of_t = typename column_result_t::type; + template + using column_result_for_tuple_t = + transform_tuple_t::template fn>; + #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template struct column_result_t, void> { @@ -63,6 +81,21 @@ namespace sqlite_orm { using type = bool; }; + template + struct column_result_t { + using type = std::string; + }; + + template + struct column_result_t { + using type = std::string; + }; + + template + struct column_result_t { + using type = std::string; + }; + template struct column_result_t> : member_field_type {}; @@ -203,9 +236,28 @@ namespace sqlite_orm { template struct column_result_t, void> : column_result_t {}; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct column_result_t>, void> { + using table_type = storage_pick_table_t; + using cte_mapper_type = cte_mapper_type_t; + + // lookup ColAlias in the final column references + using colalias_index = + find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, + "No such column mapped into the CTE."); + using type = std::tuple_element_t; + }; +#endif + template - struct column_result_t, void> { - using type = tuple_cat_t>>...>; + struct column_result_t, void> + : conc_tuple>>...> {}; + + template + struct column_result_t, void> { + using type = structure>>...>>; }; template @@ -213,11 +265,10 @@ namespace sqlite_orm { template struct column_result_t> { - using left_result = column_result_of_t; - using right_result = column_result_of_t; - static_assert(std::is_same::value, - "Compound subselect queries must return same types"); - using type = left_result; + using type = + polyfill::detected_t>; + static_assert(!std::is_same::value, + "Compound select statements must return a common type"); }; template @@ -225,6 +276,11 @@ namespace sqlite_orm { using type = typename T::result_type; }; + template + struct column_result_t, void> { + using type = std::string; + }; + /** * Result for the most simple queries like `SELECT 1` */ @@ -255,7 +311,7 @@ namespace sqlite_orm { template struct column_result_t, void> { - using type = T; + using type = table_reference; }; template diff --git a/dev/column_result_proxy.h b/dev/column_result_proxy.h new file mode 100644 index 000000000..e852f59b1 --- /dev/null +++ b/dev/column_result_proxy.h @@ -0,0 +1,41 @@ +#pragma once + +#include "type_traits.h" +#include "table_reference.h" + +namespace sqlite_orm { + namespace internal { + + /* + * Holder for the type of an unmapped aggregate/structure/object to be constructed ad-hoc from column results. + * `T` must be constructible using direct-list-initialization. + */ + template + struct structure { + using type = T; + }; + } +} + +namespace sqlite_orm { + namespace internal { + + template + struct column_result_proxy : std::remove_const {}; + + /* + * Unwrap `table_reference` + */ + template + struct column_result_proxy> : decay_table_ref

{}; + + /* + * Pass through `structure` + */ + template + struct column_result_proxy> : P {}; + + template + using column_result_proxy_t = typename column_result_proxy::type; + } +} diff --git a/dev/conditions.h b/dev/conditions.h index cc5e58c89..e1a1fccdc 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -4,6 +4,7 @@ #include // std::enable_if, std::is_same, std::remove_const #include // std::vector #include // std::tuple +#include // std::move, std::forward #include // std::stringstream #include "functional/cxx_universal.h" @@ -14,9 +15,13 @@ #include "constraints.h" #include "optional_container.h" #include "serializer_context.h" +#include "serialize_result_type.h" #include "tags.h" +#include "table_reference.h" #include "alias_traits.h" #include "expression.h" +#include "column_pointer.h" +#include "tags.h" #include "type_printer.h" #include "literal.h" @@ -124,22 +129,22 @@ namespace sqlite_orm { using right_type = R; using result_type = Res; - left_type l; - right_type r; + left_type lhs; + right_type rhs; binary_condition() = default; - binary_condition(left_type l_, right_type r_) : l(std::move(l_)), r(std::move(r_)) {} + binary_condition(left_type l_, right_type r_) : lhs(std::move(l_)), rhs(std::move(r_)) {} }; template SQLITE_ORM_INLINE_VAR constexpr bool is_binary_condition_v = is_base_of_template_v; template - using is_binary_condition = polyfill::bool_constant>; + struct is_binary_condition : polyfill::bool_constant> {}; struct and_condition_string { - operator std::string() const { + serialize_result_type serialize() const { return "AND"; } }; @@ -154,13 +159,8 @@ namespace sqlite_orm { using super::super; }; - template - and_condition_t make_and_condition(L left, R right) { - return {std::move(left), std::move(right)}; - } - struct or_condition_string { - operator std::string() const { + serialize_result_type serialize() const { return "OR"; } }; @@ -175,13 +175,8 @@ namespace sqlite_orm { using super::super; }; - template - or_condition_t make_or_condition(L left, R right) { - return {std::move(left), std::move(right)}; - } - struct is_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return "="; } }; @@ -219,8 +214,18 @@ namespace sqlite_orm { } }; + template + struct is_equal_with_table_t : negatable_t { + using left_type = L; + using right_type = R; + + right_type rhs; + + is_equal_with_table_t(right_type rhs) : rhs(std::move(rhs)) {} + }; + struct is_not_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return "!="; } }; @@ -248,7 +253,7 @@ namespace sqlite_orm { }; struct greater_than_string { - operator std::string() const { + serialize_result_type serialize() const { return ">"; } }; @@ -276,7 +281,7 @@ namespace sqlite_orm { }; struct greater_or_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return ">="; } }; @@ -303,8 +308,8 @@ namespace sqlite_orm { } }; - struct lesser_than_string { - operator std::string() const { + struct less_than_string { + serialize_result_type serialize() const { return "<"; } }; @@ -313,10 +318,10 @@ namespace sqlite_orm { * < operator object. */ template - struct lesser_than_t : binary_condition, negatable_t { - using self = lesser_than_t; + struct less_than_t : binary_condition, negatable_t { + using self = less_than_t; - using binary_condition::binary_condition; + using binary_condition::binary_condition; collate_t collate_binary() const { return {*this, collate_argument::binary}; @@ -331,8 +336,8 @@ namespace sqlite_orm { } }; - struct lesser_or_equal_string { - operator std::string() const { + struct less_or_equal_string { + serialize_result_type serialize() const { return "<="; } }; @@ -341,10 +346,10 @@ namespace sqlite_orm { * <= operator object. */ template - struct lesser_or_equal_t : binary_condition, negatable_t { - using self = lesser_or_equal_t; + struct less_or_equal_t : binary_condition, negatable_t { + using self = less_or_equal_t; - using binary_condition::binary_condition; + using binary_condition::binary_condition; collate_t collate_binary() const { return {*this, collate_argument::binary}; @@ -559,12 +564,12 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_order_by_v = - polyfill::disjunction_v, - polyfill::is_specialization_of, - polyfill::is_specialization_of>; + polyfill::disjunction, + polyfill::is_specialization_of, + polyfill::is_specialization_of>::value; template - using is_order_by = polyfill::bool_constant>; + struct is_order_by : polyfill::bool_constant> {}; struct between_string { operator std::string() const { @@ -626,7 +631,7 @@ namespace sqlite_orm { }; template - struct glob_t : condition_t, glob_string, internal::negatable_t { + struct glob_t : condition_t, glob_string, negatable_t { using self = glob_t; using arg_t = A; using pattern_t = T; @@ -826,185 +831,142 @@ namespace sqlite_orm { return {}; } - template = true> - internal::negated_condition_t operator!(T arg) { - return {std::move(arg)}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicit FROM function. Usage: + * `storage.select(&User::id, from<"a"_alias.for_>());` + */ + template + auto from() { + return from...>(); } +#endif - // Deliberately put operators for `expression_t` into the internal namespace + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { - /** - * Cute operators for columns - */ - template - lesser_than_t operator<(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + negated_condition_t operator!(T arg) { + return {std::move(arg)}; } - template - lesser_than_t operator<(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + less_than_t, unwrap_expression_t> operator<(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - lesser_or_equal_t operator<=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + less_or_equal_t, unwrap_expression_t> operator<=(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - lesser_or_equal_t operator<=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + greater_than_t, unwrap_expression_t> operator>(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - greater_than_t operator>(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + greater_or_equal_t, unwrap_expression_t> operator>=(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - greater_than_t operator>(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; + template, + std::is_base_of, + std::is_base_of, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + is_equal_t, unwrap_expression_t> operator==(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - greater_or_equal_t operator>=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; + template, + std::is_base_of, + std::is_base_of, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + is_not_equal_t, unwrap_expression_t> operator!=(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - greater_or_equal_t operator>=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + and_condition_t, unwrap_expression_t> operator&&(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - is_equal_t operator==(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; + template, std::is_base_of>::value, + bool> = true> + or_condition_t, unwrap_expression_t> operator||(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } - template - is_equal_t operator==(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - is_not_equal_t operator!=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - is_not_equal_t operator!=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - conc_t operator||(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - conc_t operator||(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - conc_t operator||(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template = true> - conc_t operator||(E expr, R r) { - return {std::move(expr), std::move(r)}; - } - - template = true> - conc_t operator||(L l, E expr) { - return {std::move(l), std::move(expr)}; - } - - template - add_t operator+(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - add_t operator+(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - add_t operator+(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - sub_t operator-(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - sub_t operator-(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - sub_t operator-(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - mul_t operator*(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - mul_t operator*(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - mul_t operator*(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - div_t operator/(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - div_t operator/(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - div_t operator/(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - mod_t operator%(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - mod_t operator%(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - mod_t operator%(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; + template< + class L, + class R, + std::enable_if_t, + std::is_base_of, + is_operator_argument, + is_operator_argument>, + // exclude conditions + polyfill::negation, + std::is_base_of>>>::value, + bool> = true> + conc_t, unwrap_expression_t> operator||(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } } template - internal::using_t using_(F O::*p) { - return {p}; + internal::using_t using_(F O::*field) { + return {field}; } template - internal::using_t using_(internal::column_pointer cp) { - return {std::move(cp)}; + internal::using_t using_(internal::column_pointer field) { + return {std::move(field)}; } template @@ -1027,21 +989,49 @@ namespace sqlite_orm { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto left_join(On on) { + return left_join, On>(std::move(on)); + } +#endif + template internal::join_t join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto join(On on) { + return join, On>(std::move(on)); + } +#endif + template internal::left_outer_join_t left_outer_join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto left_outer_join(On on) { + return left_outer_join, On>(std::move(on)); + } +#endif + template internal::inner_join_t inner_join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto inner_join(On on) { + return inner_join, On>(std::move(on)); + } +#endif + template internal::offset_t offset(T off) { return {std::move(off)}; @@ -1062,36 +1052,18 @@ namespace sqlite_orm { return {std::move(lim), {std::move(offt.off)}}; } - template, - std::is_base_of>, - bool> = true> - auto operator&&(L l, R r) { - using internal::get_from_expression; - return internal::make_and_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); - } - template auto and_(L l, R r) { - using internal::get_from_expression; - return internal::make_and_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); - } - - template, - std::is_base_of>, - bool> = true> - auto operator||(L l, R r) { - using internal::get_from_expression; - return internal::make_or_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); + using namespace ::sqlite_orm::internal; + return and_condition_t, unwrap_expression_t>{get_from_expression(std::forward(l)), + get_from_expression(std::forward(r))}; } template auto or_(L l, R r) { - using internal::get_from_expression; - return internal::make_or_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); + using namespace ::sqlite_orm::internal; + return or_condition_t, unwrap_expression_t>{get_from_expression(std::forward(l)), + get_from_expression(std::forward(r))}; } template @@ -1144,6 +1116,11 @@ namespace sqlite_orm { return {std::move(l), std::move(r)}; } + template + internal::is_equal_with_table_t is_equal(R rhs) { + return {std::move(rhs)}; + } + template internal::is_not_equal_t is_not_equal(L l, R r) { return {std::move(l), std::move(r)}; @@ -1175,22 +1152,40 @@ namespace sqlite_orm { } template - internal::lesser_than_t lesser_than(L l, R r) { + internal::less_than_t less_than(L l, R r) { + return {std::move(l), std::move(r)}; + } + + /** + * [Deprecation notice] This function is deprecated and will be removed in v1.10. Use the accurately named function `less_than(...)` instead. + */ + template + [[deprecated("Use the accurately named function `less_than(...)` instead")]] internal::less_than_t + lesser_than(L l, R r) { return {std::move(l), std::move(r)}; } template - internal::lesser_than_t lt(L l, R r) { + internal::less_than_t lt(L l, R r) { return {std::move(l), std::move(r)}; } template - internal::lesser_or_equal_t lesser_or_equal(L l, R r) { + internal::less_or_equal_t less_or_equal(L l, R r) { + return {std::move(l), std::move(r)}; + } + + /** + * [Deprecation notice] This function is deprecated and will be removed in v1.10. Use the accurately named function `less_or_equal(...)` instead. + */ + template + [[deprecated("Use the accurately named function `less_or_equal(...)` instead")]] internal::less_or_equal_t + lesser_or_equal(L l, R r) { return {std::move(l), std::move(r)}; } template - internal::lesser_or_equal_t le(L l, R r) { + internal::less_or_equal_t le(L l, R r) { return {std::move(l), std::move(r)}; } @@ -1222,8 +1217,8 @@ namespace sqlite_orm { * Example: storage.get_all(multi_order_by(order_by(&Singer::name).asc(), order_by(&Singer::gender).desc()) */ template - internal::multi_order_by_t multi_order_by(Args&&... args) { - return {std::make_tuple(std::forward(args)...)}; + internal::multi_order_by_t multi_order_by(Args... args) { + return {{std::forward(args)...}}; } /** diff --git a/dev/connection_holder.h b/dev/connection_holder.h index 0631adf81..77d277fbc 100644 --- a/dev/connection_holder.h +++ b/dev/connection_holder.h @@ -48,28 +48,35 @@ namespace sqlite_orm { }; struct connection_ref { - connection_ref(connection_holder& holder_) : holder(holder_) { - this->holder.retain(); + connection_ref(connection_holder& holder) : holder(&holder) { + this->holder->retain(); } connection_ref(const connection_ref& other) : holder(other.holder) { - this->holder.retain(); + this->holder->retain(); } - connection_ref(connection_ref&& other) : holder(other.holder) { - this->holder.retain(); + // rebind connection reference + connection_ref& operator=(const connection_ref& other) { + if(other.holder != this->holder) { + this->holder->release(); + this->holder = other.holder; + this->holder->retain(); + } + + return *this; } ~connection_ref() { - this->holder.release(); + this->holder->release(); } sqlite3* get() const { - return this->holder.get(); + return this->holder->get(); } - protected: - connection_holder& holder; + private: + connection_holder* holder = nullptr; }; } } diff --git a/dev/constraints.h b/dev/constraints.h index 37ef84039..eed1db455 100644 --- a/dev/constraints.h +++ b/dev/constraints.h @@ -3,7 +3,7 @@ #include // std::system_error #include // std::ostream #include // std::string -#include // std::tuple, std::make_tuple +#include // std::tuple #include // std::is_base_of, std::false_type, std::true_type #include "functional/cxx_universal.h" @@ -22,11 +22,6 @@ namespace sqlite_orm { namespace internal { - /** - * AUTOINCREMENT constraint class. - */ - struct autoincrement_t {}; - enum class conflict_clause_t { rollback, abort, @@ -49,12 +44,15 @@ namespace sqlite_orm { }; template - struct primary_key_with_autoincrement { + struct primary_key_with_autoincrement : T { using primary_key_type = T; - primary_key_type primary_key; - - primary_key_with_autoincrement(primary_key_type primary_key_) : primary_key(primary_key_) {} + const primary_key_type& as_base() const { + return *this; + } +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + primary_key_with_autoincrement(primary_key_type primary_key) : primary_key_type{primary_key} {} +#endif }; /** @@ -70,7 +68,7 @@ namespace sqlite_orm { columns_tuple columns; - primary_key_t(decltype(columns) columns) : columns(std::move(columns)) {} + primary_key_t(columns_tuple columns) : columns(std::move(columns)) {} self asc() const { auto res = *this; @@ -142,6 +140,34 @@ namespace sqlite_orm { unique_t(columns_tuple columns_) : columns(std::move(columns_)) {} }; + struct unindexed_t {}; + + template + struct prefix_t { + using value_type = T; + + value_type value; + }; + + template + struct tokenize_t { + using value_type = T; + + value_type value; + }; + + template + struct content_t { + using value_type = T; + + value_type value; + }; + + template + struct table_content_t { + using mapped_type = T; + }; + /** * DEFAULT constraint class. * T is a value type. @@ -158,7 +184,6 @@ namespace sqlite_orm { }; #if SQLITE_VERSION_NUMBER >= 3006019 - /** * FOREIGN KEY constraint class. * Cs are columns which has foreign key @@ -296,12 +321,12 @@ namespace sqlite_orm { /** * Holds obect type of all referenced columns. */ - using target_type = typename same_or_void...>::type; + using target_type = same_or_void_t...>; /** * Holds obect type of all source columns. */ - using source_type = typename same_or_void...>::type; + using source_type = same_or_void_t...>; columns_type columns; references_type references; @@ -349,7 +374,7 @@ namespace sqlite_orm { template foreign_key_t, std::tuple> references(Rs... refs) { - return {std::move(this->columns), std::make_tuple(std::forward(refs)...)}; + return {std::move(this->columns), {std::forward(refs)...}}; } }; #endif @@ -391,14 +416,17 @@ namespace sqlite_orm { stored, }; +#if SQLITE_VERSION_NUMBER >= 3031000 bool full = true; storage_type storage = storage_type::not_specified; +#endif #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED basic_generated_always(bool full, storage_type storage) : full{full}, storage{storage} {} #endif }; +#if SQLITE_VERSION_NUMBER >= 3031000 template struct generated_always_t : basic_generated_always { using expression_type = T; @@ -416,72 +444,69 @@ namespace sqlite_orm { return {std::move(this->expression), this->full, storage_type::stored}; } }; +#endif + + struct null_t {}; + struct not_null_t {}; } namespace internal { template - SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = polyfill::is_specialization_of_v; - - template - using is_foreign_key = polyfill::bool_constant>; - - template - struct is_primary_key : std::false_type {}; - - template - struct is_primary_key> : std::true_type {}; - - template - struct is_primary_key> : std::true_type {}; + SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = +#if SQLITE_VERSION_NUMBER >= 3006019 + polyfill::is_specialization_of::value; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = is_primary_key::value; + struct is_foreign_key : polyfill::bool_constant> {}; template - using is_generated_always = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = std::is_base_of::value; template - SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + struct is_primary_key : polyfill::bool_constant> {}; template - using is_autoincrement = std::is_same; + SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = +#if SQLITE_VERSION_NUMBER >= 3031000 + polyfill::is_specialization_of::value; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_autoincrement_v = is_autoincrement::value; + struct is_generated_always : polyfill::bool_constant> {}; /** * PRIMARY KEY INSERTABLE traits. */ - template + template struct is_primary_key_insertable : polyfill::disjunction< - mpl::instantiate, - check_if_tuple_has_template, - check_if_tuple_has_template>, - constraints_type_t>, - std::is_base_of>>> { + mpl::invoke_t, + check_if_has_template>, + constraints_type_t>, + std::is_base_of>>> { - static_assert(tuple_has>::value, "an unexpected type was passed"); + static_assert(tuple_has, is_primary_key>::value, + "an unexpected type was passed"); }; template - using is_constraint = - mpl::instantiate, - check_if, - check_if, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_type, -#if SQLITE_VERSION_NUMBER >= 3031000 - check_if, -#endif - // dummy tail because of SQLITE_VERSION_NUMBER checks above - mpl::always>, - T>; + using is_column_constraint = mpl::invoke_t>, + check_if_is_type, + check_if_is_type, + check_if_is_type>, + check_if_is_template, + check_if_is_template, + check_if_is_type, + check_if, + check_if_is_type>, + T>; } #if SQLITE_VERSION_NUMBER >= 3031000 @@ -495,43 +520,95 @@ namespace sqlite_orm { return {std::move(expression), false, internal::basic_generated_always::storage_type::not_specified}; } #endif -#if SQLITE_VERSION_NUMBER >= 3006019 +#if SQLITE_VERSION_NUMBER >= 3006019 /** * FOREIGN KEY constraint construction function that takes member pointer as argument * Available in SQLite 3.6.19 or higher */ template internal::foreign_key_intermediate_t foreign_key(Cs... columns) { - return {std::make_tuple(std::forward(columns)...)}; + return {{std::forward(columns)...}}; } #endif /** - * UNIQUE constraint builder function. + * UNIQUE table constraint builder function. */ template internal::unique_t unique(Args... args) { - return {std::make_tuple(std::forward(args)...)}; + return {{std::forward(args)...}}; } + /** + * UNIQUE column constraint builder function. + */ inline internal::unique_t<> unique() { return {{}}; } +#if SQLITE_VERSION_NUMBER >= 3009000 + /** + * UNINDEXED column constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#the_unindexed_column_option + */ + inline internal::unindexed_t unindexed() { + return {}; + } + + /** + * prefix=N table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#prefix_indexes + */ + template + internal::prefix_t prefix(T value) { + return {std::move(value)}; + } + + /** + * tokenize='...'' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#tokenizers + */ + template + internal::tokenize_t tokenize(T value) { + return {std::move(value)}; + } + /** - * AUTOINCREMENT keyword. [Deprecation notice] Use `primary_key().autoincrement()` instead of using this function. - * This function will be removed in 1.9 + * content='' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#contentless_tables */ - [[deprecated("Use primary_key().autoincrement()` instead")]] inline internal::autoincrement_t autoincrement() { + template + internal::content_t content(T value) { + return {std::move(value)}; + } + + /** + * content='table' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#external_content_tables + */ + template + internal::table_content_t content() { return {}; } +#endif + /** + * PRIMARY KEY table constraint builder function. + */ template internal::primary_key_t primary_key(Cs... cs) { - return {std::make_tuple(std::forward(cs)...)}; + return {{std::forward(cs)...}}; } + /** + * PRIMARY KEY column constraint builder function. + */ inline internal::primary_key_t<> primary_key() { return {{}}; } @@ -557,4 +634,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/core_functions.h b/dev/core_functions.h index 5461ada47..4f4c8bd87 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -7,11 +7,14 @@ #include // std::vector #include "functional/cxx_type_traits_polyfill.h" -#include "conditions.h" +#include "functional/mpl/conditional.h" #include "is_base_of_template.h" -#include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_traits.h" +#include "conditions.h" #include "serialize_result_type.h" #include "operators.h" +#include "tags.h" +#include "table_reference.h" #include "ast/into.h" namespace sqlite_orm { @@ -44,10 +47,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_built_in_function_v = is_base_of_template_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_built_in_function_v = + is_base_of_template::value; template - using is_built_in_function = polyfill::bool_constant>; + struct is_built_in_function : polyfill::bool_constant> {}; template struct filtered_aggregate_function { @@ -182,7 +186,6 @@ namespace sqlite_orm { }; #if SQLITE_VERSION_NUMBER >= 3007016 - struct char_string { serialize_result_type serialize() const { return "CHAR"; @@ -611,40 +614,23 @@ namespace sqlite_orm { template using field_type_or_type_t = polyfill::detected_or_t>; - } - - /** - * Cute operators for core functions - */ - template = true> - internal::lesser_than_t operator<(F f, R r) { - return {std::move(f), std::move(r)}; - } - - template = true> - internal::lesser_or_equal_t operator<=(F f, R r) { - return {std::move(f), std::move(r)}; - } - template = true> - internal::greater_than_t operator>(F f, R r) { - return {std::move(f), std::move(r)}; - } + template + struct highlight_t { + using table_type = T; + using argument0_type = X; + using argument1_type = Y; + using argument2_type = Z; - template = true> - internal::greater_or_equal_t operator>=(F f, R r) { - return {std::move(f), std::move(r)}; - } + argument0_type argument0; + argument1_type argument1; + argument2_type argument2; - template = true> - internal::is_equal_t operator==(F f, R r) { - return {std::move(f), std::move(r)}; + highlight_t(argument0_type argument0, argument1_type argument1, argument2_type argument2) : + argument0(std::move(argument0)), argument1(std::move(argument1)), argument2(std::move(argument2)) {} + }; } - template = true> - internal::is_not_equal_t operator!=(F f, R r) { - return {std::move(f), std::move(r)}; - } #ifdef SQLITE_ENABLE_MATH_FUNCTIONS /** @@ -1663,7 +1649,6 @@ namespace sqlite_orm { } #if SQLITE_VERSION_NUMBER >= 3007016 - /** * CHAR(X1,X2,...,XN) function https://sqlite.org/lang_corefunc.html#char */ @@ -1678,7 +1663,6 @@ namespace sqlite_orm { inline internal::built_in_function_t random() { return {{}}; } - #endif /** @@ -1686,7 +1670,7 @@ namespace sqlite_orm { */ template auto coalesce(Args... args) - -> internal::built_in_function_t internal::built_in_function_t::value, std::common_type...>, polyfill::type_identity>::type, @@ -1700,7 +1684,7 @@ namespace sqlite_orm { */ template auto ifnull(X x, Y y) -> internal::built_in_function_t< - typename std::conditional_t< // choose R or common type + typename mpl::conditional_t< // choose R or common type std::is_void::value, std::common_type, internal::field_type_or_type_t>, polyfill::type_identity>::type, @@ -1853,7 +1837,7 @@ namespace sqlite_orm { } /** - * COUNT(*) with FROM function. Specified type T will be serializeed as + * COUNT(*) with FROM function. Specified type T will be serialized as * a from argument. */ template @@ -1861,6 +1845,17 @@ namespace sqlite_orm { return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * COUNT(*) with FROM function. Specified recordset will be serialized as + * a from argument. + */ + template + auto count() { + return count>(); + } +#endif + /** * AVG(X) aggregate function. */ @@ -2041,50 +2036,69 @@ namespace sqlite_orm { internal::built_in_function_t json_group_object(X x, Y y) { return {std::tuple{std::forward(x), std::forward(y)}}; } - #endif // SQLITE_ENABLE_JSON1 - template, - std::is_base_of>, - bool> = true> - internal::add_t operator+(L l, R r) { - return {std::move(l), std::move(r)}; - } - template, - std::is_base_of>, - bool> = true> - internal::sub_t operator-(L l, R r) { - return {std::move(l), std::move(r)}; - } + // 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, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + add_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>, - bool> = true> - internal::mul_t operator*(L l, R r) { - return {std::move(l), std::move(r)}; - } + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + sub_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>, - bool> = true> - internal::div_t operator/(L l, R r) { - return {std::move(l), std::move(r)}; + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + mul_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> + div_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> + mod_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>, - bool> = true> - internal::mod_t operator%(L l, R r) { - return {std::move(l), std::move(r)}; + template + internal::highlight_t highlight(X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; } } diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h new file mode 100644 index 000000000..8cee112e1 --- /dev/null +++ b/dev/cte_column_names_collector.h @@ -0,0 +1,210 @@ +#pragma once + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#include // std::reference_wrapper +#include +#endif + +#include "functional/cxx_universal.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "type_traits.h" +#include "member_traits/member_traits.h" +#include "error_code.h" +#include "alias.h" +#include "select_constraints.h" +#include "serializer_context.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +namespace sqlite_orm { + namespace internal { + // collecting column names utilizes the statement serializer + template + auto serialize(const T& t, const C& context); + + inline void unquote_identifier(std::string& identifier) { + if(!identifier.empty()) { + constexpr char quoteChar = '"'; + constexpr char sqlEscaped[] = {quoteChar, quoteChar}; + identifier.erase(identifier.end() - 1); + identifier.erase(identifier.begin()); + for(size_t pos = 0; (pos = identifier.find(sqlEscaped, pos, 2)) != identifier.npos; ++pos) { + identifier.erase(pos, 1); + } + } + } + + inline void unquote_or_erase(std::string& name) { + constexpr char quoteChar = '"'; + if(name.front() == quoteChar) { + unquote_identifier(name); + } else { + // unaliased expression - see 3. below + name.clear(); + } + } + + template + struct cte_column_names_collector { + using expression_type = T; + + // Compound statements are never passed in by db_objects_for_expression() + static_assert(!is_compound_operator_v); + + template + std::vector operator()(const expression_type& t, const Ctx& context) const { + auto newContext = context; + newContext.skip_table_name = true; + std::string columnName = serialize(t, newContext); + if(columnName.empty()) { + throw std::system_error{orm_error_code::column_not_found}; + } + unquote_or_erase(columnName); + return {std::move(columnName)}; + } + }; + + template + std::vector get_cte_column_names(const T& t, const Ctx& context) { + cte_column_names_collector collector; + return collector(t, context); + } + + template + struct cte_column_names_collector> { + using expression_type = As; + + template + std::vector operator()(const expression_type& /*expression*/, const Ctx& /*context*/) const { + return {alias_extractor>::extract()}; + } + }; + + template + struct cte_column_names_collector> { + using expression_type = Wrapper; + + template + std::vector operator()(const expression_type& expression, const Ctx& context) const { + return get_cte_column_names(expression.get(), context); + } + }; + + template + struct cte_column_names_collector> { + using expression_type = Asterisk; + using T = typename Asterisk::type; + + template + std::vector operator()(const expression_type&, const Ctx& context) const { + auto& table = pick_table(context.db_objects); + + std::vector columnNames; + columnNames.reserve(size_t(table.template count_of())); + + table.for_each_column([&columnNames](const column_identifier& column) { + columnNames.push_back(column.name); + }); + return columnNames; + } + }; + + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + }; + + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + }; + + template + struct cte_column_names_collector> { + using expression_type = Columns; + + template + std::vector operator()(const expression_type& cols, const Ctx& context) const { + std::vector columnNames; + columnNames.reserve(size_t(cols.count)); + auto newContext = context; + newContext.skip_table_name = true; + iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { + using value_type = polyfill::remove_cvref_t; + + if constexpr(polyfill::is_specialization_of_v) { + columnNames.push_back(alias_extractor>::extract()); + } else { + std::string columnName = serialize(m, newContext); + if(!columnName.empty()) { + columnNames.push_back(std::move(columnName)); + } else { + throw std::system_error{orm_error_code::column_not_found}; + } + unquote_or_erase(columnNames.back()); + } + }); + return columnNames; + } + }; + + template = true> + std::vector + collect_cte_column_names(const E& sel, const ExplicitColRefs& explicitColRefs, const Ctx& context) { + // 1. determine column names from subselect + std::vector columnNames = get_cte_column_names(sel.col, context); + + // 2. override column names from cte expression + if(size_t n = std::tuple_size_v) { + if(n != columnNames.size()) { + throw std::system_error{orm_error_code::column_not_found}; + } + + size_t idx = 0; + iterate_tuple(explicitColRefs, [&idx, &columnNames, &context](auto& colRef) { + using ColRef = polyfill::remove_cvref_t; + + if constexpr(polyfill::is_specialization_of_v) { + columnNames[idx] = alias_extractor>::extract(); + } else if constexpr(std::is_member_pointer::value) { + using O = table_type_of_t; + if(auto* columnName = find_column_name(context.db_objects, colRef)) { + columnNames[idx] = *columnName; + } else { + // relaxed: allow any member pointer as column reference + columnNames[idx] = typeid(ColRef).name(); + } + } else if constexpr(polyfill::is_specialization_of_v) { + columnNames[idx] = colRef.name; + } else if constexpr(std::is_same_v) { + if(!colRef.empty()) { + columnNames[idx] = colRef; + } + } else if constexpr(std::is_same_v>) { + if(columnNames[idx].empty()) { + columnNames[idx] = std::to_string(idx + 1); + } + } else { + static_assert(polyfill::always_false_v, "Invalid explicit column reference specified"); + } + ++idx; + }); + } + + // 3. fill in blanks with numerical column identifiers + { + for(size_t i = 0, n = columnNames.size(); i < n; ++i) { + if(columnNames[i].empty()) { + columnNames[i] = std::to_string(i + 1); + } + } + } + + return columnNames; + } + } +} +#endif diff --git a/dev/cte_moniker.h b/dev/cte_moniker.h new file mode 100644 index 000000000..79e623ef9 --- /dev/null +++ b/dev/cte_moniker.h @@ -0,0 +1,90 @@ +#pragma once + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#include // std::make_index_sequence +#endif +#include // std::enable_if, std::is_member_pointer, std::is_same, std::is_convertible +#include // std::ignore +#include +#endif + +#include "functional/cxx_universal.h" +#include "functional/cstring_literal.h" +#include "alias.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +namespace sqlite_orm { + + namespace internal { + /** + * A special record set alias that is both, a storage lookup type (mapping type) and an alias. + */ + template + struct cte_moniker + : recordset_alias< + cte_moniker /* refer to self, since a moniker is both, an alias and a mapped type */, + A, + X...> { + /** + * Introduce the construction of a common table expression using this moniker. + * + * The list of explicit columns is optional; + * if provided the number of columns must match the number of columns of the subselect. + * The column names will be merged with the subselect: + * 1. column names of subselect + * 2. explicit columns + * 3. fill in empty column names with column index + * + * Example: + * 1_ctealias()(select(&Object::id)); + * 1_ctealias(&Object::name)(select("object")); + * + * @return A `cte_builder` instance. + * @note (internal): Defined in select_constraints.h in order to keep this member function in the same place as the named factory function `cte()`, + * and to keep the actual creation of the builder in one place. + */ +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires((is_column_alias_v || std::is_member_pointer_v || + std::same_as> || + std::convertible_to) && + ...) + auto operator()(ExplicitCols... explicitColumns) const; +#else + template, + std::is_member_pointer, + std::is_same>, + std::is_convertible>...>, + bool> = true> + auto operator()(ExplicitCols... explicitColumns) const; +#endif + }; + } + + inline namespace literals { + /** + * cte_moniker<'n'> from a numeric literal. + * E.g. 1_ctealias, 2_ctealias + */ + template + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _ctealias() { + return internal::cte_moniker{}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * cte_moniker<'1'[, ...]> from a string literal. + * E.g. "1"_cte, "2"_cte + */ + template + [[nodiscard]] consteval auto operator"" _cte() { + return internal::explode_into(std::make_index_sequence{}); + } +#endif + } +} +#endif diff --git a/dev/cte_storage.h b/dev/cte_storage.h new file mode 100644 index 000000000..61dca9102 --- /dev/null +++ b/dev/cte_storage.h @@ -0,0 +1,326 @@ +#pragma once + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#include +#include +#endif + +#include "functional/cxx_universal.h" // ::size_t +#include "tuple_helper/tuple_fy.h" +#include "table_type_of.h" +#include "column_result.h" +#include "select_constraints.h" +#include "schema/table.h" +#include "alias.h" +#include "cte_types.h" +#include "cte_column_names_collector.h" +#include "column_expression.h" +#include "storage_lookup.h" + +namespace sqlite_orm { + namespace internal { + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // F = field_type + template + struct create_cte_mapper { + using type = subselect_mapper; + }; + + // std::tuple + template + struct create_cte_mapper> { + using type = subselect_mapper; + }; + + template + using create_cte_mapper_t = + typename create_cte_mapper:: + type; + + // aliased column expressions, explicit or implicitly numbered + template = true> + static auto make_cte_column(std::string name, const ColRef& /*finalColRef*/) { + using object_type = aliased_field, F>; + + return sqlite_orm::make_column<>(std::move(name), &object_type::field); + } + + // F O::* + template = true> + static auto make_cte_column(std::string name, const ColRef& finalColRef) { + using object_type = table_type_of_t; + using column_type = column_t; + + return column_type{std::move(name), finalColRef, empty_setter{}}; + } + + /** + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ + template + auto db_objects_cat(const DBOs& dbObjects, std::index_sequence, CTETables&&... cteTables) { + return std::tuple{std::forward(cteTables)..., get(dbObjects)...}; + } + + /** + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ + template + auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { + return db_objects_cat(dbObjects, + std::make_index_sequence>{}, + std::forward(cteTables)...); + } + + /** + * This function returns the expression contained in a subselect that is relevant for + * creating the definition of a CTE table. + * Because CTEs can recursively refer to themselves in a compound statement, parsing + * the whole compound statement would lead to compiler errors if a column_pointer<> + * can't be resolved. Therefore, at the time of building a CTE table, we are only + * interested in the column results of the left-most select expression. + */ + template + decltype(auto) get_cte_driving_subselect(const Select& subSelect); + + /** + * Return given select expression. + */ + template + decltype(auto) get_cte_driving_subselect(const Select& subSelect) { + return subSelect; + } + + /** + * Return left-most select expression of compound statement. + */ + template, bool> = true> + decltype(auto) get_cte_driving_subselect(const select_t& subSelect) { + return std::get<0>(subSelect.col.compound); + } + + /** + * Return a tuple of member pointers of all columns + */ + template + auto get_table_columns_fields(const C& coldef, std::index_sequence) { + return std::make_tuple(get(coldef).member_pointer...); + } + + // any expression -> numeric column alias + template>, bool> = true> + auto extract_colref_expressions(const DBOs& /*dbObjects*/, const E& /*col*/, std::index_sequence = {}) + -> std::tuple())>> { + return {}; + } + + // expression_t<> + template + auto + extract_colref_expressions(const DBOs& dbObjects, const expression_t& col, std::index_sequence s = {}) { + return extract_colref_expressions(dbObjects, col.value, s); + } + + // F O::* (field/getter) -> field/getter + template + auto extract_colref_expressions(const DBOs& /*dbObjects*/, F O::*col, std::index_sequence = {}) { + return std::make_tuple(col); + } + + // as_t<> (aliased expression) -> alias_holder + template + std::tuple> extract_colref_expressions(const DBOs& /*dbObjects*/, + const as_t& /*col*/, + std::index_sequence = {}) { + return {}; + } + + // alias_holder<> (colref) -> alias_holder + template + std::tuple> extract_colref_expressions(const DBOs& /*dbObjects*/, + const alias_holder& /*col*/, + std::index_sequence = {}) { + return {}; + } + + // column_pointer<> + template + auto extract_colref_expressions(const DBOs& dbObjects, + const column_pointer& col, + std::index_sequence s = {}) { + return extract_colref_expressions(dbObjects, col.field, s); + } + + // column expression tuple + template + auto extract_colref_expressions(const DBOs& dbObjects, + const std::tuple& cols, + std::index_sequence) { + return std::tuple_cat(extract_colref_expressions(dbObjects, get(cols), std::index_sequence{})...); + } + + // columns_t<> + template + auto extract_colref_expressions(const DBOs& dbObjects, const columns_t& cols) { + return extract_colref_expressions(dbObjects, cols.columns, std::index_sequence_for{}); + } + + // asterisk_t<> -> fields + template + auto extract_colref_expressions(const DBOs& dbObjects, const asterisk_t& /*col*/) { + using table_type = storage_pick_table_t; + using elements_t = typename table_type::elements_type; + using column_idxs = filter_tuple_sequence_t; + + auto& table = pick_table(dbObjects); + return get_table_columns_fields(table.elements, column_idxs{}); + } + + template + void extract_colref_expressions(const DBOs& /*dbObjects*/, const select_t& /*subSelect*/) = delete; + + template, bool> = true> + void extract_colref_expressions(const DBOs& /*dbObjects*/, const Compound& /*subSelect*/) = delete; + + /* + * Depending on ExplicitColRef's type returns either the explicit column reference + * or the expression's column reference otherwise. + */ + template + auto determine_cte_colref(const DBOs& /*dbObjects*/, + const SubselectColRef& subselectColRef, + const ExplicitColRef& explicitColRef) { + if constexpr(polyfill::is_specialization_of_v) { + return explicitColRef; + } else if constexpr(std::is_member_pointer::value) { + return explicitColRef; + } else if constexpr(std::is_base_of_v) { + return explicitColRef.member_pointer; + } else if constexpr(std::is_same_v) { + return subselectColRef; + } else if constexpr(std::is_same_v>) { + return subselectColRef; + } else { + static_assert(polyfill::always_false_v, "Invalid explicit column reference specified"); + } + } + + template + auto determine_cte_colrefs([[maybe_unused]] const DBOs& dbObjects, + const SubselectColRefs& subselectColRefs, + [[maybe_unused]] const ExplicitColRefs& explicitColRefs, + std::index_sequence) { + if constexpr(std::tuple_size_v != 0) { + static_assert( + (!is_builtin_numeric_column_alias_v< + alias_holder_type_or_none_t>> && + ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + + return std::tuple{ + determine_cte_colref(dbObjects, get(subselectColRefs), get(explicitColRefs))...}; + } else { + return subselectColRefs; + } + } + + template + auto make_cte_table_using_column_indices(const DBOs& /*dbObjects*/, + std::string tableName, + std::vector columnNames, + const ColRefs& finalColRefs, + std::index_sequence) { + return make_table( + std::move(tableName), + make_cte_column>(std::move(columnNames.at(CIs)), + get(finalColRefs))...); + } + + template + auto make_cte_table(const DBOs& dbObjects, const CTE& cte) { + using cte_type = CTE; + + auto subSelect = get_cte_driving_subselect(cte.subselect); + + using subselect_type = decltype(subSelect); + using column_results = column_result_of_t; + using index_sequence = std::make_index_sequence>>; + static_assert(cte_type::explicit_colref_count == 0 || + cte_type::explicit_colref_count == index_sequence::size(), + "Number of explicit columns of common table expression doesn't match the number of columns " + "in the subselect."); + + std::string tableName = alias_extractor>::extract(); + auto subselectColRefs = extract_colref_expressions(dbObjects, subSelect.col); + const auto& finalColRefs = + determine_cte_colrefs(dbObjects, subselectColRefs, cte.explicitColumns, index_sequence{}); + + serializer_context context{dbObjects}; + std::vector columnNames = collect_cte_column_names(subSelect, cte.explicitColumns, context); + + using mapper_type = create_cte_mapper_t, + typename cte_type::explicit_colrefs_tuple, + column_expression_of_t, + decltype(subselectColRefs), + polyfill::remove_cvref_t, + column_results>; + return make_cte_table_using_column_indices(dbObjects, + std::move(tableName), + std::move(columnNames), + finalColRefs, + index_sequence{}); + } + + template + decltype(auto) make_recursive_cte_db_objects(const DBOs& dbObjects, + const common_table_expressions& cte, + std::index_sequence) { + auto tbl = make_cte_table(dbObjects, get(cte)); + + if constexpr(sizeof...(In) > 0) { + return make_recursive_cte_db_objects( + // Because CTEs can depend on their predecessor we recursively pass in a new set of DBOs + db_objects_cat(dbObjects, std::move(tbl)), + cte, + std::index_sequence{}); + } else { + return db_objects_cat(dbObjects, std::move(tbl)); + } + } + + /** + * Return new DBOs for CTE expressions. + */ + template = true> + decltype(auto) db_objects_for_expression(DBOs& dbObjects, const with_t& e) { + return make_recursive_cte_db_objects(dbObjects, e.cte, std::index_sequence_for{}); + } +#endif + } +} diff --git a/dev/cte_types.h b/dev/cte_types.h new file mode 100644 index 000000000..7594da147 --- /dev/null +++ b/dev/cte_types.h @@ -0,0 +1,61 @@ +#pragma once + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#endif + +#include "functional/cxx_core_features.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "tuple_helper/tuple_fy.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +namespace sqlite_orm { + + namespace internal { + + /** + * Aliased column expression mapped into a CTE, stored as a field in a table column. + */ + template + struct aliased_field { + ~aliased_field() = delete; + aliased_field(const aliased_field&) = delete; + void operator=(const aliased_field&) = delete; + + F field; + }; + + /** + * This class captures various properties and aspects of a subselect's column expression, + * and is used as a proxy in table_t<>. + */ + template + class subselect_mapper { + public: + subselect_mapper() = delete; + + // this type name is used to detect the mapping from moniker to object + using cte_moniker_type = Moniker; + using fields_type = std::tuple; + // this type captures the expressions forming the columns in a subselect; + // it is currently unused, however proves to be useful in compilation errors, + // as it simplifies recognizing errors in column expressions + using expressions_tuple = tuplify_t; + // this type captures column reference expressions specified at CTE construction; + // those are: member pointers, alias holders + using explicit_colrefs_tuple = ExplicitColRefs; + // this type captures column reference expressions from the subselect; + // those are: member pointers, alias holders + using subselect_colrefs_tuple = SubselectColRefs; + // this type captures column reference expressions merged from SubselectColRefs and ExplicitColRefs + using final_colrefs_tuple = FinalColRefs; + }; + } +} +#endif diff --git a/dev/default_value_extractor.h b/dev/default_value_extractor.h index ae8d90e20..01002cdc4 100644 --- a/dev/default_value_extractor.h +++ b/dev/default_value_extractor.h @@ -10,8 +10,8 @@ namespace sqlite_orm { namespace internal { - template - std::string serialize(const T&, const serializer_context&); + template + auto serialize(const T& t, const C& context); /** * Serialize default value of a column's default valu diff --git a/dev/dbstat.h b/dev/eponymous_vtabs/dbstat.h similarity index 92% rename from dev/dbstat.h rename to dev/eponymous_vtabs/dbstat.h index ccc9d01f3..1b674ed9b 100644 --- a/dev/dbstat.h +++ b/dev/eponymous_vtabs/dbstat.h @@ -1,9 +1,11 @@ #pragma once +#ifdef SQLITE_ENABLE_DBSTAT_VTAB #include // std::string +#endif -#include "column.h" -#include "table.h" +#include "../schema/column.h" +#include "../schema/table.h" namespace sqlite_orm { #ifdef SQLITE_ENABLE_DBSTAT_VTAB diff --git a/dev/error_code.h b/dev/error_code.h index ece9d1ad5..7410c2687 100644 --- a/dev/error_code.h +++ b/dev/error_code.h @@ -137,7 +137,7 @@ namespace sqlite_orm { std::string get_error_message(sqlite3* db, T&&... args) { std::ostringstream stream; using unpack = int[]; - static_cast(unpack{0, (static_cast(static_cast(stream << args)), 0)...}); + (void)unpack{0, (stream << args, 0)...}; stream << sqlite3_errmsg(db); return stream.str(); } diff --git a/dev/expression.h b/dev/expression.h index b92fb0030..657bf56e2 100644 --- a/dev/expression.h +++ b/dev/expression.h @@ -1,16 +1,21 @@ #pragma once #include -#include // std::move, std::forward +#include // std::enable_if +#include // std::move, std::forward, std::declval #include "functional/cxx_optional.h" #include "functional/cxx_universal.h" -#include "operators.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "tags.h" namespace sqlite_orm { namespace internal { + template + struct in_t; + template struct and_condition_t; @@ -18,14 +23,12 @@ namespace sqlite_orm { struct or_condition_t; /** - * Is not an operator but a result of c(...) function. Has operator= overloaded which returns assign_t + * Result of c(...) function. Has operator= overloaded which returns assign_t */ template - struct expression_t : condition_t { + struct expression_t { T value; - expression_t(T value_) : value(std::move(value_)) {} - template assign_t operator=(R r) const { return {this->value, std::move(r)}; @@ -41,12 +44,12 @@ namespace sqlite_orm { #endif template in_t in(Args... args) const { - return {this->value, std::make_tuple(std::forward(args)...), false}; + return {this->value, {std::forward(args)...}, false}; } template in_t not_in(Args... args) const { - return {this->value, std::make_tuple(std::forward(args)...), true}; + return {this->value, {std::forward(args)...}, true}; } template @@ -60,6 +63,10 @@ namespace sqlite_orm { } }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + template T get_from_expression(T value) { return std::move(value); @@ -69,6 +76,9 @@ namespace sqlite_orm { T get_from_expression(expression_t expression) { return std::move(expression.value); } + + template + using unwrap_expression_t = decltype(get_from_expression(std::declval())); } /** diff --git a/dev/expression_object_type.h b/dev/expression_object_type.h index 3e54c1b7e..087602e3b 100644 --- a/dev/expression_object_type.h +++ b/dev/expression_object_type.h @@ -1,8 +1,9 @@ #pragma once -#include // std::decay +#include // std::decay, std::remove_reference #include // std::reference_wrapper +#include "type_traits.h" #include "prepared_statement.h" namespace sqlite_orm { @@ -13,58 +14,35 @@ namespace sqlite_orm { struct expression_object_type; template - struct expression_object_type> : std::decay {}; + using expression_object_type_t = typename expression_object_type::type; - template - struct expression_object_type>> : std::decay {}; + template + using statement_object_type_t = expression_object_type_t>>; template - struct expression_object_type> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type>> : std::decay {}; - - template - struct expression_object_type> { - using type = typename replace_range_t::object_type; - }; + struct expression_object_type, void> : value_unref_type {}; - template - struct expression_object_type, L, O>> { - using type = typename replace_range_t, L, O>::object_type; + template + struct expression_object_type> { + using type = object_type_t; }; template - struct expression_object_type> { - using type = T; - }; - - template - struct expression_object_type, Ids...>> { - using type = T; - }; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type>> : std::decay {}; - - template - struct expression_object_type> { - using type = typename insert_range_t::object_type; - }; - - template - struct expression_object_type, L, O>> { - using type = typename insert_range_t, L, O>::object_type; + struct expression_object_type> { + using type = object_type_t; }; template - struct expression_object_type> : std::decay {}; - - template - struct expression_object_type, Cols...>> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template struct get_ref_t { diff --git a/dev/field_printer.h b/dev/field_printer.h index 71d1eb220..6bd990348 100644 --- a/dev/field_printer.h +++ b/dev/field_printer.h @@ -1,18 +1,19 @@ #pragma once -#include // std::wstring_convert #include // std::string #include // std::stringstream #include // std::vector #include // std::shared_ptr, std::unique_ptr #ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert #include // std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT +#endif #include "functional/cxx_optional.h" #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" #include "is_std_ptr.h" +#include "type_traits.h" namespace sqlite_orm { @@ -24,18 +25,26 @@ namespace sqlite_orm { struct field_printer; namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_preparable_v`, `is_bindable_v`. + */ + template + struct indirectly_test_printable; + template SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v{})>> = true - // Also see implementation note for `is_bindable_v` - ; + SQLITE_ORM_INLINE_VAR constexpr bool + is_printable_v{})>>> = true; + template - using is_printable = polyfill::bool_constant>; + struct is_printable : polyfill::bool_constant> {}; } template - struct field_printer::value>> { + struct field_printer> { std::string operator()(const T& t) const { std::stringstream ss; ss << t; @@ -80,7 +89,7 @@ namespace sqlite_orm { }; template - struct field_printer::value>> { + struct field_printer> { std::string operator()(std::string string) const { return string; } @@ -102,7 +111,7 @@ namespace sqlite_orm { * Specialization for std::wstring (UTF-16 assumed). */ template - struct field_printer::value>> { + struct field_printer> { std::string operator()(const std::wstring& wideString) const { std::wstring_convert> converter; return converter.to_bytes(wideString); @@ -112,22 +121,22 @@ namespace sqlite_orm { template<> struct field_printer { std::string operator()(const nullptr_t&) const { - return "null"; + return "NULL"; } }; #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template<> struct field_printer { std::string operator()(const std::nullopt_t&) const { - return "null"; + return "NULL"; } }; #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template - struct field_printer< - T, - std::enable_if_t, - internal::is_printable>>>> { + struct field_printer, + internal::is_printable>>::value>> { using unqualified_type = std::remove_cv_t; std::string operator()(const T& t) const { diff --git a/dev/function.h b/dev/function.h index 83563221a..b26335ffc 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,73 +1,32 @@ #pragma once -#include -#include -#include // std::string -#include // std::tuple -#include // std::function -#include // std::min +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::copy_constructible +#endif +#include // std::tuple, std::tuple_size, std::tuple_element +#include // std::min, std::copy_n #include // std::move, std::forward -#include "functional/cxx_universal.h" +#include "functional/cxx_universal.h" // ::size_t, ::nullptr_t #include "functional/cxx_type_traits_polyfill.h" +#include "functional/cstring_literal.h" +#include "functional/function_traits.h" +#include "type_traits.h" +#include "tags.h" namespace sqlite_orm { struct arg_values; - template + // note (internal): forward declare even if `SQLITE_VERSION_NUMBER < 3020000` in order to simplify coding below + template struct pointer_arg; - template + // note (internal): forward declare even if `SQLITE_VERSION_NUMBER < 3020000` in order to simplify coding below + template class pointer_binding; namespace internal { - - struct user_defined_function_base { - using func_call = std::function< - void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; - using final_call = std::function; - - std::string name; - int argumentsCount = 0; - std::function create; - void (*destroy)(int*) = nullptr; - -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - user_defined_function_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : - name(std::move(name_)), - argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} -#endif - }; - - struct user_defined_scalar_function_t : user_defined_function_base { - func_call run; - - user_defined_scalar_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - run(std::move(run_)) {} - }; - - struct user_defined_aggregate_function_t : user_defined_function_base { - func_call step; - final_call finalCall; - - user_defined_aggregate_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - step(std::move(step_)), finalCall(std::move(finalCall_)) {} - }; - template using scalar_call_function_t = decltype(&F::operator()); @@ -77,16 +36,18 @@ namespace sqlite_orm { template using aggregate_fin_function_t = decltype(&F::fin); - template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v>> = - true; + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v>> = true; + + template + struct is_scalar_udf : polyfill::bool_constant> {}; - template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v< + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v< F, polyfill::void_t, aggregate_fin_function_t, @@ -94,153 +55,499 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; - template - struct member_function_arguments; + template + struct is_aggregate_udf : polyfill::bool_constant> {}; - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...) const; - using tuple_type = std::tuple...>; - using return_type = R; - }; + template + struct function; + } - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...); - using tuple_type = std::tuple...>; - using return_type = R; - }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a function signature (i.e. a function in the C++ type system). + */ + template + concept orm_function_sig = std::is_function_v; + + /** @short Specifies that a type is a classic function object. + * + * A classic function object meets the following requirements: + * - defines a single call operator `F::operator()` + * - isn't a traditional sqlite_orm scalar function (having a static `F::name()` function + */ + template + concept orm_classic_function_object = + ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && + /*rule out sqlite_orm scalar function*/ + (!requires { F::name(); })); + + /** @short Specifies that a type is a user-defined scalar function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::operator()()` call operator + */ + template + concept orm_scalar_udf = requires { + UDF::name(); + typename internal::scalar_call_function_t; + }; + + /** @short Specifies that a type is a user-defined aggregate function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::step()` member function + * - `UDF::fin()` member function + */ + template + concept orm_aggregate_udf = requires { + UDF::name(); + typename internal::aggregate_step_function_t; + typename internal::aggregate_fin_function_t; + requires std::is_member_function_pointer_v>; + requires std::is_member_function_pointer_v>; + }; + + /** @short Specifies that a type is a framed user-defined scalar function. + */ + template + concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && + orm_scalar_udf); + /** @short Specifies that a type is a framed user-defined aggregate function. + */ + template + concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && + orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; +#endif + + namespace internal { template struct callable_arguments_impl; template - struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + struct callable_arguments_impl> { + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template - struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + struct callable_arguments_impl> { + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires(std::is_function_v) + struct callable_arguments_impl { + using args_tuple = function_arguments; + using return_type = std::decay_t>; + }; +#endif + template struct callable_arguments : callable_arguments_impl {}; - template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ + template + struct udf_holder : private std::string { + using udf_type = UDF; + + using std::string::basic_string; + + const std::string& operator()() const { + return *this; + } + }; +#endif + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct udf_holder +#else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + struct udf_holder +#endif + { + using udf_type = UDF; + + template>::value, bool> = true> + decltype(auto) operator()() const { + return UDF::name(); + } + + template::value, bool> = true> + std::string operator()() const { + return std::string{UDF::name()}; + } + }; + + /* + * Represents a call of a user-defined function. + */ + template struct function_call { - using function_type = F; - using args_tuple = std::tuple; + using udf_type = UDF; + using args_tuple = std::tuple; - args_tuple args; + udf_holder name; + args_tuple callArgs; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + template struct unpacked_arg { using type = T; }; - template - struct unpacked_arg> { + template + struct unpacked_arg> { using type = typename callable_arguments::return_type; }; template using unpacked_arg_t = typename unpacked_arg::type; - template + template SQLITE_ORM_CONSTEVAL bool expected_pointer_value() { - static_assert(polyfill::always_false_v, "Expected a pointer value for I-th argument"); + static_assert(polyfill::always_false_v, "Expected a pointer value for I-th argument"); return false; } - template - constexpr bool is_same_pvt_v = expected_pointer_value(); + template + constexpr bool is_same_pvt_v = expected_pointer_value(); // Always allow binding nullptr to a pointer argument template constexpr bool is_same_pvt_v> = true; + // Always allow binding nullptr to a pointer argument + template + constexpr bool is_same_pvt_v, pointer_binding, void> = true; + + template + SQLITE_ORM_CONSTEVAL bool assert_same_pointer_data_type() { + constexpr bool valid = std::is_convertible::value; + static_assert(valid, "Pointer data types of I-th argument do not match"); + return valid; + } #if __cplusplus >= 201703L // C++17 or later template - SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() { + SQLITE_ORM_CONSTEVAL bool assert_same_pointer_tag() { constexpr bool valid = Binding == PointerArg; - static_assert(valid, "Pointer value types of I-th argument do not match"); + static_assert(valid, "Pointer types (tags) of I-th argument do not match"); return valid; } - template constexpr bool is_same_pvt_v> = - assert_same_pointer_type(); + assert_same_pointer_tag() && + assert_same_pointer_data_type(); #else template - SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() { + constexpr bool assert_same_pointer_tag() { constexpr bool valid = Binding::value == PointerArg::value; - static_assert(valid, "Pointer value types of I-th argument do not match"); + static_assert(valid, "Pointer types (tags) of I-th argument do not match"); return valid; } template constexpr bool is_same_pvt_v> = - assert_same_pointer_type(); + assert_same_pointer_tag(); #endif - template + // not a pointer value, currently leave it unchecked + template SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::false_type) { return true; } - template + // check the type of pointer values + template SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::true_type) { - return is_same_pvt_v; + return is_same_pvt_v; } - template + template SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { return true; } - template + template SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { - using func_arg_t = std::tuple_element_t; - using passed_arg_t = unpacked_arg_t>; + using func_param_type = std::tuple_element_t; + using call_arg_type = unpacked_arg_t>; #ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED constexpr bool valid = validate_pointer_value_type, + std::tuple_element_t, unpacked_arg_t>>( - polyfill::bool_constant < (polyfill::is_specialization_of_v) || - (polyfill::is_specialization_of_v) > {}); + polyfill::bool_constant < (polyfill::is_specialization_of_v) || + (polyfill::is_specialization_of_v) > {}); - return validate_pointer_value_types(polyfill::index_constant{}) && valid; + return validate_pointer_value_types(polyfill::index_constant{}) && valid; #else - return validate_pointer_value_types(polyfill::index_constant{}) && + return validate_pointer_value_types(polyfill::index_constant{}) && validate_pointer_value_type, + std::tuple_element_t, unpacked_arg_t>>( - polyfill::bool_constant < (polyfill::is_specialization_of_v) || - (polyfill::is_specialization_of_v) > {}); + polyfill::bool_constant < (polyfill::is_specialization_of_v) || + (polyfill::is_specialization_of_v) > {}); #endif } + + /* + * Note: Currently the number of call arguments is checked and whether the types of pointer values match, + * but other call argument types are not checked against the parameter types of the function. + */ + template +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + SQLITE_ORM_CONSTEVAL void check_function_call() { +#else + void check_function_call() { +#endif + using call_args_tuple = std::tuple; + using function_params_tuple = typename callable_arguments::args_tuple; + constexpr size_t callArgsCount = std::tuple_size::value; + constexpr size_t functionParamsCount = std::tuple_size::value; + static_assert(std::is_same>::value || + (callArgsCount == functionParamsCount && + validate_pointer_value_types( + polyfill::index_constant{})), + "Check the number and types of the function call arguments"); + } + + /* + * Generator of a user-defined function call in a sql query expression. + * + * Use the variable template `func<>` to instantiate. + * + * Calling the function captures the parameters in a `function_call` node. + */ + template + struct function { + using udf_type = UDF; + using callable_type = UDF; + + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } + + constexpr auto udf_holder() const { + return internal::udf_holder{}; + } + + // returns a character range + constexpr auto name() const { + return this->udf_holder()(); + } + }; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Generator of a user-defined function call in a sql query expression. + * + * Use the string literal operator template `""_scalar.quote()` to quote + * a freestanding function, stateless lambda or function object. + * + * Calling the function captures the parameters in a `function_call` node. + * + * Internal note: + * 1. Captures and represents a function [pointer or object], especially one without side effects. + * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, + * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. + * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the user-defined function. + */ + template + struct quoted_scalar_function { + using udf_type = Sig; + using callable_type = F; + + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } + + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (this->udf); + } else { + // non-const copy + return F(this->udf); + } + } + + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; + } + + constexpr auto name() const { + return this->nme; + } + + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, this->nme); + } + + F udf; + char nme[N]; + }; + + template + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; + + /* + * From a freestanding function, possibly overloaded. + */ + template + [[nodiscard]] consteval auto quote(F* callable) const { + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object instance. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(F callable) const { + using Sig = function_signature_type_t; + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(F callable) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + using Sig = function_signature_type_t; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + }; +#endif } - /** - * Used to call user defined function: `func(...);` + /** @short Call a user-defined function. + * + * Note: Currently the number of call arguments is checked and whether the types of pointer values match, + * but other call argument types are not checked against the parameter types of the function. + * + * Example: + * struct IdFunc { int oeprator(int arg)() const { return arg; } }; + * // inline: + * select(func(42)); + * // As this is a variable template, you can frame the user-defined function and define a variable for syntactic sugar and legibility: + * inline constexpr orm_scalar_function auto idfunc = func; + * select(idfunc(42)); + * */ - template - internal::function_call func(Args... args) { - using args_tuple = std::tuple; - using function_args_tuple = typename internal::callable_arguments::args_tuple; - constexpr auto argsCount = std::tuple_size::value; - constexpr auto functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - internal::validate_pointer_value_types( - polyfill::index_constant(functionArgsCount, argsCount) - 1>{})) || - std::is_same>::value, - "Number of arguments does not match"); - return {std::make_tuple(std::forward(args)...)}; + template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + requires(orm_scalar_udf || orm_aggregate_udf) +#endif + SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + inline namespace literals { + /* @short Create a scalar function from a freestanding function, stateless lambda or function object, + * and call such a user-defined function. + * + * If you need to pick a function or method from an overload set, or pick a template function you can + * specify an explicit function signature in the call to `from()`. + * + * Examples: + * // freestanding function from a library + * constexpr orm_quoted_scalar_function auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); + * // stateless lambda + * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { + * return errcode != 0; + * }); + * // function object instance + * constexpr orm_quoted_scalar_function auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); + * // function object + * constexpr orm_quoted_scalar_function auto equal_to_int_2_f = "equal_to"_scalar.quote>(); + * // pick function object's template call operator + * constexpr orm_quoted_scalar_function auto equal_to_int_3_f = "equal_to"_scalar.quote(std::equal_to{}); + * + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * + * auto rows = storage.select(clamp_int_f(0, 1, 1)); + * auto rows = storage.select(is_fatal_error_f(1)); + * auto rows = storage.select(equal_to_int_f(1, 1)); + * auto rows = storage.select(equal_to_int_2_f(1, 1)); + * auto rows = storage.select(equal_to_int_3_f(1, 1)); + */ + template + [[nodiscard]] consteval auto operator"" _scalar() { + return builder; + } } - +#endif } diff --git a/dev/functional/config.h b/dev/functional/config.h index eb36d578d..aa2ee2cba 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -1,21 +1,86 @@ -#pragma once - -#include "cxx_universal.h" - -#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED -#define SQLITE_ORM_INLINE_VAR inline -#else -#define SQLITE_ORM_INLINE_VAR -#endif - -#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(no_unique_address) >= 201803L -#define SQLITE_ORM_NOUNIQUEADDRESS [[no_unique_address]] -#else -#define SQLITE_ORM_NOUNIQUEADDRESS -#endif - -#ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED -#define SQLITE_ORM_CONSTEVAL consteval -#else -#define SQLITE_ORM_CONSTEVAL constexpr -#endif +#pragma once + +#include "cxx_universal.h" + +#if SQLITE_ORM_HAS_INCLUDE() +#include +#endif + +#ifdef SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 +#endif + +#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED +#define SQLITE_ORM_INLINE_VAR inline +#else +#define SQLITE_ORM_INLINE_VAR +#endif + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_IF constexpr +#else +#define SQLITE_ORM_CONSTEXPR_IF +#endif + +#if __cpp_lib_constexpr_functional >= 201907L +#define SQLITE_ORM_CONSTEXPR_CPP20 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_CPP20 +#endif + +#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(no_unique_address) >= 201803L +#define SQLITE_ORM_NOUNIQUEADDRESS [[no_unique_address]] +#else +#define SQLITE_ORM_NOUNIQUEADDRESS +#endif + +#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define SQLITE_ORM_CPP_LIKELY [[likely]] +#define SQLITE_ORM_CPP_UNLIKELY [[unlikely]] +#else +#define SQLITE_ORM_CPP_LIKELY +#define SQLITE_ORM_CPP_UNLIKELY +#endif + +#ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED +#define SQLITE_ORM_CONSTEVAL consteval +#else +#define SQLITE_ORM_CONSTEVAL constexpr +#endif + +#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && __cpp_lib_concepts >= 202002L +#define SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#endif + +#if __cpp_lib_ranges >= 201911L +#define SQLITE_ORM_CPP20_RANGES_SUPPORTED +#endif + +// C++20 or later (unfortunately there's no feature test macro). +// Stupidly, clang says C++20, but `std::default_sentinel_t` was only implemented in libc++ 13 and libstd++-v3 10 +// (the latter is used on Linux). +// gcc got it right and reports C++20 only starting with v10. +// The check here doesn't care and checks the library versions in use. +// +// Another way of detection might be the feature-test macro __cpp_lib_concepts +#if(__cplusplus >= 202002L) && \ + ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13000) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) +#define SQLITE_ORM_STL_HAS_DEFAULT_SENTINEL +#endif + +#if(defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED) && defined(SQLITE_ORM_INLINE_VARIABLES_SUPPORTED) && \ + defined(SQLITE_ORM_CONSTEVAL_SUPPORTED)) && \ + (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) +#define SQLITE_ORM_WITH_CPP20_ALIASES +#endif + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) +#define SQLITE_ORM_WITH_CTE +#endif + +// define the inline namespace "literals" so that it is available even if it was not introduced by a feature +namespace sqlite_orm { + inline namespace literals {} +} diff --git a/dev/functional/cstring_literal.h b/dev/functional/cstring_literal.h new file mode 100644 index 000000000..db1d8471f --- /dev/null +++ b/dev/functional/cstring_literal.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::index_sequence +#include // std::copy_n +#endif + +#include "cxx_universal.h" // ::size_t + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * Wraps a C string of fixed size. + * Its main purpose is to enable the user-defined string literal operator template. + */ + template + struct cstring_literal { + static constexpr size_t size() { + return N - 1; + } + + constexpr cstring_literal(const char (&cstr)[N]) { + std::copy_n(cstr, N, this->cstr); + } + + char cstr[N]; + }; + + template class Template, cstring_literal literal, size_t... Idx> + consteval auto explode_into(std::index_sequence) { + return Template{}; + } +} +#endif diff --git a/dev/functional/cxx_compiler_quirks.h b/dev/functional/cxx_compiler_quirks.h index e97b3e7ec..d2a0b2d85 100644 --- a/dev/functional/cxx_compiler_quirks.h +++ b/dev/functional/cxx_compiler_quirks.h @@ -1,42 +1,64 @@ -#pragma once - -/* - * This header defines macros for circumventing compiler quirks on which sqlite_orm depends. - * May amend cxx_core_features.h - */ - -#ifdef __clang__ -#define SQLITE_ORM_DO_PRAGMA(...) _Pragma(#__VA_ARGS__) -#endif - -#ifdef __clang__ -#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) \ - SQLITE_ORM_DO_PRAGMA(clang diagnostic push) \ - SQLITE_ORM_DO_PRAGMA(clang diagnostic ignored warnoption) \ - __VA_ARGS__ \ - SQLITE_ORM_DO_PRAGMA(clang diagnostic pop) - -#else -#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) __VA_ARGS__ -#endif - -// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. -// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, -// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: -// "The braces around the nested initializer lists may be elided (omitted), -// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, -// and the subsequent initializer clauses are used to initialize the following members of the object." -// In this sense clang should only warn about missing field initializers. -// Because we know what we are doing, we suppress the diagnostic message -#define SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(...) SQLITE_ORM_CLANG_SUPPRESS("-Wmissing-braces", __VA_ARGS__) - -#if defined(_MSC_VER) && (_MSC_VER < 1920) -#define SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION -#endif - -// clang 10 chokes on concepts that don't depend on template parameters; -// when it tries to instantiate an expression in a requires expression, which results in an error, -// the compiler reports an error instead of dismissing the templated function. -#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && (defined(__clang__) && (__clang_major__ == 10)) -#define SQLITE_ORM_BROKEN_NONTEMPLATE_CONCEPTS -#endif +#pragma once + +/* + * This header defines macros for circumventing compiler quirks on which sqlite_orm depends. + * May amend cxx_core_features.h + */ + +#ifdef __clang__ +#define SQLITE_ORM_DO_PRAGMA(...) _Pragma(#__VA_ARGS__) +#endif + +#ifdef __clang__ +#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) \ + SQLITE_ORM_DO_PRAGMA(clang diagnostic push) \ + SQLITE_ORM_DO_PRAGMA(clang diagnostic ignored warnoption) \ + __VA_ARGS__ \ + SQLITE_ORM_DO_PRAGMA(clang diagnostic pop) + +#else +#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) __VA_ARGS__ +#endif + +// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. +// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, +// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: +// "The braces around the nested initializer lists may be elided (omitted), +// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, +// and the subsequent initializer clauses are used to initialize the following members of the object." +// In this sense clang should only warn about missing field initializers. +// Because we know what we are doing, we suppress the diagnostic message +#define SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(...) SQLITE_ORM_CLANG_SUPPRESS("-Wmissing-braces", __VA_ARGS__) + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION +// Type replacement may fail if an alias template has a non-type template parameter from a dependent expression in it, +// `e.g. template using is_something = std::bool_constant>;` +// Remedy, e.g.: use a derived struct: `template struct is_somthing : std::bool_constant>;` +#define SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR +#endif + +// These compilers are known to have problems with alias templates in SFINAE contexts: +// clang 3.5 +// gcc 8.3 +// msvc 15.9 +// Type replacement may fail if an alias template has dependent expression or decltype in it. +// In these cases we have to use helper structures to break down the type alias. +// Note that the detection of specific compilers is so complicated because some compilers emulate other compilers, +// so we simply exclude all compilers that do not support C++20, even though this test is actually inaccurate. +#if(defined(_MSC_VER) && (_MSC_VER < 1920)) || (!defined(_MSC_VER) && (__cplusplus < 202002L)) +#define SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE +#endif + +// overwrite SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#if(__cpp_nontype_template_args < 201911L) && \ + (defined(__clang__) && (__clang_major__ >= 12) && (__cplusplus >= 202002L)) +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#endif + +// clang 10 chokes on concepts that don't depend on template parameters; +// when it tries to instantiate an expression in a requires expression, which results in an error, +// the compiler reports an error instead of dismissing the templated function. +#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && (defined(__clang__) && (__clang_major__ == 10)) +#define SQLITE_ORM_BROKEN_NONTEMPLATE_CONCEPTS +#endif diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 99474da39..86da3dcb3 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -1,67 +1,99 @@ -#pragma once - -/* - * This header detects core C++ language features on which sqlite_orm depends. - * May be updated/overwritten by cxx_compiler_quirks.h - */ - -#ifdef __has_cpp_attribute -#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) -#else -#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) 0L -#endif - -#ifdef __has_include -#define SQLITE_ORM_HAS_INCLUDE(file) __has_include(file) -#else -#define SQLITE_ORM_HAS_INCLUDE(file) 0L -#endif - -#if __cpp_aggregate_nsdmi >= 201304L -#define SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED -#endif - -#if __cpp_constexpr >= 201304L -#define SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED -#endif - -#if __cpp_noexcept_function_type >= 201510L -#define SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED -#endif - -#if __cpp_aggregate_bases >= 201603L -#define SQLITE_ORM_AGGREGATE_BASES_SUPPORTED -#endif - -#if __cpp_fold_expressions >= 201603L -#define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED -#endif - -#if __cpp_inline_variables >= 201606L -#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED -#endif - -#if __cpp_if_constexpr >= 201606L -#define SQLITE_ORM_IF_CONSTEXPR_SUPPORTED -#endif - -#if __cpp_inline_variables >= 201606L -#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED -#endif - -#if __cpp_generic_lambdas >= 201707L -#define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED -#else -#endif - -#if __cpp_consteval >= 201811L -#define SQLITE_ORM_CONSTEVAL_SUPPORTED -#endif - -#if __cpp_aggregate_paren_init >= 201902L -#define SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED -#endif - -#if __cpp_concepts >= 201907L -#define SQLITE_ORM_CONCEPTS_SUPPORTED -#endif +#pragma once + +/* + * This header detects core C++ language features on which sqlite_orm depends. + * May be updated/overwritten by cxx_compiler_quirks.h + */ + +#ifdef __has_cpp_attribute +#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) 0L +#endif + +#ifdef __has_include +#define SQLITE_ORM_HAS_INCLUDE(file) __has_include(file) +#else +#define SQLITE_ORM_HAS_INCLUDE(file) 0L +#endif + +#if __cpp_aggregate_nsdmi >= 201304L +#define SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED +#endif + +#if __cpp_constexpr >= 201304L +#define SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED +#endif + +#if __cpp_noexcept_function_type >= 201510L +#define SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED +#endif + +#if __cpp_aggregate_bases >= 201603L +#define SQLITE_ORM_AGGREGATE_BASES_SUPPORTED +#endif + +#if __cpp_fold_expressions >= 201603L +#define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED +#endif + +#if __cpp_constexpr >= 201603L +#define SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#endif + +#if __cpp_range_based_for >= 201603L +#define SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED +#endif + +#if __cpp_if_constexpr >= 201606L +#define SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#endif + +#if __cpp_inline_variables >= 201606L +#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED +#endif + +#if __cpp_structured_bindings >= 201606L +#define SQLITE_ORM_STRUCTURED_BINDINGS_SUPPORTED +#endif + +#if __cpp_generic_lambdas >= 201707L +#define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED +#else +#endif + +#if __cpp_init_captures >= 201803L +#define SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED +#endif + +#if __cpp_consteval >= 201811L +#define SQLITE_ORM_CONSTEVAL_SUPPORTED +#endif + +#if __cpp_char8_t >= 201811L +#define SQLITE_ORM_CHAR8T_SUPPORTED +#endif + +#if __cpp_aggregate_paren_init >= 201902L +#define SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED +#endif + +#if __cpp_concepts >= 201907L +#define SQLITE_ORM_CONCEPTS_SUPPORTED +#endif + +#if __cpp_nontype_template_args >= 201911L +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#endif + +#if __cpp_nontype_template_args >= 201911L +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#endif + +#if __cpp_pack_indexing >= 202311L +#define SQLITE_ORM_PACK_INDEXING_SUPPORTED +#endif + +#if __cplusplus >= 202002L +#define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED +#endif diff --git a/dev/functional/cxx_functional_polyfill.h b/dev/functional/cxx_functional_polyfill.h index 5380f2fcc..723923633 100644 --- a/dev/functional/cxx_functional_polyfill.h +++ b/dev/functional/cxx_functional_polyfill.h @@ -5,9 +5,7 @@ #endif #include // std::forward -#if __cpp_lib_invoke < 201411L #include "cxx_type_traits_polyfill.h" -#endif #include "../member_traits/member_traits.h" namespace sqlite_orm { @@ -19,9 +17,9 @@ namespace sqlite_orm { // gcc got it right and reports C++20 only starting with v10. // The check here doesn't care and checks the library versions in use. // - // Another way of detection would be the constrained algorithms feature macro __cpp_lib_ranges + // Another way of detection would be the constrained algorithms feature-test macro __cpp_lib_ranges #if(__cplusplus >= 202002L) && \ - ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) + ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13000) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) using std::identity; #else struct identity { @@ -61,9 +59,9 @@ namespace sqlite_orm { template>, - std::reference_wrapper>>, + std::reference_wrapper>>::value, bool> = true> decltype(auto) invoke(Callable&& callable, std::reference_wrapper wrapper, Args&&... args) { return invoke(std::forward(callable), wrapper.get(), std::forward(args)...); diff --git a/dev/functional/cxx_optional.h b/dev/functional/cxx_optional.h index 55d3cb87d..627a4fb58 100644 --- a/dev/functional/cxx_optional.h +++ b/dev/functional/cxx_optional.h @@ -1,11 +1,11 @@ -#pragma once - -#include "cxx_core_features.h" - -#if SQLITE_ORM_HAS_INCLUDE() -#include -#endif - -#if __cpp_lib_optional >= 201606L -#define SQLITE_ORM_OPTIONAL_SUPPORTED -#endif +#pragma once + +#include "cxx_core_features.h" + +#if SQLITE_ORM_HAS_INCLUDE() +#include +#endif + +#if __cpp_lib_optional >= 201606L +#define SQLITE_ORM_OPTIONAL_SUPPORTED +#endif diff --git a/dev/functional/cxx_string_view.h b/dev/functional/cxx_string_view.h index 3be0f9a63..9fedcce7c 100644 --- a/dev/functional/cxx_string_view.h +++ b/dev/functional/cxx_string_view.h @@ -1,11 +1,11 @@ -#pragma once - -#include "cxx_core_features.h" - -#if SQLITE_ORM_HAS_INCLUDE() -#include -#endif - -#if __cpp_lib_string_view >= 201606L -#define SQLITE_ORM_STRING_VIEW_SUPPORTED -#endif +#pragma once + +#include "cxx_core_features.h" + +#if SQLITE_ORM_HAS_INCLUDE() +#include +#endif + +#if __cpp_lib_string_view >= 201606L +#define SQLITE_ORM_STRING_VIEW_SUPPORTED +#endif diff --git a/dev/functional/cxx_tuple_polyfill.h b/dev/functional/cxx_tuple_polyfill.h new file mode 100644 index 000000000..e77fe2ae5 --- /dev/null +++ b/dev/functional/cxx_tuple_polyfill.h @@ -0,0 +1,34 @@ +#pragma once + +#include // std::apply; std::tuple_size +#if __cpp_lib_apply < 201603L +#include // std::forward, std::index_sequence, std::make_index_sequence +#endif + +#include "../functional/cxx_universal.h" // ::size_t +#include "../functional/cxx_functional_polyfill.h" // std::invoke + +namespace sqlite_orm { + namespace internal { + namespace polyfill { +#if __cpp_lib_apply >= 201603L + using std::apply; +#else + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl, std::index_sequence) { + return polyfill::invoke(std::forward(callable), std::get(std::forward(tpl))...); + } + + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl) { + constexpr size_t size = std::tuple_size>::value; + return apply(std::forward(callable), + std::forward(tpl), + std::make_index_sequence{}); + } +#endif + } + } + + namespace polyfill = internal::polyfill; +} diff --git a/dev/functional/cxx_type_traits_polyfill.h b/dev/functional/cxx_type_traits_polyfill.h index 7c19451c5..e5c985696 100644 --- a/dev/functional/cxx_type_traits_polyfill.h +++ b/dev/functional/cxx_type_traits_polyfill.h @@ -1,145 +1,153 @@ -#pragma once -#include - -#include "cxx_universal.h" - -namespace sqlite_orm { - namespace internal { - namespace polyfill { -#if __cpp_lib_void_t >= 201411L - using std::void_t; -#else - template - using void_t = void; -#endif - -#if __cpp_lib_bool_constant >= 201505L - using std::bool_constant; -#else - template - using bool_constant = std::integral_constant; -#endif - -#if __cpp_lib_logical_traits >= 201510L && __cpp_lib_type_trait_variable_templates >= 201510L - using std::conjunction; - using std::conjunction_v; - using std::disjunction; - using std::disjunction_v; - using std::negation; - using std::negation_v; -#else - template - struct conjunction : std::true_type {}; - template - struct conjunction : B1 {}; - template - struct conjunction : std::conditional_t, B1> {}; - template - SQLITE_ORM_INLINE_VAR constexpr bool conjunction_v = conjunction::value; - - template - struct disjunction : std::false_type {}; - template - struct disjunction : B1 {}; - template - struct disjunction : std::conditional_t> {}; - template - SQLITE_ORM_INLINE_VAR constexpr bool disjunction_v = disjunction::value; - - template - struct negation : bool_constant {}; - template - SQLITE_ORM_INLINE_VAR constexpr bool negation_v = negation::value; -#endif - -#if __cpp_lib_remove_cvref >= 201711L - using std::remove_cvref, std::remove_cvref_t; -#else - template - struct remove_cvref : std::remove_cv> {}; - - template - using remove_cvref_t = typename remove_cvref::type; -#endif - -#if __cpp_lib_type_identity >= 201806L - using std::type_identity, std::type_identity_t; -#else - template - struct type_identity { - using type = T; - }; - - template - using type_identity_t = typename type_identity::type; -#endif - -#if 0 // __cpp_lib_detect >= 0L // library fundamentals TS v2, [meta.detect] - using std::nonesuch; - using std::detector; - using std::is_detected, std::is_detected_v; - using std::detected, std::detected_t; - using std::detected_or, std::detected_or_t; -#else - struct nonesuch { - ~nonesuch() = delete; - nonesuch(const nonesuch&) = delete; - void operator=(const nonesuch&) = delete; - }; - - template class Op, class... Args> - struct detector { - using value_t = std::false_type; - using type = Default; - }; - - template class Op, class... Args> - struct detector>, Op, Args...> { - using value_t = std::true_type; - using type = Op; - }; - - template class Op, class... Args> - using is_detected = typename detector::value_t; - - template class Op, class... Args> - using detected = detector; - - template class Op, class... Args> - using detected_t = typename detector::type; - - template class Op, class... Args> - using detected_or = detector; - - template class Op, class... Args> - using detected_or_t = typename detected_or::type; - - template class Op, class... Args> - SQLITE_ORM_INLINE_VAR constexpr bool is_detected_v = is_detected::value; -#endif - -#if 0 // proposed but not pursued - using std::is_specialization_of, std::is_specialization_of_t, std::is_specialization_of_v; -#else - // is_specialization_of: https://github.com/cplusplus/papers/issues/812 - - template class Primary> - SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v = false; - - template class Primary, class... Types> - SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v, Primary> = true; - - template class Primary> - struct is_specialization_of : bool_constant> {}; -#endif - - template - SQLITE_ORM_INLINE_VAR constexpr bool always_false_v = false; - - template - using index_constant = std::integral_constant; - } - } - - namespace polyfill = internal::polyfill; -} +#pragma once +#include + +#include "cxx_universal.h" +#include "mpl/conditional.h" + +namespace sqlite_orm { + namespace internal { + namespace polyfill { +#if __cpp_lib_void_t >= 201411L + using std::void_t; +#else + /* + * Implementation note: Conservative implementation due to CWG issue 1558 (Unused arguments in alias template specializations). + */ + template + struct always_void { + using type = void; + }; + template + using void_t = typename always_void::type; +#endif + +#if __cpp_lib_bool_constant >= 201505L + using std::bool_constant; +#else + template + using bool_constant = std::integral_constant; +#endif + +#if __cpp_lib_logical_traits >= 201510L && __cpp_lib_type_trait_variable_templates >= 201510L + using std::conjunction; + using std::conjunction_v; + using std::disjunction; + using std::disjunction_v; + using std::negation; + using std::negation_v; +#else + template + struct conjunction : std::true_type {}; + template + struct conjunction : B1 {}; + template + struct conjunction : mpl::conditional_t, B1> {}; + template + SQLITE_ORM_INLINE_VAR constexpr bool conjunction_v = conjunction::value; + + template + struct disjunction : std::false_type {}; + template + struct disjunction : B1 {}; + template + struct disjunction : mpl::conditional_t> {}; + template + SQLITE_ORM_INLINE_VAR constexpr bool disjunction_v = disjunction::value; + + template + struct negation : bool_constant {}; + template + SQLITE_ORM_INLINE_VAR constexpr bool negation_v = negation::value; +#endif + +#if __cpp_lib_remove_cvref >= 201711L + using std::remove_cvref, std::remove_cvref_t; +#else + template + struct remove_cvref : std::remove_cv> {}; + + template + using remove_cvref_t = typename remove_cvref::type; +#endif + +#if __cpp_lib_type_identity >= 201806L + using std::type_identity, std::type_identity_t; +#else + template + struct type_identity { + using type = T; + }; + + template + using type_identity_t = typename type_identity::type; +#endif + +#if 0 // __cpp_lib_detect >= 0L // library fundamentals TS v2, [meta.detect] + using std::nonesuch; + using std::detector; + using std::is_detected, std::is_detected_v; + using std::detected, std::detected_t; + using std::detected_or, std::detected_or_t; +#else + struct nonesuch { + ~nonesuch() = delete; + nonesuch(const nonesuch&) = delete; + void operator=(const nonesuch&) = delete; + }; + + template class Op, class... Args> + struct detector { + using value_t = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value_t; + + template class Op, class... Args> + using detected = detector; + + template class Op, class... Args> + using detected_t = typename detector::type; + + template class Op, class... Args> + using detected_or = detector; + + template class Op, class... Args> + using detected_or_t = typename detected_or::type; + + template class Op, class... Args> + SQLITE_ORM_INLINE_VAR constexpr bool is_detected_v = is_detected::value; +#endif + +#if 0 // proposed but not pursued + using std::is_specialization_of, std::is_specialization_of_t, std::is_specialization_of_v; +#else + // is_specialization_of: https://github.com/cplusplus/papers/issues/812 + + template class Primary> + SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v = false; + + template class Primary, class... Types> + SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v, Primary> = true; + + template class Primary> + struct is_specialization_of : bool_constant> {}; +#endif + + template + SQLITE_ORM_INLINE_VAR constexpr bool always_false_v = false; + + template + using index_constant = std::integral_constant; + } + } + + namespace polyfill = internal::polyfill; +} diff --git a/dev/functional/cxx_universal.h b/dev/functional/cxx_universal.h index 90c143299..dd2b4dc20 100644 --- a/dev/functional/cxx_universal.h +++ b/dev/functional/cxx_universal.h @@ -1,20 +1,20 @@ -#pragma once - -/* - * This header makes central C++ functionality on which sqlite_orm depends universally available: - * - alternative operator representations - * - ::size_t, ::ptrdiff_t, ::nullptr_t - * - C++ core language feature macros - * - macros for dealing with compiler quirks - */ - -#include // alternative operator representations -#include // sqlite_orm is using size_t, ptrdiff_t, nullptr_t everywhere, pull it in early - -// earlier clang versions didn't make nullptr_t available in the global namespace via stddef.h, -// though it should have according to C++ documentation (see https://en.cppreference.com/w/cpp/types/nullptr_t#Notes). -// actually it should be available when including stddef.h -using std::nullptr_t; - -#include "cxx_core_features.h" -#include "cxx_compiler_quirks.h" +#pragma once + +/* + * This header makes central C++ functionality on which sqlite_orm depends universally available: + * - alternative operator representations + * - ::size_t, ::ptrdiff_t, ::nullptr_t + * - C++ core language feature macros + * - macros for dealing with compiler quirks + */ + +#include // alternative operator representations +#include // sqlite_orm is using size_t, ptrdiff_t, nullptr_t everywhere, pull it in early + +// earlier clang versions didn't make nullptr_t available in the global namespace via stddef.h, +// though it should have according to C++ documentation (see https://en.cppreference.com/w/cpp/types/nullptr_t#Notes). +// actually it should be available when including stddef.h +using std::nullptr_t; + +#include "cxx_core_features.h" +#include "cxx_compiler_quirks.h" diff --git a/dev/functional/function_traits.h b/dev/functional/function_traits.h new file mode 100644 index 000000000..a5f6a642b --- /dev/null +++ b/dev/functional/function_traits.h @@ -0,0 +1,85 @@ +#pragma once + +#include "cxx_type_traits_polyfill.h" +#include "mpl.h" + +namespace sqlite_orm { + namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ + template + struct function_traits; + + /* + * A function's return type + */ + template + using function_return_type_t = typename function_traits::return_type; + + /* + * A function's arguments tuple + */ + template + class Tuple, + template class ProjectOp = polyfill::type_identity_t> + using function_arguments = typename function_traits::template arguments_tuple; + + /* + * A function's signature + */ + template + using function_signature_type_t = typename function_traits::signature_type; + + template + struct function_traits { + using return_type = R; + + template class Tuple, template class ProjectOp> + using arguments_tuple = Tuple...>; + + using signature_type = R(Args...); + }; + + // non-exhaustive partial specializations of `function_traits` + + template + struct function_traits : function_traits { + using signature_type = R(Args...) const; + }; + +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; + + template + struct function_traits : function_traits { + using signature_type = R(Args...) const noexcept; + }; +#endif + + /* + * Pick signature of function pointer + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of pointer-to-member function + */ + template + struct function_traits : function_traits {}; + } +} diff --git a/dev/functional/index_sequence_util.h b/dev/functional/index_sequence_util.h index b13343663..cf51a23f1 100644 --- a/dev/functional/index_sequence_util.h +++ b/dev/functional/index_sequence_util.h @@ -1,18 +1,47 @@ #pragma once -#include // std::index_sequence, std::make_index_sequence +#include // std::index_sequence -#include "../functional/cxx_universal.h" +#include "../functional/cxx_universal.h" // ::size_t namespace sqlite_orm { namespace internal { +#if defined(SQLITE_ORM_PACK_INDEXING_SUPPORTED) /** - * Get the first value of an index_sequence. + * Get the index value of an `index_sequence` at a specific position. */ - template - SQLITE_ORM_CONSTEVAL size_t first_index_sequence_value(std::index_sequence) { + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + return Idx...[Pos]; + } +#elif defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + /** + * Get the index value of an `index_sequence` at a specific position. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + static_assert(Pos < sizeof...(Idx)); +#ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED + size_t result; +#else + size_t result = 0; +#endif + size_t i = 0; + // note: `(void)` cast silences warning 'expression result unused' + (void)((result = Idx, i++ == Pos) || ...); + return result; + } +#else + /** + * Get the index value of an `index_sequence` at a specific position. + * `Pos` must always be `0`. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + static_assert(Pos == 0, ""); return I; } +#endif template struct flatten_idxseq { diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index 26006177a..06cb4b09e 100644 --- a/dev/functional/mpl.h +++ b/dev/functional/mpl.h @@ -1,306 +1,543 @@ -#pragma once - -/* - * Symbols for 'template metaprogramming' (compile-time template programming), - * inspired by the MPL of Aleksey Gurtovoy and David Abrahams. - * - * Currently, the focus is on facilitating advanced type filtering, - * such as filtering columns by constraints having various traits. - * Hence it contains only a very small subset of a full MPL. - * - * Two key concepts are critical to understanding: - * 1. A 'metafunction' is a class template that represents a function invocable at compile-time. - * 2. A 'metafunction class' is a certain form of metafunction representation that enables higher-order metaprogramming. - * More precisely, it's a class with a nested metafunction called "fn" - * Correspondingly, a metafunction class invocation is defined as invocation of its nested "fn" metafunction. - * 3. A 'metafunction operation' is an alias template that represents a function whose instantiation already yields a type. - * - * Conventions: - * - "Fn" is the name for a metafunction template template parameter. - * - "FnCls" is the name for a metafunction class template parameter. - * - "_fn" is a suffix for a type that accepts metafunctions and turns them into metafunction classes. - * - "higher order" denotes a metafunction that operates on another metafunction (i.e. takes it as an argument). - */ - -#include // std::false_type, std::true_type - -#include "cxx_universal.h" -#include "cxx_type_traits_polyfill.h" - -namespace sqlite_orm { - namespace internal { - namespace mpl { - template class Fn> - struct indirectly_test_metafunction; - - /* - * Determines whether a class template has a nested metafunction `fn`. - * - * Implementation note: the technique of specialiazing on the inline variable must come first because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION]. - */ - template - SQLITE_ORM_INLINE_VAR constexpr bool is_metafunction_class_v = false; - template - SQLITE_ORM_INLINE_VAR constexpr bool - is_metafunction_class_v>> = - true; - - template - struct is_metafunction_class : polyfill::bool_constant> {}; - - /* - * Invoke metafunction. - */ - template class Fn, class... Args> - using invoke_fn_t = typename Fn::type; - -#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - template class Op, class... Args> - struct wrap_op { - using type = Op; - }; - - /* - * Invoke metafunction operation. - * - * Note: legacy compilers need an extra layer of indirection, otherwise type replacement may fail - * if alias template `Op` has a dependent expression in it. - */ - template class Op, class... Args> - using invoke_op_t = typename wrap_op::type; -#else - /* - * Invoke metafunction operation. - */ - template class Op, class... Args> - using invoke_op_t = Op; -#endif - - /* - * Invoke metafunction class by invoking its nested metafunction. - */ - template - using invoke_t = typename FnCls::template fn::type; - - /* - * Instantiate metafunction class' nested metafunction. - */ - template - using instantiate = typename FnCls::template fn; - - /* - * Wrap given type such that `typename T::type` is valid. - */ - template - struct type_wrap : polyfill::type_identity {}; - template - struct type_wrap> : T {}; - - /* - * Turn metafunction into a metafunction class. - * - * Invocation of the nested metafunction `fn` is SFINAE-friendly (detection idiom). - * This is necessary because `fn` is a proxy to the originally quoted metafunction, - * and the instantiation of the metafunction might be an invalid expression. - */ - template class Fn> - struct quote_fn { - template class, class...> - struct invoke_fn; - - template class F, class... Args> - struct invoke_fn>, F, Args...> { - using type = type_wrap>; - }; - - template - using fn = typename invoke_fn::type; - }; - - /* - * Indirection wrapper for higher-order metafunctions, - * specialized on the argument indexes where metafunctions appear. - */ - template - struct higherorder; - - template<> - struct higherorder<0u> { - /* - * Turn higher-order metafunction into a metafunction class. - */ - template class Fn, class... Args2> class HigherFn> - struct quote_fn { - template - struct fn : HigherFn {}; - }; - }; - - /* - * Metafunction class that extracts the nested metafunction of its metafunction class argument, - * quotes the extracted metafunction and passes it on to the next metafunction class - * (kind of the inverse of quoting). - */ - template - struct pass_extracted_fn_to { - template - struct fn : FnCls::template fn {}; - - // extract, quote, pass on - template class Fn, class... Args> - struct fn> : FnCls::template fn> {}; - }; - - /* - * Metafunction class that invokes the specified metafunction operation, - * and passes its result on to the next metafunction class. - */ - template class Op, class FnCls> - struct pass_result_to { - // call Op, pass on its result - template - struct fn : FnCls::template fn> {}; - }; - - /* - * Bind arguments at the front of a metafunction class. - * Metafunction class equivalent to std::bind_front(). - */ - template - struct bind_front { - template - struct fn : FnCls::template fn {}; - }; - - /* - * Bind arguments at the back of a metafunction class. - * Metafunction class equivalent to std::bind_back() - */ - template - struct bind_back { - template - struct fn : FnCls::template fn {}; - }; - - /* - * Metafunction class equivalent to polyfill::always_false. - * It ignores arguments passed to the metafunction, - * and always returns the given type. - */ - template - struct always { - template - struct fn : type_wrap {}; - }; - - /* - * Unary metafunction class equivalent to std::type_identity. - */ - struct identity { - template - struct fn : type_wrap {}; - }; - - /* - * Metafunction class equivalent to std::negation. - */ - template - struct not_ { - template - struct fn : polyfill::negation> {}; - }; - - /* - * Metafunction class equivalent to std::conjunction - */ - template - struct conjunction { - template - struct fn : polyfill::conjunction...> {}; - }; - - /* - * Metafunction class equivalent to std::disjunction. - */ - template - struct disjunction { - template - struct fn : polyfill::disjunction...> {}; - }; - -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - /* - * Metafunction equivalent to std::conjunction. - */ - template class... TraitFn> - using conjunction_fn = conjunction...>; - - /* - * Metafunction equivalent to std::disjunction. - */ - template class... TraitFn> - using disjunction_fn = disjunction...>; -#else - template class... TraitFn> - struct conjunction_fn : conjunction...> {}; - - template class... TraitFn> - struct disjunction_fn : disjunction...> {}; -#endif - - /* - * Convenience template alias for binding arguments at the front of a metafunction. - */ - template class Fn, class... Bound> - using bind_front_fn = bind_front, Bound...>; - - /* - * Convenience template alias for binding arguments at the back of a metafunction. - */ - template class Fn, class... Bound> - using bind_back_fn = bind_back, Bound...>; - - /* - * Convenience template alias for binding a metafunction at the front of a higher-order metafunction. - */ - template class Fn, class... Args2> class HigherFn, - template - class BoundFn, - class... Bound> - using bind_front_higherorder_fn = - bind_front::quote_fn, quote_fn, Bound...>; - } - } - - namespace mpl = internal::mpl; - - // convenience metafunction classes - namespace internal { - /* - * Trait metafunction class that checks if a type has the specified trait. - */ - template class TraitFn> - using check_if = mpl::quote_fn; - - /* - * Trait metafunction class that checks if a type doesn't have the specified trait. - */ - template class TraitFn> - using check_if_not = mpl::not_>; - - /* - * Trait metafunction class that checks if a type is the same as the specified type. - */ - template - using check_if_is_type = mpl::bind_front_fn; - - /* - * Trait metafunction class that checks if a type's template matches the specified template - * (similar to `is_specialization_of`). - */ - template class Template> - using check_if_is_template = - mpl::pass_extracted_fn_to>>; - } -} +#pragma once + +/* + * Symbols for 'template metaprogramming' (compile-time template programming), + * inspired by the MPL of Aleksey Gurtovoy and David Abrahams, and the Mp11 of Peter Dimov and Bjorn Reese. + * + * Currently, the focus is on facilitating advanced type filtering, + * such as filtering columns by constraints having various traits. + * Hence it contains only a very small subset of a full MPL. + * + * Three key concepts are critical to understanding: + * 1. A 'trait' is a class template with a nested `type` typename. + * The term 'trait' might be too narrow or not entirely accurate, however in the STL those class templates are summarized as "Type transformations". + * hence being "transformation type traits". + * It was the traditional way of transforming types before the arrival of alias templates. + * E.g. `template struct x { using type = T; };` + * They are of course still available today, but are rather used as building blocks. + * 2. A 'metafunction' is an alias template for a class template or a nested template expression, whose instantiation yields a type. + * E.g. `template using alias_op_t = typename x::type` + * 3. A 'quoted metafunction' (aka 'metafunction class') is a certain form of metafunction representation that enables higher-order metaprogramming. + * More precisely, it's a class with a nested metafunction called "fn". + * Correspondingly, a quoted metafunction invocation is defined as invocation of its nested "fn" metafunction. + * + * Conventions: + * - "Fn" is the name of a template template parameter for a metafunction. + * - "Q" is the name of class template parameter for a quoted metafunction. + * - "_fn" is a suffix for a class or alias template that accepts metafunctions and turns them into quoted metafunctions. + * - "higher order" denotes a metafunction that operates on another metafunction (i.e. takes it as an argument). + */ + +#include // std::true_type, std::false_type, std::is_same, std::negation, std::conjunction, std::disjunction +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED +#include +#else +#include +#endif + +#include "cxx_universal.h" // ::size_t +#include "cxx_type_traits_polyfill.h" +#include "mpl/conditional.h" + +namespace sqlite_orm { + namespace internal { + namespace mpl { + template class Fn> + struct indirectly_test_metafunction; + + /* + * Determines whether a class template has a nested metafunction `fn`. + * + * Implementation note: the technique of specialiazing on the inline variable must come first because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_quoted_metafuntion_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_quoted_metafuntion_v>> = true; + + template + struct is_quoted_metafuntion : polyfill::bool_constant> {}; + + /* + * Type pack. + */ + template + struct pack {}; + + /* + * The indirection through `defer_fn` works around the language inability + * to expand `Args...` into a fixed parameter list of an alias template. + * + * Also, legacy compilers need an extra layer of indirection, otherwise type replacement may fail + * if alias template `Fn` has a dependent expression in it. + */ + template class Fn, class... Args> + struct defer_fn { + using type = Fn; + }; + + /* + * The indirection through `defer` works around the language inability + * to expand `Args...` into a fixed parameter list of an alias template. + */ + template + struct defer { + using type = typename Q::template fn; + }; + + /* + * Invoke metafunction. + */ + template class Fn, class... Args> + using invoke_fn_t = typename defer_fn::type; + + /* + * Invoke quoted metafunction by invoking its nested metafunction. + */ + template + using invoke_t = typename defer::type; + + /* + * Turn metafunction into a quoted metafunction. + * + * Invocation of the nested metafunction `fn` is SFINAE-friendly (detection idiom). + * This is necessary because `fn` is a proxy to the originally quoted metafunction, + * and the instantiation of the metafunction might be an invalid expression. + */ + template class Fn> + struct quote_fn { + template class, class...> + struct invoke_this_fn { + // error N: 'type': is not a member of any direct or indirect base class of 'quote_fn::invoke_this_fn' + // means that the metafunction cannot be called with the passed arguments. + }; + + template class F, class... Args> + struct invoke_this_fn>, F, Args...> { + using type = F; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + /* + * Indirection wrapper for higher-order metafunctions, + * specialized on the argument indexes where metafunctions appear. + */ + template + struct higherorder; + + template<> + struct higherorder<0u> { + template class Fn, class... Args2> class HigherFn, class Q, class... Args> + struct defer_higher_fn { + using type = HigherFn; + }; + + /* + * Turn higher-order metafunction into a quoted metafunction. + */ + template class Fn, class... Args2> class HigherFn> + struct quote_fn { + template + using fn = typename defer_higher_fn::type; + }; + }; + + /* + * Quoted metafunction that extracts the nested metafunction of its quoted metafunction argument, + * quotes the extracted metafunction and passes it on to the next quoted metafunction + * (kind of the inverse of quoting). + */ + template + struct pass_extracted_fn_to { + template + struct invoke_this_fn { + using type = typename Q::template fn; + }; + + // extract class template, quote, pass on + template class Fn, class... T> + struct invoke_this_fn> { + using type = typename Q::template fn>; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + /* + * Quoted metafunction that invokes the specified quoted metafunctions, + * and passes their results on to the next quoted metafunction. + */ + template + struct pass_result_of { + // invoke `Fn`, pass on their result + template + using fn = typename Q::template fn::type...>; + }; + + /* + * Quoted metafunction that invokes the specified metafunctions, + * and passes their results on to the next quoted metafunction. + */ + template class... Fn> + using pass_result_of_fn = pass_result_of...>; + + /* + * Bind arguments at the front of a quoted metafunction. + */ + template + struct bind_front { + template + using fn = typename Q::template fn; + }; + + /* + * Bind arguments at the back of a quoted metafunction. + */ + template + struct bind_back { + template + using fn = typename Q::template fn; + }; + + /* + * Quoted metafunction equivalent to `polyfill::always_false`. + * It ignores arguments passed to the metafunction, and always returns the specified type. + */ + template + struct always { + template + using fn = T; + }; + + /* + * Unary quoted metafunction equivalent to `std::type_identity_t`. + */ + using identity = quote_fn; + + /* + * Quoted metafunction equivalent to `std::negation`. + */ + template + using not_ = pass_result_of, TraitQ>; + + /* + * Quoted metafunction equivalent to `std::conjunction`. + */ + template + struct conjunction { + template + using fn = std::true_type; + }; + + template + struct conjunction { + // match last or `std::false_type` + template + struct invoke_this_fn { + static_assert(std::is_same::value || + std::is_same::value, + "Resulting trait must be a std::bool_constant"); + using type = ResultTrait; + }; + + // match `std::true_type` and one or more remaining + template + struct invoke_this_fn, std::true_type, NextQ, RestQ...> + : invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + RestQ...> {}; + + template + using fn = typename invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + TraitQ...>::type; + }; + + /* + * Quoted metafunction equivalent to `std::disjunction`. + */ + template + struct disjunction { + template + using fn = std::false_type; + }; + + template + struct disjunction { + // match last or `std::true_type` + template + struct invoke_this_fn { + static_assert(std::is_same::value || + std::is_same::value, + "Resulting trait must be a std::bool_constant"); + using type = ResultTrait; + }; + + // match `std::false_type` and one or more remaining + template + struct invoke_this_fn, std::false_type, NextQ, RestQ...> + : invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + RestQ...> {}; + + template + using fn = typename invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + TraitQ...>::type; + }; + + /* + * Metafunction equivalent to `std::conjunction`. + */ + template class... TraitFn> + using conjunction_fn = pass_result_of_fn, TraitFn...>; + + /* + * Metafunction equivalent to `std::disjunction`. + */ + template class... TraitFn> + using disjunction_fn = pass_result_of_fn, TraitFn...>; + + /* + * Metafunction equivalent to `std::negation`. + */ + template class Fn> + using not_fn = pass_result_of_fn, Fn>; + + /* + * Bind arguments at the front of a metafunction. + */ + template class Fn, class... Bound> + using bind_front_fn = bind_front, Bound...>; + + /* + * Bind arguments at the back of a metafunction. + */ + template class Fn, class... Bound> + using bind_back_fn = bind_back, Bound...>; + + /* + * Bind a metafunction and arguments at the front of a higher-order metafunction. + */ + template class Fn, class... Args2> class HigherFn, + template + class BoundFn, + class... Bound> + using bind_front_higherorder_fn = + bind_front::quote_fn, quote_fn, Bound...>; + +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + constexpr size_t find_first_true_helper(std::initializer_list values) { + size_t i = 0; + for(auto first = values.begin(); first != values.end() && !*first; ++first) { + ++i; + } + return i; + } + + constexpr size_t count_true_helper(std::initializer_list values) { + size_t n = 0; + for(auto first = values.begin(); first != values.end(); ++first) { + n += *first; + } + return n; + } +#else + template + constexpr size_t find_first_true_helper(const std::array& values, size_t i = 0) { + return i == N || values[i] ? 0 : 1 + find_first_true_helper(values, i + 1); + } + + template + constexpr size_t count_true_helper(const std::array& values, size_t i = 0) { + return i == N ? 0 : values[i] + count_true_helper(values, i + 1); + } +#endif + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct finds { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`finds` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = find_first_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({PredicateQ::template fn>::value...}); + using type = polyfill::index_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class PredicateFn> + using finds_fn = finds>; + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct counts { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`counts` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = count_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({PredicateQ::template fn>::value...}); + using type = polyfill::index_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class PredicateFn> + using counts_fn = counts>; + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct contains { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`contains` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = + static_cast(count_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({TraitQ::template fn>::value...})); + using type = polyfill::bool_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class TraitFn> + using contains_fn = contains>; + } + } + + namespace mpl = internal::mpl; + + // convenience quoted metafunctions + namespace internal { + /* + * Quoted trait metafunction that checks if a type has the specified trait. + */ + template class TraitFn, class... Bound> + using check_if = + mpl::conditional_t, mpl::bind_front_fn>; + + /* + * Quoted trait metafunction that checks if a type doesn't have the specified trait. + */ + template class TraitFn> + using check_if_not = mpl::not_fn; + + /* + * Quoted trait metafunction that checks if a type is the same as the specified type. + * Commonly used named abbreviation for `check_if`. + */ + template + using check_if_is_type = mpl::bind_front_fn; + + /* + * Quoted trait metafunction that checks if a type's template matches the specified template + * (similar to `is_specialization_of`). + */ + template class Template> + using check_if_is_template = + mpl::pass_extracted_fn_to>>; + + /* + * Quoted metafunction that finds the index of the given type in a tuple. + */ + template + using finds_if_has_type = mpl::finds>; + + /* + * Quoted metafunction that finds the index of the given class template in a tuple. + */ + template class Template> + using finds_if_has_template = mpl::finds>; + + /* + * Quoted trait metafunction that counts tuple elements having a given trait. + */ + template class TraitFn> + using counts_if_has = mpl::counts_fn; + + /* + * Quoted trait metafunction that checks whether a tuple contains a type with given trait. + */ + template class TraitFn> + using check_if_has = mpl::contains_fn; + + /* + * Quoted trait metafunction that checks whether a tuple doesn't contain a type with given trait. + */ + template class TraitFn> + using check_if_has_not = mpl::not_>; + + /* + * Quoted metafunction that checks whether a tuple contains given type. + */ + template + using check_if_has_type = mpl::contains>; + + /* + * Quoted metafunction that checks whether a tuple contains a given template. + * + * Note: we are using 2 small tricks: + * 1. A template template parameter can be treated like a metafunction, so we can just "quote" a 'primary' + * template into the MPL system (e.g. `std::vector`). + * 2. This quoted metafunction does the opposite of the trait metafunction `is_specialization`: + * `is_specialization` tries to instantiate the primary template template parameter using the + * template parameters of a template type, then compares both instantiated types. + * Here instead, `pass_extracted_fn_to` extracts the template template parameter from a template type, + * then compares the resulting template template parameters. + */ + template class Template> + using check_if_has_template = mpl::contains>; + } +} diff --git a/dev/functional/mpl/conditional.h b/dev/functional/mpl/conditional.h new file mode 100644 index 000000000..8018b9b91 --- /dev/null +++ b/dev/functional/mpl/conditional.h @@ -0,0 +1,34 @@ +#pragma once + +namespace sqlite_orm { + namespace internal { + namespace mpl { + + /* + * Binary quoted metafunction equivalent to `std::conditional`, + * using an improved implementation in respect to memoization. + * + * Because `conditional` is only typed on a single bool non-type template parameter, + * the compiler only ever needs to memoize 2 instances of this class template. + * The type selection is a nested cheap alias template. + */ + template + struct conditional { + template + using fn = A; + }; + + template<> + struct conditional { + template + using fn = B; + }; + + // directly invoke `conditional` + template + using conditional_t = typename conditional::template fn; + } + } + + namespace mpl = internal::mpl; +} diff --git a/dev/functional/sqlite3_config.h b/dev/functional/sqlite3_config.h new file mode 100644 index 000000000..bf497570b --- /dev/null +++ b/dev/functional/sqlite3_config.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/dev/functional/static_magic.h b/dev/functional/static_magic.h index 980107270..c736d4769 100644 --- a/dev/functional/static_magic.h +++ b/dev/functional/static_magic.h @@ -11,13 +11,17 @@ namespace sqlite_orm { // https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co namespace internal { - template - decltype(auto) empty_callable() { - static auto res = [](auto&&...) -> R { + // note: this is a class template accompanied with a variable template because older compilers (e.g. VC 2017) + // cannot handle a static lambda variable inside a template function + template + struct empty_callable_t { + template + R operator()(Args&&...) const { return R(); - }; - return (res); - } + } + }; + template + constexpr empty_callable_t empty_callable{}; #ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED template @@ -34,7 +38,7 @@ namespace sqlite_orm { if constexpr(B) { return std::forward(trueFn); } else { - return empty_callable(); + return empty_callable<>; } } @@ -62,7 +66,7 @@ namespace sqlite_orm { template decltype(auto) static_if(T&& trueFn) { - return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable()); + return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable<>); } template diff --git a/dev/get_prepared_statement.h b/dev/get_prepared_statement.h index dbd415d3a..10848fa43 100644 --- a/dev/get_prepared_statement.h +++ b/dev/get_prepared_statement.h @@ -1,10 +1,12 @@ #pragma once -#include // std::is_same, std::decay, std::remove_reference +#include // std::is_same, std::remove_reference, std::remove_cvref #include // std::get #include "functional/cxx_universal.h" // ::size_t +#include "functional/cxx_type_traits_polyfill.h" #include "functional/static_magic.h" +#include "type_traits.h" #include "prepared_statement.h" #include "ast_iterator.h" #include "node_tuple.h" @@ -124,15 +126,15 @@ namespace sqlite_orm { template const auto& get(const internal::prepared_statement_t& statement) { - using statement_type = std::decay_t; - using expression_type = typename statement_type::expression_type; + using statement_type = polyfill::remove_cvref_t; + using expression_type = internal::expression_type_t; using node_tuple = internal::node_tuple_t; using bind_tuple = internal::bindable_filter_t; using result_type = std::tuple_element_t(N), bind_tuple>; const result_type* result = nullptr; internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = std::decay_t; - if(internal::is_bindable_v) { + using node_type = polyfill::remove_cvref_t; + if(internal::is_bindable::value) { ++index; } if(index == N) { @@ -149,16 +151,16 @@ namespace sqlite_orm { template auto& get(internal::prepared_statement_t& statement) { - using statement_type = std::decay_t; - using expression_type = typename statement_type::expression_type; + using statement_type = std::remove_reference_t; + using expression_type = internal::expression_type_t; using node_tuple = internal::node_tuple_t; using bind_tuple = internal::bindable_filter_t; using result_type = std::tuple_element_t(N), bind_tuple>; result_type* result = nullptr; internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable { - using node_type = std::decay_t; - if(internal::is_bindable_v) { + using node_type = polyfill::remove_cvref_t; + if(internal::is_bindable::value) { ++index; } if(index == N) { diff --git a/dev/implementations/column_definitions.h b/dev/implementations/column_definitions.h index e303fb7fd..370deec55 100644 --- a/dev/implementations/column_definitions.h +++ b/dev/implementations/column_definitions.h @@ -1,38 +1,32 @@ -/** @file Mainly existing to disentangle implementation details from circular and cross dependencies - * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) - * this file is also used to provide definitions of interface methods 'hitting the database'. - */ -#pragma once - -#include // std::make_unique - -#include "../functional/cxx_core_features.h" -#include "../functional/static_magic.h" -#include "../functional/index_sequence_util.h" -#include "../tuple_helper/tuple_filter.h" -#include "../tuple_helper/tuple_traits.h" -#include "../default_value_extractor.h" -#include "../column.h" - -namespace sqlite_orm { - namespace internal { - - template - std::unique_ptr column_constraints::default_value() const { - using default_op_index_sequence = - filter_tuple_sequence_t::template fn>; - - std::unique_ptr value; - call_if_constexpr( - [&value](auto& constraints, auto op_index_sequence) { - using default_op_index_sequence = decltype(op_index_sequence); - constexpr size_t opIndex = first_index_sequence_value(default_op_index_sequence{}); - value = std::make_unique(serialize_default_value(get(constraints))); - }, - this->constraints, - default_op_index_sequence{}); - return value; - } - - } -} +/** @file Mainly existing to disentangle implementation details from circular and cross dependencies + * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) + * this file is also used to provide definitions of interface methods 'hitting the database'. + */ +#pragma once + +#include // std::make_unique + +#include "../functional/static_magic.h" +#include "../tuple_helper/tuple_traits.h" +#include "../default_value_extractor.h" +#include "../schema/column.h" + +namespace sqlite_orm { + namespace internal { + + template + std::unique_ptr column_constraints::default_value() const { + static constexpr size_t default_op_index = find_tuple_template::value; + + std::unique_ptr value; + call_if_constexpr::value>( + [&value](auto& constraints) { + value = + std::make_unique(serialize_default_value(std::get(constraints))); + }, + this->constraints); + return value; + } + + } +} diff --git a/dev/implementations/storage_definitions.h b/dev/implementations/storage_definitions.h index f832b6959..30d498b13 100644 --- a/dev/implementations/storage_definitions.h +++ b/dev/implementations/storage_definitions.h @@ -1,144 +1,148 @@ -/** @file Mainly existing to disentangle implementation details from circular and cross dependencies - * this file is also used to separate implementation details from the main header file, - * e.g. usage of the dbstat table. - */ -#pragma once -#include // std::is_same -#include -#include // std::reference_wrapper, std::cref -#include // std::find_if, std::ranges::find - -#include "../dbstat.h" -#include "../type_traits.h" -#include "../util.h" -#include "../serializing_util.h" -#include "../storage.h" - -namespace sqlite_orm { - namespace internal { - - template - template> - sync_schema_result storage_t::sync_table(const Table& table, sqlite3* db, bool preserve) { -#ifdef SQLITE_ENABLE_DBSTAT_VTAB - if(std::is_same, dbstat>::value) { - return sync_schema_result::already_in_sync; - } -#endif // SQLITE_ENABLE_DBSTAT_VTAB - auto res = sync_schema_result::already_in_sync; - bool attempt_to_preserve = true; - - auto schema_stat = this->schema_status(table, db, preserve, &attempt_to_preserve); - if(schema_stat != sync_schema_result::already_in_sync) { - if(schema_stat == sync_schema_result::new_table_created) { - this->create_table(db, table.name, table); - res = sync_schema_result::new_table_created; - } else { - if(schema_stat == sync_schema_result::old_columns_removed || - schema_stat == sync_schema_result::new_columns_added || - schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { - - // get table info provided in `make_table` call.. - auto storageTableInfo = table.get_table_info(); - - // now get current table info from db using `PRAGMA table_xinfo` query.. - auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns - - // this vector will contain pointers to columns that gotta be added.. - std::vector columnsToAdd; - - this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); - - if(schema_stat == sync_schema_result::old_columns_removed) { -#if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0) - for(auto& tableInfo: dbTableInfo) { - this->drop_column(db, table.name, tableInfo.name); - } - res = sync_schema_result::old_columns_removed; -#else - // extra table columns than storage columns - this->backup_table(db, table, {}); - res = sync_schema_result::old_columns_removed; -#endif - } - - if(schema_stat == sync_schema_result::new_columns_added) { - for(const table_xinfo* colInfo: columnsToAdd) { - table.for_each_column([this, colInfo, &tableName = table.name, db](auto& column) { - if(column.name != colInfo->name) { - return; - } - this->add_column(db, tableName, column); - }); - } - res = sync_schema_result::new_columns_added; - } - - if(schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { - - auto storageTableInfo = table.get_table_info(); - this->add_generated_cols(columnsToAdd, storageTableInfo); - - // remove extra columns and generated columns - this->backup_table(db, table, columnsToAdd); - res = sync_schema_result::new_columns_added_and_old_columns_removed; - } - } else if(schema_stat == sync_schema_result::dropped_and_recreated) { - // now get current table info from db using `PRAGMA table_xinfo` query.. - auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns - auto storageTableInfo = table.get_table_info(); - - // this vector will contain pointers to columns that gotta be added.. - std::vector columnsToAdd; - - this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); - - this->add_generated_cols(columnsToAdd, storageTableInfo); - - if(preserve && attempt_to_preserve) { - this->backup_table(db, table, columnsToAdd); - } else { - this->drop_create_with_loss(db, table); - } - res = schema_stat; - } - } - } - return res; - } - - template - template - void storage_t::copy_table( - sqlite3* db, - const std::string& sourceTableName, - const std::string& destinationTableName, - const Table& table, - const std::vector& columnsToIgnore) const { // must ignore generated columns - std::vector> columnNames; - columnNames.reserve(table.count_columns_amount()); - table.for_each_column([&columnNames, &columnsToIgnore](const column_identifier& column) { - auto& columnName = column.name; -#if __cpp_lib_ranges >= 201911L - auto columnToIgnoreIt = std::ranges::find(columnsToIgnore, columnName, &table_xinfo::name); -#else - auto columnToIgnoreIt = std::find_if(columnsToIgnore.begin(), - columnsToIgnore.end(), - [&columnName](const table_xinfo* tableInfo) { - return columnName == tableInfo->name; - }); -#endif - if(columnToIgnoreIt == columnsToIgnore.end()) { - columnNames.push_back(cref(columnName)); - } - }); - - std::stringstream ss; - ss << "INSERT INTO " << streaming_identifier(destinationTableName) << " (" - << streaming_identifiers(columnNames) << ") " - << "SELECT " << streaming_identifiers(columnNames) << " FROM " << streaming_identifier(sourceTableName) - << std::flush; - perform_void_exec(db, ss.str()); - } - } -} +/** @file Mainly existing to disentangle implementation details from circular and cross dependencies + * this file is also used to separate implementation details from the main header file, + * e.g. usage of the dbstat table. + */ +#pragma once +#include // std::is_same +#include +#include // std::reference_wrapper, std::cref +#include // std::find_if, std::ranges::find + +#include "../sqlite_schema_table.h" +#include "../eponymous_vtabs/dbstat.h" +#include "../type_traits.h" +#include "../util.h" +#include "../serializing_util.h" +#include "../storage.h" + +namespace sqlite_orm { + namespace internal { + + template + template> + sync_schema_result storage_t::sync_table(const Table& table, sqlite3* db, bool preserve) { + if(std::is_same, sqlite_master>::value) { + return sync_schema_result::already_in_sync; + } +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + if(std::is_same, dbstat>::value) { + return sync_schema_result::already_in_sync; + } +#endif // SQLITE_ENABLE_DBSTAT_VTAB + auto res = sync_schema_result::already_in_sync; + bool attempt_to_preserve = true; + + auto schema_stat = this->schema_status(table, db, preserve, &attempt_to_preserve); + if(schema_stat != sync_schema_result::already_in_sync) { + if(schema_stat == sync_schema_result::new_table_created) { + this->create_table(db, table.name, table); + res = sync_schema_result::new_table_created; + } else { + if(schema_stat == sync_schema_result::old_columns_removed || + schema_stat == sync_schema_result::new_columns_added || + schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { + + // get table info provided in `make_table` call.. + auto storageTableInfo = table.get_table_info(); + + // now get current table info from db using `PRAGMA table_xinfo` query.. + auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns + + // this vector will contain pointers to columns that gotta be added.. + std::vector columnsToAdd; + + this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); + + if(schema_stat == sync_schema_result::old_columns_removed) { +#if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0) + for(auto& tableInfo: dbTableInfo) { + this->drop_column(db, table.name, tableInfo.name); + } + res = sync_schema_result::old_columns_removed; +#else + // extra table columns than storage columns + this->backup_table(db, table, {}); + res = sync_schema_result::old_columns_removed; +#endif + } + + if(schema_stat == sync_schema_result::new_columns_added) { + for(const table_xinfo* colInfo: columnsToAdd) { + table.for_each_column([this, colInfo, &tableName = table.name, db](auto& column) { + if(column.name != colInfo->name) { + return; + } + this->add_column(db, tableName, column); + }); + } + res = sync_schema_result::new_columns_added; + } + + if(schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { + + auto storageTableInfo = table.get_table_info(); + this->add_generated_cols(columnsToAdd, storageTableInfo); + + // remove extra columns and generated columns + this->backup_table(db, table, columnsToAdd); + res = sync_schema_result::new_columns_added_and_old_columns_removed; + } + } else if(schema_stat == sync_schema_result::dropped_and_recreated) { + // now get current table info from db using `PRAGMA table_xinfo` query.. + auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns + auto storageTableInfo = table.get_table_info(); + + // this vector will contain pointers to columns that gotta be added.. + std::vector columnsToAdd; + + this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); + + this->add_generated_cols(columnsToAdd, storageTableInfo); + + if(preserve && attempt_to_preserve) { + this->backup_table(db, table, columnsToAdd); + } else { + this->drop_create_with_loss(db, table); + } + res = schema_stat; + } + } + } + return res; + } + + template + template + void storage_t::copy_table( + sqlite3* db, + const std::string& sourceTableName, + const std::string& destinationTableName, + const Table& table, + const std::vector& columnsToIgnore) const { // must ignore generated columns + std::vector> columnNames; + columnNames.reserve(table.template count_of()); + table.for_each_column([&columnNames, &columnsToIgnore](const column_identifier& column) { + auto& columnName = column.name; +#if __cpp_lib_ranges >= 201911L + auto columnToIgnoreIt = std::ranges::find(columnsToIgnore, columnName, &table_xinfo::name); +#else + auto columnToIgnoreIt = std::find_if(columnsToIgnore.begin(), + columnsToIgnore.end(), + [&columnName](const table_xinfo* tableInfo) { + return columnName == tableInfo->name; + }); +#endif + if(columnToIgnoreIt == columnsToIgnore.end()) { + columnNames.push_back(cref(columnName)); + } + }); + + std::stringstream ss; + ss << "INSERT INTO " << streaming_identifier(destinationTableName) << " (" + << streaming_identifiers(columnNames) << ") " + << "SELECT " << streaming_identifiers(columnNames) << " FROM " << streaming_identifier(sourceTableName) + << std::flush; + perform_void_exec(db, ss.str()); + } + } +} diff --git a/dev/implementations/table_definitions.h b/dev/implementations/table_definitions.h index 9b0b2e996..a869d2272 100644 --- a/dev/implementations/table_definitions.h +++ b/dev/implementations/table_definitions.h @@ -1,53 +1,54 @@ -/** @file Mainly existing to disentangle implementation details from circular and cross dependencies - * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) - * this file is also used to provide definitions of interface methods 'hitting the database'. - */ -#pragma once -#include // std::decay_t -#include // std::move -#include // std::find_if, std::ranges::find - -#include "../type_printer.h" -#include "../column.h" -#include "../table.h" - -namespace sqlite_orm { - namespace internal { - - template - std::vector table_t::get_table_info() const { - std::vector res; - res.reserve(size_t(filter_tuple_sequence_t::size())); - this->for_each_column([&res](auto& column) { - using field_type = field_type_t>; - std::string dft; - if(auto d = column.default_value()) { - dft = std::move(*d); - } - res.emplace_back(-1, - column.name, - type_printer().print(), - column.is_not_null(), - dft, - column.template is(), - column.is_generated()); - }); - auto compositeKeyColumnNames = this->composite_key_columns_names(); - for(size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { - auto& columnName = compositeKeyColumnNames[i]; -#if __cpp_lib_ranges >= 201911L - auto it = std::ranges::find(res, columnName, &table_xinfo::name); -#else - auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) { - return ti.name == columnName; - }); -#endif - if(it != res.end()) { - it->pk = static_cast(i + 1); - } - } - return res; - } - - } -} +/** @file Mainly existing to disentangle implementation details from circular and cross dependencies + * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) + * this file is also used to provide definitions of interface methods 'hitting the database'. + */ +#pragma once +#include // std::decay_t +#include // std::move +#include // std::find_if, std::ranges::find + +#include "../functional/cxx_universal.h" // ::size_t +#include "../type_printer.h" +#include "../schema/column.h" +#include "../schema/table.h" + +namespace sqlite_orm { + namespace internal { + + template + std::vector table_t::get_table_info() const { + std::vector res; + res.reserve(filter_tuple_sequence_t::size()); + this->for_each_column([&res](auto& column) { + using field_type = field_type_t>; + std::string dft; + if(auto d = column.default_value()) { + dft = std::move(*d); + } + res.emplace_back(-1, + column.name, + type_printer().print(), + column.is_not_null(), + std::move(dft), + column.template is(), + column.template is()); + }); + auto compositeKeyColumnNames = this->composite_key_columns_names(); + for(size_t i = 0; i < compositeKeyColumnNames.size(); ++i) { + const std::string& columnName = compositeKeyColumnNames[i]; +#if __cpp_lib_ranges >= 201911L + auto it = std::ranges::find(res, columnName, &table_xinfo::name); +#else + auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) { + return ti.name == columnName; + }); +#endif + if(it != res.end()) { + it->pk = static_cast(i + 1); + } + } + return res; + } + + } +} diff --git a/dev/interface_definitions.h b/dev/interface_definitions.h index 323b515f1..d8fbdbd62 100644 --- a/dev/interface_definitions.h +++ b/dev/interface_definitions.h @@ -1,9 +1,9 @@ -/** @file Mainly existing to disentangle implementation details from circular and cross dependencies - * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) - * this file is also used to provide definitions of interface methods 'hitting the database'. - */ -#pragma once - -#include "implementations/column_definitions.h" -#include "implementations/table_definitions.h" -#include "implementations/storage_definitions.h" +/** @file Mainly existing to disentangle implementation details from circular and cross dependencies + * (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t) + * this file is also used to provide definitions of interface methods 'hitting the database'. + */ +#pragma once + +#include "implementations/column_definitions.h" +#include "implementations/table_definitions.h" +#include "implementations/storage_definitions.h" diff --git a/dev/iterator.h b/dev/iterator.h deleted file mode 100644 index 932087eae..000000000 --- a/dev/iterator.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include -#include // std::shared_ptr, std::unique_ptr, std::make_shared -#include // std::decay -#include // std::move -#include // std::input_iterator_tag -#include // std::system_error -#include // std::bind - -#include "functional/cxx_universal.h" -#include "statement_finalizer.h" -#include "error_code.h" -#include "object_from_column_builder.h" -#include "storage_lookup.h" -#include "util.h" - -namespace sqlite_orm { - - namespace internal { - - template - struct iterator_t { - using view_type = V; - using value_type = typename view_type::mapped_type; - - protected: - /** - * shared_ptr is used over unique_ptr here - * so that the iterator can be copyable. - */ - std::shared_ptr stmt; - - // only null for the default constructed iterator - view_type* view = nullptr; - - /** - * shared_ptr is used over unique_ptr here - * so that the iterator can be copyable. - */ - std::shared_ptr current; - - void extract_value() { - auto& dbObjects = obtain_db_objects(this->view->storage); - this->current = std::make_shared(); - object_from_column_builder builder{*this->current, this->stmt.get()}; - pick_table(dbObjects).for_each_column(builder); - } - - void next() { - this->current.reset(); - if(sqlite3_stmt* stmt = this->stmt.get()) { - perform_step(stmt, std::bind(&iterator_t::extract_value, this)); - if(!this->current) { - this->stmt.reset(); - } - } - } - - public: - using difference_type = ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::input_iterator_tag; - - iterator_t(){}; - - iterator_t(statement_finalizer stmt_, view_type& view_) : stmt{std::move(stmt_)}, view{&view_} { - next(); - } - - const value_type& operator*() const { - if(!this->stmt || !this->current) { - throw std::system_error{orm_error_code::trying_to_dereference_null_iterator}; - } - return *this->current; - } - - const value_type* operator->() const { - return &(this->operator*()); - } - - iterator_t& operator++() { - next(); - return *this; - } - - void operator++(int) { - this->operator++(); - } - - bool operator==(const iterator_t& other) const { - return this->current == other.current; - } - - bool operator!=(const iterator_t& other) const { - return !(*this == other); - } - }; - } -} diff --git a/dev/literal.h b/dev/literal.h index 9f0191ff4..65cb1b265 100644 --- a/dev/literal.h +++ b/dev/literal.h @@ -1,17 +1,17 @@ -#pragma once - -namespace sqlite_orm { - namespace internal { - - /* - * Protect an otherwise bindable element so that it is always serialized as a literal value. - */ - template - struct literal_holder { - using type = T; - - type value; - }; - - } -} +#pragma once + +namespace sqlite_orm { + namespace internal { + + /* + * Protect an otherwise bindable element so that it is always serialized as a literal value. + */ + template + struct literal_holder { + using type = T; + + type value; + }; + + } +} diff --git a/dev/mapped_iterator.h b/dev/mapped_iterator.h new file mode 100644 index 000000000..4b5d57ef8 --- /dev/null +++ b/dev/mapped_iterator.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include // std::shared_ptr, std::make_shared +#include // std::move +#include // std::input_iterator_tag +#include // std::system_error +#include // std::bind + +#include "functional/cxx_universal.h" // ::ptrdiff_t +#include "statement_finalizer.h" +#include "error_code.h" +#include "object_from_column_builder.h" +#include "storage_lookup.h" +#include "util.h" + +namespace sqlite_orm { + namespace internal { + + /* + * (Legacy) Input iterator over a result set for a mapped object. + */ + template + class mapped_iterator { + public: + using db_objects_type = DBOs; + + using iterator_category = std::input_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = O; + using reference = O&; + using pointer = O*; + + private: + /** + pointer to the db objects. + only null for the default constructed iterator. + */ + const db_objects_type* db_objects = nullptr; + + /** + * shared_ptr is used over unique_ptr here + * so that the iterator can be copyable. + */ + std::shared_ptr stmt; + + /** + * shared_ptr is used over unique_ptr here + * so that the iterator can be copyable. + */ + std::shared_ptr current; + + void extract_object() { + this->current = std::make_shared(); + object_from_column_builder builder{*this->current, this->stmt.get()}; + auto& table = pick_table(*this->db_objects); + table.for_each_column(builder); + } + + void step() { + perform_step(this->stmt.get(), std::bind(&mapped_iterator::extract_object, this)); + if(!this->current) { + this->stmt.reset(); + } + } + + void next() { + this->current.reset(); + this->step(); + } + + public: + mapped_iterator() = default; + + mapped_iterator(const db_objects_type& dbObjects, statement_finalizer stmt) : + db_objects{&dbObjects}, stmt{std::move(stmt)} { + this->step(); + } + + mapped_iterator(const mapped_iterator&) = default; + mapped_iterator& operator=(const mapped_iterator&) = default; + mapped_iterator(mapped_iterator&&) = default; + mapped_iterator& operator=(mapped_iterator&&) = default; + + value_type& operator*() const { + if(!this->stmt) SQLITE_ORM_CPP_UNLIKELY { + throw std::system_error{orm_error_code::trying_to_dereference_null_iterator}; + } + return *this->current; + } + + // note: should actually be only present for contiguous iterators + value_type* operator->() const { + return &(this->operator*()); + } + + mapped_iterator& operator++() { + next(); + return *this; + } + + mapped_iterator operator++(int) { + auto tmp = *this; + ++*this; + return tmp; + } + + friend bool operator==(const mapped_iterator& lhs, const mapped_iterator& rhs) { + return lhs.current == rhs.current; + } + +#ifndef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + friend bool operator!=(const mapped_iterator& lhs, const mapped_iterator& rhs) { + return !(lhs == rhs); + } +#endif + }; + } +} diff --git a/dev/mapped_row_extractor.h b/dev/mapped_row_extractor.h deleted file mode 100644 index faf47ecdd..000000000 --- a/dev/mapped_row_extractor.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -#include "object_from_column_builder.h" - -namespace sqlite_orm { - - namespace internal { - - /** - * This is a private row extractor class. It is used for extracting rows as objects instead of tuple. - * Main difference from regular `row_extractor` is that this class takes table info which is required - * for constructing objects by member pointers. To construct please use `make_row_extractor()`. - * Type arguments: - * V is value type just like regular `row_extractor` has - * T is table info class `table_t` - */ - template - struct mapped_row_extractor { - using table_type = Table; - - V extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - V res; - object_from_column_builder builder{res, stmt}; - this->tableInfo.for_each_column(builder); - return res; - } - - const table_type& tableInfo; - }; - - } - -} diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index fd914d0b7..b18eea0e6 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -3,6 +3,7 @@ #include // std::remove_const #include "type_traits.h" +#include "table_reference.h" #include "alias_traits.h" namespace sqlite_orm { @@ -10,14 +11,19 @@ namespace sqlite_orm { namespace internal { /** - * If T is a recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, + * If T is a table reference or recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, * otherwise unqualified T. */ template struct mapped_type_proxy : std::remove_const {}; - template - struct mapped_type_proxy> : std::remove_const> {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct mapped_type_proxy : R {}; +#endif + + template + struct mapped_type_proxy> : std::remove_const> {}; template using mapped_type_proxy_t = typename mapped_type_proxy::type; diff --git a/dev/mapped_view.h b/dev/mapped_view.h new file mode 100644 index 000000000..b2b61543a --- /dev/null +++ b/dev/mapped_view.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include // std::forward, std::move + +#include "row_extractor.h" +#include "mapped_iterator.h" +#include "ast_iterator.h" +#include "prepared_statement.h" +#include "connection_holder.h" +#include "util.h" + +namespace sqlite_orm { + + namespace internal { + + /** + * A C++ view-like class which is returned + * by `storage_t::iterate()` function. This class contains STL functions: + * - size_t size() + * - bool empty() + * - iterator end() + * - iterator begin() + * All these functions are not right const cause all of them may open SQLite connections. + * + * `mapped_view` is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ + template + struct mapped_view { + using mapped_type = T; + using storage_type = S; + using db_objects_type = typename S::db_objects_type; + + storage_type& storage; + connection_ref connection; + get_all_t expression; + + mapped_view(storage_type& storage, connection_ref conn, Args&&... args) : + storage(storage), connection(std::move(conn)), expression{std::forward(args)...} {} + + size_t size() const { + return this->storage.template count(); + } + + bool empty() const { + return !this->size(); + } + + mapped_iterator begin() { + using context_t = serializer_context; + auto& dbObjects = obtain_db_objects(this->storage); + context_t context{dbObjects}; + context.skip_table_name = false; + context.replace_bindable_with_question = true; + + statement_finalizer stmt{prepare_stmt(this->connection.get(), serialize(this->expression, context))}; + iterate_ast(this->expression.conditions, conditional_binder{stmt.get()}); + return {dbObjects, std::move(stmt)}; + } + + mapped_iterator end() { + return {}; + } + }; + } +} + +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; +#endif diff --git a/dev/node_tuple.h b/dev/node_tuple.h index b737d1b91..1cf4729ee 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -8,6 +8,7 @@ #include "functional/cxx_type_traits_polyfill.h" #include "tuple_helper/tuple_filter.h" +#include "type_traits.h" #include "conditions.h" #include "operators.h" #include "select_constraints.h" @@ -20,9 +21,9 @@ #include "ast/where.h" #include "ast/into.h" #include "ast/group_by.h" +#include "ast/match.h" namespace sqlite_orm { - namespace internal { template @@ -33,34 +34,44 @@ namespace sqlite_orm { template using node_tuple_t = typename node_tuple::type; + /* + * Node tuple for several types. + */ + template + using node_tuple_for = conc_tuple::type...>; + template<> struct node_tuple { using type = std::tuple<>; }; + + template + struct node_tuple, void> : node_tuple {}; + + template + struct node_tuple, void> : node_tuple_for {}; + #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template struct node_tuple, void> : node_tuple {}; #endif // SQLITE_ORM_OPTIONAL_SUPPORTED - template - struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> : node_tuple> {}; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using args_tuple = node_tuple_t>; - using expression_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; - template - struct node_tuple> : node_tuple {}; +#if SQLITE_VERSION_NUMBER >= 3024000 + template + struct node_tuple, void> : node_tuple {}; +#endif template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; + + template + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; @@ -68,6 +79,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + /** * Column alias */ @@ -89,116 +103,71 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; + template + struct node_tuple, void> : node_tuple {}; + template - struct node_tuple> { - using node_type = T; - using left_type = typename node_type::left_type; - using right_type = typename node_type::right_type; - using left_node_tuple = node_tuple_t; - using right_node_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple> : node_tuple_for, right_type_t> {}; - template - struct node_tuple, void> { - using node_type = binary_operator; - using left_type = typename node_type::left_type; - using right_type = typename node_type::right_type; - using left_node_tuple = node_tuple_t; - using right_node_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + template + struct node_tuple> : node_tuple_for, right_type_t> {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; + + template + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using left_tuple = node_tuple_t; - using right_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using left_tuple = node_tuple_t; - using right_tuple = tuple_cat_t...>; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple> { - using node_type = T; - using left_type = typename node_type::left_type; - using right_type = typename node_type::right_type; - using left_tuple = node_tuple_t; - using right_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple> : node_tuple {}; + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct node_tuple> + : node_tuple {}; + + template + struct node_tuple> + : node_tuple_for {}; +#endif template - struct node_tuple, void> { - using columns_tuple = node_tuple_t; - using args_tuple = tuple_cat_t...>; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; - - template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template - struct node_tuple, Wargs...>, void> { - using set_tuple = tuple_cat_t...>; - using conditions_tuple = tuple_cat_t...>; - using type = tuple_cat_t; - }; + struct node_tuple, Wargs...>, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; - - template - struct node_tuple, void> : node_tuple {}; + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; @@ -210,27 +179,13 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> { - using arg_tuple = node_tuple_t; - using pattern_tuple = node_tuple_t; - using escape_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using arg_tuple = node_tuple_t; - using pattern_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using expression_tuple = node_tuple_t; - using lower_tuple = node_tuple_t; - using upper_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; @@ -245,26 +200,16 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using left_tuple = node_tuple_t; - using right_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t...>; - }; + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; @@ -287,19 +232,10 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> { - using case_tuple = node_tuple_t; - using args_tuple = tuple_cat_t...>; - using else_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using left_tuple = node_tuple_t; - using right_tuple = node_tuple_t; - using type = tuple_cat_t; - }; + struct node_tuple, void> : node_tuple_for {}; template struct node_tuple, void> : node_tuple {}; @@ -308,13 +244,9 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> { - using type = tuple_cat_t, node_tuple_t>; - }; + struct node_tuple, void> : node_tuple_for {}; template - struct node_tuple, void> { - using type = tuple_cat_t, node_tuple_t>; - }; + struct node_tuple, void> : node_tuple_for {}; } } diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index a613262c6..86aae7f40 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -2,9 +2,14 @@ #include #include // std::is_member_object_pointer +#include // std::move #include "functional/static_magic.h" +#include "member_traits/member_traits.h" +#include "table_reference.h" #include "row_extractor.h" +#include "schema/column.h" +#include "storage_lookup.h" namespace sqlite_orm { @@ -12,15 +17,16 @@ namespace sqlite_orm { struct object_from_column_builder_base { sqlite3_stmt* stmt = nullptr; - int index = 0; + int columnIndex = -1; #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} + object_from_column_builder_base(sqlite3_stmt* stmt, int columnIndex = -1) : + stmt{stmt}, columnIndex{columnIndex} {} #endif }; /** - * This is a cute lambda replacement which is used in several places. + * Function object for building an object from a result row. */ template struct object_from_column_builder : object_from_column_builder_base { @@ -28,12 +34,13 @@ namespace sqlite_orm { object_type& object; - object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : - object_from_column_builder_base{stmt_}, object(object_) {} + object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_, int nextColumnIndex = 0) : + object_from_column_builder_base{stmt_, nextColumnIndex - 1}, object(object_) {} template void operator()(const column_field& column) { - auto value = row_extractor>().extract(this->stmt, this->index++); + const auto rowExtractor = row_value_extractor>(); + auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); static_if::value>( [&value, &object = this->object](const auto& column) { object.*column.member_pointer = std::move(value); @@ -43,5 +50,34 @@ namespace sqlite_orm { })(column); } }; + + /** + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_referenece` + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = 0; + return this->extract(stmt, columnIndex); + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + O obj; + object_from_column_builder builder{obj, stmt, columnIndex}; + auto& table = pick_table(this->db_objects); + table.for_each_column(builder); + columnIndex = builder.columnIndex; + return obj; + } + + O extract(sqlite3_value* value) const = delete; + }; } } diff --git a/dev/operators.h b/dev/operators.h index 238eb9e27..d6cd2b225 100644 --- a/dev/operators.h +++ b/dev/operators.h @@ -2,8 +2,9 @@ #include // std::false_type, std::true_type #include // std::move -#include "functional/cxx_optional.h" +#include "functional/cxx_type_traits_polyfill.h" +#include "is_base_of_template.h" #include "tags.h" #include "serialize_result_type.h" @@ -11,11 +12,6 @@ namespace sqlite_orm { namespace internal { - /** - * Inherit this class to support arithmetic types overloading - */ - struct arithmetic_t {}; - template struct binary_operator : Ds... { using left_type = L; @@ -27,6 +23,12 @@ namespace sqlite_orm { binary_operator(left_type lhs_, right_type rhs_) : lhs(std::move(lhs_)), rhs(std::move(rhs_)) {} }; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_binary_operator_v = is_base_of_template::value; + + template + using is_binary_operator = polyfill::bool_constant>; + struct conc_string { serialize_result_type serialize() const { return "||"; @@ -188,10 +190,6 @@ namespace sqlite_orm { */ template struct is_assign_t> : public std::true_type {}; - - template - struct in_t; - } /** diff --git a/dev/pointer_value.h b/dev/pointer_value.h index 36d37cd25..9b21294da 100644 --- a/dev/pointer_value.h +++ b/dev/pointer_value.h @@ -1,13 +1,43 @@ #pragma once +#if SQLITE_VERSION_NUMBER >= 3020000 #include #include #include +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif +#endif -#include "functional/cxx_universal.h" +#include "functional/cstring_literal.h" #include "xdestroy_handling.h" +#if SQLITE_VERSION_NUMBER >= 3020000 namespace sqlite_orm { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + namespace internal { + template + struct pointer_type { + using value_type = const char[sizeof...(C) + 1]; + static inline constexpr value_type value = {C..., '\0'}; + }; + } + + inline namespace literals { + template + [[nodiscard]] consteval auto operator"" _pointer_type() { + return internal::explode_into(std::make_index_sequence{}); + } + } + + /** @short Specifies that a type is an integral constant string usable as a pointer type. + */ + template + concept orm_pointer_type = requires { + typename T::value_type; + { T::value } -> std::convertible_to; + }; +#endif /** * Wraps a pointer and tags it with a pointer type, @@ -16,16 +46,24 @@ namespace sqlite_orm { * * Template parameters: * - P: The value type, possibly const-qualified. - * - T: An integral constant string denoting the pointer type, e.g. `carray_pvt_name`. + * - T: An integral constant string denoting the pointer type, e.g. `"carray"_pointer_type`. * */ template struct pointer_arg { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // note (internal): this is currently a static assertion instead of a type constraint because + // of forward declarations in other places (e.g. function.h) + static_assert(orm_pointer_type, "T must be a pointer type (tag)"); +#else static_assert(std::is_convertible::value, - "`std::integral_constant<>` must be convertible to `const char*`"); + "The pointer type (tag) must be convertible to `const char*`"); +#endif using tag = T; + using qualified_type = P; + P* p_; P* ptr() const noexcept { @@ -43,6 +81,8 @@ namespace sqlite_orm { * as part of facilitating the 'pointer-passing interface'. * * Template parameters: + * - P: The value type, possibly const-qualified. + * - T: An integral constant string denoting the pointer type, e.g. `carray_pointer_type`. * - D: The deleter for the pointer value; * can be one of: * - function pointer @@ -68,11 +108,16 @@ namespace sqlite_orm { D d_; protected: - // Constructing pointer bindings must go through bindable_pointer() + // Constructing pointer bindings must go through bind_pointer() template - friend auto bindable_pointer(P2*, D2) noexcept -> pointer_binding; + friend auto bind_pointer(P2*, D2) noexcept -> pointer_binding; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // Constructing pointer bindings must go through bind_pointer() + template + friend auto bind_pointer(P2*, D2) noexcept -> pointer_binding; +#endif template - friend B bindable_pointer(typename B::qualified_type*, typename B::deleter_type) noexcept; + friend B bind_pointer(typename B::qualified_type*, typename B::deleter_type) noexcept; // Construct from pointer and deleter. // Transfers ownership of the passed in object. @@ -113,17 +158,33 @@ namespace sqlite_orm { }; /** - * Template alias for a static pointer value binding. + * Alias template for a static pointer value binding. * 'Static' means that ownership won't be transferred to sqlite, * sqlite doesn't delete it, and sqlite assumes the object * pointed to is valid throughout the lifetime of a statement. */ template using static_pointer_binding = pointer_binding; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using pointer_arg_t = pointer_arg; + + template + using pointer_binding_t = pointer_binding; + + /** + * Alias template for a static pointer value binding. + * 'Static' means that ownership won't be transferred to sqlite, + * sqlite doesn't delete it, and sqlite assumes the object + * pointed to is valid throughout the lifetime of a statement. + */ + template + using static_pointer_binding_t = pointer_binding_t; +#endif } namespace sqlite_orm { - /** * Wrap a pointer, its type and its deleter function for binding it to a statement. * @@ -132,20 +193,57 @@ namespace sqlite_orm { * the deleter when the statement finishes. */ template - auto bindable_pointer(P* p, D d) noexcept -> pointer_binding { + auto bind_pointer(P* p, D d) noexcept -> pointer_binding { return {p, std::move(d)}; } template - auto bindable_pointer(std::unique_ptr p) noexcept -> pointer_binding { - return bindable_pointer(p.release(), p.get_deleter()); + auto bind_pointer(std::unique_ptr p) noexcept -> pointer_binding { + return bind_pointer(p.release(), p.get_deleter()); } template - B bindable_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept { + auto bind_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept -> B { return B{p, std::move(d)}; } + template + [[deprecated("Use the better named function `bind_pointer(...)`")]] pointer_binding + bindable_pointer(P* p, D d) noexcept { + return bind_pointer(p, std::move(d)); + } + + template + [[deprecated("Use the better named function `bind_pointer(...)`")]] pointer_binding + bindable_pointer(std::unique_ptr p) noexcept { + return bind_pointer(p.release(), p.get_deleter()); + } + + template + [[deprecated("Use the better named function `bind_pointer(...)`")]] B + bindable_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept { + return bind_pointer(p, std::move(d)); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Wrap a pointer, its type (tag) and its deleter function for binding it to a statement. + * + * Unless the deleter yields a nullptr 'xDestroy' function the ownership of the pointed-to-object + * is transferred to the pointer binding, which will delete it through + * the deleter when the statement finishes. + */ + template + auto bind_pointer(P* p, D d) noexcept -> pointer_binding { + return {p, std::move(d)}; + } + + template + auto bind_pointer(std::unique_ptr p) noexcept -> pointer_binding { + return bind_pointer(p.release(), p.get_deleter()); + } +#endif + /** * Wrap a pointer and its type for binding it to a statement. * @@ -153,21 +251,48 @@ namespace sqlite_orm { * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. */ template - auto statically_bindable_pointer(P* p) noexcept -> static_pointer_binding { - return bindable_pointer(p, null_xdestroy_f); + auto bind_pointer_statically(P* p) noexcept -> static_pointer_binding { + return bind_pointer(p, null_xdestroy_f); } template - B statically_bindable_pointer(typename B::qualified_type* p, - typename B::deleter_type* /*exposition*/ = nullptr) noexcept { - return bindable_pointer(p); + B bind_pointer_statically(typename B::qualified_type* p, + typename B::deleter_type* /*exposition*/ = nullptr) noexcept { + return bind_pointer(p); + } + + template + [[deprecated("Use the better named function `bind_pointer_statically(...)`")]] static_pointer_binding + statically_bindable_pointer(P* p) noexcept { + return bind_pointer(p, null_xdestroy_f); + } + + template + [[deprecated("Use the better named function `bind_pointer_statically(...)`")]] B + statically_bindable_pointer(typename B::qualified_type* p, + typename B::deleter_type* /*exposition*/ = nullptr) noexcept { + return bind_pointer(p); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Wrap a pointer and its type (tag) for binding it to a statement. + * + * Note: 'Static' means that ownership of the pointed-to-object won't be transferred + * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. + */ + template + auto bind_pointer_statically(P* p) noexcept -> static_pointer_binding { + return bind_pointer(p, null_xdestroy_f); } +#endif /** * Forward a pointer value from an argument. */ template auto rebind_statically(const pointer_arg& pv) noexcept -> static_pointer_binding { - return statically_bindable_pointer(pv.ptr()); + return bind_pointer_statically(pv.ptr()); } } +#endif diff --git a/dev/pragma.h b/dev/pragma.h index ba1b03e88..c30008cbc 100644 --- a/dev/pragma.h +++ b/dev/pragma.h @@ -28,8 +28,9 @@ namespace sqlite_orm { inline int getPragmaCallback>(void* data, int argc, char** argv, char**) { auto& res = *(std::vector*)data; res.reserve(argc); - for(decltype(argc) i = 0; i < argc; ++i) { - auto rowString = row_extractor().extract(argv[i]); + const auto rowExtractor = column_text_extractor(); + for(int i = 0; i < argc; ++i) { + auto rowString = rowExtractor.extract(argv[i]); res.push_back(std::move(rowString)); } return 0; @@ -40,6 +41,18 @@ namespace sqlite_orm { pragma_t(get_connection_t get_connection_) : get_connection(std::move(get_connection_)) {} + std::vector module_list() { + return this->get_pragma>("module_list"); + } + + bool recursive_triggers() { + return bool(this->get_pragma("recursive_triggers")); + } + + void recursive_triggers(bool value) { + this->set_pragma("recursive_triggers", int(value)); + } + void busy_timeout(int value) { this->set_pragma("busy_timeout", value); } @@ -115,6 +128,10 @@ namespace sqlite_orm { return this->get_pragma>(ss.str()); } + std::vector quick_check() { + return this->get_pragma>("quick_check"); + } + // will include generated columns in response as opposed to table_info std::vector table_xinfo(const std::string& tableName) const { auto connection = this->get_connection(); diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index b73853fef..9dbe7d0d1 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -5,15 +5,18 @@ #include // std::iterator_traits #include // std::string #include // std::integral_constant, std::declval -#include // std::pair +#include // std::move, std::forward, std::pair +#include // std::tuple #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" #include "functional/cxx_functional_polyfill.h" -#include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_traits.h" #include "connection_holder.h" #include "select_constraints.h" #include "values.h" +#include "table_reference.h" +#include "mapped_type_proxy.h" #include "ast/upsert_clause.h" #include "ast/set.h" @@ -89,10 +92,10 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_prepared_statement_v = - polyfill::is_specialization_of_v; + polyfill::is_specialization_of::value; template - using is_prepared_statement = polyfill::bool_constant>; + struct is_prepared_statement : polyfill::bool_constant> {}; /** * T - type of object to obtain from a database @@ -197,10 +200,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_v = polyfill::is_specialization_of::value; template - using is_insert = polyfill::bool_constant>; + struct is_insert : polyfill::bool_constant> {}; template struct insert_explicit { @@ -219,10 +222,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_v = polyfill::is_specialization_of::value; template - using is_replace = polyfill::bool_constant>; + struct is_replace : polyfill::bool_constant> {}; template struct insert_range_t { @@ -235,10 +238,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_range_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_range_v = + polyfill::is_specialization_of::value; template - using is_insert_range = polyfill::bool_constant>; + struct is_insert_range : polyfill::bool_constant> {}; template struct replace_range_t { @@ -251,10 +255,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_range_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_range_v = + polyfill::is_specialization_of::value; template - using is_replace_range = polyfill::bool_constant>; + struct is_replace_range : polyfill::bool_constant> {}; template struct insert_raw_t { @@ -264,10 +269,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_raw_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_raw_v = polyfill::is_specialization_of::value; template - using is_insert_raw = polyfill::bool_constant>; + struct is_insert_raw : polyfill::bool_constant> {}; template struct replace_raw_t { @@ -277,10 +282,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_raw_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_raw_v = polyfill::is_specialization_of::value; template - using is_replace_raw = polyfill::bool_constant>; + struct is_replace_raw : polyfill::bool_constant> {}; struct default_values_t {}; @@ -592,10 +597,21 @@ namespace sqlite_orm { */ template internal::remove_t remove(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a remove statement + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: remove(5); + */ + template + auto remove(Ids... ids) { + return remove>(std::forward(ids)...); + } +#endif + /** * Create an update statement. * T is an object type mapped to a storage. @@ -615,9 +631,20 @@ namespace sqlite_orm { */ template internal::get_t get(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get(5); + */ + template + auto get(Ids... ids) { + return get>(std::forward(ids)...); } +#endif /** * Create a get pointer statement. @@ -626,9 +653,20 @@ namespace sqlite_orm { */ template internal::get_pointer_t get_pointer(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get pointer statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get_pointer(5); + */ + template + auto get_pointer(Ids... ids) { + return get_pointer>(std::forward(ids)...); } +#endif #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** @@ -638,11 +676,22 @@ namespace sqlite_orm { */ template internal::get_optional_t get_optional(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get optional statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get_optional(5); + */ + template + auto get_optional(Ids... ids) { + return get_optional>(std::forward(ids)...); + } +#endif + /** * Create a remove all statement. * T is an object type mapped to a storage. @@ -652,36 +701,48 @@ namespace sqlite_orm { internal::remove_all_t remove_all(Args... args) { using args_tuple = std::tuple; internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + return {{std::forward(args)...}}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a get all statement. - * T is an object type mapped to a storage. - * Usage: storage.get_all(...); + * Create a remove all statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: storage.remove_all(...); */ - template - internal::get_all_t, Args...> get_all(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template + auto remove_all(Args... args) { + return remove_all>(std::forward(args)...); } +#endif /** * Create a get all statement. - * T is an object type mapped to a storage. + * T is an explicitly specified object mapped to a storage or a table alias. * R is a container type. std::vector is default - * Usage: storage.get_all(...); - */ - template - internal::get_all_t get_all(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + * Usage: storage.prepare(get_all(...)); + */ + template>, class... Args> + internal::get_all_t get_all(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get all statement. + * `mapped` is an explicitly specified table reference or table alias to be extracted. + * `R` is the container return type, which must have a `R::push_back(T&&)` method, and defaults to `std::vector` + * Usage: storage.get_all(...); + */ + template>, + class... Args> + auto get_all(Args&&... conditions) { + return get_all, R>(std::forward(conditions)...); } +#endif /** * Create an update all statement. @@ -692,62 +753,64 @@ namespace sqlite_orm { static_assert(internal::is_set::value, "first argument in update_all can be either set or dynamic_set"); using args_tuple = std::tuple; internal::validate_conditions(); - args_tuple conditions{std::forward(wh)...}; - return {std::move(set), std::move(conditions)}; + return {std::move(set), {std::forward(wh)...}}; } /** * Create a get all pointer statement. * T is an object type mapped to a storage. - * Usage: storage.get_all_pointer(...); + * R is a container return type. std::vector> is default + * Usage: storage.prepare(get_all_pointer(...)); */ - template - internal::get_all_pointer_t>, Args...> get_all_pointer(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, class... Args> + internal::get_all_pointer_t get_all_pointer(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all pointer statement. - * T is an object type mapped to a storage. + * `table` is an explicitly specified table reference of a mapped object to be extracted. * R is a container return type. std::vector> is default - * Usage: storage.get_all_pointer(...); - */ - template - internal::get_all_pointer_t get_all_pointer(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + * Usage: storage.prepare(get_all_pointer(...)); + */ + template>, + class... Args> + auto get_all_pointer(Args... conditions) { + return get_all_pointer, R>(std::forward(conditions)...); } +#endif #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** * Create a get all optional statement. * T is an object type mapped to a storage. + * R is a container return type. std::vector> is default * Usage: storage.get_all_optional(...); */ - template - internal::get_all_optional_t>, Args...> get_all_optional(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, class... Args> + internal::get_all_optional_t get_all_optional(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; } +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all optional statement. - * T is an object type mapped to a storage. + * `table` is an explicitly specified table reference of a mapped object to be extracted. * R is a container return type. std::vector> is default - * Usage: storage.get_all_optional(...); + * Usage: storage.get_all_optional(...); */ - template - internal::get_all_optional_t get_all_optional(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, + class... Args> + auto get_all_optional(Args&&... conditions) { + return get_all_optional, R>(std::forward(conditions)...); } -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#endif } diff --git a/dev/result_set_iterator.h b/dev/result_set_iterator.h new file mode 100644 index 000000000..e2430bc3b --- /dev/null +++ b/dev/result_set_iterator.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include // std::move +#include // std::input_iterator_tag, std::default_sentinel_t +#include // std::reference_wrapper + +#include "functional/cxx_universal.h" // ::ptrdiff_t +#include "statement_finalizer.h" +#include "row_extractor.h" +#include "column_result_proxy.h" +#include "util.h" + +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) +namespace sqlite_orm::internal { + + template + class result_set_iterator; + +#ifdef SQLITE_ORM_STL_HAS_DEFAULT_SENTINEL + using result_set_sentinel_t = std::default_sentinel_t; +#else + // sentinel + template<> + class result_set_iterator {}; + + using result_set_sentinel_t = result_set_iterator; +#endif + + /* + * Input iterator over a result set for a select statement. + */ + template + class result_set_iterator { + public: + using db_objects_type = DBOs; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + using iterator_concept = std::input_iterator_tag; +#else + using iterator_category = std::input_iterator_tag; +#endif + using difference_type = ptrdiff_t; + using value_type = column_result_proxy_t; + + public: + result_set_iterator(const db_objects_type& dbObjects, statement_finalizer stmt) : + db_objects{dbObjects}, stmt{std::move(stmt)} { + this->step(); + } + result_set_iterator(result_set_iterator&&) = default; + result_set_iterator& operator=(result_set_iterator&&) = default; + result_set_iterator(const result_set_iterator&) = delete; + result_set_iterator& operator=(const result_set_iterator&) = delete; + + /** @pre `*this != std::default_sentinel` */ + value_type operator*() const { + return this->extract(); + } + + result_set_iterator& operator++() { + this->step(); + return *this; + } + + void operator++(int) { + ++*this; + } + + friend bool operator==(const result_set_iterator& it, const result_set_sentinel_t&) noexcept { + return sqlite3_data_count(it.stmt.get()) == 0; + } + + private: + void step() { + perform_step(this->stmt.get(), [](sqlite3_stmt*) {}); + } + + value_type extract() const { + const auto rowExtractor = make_row_extractor(this->db_objects.get()); + return rowExtractor.extract(this->stmt.get(), 0); + } + + private: + std::reference_wrapper db_objects; + statement_finalizer stmt; + }; +} +#endif diff --git a/dev/result_set_view.h b/dev/result_set_view.h new file mode 100644 index 000000000..776e021c8 --- /dev/null +++ b/dev/result_set_view.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include // std::move, std::remove_cvref +#include // std::reference_wrapper +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) && \ + defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED) +#include // std::ranges::view_interface +#endif + +#include "functional/cxx_type_traits_polyfill.h" +#include "row_extractor.h" +#include "result_set_iterator.h" +#include "ast_iterator.h" +#include "connection_holder.h" +#include "util.h" +#include "type_traits.h" +#include "storage_lookup.h" + +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) +namespace sqlite_orm::internal { + /* + * A C++ view over a result set of a select statement, returned by `storage_t::iterate()`. + * + * `result_set_view` is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ + template + struct result_set_view +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED + : std::ranges::view_interface> +#endif + { + using db_objects_type = DBOs; + using expression_type = Select; + + result_set_view(const db_objects_type& dbObjects, connection_ref conn, Select expression) : + db_objects{dbObjects}, connection{std::move(conn)}, expression{std::move(expression)} {} + + result_set_view(result_set_view&&) = default; + result_set_view& operator=(result_set_view&&) = default; + result_set_view(const result_set_view&) = default; + result_set_view& operator=(const result_set_view&) = default; + + auto begin() { + const auto& exprDBOs = db_objects_for_expression(this->db_objects.get(), this->expression); + using ExprDBOs = std::remove_cvref_t; + // note: Select can be `select_t` or `with_t` + using select_type = polyfill::detected_or_t; + using column_result_type = column_result_of_t; + using context_t = serializer_context; + context_t context{exprDBOs}; + context.skip_table_name = false; + context.replace_bindable_with_question = true; + + statement_finalizer stmt{prepare_stmt(this->connection.get(), serialize(this->expression, context))}; + iterate_ast(this->expression, conditional_binder{stmt.get()}); + + // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; + // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. + using iterator_type = result_set_iterator; + return iterator_type{this->db_objects, std::move(stmt)}; + } + + result_set_sentinel_t end() { + return {}; + } + + private: + std::reference_wrapper db_objects; + connection_ref connection; + expression_type expression; + }; +} + +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; +#endif +#endif diff --git a/dev/row_extractor.h b/dev/row_extractor.h index ed60e596a..42becde10 100644 --- a/dev/row_extractor.h +++ b/dev/row_extractor.h @@ -6,50 +6,128 @@ #include // std::system_error #include // std::string, std::wstring #ifndef SQLITE_ORM_OMITS_CODECVT -#include // std::wstring_convert, std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert +#include // std::codecvt_utf8_utf16 +#endif #include // std::vector #include // strlen -#include #include // std::copy #include // std::back_inserter #include // std::tuple, std::tuple_size, std::tuple_element +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif #include "functional/cxx_universal.h" +#include "functional/cxx_functional_polyfill.h" +#include "functional/static_magic.h" +#include "tuple_helper/tuple_transformer.h" +#include "column_result_proxy.h" #include "arithmetic_tag.h" #include "pointer_value.h" #include "journal_mode.h" #include "error_code.h" #include "is_std_ptr.h" +#include "type_traits.h" namespace sqlite_orm { /** - * Helper class used to cast values from argv to V class - * which depends from column type. - * + * Helper for casting values originating from SQL to C++ typed values, usually from rows of a result set. + * + * sqlite_orm provides specializations for known C++ types, users may define their custom specialization + * of this helper. + * + * @note (internal): Since row extractors are used in certain contexts with only one purpose at a time + * (e.g., converting a row result set but not function values or column text), + * there are factory functions that perform conceptual checking that should be used + * instead of directly creating row extractors. + * + * */ template struct row_extractor { - // used in sqlite3_exec (select) - V extract(const char* row_value) const = delete; - - // used in sqlite_column (iteration, get_all) + /* + * Called during one-step query execution (one result row) for each column of a result row. + */ + V extract(const char* columnText) const = delete; + + /* + * Called during multi-step query execution (result set) for each column of a result row. + */ V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; - // used in user defined functions + /* + * Called before invocation of user-defined scalar or aggregate functions, + * in order to unbox dynamically typed SQL function values into a tuple of C++ function arguments. + */ V extract(sqlite3_value* value) const = delete; }; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_column_text_extractable = requires(const row_extractor& extractor, const char* columnText) { + { extractor.extract(columnText) } -> std::same_as; + }; + + template + concept orm_row_value_extractable = + requires(const row_extractor& extractor, sqlite3_stmt* stmt, int columnIndex) { + { extractor.extract(stmt, columnIndex) } -> std::same_as; + }; + + template + concept orm_boxed_value_extractable = requires(const row_extractor& extractor, sqlite3_value* value) { + { extractor.extract(value) } -> std::same_as; + }; +#endif + + namespace internal { + /* + * Make a row extractor to be used for casting SQL column text to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + row_extractor column_text_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for converting a value from a SQL result row set to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + row_extractor row_value_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for unboxing a dynamically typed SQL value to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + row_extractor boxed_value_extractor() { + return {}; + } + } + template int extract_single_value(void* data, int argc, char** argv, char**) { auto& res = *(R*)data; if(argc) { - res = row_extractor{}.extract(argv[0]); + const auto rowExtractor = internal::column_text_extractor(); + res = rowExtractor.extract(argv[0]); } return 0; } +#if SQLITE_VERSION_NUMBER >= 3020000 /** * Specialization for the 'pointer-passing interface'. * @@ -60,6 +138,10 @@ namespace sqlite_orm { struct row_extractor, void> { using V = pointer_arg; + V extract(const char* columnText) const = delete; + + V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; + V extract(sqlite3_value* value) const { return {(P*)sqlite3_value_pointer(value, T::value)}; } @@ -70,14 +152,15 @@ namespace sqlite_orm { */ template struct row_extractor, void>; +#endif /** * Specialization for arithmetic types. */ template struct row_extractor::value>> { - V extract(const char* row_value) const { - return this->extract(row_value, tag()); + V extract(const char* columnText) const { + return this->extract(columnText, tag()); } V extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -91,8 +174,8 @@ namespace sqlite_orm { private: using tag = arithmetic_tag_t; - V extract(const char* row_value, const int_or_smaller_tag&) const { - return static_cast(atoi(row_value)); + V extract(const char* columnText, const int_or_smaller_tag&) const { + return static_cast(atoi(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const int_or_smaller_tag&) const { @@ -103,8 +186,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int(value)); } - V extract(const char* row_value, const bigint_tag&) const { - return static_cast(atoll(row_value)); + V extract(const char* columnText, const bigint_tag&) const { + return static_cast(atoll(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const bigint_tag&) const { @@ -115,8 +198,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int64(value)); } - V extract(const char* row_value, const real_tag&) const { - return static_cast(atof(row_value)); + V extract(const char* columnText, const real_tag&) const { + return static_cast(atof(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const real_tag&) const { @@ -131,17 +214,17 @@ namespace sqlite_orm { /** * Specialization for std::string. */ - template<> - struct row_extractor { - std::string extract(const char* row_value) const { - if(row_value) { - return row_value; + template + struct row_extractor::value>> { + T extract(const char* columnText) const { + if(columnText) { + return columnText; } else { return {}; } } - std::string extract(sqlite3_stmt* stmt, int columnIndex) const { + T extract(sqlite3_stmt* stmt, int columnIndex) const { if(auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex)) { return cStr; } else { @@ -149,7 +232,7 @@ namespace sqlite_orm { } } - std::string extract(sqlite3_value* value) const { + T extract(sqlite3_value* value) const { if(auto cStr = (const char*)sqlite3_value_text(value)) { return cStr; } else { @@ -163,10 +246,10 @@ namespace sqlite_orm { */ template<> struct row_extractor { - std::wstring extract(const char* row_value) const { - if(row_value) { + std::wstring extract(const char* columnText) const { + if(columnText) { std::wstring_convert> converter; - return converter.from_bytes(row_value); + return converter.from_bytes(columnText); } else { return {}; } @@ -196,27 +279,42 @@ namespace sqlite_orm { struct row_extractor::value>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return is_std_ptr::make(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(columnText)); } else { return {}; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(stmt, columnIndex)); } else { return {}; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(value)); } else { return {}; } @@ -228,27 +326,42 @@ namespace sqlite_orm { struct row_extractor>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return std::make_optional(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(columnText)); } else { return std::nullopt; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(stmt, columnIndex)); } else { return std::nullopt; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(value)); } else { return std::nullopt; } @@ -257,8 +370,8 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template<> - struct row_extractor { - nullptr_t extract(const char* /*row_value*/) const { + struct row_extractor { + nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -274,9 +387,9 @@ namespace sqlite_orm { * Specialization for std::vector. */ template<> - struct row_extractor> { - std::vector extract(const char* row_value) const { - return {row_value, row_value + (row_value ? ::strlen(row_value) : 0)}; + struct row_extractor, void> { + std::vector extract(const char* columnText) const { + return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } std::vector extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -292,37 +405,14 @@ namespace sqlite_orm { } }; - template - struct row_extractor> { - - std::tuple extract(char** argv) const { - return this->extract(argv, std::make_index_sequence{}); - } - - std::tuple extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - return this->extract(stmt, std::make_index_sequence{}); - } - - protected: - template - std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(stmt, Idx)...}; - } - - template - std::tuple extract(char** argv, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(argv[Idx])...}; - } - }; - /** * Specialization for journal_mode. */ template<> struct row_extractor { - journal_mode extract(const char* row_value) const { - if(row_value) { - if(auto res = internal::journal_mode_from_string(row_value)) { + journal_mode extract(const char* columnText) const { + if(columnText) { + if(auto res = internal::journal_mode_from_string(columnText)) { return std::move(*res); } else { throw std::system_error{orm_error_code::incorrect_journal_mode_string}; @@ -336,5 +426,128 @@ namespace sqlite_orm { auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex); return this->extract(cStr); } + + journal_mode extract(sqlite3_value* value) const = delete; }; + + namespace internal { + + /* + * Helper to extract a structure from a rowset. + */ + template + struct struct_extractor; + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + /* + * Returns a value-based row extractor for an unmapped type, + * returns a structure extractor for a table reference, tuple or named struct. + */ + template + auto make_row_extractor([[maybe_unused]] const DBOs& dbObjects) { + if constexpr(polyfill::is_specialization_of_v || + polyfill::is_specialization_of_v || is_table_reference_v) { + return struct_extractor{dbObjects}; + } else { + return row_value_extractor(); + } + } +#else + /* + * Overload for an unmapped type returns a common row extractor. + */ + template< + class R, + class DBOs, + std::enable_if_t, + polyfill::is_specialization_of, + is_table_reference>>::value, + bool> = true> + auto make_row_extractor(const DBOs& /*dbObjects*/) { + return row_value_extractor(); + } + + /* + * Overload for a table reference, tuple or aggregate of column results returns a structure extractor. + */ + template, + polyfill::is_specialization_of, + is_table_reference>::value, + bool> = true> + struct_extractor make_row_extractor(const DBOs& dbObjects) { + return {dbObjects}; + } +#endif + + /** + * Specialization for a tuple of top-level column results. + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + std::tuple extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + std::tuple...> extract(sqlite3_stmt* stmt, + int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return {make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + // unused to date + std::tuple...> extract(sqlite3_stmt* stmt, int& columnIndex) const = delete; + + std::tuple extract(sqlite3_value* value) const = delete; + }; + + /** + * Specialization for an unmapped structure to be constructed ad-hoc from column results. + * + * This plays together with `column_result_of_t`, which returns `struct_t` as `structure` + */ + template + struct struct_extractor>, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + // see unit test tests/prepared_statement_tests/select.cpp/TEST_CASE("Prepared select")/SECTION("non-aggregate struct") + template = true> + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + template = true> + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + std::tuple t{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + return create_from_tuple(std::move(t), std::index_sequence_for{}); + } + + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + // see unit test tests/prepared_statement_tests/select.cpp/TEST_CASE("Prepared select")/SECTION("non-aggregate struct") + template = true> + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + template = true> + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + std::tuple t{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + return create_from_tuple(std::move(t), std::index_sequence_for{}); + } + + O extract(sqlite3_value* value) const = delete; + }; + } } diff --git a/dev/row_extractor_builder.h b/dev/row_extractor_builder.h deleted file mode 100644 index 8ebf796e6..000000000 --- a/dev/row_extractor_builder.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "functional/cxx_universal.h" -#include "row_extractor.h" -#include "mapped_row_extractor.h" - -namespace sqlite_orm { - - namespace internal { - - template - row_extractor make_row_extractor(nullptr_t) { - return {}; - } - - template - mapped_row_extractor make_row_extractor(const Table* table) { - return {*table}; - } - } - -} diff --git a/dev/column.h b/dev/schema/column.h similarity index 65% rename from dev/column.h rename to dev/schema/column.h index 61f744800..45c95a367 100644 --- a/dev/column.h +++ b/dev/schema/column.h @@ -4,15 +4,16 @@ #include // std::string #include // std::unique_ptr #include // std::is_same, std::is_member_object_pointer +#include // std::move -#include "functional/cxx_universal.h" -#include "functional/cxx_type_traits_polyfill.h" -#include "tuple_helper/tuple_traits.h" -#include "tuple_helper/tuple_filter.h" -#include "type_traits.h" -#include "member_traits/member_traits.h" -#include "type_is_nullable.h" -#include "constraints.h" +#include "../functional/cxx_universal.h" +#include "../functional/cxx_type_traits_polyfill.h" +#include "../tuple_helper/tuple_traits.h" +#include "../tuple_helper/tuple_filter.h" +#include "../type_traits.h" +#include "../member_traits/member_traits.h" +#include "../type_is_nullable.h" +#include "../constraints.h" namespace sqlite_orm { @@ -75,19 +76,11 @@ namespace sqlite_orm { constraints_type constraints; /** - * Checks whether contraints are of trait `Trait` + * Checks whether contraints contain specified type. */ template class Trait> - constexpr bool is() const { - return tuple_has::value; - } - - constexpr bool is_generated() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - return is(); -#else - return false; -#endif + constexpr static bool is() { + return tuple_has::value; } /** @@ -111,8 +104,21 @@ namespace sqlite_orm { #endif }; + template + struct column_field_expression { + using type = void; + }; + + template + struct column_field_expression, void> { + using type = typename column_t::member_pointer_t; + }; + + template + using column_field_expression_t = typename column_field_expression::type; + template - SQLITE_ORM_INLINE_VAR constexpr bool is_column_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_column_v = polyfill::is_specialization_of::value; template using is_column = polyfill::bool_constant>; @@ -126,29 +132,33 @@ namespace sqlite_orm { template class TraitFn> using col_index_sequence_with = filter_tuple_sequence_t::template fn, + check_if_has::template fn, constraints_type_t, filter_tuple_sequence_t>; template class TraitFn> using col_index_sequence_excluding = filter_tuple_sequence_t::template fn, + check_if_has_not::template fn, constraints_type_t, filter_tuple_sequence_t>; } /** - * Column builder function. You should use it to create columns instead of constructor + * Factory function for a column definition from a member object pointer of the object to be mapped. */ template = true> - internal::column_t make_column(std::string name, M m, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::column_t + make_column(std::string name, M memberPointer, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), m, {}, std::make_tuple(constraints...)}); + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); } /** - * Column builder function with setter and getter. You should use it to create columns instead of constructor + * Factory function for a column definition from "setter" and "getter" member function pointers of the object to be mapped. */ template make_column(std::string name, S setter, G getter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::make_tuple(constraints...)}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); } /** - * Column builder function with getter and setter (reverse order). You should use it to create columns instead of - * constructor + * Factory function for a column definition from "getter" and "setter" member function pointers of the object to be mapped. */ template make_column(std::string name, G getter, S setter, Op... constraints) { static_assert(std::is_same, internal::getter_field_type_t>::value, "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::make_tuple(constraints...)}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); } } diff --git a/dev/index.h b/dev/schema/index.h similarity index 95% rename from dev/index.h rename to dev/schema/index.h index efa5a9ff5..a846e51d9 100644 --- a/dev/index.h +++ b/dev/schema/index.h @@ -4,10 +4,10 @@ #include // std::string #include // std::forward -#include "functional/cxx_universal.h" -#include "tuple_helper/tuple_filter.h" -#include "indexed_column.h" -#include "table_type_of.h" +#include "../functional/cxx_universal.h" +#include "../tuple_helper/tuple_traits.h" +#include "../indexed_column.h" +#include "../table_type_of.h" namespace sqlite_orm { diff --git a/dev/schema/table.h b/dev/schema/table.h new file mode 100644 index 000000000..055e70be5 --- /dev/null +++ b/dev/schema/table.h @@ -0,0 +1,480 @@ +#pragma once + +#include // std::string +#include // std::remove_const, std::is_member_pointer, std::true_type, std::false_type +#include // std::vector +#include // std::tuple_element +#include // std::forward, std::move + +#include "../functional/cxx_universal.h" // ::size_t +#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cxx_functional_polyfill.h" +#include "../functional/static_magic.h" +#include "../functional/mpl.h" +#include "../functional/index_sequence_util.h" +#include "../tuple_helper/tuple_filter.h" +#include "../tuple_helper/tuple_traits.h" +#include "../tuple_helper/tuple_iteration.h" +#include "../tuple_helper/tuple_transformer.h" +#include "../member_traits/member_traits.h" +#include "../typed_comparator.h" +#include "../type_traits.h" +#include "../alias_traits.h" +#include "../constraints.h" +#include "../table_info.h" +#include "column.h" + +namespace sqlite_orm { + + namespace internal { + + template + using is_table_element_or_constraint = mpl::invoke_t, + check_if, + check_if, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template>, + T>; + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * A subselect mapper's CTE moniker, void otherwise. + */ + template + using moniker_of_or_void_t = polyfill::detected_or_t; + + /** + * If O is a subselect_mapper then returns its nested type name O::cte_moniker_type, + * otherwise O itself is a regular object type to be mapped. + */ + template + using mapped_object_type_for_t = polyfill::detected_or_t; +#endif + + struct basic_table { + + /** + * Table name. + */ + std::string name; + }; + + /** + * Table definition. + */ + template + struct table_t : basic_table { +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // this typename is used in contexts where it is known that the 'table' holds a subselect_mapper + // instead of a regular object type + using cte_mapper_type = O; + using cte_moniker_type = moniker_of_or_void_t; + using object_type = mapped_object_type_for_t; +#else + using object_type = O; +#endif + using elements_type = std::tuple; + + static constexpr bool is_without_rowid_v = WithoutRowId; + + using is_without_rowid = polyfill::bool_constant; + + elements_type elements; + +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + table_t(std::string name_, elements_type elements_) : + basic_table{std::move(name_)}, elements{std::move(elements_)} {} +#endif + + table_t without_rowid() const { + return {this->name, this->elements}; + } + + /* + * Returns the number of elements of the specified type. + */ + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using filtered_index_sequence = col_index_sequence_with; + return int(filtered_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); + } + + /** + * Function used to get field value from object by mapped member pointer/setter/getter. + * + * For a setter the corresponding getter has to be searched, + * so the method returns a pointer to the field as returned by the found getter. + * Otherwise the method invokes the member pointer and returns its result. + */ + template = true> + decltype(auto) object_field_value(const object_type& object, M memberPointer) const { + return polyfill::invoke(memberPointer, object); + } + + template = true> + const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { + using field_type = member_field_type_t; + const field_type* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + call_as_template_base([&res, &memberPointer, &object](const auto& column) { + if(compare_any(column.setter, memberPointer)) { + res = &polyfill::invoke(column.member_pointer, object); + } + })); + return res; + } + + const basic_generated_always::storage_type* + find_column_generated_storage_type(const std::string& name) const { + const basic_generated_always::storage_type* result = nullptr; +#if SQLITE_VERSION_NUMBER >= 3031000 + iterate_tuple(this->elements, + col_index_sequence_with{}, + [&result, &name](auto& column) { + if(column.name != name) { + return; + } + using generated_op_index_sequence = + filter_tuple_sequence_t, + is_generated_always>; + constexpr size_t opIndex = index_sequence_value_at<0>(generated_op_index_sequence{}); + result = &std::get(column.constraints).storage; + }); +#else + (void)name; +#endif + return result; + } + + /** + * Call passed lambda with all defined primary keys. + */ + template + void for_each_primary_key(L&& lambda) const { + using pk_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->elements, pk_index_sequence{}, lambda); + } + + std::vector composite_key_columns_names() const { + std::vector res; + this->for_each_primary_key([this, &res](auto& primaryKey) { + res = this->composite_key_columns_names(primaryKey); + }); + return res; + } + + std::vector primary_key_column_names() const { + using pkcol_index_sequence = col_index_sequence_with; + + if(pkcol_index_sequence::size() > 0) { + return create_from_tuple>(this->elements, + pkcol_index_sequence{}, + &column_identifier::name); + } else { + return this->composite_key_columns_names(); + } + } + + template + void for_each_primary_key_column(L&& lambda) const { + iterate_tuple(this->elements, + col_index_sequence_with{}, + call_as_template_base([&lambda](const auto& column) { + lambda(column.member_pointer); + })); + this->for_each_primary_key([&lambda](auto& primaryKey) { + iterate_tuple(primaryKey.columns, lambda); + }); + } + + template + std::vector composite_key_columns_names(const primary_key_t& primaryKey) const { + return create_from_tuple>(primaryKey.columns, + [this, empty = std::string{}](auto& memberPointer) { + if(const std::string* columnName = + this->find_column_name(memberPointer)) { + return *columnName; + } else { + return empty; + } + }); + } + + /** + * Searches column name by class member pointer passed as the first argument. + * @return column name or empty string if nothing found. + */ + template = true> + const std::string* find_column_name(M m) const { + const std::string* res = nullptr; + using field_type = member_field_type_t; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + [&res, m](auto& c) { + if(compare_any(c.member_pointer, m) || compare_any(c.setter, m)) { + res = &c.name; + } + }); + return res; + } + + /** + * Call passed lambda with all defined foreign keys. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_foreign_key(L&& lambda) const { + using fk_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->elements, fk_index_sequence{}, lambda); + } + + template + void for_each_foreign_key_to(L&& lambda) const { + using fk_index_sequence = filter_tuple_sequence_t; + using filtered_index_sequence = filter_tuple_sequence_t::template fn, + target_type_t, + fk_index_sequence>; + iterate_tuple(this->elements, filtered_index_sequence{}, lambda); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + using col_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->elements, col_index_sequence{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } + + std::vector get_table_info() const; + }; + + template + struct is_table : std::false_type {}; + + template + struct is_table> : std::true_type {}; + + template + struct virtual_table_t : basic_table { + using module_details_type = M; + using object_type = typename module_details_type::object_type; + using elements_type = typename module_details_type::columns_type; + + static constexpr bool is_without_rowid_v = false; + using is_without_rowid = polyfill::bool_constant; + + module_details_type module_details; + +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + virtual_table_t(std::string name, module_details_type module_details) : + basic_table{std::move(name)}, module_details{std::move(module_details)} {} +#endif + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + this->module_details.template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->module_details.template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + this->module_details.for_each_column(lambda); + } + }; + + template + struct is_virtual_table : std::false_type {}; + + template + struct is_virtual_table> : std::true_type {}; + +#if SQLITE_VERSION_NUMBER >= 3009000 + template + struct using_fts5_t { + using object_type = T; + using columns_type = std::tuple; + + columns_type columns; + + using_fts5_t(columns_type columns) : columns(std::move(columns)) {} + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->columns, col_index_sequence_excluding{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + using col_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->columns, col_index_sequence{}, lambda); + } + }; +#endif + + template + bool exists_in_composite_primary_key(const table_t& table, + const column_field& column) { + bool res = false; + table.for_each_primary_key([&column, &res](auto& primaryKey) { + using colrefs_tuple = decltype(primaryKey.columns); + using same_type_index_sequence = + filter_tuple_sequence_t>::template fn, + member_field_type_t>; + iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { + if(compare_any(memberPointer, column.member_pointer) || compare_any(memberPointer, column.setter)) { + res = true; + } + }); + }); + return res; + } + + template + bool exists_in_composite_primary_key(const virtual_table_t& /*virtualTable*/, + const column_field& /*column*/) { + return false; + } + } + +#if SQLITE_VERSION_NUMBER >= 3009000 + template>::object_type> + internal::using_fts5_t using_fts5(Cs... columns) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(columns)...)}); + } + + template + internal::using_fts5_t using_fts5(Cs... columns) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(columns)...)}); + } +#endif + + /** + * Factory function for a table definition. + * + * The mapped object type is determined implicitly from the first column definition. + */ + template>::object_type> + internal::table_t make_table(std::string name, Cs... args) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), std::make_tuple(std::forward(args)...)}); + } + + /** + * Factory function for a table definition. + * + * The mapped object type is explicitly specified. + */ + template + internal::table_t make_table(std::string name, Cs... args) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), std::make_tuple(std::forward(args)...)}); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a table definition. + * + * The mapped object type is explicitly specified. + */ + template + auto make_table(std::string name, Cs... args) { + return make_table>(std::move(name), std::forward(args)...); + } +#endif + + template + internal::virtual_table_t make_virtual_table(std::string name, M module_details) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); + } +} diff --git a/dev/triggers.h b/dev/schema/triggers.h similarity index 99% rename from dev/triggers.h rename to dev/schema/triggers.h index c85e65a13..a090c4015 100644 --- a/dev/triggers.h +++ b/dev/schema/triggers.h @@ -5,8 +5,8 @@ #include #include -#include "functional/cxx_universal.h" -#include "optional_container.h" +#include "../functional/cxx_universal.h" +#include "../optional_container.h" // NOTE Idea : Maybe also implement a custom trigger system to call a c++ callback when a trigger triggers ? // (Could be implemented with a normal trigger that insert or update an internal table and then retreive diff --git a/dev/select_constraints.h b/dev/select_constraints.h index 7b1f690bd..4b7226b1e 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -1,21 +1,26 @@ #pragma once +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif #include // std::remove_const #include // std::string #include // std::move #include // std::tuple, std::get, std::tuple_size #include "functional/cxx_optional.h" -#include "functional/cxx_universal.h" +#include "functional/cxx_universal.h" // ::size_t #include "functional/cxx_type_traits_polyfill.h" #include "is_base_of_template.h" -#include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_traits.h" +#include "tuple_helper/tuple_transformer.h" +#include "tuple_helper/tuple_iteration.h" #include "optional_container.h" #include "ast/where.h" #include "ast/group_by.h" #include "core_functions.h" #include "alias_traits.h" -#include "column_pointer.h" +#include "cte_moniker.h" namespace sqlite_orm { @@ -78,11 +83,36 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_columns_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_columns_v = polyfill::is_specialization_of::value; template using is_columns = polyfill::bool_constant>; + /* + * Captures the type of an aggregate/structure/object and column expressions, such that + * `T` can be constructed in-place as part of a result row. + * `T` must be constructible using direct-list-initialization. + */ + template + struct struct_t { + using columns_type = std::tuple; + + columns_type columns; + bool distinct = false; + + static constexpr int count = std::tuple_size::value; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + struct_t(columns_type columns) : columns{std::move(columns)} {} +#endif + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_struct_v = polyfill::is_specialization_of::value; + + template + using is_struct = polyfill::bool_constant>; + /** * Subselect object type. */ @@ -102,7 +132,7 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_select_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_select_v = polyfill::is_specialization_of::value; template using is_select = polyfill::bool_constant>; @@ -110,22 +140,21 @@ namespace sqlite_orm { /** * Base for UNION, UNION ALL, EXCEPT and INTERSECT */ - template + template struct compound_operator { - using left_type = L; - using right_type = R; + using expressions_tuple = std::tuple; - left_type left; - right_type right; + expressions_tuple compound; - compound_operator(left_type l, right_type r) : left(std::move(l)), right(std::move(r)) { - this->left.highest_level = true; - this->right.highest_level = true; + compound_operator(expressions_tuple compound) : compound{std::move(compound)} { + iterate_tuple(this->compound, [](auto& expression) { + expression.highest_level = true; + }); } }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_compound_operator_v = is_base_of_template_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_compound_operator_v = is_base_of_template::value; template using is_compound_operator = polyfill::bool_constant>; @@ -149,15 +178,12 @@ namespace sqlite_orm { /** * UNION object type. */ - template - struct union_t : public compound_operator, union_base { - using left_type = typename compound_operator::left_type; - using right_type = typename compound_operator::right_type; + template + struct union_t : public compound_operator, union_base { + using typename compound_operator::expressions_tuple; - union_t(left_type l, right_type r, bool all_) : - compound_operator{std::move(l), std::move(r)}, union_base{all_} {} - - union_t(left_type l, right_type r) : union_t{std::move(l), std::move(r), false} {} + union_t(expressions_tuple compound, bool all) : + compound_operator{std::move(compound)}, union_base{all} {} }; struct except_string { @@ -169,11 +195,9 @@ namespace sqlite_orm { /** * EXCEPT object type. */ - template - struct except_t : compound_operator, except_string { - using super = compound_operator; - using left_type = typename super::left_type; - using right_type = typename super::right_type; + template + struct except_t : compound_operator, except_string { + using super = compound_operator; using super::super; }; @@ -186,15 +210,117 @@ namespace sqlite_orm { /** * INTERSECT object type. */ - template - struct intersect_t : compound_operator, intersect_string { - using super = compound_operator; - using left_type = typename super::left_type; - using right_type = typename super::right_type; + template + struct intersect_t : compound_operator, intersect_string { + using super = compound_operator; using super::super; }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /* + * Turn explicit columns for a CTE into types that the CTE backend understands + */ + template + struct decay_explicit_column { + using type = T; + }; + template + struct decay_explicit_column> { + using type = alias_holder; + }; + template + struct decay_explicit_column> { + using type = std::string; + }; + template + using decay_explicit_column_t = typename decay_explicit_column::type; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Materialization hint to instruct SQLite to materialize the select statement of a CTE into an ephemeral table as an "optimization fence". + */ + struct materialized_t {}; + + /* + * Materialization hint to instruct SQLite to substitute a CTE's select statement as a subquery subject to optimization. + */ + struct not_materialized_t {}; +#endif + + /** + * Monikered (aliased) CTE expression. + */ + template + struct common_table_expression { + using cte_moniker_type = Moniker; + using expression_type = Select; + using explicit_colrefs_tuple = ExplicitCols; + using hints_tuple = Hints; + static constexpr size_t explicit_colref_count = std::tuple_size_v; + + SQLITE_ORM_NOUNIQUEADDRESS hints_tuple hints; + explicit_colrefs_tuple explicitColumns; + expression_type subselect; + + common_table_expression(explicit_colrefs_tuple explicitColumns, expression_type subselect) : + explicitColumns{std::move(explicitColumns)}, subselect{std::move(subselect)} { + this->subselect.highest_level = true; + } + }; + + template + using common_table_expressions = std::tuple; + + template + struct cte_builder { + ExplicitCols explicitColumns; + +#if SQLITE_VERSION_NUMBER >= 3035000 && defined(SQLITE_ORM_WITH_CPP20_ALIASES) + template = true> + common_table_expression, Select> as(Select sel) && { + return {std::move(this->explicitColumns), std::move(sel)}; + } + + template = true> + common_table_expression, select_t> + as(Compound sel) && { + return {std::move(this->explicitColumns), {std::move(sel)}}; + } +#else + template = true> + common_table_expression, Select> as(Select sel) && { + return {std::move(this->explicitColumns), std::move(sel)}; + } + + template = true> + common_table_expression, select_t> as(Compound sel) && { + return {std::move(this->explicitColumns), {std::move(sel)}}; + } +#endif + }; + + /** + * WITH object type - expression with prepended CTEs. + */ + template + struct with_t { + using cte_type = common_table_expressions; + using expression_type = E; + + bool recursiveIndicated; + cte_type cte; + expression_type expression; + + with_t(bool recursiveIndicated, cte_type cte, expression_type expression) : + recursiveIndicated{recursiveIndicated}, cte{std::move(cte)}, expression{std::move(expression)} { + if constexpr(is_select_v) { + this->expression.highest_level = true; + } + } + }; +#endif + /** * Generic way to get DISTINCT value from any type. */ @@ -208,6 +334,11 @@ namespace sqlite_orm { return cols.distinct; } + template + bool get_distinct(const struct_t& cols) { + return cols.distinct; + } + template struct asterisk_t { using type = T; @@ -331,19 +462,21 @@ namespace sqlite_orm { return cols; } + /* + * Combine multiple columns in a tuple. + */ template - internal::columns_t columns(Args... args) { + constexpr internal::columns_t columns(Args... args) { return {std::make_tuple(std::forward(args)...)}; } - /** - * Use it like this: - * struct MyType : BaseType { ... }; - * storage.select(column(&BaseType::id)); + /* + * Construct an unmapped structure ad-hoc from multiple columns. + * `T` must be constructible from the column results using direct-list-initialization. */ - template - internal::column_pointer column(F f) { - return {std::move(f)}; + template + constexpr internal::struct_t struct_(Args... args) { + return {std::make_tuple(std::forward(args)...)}; } /** @@ -353,44 +486,263 @@ namespace sqlite_orm { internal::select_t select(T t, Args... args) { using args_tuple = std::tuple; internal::validate_conditions(); - return {std::move(t), std::make_tuple(std::forward(args)...)}; + return {std::move(t), {std::forward(args)...}}; } /** * Public function for UNION operator. - * lhs and rhs are subselect objects. + * Expressions are subselect objects. + * Look through example in examples/union.cpp + */ + template + internal::union_t union_(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}, false}; + } + + /** + * Public function for UNION ALL operator. + * Expressions are subselect objects. * Look through example in examples/union.cpp */ - template - internal::union_t union_(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + template + internal::union_t union_all(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}, true}; } /** * Public function for EXCEPT operator. - * lhs and rhs are subselect objects. + * Expressions are subselect objects. * Look through example in examples/except.cpp */ - template - internal::except_t except(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + template + internal::except_t except(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}}; + } + + template + internal::intersect_t intersect(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}}; + } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#if SQLITE_VERSION_NUMBER >= 3035003 +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Materialization hint to instruct SQLite to materialize the select statement of a CTE into an ephemeral table as an "optimization fence". + * + * Example: + * 1_ctealias().as(select(1)); + */ + inline consteval internal::materialized_t materialized() { + return {}; } - template - internal::intersect_t intersect(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + /* + * Materialization hint to instruct SQLite to substitute a CTE's select statement as a subquery subject to optimization. + * + * Example: + * 1_ctealias().as(select(1)); + */ + inline consteval internal::not_materialized_t not_materialized() { + return {}; } +#endif +#endif /** - * Public function for UNION ALL operator. - * lhs and rhs are subselect objects. - * Look through example in examples/union.cpp + * Introduce the construction of a common table expression using the specified moniker. + * + * The list of explicit columns is optional; + * if provided the number of columns must match the number of columns of the subselect. + * The column names will be merged with the subselect: + * 1. column names of subselect + * 2. explicit columns + * 3. fill in empty column names with column index + * + * Example: + * using cte_1 = decltype(1_ctealias); + * cte()(select(&Object::id)); + * cte(&Object::name)(select("object")); */ - template - internal::union_t union_all(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs), true}; + template, + std::is_member_pointer, + internal::is_column, + std::is_same>, + std::is_convertible>...>, + bool> = true> + auto cte(ExplicitCols... explicitColumns) { + using namespace ::sqlite_orm::internal; + static_assert(is_cte_moniker_v, "Moniker must be a CTE moniker"); + static_assert((!is_builtin_numeric_column_alias_v && ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + + using builder_type = + cte_builder, decay_explicit_column_t>>; + return builder_type{{std::move(explicitColumns)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires((internal::is_column_alias_v || std::is_member_pointer_v || + internal::is_column_v || + std::same_as> || + std::convertible_to) && + ...) + auto cte(ExplicitCols... explicitColumns) { + using namespace ::sqlite_orm::internal; + static_assert((!is_builtin_numeric_column_alias_v && ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + + using builder_type = + cte_builder, decay_explicit_column_t>>; + return builder_type{{std::move(explicitColumns)...}}; + } +#endif + + namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + template + requires((is_column_alias_v || std::is_member_pointer_v || + std::same_as> || + std::convertible_to) && + ...) + auto cte_moniker::operator()(ExplicitCols... explicitColumns) const { + return cte>(std::forward(explicitColumns)...); + } +#else + template + template, + std::is_member_pointer, + std::is_same>, + std::is_convertible>...>, + bool>> + auto cte_moniker::operator()(ExplicitCols... explicitColumns) const { + return cte>(std::forward(explicitColumns)...); + } +#endif } + /** + * With-clause for a tuple of ordinary CTEs. + * + * Despite the missing RECURSIVE keyword, the CTEs can be recursive. + */ + template = true> + internal::with_t with(internal::common_table_expressions ctes, E expression) { + return {false, std::move(ctes), std::move(expression)}; + } + + /** + * With-clause for a tuple of ordinary CTEs. + * + * Despite the missing RECURSIVE keyword, the CTEs can be recursive. + */ + template = true> + internal::with_t, CTEs...> with(internal::common_table_expressions ctes, + Compound sel) { + return {false, std::move(ctes), sqlite_orm::select(std::move(sel))}; + } + + /** + * With-clause for a single ordinary CTE. + * + * Despite the missing `RECURSIVE` keyword, the CTE can be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies_not = true> + internal::with_t with(CTE cte, E expression) { + return {false, {std::move(cte)}, std::move(expression)}; + } + + /** + * With-clause for a single ordinary CTE. + * + * Despite the missing `RECURSIVE` keyword, the CTE can be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies = true> + internal::with_t, CTE> with(CTE cte, Compound sel) { + return {false, {std::move(cte)}, sqlite_orm::select(std::move(sel))}; + } + + /** + * With-clause for a tuple of potentially recursive CTEs. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + */ + template = true> + internal::with_t with_recursive(internal::common_table_expressions ctes, E expression) { + return {true, std::move(ctes), std::move(expression)}; + } + + /** + * With-clause for a tuple of potentially recursive CTEs. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + */ + template = true> + internal::with_t, CTEs...> + with_recursive(internal::common_table_expressions ctes, Compound sel) { + return {true, std::move(ctes), sqlite_orm::select(std::move(sel))}; + } + + /** + * With-clause for a single potentially recursive CTE. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with_recursive(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies_not = true> + internal::with_t with_recursive(CTE cte, E expression) { + return {true, {std::move(cte)}, std::move(expression)}; + } + + /** + * With-clause for a single potentially recursive CTE. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with_recursive(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies = true> + internal::with_t, CTE> with_recursive(CTE cte, Compound sel) { + return {true, {std::move(cte)}, sqlite_orm::select(std::move(sel))}; + } +#endif + /** * `SELECT * FROM T` expression that fetches results as tuples. * T is a type mapped to a storage, or an alias of it. @@ -415,6 +767,19 @@ namespace sqlite_orm { return {definedOrder}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Example: + * constexpr orm_table_alias auto m = "m"_alias.for_(); + * auto reportingTo = + * storage.select(asterisk(), inner_join(on(m->*&Employee::reportsTo == &Employee::employeeId))); + */ + template + auto asterisk(bool definedOrder = false) { + return asterisk>(definedOrder); + } +#endif + /** * `SELECT * FROM T` expression that fetches results as objects of type T. * T is a type mapped to a storage, or an alias of it. @@ -430,4 +795,11 @@ namespace sqlite_orm { internal::object_t object(bool definedOrder = false) { return {definedOrder}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto object(bool definedOrder = false) { + return object>(definedOrder); + } +#endif } diff --git a/dev/serializer_context.h b/dev/serializer_context.h index 8536f3d9f..dfa6d3ad0 100644 --- a/dev/serializer_context.h +++ b/dev/serializer_context.h @@ -8,6 +8,7 @@ namespace sqlite_orm { bool replace_bindable_with_question = false; bool skip_table_name = true; bool use_parentheses = true; + bool fts5_columns = false; }; template @@ -32,7 +33,6 @@ namespace sqlite_orm { const storage_type& storage; }; - } } diff --git a/dev/serializing_util.h b/dev/serializing_util.h index b1e5d1948..abfcfe0d0 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -1,403 +1,430 @@ -#pragma once - -#include // std::index_sequence -#include -#include -#include -#include -#include // std::exchange, std::tuple_size - -#include "functional/cxx_universal.h" // ::size_t -#include "functional/cxx_type_traits_polyfill.h" -#include "tuple_helper/tuple_iteration.h" -#include "error_code.h" -#include "serializer_context.h" -#include "serialize_result_type.h" -#include "util.h" - -namespace sqlite_orm { - namespace internal { - template - struct order_by_t; - - template - std::string serialize(const T&, const serializer_context&); - - template - std::string serialize_order_by(const T&, const Ctx&); - - inline void stream_sql_escaped(std::ostream& os, serialize_arg_type str, char char2Escape) { - for(size_t offset = 0, next; true; offset = next + 1) { - next = str.find(char2Escape, offset); - - if(next == str.npos) { - os.write(str.data() + offset, str.size() - offset); - break; - } - - os.write(str.data() + offset, next - offset + 1); - os.write(&char2Escape, 1); - } - } - - inline void stream_identifier(std::ostream& ss, - serialize_arg_type qualifier, - serialize_arg_type identifier, - serialize_arg_type alias) { - constexpr char quoteChar = '"'; - constexpr char qualified[] = {quoteChar, '.', '\0'}; - constexpr char aliased[] = {' ', quoteChar, '\0'}; - - // note: In practice, escaping double quotes in identifiers is arguably overkill, - // but since the SQLite grammar allows it, it's better to be safe than sorry. - - if(!qualifier.empty()) { - ss << quoteChar; - stream_sql_escaped(ss, qualifier, quoteChar); - ss << qualified; - } - { - ss << quoteChar; - stream_sql_escaped(ss, identifier, quoteChar); - ss << quoteChar; - } - if(!alias.empty()) { - ss << aliased; - stream_sql_escaped(ss, alias, quoteChar); - ss << quoteChar; - } - } - - inline void stream_identifier(std::ostream& ss, const std::string& identifier, const std::string& alias) { - return stream_identifier(ss, "", identifier, alias); - } - - inline void stream_identifier(std::ostream& ss, const std::string& identifier) { - return stream_identifier(ss, "", identifier, ""); - } - - template - void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { - static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); - return stream_identifier(ss, std::get(tpl)...); - } - - template>, bool> = true> - void stream_identifier(std::ostream& ss, const Tpl& tpl) { - return stream_identifier(ss, tpl, std::make_index_sequence::value>{}); - } - - enum class stream_as { - conditions_tuple, - actions_tuple, - expressions_tuple, - dynamic_expressions, - serialized, - identifier, - identifiers, - values_placeholders, - table_columns, - non_generated_columns, - field_values_excluding, - mapped_columns_expressions, - column_constraints, - }; - - template - struct streaming { - template - auto operator()(const Ts&... ts) const { - return std::forward_as_tuple(*this, ts...); - } - - template - constexpr std::index_sequence<1u + Idx...> offset_index(std::index_sequence) const { - return {}; - } - }; - constexpr streaming streaming_conditions_tuple{}; - constexpr streaming streaming_actions_tuple{}; - constexpr streaming streaming_expressions_tuple{}; - constexpr streaming streaming_dynamic_expressions{}; - constexpr streaming streaming_serialized{}; - constexpr streaming streaming_identifier{}; - constexpr streaming streaming_identifiers{}; - constexpr streaming streaming_values_placeholders{}; - constexpr streaming streaming_table_column_names{}; - constexpr streaming streaming_non_generated_column_names{}; - constexpr streaming streaming_field_values_excluding{}; - constexpr streaming streaming_mapped_columns_expressions{}; - constexpr streaming streaming_column_constraints{}; - - // serialize and stream a tuple of condition expressions; - // space + space-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, T, Ctx> tpl) { - const auto& conditions = get<1>(tpl); - auto& context = get<2>(tpl); - - iterate_tuple(conditions, [&ss, &context](auto& c) { - ss << " " << serialize(c, context); - }); - return ss; - } - - // serialize and stream a tuple of action expressions; - // space-separated - template - std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { - const auto& actions = get<1>(tpl); - auto& context = get<2>(tpl); - - iterate_tuple(actions, [&ss, &context, first = true](auto& action) mutable { - constexpr std::array sep = {" ", ""}; - ss << sep[std::exchange(first, false)] << serialize(action, context); - }); - return ss; - } - - // serialize and stream a tuple of expressions; - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, T, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); - - iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)] << serialize(arg, context); - }); - return ss; - } - - // serialize and stream multi_order_by arguments; - // comma-separated - template - std::ostream& operator<<( - std::ostream& ss, - std::tuple&, const std::tuple...>&, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); - - iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)] << serialize_order_by(arg, context); - }); - return ss; - } - - // serialize and stream a vector of expressions; - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, C, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); - - constexpr std::array sep = {", ", ""}; - for(size_t i = 0, first = true; i < args.size(); ++i) { - ss << sep[std::exchange(first, false)] << serialize(args[i], context); - } - return ss; - } - - // stream a vector of already serialized strings; - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { - const auto& strings = get<1>(tpl); - - constexpr std::array sep = {", ", ""}; - for(size_t i = 0, first = true; i < strings.size(); ++i) { - ss << sep[std::exchange(first, false)] << strings[i]; - } - return ss; - } - - // stream an identifier described by a variadic string pack, which is one of: - // 1. identifier - // 2. identifier, alias - // 3. qualifier, identifier, alias - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, Strings...> tpl) { - stream_identifier(ss, tpl, streaming_identifier.offset_index(std::index_sequence_for{})); - return ss; - } - - // stream a container of identifiers described by a string or a tuple, which is one of: - // 1. identifier - // 1. tuple(identifier) - // 2. tuple(identifier, alias), pair(identifier, alias) - // 3. tuple(qualifier, identifier, alias) - // - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { - const auto& identifiers = get<1>(tpl); - - constexpr std::array sep = {", ", ""}; - bool first = true; - for(auto& identifier: identifiers) { - ss << sep[std::exchange(first, false)]; - stream_identifier(ss, identifier); - } - return ss; - } - - // stream placeholders as part of a values clause - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, Ts...> tpl) { - const size_t& columnsCount = get<1>(tpl); - const ptrdiff_t& valuesCount = get<2>(tpl); - - if(!valuesCount || !columnsCount) { - return ss; - } - - std::string result; - result.reserve((1 + (columnsCount * 1) + (columnsCount * 2 - 2) + 1) * valuesCount + (valuesCount * 2 - 2)); - - constexpr std::array sep = {", ", ""}; - for(ptrdiff_t i = 0, first = true; i < valuesCount; ++i) { - result += sep[std::exchange(first, false)]; - result += "("; - for(size_t i = 0, first = true; i < columnsCount; ++i) { - result += sep[std::exchange(first, false)]; - result += "?"; - } - result += ")"; - } - ss << result; - return ss; - } - - // stream a table's column identifiers, possibly qualified; - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, Table, const bool&> tpl) { - const auto& table = get<1>(tpl); - const bool& qualified = get<2>(tpl); - - table.for_each_column([&ss, &tableName = qualified ? table.name : std::string{}, first = true]( - const column_identifier& column) mutable { - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)]; - stream_identifier(ss, tableName, column.name, std::string{}); - }); - return ss; - } - - // stream a table's non-generated column identifiers, unqualified; - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, Table> tpl) { - const auto& table = get<1>(tpl); - - table.template for_each_column_excluding( - [&ss, first = true](const column_identifier& column) mutable { - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)]; - stream_identifier(ss, column.name); - }); - return ss; - } - - // stream a table's non-generated column identifiers, unqualified; - // comma-separated - template - std::ostream& - operator<<(std::ostream& ss, - std::tuple&, PredFnCls, L, Ctx, Obj> tpl) { - using check_if_excluded = polyfill::remove_cvref_t>; - auto& excluded = get<2>(tpl); - auto& context = get<3>(tpl); - auto& object = get<4>(tpl); - using object_type = polyfill::remove_cvref_t; - auto& table = pick_table(context.db_objects); - - table.template for_each_column_excluding(call_as_template_base( - [&ss, &excluded, &context, &object, first = true](auto& column) mutable { - if(excluded(column)) { - return; - } - - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)] - << serialize(polyfill::invoke(column.member_pointer, object), context); - })); - return ss; - } - - // stream a tuple of mapped columns (which are member pointers or column pointers); - // comma-separated - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, T, Ctx> tpl) { - const auto& columns = get<1>(tpl); - auto& context = get<2>(tpl); - - iterate_tuple(columns, [&ss, &context, first = true](auto& colRef) mutable { - const std::string* columnName = find_column_name(context.db_objects, colRef); - if(!columnName) { - throw std::system_error{orm_error_code::column_not_found}; - } - - constexpr std::array sep = {", ", ""}; - ss << sep[std::exchange(first, false)]; - stream_identifier(ss, *columnName); - }); - return ss; - } - - template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, - const column_constraints&, - const bool&, - Ctx> tpl) { - const auto& column = get<1>(tpl); - const bool& isNotNull = get<2>(tpl); - auto& context = get<3>(tpl); - - using constraints_type = constraints_type_t>; - constexpr size_t constraintsCount = std::tuple_size::value; - if(constraintsCount) { - std::vector constraintsStrings; - constraintsStrings.reserve(constraintsCount); - int primaryKeyIndex = -1; - int autoincrementIndex = -1; - int tupleIndex = 0; - iterate_tuple(column.constraints, - [&constraintsStrings, &primaryKeyIndex, &autoincrementIndex, &tupleIndex, &context]( - auto& constraint) { - using constraint_type = std::decay_t; - constraintsStrings.push_back(serialize(constraint, context)); - if(is_primary_key_v) { - primaryKeyIndex = tupleIndex; - } else if(is_autoincrement_v) { - autoincrementIndex = tupleIndex; - } - ++tupleIndex; - }); - if(primaryKeyIndex != -1 && autoincrementIndex != -1 && autoincrementIndex < primaryKeyIndex) { - iter_swap(constraintsStrings.begin() + primaryKeyIndex, - constraintsStrings.begin() + autoincrementIndex); - } - for(auto& str: constraintsStrings) { - ss << str << ' '; - } - } - if(isNotNull) { - ss << "NOT NULL "; - } - - return ss; - } - } -} +#pragma once + +#include // std::index_sequence +#include +#include +#include +#include +#include // std::exchange, std::tuple_size + +#include "functional/cxx_universal.h" // ::size_t +#include "functional/cxx_type_traits_polyfill.h" +#include "tuple_helper/tuple_iteration.h" +#include "error_code.h" +#include "serializer_context.h" +#include "serialize_result_type.h" +#include "util.h" + +namespace sqlite_orm { + namespace internal { + template + struct order_by_t; + + template + auto serialize(const T& t, const C& context); + + template + std::string serialize_order_by(const T&, const Ctx&); + + inline void stream_sql_escaped(std::ostream& os, serialize_arg_type str, char char2Escape) { + for(size_t offset = 0, next; true; offset = next + 1) { + next = str.find(char2Escape, offset); + + if(next == str.npos) SQLITE_ORM_CPP_LIKELY { + os.write(str.data() + offset, str.size() - offset); + break; + } + + os.write(str.data() + offset, next - offset + 1); + os.write(&char2Escape, 1); + } + } + + inline void stream_identifier(std::ostream& ss, + serialize_arg_type qualifier, + serialize_arg_type identifier, + serialize_arg_type alias) { + constexpr char quoteChar = '"'; + constexpr char qualified[] = {quoteChar, '.', '\0'}; + constexpr char aliased[] = {' ', quoteChar, '\0'}; + + // note: In practice, escaping double quotes in identifiers is arguably overkill, + // but since the SQLite grammar allows it, it's better to be safe than sorry. + + if(!qualifier.empty()) { + ss << quoteChar; + stream_sql_escaped(ss, qualifier, quoteChar); + ss << qualified; + } + { + ss << quoteChar; + stream_sql_escaped(ss, identifier, quoteChar); + ss << quoteChar; + } + if(!alias.empty()) { + ss << aliased; + stream_sql_escaped(ss, alias, quoteChar); + ss << quoteChar; + } + } + + inline void stream_identifier(std::ostream& ss, const std::string& identifier, const std::string& alias) { + return stream_identifier(ss, "", identifier, alias); + } + + inline void stream_identifier(std::ostream& ss, const std::string& identifier) { + return stream_identifier(ss, "", identifier, ""); + } + + template + void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { + static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); + return stream_identifier(ss, std::get(tpl)...); + } + + template>::value, bool> = true> + void stream_identifier(std::ostream& ss, const Tpl& tpl) { + return stream_identifier(ss, tpl, std::make_index_sequence::value>{}); + } + + enum class stream_as { + conditions_tuple, + actions_tuple, + expressions_tuple, + dynamic_expressions, + compound_expressions, + serialized, + identifier, + identifiers, + values_placeholders, + table_columns, + non_generated_columns, + field_values_excluding, + mapped_columns_expressions, + column_constraints, + constraints_tuple, + }; + + template + struct streaming { + template + auto operator()(const Ts&... ts) const { + return std::forward_as_tuple(*this, ts...); + } + + template + constexpr std::index_sequence<1u + Idx...> offset_index(std::index_sequence) const { + return {}; + } + }; + constexpr streaming streaming_conditions_tuple{}; + constexpr streaming streaming_actions_tuple{}; + constexpr streaming streaming_expressions_tuple{}; + constexpr streaming streaming_dynamic_expressions{}; + constexpr streaming streaming_compound_expressions{}; + constexpr streaming streaming_serialized{}; + constexpr streaming streaming_identifier{}; + constexpr streaming streaming_identifiers{}; + constexpr streaming streaming_values_placeholders{}; + constexpr streaming streaming_table_column_names{}; + constexpr streaming streaming_non_generated_column_names{}; + constexpr streaming streaming_field_values_excluding{}; + constexpr streaming streaming_mapped_columns_expressions{}; + constexpr streaming streaming_constraints_tuple{}; + constexpr streaming streaming_column_constraints{}; + + // serialize and stream a tuple of condition expressions; + // space + space-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, T, Ctx> tpl) { + const auto& conditions = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + iterate_tuple(conditions, [&ss, &context](auto& c) { + ss << " " << serialize(c, context); + }); + return ss; + } + + // serialize and stream a tuple of action expressions; + // space-separated + template + std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { + const auto& actions = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + iterate_tuple(actions, [&ss, &context, first = true](auto& action) mutable { + constexpr std::array sep = {" ", ""}; + ss << sep[std::exchange(first, false)] << serialize(action, context); + }); + return ss; + } + + // serialize and stream a tuple of expressions; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, T, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] << serialize(arg, context); + }); + return ss; + } + + // serialize and stream expressions of a compound statement; + // separated by compound operator + template + std::ostream& + operator<<(std::ostream& ss, + std::tuple&, T, const std::string&, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + const std::string& opString = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + iterate_tuple(args, [&ss, &opString, &context, first = true](auto& arg) mutable { + if(!std::exchange(first, false)) { + ss << ' ' << opString << ' '; + } + ss << serialize(arg, context); + }); + return ss; + } + + // serialize and stream multi_order_by arguments; + // comma-separated + template + std::ostream& operator<<( + std::ostream& ss, + std::tuple&, const std::tuple...>&, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] << serialize_order_by(arg, context); + }); + return ss; + } + + // serialize and stream a vector or any other STL container of expressions; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, C, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + constexpr std::array sep = {", ", ""}; + bool first = true; + for(auto& argument: args) { + ss << sep[std::exchange(first, false)] << serialize(argument, context); + } + return ss; + } + + // stream a vector of already serialized strings; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { + const auto& strings = std::get<1>(tpl); + + constexpr std::array sep = {", ", ""}; + for(size_t i = 0, first = true; i < strings.size(); ++i) { + ss << sep[std::exchange(first, false)] << strings[i]; + } + return ss; + } + + // stream an identifier described by a variadic string pack, which is one of: + // 1. identifier + // 2. identifier, alias + // 3. qualifier, identifier, alias + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, Strings...> tpl) { + stream_identifier(ss, tpl, streaming_identifier.offset_index(std::index_sequence_for{})); + return ss; + } + + // stream a container of identifiers described by a string or a tuple, which is one of: + // 1. identifier + // 1. tuple(identifier) + // 2. tuple(identifier, alias), pair(identifier, alias) + // 3. tuple(qualifier, identifier, alias) + // + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { + const auto& identifiers = std::get<1>(tpl); + + constexpr std::array sep = {", ", ""}; + bool first = true; + for(auto& identifier: identifiers) { + ss << sep[std::exchange(first, false)]; + stream_identifier(ss, identifier); + } + return ss; + } + + // stream placeholders as part of a values clause + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, Ts...> tpl) { + const size_t& columnsCount = std::get<1>(tpl); + const ptrdiff_t& valuesCount = std::get<2>(tpl); + + if(!valuesCount || !columnsCount) { + return ss; + } + + std::string result; + result.reserve((1 + (columnsCount * 1) + (columnsCount * 2 - 2) + 1) * valuesCount + (valuesCount * 2 - 2)); + + constexpr std::array sep = {", ", ""}; + for(ptrdiff_t i = 0, first = true; i < valuesCount; ++i) { + result += sep[std::exchange(first, false)]; + result += "("; + for(size_t i = 0, first = true; i < columnsCount; ++i) { + result += sep[std::exchange(first, false)]; + result += "?"; + } + result += ")"; + } + ss << result; + return ss; + } + + // stream a table's column identifiers, possibly qualified; + // comma-separated + template + std::ostream& + operator<<(std::ostream& ss, + std::tuple&, Table, const std::string&> tpl) { + const auto& table = std::get<1>(tpl); + const std::string& qualifier = std::get<2>(tpl); + + table.for_each_column([&ss, &qualifier, first = true](const column_identifier& column) mutable { + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)]; + stream_identifier(ss, qualifier, column.name, std::string{}); + }); + return ss; + } + + // stream a table's non-generated column identifiers, unqualified; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, Table> tpl) { + const auto& table = std::get<1>(tpl); + + table.template for_each_column_excluding( + [&ss, first = true](const column_identifier& column) mutable { + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)]; + stream_identifier(ss, column.name); + }); + return ss; + } + + // stream a table's non-generated column identifiers, unqualified; + // comma-separated + template + std::ostream& + operator<<(std::ostream& ss, + std::tuple&, PredFnCls, L, Ctx, Obj> tpl) { + using check_if_excluded = polyfill::remove_cvref_t>; + auto& excluded = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + auto& object = std::get<4>(tpl); + using object_type = polyfill::remove_cvref_t; + auto& table = pick_table(context.db_objects); + + table.template for_each_column_excluding(call_as_template_base( + [&ss, &excluded, &context, &object, first = true](auto& column) mutable { + if(excluded(column)) { + return; + } + + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] + << serialize(polyfill::invoke(column.member_pointer, object), context); + })); + return ss; + } + + // stream a tuple of mapped columns (which are member pointers or column pointers); + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, T, Ctx> tpl) { + const auto& columns = std::get<1>(tpl); + auto& context = std::get<2>(tpl); + + iterate_tuple(columns, [&ss, &context, first = true](auto& colRef) mutable { + const std::string* columnName = find_column_name(context.db_objects, colRef); + if(!columnName) { + throw std::system_error{orm_error_code::column_not_found}; + } + + constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)]; + stream_identifier(ss, *columnName); + }); + return ss; + } + + // serialize and stream a tuple of conditions or hints; + // space + space-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, T, Ctx> tpl) { + const auto& constraints = get<1>(tpl); + auto& context = get<2>(tpl); + + iterate_tuple(constraints, [&ss, &context](auto& constraint) mutable { + ss << ' ' << serialize(constraint, context); + }); + return ss; + } + + // serialize and stream a tuple of column constraints; + // space + space-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, + const column_constraints&, + const bool&, + Ctx> tpl) { + const auto& column = std::get<1>(tpl); + const bool& isNotNull = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + using constraints_tuple = decltype(column.constraints); + iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { + ss << ' ' << serialize(constraint, context); + }); + // add implicit null constraint + if(!context.fts5_columns) { + constexpr bool hasExplicitNullableConstraint = + mpl::invoke_t, check_if_has_type>, + constraints_tuple>::value; + if SQLITE_ORM_CONSTEXPR_IF(!hasExplicitNullableConstraint) { + if(isNotNull) { + ss << " NOT NULL"; + } else { + ss << " NULL"; + } + } + } + + return ss; + } + } +} diff --git a/dev/sqlite_schema_table.h b/dev/sqlite_schema_table.h new file mode 100644 index 000000000..9dfda257c --- /dev/null +++ b/dev/sqlite_schema_table.h @@ -0,0 +1,45 @@ +#pragma once + +#include // std::string + +#include "schema/column.h" +#include "schema/table.h" +#include "column_pointer.h" +#include "alias.h" + +namespace sqlite_orm { + /** + * SQLite's "schema table" that stores the schema for a database. + * + * @note Despite the fact that the schema table was renamed from "sqlite_master" to "sqlite_schema" in SQLite 3.33.0 + * the renaming process was more like keeping the previous name "sqlite_master" and attaching an internal alias "sqlite_schema". + * One can infer this fact from the following SQL statement: + * It qualifies the set of columns, but bails out with error "no such table: sqlite_schema": `SELECT sqlite_schema.* from sqlite_schema`. + * Hence we keep its previous table name `sqlite_master`, and provide `sqlite_schema` as a table alias in sqlite_orm. + */ + struct sqlite_master { + std::string type; + std::string name; + std::string tbl_name; + int rootpage = 0; + std::string sql; + +#ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + friend bool operator==(const sqlite_master&, const sqlite_master&) = default; +#endif + }; + + inline auto make_sqlite_schema_table() { + return make_table("sqlite_master", + make_column("type", &sqlite_master::type), + make_column("name", &sqlite_master::name), + make_column("tbl_name", &sqlite_master::tbl_name), + make_column("rootpage", &sqlite_master::rootpage), + make_column("sql", &sqlite_master::sql)); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + inline constexpr orm_table_reference auto sqlite_master_table = c(); + inline constexpr orm_table_alias auto sqlite_schema = "sqlite_schema"_alias.for_(); +#endif +} diff --git a/dev/statement_binder.h b/dev/statement_binder.h index b16fe3c87..0473f7943 100644 --- a/dev/statement_binder.h +++ b/dev/statement_binder.h @@ -10,12 +10,17 @@ #ifndef SQLITE_ORM_STRING_VIEW_SUPPORTED #include // ::wcsncpy, ::wcslen #endif +#ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert +#include // std::codecvt_utf8_utf16 +#endif #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" #include "functional/cxx_functional_polyfill.h" #include "is_std_ptr.h" #include "tuple_helper/tuple_filter.h" +#include "type_traits.h" #include "error_code.h" #include "arithmetic_tag.h" #include "xdestroy_handling.h" @@ -30,22 +35,27 @@ namespace sqlite_orm { struct statement_binder; namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. + */ + template + struct indirectly_test_bindable; template SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v())>> = true - // note : msvc 14.0 needs the parentheses constructor, otherwise `is_bindable` isn't recognised. - // The strangest thing is that this is mutually exclusive with `is_printable_v`. - ; + SQLITE_ORM_INLINE_VAR constexpr bool + is_bindable_v{})>>> = true; template - using is_bindable = polyfill::bool_constant>; - + struct is_bindable : polyfill::bool_constant> {}; } +#if SQLITE_VERSION_NUMBER >= 3020000 /** - * Specialization for 'pointer-passing interface'. + * Specialization for pointer bindings (part of the 'pointer-passing interface'). */ template struct statement_binder, void> { @@ -64,12 +74,13 @@ namespace sqlite_orm { sqlite3_result_pointer(context, (void*)value.take_ptr(), T::value, value.get_xdestroy()); } }; +#endif /** * Specialization for arithmetic types. */ template - struct statement_binder::value>> { + struct statement_binder> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { return this->bind(stmt, index, value, tag()); @@ -112,13 +123,13 @@ namespace sqlite_orm { */ template struct statement_binder, - std::is_same + std::enable_if_t, + std::is_same #ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED - , - std::is_same + , + std::is_same #endif - >>> { + >::value>> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { auto stringData = this->string_data(value); @@ -152,13 +163,13 @@ namespace sqlite_orm { #ifndef SQLITE_ORM_OMITS_CODECVT template struct statement_binder, - std::is_same + std::enable_if_t, + std::is_same #ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED - , - std::is_same + , + std::is_same #endif - >>> { + >::value>> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { auto stringData = this->string_data(value); @@ -222,7 +233,8 @@ namespace sqlite_orm { template struct statement_binder< V, - std::enable_if_t::value && internal::is_bindable_v>>> { + std::enable_if_t::value && + internal::is_bindable>::value>> { using unqualified_type = std::remove_cv_t; int bind(sqlite3_stmt* stmt, int index, const V& value) const { @@ -328,14 +340,11 @@ namespace sqlite_orm { (this->bind(polyfill::invoke(project, std::get(tpl)), Idx), ...); } #else - template - void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { - this->bind(polyfill::invoke(project, std::get(tpl)), I); - (*this)(tpl, std::index_sequence{}, std::forward(project)); + template + void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->bind(polyfill::invoke(project, std::get(tpl)), Idx), 0)...}; } - - template - void operator()(const Tpl&, std::index_sequence<>, Projection) const {} #endif template diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index c8c96e4c9..0e7249257 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -4,12 +4,13 @@ #include // std::string #include // std::enable_if, std::remove_pointer #include // std::vector -#include // std::iter_swap #ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert #include // std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT +#endif #include #include +#include // std::list #include "functional/cxx_string_view.h" #include "functional/cxx_optional.h" @@ -21,10 +22,13 @@ #include "ast/excluded.h" #include "ast/group_by.h" #include "ast/into.h" +#include "ast/match.h" +#include "ast/rank.h" +#include "ast/special_keywords.h" #include "core_functions.h" #include "constraints.h" #include "conditions.h" -#include "column.h" +#include "schema/column.h" #include "indexed_column.h" #include "function.h" #include "prepared_statement.h" @@ -35,14 +39,18 @@ #include "literal.h" #include "table_name_collector.h" #include "column_names_getter.h" +#include "cte_column_names_collector.h" #include "order_by_serializer.h" #include "serializing_util.h" +#include "serialize_result_type.h" #include "statement_binder.h" #include "values.h" -#include "triggers.h" +#include "schema/triggers.h" #include "table_type_of.h" -#include "index.h" +#include "schema/index.h" +#include "schema/table.h" #include "util.h" +#include "error_code.h" namespace sqlite_orm { @@ -51,8 +59,8 @@ namespace sqlite_orm { template struct statement_serializer; - template - std::string serialize(const T& t, const serializer_context& context) { + template + auto serialize(const T& t, const C& context) { statement_serializer serializer; return serializer(t, context); } @@ -75,7 +83,7 @@ namespace sqlite_orm { private: template && !std::is_base_of::value + std::enable_if_t::value && !std::is_base_of::value #ifndef SQLITE_ORM_OMITS_CODECVT && !std::is_base_of::value #endif @@ -125,11 +133,80 @@ namespace sqlite_orm { return quote_blob_literal(field_printer>{}(t)); } +#if SQLITE_VERSION_NUMBER >= 3020000 template std::string do_serialize(const pointer_binding&) const { // always serialize null (security reasons) return field_printer{}(nullptr); } +#endif + }; + + template + struct statement_serializer, void> { + using statement_type = table_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) { + return this->serialize(statement, context, statement.name); + } + + template + auto serialize(const statement_type& statement, const Ctx& context, const std::string& tableName) { + std::stringstream ss; + ss << "CREATE TABLE " << streaming_identifier(tableName) << " (" + << streaming_expressions_tuple(statement.elements, context) << ")"; + if(statement_type::is_without_rowid_v) { + ss << " WITHOUT ROWID"; + } + return ss.str(); + } + }; + + template<> + struct statement_serializer { + using statement_type = current_time_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_TIME"; + } + }; + + template<> + struct statement_serializer { + using statement_type = current_date_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_DATE"; + } + }; + + template<> + struct statement_serializer { + using statement_type = current_timestamp_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_TIMESTAMP"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = highlight_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) { + std::stringstream ss; + auto& tableName = lookup_table_name(context.db_objects); + ss << "HIGHLIGHT (" << streaming_identifier(tableName); + ss << ", " << serialize(statement.argument0, context); + ss << ", " << serialize(statement.argument1, context); + ss << ", " << serialize(statement.argument2, context) << ")"; + return ss.str(); + } }; /** @@ -140,12 +217,12 @@ namespace sqlite_orm { using statement_type = T; template - std::string operator()(const T& literal, const Ctx& context) const { - static_assert(is_bindable_v>, "A literal value must be also bindable"); + std::string operator()(const statement_type& literal, const Ctx& context) const { + static_assert(is_bindable_v>, "A literal value must be also bindable"); Ctx literalCtx = context; literalCtx.replace_bindable_with_question = false; - statement_serializer> serializer{}; + statement_serializer> serializer{}; return serializer(literal.value, literalCtx); } }; @@ -212,6 +289,19 @@ namespace sqlite_orm { } }; + template + struct statement_serializer, void> { + using statement_type = match_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + auto& table = pick_table(context.db_objects); + std::stringstream ss; + ss << streaming_identifier(table.name) << " MATCH " << serialize(statement.argument, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = column_alias; @@ -263,13 +353,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } ss << statement.serialize() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; - if(context.use_parentheses) { - ss << ')'; - } return ss.str(); } }; @@ -278,14 +362,15 @@ namespace sqlite_orm { struct statement_serializer, void> : statement_serializer, void> {}; - template - struct statement_serializer, void> { - using statement_type = function_call; + template + struct statement_serializer, void> { + using statement_type = function_call; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; + stream_identifier(ss, "", statement.name(), ""); + ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; @@ -322,7 +407,7 @@ namespace sqlite_orm { template struct statement_serializer< E, - std::enable_if_t, is_column_pointer>>> { + std::enable_if_t, is_column_pointer>::value>> { using statement_type = E; template @@ -340,13 +425,23 @@ namespace sqlite_orm { } }; + template<> + struct statement_serializer { + using statement_type = rank_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx&) const { + return "rank"; + } + }; + template<> struct statement_serializer { using statement_type = rowid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -355,8 +450,8 @@ namespace sqlite_orm { using statement_type = oid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -365,8 +460,8 @@ namespace sqlite_orm { using statement_type = _rowid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -375,12 +470,12 @@ namespace sqlite_orm { using statement_type = table_rowid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; @@ -390,12 +485,12 @@ namespace sqlite_orm { using statement_type = table_oid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; @@ -405,32 +500,27 @@ namespace sqlite_orm { using statement_type = table__rowid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; - template - struct statement_serializer, void> { - using statement_type = binary_operator; + template + struct statement_serializer, void> { + using statement_type = is_equal_with_table_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { - auto lhs = serialize(statement.lhs, context); - auto rhs = serialize(statement.rhs, context); std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } - ss << lhs << " " << statement.serialize() << " " << rhs; - if(context.use_parentheses) { - ss << ')'; - } + const auto tableName = lookup_table_name(context.db_objects); + ss << streaming_identifier(tableName); + ss << " = "; + ss << serialize(statement.rhs, context); return ss.str(); } }; @@ -497,6 +587,77 @@ namespace sqlite_orm { } }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#if SQLITE_VERSION_NUMBER >= 3035003 +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template<> + struct statement_serializer { + using statement_type = materialized_t; + + template + std::string_view operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "MATERIALIZED"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_materialized_t; + + template + std::string_view operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NOT MATERIALIZED"; + } + }; +#endif +#endif + + template + struct statement_serializer> { + using statement_type = CTE; + + template + std::string operator()(const statement_type& cte, const Ctx& context) const { + // A CTE always starts a new 'highest level' context + Ctx cteContext = context; + cteContext.use_parentheses = false; + + std::stringstream ss; + ss << streaming_identifier(alias_extractor>::extract()); + { + std::vector columnNames = + collect_cte_column_names(get_cte_driving_subselect(cte.subselect), + cte.explicitColumns, + context); + ss << '(' << streaming_identifiers(columnNames) << ')'; + } + ss << " AS" << streaming_constraints_tuple(cte.hints, context) << " (" + << serialize(cte.subselect, cteContext) << ')'; + return ss.str(); + } + }; + + template + struct statement_serializer> { + using statement_type = With; + + template + std::string operator()(const statement_type& c, const Ctx& context) const { + Ctx tupleContext = context; + tupleContext.use_parentheses = false; + + std::stringstream ss; + ss << "WITH"; + if(c.recursiveIndicated) { + ss << " RECURSIVE"; + } + ss << " " << serialize(c.cte, tupleContext); + ss << " " << serialize(c.expression, context); + return ss.str(); + } + }; +#endif + template struct statement_serializer> { using statement_type = T; @@ -504,9 +665,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& c, const Ctx& context) const { std::stringstream ss; - ss << serialize(c.left, context) << " "; - ss << static_cast(c) << " "; - ss << serialize(c.right, context); + ss << streaming_compound_expressions(c.compound, static_cast(c), context); return ss.str(); } }; @@ -587,19 +746,36 @@ namespace sqlite_orm { }; template - struct statement_serializer> { + struct statement_serializer< + T, + std::enable_if_t, is_binary_operator>::value>> { using statement_type = T; template - std::string operator()(const statement_type& c, const Ctx& context) const { - auto leftString = serialize(c.l, context); - auto rightString = serialize(c.r, context); + std::string operator()(const statement_type& statement, 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 parenthesizeLeft = is_binary_condition>::value || + is_binary_operator>::value; + constexpr bool parenthesizeRight = is_binary_condition>::value || + is_binary_operator>::value; + std::stringstream ss; - if(context.use_parentheses) { + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeLeft) { ss << "("; } - ss << leftString << " " << static_cast(c) << " " << rightString; - if(context.use_parentheses) { + ss << serialize(statement.lhs, subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeLeft) { + ss << ")"; + } + ss << " " << statement.serialize() << " "; + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeRight) { + ss << "("; + } + ss << serialize(statement.rhs, subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeRight) { ss << ")"; } return ss.str(); @@ -632,9 +808,12 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = dynamic_in_t; + template + struct statement_serializer< + dynamic_in_t, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = dynamic_in_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { @@ -647,22 +826,25 @@ namespace sqlite_orm { ss << "NOT IN"; } ss << " "; - if(is_compound_operator_v) { + if(is_compound_operator::value) { ss << '('; } auto newContext = context; newContext.use_parentheses = true; ss << serialize(statement.argument, newContext); - if(is_compound_operator_v) { + if(is_compound_operator::value) { ss << ')'; } return ss.str(); } }; - template - struct statement_serializer>, void> { - using statement_type = dynamic_in_t>; + template + struct statement_serializer< + dynamic_in_t, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = dynamic_in_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { @@ -696,7 +878,7 @@ namespace sqlite_orm { ss << " "; using args_type = std::tuple; constexpr bool theOnlySelect = - std::tuple_size::value == 1 && is_select_v>; + std::tuple_size::value == 1 && is_select>::value; if(!theOnlySelect) { ss << "("; } @@ -769,22 +951,12 @@ namespace sqlite_orm { } }; - template<> - struct statement_serializer { - using statement_type = autoincrement_t; - - template - std::string operator()(const statement_type&, const Ctx&) const { - return "AUTOINCREMENT"; - } - }; - template<> struct statement_serializer { using statement_type = conflict_clause_t; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case conflict_clause_t::rollback: return "ROLLBACK"; @@ -801,13 +973,33 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = primary_key_with_autoincrement; + template + struct statement_serializer, void> { + using statement_type = primary_key_with_autoincrement; template std::string operator()(const statement_type& statement, const Ctx& context) const { - return serialize(statement.primary_key, context) + " AUTOINCREMENT"; + return serialize(statement.as_base(), context) + " AUTOINCREMENT"; + } + }; + + template<> + struct statement_serializer { + using statement_type = null_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NULL"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_null_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NOT NULL"; } }; @@ -858,13 +1050,77 @@ namespace sqlite_orm { } }; +#if SQLITE_VERSION_NUMBER >= 3009000 + template<> + struct statement_serializer { + using statement_type = unindexed_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "UNINDEXED"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = prefix_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "prefix=" << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = tokenize_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "tokenize = " << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = content_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "content=" << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = table_content_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& context) const { + using mapped_type = typename statement_type::mapped_type; + + auto& table = pick_table(context.db_objects); + + std::stringstream ss; + ss << "content=" << streaming_identifier(table.name); + return ss.str(); + } + }; +#endif + template<> struct statement_serializer { using statement_type = collate_constraint_t; template - std::string operator()(const statement_type& c, const Ctx&) const { - return static_cast(c); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -873,11 +1129,12 @@ namespace sqlite_orm { using statement_type = default_t; template - std::string operator()(const statement_type& c, const Ctx& context) const { - return static_cast(c) + " (" + serialize(c.value, context) + ")"; + std::string operator()(const statement_type& statement, const Ctx& context) const { + return static_cast(statement) + " (" + serialize(statement.value, context) + ")"; } }; +#if SQLITE_VERSION_NUMBER >= 3006019 template struct statement_serializer, std::tuple>, void> { using statement_type = foreign_key_t, std::tuple>; @@ -903,6 +1160,7 @@ namespace sqlite_orm { return ss.str(); } }; +#endif template struct statement_serializer, void> { @@ -949,15 +1207,15 @@ namespace sqlite_orm { template std::string operator()(const statement_type& column, const Ctx& context) const { - using column_type = statement_type; - std::stringstream ss; - ss << streaming_identifier(column.name) << " " << type_printer>().print() - << " " - << streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - column.is_not_null(), - context); + ss << streaming_identifier(column.name); + if(!context.fts5_columns) { + ss << " " << type_printer>>().print(); + } + ss << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + column.is_not_null(), + context); return ss.str(); } }; @@ -983,15 +1241,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")" << " VALUES (" << streaming_field_values_excluding(check_if{}, - empty_callable(), // don't exclude + empty_callable, // don't exclude context, get_ref(statement.object)) << ")"; @@ -1007,8 +1264,7 @@ namespace sqlite_orm { std::string operator()(const statement_type& ins, const Ctx& context) const { constexpr size_t colsCount = std::tuple_size>::value; static_assert(colsCount > 0, "Use insert or replace with 1 argument instead"); - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "INSERT INTO " << streaming_identifier(table.name) << " "; @@ -1035,15 +1291,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "UPDATE " << streaming_identifier(table.name) << " SET "; table.template for_each_column_excluding>( [&table, &ss, &context, &object = get_ref(statement.object), first = true](auto& column) mutable { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -1054,7 +1309,7 @@ namespace sqlite_orm { ss << " WHERE "; table.for_each_column( [&table, &context, &ss, &object = get_ref(statement.object), first = true](auto& column) mutable { - if(!column.template is() && !table.exists_in_composite_primary_key(column)) { + if(!column.template is() && !exists_in_composite_primary_key(table, column)) { return; } @@ -1126,11 +1381,6 @@ namespace sqlite_orm { return std::move(collector.table_names); } - template = true> - std::set> collect_table_names(const T& table, const Ctx&) { - return {{table.name, ""}}; - } - template struct statement_serializer, void> { using statement_type = update_all_t; @@ -1156,7 +1406,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); using is_without_rowid = typename std::decay_t::is_without_rowid; @@ -1165,7 +1415,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -1187,7 +1437,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>{}, [&table](auto& column) { - return table.exists_in_composite_primary_key(column); + return exists_in_composite_primary_key(table, column); }, context, get_ref(statement.object)) @@ -1212,18 +1462,21 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = columns_t; + template + struct statement_serializer, is_struct>::value>> { + using statement_type = C; template std::string operator()(const statement_type& statement, const Ctx& context) const { + // subqueries should always use parentheses in column names + auto subCtx = context; + subCtx.use_parentheses = true; + std::stringstream ss; if(context.use_parentheses) { ss << '('; } - // note: pass `statement` itself - ss << streaming_serialized(get_column_names(statement, context)); + ss << streaming_serialized(get_column_names(statement, subCtx)); if(context.use_parentheses) { ss << ')'; } @@ -1232,13 +1485,15 @@ namespace sqlite_orm { }; template - struct statement_serializer, is_replace_raw>>> { + struct statement_serializer< + T, + std::enable_if_t, is_replace_raw>::value>> { using statement_type = T; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - if(is_insert_raw_v) { + if(is_insert_raw::value) { ss << "INSERT"; } else { ss << "REPLACE"; @@ -1246,12 +1501,12 @@ namespace sqlite_orm { iterate_tuple(statement.args, [&context, &ss](auto& value) { using value_type = std::decay_t; ss << ' '; - if(is_columns_v) { + if(is_columns::value) { auto newContext = context; newContext.skip_table_name = true; newContext.use_parentheses = true; ss << serialize(value, newContext); - } else if(is_values_v || is_select_v) { + } else if(is_values::value || is_select::value) { auto newContext = context; newContext.use_parentheses = false; ss << serialize(value, newContext); @@ -1298,15 +1553,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& rep, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")"; const auto valuesCount = std::distance(rep.range.first, rep.range.second); - const auto columnsCount = table.non_generated_columns_count(); + const auto columnsCount = table.template count_of_columns_excluding(); ss << " VALUES " << streaming_values_placeholders(columnsCount, valuesCount); return ss.str(); } @@ -1318,7 +1572,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); using is_without_rowid = typename std::decay_t::is_without_rowid; @@ -1327,7 +1581,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -1354,15 +1608,16 @@ namespace sqlite_orm { }; template - std::string serialize_get_all_impl(const T& get, const Ctx& context) { - using primary_type = type_t; + std::string serialize_get_all_impl(const T& getAll, const Ctx& context) { + using table_type = type_t; + using mapped_type = mapped_type_proxy_t; - auto& table = pick_table(context.db_objects); - auto tableNames = collect_table_names(table, context); + auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, true) << " FROM " - << streaming_identifiers(tableNames) << streaming_conditions_tuple(get.conditions, context); + ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)) + << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()) + << streaming_conditions_tuple(getAll.conditions, context); return ss.str(); } @@ -1403,7 +1658,7 @@ namespace sqlite_orm { using primary_type = type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, false) << " FROM " + ss << "SELECT " << streaming_table_column_names(table, std::string{}) << " FROM " << streaming_identifier(table.name) << " WHERE "; auto primaryKeyColumnNames = table.primary_key_column_names(); @@ -1445,7 +1700,7 @@ namespace sqlite_orm { using statement_type = conflict_action; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case conflict_action::replace: return "REPLACE"; @@ -1468,7 +1723,10 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - return "OR " + serialize(statement.action, context); + std::stringstream ss; + + ss << "OR " << serialize(statement.action, context); + return ss.str(); } }; @@ -1490,9 +1748,12 @@ namespace sqlite_orm { template std::string operator()(const statement_type& sel, Ctx context) const { context.skip_table_name = false; + // subqueries should always use parentheses in column names + auto subCtx = context; + subCtx.use_parentheses = true; std::stringstream ss; - if(!is_compound_operator_v) { + if(!is_compound_operator::value) { if(!sel.highest_level && context.use_parentheses) { ss << "("; } @@ -1501,9 +1762,9 @@ namespace sqlite_orm { if(get_distinct(sel.col)) { ss << static_cast(distinct(0)) << " "; } - ss << streaming_serialized(get_column_names(sel.col, context)); + ss << streaming_serialized(get_column_names(sel.col, subCtx)); using conditions_tuple = typename statement_type::conditions_type; - constexpr bool hasExplicitFrom = tuple_has::value; + constexpr bool hasExplicitFrom = tuple_has::value; if(!hasExplicitFrom) { auto tableNames = collect_table_names(sel, context); using joins_index_sequence = filter_tuple_sequence_t; @@ -1516,12 +1777,12 @@ namespace sqlite_orm { alias_extractor::as_alias()}; tableNames.erase(tableNameWithAlias); }); - if(!tableNames.empty() && !is_compound_operator_v) { + if(!tableNames.empty() && !is_compound_operator::value) { ss << " FROM " << streaming_identifiers(tableNames); } } ss << streaming_conditions_tuple(sel.conditions, context); - if(!is_compound_operator_v) { + if(!is_compound_operator::value) { if(!sel.highest_level && context.use_parentheses) { ss << ")"; } @@ -1557,6 +1818,37 @@ namespace sqlite_orm { } }; +#if SQLITE_VERSION_NUMBER >= 3009000 + template + struct statement_serializer, void> { + using statement_type = using_fts5_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "USING FTS5("; + auto subContext = context; + subContext.fts5_columns = true; + ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; + return ss.str(); + } + }; +#endif + + template + struct statement_serializer, void> { + using statement_type = virtual_table_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "CREATE VIRTUAL TABLE IF NOT EXISTS "; + ss << streaming_identifier(statement.name) << ' '; + ss << serialize(statement.module_details, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = index_t; @@ -1575,7 +1867,7 @@ namespace sqlite_orm { std::string whereString; iterate_tuple(statement.elements, [&columnNames, &context, &whereString](auto& value) { using value_type = std::decay_t; - if(!is_where_v) { + if(!is_where::value) { auto newContext = context; newContext.use_parentheses = false; auto whereString = serialize(value, newContext); @@ -1593,23 +1885,21 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = from_t; + template + struct statement_serializer> { + using statement_type = From; template std::string operator()(const statement_type&, const Ctx& context) const { - using tuple = std::tuple; - std::stringstream ss; ss << "FROM "; - iterate_tuple([&context, &ss, first = true](auto* item) mutable { - using from_type = std::remove_pointer_t; + iterate_tuple([&context, &ss, first = true](auto* dummyItem) mutable { + using table_type = std::remove_pointer_t; constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] - << streaming_identifier(lookup_table_name>(context.db_objects), - alias_extractor::as_alias()); + << streaming_identifier(lookup_table_name>(context.db_objects), + alias_extractor::as_alias()); }); return ss.str(); } @@ -1673,7 +1963,7 @@ namespace sqlite_orm { using statement_type = trigger_timing; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case trigger_timing::trigger_before: return "BEFORE"; @@ -1691,7 +1981,7 @@ namespace sqlite_orm { using statement_type = trigger_type; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case trigger_type::trigger_delete: return "DELETE"; @@ -1765,7 +2055,7 @@ namespace sqlite_orm { ss << " BEGIN "; iterate_tuple(statement.elements, [&ss, &context](auto& element) { using element_type = std::decay_t; - if(is_select_v) { + if(is_select::value) { auto newContext = context; newContext.use_parentheses = false; ss << serialize(element, newContext); @@ -1832,8 +2122,8 @@ namespace sqlite_orm { template struct statement_serializer< Join, - std::enable_if_t, - polyfill::is_specialization_of>>> { + std::enable_if_t, + polyfill::is_specialization_of>::value>> { using statement_type = Join; template @@ -1903,20 +2193,6 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = having_t; - - template - std::string operator()(const statement_type& statement, const Ctx& context) const { - std::stringstream ss; - auto newContext = context; - newContext.skip_table_name = false; - ss << "HAVING " << serialize(statement.expression, newContext); - return ss.str(); - } - }; - /** * HO - has offset * OI - offset is implicit @@ -1956,7 +2232,7 @@ namespace sqlite_orm { using statement_type = default_values_t; template - std::string operator()(const statement_type&, const Ctx&) const { + serialize_result_type operator()(const statement_type&, const Ctx&) const { return "DEFAULT VALUES"; } }; diff --git a/dev/storage.h b/dev/storage.h index fda117d5b..a7bf160df 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -20,10 +20,10 @@ #include "functional/mpl.h" #include "tuple_helper/tuple_traits.h" #include "tuple_helper/tuple_filter.h" +#include "tuple_helper/tuple_transformer.h" #include "tuple_helper/tuple_iteration.h" #include "type_traits.h" #include "alias.h" -#include "row_extractor_builder.h" #include "error_code.h" #include "type_printer.h" #include "constraints.h" @@ -40,30 +40,43 @@ #include "table_info.h" #include "storage_impl.h" #include "journal_mode.h" -#include "view.h" +#include "mapped_view.h" +#include "result_set_view.h" #include "ast_iterator.h" #include "storage_base.h" #include "prepared_statement.h" #include "expression_object_type.h" #include "statement_serializer.h" -#include "triggers.h" +#include "serializer_context.h" +#include "schema/triggers.h" #include "object_from_column_builder.h" -#include "table.h" -#include "column.h" -#include "index.h" +#include "row_extractor.h" +#include "schema/table.h" +#include "schema/column.h" +#include "schema/index.h" +#include "cte_storage.h" #include "util.h" #include "serializing_util.h" namespace sqlite_orm { namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. + */ + template + struct indirectly_test_preparable; template SQLITE_ORM_INLINE_VAR constexpr bool is_preparable_v = false; - template - SQLITE_ORM_INLINE_VAR constexpr bool - is_preparable_v().prepare(std::declval()))>> = true; + SQLITE_ORM_INLINE_VAR constexpr bool is_preparable_v< + S, + E, + polyfill::void_t().prepare(std::declval()))>>> = + true; /** * Storage class itself. Create an instanse to use it as an interfacto to sqlite db by calling `make_storage` @@ -81,6 +94,8 @@ namespace sqlite_orm { storage_t(std::string filename, db_objects_type dbObjects) : storage_base{std::move(filename), foreign_keys_count(dbObjects)}, db_objects{std::move(dbObjects)} {} + storage_t(const storage_t&) = default; + private: db_objects_type db_objects; @@ -105,21 +120,16 @@ namespace sqlite_orm { using table_type = std::decay_t; using context_t = serializer_context; - std::stringstream ss; context_t context{this->db_objects}; - ss << "CREATE TABLE " << streaming_identifier(tableName) << " ( " - << streaming_expressions_tuple(table.elements, context) << ")"; - if(table_type::is_without_rowid_v) { - ss << " WITHOUT ROWID"; - } - ss.flush(); - perform_void_exec(db, ss.str()); + statement_serializer serializer; + std::string sql = serializer.serialize(table, context, tableName); + perform_void_exec(db, std::move(sql)); } /** - * Copies sourceTableName to another table with name: destinationTableName - * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% - */ + * Copies sourceTableName to another table with name: destinationTableName + * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% + */ template void copy_table(sqlite3* db, const std::string& sourceTableName, @@ -173,19 +183,39 @@ namespace sqlite_orm { template void assert_mapped_type() const { - using mapped_types_tuple = std::tuple; - static_assert(mpl::invoke_t, mapped_types_tuple>::value, - "type is not mapped to a storage"); + static_assert(tuple_has_type::value, + "type is not mapped to storage"); + } + + template + void assert_updatable_type() const { +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + using Table = storage_pick_table_t; + using elements_type = elements_type_t; + using col_index_sequence = filter_tuple_sequence_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + constexpr size_t dedicatedPrimaryKeyColumnsCount = + nested_tuple_size_for_t::value; + + constexpr size_t primaryKeyColumnsCount = + dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + static_assert( + nonPrimaryKeysColumnsCount > 0, + "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); +#endif } template, - std::enable_if_t = true> + std::enable_if_t = true> void assert_insertable_type() const {} template, - std::enable_if_t = true> + std::enable_if_t = true> void assert_insertable_type() const { using elements_type = elements_type_t
; using pkcol_index_sequence = col_index_sequence_with; @@ -213,14 +243,44 @@ namespace sqlite_orm { } public: - template - view_t iterate(Args&&... args) { - this->assert_mapped_type(); + template, class... Args> + mapped_view iterate(Args&&... args) { + this->assert_mapped_type(); auto con = this->get_connection(); return {*this, std::move(con), std::forward(args)...}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto iterate(Args&&... args) { + return this->iterate(std::forward(args)...); + } +#endif + +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) + template +#ifdef SQLITE_ORM_CONCEPTS_SUPPORTED + requires(is_select_v(sud).as( + select("53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79")), + cte(z, lp).as( + union_all(select(columns("1", 1)), + select(columns(cast(digits->*lp + 1), digits->*lp + 1), where(digits->*lp < 9)))), + cte(s, ind).as(union_all( + select(columns(input->*sud, instr(input->*sud, "."))), + select(columns(substr(x->*s, 1, x->*ind - 1) || z || substr(x->*s, x->*ind + 1), + instr(substr(x->*s, 1, x->*ind - 1) || z || substr(x->*s, x->*ind + 1), ".")), + where(x->*ind > 0 and + not exists(select( + 1 >>= lp, + from(), + where(z_alias->*z == substr(x->*s, ((x->*ind - 1) / 9) * 9 + lp, 1) or + z_alias->*z == substr(x->*s, ((x->*ind - 1) % 9) + (lp - 1) * 9 + 1, 1) or + z_alias->*z == substr(x->*s, + (((x->*ind - 1) / 3) % 3) * 3 + ((x->*ind - 1) / 27) * 27 + + lp + ((lp - 1) / 3) * 6, + 1))))))))), + select(x->*s, where(x->*ind == 0))); + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + + cout << "Sudoku solution:\n"; + for(const string& answer: results) { + cout << answer << '\n'; + } + cout << endl; +#endif +} + +void show_optimization_fence() { +#if SQLITE_VERSION_NUMBER >= 3035003 +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + auto storage = make_storage(""); + + { + //WITH + // cnt(x) AS MATERIALIZED(VALUES(1)) + // SELECT x FROM cnt; + constexpr orm_cte_moniker auto cnt = "cnt"_cte; + auto ast = with(cnt().as(select(1)), select(cnt->*1_colalias)); + + [[maybe_unused]] string sql = storage.dump(ast); + + [[maybe_unused]] auto stmt = storage.prepare(ast); + } + + { + //WITH + // cnt(x) AS NOT MATERIALIZED(VALUES(1)) + // SELECT x FROM cnt; + constexpr orm_cte_moniker auto cnt = "cnt"_cte; + auto ast = with(cnt().as(select(1)), select(cnt->*1_colalias)); + + [[maybe_unused]] string sql = storage.dump(ast); + + [[maybe_unused]] auto stmt = storage.prepare(ast); + } +#endif +#endif +} + +void show_mapping_and_backreferencing() { + struct Object { + int64 id = 0; + }; + + // column alias + struct cnt : alias_tag { + static constexpr std::string_view get() { + return "counter"; + } + }; + using cte_1 = decltype(1_ctealias); + + auto storage = make_storage("", make_table("object", make_column("id", &Object::id))); + storage.sync_schema(); + + // back-reference via `column_pointer`; + // WITH "1"("id") AS (SELECT "object"."id" FROM "object") SELECT "1"."id" FROM "1" + { + auto ast = with(cte().as(select(&Object::id)), select(column(&Object::id))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + + // map column via alias_holder into cte, + // back-reference via `column_pointer`; + // WITH "1"("x") AS (SELECT 1 AS "counter" UNION ALL SELECT "1"."x" + 1 FROM "1" LIMIT 10) SELECT "1"."x" FROM "1" + { + auto ast = + with(cte("x").as(union_all(select(as(1)), select(column(get()) + 1, limit(10)))), + select(column(get()))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + // map column via alias_holder into cte, + // back-reference via `column_pointer`; + // WITH "1"("x") AS (SELECT 1 AS "counter" UNION ALL SELECT "1"."x" + 1 FROM "1" LIMIT 10) SELECT "1"."x" FROM "1" + { + auto ast = with(cte("x").as(union_all(select(as(1)), select(column(cnt{}) + 1, limit(10)))), + select(column(cnt{}))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + + // implicitly remap column into cte; + // WITH "1"("id") AS (SELECT "object"."id" FROM "object") SELECT "1"."id" FROM "1" + { + auto ast = with(cte(std::ignore).as(select(&Object::id)), select(column(&Object::id))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + + // explicitly remap column into cte (independent of subselect); + // WITH "1"("id") AS (SELECT "object"."id" FROM "object") SELECT "1"."id" FROM "1" + { + auto ast = with(cte(&Object::id).as(select(&Object::id)), select(column(&Object::id))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + + // explicitly remap column as an alias into cte (independent of subselect); + // WITH "1"("counter") AS (SELECT "object"."id" FROM "object") SELECT "1"."counter" FROM "1" + { + auto ast = with(cte(cnt{}).as(select(&Object::id)), select(column(get()))); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } + + // explicitly state that column name should be taken from subselect; + // WITH "CTEObj"("xyz") AS (SELECT "object"."id" FROM "object") SELECT "CTEObj"."xyz" FROM "CTEObj" + { + struct CTEObject : alias_tag { + // a CTE object is its own table alias + using type = CTEObject; + + static std::string get() { + return "CTEObj"; + } + + int64 xyz = 0; + }; + auto ast = + with(cte(make_column("xyz", &CTEObject::xyz)).as(select(&Object::id)), select(&CTEObject::xyz)); + + string sql = storage.dump(ast); + auto stmt = storage.prepare(ast); + } +} + +void neevek_issue_222() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + //WITH + // register_user AS( + // SELECT + // uid, MIN(DATE(user_activity.timestamp)) register_date + // FROM user_activity + // GROUP BY uid + // HAVING register_date >= DATE('now', '-7 days') + // ), + // register_user_count AS( + // SELECT + // R.register_date, + // COUNT(DISTINCT R.uid) AS user_count + // FROM register_user R + // GROUP BY R.register_date + // ) + // SELECT + // R.register_date, + // CAST(julianday(DATE(A.timestamp)) AS INT) - CAST(julianday(R.register_date) AS INT) AS ndays, + // COUNT(DISTINCT A.uid) AS retention, + // C.user_count + // FROM user_activity A + // LEFT JOIN register_user R ON A.uid = R.uid + // LEFT JOIN register_user_count C ON R.register_date = C.register_date + // GROUP BY R.register_date, ndays + // HAVING DATE(A.timestamp) >= DATE('now', '-7 days'); + + struct user_activity { + int64 id; + int64 uid; + time_t timestamp; + }; + + auto storage = make_storage("", + make_table("user_activity", + make_column("id", &user_activity::id, primary_key().autoincrement()), + make_column("uid", &user_activity::uid), + make_column("timestamp", &user_activity::timestamp))); + storage.sync_schema(); + storage.transaction([&storage]() { + time_t now = std::time(nullptr); + auto values = {user_activity{0, 1, now - 86400 * 3}, + user_activity{0, 1, now - 86400 * 2}, + user_activity{0, 1, now}, + user_activity{0, 2, now}}; + storage.insert_range(values.begin(), values.end()); + return true; + }); + + constexpr orm_cte_moniker auto register_user = "register_user"_cte; + constexpr orm_cte_moniker auto registered_cnt = "registered_cnt"_cte; + constexpr orm_column_alias auto register_date = "register_date"_col; + constexpr orm_column_alias auto user_count = "user_count"_col; + constexpr orm_column_alias auto ndays = "ndays"_col; + auto expression = with( + make_tuple( + register_user().as(select( + columns(&user_activity::uid, min(date(&user_activity::timestamp, "unixepoch")) >>= register_date), + group_by(&user_activity::uid).having(greater_or_equal(register_date, date("now", "-7 days"))))), + registered_cnt().as(select(columns(register_user->*register_date, + count(distinct(register_user->*&user_activity::uid)) >>= user_count), + group_by(register_user->*register_date)))), + select(columns(register_user->*register_date, + c(cast(julianday(date(&user_activity::timestamp, "unixepoch")))) - + cast(julianday(register_user->*register_date)) >>= ndays, + count(distinct(&user_activity::uid)) >>= "retention"_col, + registered_cnt->*user_count), + left_join(using_(&user_activity::uid)), + left_join(using_(registered_cnt->*register_date)), + group_by(register_user->*register_date, ndays) + .having(date(&user_activity::timestamp, "unixepoch") >= date("now", "-7 days")))); + + string sql = storage.dump(expression); + + auto stmt = storage.prepare(expression); + auto results = storage.execute(stmt); + + cout << "User Retention:\n"; + for(const char* colName: {"register_date", "ndays", "retention", "user_count"}) { + cout << " " << std::setw(13) << colName; + } + cout << '\n'; + for(int i = 0; i < 4; ++i) { + cout << std::setfill(' ') << " " << std::setw(13) << std::setfill('_') << ""; + } + cout << std::setfill(' ') << '\n'; + for(auto& result: results) { + cout << " " << std::setw(13) << *get<0>(result); + cout << " " << std::setw(13) << get<1>(result); + cout << " " << std::setw(13) << get<2>(result); + cout << " " << std::setw(13) << get<3>(result); + cout << '\n'; + } + cout << endl; +#endif +} + +void greatest_n_per_group() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // Having a table consisting of multiple results for items, + // I want to select a single result row per item, based on a condition like an aggregated value. + // This is possible using a (self) join and filtering by aggregated value (in case it is unique) or using window functions. + // + // In this example, we select the most recent (successful) result per item + + struct some_result { + int64 result_id; + int64 item_id; + time_t timestamp; + bool flag; + }; + + auto storage = + make_storage("", + make_table("some_result", + make_column("result_id", &some_result::result_id, primary_key().autoincrement()), + make_column("item_id", &some_result::item_id) /*foreign key*/, + make_column("timestamp", &some_result::timestamp), + make_column("flag", &some_result::flag))); + storage.sync_schema(); + storage.transaction([&storage]() { + time_t now = std::time(nullptr); + auto values = std::initializer_list{{-1, 1, now - 86400 * 3, false}, + {-1, 1, now - 86400 * 2, true}, + {-1, 1, now, true}, + {-1, 2, now, false}, + {-1, 3, now - 86400 * 2, true}}; + storage.insert_range(values.begin(), values.end()); + return true; + }); + + //select r.* + // from some_result r + // inner join( + // select item_id, + // max(timestamp) as max_date + // from some_result + // group by item)id + // ) wnd + // on wnd.item_id = r.item_id + // and r.timestamp = wnd.max_date + // -- other conditions + //where r.flag = 1 + constexpr orm_cte_moniker auto wnd = "wnd"_cte; + constexpr orm_column_alias auto max_date = "max_date"_col; + auto expression = with( + wnd(&some_result::item_id, max_date) + .as(select(columns(&some_result::item_id, max(&some_result::timestamp)), group_by(&some_result::item_id))), + select(object(), + inner_join(on(&some_result::item_id == wnd->*&some_result::item_id and + &some_result::timestamp == wnd->*max_date)), + // additional conditions + where(c(&some_result::flag) == true))); + string sql = storage.dump(expression); + + auto stmt = storage.prepare(expression); + auto results = storage.execute(stmt); + + cout << "most recent (successful) result per item:\n"; + for(const char* colName: {"id", "item_id", "timestamp", "flag"}) { + cout << "\t" << colName; + } + cout << '\n'; + for(auto& result: results) { + cout << "\t" << result.result_id << "\t" << result.item_id << "\t" << result.timestamp << "\t" << result.flag + << '\n'; + } + cout << endl; +#endif +} +#endif + +int main() { +#ifdef ENABLE_THIS_EXAMPLE + try { + all_integers_between(1, 10); + supervisor_chain(); + works_for_alice(); + family_tree(); + depth_or_breadth_first(); + apfelmaennchen(); + sudoku(); + neevek_issue_222(); + select_from_subselect(); + greatest_n_per_group(); + show_optimization_fence(); + show_mapping_and_backreferencing(); + } catch(const system_error& e) { + cout << "[" << e.code() << "] " << e.what(); + } +#endif + + return 0; +} diff --git a/examples/composite_key.cpp b/examples/composite_key.cpp index e128f0efb..08b77a842 100644 --- a/examples/composite_key.cpp +++ b/examples/composite_key.cpp @@ -10,6 +10,11 @@ #include +#if SQLITE_VERSION_NUMBER >= 3006019 +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE using std::cout; using std::endl; @@ -24,8 +29,10 @@ struct UserVisit { std::string userFirstName; time_t time; }; +#endif int main() { +#ifdef ENABLE_THIS_EXAMPLE using namespace sqlite_orm; auto storage = make_storage( @@ -59,5 +66,7 @@ int main() { storage.replace(User{2, "The Weeknd", "Singer"}); auto weeknd = storage.get(2, "The Weeknd"); cout << "weeknd = " << storage.dump(weeknd) << endl; +#endif + return 0; } diff --git a/examples/core_functions.cpp b/examples/core_functions.cpp index 4f783db21..cf20d88ea 100644 --- a/examples/core_functions.cpp +++ b/examples/core_functions.cpp @@ -1029,6 +1029,7 @@ int main(int, char** argv) { // SELECT TRIM('42totn6372', '0123456789') cout << "TRIM('42totn6372', '0123456789') = " << storage.select(trim("42totn6372", "0123456789")).front() << endl; +#if SQLITE_VERSION_NUMBER >= 3007016 // SELECT RANDOM() for(auto i = 0; i < 10; ++i) { cout << "RANDOM() = " << storage.select(sqlite_orm::random()).front() << endl; @@ -1038,6 +1039,7 @@ int main(int, char** argv) { for(auto& hero: storage.iterate(order_by(sqlite_orm::random()))) { cout << "hero = " << storage.dump(hero) << endl; } +#endif // https://www.techonthenet.com/sqlite/functions/ltrim.php diff --git a/examples/custom_aliases.cpp b/examples/custom_aliases.cpp index 0d6ffa7ca..cacc49433 100644 --- a/examples/custom_aliases.cpp +++ b/examples/custom_aliases.cpp @@ -104,6 +104,35 @@ int main(int, char** argv) { // SELECT C.ID, C.NAME, C.AGE, D.DEPT // FROM COMPANY AS C, DEPARTMENT AS D // WHERE C.ID = D.EMP_ID; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_table_alias auto c_als = "c"_alias.for_(); + constexpr orm_table_alias auto d = "d"_alias.for_(); + static_assert(std::is_empty_v); + constexpr orm_column_alias auto empId = EmployeeIdAlias{}; + auto rowsWithTableAliases = storage.select( + columns(c_als->*&Employee::id, c_als->*&Employee::name, c_als->*&Employee::age, d->*&Department::dept), + where(is_equal(c_als->*&Employee::id, d->*&Department::empId))); + assert(rowsWithTableAliases == simpleRows); + + // SELECT COMPANY.ID as COMPANY_ID, COMPANY.NAME AS COMPANY_NAME, COMPANY.AGE, DEPARTMENT.DEPT + // FROM COMPANY, DEPARTMENT + // WHERE COMPANY_ID = DEPARTMENT.EMP_ID; + auto rowsWithColumnAliases = storage.select( + columns(&Employee::id >>= empId, as(&Employee::name), &Employee::age, &Department::dept), + where(get() == &Department::empId)); + assert(rowsWithColumnAliases == rowsWithTableAliases); + + // SELECT C.ID AS COMPANY_ID, C.NAME AS COMPANY_NAME, C.AGE, D.DEPT + // FROM COMPANY AS C, DEPARTMENT AS D + // WHERE C.ID = D.EMP_ID; + auto rowsWithBothTableAndColumnAliases = + storage.select(columns(c_als->*& Employee::id >>= empId, + as(c_als->*&Employee::name), + c_als->*&Employee::age, + d->*&Department::dept), + where(is_equal(c_als->*&Employee::id, d->*&Department::empId))); + assert(rowsWithBothTableAndColumnAliases == rowsWithBothTableAndColumnAliases); +#else using als_c = alias_c; using als_d = alias_d; auto rowsWithTableAliases = @@ -134,6 +163,7 @@ int main(int, char** argv) { alias_column(&Department::dept)), where(is_equal(alias_column(&Employee::id), alias_column(&Department::empId)))); assert(rowsWithBothTableAndColumnAliases == rowsWithBothTableAndColumnAliases); +#endif return 0; } diff --git a/examples/enum_binding.cpp b/examples/enum_binding.cpp index e8164f730..0bee82ca8 100644 --- a/examples/enum_binding.cpp +++ b/examples/enum_binding.cpp @@ -108,15 +108,15 @@ namespace sqlite_orm { */ template<> struct row_extractor { - Gender extract(const char* row_value) { - if(auto gender = GenderFromString(row_value)) { + Gender extract(const char* columnText) const { + if(auto gender = GenderFromString(columnText)) { return *gender; } else { - throw std::runtime_error("incorrect gender string (" + std::string(row_value) + ")"); + throw std::runtime_error("incorrect gender string (" + std::string(columnText) + ")"); } } - Gender extract(sqlite3_stmt* stmt, int columnIndex) { + Gender extract(sqlite3_stmt* stmt, int columnIndex) const { auto str = sqlite3_column_text(stmt, columnIndex); return this->extract((const char*)str); } diff --git a/examples/exists.cpp b/examples/exists.cpp index ab5a888ba..56815a401 100644 --- a/examples/exists.cpp +++ b/examples/exists.cpp @@ -475,6 +475,25 @@ int main(int, char**) { // ) // ORDER BY 'c'."PAYMENT_AMT" +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_table_alias auto c_als = "c"_alias.for_(); + constexpr orm_table_alias auto d = "d"_alias.for_(); + + double amount = 2000; + auto where_clause = select(d->*&Customer::agentCode, + from(), + where(is_equal(c_als->*&Customer::paymentAmt, std::ref(amount)) and + (d->*&Customer::agentCode == c_als->*&Customer::agentCode))); + + amount = 7000; + + auto statement = storage.prepare(select( + columns(&Order::agentCode, &Order::num, &Order::amount, &Order::custCode, c_als->*&Customer::paymentAmt), + from(), + inner_join(using_(&Customer::agentCode)), + where(not exists(where_clause)), + order_by(c_als->*&Customer::paymentAmt))); +#else using als = alias_c; using als_2 = alias_d; @@ -483,7 +502,7 @@ int main(int, char**) { select(alias_column(&Customer::agentCode), from(), where(is_equal(alias_column(&Customer::paymentAmt), std::ref(amount)) and - (alias_column(&Customer::agentCode) == c(alias_column(&Customer::agentCode))))); + (alias_column(&Customer::agentCode) == alias_column(&Customer::agentCode)))); amount = 7000; @@ -494,11 +513,14 @@ int main(int, char**) { &Order::custCode, alias_column(&Customer::paymentAmt)), from(), - inner_join(on(alias_column(&Customer::agentCode) == c(&Order::agentCode))), + inner_join(on(alias_column(&Customer::agentCode) == &Order::agentCode)), where(not exists(where_clause)), order_by(alias_column(&Customer::paymentAmt)))); +#endif +#if SQLITE_VERSION_NUMBER >= 3014000 auto sql = statement.expanded_sql(); +#endif auto rows = storage.execute(statement); cout << endl; for(auto& row: rows) { diff --git a/examples/foreign_key.cpp b/examples/foreign_key.cpp index b8bb85051..3574bbabb 100644 --- a/examples/foreign_key.cpp +++ b/examples/foreign_key.cpp @@ -9,6 +9,11 @@ #include #include +#if SQLITE_VERSION_NUMBER >= 3006019 +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE using std::cout; using std::endl; @@ -22,10 +27,10 @@ struct Track { std::string trackName; std::unique_ptr trackArtist; // must map to &Artist::artistId }; +#endif -int main(int, char** argv) { - cout << "path = " << argv[0] << endl; - +int main() { +#ifdef ENABLE_THIS_EXAMPLE using namespace sqlite_orm; { // simple case with foreign key to a single column without actions auto storage = make_storage("foreign_key.sqlite", @@ -228,6 +233,7 @@ int main(int, char** argv) { } cout << endl; } +#endif return 0; } diff --git a/examples/having.cpp b/examples/having.cpp index 8b5481a6c..dea676f97 100644 --- a/examples/having.cpp +++ b/examples/having.cpp @@ -47,7 +47,7 @@ int main() { // FROM COMPANY // GROUP BY name // HAVING count(name) < 2; - auto rows = storage.get_all(group_by(&Employee::name).having(lesser_than(count(&Employee::name), 2))); + auto rows = storage.get_all(group_by(&Employee::name).having(less_than(count(&Employee::name), 2))); for(auto& employee: rows) { cout << storage.dump(employee) << endl; } diff --git a/examples/insert.cpp b/examples/insert.cpp index 742e4d76e..62ca2d243 100644 --- a/examples/insert.cpp +++ b/examples/insert.cpp @@ -110,6 +110,7 @@ int main(int, char**) { cout << storage.dump(employee) << endl; } +#if SQLITE_VERSION_NUMBER >= 3024000 // INSERT INTO COMPANY(ID, NAME, AGE, ADDRESS, SALARY) // VALUES (3, 'Sofia', 26, 'Madrid', 15000.0) // (4, 'Doja', 26, 'LA', 25000.0) @@ -127,6 +128,7 @@ int main(int, char**) { c(&Employee::age) = excluded(&Employee::age), c(&Employee::address) = excluded(&Employee::address), c(&Employee::salary) = excluded(&Employee::salary)))); +#endif return 0; } diff --git a/examples/iteration.cpp b/examples/iteration.cpp index f7bc9ec23..1ca54b8d3 100644 --- a/examples/iteration.cpp +++ b/examples/iteration.cpp @@ -64,5 +64,14 @@ int main(int, char**) { } cout << "heroesByAlgorithm.size = " << heroesByAlgorithm.size() << endl; +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) + cout << "====" << endl; + + cout << "Distinct hero names:" << endl; + for(std::string name: storage.iterate(select(distinct(&MarvelHero::name)))) { + cout << name << endl; + } +#endif + return 0; } diff --git a/examples/nullable_enum_binding.cpp b/examples/nullable_enum_binding.cpp index 8625af059..375097144 100644 --- a/examples/nullable_enum_binding.cpp +++ b/examples/nullable_enum_binding.cpp @@ -76,19 +76,19 @@ namespace sqlite_orm { template<> struct row_extractor { - Gender extract(const char* row_value) { - if(row_value) { - if(auto gender = GenderFromString(row_value)) { + Gender extract(const char* columnText) const { + if(columnText) { + if(auto gender = GenderFromString(columnText)) { return *gender; } else { - throw std::runtime_error("incorrect gender string (" + std::string(row_value) + ")"); + throw std::runtime_error("incorrect gender string (" + std::string(columnText) + ")"); } } else { return Gender::None; } } - Gender extract(sqlite3_stmt* stmt, int columnIndex) { + Gender extract(sqlite3_stmt* stmt, int columnIndex) const { auto str = sqlite3_column_text(stmt, columnIndex); return this->extract((const char*)str); } diff --git a/examples/pointer_passing_interface.cpp b/examples/pointer_passing_interface.cpp index f9c68627d..55a6a8039 100644 --- a/examples/pointer_passing_interface.cpp +++ b/examples/pointer_passing_interface.cpp @@ -17,9 +17,11 @@ * Note: pointers are only accessible within application code, and therefore unleakable. */ #include +#if SQLITE_VERSION_NUMBER >= 3020000 #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED #define ENABLE_THIS_EXAMPLE #endif +#endif #ifdef ENABLE_THIS_EXAMPLE #include @@ -40,12 +42,15 @@ using std::error_code; using std::make_unique; using std::min; -// name for our pointer value types -inline constexpr const char ecat_pvt_name[] = "ecat"; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +// c++ integral constant for our pointer type domain +inline constexpr orm_pointer_type auto ecode_pointer_tag = "ecode"_pointer_type; +#else +// name for our pointer type domain inline constexpr const char ecode_pvt_name[] = "ecode"; -// c++ integral constant for our pointer value types -using ecat_pvt = std::integral_constant; -using ecode_pvt = std::integral_constant; +// c++ integral constant for our pointer type domain +using ecode_pointer_type = std::integral_constant; +#endif // a fixed set of error categories the application is dealing with enum class app_error_category : unsigned int { @@ -86,18 +91,31 @@ int main() { int errorValue = 0; unsigned int errorCategory = 0; }; - using ecat_arg_t = pointer_arg; - using ecode_arg_t = pointer_arg; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + using ecat_arg = pointer_arg_t; + using ecode_arg = pointer_arg_t; +#else + using ecat_arg = pointer_arg; + using ecode_arg = pointer_arg; +#endif // function returning a pointer to a std::error_category, // which is only visible to functions accepting pointer values of type "ecat" struct get_error_category_fn { - using ecat_binding = static_pointer_binding; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + using ecat_binding = static_pointer_binding_t; +#else + using ecat_binding = static_pointer_binding; +#endif ecat_binding operator()(unsigned int errorCategory) const { size_t idx = min(errorCategory, ecat_map.size()); const error_category* ecat = idx != ecat_map.size() ? &get(ecat_map[idx]) : nullptr; - return statically_bindable_pointer(ecat); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + return bind_pointer_statically(ecat); +#else + return bind_pointer_statically(ecat); +#endif } static constexpr const char* name() { @@ -108,7 +126,7 @@ int main() { // function accepting a pointer to a std::error_category, // returns the category's name struct error_category_name_fn { - std::string operator()(ecat_arg_t pv) const { + std::string operator()(ecat_arg pv) const { if(const error_category* ec = pv) { return ec->name(); } @@ -123,7 +141,7 @@ int main() { // function accepting a pointer to a std::error_category and an error code, // returns the error message struct error_category_message_fn { - std::string operator()(ecat_arg_t pv, int errorValue) const { + std::string operator()(ecat_arg pv, int errorValue) const { if(const error_category* ec = pv) { return ec->message(errorValue); } @@ -137,14 +155,20 @@ int main() { // function returning an error_code object from an error value struct make_error_code_fn { - using ecode_binding = pointer_binding>; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + using ecode_binding = + pointer_binding_t>; +#else + using ecode_binding = + pointer_binding>; +#endif ecode_binding operator()(int errorValue, unsigned int errorCategory) const { size_t idx = min(errorCategory, ecat_map.size()); error_code* ec = idx != ecat_map.size() ? new error_code{errorValue, get(ecat_map[idx])} : nullptr; - return bindable_pointer(ec, default_delete{}); + return bind_pointer(ec, default_delete{}); } static constexpr const char* name() { @@ -154,8 +178,8 @@ int main() { // function comparing two error_code objects struct equal_error_code_fn { - bool operator()(ecode_arg_t pv1, ecode_arg_t pv2) const { - error_code *ec1 = pv1, *ec2 = pv2; + bool operator()(ecode_arg pv1, ecode_arg pv2) const { + const error_code *ec1 = pv1, *ec2 = pv2; if(ec1 && ec2) { return *ec1 == *ec2; } @@ -207,7 +231,11 @@ int main() { &Result::errorCategory, as>(func( func(&Result::errorValue, &Result::errorCategory), - bindable_pointer(make_unique()))), +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + bind_pointer(make_unique()))), +#else + bind_pointer(make_unique()))), +#endif func(func(&Result::errorCategory)), func(func(&Result::errorCategory), &Result::errorValue)), diff --git a/examples/private_class_members.cpp b/examples/private_class_members.cpp index 00a4294ef..a7544be81 100644 --- a/examples/private_class_members.cpp +++ b/examples/private_class_members.cpp @@ -78,7 +78,7 @@ int main(int, char**) { auto idsOnly = storage.select(&Player::getId); // or storage.select(&Player::setId); cout << "idsOnly count = " << idsOnly.size() << endl; - auto somePlayers = storage.get_all(where(lesser_than(length(&Player::getName), 5))); + auto somePlayers = storage.get_all(where(less_than(length(&Player::getName), 5))); cout << "players with length(name) < 5 = " << somePlayers.size() << endl; assert(somePlayers.size() == 1); for(auto& player: somePlayers) { diff --git a/examples/select.cpp b/examples/select.cpp index eaf6cbf3e..ae2a622fc 100644 --- a/examples/select.cpp +++ b/examples/select.cpp @@ -6,6 +6,11 @@ #include +#if SQLITE_VERSION_NUMBER >= 3006019 +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE using namespace sqlite_orm; using std::cout; using std::endl; @@ -66,7 +71,7 @@ void all_employees() { // now let's select id, name and salary.. auto idsNamesSalarys = storage.select(columns(&Employee::id, &Employee::name, &Employee::salary)); - for(auto& row: idsNamesSalarys) { // row's type is tuple> + for(auto& row: idsNamesSalarys) { // row's type is `tuple>` cout << "id = " << get<0>(row) << ", name = " << get<1>(row) << ", salary = "; if(get<2>(row)) { cout << *get<2>(row); @@ -150,7 +155,7 @@ void all_artists() { // SELECT artists.*, albums.* FROM artists JOIN albums ON albums.artist_id = artist.id cout << "artists.*, albums.*\n"; - // row's type is std::tuple + // row's type is `std::tuple` for(auto& row: storage.select(columns(asterisk(), asterisk()), join(on(c(&Album::artist_id) == &Artist::id)))) { cout << get<0>(row) << '\t' << get<1>(row) << '\t' << get<2>(row) << '\t' << get<3>(row) << '\n'; @@ -158,14 +163,63 @@ void all_artists() { cout << endl; } -int main() { +void named_adhoc_structs() { + struct Artist { + int id; + std::string name; + }; + + struct Album { + int id; + int artist_id; + std::string name; + }; + + struct Z { + decltype(Album::name) album_name; + decltype(Artist::name) artist_name; + }; + // define SQL expression for ad-hoc construction of Z + constexpr auto z_struct = struct_(&Album::name, &Artist::name); + + auto storage = make_storage("", + make_table("artists", + make_column("id", &Artist::id, primary_key().autoincrement()), + make_column("name", &Artist::name)), + make_table("albums", + make_column("id", &Album::id, primary_key().autoincrement()), + make_column("artist_id", &Album::artist_id), + make_column("name", &Album::name), + foreign_key(&Album::artist_id).references(&Artist::id))); + storage.sync_schema(); + storage.transaction([&storage] { + auto artistPk = storage.insert(Artist{-1, "Artist"}); + storage.insert(Album{-1, artistPk, "Album 1"}); + storage.insert(Album{-1, artistPk, "Album 2"}); + return true; + }); + + // SELECT albums.name, artists.name FROM albums JOIN artists ON artist.id = albums.artist_id + + cout << "albums.name, artists.name\n"; + // row's type is Z + for(auto& row: storage.select(z_struct, join(on(c(&Album::artist_id) == &Artist::id)))) { + cout << row.album_name << '\t' << row.artist_name << '\n'; + } + cout << endl; +} +#endif +int main() { +#ifdef ENABLE_THIS_EXAMPLE try { all_employees(); all_artists(); + named_adhoc_structs(); } catch(const std::system_error& e) { cout << "[" << e.code() << "] " << e.what(); } +#endif return 0; } diff --git a/examples/self_join.cpp b/examples/self_join.cpp index d135e5666..46cdda68f 100644 --- a/examples/self_join.cpp +++ b/examples/self_join.cpp @@ -6,6 +6,11 @@ #include #include +#if SQLITE_VERSION_NUMBER >= 3006019 +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE using std::cout; using std::endl; @@ -26,6 +31,9 @@ struct Employee { std::string fax; std::string email; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr sqlite_orm::orm_table_reference auto employee = sqlite_orm::c(); +#endif /** * This is how custom alias is made: @@ -41,8 +49,10 @@ struct custom_alias : sqlite_orm::alias_tag { return res; } }; +#endif int main() { +#ifdef ENABLE_THIS_EXAMPLE using namespace sqlite_orm; auto storage = make_storage("self_join.sqlite", @@ -199,15 +209,27 @@ int main() { // FROM employees // INNER JOIN employees m // ON m.ReportsTo = employees.EmployeeId +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_table_alias auto m = "m"_alias.for_(); + auto firstNames = + storage.select(columns(m->*&Employee::firstName || " " || m->*&Employee::lastName, + employee->*&Employee::firstName || " " || employee->*&Employee::lastName), + inner_join(on(m->*&Employee::reportsTo == employee->*&Employee::employeeId))); + cout << "firstNames count = " << firstNames.size() << endl; + for(auto& row: firstNames) { + cout << std::get<0>(row) << '\t' << std::get<1>(row) << endl; + } +#else using als = alias_m; auto firstNames = storage.select( - columns(alias_column(&Employee::firstName) || c(" ") || alias_column(&Employee::lastName), + columns(alias_column(&Employee::firstName) || " " || alias_column(&Employee::lastName), &Employee::firstName || c(" ") || &Employee::lastName), - inner_join(on(alias_column(&Employee::reportsTo) == c(&Employee::employeeId)))); + inner_join(on(alias_column(&Employee::reportsTo) == &Employee::employeeId))); cout << "firstNames count = " << firstNames.size() << endl; for(auto& row: firstNames) { cout << std::get<0>(row) << '\t' << std::get<1>(row) << endl; } +#endif assert(storage.count() == storage.count>()); } @@ -219,16 +241,30 @@ int main() { // FROM employees // INNER JOIN employees emp // ON emp.ReportsTo = employees.EmployeeId +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + static_assert(std::is_empty_v>); + constexpr orm_table_alias auto emp = custom_alias{}; + auto firstNames = + storage.select(columns(emp->*&Employee::firstName || " " || emp->*&Employee::lastName, + employee->*&Employee::firstName || " " || employee->*&Employee::lastName), + inner_join(on(emp->*&Employee::reportsTo == employee->*&Employee::employeeId))); + cout << "firstNames count = " << firstNames.size() << endl; + for(auto& row: firstNames) { + cout << std::get<0>(row) << '\t' << std::get<1>(row) << endl; + } +#else using als = custom_alias; auto firstNames = storage.select( - columns(alias_column(&Employee::firstName) || c(" ") || alias_column(&Employee::lastName), + columns(alias_column(&Employee::firstName) || " " || alias_column(&Employee::lastName), &Employee::firstName || c(" ") || &Employee::lastName), - inner_join(on(alias_column(&Employee::reportsTo) == c(&Employee::employeeId)))); + inner_join(on(alias_column(&Employee::reportsTo) == &Employee::employeeId))); cout << "firstNames count = " << firstNames.size() << endl; for(auto& row: firstNames) { cout << std::get<0>(row) << '\t' << std::get<1>(row) << endl; } +#endif } +#endif return 0; } diff --git a/examples/subquery.cpp b/examples/subquery.cpp index c2962034f..451b33cd3 100644 --- a/examples/subquery.cpp +++ b/examples/subquery.cpp @@ -1305,6 +1305,22 @@ int main(int, char**) { << endl; } } + { + // SELECT employee_id, first_name, last_name, salary, (SELECT AVG(salary) FROM employees), salary > (SELECT AVG(salary) FROM employees) + // FROM "employees"; + auto rows = storage.select(columns(&Employee::id, + &Employee::firstName, + &Employee::lastName, + &Employee::salary, + select(avg(&Employee::salary)), + greater_than(&Employee::salary, select(avg(&Employee::salary))))); + cout << "employee_id first_name last_name salary avg_salary salary>avg" << endl; + cout << "----------- ---------- ---------- ---------- ---------- ----------" << endl; + for(auto& row: rows) { + cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << '\t' << std::get<3>(row) + << '\t' << std::get<4>(row) << '\t' << std::get<5>(row) << endl; + } + } { // SELECT first_name, last_name, department_id // FROM employees @@ -1346,6 +1362,21 @@ int main(int, char**) { // WHERE salary >(SELECT AVG(salary) // FROM employees // WHERE department_id = e.department_id); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_table_alias auto e = "e"_alias.for_(); + auto rows = storage.select( + columns(e->*&Employee::lastName, e->*&Employee::salary, e->*&Employee::departmentId), + from(), + where(greater_than(e->*&Employee::salary, + select(avg(&Employee::salary), + from(), + where(is_equal(&Employee::departmentId, e->*&Employee::departmentId)))))); + cout << "last_name salary department_id" << endl; + cout << "---------- ---------- -------------" << endl; + for(auto& row: rows) { + cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << endl; + } +#else using als = alias_e; auto rows = storage.select( columns(alias_column(&Employee::lastName), @@ -1362,6 +1393,7 @@ int main(int, char**) { for(auto& row: rows) { cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << endl; } +#endif } { // SELECT first_name, last_name, employee_id, job_id @@ -1372,10 +1404,10 @@ int main(int, char**) { auto rows = storage.select(columns(&Employee::firstName, &Employee::lastName, &Employee::id, &Employee::jobId), from(), - where(lesser_or_equal(1, - select(count(), - from(), - where(is_equal(&Employee::id, &JobHistory::employeeId)))))); + where(less_or_equal(1, + select(count(), + from(), + where(is_equal(&Employee::id, &JobHistory::employeeId)))))); cout << "first_name last_name employee_id job_id" << endl; cout << "---------- ---------- ----------- ----------" << endl; for(auto& row: rows) { diff --git a/examples/union.cpp b/examples/union.cpp index 4c5b588de..8952f5394 100644 --- a/examples/union.cpp +++ b/examples/union.cpp @@ -8,11 +8,17 @@ #include #include +#if SQLITE_VERSION_NUMBER >= 3006019 +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE using std::cout; using std::endl; +#endif int main() { - +#ifdef ENABLE_THIS_EXAMPLE struct Employee { int id; std::string name; @@ -108,6 +114,7 @@ int main() { } cout << endl; } +#endif return 0; } diff --git a/examples/user_defined_functions.cpp b/examples/user_defined_functions.cpp index 34bb30c33..5c3d60314 100644 --- a/examples/user_defined_functions.cpp +++ b/examples/user_defined_functions.cpp @@ -34,6 +34,9 @@ struct SignFunction { return "SIGN"; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr orm_scalar_function auto sign = func; +#endif /** * Aggregate function must be defined as a dedicated class with at least three functions: @@ -77,6 +80,9 @@ struct AcceleratedSumFunction { return result; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr orm_aggregate_function auto accelerated_sum = func; +#endif /** * This is also a scalar function just like `SignFunction` but this function @@ -105,6 +111,9 @@ struct ArithmeticMeanFunction { return result; } }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline constexpr orm_scalar_function auto arithmetic_mean = func; +#endif int main() { @@ -124,6 +133,49 @@ int main() { make_table("t", make_column("a", &Table::a), make_column("b", &Table::b), make_column("c", &Table::c))); storage.sync_schema(); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * This function can be called at any time doesn't matter whether connection is open or not. + * To delete created scalar function use `storage.delete_scalar_function()` function call. + */ + storage.create_scalar_function(); + + // SELECT SIGN(3) + auto signRows = storage.select(sign(3)); + cout << "SELECT SIGN(3) = " << signRows.at(0) << endl; + + storage.insert(Table{1, -1, 2}); + storage.insert(Table{2, -2, 4}); + storage.insert(Table{3, -3, 8}); + storage.insert(Table{4, -4, 16}); + + storage.create_aggregate_function(); + + // SELECT ASUM(a), ASUM(b), ASUM(c) + // FROM t + auto aSumRows = + storage.select(columns(accelerated_sum(&Table::a), accelerated_sum(&Table::b), accelerated_sum(&Table::c))); + cout << "SELECT ASUM(a), ASUM(b), ASUM(c) FROM t:" << endl; + for(auto& row: aSumRows) { + cout << '\t' << get<0>(row) << endl; + cout << '\t' << get<1>(row) << endl; + cout << '\t' << get<2>(row) << endl; + } + + storage.create_scalar_function(); + + // SELECT ARITHMETIC_MEAN(5, 6, 7) + auto arithmeticMeanRows1 = storage.select(arithmetic_mean(5, 6, 7)); + cout << "SELECT ARITHMETIC_MEAN(5, 6, 7) = " << arithmeticMeanRows1.front() << endl; + + // SELECT ARITHMETIC_MEAN(-2, 1) + auto arithmeticMeanRows2 = storage.select(arithmetic_mean(-2, 1)); + cout << "SELECT ARITHMETIC_MEAN(-2, 1) = " << arithmeticMeanRows2.front() << endl; + + // SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) + auto arithmeticMeanRows3 = storage.select(arithmetic_mean(-5.5, 4, 13.2, 256.4)); + cout << "SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) = " << arithmeticMeanRows3.front() << endl; +#else /** * This function can be called at any time doesn't matter whether connection is open or not. * To delete created scalar function use `storage.delete_scalar_function()` function call. @@ -166,6 +218,7 @@ int main() { // SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) auto arithmeticMeanRows3 = storage.select(func(-5.5, 4, 13.2, 256.4)); cout << "SELECT ARITHMETIC_MEAN(-5.5, 4, 13.2, 256.4) = " << arithmeticMeanRows3.front() << endl; +#endif return 0; } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ae74309b2..fc45ba9f2 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -8,6 +8,9 @@ __pragma(push_macro("max")) #endif // defined(_MSC_VER) #pragma once +#include +#pragma once + // #include "cxx_universal.h" /* @@ -65,8 +68,12 @@ using std::nullptr_t; #define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED #endif -#if __cpp_inline_variables >= 201606L -#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED +#if __cpp_constexpr >= 201603L +#define SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#endif + +#if __cpp_range_based_for >= 201603L +#define SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED #endif #if __cpp_if_constexpr >= 201606L @@ -77,15 +84,27 @@ using std::nullptr_t; #define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED #endif +#if __cpp_structured_bindings >= 201606L +#define SQLITE_ORM_STRUCTURED_BINDINGS_SUPPORTED +#endif + #if __cpp_generic_lambdas >= 201707L #define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED #else #endif +#if __cpp_init_captures >= 201803L +#define SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED +#endif + #if __cpp_consteval >= 201811L #define SQLITE_ORM_CONSTEVAL_SUPPORTED #endif +#if __cpp_char8_t >= 201811L +#define SQLITE_ORM_CHAR8T_SUPPORTED +#endif + #if __cpp_aggregate_paren_init >= 201902L #define SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED #endif @@ -94,6 +113,22 @@ using std::nullptr_t; #define SQLITE_ORM_CONCEPTS_SUPPORTED #endif +#if __cpp_nontype_template_args >= 201911L +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#endif + +#if __cpp_nontype_template_args >= 201911L +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#endif + +#if __cpp_pack_indexing >= 202311L +#define SQLITE_ORM_PACK_INDEXING_SUPPORTED +#endif + +#if __cplusplus >= 202002L +#define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED +#endif + // #include "cxx_compiler_quirks.h" /* @@ -128,6 +163,28 @@ using std::nullptr_t; #if defined(_MSC_VER) && (_MSC_VER < 1920) #define SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION +// Type replacement may fail if an alias template has a non-type template parameter from a dependent expression in it, +// `e.g. template using is_something = std::bool_constant>;` +// Remedy, e.g.: use a derived struct: `template struct is_somthing : std::bool_constant>;` +#define SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR +#endif + +// These compilers are known to have problems with alias templates in SFINAE contexts: +// clang 3.5 +// gcc 8.3 +// msvc 15.9 +// Type replacement may fail if an alias template has dependent expression or decltype in it. +// In these cases we have to use helper structures to break down the type alias. +// Note that the detection of specific compilers is so complicated because some compilers emulate other compilers, +// so we simply exclude all compilers that do not support C++20, even though this test is actually inaccurate. +#if(defined(_MSC_VER) && (_MSC_VER < 1920)) || (!defined(_MSC_VER) && (__cplusplus < 202002L)) +#define SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE +#endif + +// overwrite SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED +#if(__cpp_nontype_template_args < 201911L) && \ + (defined(__clang__) && (__clang_major__ >= 12) && (__cplusplus >= 202002L)) +#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED #endif // clang 10 chokes on concepts that don't depend on template parameters; @@ -137,26 +194,98 @@ using std::nullptr_t; #define SQLITE_ORM_BROKEN_NONTEMPLATE_CONCEPTS #endif +#if SQLITE_ORM_HAS_INCLUDE() +#include +#endif + +#ifdef SQLITE_ORM_CONSTEXPR_LAMBDAS_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 +#endif + #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED #define SQLITE_ORM_INLINE_VAR inline #else #define SQLITE_ORM_INLINE_VAR #endif +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#define SQLITE_ORM_CONSTEXPR_IF constexpr +#else +#define SQLITE_ORM_CONSTEXPR_IF +#endif + +#if __cpp_lib_constexpr_functional >= 201907L +#define SQLITE_ORM_CONSTEXPR_CPP20 constexpr +#else +#define SQLITE_ORM_CONSTEXPR_CPP20 +#endif + #if SQLITE_ORM_HAS_CPP_ATTRIBUTE(no_unique_address) >= 201803L #define SQLITE_ORM_NOUNIQUEADDRESS [[no_unique_address]] #else #define SQLITE_ORM_NOUNIQUEADDRESS #endif +#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define SQLITE_ORM_CPP_LIKELY [[likely]] +#define SQLITE_ORM_CPP_UNLIKELY [[unlikely]] +#else +#define SQLITE_ORM_CPP_LIKELY +#define SQLITE_ORM_CPP_UNLIKELY +#endif + #ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED #define SQLITE_ORM_CONSTEVAL consteval #else #define SQLITE_ORM_CONSTEVAL constexpr #endif + +#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && __cpp_lib_concepts >= 202002L +#define SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#endif + +#if __cpp_lib_ranges >= 201911L +#define SQLITE_ORM_CPP20_RANGES_SUPPORTED +#endif + +// C++20 or later (unfortunately there's no feature test macro). +// Stupidly, clang says C++20, but `std::default_sentinel_t` was only implemented in libc++ 13 and libstd++-v3 10 +// (the latter is used on Linux). +// gcc got it right and reports C++20 only starting with v10. +// The check here doesn't care and checks the library versions in use. +// +// Another way of detection might be the feature-test macro __cpp_lib_concepts +#if(__cplusplus >= 202002L) && \ + ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13000) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) +#define SQLITE_ORM_STL_HAS_DEFAULT_SENTINEL +#endif + +#if(defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED) && defined(SQLITE_ORM_INLINE_VARIABLES_SUPPORTED) && \ + defined(SQLITE_ORM_CONSTEVAL_SUPPORTED)) && \ + (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) +#define SQLITE_ORM_WITH_CPP20_ALIASES +#endif + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) +#define SQLITE_ORM_WITH_CTE +#endif + +// define the inline namespace "literals" so that it is available even if it was not introduced by a feature +namespace sqlite_orm { + inline namespace literals {} +} #pragma once -#include // std::enable_if, std::is_same +#include // std::enable_if, std::is_same, std::is_empty, std::is_aggregate +#if __cpp_lib_unwrap_ref >= 201811L +#include // std::reference_wrapper +#else +#include // std::reference_wrapper +#endif + +// #include "functional/cxx_core_features.h" // #include "functional/cxx_type_traits_polyfill.h" @@ -164,14 +293,56 @@ using std::nullptr_t; // #include "cxx_universal.h" +// #include "mpl/conditional.h" + +namespace sqlite_orm { + namespace internal { + namespace mpl { + + /* + * Binary quoted metafunction equivalent to `std::conditional`, + * using an improved implementation in respect to memoization. + * + * Because `conditional` is only typed on a single bool non-type template parameter, + * the compiler only ever needs to memoize 2 instances of this class template. + * The type selection is a nested cheap alias template. + */ + template + struct conditional { + template + using fn = A; + }; + + template<> + struct conditional { + template + using fn = B; + }; + + // directly invoke `conditional` + template + using conditional_t = typename conditional::template fn; + } + } + + namespace mpl = internal::mpl; +} + namespace sqlite_orm { namespace internal { namespace polyfill { #if __cpp_lib_void_t >= 201411L using std::void_t; #else + /* + * Implementation note: Conservative implementation due to CWG issue 1558 (Unused arguments in alias template specializations). + */ template - using void_t = void; + struct always_void { + using type = void; + }; + template + using void_t = typename always_void::type; #endif #if __cpp_lib_bool_constant >= 201505L @@ -194,7 +365,7 @@ namespace sqlite_orm { template struct conjunction : B1 {}; template - struct conjunction : std::conditional_t, B1> {}; + struct conjunction : mpl::conditional_t, B1> {}; template SQLITE_ORM_INLINE_VAR constexpr bool conjunction_v = conjunction::value; @@ -203,7 +374,7 @@ namespace sqlite_orm { template struct disjunction : B1 {}; template - struct disjunction : std::conditional_t> {}; + struct disjunction : mpl::conditional_t> {}; template SQLITE_ORM_INLINE_VAR constexpr bool disjunction_v = disjunction::value; @@ -311,17 +482,34 @@ namespace sqlite_orm { template using is_any_of = polyfill::disjunction...>; + template + struct value_unref_type : polyfill::remove_cvref {}; + + template + struct value_unref_type> : std::remove_const {}; + + template + using value_unref_type_t = typename value_unref_type::type; + + template + using is_eval_order_garanteed = +#if __cpp_lib_is_aggregate >= 201703L + std::is_aggregate; +#else + std::is_pod; +#endif + // enable_if for types template class Op, class... Args> using match_if = std::enable_if_t::value>; // enable_if for types template class Op, class... Args> - using match_if_not = std::enable_if_t>>; + using match_if_not = std::enable_if_t>::value>; // enable_if for types template class Primary> - using match_specialization_of = std::enable_if_t>; + using match_specialization_of = std::enable_if_t::value>; // enable_if for functions template class Op, class... Args> @@ -333,7 +521,8 @@ namespace sqlite_orm { // enable_if for functions template class Primary> - using satisfies_is_specialization_of = std::enable_if_t, bool>; + using satisfies_is_specialization_of = + std::enable_if_t::value, bool>; } // type name template aliases for syntactic sugar @@ -341,373 +530,153 @@ namespace sqlite_orm { template using type_t = typename T::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_type_t = typename decltype(a)::type; +#endif + + template + using value_type_t = typename T::value_type; + template using field_type_t = typename T::field_type; template using constraints_type_t = typename T::constraints_type; + template + using columns_tuple_t = typename T::columns_tuple; + template using object_type_t = typename T::object_type; template using elements_type_t = typename T::elements_type; + template + using table_type_t = typename T::table_type; + template using target_type_t = typename T::target_type; + template + using left_type_t = typename T::left_type; + + template + using right_type_t = typename T::right_type; + template using on_type_t = typename T::on_type; - } -} -#pragma once -#include -#include // std::error_code, std::system_error -#include // std::string -#include -#include // std::ostringstream -#include + template + using expression_type_t = typename T::expression_type; -namespace sqlite_orm { + template + using alias_type_t = typename As::alias_type; - /** @short Enables classifying sqlite error codes. +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using udf_type_t = typename T::udf_type; - @note We don't bother listing all possible values; - this also allows for compatibility with - 'Construction rules for enum class values (P0138R2)' - */ - enum class sqlite_errc {}; + template + using auto_udf_type_t = typename decltype(a)::udf_type; +#endif - enum class orm_error_code { - not_found = 1, - type_is_not_mapped_to_storage, - trying_to_dereference_null_iterator, - too_many_tables_specified, - incorrect_set_fields_specified, - column_not_found, - table_has_no_primary_key_column, - cannot_start_a_transaction_within_a_transaction, - no_active_transaction, - incorrect_journal_mode_string, - invalid_collate_argument_enum, - failed_to_init_a_backup, - unknown_member_value, - incorrect_order, - cannot_use_default_value, - arguments_count_does_not_match, - function_not_found, - index_is_out_of_bounds, - value_is_null, - no_tables_specified, - }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + using cte_moniker_type_t = typename T::cte_moniker_type; -} + template + using cte_mapper_type_t = typename T::cte_mapper_type; -namespace std { - template<> - struct is_error_code_enum<::sqlite_orm::sqlite_errc> : true_type {}; + // T::alias_type or nonesuch + template + using alias_holder_type_or_none = polyfill::detected; - template<> - struct is_error_code_enum<::sqlite_orm::orm_error_code> : true_type {}; + template + using alias_holder_type_or_none_t = typename alias_holder_type_or_none::type; +#endif + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept stateless = std::is_empty_v; +#endif + } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_names_type = requires { typename T::type; }; +#endif } +#pragma once namespace sqlite_orm { - class orm_error_category : public std::error_category { - public: - const char* name() const noexcept override final { - return "ORM error"; - } + namespace internal { - std::string message(int c) const override final { - switch(static_cast(c)) { - case orm_error_code::not_found: - return "Not found"; - case orm_error_code::type_is_not_mapped_to_storage: - return "Type is not mapped to storage"; - case orm_error_code::trying_to_dereference_null_iterator: - return "Trying to dereference null iterator"; - case orm_error_code::too_many_tables_specified: - return "Too many tables specified"; - case orm_error_code::incorrect_set_fields_specified: - return "Incorrect set fields specified"; - case orm_error_code::column_not_found: - return "Column not found"; - case orm_error_code::table_has_no_primary_key_column: - return "Table has no primary key column"; - case orm_error_code::cannot_start_a_transaction_within_a_transaction: - return "Cannot start a transaction within a transaction"; - case orm_error_code::no_active_transaction: - return "No active transaction"; - case orm_error_code::invalid_collate_argument_enum: - return "Invalid collate_argument enum"; - case orm_error_code::failed_to_init_a_backup: - return "Failed to init a backup"; - case orm_error_code::unknown_member_value: - return "Unknown member value"; - case orm_error_code::incorrect_order: - return "Incorrect order"; - case orm_error_code::cannot_use_default_value: - return "The statement 'INSERT INTO * DEFAULT VALUES' can be used with only one row"; - case orm_error_code::arguments_count_does_not_match: - return "Arguments count does not match"; - case orm_error_code::function_not_found: - return "Function not found"; - case orm_error_code::index_is_out_of_bounds: - return "Index is out of bounds"; - case orm_error_code::value_is_null: - return "Value is null"; - case orm_error_code::no_tables_specified: - return "No tables specified"; - default: - return "unknown error"; - } - } - }; + enum class collate_argument { + binary, + nocase, + rtrim, + }; + } - class sqlite_error_category : public std::error_category { - public: - const char* name() const noexcept override final { - return "SQLite error"; - } +} +#pragma once - std::string message(int c) const override final { - return sqlite3_errstr(c); - } - }; +#include // std::system_error +#include // std::ostream +#include // std::string +#include // std::tuple +#include // std::is_base_of, std::false_type, std::true_type - inline const orm_error_category& get_orm_error_category() { - static orm_error_category res; - return res; - } +// #include "functional/cxx_universal.h" - inline const sqlite_error_category& get_sqlite_error_category() { - static sqlite_error_category res; - return res; - } +// #include "functional/cxx_type_traits_polyfill.h" - inline std::error_code make_error_code(sqlite_errc ev) noexcept { - return {static_cast(ev), get_sqlite_error_category()}; - } - - inline std::error_code make_error_code(orm_error_code ev) noexcept { - return {static_cast(ev), get_orm_error_category()}; - } - - template - std::string get_error_message(sqlite3* db, T&&... args) { - std::ostringstream stream; - using unpack = int[]; - static_cast(unpack{0, (static_cast(static_cast(stream << args)), 0)...}); - stream << sqlite3_errmsg(db); - return stream.str(); - } - - template - [[noreturn]] void throw_error(sqlite3* db, T&&... args) { - throw std::system_error{sqlite_errc(sqlite3_errcode(db)), get_error_message(db, std::forward(args)...)}; - } - - inline std::system_error sqlite_to_system_error(int ev) { - return {sqlite_errc(ev)}; - } - - inline std::system_error sqlite_to_system_error(sqlite3* db) { - return {sqlite_errc(sqlite3_errcode(db)), sqlite3_errmsg(db)}; - } - - [[noreturn]] inline void throw_translated_sqlite_error(int ev) { - throw sqlite_to_system_error(ev); - } - - [[noreturn]] inline void throw_translated_sqlite_error(sqlite3* db) { - throw sqlite_to_system_error(db); - } - - [[noreturn]] inline void throw_translated_sqlite_error(sqlite3_stmt* stmt) { - throw sqlite_to_system_error(sqlite3_db_handle(stmt)); - } -} -#pragma once - -#include // std::string -#include // std::shared_ptr, std::unique_ptr -#include // std::vector -// #include "functional/cxx_optional.h" - -// #include "cxx_core_features.h" - -#if SQLITE_ORM_HAS_INCLUDE() -#include -#endif - -#if __cpp_lib_optional >= 201606L -#define SQLITE_ORM_OPTIONAL_SUPPORTED -#endif - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "type_traits.h" - -// #include "is_std_ptr.h" - -#include -#include - -namespace sqlite_orm { - - /** - * Specialization for optional type (std::shared_ptr / std::unique_ptr). - */ - template - struct is_std_ptr : std::false_type {}; - - template - struct is_std_ptr> : std::true_type { - using element_type = typename std::shared_ptr::element_type; - - static std::shared_ptr make(std::remove_cv_t&& v) { - return std::make_shared(std::move(v)); - } - }; - - template - struct is_std_ptr> : std::true_type { - using element_type = typename std::unique_ptr::element_type; - - static auto make(std::remove_cv_t&& v) { - return std::make_unique(std::move(v)); - } - }; -} - -namespace sqlite_orm { - - /** - * This class transforms a C++ type to a sqlite type name (int -> INTEGER, ...) - */ - template - struct type_printer {}; - - struct integer_printer { - const std::string& print() const { - static const std::string res = "INTEGER"; - return res; - } - }; - - struct text_printer { - const std::string& print() const { - static const std::string res = "TEXT"; - return res; - } - }; - - struct real_printer { - const std::string& print() const { - static const std::string res = "REAL"; - return res; - } - }; - - struct blob_printer { - const std::string& print() const { - static const std::string res = "BLOB"; - return res; - } - }; - - // Note: char, unsigned/signed char are used for storing integer values, not char values. - template - struct type_printer>, - std::is_integral>>> : integer_printer { - }; - - template - struct type_printer::value>> : real_printer {}; - - template - struct type_printer, - std::is_base_of, - std::is_base_of>>> : text_printer {}; - - template - struct type_printer::value>> : type_printer {}; - -#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED - template - struct type_printer>> - : type_printer {}; -#endif - - template<> - struct type_printer, void> : blob_printer {}; -} -#pragma once - -namespace sqlite_orm { - - namespace internal { - - enum class collate_argument { - binary, - nocase, - rtrim, - }; - } - -} -#pragma once - -#include // std::system_error -#include // std::ostream -#include // std::string -#include // std::tuple, std::make_tuple -#include // std::is_base_of, std::false_type, std::true_type - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "functional/mpl.h" +// #include "functional/mpl.h" /* * Symbols for 'template metaprogramming' (compile-time template programming), - * inspired by the MPL of Aleksey Gurtovoy and David Abrahams. + * inspired by the MPL of Aleksey Gurtovoy and David Abrahams, and the Mp11 of Peter Dimov and Bjorn Reese. * * Currently, the focus is on facilitating advanced type filtering, * such as filtering columns by constraints having various traits. * Hence it contains only a very small subset of a full MPL. * - * Two key concepts are critical to understanding: - * 1. A 'metafunction' is a class template that represents a function invocable at compile-time. - * 2. A 'metafunction class' is a certain form of metafunction representation that enables higher-order metaprogramming. - * More precisely, it's a class with a nested metafunction called "fn" - * Correspondingly, a metafunction class invocation is defined as invocation of its nested "fn" metafunction. - * 3. A 'metafunction operation' is an alias template that represents a function whose instantiation already yields a type. + * Three key concepts are critical to understanding: + * 1. A 'trait' is a class template with a nested `type` typename. + * The term 'trait' might be too narrow or not entirely accurate, however in the STL those class templates are summarized as "Type transformations". + * hence being "transformation type traits". + * It was the traditional way of transforming types before the arrival of alias templates. + * E.g. `template struct x { using type = T; };` + * They are of course still available today, but are rather used as building blocks. + * 2. A 'metafunction' is an alias template for a class template or a nested template expression, whose instantiation yields a type. + * E.g. `template using alias_op_t = typename x::type` + * 3. A 'quoted metafunction' (aka 'metafunction class') is a certain form of metafunction representation that enables higher-order metaprogramming. + * More precisely, it's a class with a nested metafunction called "fn". + * Correspondingly, a quoted metafunction invocation is defined as invocation of its nested "fn" metafunction. * * Conventions: - * - "Fn" is the name for a metafunction template template parameter. - * - "FnCls" is the name for a metafunction class template parameter. - * - "_fn" is a suffix for a type that accepts metafunctions and turns them into metafunction classes. + * - "Fn" is the name of a template template parameter for a metafunction. + * - "Q" is the name of class template parameter for a quoted metafunction. + * - "_fn" is a suffix for a class or alias template that accepts metafunctions and turns them into quoted metafunctions. * - "higher order" denotes a metafunction that operates on another metafunction (i.e. takes it as an argument). */ -#include // std::false_type, std::true_type +#include // std::true_type, std::false_type, std::is_same, std::negation, std::conjunction, std::disjunction +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED +#include +#else +#include +#endif // #include "cxx_universal.h" - +// ::size_t // #include "cxx_type_traits_polyfill.h" +// #include "mpl/conditional.h" + namespace sqlite_orm { namespace internal { namespace mpl { @@ -718,68 +687,58 @@ namespace sqlite_orm { * Determines whether a class template has a nested metafunction `fn`. * * Implementation note: the technique of specialiazing on the inline variable must come first because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION]. + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. */ template - SQLITE_ORM_INLINE_VAR constexpr bool is_metafunction_class_v = false; - template + SQLITE_ORM_INLINE_VAR constexpr bool is_quoted_metafuntion_v = false; + template SQLITE_ORM_INLINE_VAR constexpr bool - is_metafunction_class_v>> = - true; + is_quoted_metafuntion_v>> = true; template - struct is_metafunction_class : polyfill::bool_constant> {}; + struct is_quoted_metafuntion : polyfill::bool_constant> {}; - /* - * Invoke metafunction. + /* + * Type pack. */ - template class Fn, class... Args> - using invoke_fn_t = typename Fn::type; - -#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - template class Op, class... Args> - struct wrap_op { - using type = Op; - }; + template + struct pack {}; /* - * Invoke metafunction operation. + * The indirection through `defer_fn` works around the language inability + * to expand `Args...` into a fixed parameter list of an alias template. * - * Note: legacy compilers need an extra layer of indirection, otherwise type replacement may fail - * if alias template `Op` has a dependent expression in it. - */ - template class Op, class... Args> - using invoke_op_t = typename wrap_op::type; -#else - /* - * Invoke metafunction operation. + * Also, legacy compilers need an extra layer of indirection, otherwise type replacement may fail + * if alias template `Fn` has a dependent expression in it. */ - template class Op, class... Args> - using invoke_op_t = Op; -#endif + template class Fn, class... Args> + struct defer_fn { + using type = Fn; + }; /* - * Invoke metafunction class by invoking its nested metafunction. + * The indirection through `defer` works around the language inability + * to expand `Args...` into a fixed parameter list of an alias template. */ - template - using invoke_t = typename FnCls::template fn::type; + template + struct defer { + using type = typename Q::template fn; + }; /* - * Instantiate metafunction class' nested metafunction. + * Invoke metafunction. */ - template - using instantiate = typename FnCls::template fn; + template class Fn, class... Args> + using invoke_fn_t = typename defer_fn::type; /* - * Wrap given type such that `typename T::type` is valid. + * Invoke quoted metafunction by invoking its nested metafunction. */ - template - struct type_wrap : polyfill::type_identity {}; - template - struct type_wrap> : T {}; + template + using invoke_t = typename defer::type; /* - * Turn metafunction into a metafunction class. + * Turn metafunction into a quoted metafunction. * * Invocation of the nested metafunction `fn` is SFINAE-friendly (detection idiom). * This is necessary because `fn` is a proxy to the originally quoted metafunction, @@ -788,15 +747,18 @@ namespace sqlite_orm { template class Fn> struct quote_fn { template class, class...> - struct invoke_fn; + struct invoke_this_fn { + // error N: 'type': is not a member of any direct or indirect base class of 'quote_fn::invoke_this_fn' + // means that the metafunction cannot be called with the passed arguments. + }; template class F, class... Args> - struct invoke_fn>, F, Args...> { - using type = type_wrap>; + struct invoke_this_fn>, F, Args...> { + using type = F; }; template - using fn = typename invoke_fn::type; + using fn = typename invoke_this_fn::type; }; /* @@ -808,142 +770,202 @@ namespace sqlite_orm { template<> struct higherorder<0u> { + template class Fn, class... Args2> class HigherFn, class Q, class... Args> + struct defer_higher_fn { + using type = HigherFn; + }; + /* - * Turn higher-order metafunction into a metafunction class. + * Turn higher-order metafunction into a quoted metafunction. */ template class Fn, class... Args2> class HigherFn> struct quote_fn { - template - struct fn : HigherFn {}; + template + using fn = typename defer_higher_fn::type; }; }; /* - * Metafunction class that extracts the nested metafunction of its metafunction class argument, - * quotes the extracted metafunction and passes it on to the next metafunction class + * Quoted metafunction that extracts the nested metafunction of its quoted metafunction argument, + * quotes the extracted metafunction and passes it on to the next quoted metafunction * (kind of the inverse of quoting). */ - template + template struct pass_extracted_fn_to { template - struct fn : FnCls::template fn {}; + struct invoke_this_fn { + using type = typename Q::template fn; + }; - // extract, quote, pass on - template class Fn, class... Args> - struct fn> : FnCls::template fn> {}; + // extract class template, quote, pass on + template class Fn, class... T> + struct invoke_this_fn> { + using type = typename Q::template fn>; + }; + + template + using fn = typename invoke_this_fn::type; }; /* - * Metafunction class that invokes the specified metafunction operation, - * and passes its result on to the next metafunction class. + * Quoted metafunction that invokes the specified quoted metafunctions, + * and passes their results on to the next quoted metafunction. */ - template class Op, class FnCls> - struct pass_result_to { - // call Op, pass on its result + template + struct pass_result_of { + // invoke `Fn`, pass on their result template - struct fn : FnCls::template fn> {}; + using fn = typename Q::template fn::type...>; }; /* - * Bind arguments at the front of a metafunction class. - * Metafunction class equivalent to std::bind_front(). + * Quoted metafunction that invokes the specified metafunctions, + * and passes their results on to the next quoted metafunction. + */ + template class... Fn> + using pass_result_of_fn = pass_result_of...>; + + /* + * Bind arguments at the front of a quoted metafunction. */ - template + template struct bind_front { template - struct fn : FnCls::template fn {}; + using fn = typename Q::template fn; }; /* - * Bind arguments at the back of a metafunction class. - * Metafunction class equivalent to std::bind_back() + * Bind arguments at the back of a quoted metafunction. */ - template + template struct bind_back { template - struct fn : FnCls::template fn {}; + using fn = typename Q::template fn; }; /* - * Metafunction class equivalent to polyfill::always_false. - * It ignores arguments passed to the metafunction, - * and always returns the given type. + * Quoted metafunction equivalent to `polyfill::always_false`. + * It ignores arguments passed to the metafunction, and always returns the specified type. */ template struct always { - template - struct fn : type_wrap {}; + template + using fn = T; }; /* - * Unary metafunction class equivalent to std::type_identity. + * Unary quoted metafunction equivalent to `std::type_identity_t`. */ - struct identity { - template - struct fn : type_wrap {}; - }; + using identity = quote_fn; /* - * Metafunction class equivalent to std::negation. + * Quoted metafunction equivalent to `std::negation`. */ - template - struct not_ { - template - struct fn : polyfill::negation> {}; - }; + template + using not_ = pass_result_of, TraitQ>; /* - * Metafunction class equivalent to std::conjunction + * Quoted metafunction equivalent to `std::conjunction`. */ - template + template struct conjunction { template - struct fn : polyfill::conjunction...> {}; + using fn = std::true_type; + }; + + template + struct conjunction { + // match last or `std::false_type` + template + struct invoke_this_fn { + static_assert(std::is_same::value || + std::is_same::value, + "Resulting trait must be a std::bool_constant"); + using type = ResultTrait; + }; + + // match `std::true_type` and one or more remaining + template + struct invoke_this_fn, std::true_type, NextQ, RestQ...> + : invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + RestQ...> {}; + + template + using fn = typename invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + TraitQ...>::type; }; /* - * Metafunction class equivalent to std::disjunction. + * Quoted metafunction equivalent to `std::disjunction`. */ - template + template struct disjunction { template - struct fn : polyfill::disjunction...> {}; + using fn = std::false_type; + }; + + template + struct disjunction { + // match last or `std::true_type` + template + struct invoke_this_fn { + static_assert(std::is_same::value || + std::is_same::value, + "Resulting trait must be a std::bool_constant"); + using type = ResultTrait; + }; + + // match `std::false_type` and one or more remaining + template + struct invoke_this_fn, std::false_type, NextQ, RestQ...> + : invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + RestQ...> {}; + + template + using fn = typename invoke_this_fn, + // access resulting trait::type + typename defer::type::type, + TraitQ...>::type; }; -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION /* - * Metafunction equivalent to std::conjunction. + * Metafunction equivalent to `std::conjunction`. */ template class... TraitFn> - using conjunction_fn = conjunction...>; + using conjunction_fn = pass_result_of_fn, TraitFn...>; /* - * Metafunction equivalent to std::disjunction. + * Metafunction equivalent to `std::disjunction`. */ template class... TraitFn> - using disjunction_fn = disjunction...>; -#else - template class... TraitFn> - struct conjunction_fn : conjunction...> {}; + using disjunction_fn = pass_result_of_fn, TraitFn...>; - template class... TraitFn> - struct disjunction_fn : disjunction...> {}; -#endif + /* + * Metafunction equivalent to `std::negation`. + */ + template class Fn> + using not_fn = pass_result_of_fn, Fn>; /* - * Convenience template alias for binding arguments at the front of a metafunction. + * Bind arguments at the front of a metafunction. */ template class Fn, class... Bound> using bind_front_fn = bind_front, Bound...>; /* - * Convenience template alias for binding arguments at the back of a metafunction. + * Bind arguments at the back of a metafunction. */ template class Fn, class... Bound> using bind_back_fn = bind_back, Bound...>; /* - * Convenience template alias for binding a metafunction at the front of a higher-order metafunction. + * Bind a metafunction and arguments at the front of a higher-order metafunction. */ template class Fn, class... Args2> class HigherFn, template @@ -951,48 +973,222 @@ namespace sqlite_orm { class... Bound> using bind_front_higherorder_fn = bind_front::quote_fn, quote_fn, Bound...>; - } - } - namespace mpl = internal::mpl; +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + constexpr size_t find_first_true_helper(std::initializer_list values) { + size_t i = 0; + for(auto first = values.begin(); first != values.end() && !*first; ++first) { + ++i; + } + return i; + } - // convenience metafunction classes - namespace internal { - /* - * Trait metafunction class that checks if a type has the specified trait. - */ - template class TraitFn> - using check_if = mpl::quote_fn; + constexpr size_t count_true_helper(std::initializer_list values) { + size_t n = 0; + for(auto first = values.begin(); first != values.end(); ++first) { + n += *first; + } + return n; + } +#else + template + constexpr size_t find_first_true_helper(const std::array& values, size_t i = 0) { + return i == N || values[i] ? 0 : 1 + find_first_true_helper(values, i + 1); + } - /* - * Trait metafunction class that checks if a type doesn't have the specified trait. - */ - template class TraitFn> - using check_if_not = mpl::not_>; + template + constexpr size_t count_true_helper(const std::array& values, size_t i = 0) { + return i == N ? 0 : values[i] + count_true_helper(values, i + 1); + } +#endif + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct finds { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`finds` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = find_first_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({PredicateQ::template fn>::value...}); + using type = polyfill::index_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class PredicateFn> + using finds_fn = finds>; + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct counts { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`counts` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = count_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({PredicateQ::template fn>::value...}); + using type = polyfill::index_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class PredicateFn> + using counts_fn = counts>; + + /* + * Quoted metafunction that invokes the specified quoted predicate metafunction on each element of a type list, + * and returns the index constant of the first element for which the predicate returns true. + */ + template + struct contains { + template + struct invoke_this_fn { + static_assert(polyfill::always_false_v, + "`contains` must be invoked with a type list as first argument."); + }; + + template class Pack, class... T, class ProjectQ> + struct invoke_this_fn, ProjectQ> { + // hoist result into `value` [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_NTTP_EXPR] + static constexpr size_t value = + static_cast(count_true_helper +#ifndef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + +#endif + ({TraitQ::template fn>::value...})); + using type = polyfill::bool_constant; + }; + + template + using fn = typename invoke_this_fn::type; + }; + + template class TraitFn> + using contains_fn = contains>; + } + } + + namespace mpl = internal::mpl; + + // convenience quoted metafunctions + namespace internal { + /* + * Quoted trait metafunction that checks if a type has the specified trait. + */ + template class TraitFn, class... Bound> + using check_if = + mpl::conditional_t, mpl::bind_front_fn>; /* - * Trait metafunction class that checks if a type is the same as the specified type. + * Quoted trait metafunction that checks if a type doesn't have the specified trait. + */ + template class TraitFn> + using check_if_not = mpl::not_fn; + + /* + * Quoted trait metafunction that checks if a type is the same as the specified type. + * Commonly used named abbreviation for `check_if`. */ template using check_if_is_type = mpl::bind_front_fn; /* - * Trait metafunction class that checks if a type's template matches the specified template + * Quoted trait metafunction that checks if a type's template matches the specified template * (similar to `is_specialization_of`). */ template class Template> using check_if_is_template = mpl::pass_extracted_fn_to>>; + + /* + * Quoted metafunction that finds the index of the given type in a tuple. + */ + template + using finds_if_has_type = mpl::finds>; + + /* + * Quoted metafunction that finds the index of the given class template in a tuple. + */ + template class Template> + using finds_if_has_template = mpl::finds>; + + /* + * Quoted trait metafunction that counts tuple elements having a given trait. + */ + template class TraitFn> + using counts_if_has = mpl::counts_fn; + + /* + * Quoted trait metafunction that checks whether a tuple contains a type with given trait. + */ + template class TraitFn> + using check_if_has = mpl::contains_fn; + + /* + * Quoted trait metafunction that checks whether a tuple doesn't contain a type with given trait. + */ + template class TraitFn> + using check_if_has_not = mpl::not_>; + + /* + * Quoted metafunction that checks whether a tuple contains given type. + */ + template + using check_if_has_type = mpl::contains>; + + /* + * Quoted metafunction that checks whether a tuple contains a given template. + * + * Note: we are using 2 small tricks: + * 1. A template template parameter can be treated like a metafunction, so we can just "quote" a 'primary' + * template into the MPL system (e.g. `std::vector`). + * 2. This quoted metafunction does the opposite of the trait metafunction `is_specialization`: + * `is_specialization` tries to instantiate the primary template template parameter using the + * template parameters of a template type, then compares both instantiated types. + * Here instead, `pass_extracted_fn_to` extracts the template template parameter from a template type, + * then compares the resulting template template parameters. + */ + template class Template> + using check_if_has_template = mpl::contains>; } } // #include "tuple_helper/same_or_void.h" +#include // std::common_type + namespace sqlite_orm { namespace internal { /** - * Accepts any number of arguments and evaluates `type` alias as T if all arguments are the same or void otherwise + * Accepts any number of arguments and evaluates a nested `type` typename as `T` if all arguments are the same, otherwise `void`. */ template struct same_or_void { @@ -1009,88 +1205,149 @@ namespace sqlite_orm { using type = A; }; + template + using same_or_void_t = typename same_or_void::type; + template struct same_or_void : same_or_void {}; + template + struct common_type_of; + + template class Pack, class... Types> + struct common_type_of> : std::common_type {}; + + /** + * Accepts a pack of types and defines a nested `type` typename to a common type if possible, otherwise nonexistent. + * + * @note: SFINAE friendly like `std::common_type`. + */ + template + using common_type_of_t = typename common_type_of::type; } } // #include "tuple_helper/tuple_traits.h" -#include // std::is_same -#include - // #include "../functional/cxx_type_traits_polyfill.h" // #include "../functional/mpl.h" namespace sqlite_orm { + // convenience metafunction algorithms namespace internal { /* - * Higher-order trait metafunction that checks whether a tuple contains a type with given trait. + * Higher-order trait metafunction that checks whether a tuple contains a type with given trait (possibly projected). + * + * `ProjOp` is a metafunction */ - template class TraitFn, class Tuple> - struct tuple_has {}; - template class TraitFn, class... Types> - struct tuple_has> : polyfill::disjunction...> {}; + template + class TraitFn, + template class ProjOp = polyfill::type_identity_t> + using tuple_has = mpl::invoke_t, Pack, mpl::quote_fn>; /* - * Trait metafunction class that checks whether a tuple contains a type with given trait. + * Higher-order trait metafunction that checks whether a tuple contains the specified type (possibly projected). + * + * `ProjOp` is a metafunction */ - template class TraitFn> - using check_if_tuple_has = mpl::bind_front_higherorder_fn; + template class ProjOp = polyfill::type_identity_t> + using tuple_has_type = mpl::invoke_t, Pack, mpl::quote_fn>; + + /* + * Higher-order trait metafunction that checks whether a tuple contains the specified class template (possibly projected). + * + * `ProjOp` is a metafunction + */ + template + class Template, + template class ProjOp = polyfill::type_identity_t> + using tuple_has_template = mpl::invoke_t, Pack, mpl::quote_fn>; /* - * Trait metafunction class that checks whether a tuple doesn't contain a type with given trait. + * Higher-order metafunction returning the first index constant of the desired type in a tuple (possibly projected). */ - template class TraitFn> - using check_if_tuple_has_not = mpl::not_>; + template class ProjOp = polyfill::type_identity_t> + using find_tuple_type = mpl::invoke_t, Pack, mpl::quote_fn>; /* - * Metafunction class that checks whether a tuple contains given type. + * Higher-order metafunction returning the first index constant of the desired class template in a tuple (possibly projected). + * + * `ProjOp` is a metafunction */ - template - using check_if_tuple_has_type = mpl::bind_front_higherorder_fn::template fn>; + template + class Template, + template class ProjOp = polyfill::type_identity_t> + using find_tuple_template = mpl::invoke_t, Pack, mpl::quote_fn>; /* - * Metafunction class that checks whether a tuple contains a given template. - * - * Note: we are using 2 small tricks: - * 1. A template template parameter can be treated like a metafunction, so we can just "quote" a 'primary' - * template into the MPL system (e.g. `std::vector`). - * 2. This metafunction class does the opposite of the trait function `is_specialization`: - * `is_specialization` tries to instantiate the primary template template parameter using the - * template parameters of a template type, then compares both instantiated types. - * Here instead, `pass_extracted_fn_to` extracts the template template parameter from a template type, - * then compares the resulting template template parameters. + * Higher-order trait metafunction that counts the types having the specified trait in a tuple (possibly projected). + * + * `Pred` is a predicate metafunction with a nested bool member named `value` + * `ProjOp` is a metafunction */ - template class Primary> - using check_if_tuple_has_template = - mpl::bind_front_higherorder_fn::template fn>; + template class Pred, template class ProjOp = polyfill::type_identity_t> + using count_tuple = mpl::invoke_t, Pack, mpl::quote_fn>; } } + // #include "tuple_helper/tuple_filter.h" #include // std::integral_constant, std::index_sequence, std::conditional, std::declval #include // std::tuple // #include "../functional/cxx_universal.h" +// ::size_t +// #include "../functional/mpl/conditional.h" // #include "../functional/index_sequence_util.h" -#include // std::index_sequence, std::make_index_sequence +#include // std::index_sequence // #include "../functional/cxx_universal.h" +// ::size_t namespace sqlite_orm { namespace internal { +#if defined(SQLITE_ORM_PACK_INDEXING_SUPPORTED) + /** + * Get the index value of an `index_sequence` at a specific position. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + return Idx...[Pos]; + } +#elif defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) /** - * Get the first value of an index_sequence. + * Get the index value of an `index_sequence` at a specific position. */ - template - SQLITE_ORM_CONSTEVAL size_t first_index_sequence_value(std::index_sequence) { + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + static_assert(Pos < sizeof...(Idx)); +#ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED + size_t result; +#else + size_t result = 0; +#endif + size_t i = 0; + // note: `(void)` cast silences warning 'expression result unused' + (void)((result = Idx, i++ == Pos) || ...); + return result; + } +#else + /** + * Get the index value of an `index_sequence` at a specific position. + * `Pos` must always be `0`. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value_at(std::index_sequence) { + static_assert(Pos == 0, ""); return I; } +#endif template struct flatten_idxseq { @@ -1139,7 +1396,7 @@ namespace sqlite_orm { #ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION template class Pred, template class Proj, size_t... Idx> struct filter_tuple_sequence> - : flatten_idxseq>>::value, + : flatten_idxseq>>::value, std::index_sequence, std::index_sequence<>>...> {}; #else @@ -1155,16 +1412,26 @@ namespace sqlite_orm { template class Pred, template class Proj, size_t... Idx> struct filter_tuple_sequence> - : flatten_idxseq>, Pred>::type...> {}; + : flatten_idxseq>, + Pred>::type...> {}; #endif + /* + * `Pred` is a metafunction that defines a bool member named `value` + * `FilterProj` is a metafunction + */ template class Pred, - template class Proj = polyfill::type_identity_t, + template class FilterProj = polyfill::type_identity_t, class Seq = std::make_index_sequence::value>> - using filter_tuple_sequence_t = typename filter_tuple_sequence::type; + using filter_tuple_sequence_t = typename filter_tuple_sequence::type; + /* + * `Pred` is a metafunction that defines a bool member named `value` + * `FilterProj` is a metafunction + */ template class Pred, @@ -1172,16 +1439,13 @@ namespace sqlite_orm { class Seq = std::make_index_sequence::value>> using filter_tuple_t = tuple_from_index_sequence_t>; - template - class Pred, - template class FilterProj = polyfill::type_identity_t> - struct count_tuple : std::integral_constant::size()> {}; - /* * Count a tuple, picking only those elements specified in the index sequence. * - * Implementation note: must be distinct from `count_tuple` because legacy compilers have problems + * `Pred` is a metafunction that defines a bool member named `value` + * `FilterProj` is a metafunction + * + * Implementation note: must be distinct from a `count_tuple` w/o index sequence parameter because legacy compilers have problems * with a default Sequence in function template parameters [SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION]. */ template +#include // std::error_code, std::system_error +#include // std::string +#include +#include // std::ostringstream +#include namespace sqlite_orm { - namespace internal { - - template - struct column_pointer; - - template - struct indexed_column_t; + /** @short Enables classifying sqlite error codes. - /** - * Trait class used to define table mapped type by setter/getter/member - * T - member pointer - * `type` is a type which is mapped. - * E.g. - * - `table_type_of::type` is `User` - * - `table_type_of::type` is `User` - * - `table_type_of::type` is `User` - */ - template - struct table_type_of; + @note We don't bother listing all possible values; + this also allows for compatibility with + 'Construction rules for enum class values (P0138R2)' + */ + enum class sqlite_errc {}; - template - struct table_type_of { - using type = O; - }; + enum class orm_error_code { + not_found = 1, + type_is_not_mapped_to_storage, + trying_to_dereference_null_iterator, + too_many_tables_specified, + incorrect_set_fields_specified, + column_not_found, + table_has_no_primary_key_column, + cannot_start_a_transaction_within_a_transaction, + no_active_transaction, + incorrect_journal_mode_string, + invalid_collate_argument_enum, + failed_to_init_a_backup, + unknown_member_value, + incorrect_order, + cannot_use_default_value, + arguments_count_does_not_match, + function_not_found, + index_is_out_of_bounds, + value_is_null, + no_tables_specified, + }; - template - struct table_type_of> { - using type = T; - }; +} - template - struct table_type_of> { - using type = typename table_type_of::type; - }; +namespace std { + template<> + struct is_error_code_enum<::sqlite_orm::sqlite_errc> : true_type {}; - template - using table_type_of_t = typename table_type_of::type; - } + template<> + struct is_error_code_enum<::sqlite_orm::orm_error_code> : true_type {}; } -// #include "type_printer.h" - namespace sqlite_orm { - namespace internal { - - /** - * AUTOINCREMENT constraint class. - */ - struct autoincrement_t {}; - - enum class conflict_clause_t { - rollback, - abort, - fail, - ignore, - replace, - }; - - struct primary_key_base { - enum class order_by { - unspecified, - ascending, - descending, - }; - struct { - order_by asc_option = order_by::unspecified; - conflict_clause_t conflict_clause = conflict_clause_t::rollback; - bool conflict_clause_is_on = false; - } options; - }; - - template - struct primary_key_with_autoincrement { - using primary_key_type = T; - - primary_key_type primary_key; - - primary_key_with_autoincrement(primary_key_type primary_key_) : primary_key(primary_key_) {} - }; + class orm_error_category : public std::error_category { + public: + const char* name() const noexcept override final { + return "ORM error"; + } - /** - * PRIMARY KEY constraint class. - * Cs is parameter pack which contains columns (member pointers and/or function pointers). Can be empty when - * used within `make_column` function. - */ - template - struct primary_key_t : primary_key_base { + std::string message(int c) const override final { + switch(static_cast(c)) { + case orm_error_code::not_found: + return "Not found"; + case orm_error_code::type_is_not_mapped_to_storage: + return "Type is not mapped to storage"; + case orm_error_code::trying_to_dereference_null_iterator: + return "Trying to dereference null iterator"; + case orm_error_code::too_many_tables_specified: + return "Too many tables specified"; + case orm_error_code::incorrect_set_fields_specified: + return "Incorrect set fields specified"; + case orm_error_code::column_not_found: + return "Column not found"; + case orm_error_code::table_has_no_primary_key_column: + return "Table has no primary key column"; + case orm_error_code::cannot_start_a_transaction_within_a_transaction: + return "Cannot start a transaction within a transaction"; + case orm_error_code::no_active_transaction: + return "No active transaction"; + case orm_error_code::invalid_collate_argument_enum: + return "Invalid collate_argument enum"; + case orm_error_code::failed_to_init_a_backup: + return "Failed to init a backup"; + case orm_error_code::unknown_member_value: + return "Unknown member value"; + case orm_error_code::incorrect_order: + return "Incorrect order"; + case orm_error_code::cannot_use_default_value: + return "The statement 'INSERT INTO * DEFAULT VALUES' can be used with only one row"; + case orm_error_code::arguments_count_does_not_match: + return "Arguments count does not match"; + case orm_error_code::function_not_found: + return "Function not found"; + case orm_error_code::index_is_out_of_bounds: + return "Index is out of bounds"; + case orm_error_code::value_is_null: + return "Value is null"; + case orm_error_code::no_tables_specified: + return "No tables specified"; + default: + return "unknown error"; + } + } + }; + + class sqlite_error_category : public std::error_category { + public: + const char* name() const noexcept override final { + return "SQLite error"; + } + + std::string message(int c) const override final { + return sqlite3_errstr(c); + } + }; + + inline const orm_error_category& get_orm_error_category() { + static orm_error_category res; + return res; + } + + inline const sqlite_error_category& get_sqlite_error_category() { + static sqlite_error_category res; + return res; + } + + inline std::error_code make_error_code(sqlite_errc ev) noexcept { + return {static_cast(ev), get_sqlite_error_category()}; + } + + inline std::error_code make_error_code(orm_error_code ev) noexcept { + return {static_cast(ev), get_orm_error_category()}; + } + + template + std::string get_error_message(sqlite3* db, T&&... args) { + std::ostringstream stream; + using unpack = int[]; + (void)unpack{0, (stream << args, 0)...}; + stream << sqlite3_errmsg(db); + return stream.str(); + } + + template + [[noreturn]] void throw_error(sqlite3* db, T&&... args) { + throw std::system_error{sqlite_errc(sqlite3_errcode(db)), get_error_message(db, std::forward(args)...)}; + } + + inline std::system_error sqlite_to_system_error(int ev) { + return {sqlite_errc(ev)}; + } + + inline std::system_error sqlite_to_system_error(sqlite3* db) { + return {sqlite_errc(sqlite3_errcode(db)), sqlite3_errmsg(db)}; + } + + [[noreturn]] inline void throw_translated_sqlite_error(int ev) { + throw sqlite_to_system_error(ev); + } + + [[noreturn]] inline void throw_translated_sqlite_error(sqlite3* db) { + throw sqlite_to_system_error(db); + } + + [[noreturn]] inline void throw_translated_sqlite_error(sqlite3_stmt* stmt) { + throw sqlite_to_system_error(sqlite3_db_handle(stmt)); + } +} + +// #include "table_type_of.h" + +#include // std::enable_if, std::is_convertible + +namespace sqlite_orm { + + namespace internal { + + template + struct column_pointer; + + template + struct indexed_column_t; + + /** + * Trait class used to define table mapped type by setter/getter/member + * T - member pointer + * `type` is a type which is mapped. + * E.g. + * - `table_type_of::type` is `User` + * - `table_type_of::type` is `User` + * - `table_type_of::type` is `User` + * - `table_type_of(&User::id))>::type` is `User` + * - `table_type_of*&User::id)>::type` is `User` + */ + template + struct table_type_of; + + template + struct table_type_of { + using type = O; + }; + + template + struct table_type_of> { + using type = T; + }; + + template + struct table_type_of> : table_type_of {}; + + template + using table_type_of_t = typename table_type_of::type; + + /* + * This trait can be used to check whether the object type of a member pointer or column pointer matches the target type. + * + * One use case is the ability to create column reference to an aliased table column of a derived object field without explicitly using a column pointer. + * E.g. + * regular: `alias_column>(column(&Base::field))` + * short: `alias_column>(&Base::field)` + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_field_of_v = false; + + /* + * `true` if a pointer-to-member of Base is convertible to a pointer-to-member of Derived. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_field_of_v::value>> = true; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_field_of_v, T, void> = true; + } +} + +// #include "type_printer.h" + +#include // std::string +#include // std::shared_ptr, std::unique_ptr +#include // std::vector +// #include "functional/cxx_optional.h" + +// #include "cxx_core_features.h" + +#if SQLITE_ORM_HAS_INCLUDE() +#include +#endif + +#if __cpp_lib_optional >= 201606L +#define SQLITE_ORM_OPTIONAL_SUPPORTED +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "type_traits.h" + +// #include "is_std_ptr.h" + +#include +#include + +namespace sqlite_orm { + + /** + * Specialization for optional type (std::shared_ptr / std::unique_ptr). + */ + template + struct is_std_ptr : std::false_type {}; + + template + struct is_std_ptr> : std::true_type { + using element_type = typename std::shared_ptr::element_type; + + static std::shared_ptr make(std::remove_cv_t&& v) { + return std::make_shared(std::move(v)); + } + }; + + template + struct is_std_ptr> : std::true_type { + using element_type = typename std::unique_ptr::element_type; + + static auto make(std::remove_cv_t&& v) { + return std::make_unique(std::move(v)); + } + }; +} + +namespace sqlite_orm { + + /** + * This class transforms a C++ type to a sqlite type name (int -> INTEGER, ...) + */ + template + struct type_printer {}; + + struct integer_printer { + const std::string& print() const { + static const std::string res = "INTEGER"; + return res; + } + }; + + struct text_printer { + const std::string& print() const { + static const std::string res = "TEXT"; + return res; + } + }; + + struct real_printer { + const std::string& print() const { + static const std::string res = "REAL"; + return res; + } + }; + + struct blob_printer { + const std::string& print() const { + static const std::string res = "BLOB"; + return res; + } + }; + + // Note: char, unsigned/signed char are used for storing integer values, not char values. + template + struct type_printer>, + std::is_integral>::value>> : integer_printer { + }; + + template + struct type_printer::value>> : real_printer {}; + + template + struct type_printer, + std::is_base_of, + std::is_base_of>::value>> + : text_printer {}; + + template + struct type_printer::value>> : type_printer {}; + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + template + struct type_printer>> + : type_printer {}; +#endif + + template<> + struct type_printer, void> : blob_printer {}; +} + +namespace sqlite_orm { + + namespace internal { + + enum class conflict_clause_t { + rollback, + abort, + fail, + ignore, + replace, + }; + + struct primary_key_base { + enum class order_by { + unspecified, + ascending, + descending, + }; + struct { + order_by asc_option = order_by::unspecified; + conflict_clause_t conflict_clause = conflict_clause_t::rollback; + bool conflict_clause_is_on = false; + } options; + }; + + template + struct primary_key_with_autoincrement : T { + using primary_key_type = T; + + const primary_key_type& as_base() const { + return *this; + } +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + primary_key_with_autoincrement(primary_key_type primary_key) : primary_key_type{primary_key} {} +#endif + }; + + /** + * PRIMARY KEY constraint class. + * Cs is parameter pack which contains columns (member pointers and/or function pointers). Can be empty when + * used within `make_column` function. + */ + template + struct primary_key_t : primary_key_base { using self = primary_key_t; using order_by = primary_key_base::order_by; using columns_tuple = std::tuple; columns_tuple columns; - primary_key_t(decltype(columns) columns) : columns(std::move(columns)) {} + primary_key_t(columns_tuple columns) : columns(std::move(columns)) {} self asc() const { auto res = *this; @@ -1370,6 +1946,34 @@ namespace sqlite_orm { unique_t(columns_tuple columns_) : columns(std::move(columns_)) {} }; + struct unindexed_t {}; + + template + struct prefix_t { + using value_type = T; + + value_type value; + }; + + template + struct tokenize_t { + using value_type = T; + + value_type value; + }; + + template + struct content_t { + using value_type = T; + + value_type value; + }; + + template + struct table_content_t { + using mapped_type = T; + }; + /** * DEFAULT constraint class. * T is a value type. @@ -1386,7 +1990,6 @@ namespace sqlite_orm { }; #if SQLITE_VERSION_NUMBER >= 3006019 - /** * FOREIGN KEY constraint class. * Cs are columns which has foreign key @@ -1524,12 +2127,12 @@ namespace sqlite_orm { /** * Holds obect type of all referenced columns. */ - using target_type = typename same_or_void...>::type; + using target_type = same_or_void_t...>; /** * Holds obect type of all source columns. */ - using source_type = typename same_or_void...>::type; + using source_type = same_or_void_t...>; columns_type columns; references_type references; @@ -1577,7 +2180,7 @@ namespace sqlite_orm { template foreign_key_t, std::tuple> references(Rs... refs) { - return {std::move(this->columns), std::make_tuple(std::forward(refs)...)}; + return {std::move(this->columns), {std::forward(refs)...}}; } }; #endif @@ -1619,14 +2222,17 @@ namespace sqlite_orm { stored, }; +#if SQLITE_VERSION_NUMBER >= 3031000 bool full = true; storage_type storage = storage_type::not_specified; +#endif #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED basic_generated_always(bool full, storage_type storage) : full{full}, storage{storage} {} #endif }; +#if SQLITE_VERSION_NUMBER >= 3031000 template struct generated_always_t : basic_generated_always { using expression_type = T; @@ -1644,72 +2250,69 @@ namespace sqlite_orm { return {std::move(this->expression), this->full, storage_type::stored}; } }; +#endif + + struct null_t {}; + struct not_null_t {}; } namespace internal { template - SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = polyfill::is_specialization_of_v; - - template - using is_foreign_key = polyfill::bool_constant>; - - template - struct is_primary_key : std::false_type {}; - - template - struct is_primary_key> : std::true_type {}; - - template - struct is_primary_key> : std::true_type {}; + SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = +#if SQLITE_VERSION_NUMBER >= 3006019 + polyfill::is_specialization_of::value; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = is_primary_key::value; + struct is_foreign_key : polyfill::bool_constant> {}; template - using is_generated_always = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = std::is_base_of::value; template - SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always::value; + struct is_primary_key : polyfill::bool_constant> {}; template - using is_autoincrement = std::is_same; + SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = +#if SQLITE_VERSION_NUMBER >= 3031000 + polyfill::is_specialization_of::value; +#else + false; +#endif template - SQLITE_ORM_INLINE_VAR constexpr bool is_autoincrement_v = is_autoincrement::value; + struct is_generated_always : polyfill::bool_constant> {}; /** * PRIMARY KEY INSERTABLE traits. */ - template + template struct is_primary_key_insertable : polyfill::disjunction< - mpl::instantiate, - check_if_tuple_has_template, - check_if_tuple_has_template>, - constraints_type_t>, - std::is_base_of>>> { + mpl::invoke_t, + check_if_has_template>, + constraints_type_t>, + std::is_base_of>>> { - static_assert(tuple_has>::value, "an unexpected type was passed"); + static_assert(tuple_has, is_primary_key>::value, + "an unexpected type was passed"); }; template - using is_constraint = - mpl::instantiate, - check_if, - check_if, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_type, -#if SQLITE_VERSION_NUMBER >= 3031000 - check_if, -#endif - // dummy tail because of SQLITE_VERSION_NUMBER checks above - mpl::always>, - T>; + using is_column_constraint = mpl::invoke_t>, + check_if_is_type, + check_if_is_type, + check_if_is_type>, + check_if_is_template, + check_if_is_template, + check_if_is_type, + check_if, + check_if_is_type>, + T>; } #if SQLITE_VERSION_NUMBER >= 3031000 @@ -1723,126 +2326,415 @@ namespace sqlite_orm { return {std::move(expression), false, internal::basic_generated_always::storage_type::not_specified}; } #endif -#if SQLITE_VERSION_NUMBER >= 3006019 +#if SQLITE_VERSION_NUMBER >= 3006019 /** * FOREIGN KEY constraint construction function that takes member pointer as argument * Available in SQLite 3.6.19 or higher */ template internal::foreign_key_intermediate_t foreign_key(Cs... columns) { - return {std::make_tuple(std::forward(columns)...)}; + return {{std::forward(columns)...}}; } #endif /** - * UNIQUE constraint builder function. + * UNIQUE table constraint builder function. */ template internal::unique_t unique(Args... args) { - return {std::make_tuple(std::forward(args)...)}; + return {{std::forward(args)...}}; } + /** + * UNIQUE column constraint builder function. + */ inline internal::unique_t<> unique() { return {{}}; } +#if SQLITE_VERSION_NUMBER >= 3009000 + /** + * UNINDEXED column constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#the_unindexed_column_option + */ + inline internal::unindexed_t unindexed() { + return {}; + } + + /** + * prefix=N table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#prefix_indexes + */ + template + internal::prefix_t prefix(T value) { + return {std::move(value)}; + } + + /** + * tokenize='...'' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#tokenizers + */ + template + internal::tokenize_t tokenize(T value) { + return {std::move(value)}; + } + + /** + * content='' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#contentless_tables + */ + template + internal::content_t content(T value) { + return {std::move(value)}; + } + /** - * AUTOINCREMENT keyword. [Deprecation notice] Use `primary_key().autoincrement()` instead of using this function. - * This function will be removed in 1.9 + * content='table' table constraint builder function. Used in FTS virtual tables. + * + * https://www.sqlite.org/fts5.html#external_content_tables */ - [[deprecated("Use primary_key().autoincrement()` instead")]] inline internal::autoincrement_t autoincrement() { + template + internal::table_content_t content() { return {}; } +#endif + /** + * PRIMARY KEY table constraint builder function. + */ template internal::primary_key_t primary_key(Cs... cs) { - return {std::make_tuple(std::forward(cs)...)}; + return {{std::forward(cs)...}}; + } + + /** + * PRIMARY KEY column constraint builder function. + */ + inline internal::primary_key_t<> primary_key() { + return {{}}; + } + + template + internal::default_t default_value(T t) { + return {std::move(t)}; + } + + inline internal::collate_constraint_t collate_nocase() { + return {internal::collate_argument::nocase}; + } + + inline internal::collate_constraint_t collate_binary() { + return {internal::collate_argument::binary}; + } + + inline internal::collate_constraint_t collate_rtrim() { + return {internal::collate_argument::rtrim}; + } + + template + 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 + +#include // std::false_type, std::true_type, std::enable_if +#include // std::shared_ptr, std::unique_ptr +// #include "functional/cxx_optional.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm { + + /** + * This is class that tells `sqlite_orm` that type is nullable. Nullable types + * are mapped to sqlite database as `NULL` and not-nullable are mapped as `NOT NULL`. + * Default nullability status for all types is `NOT NULL`. So if you want to map + * custom type as `NULL` (for example: boost::optional) you have to create a specialization + * of `type_is_nullable` for your type and derive from `std::true_type`. + */ + template + struct type_is_nullable : std::false_type { + bool operator()(const T&) const { + return true; + } + }; + + /** + * This is a specialization for std::shared_ptr, std::unique_ptr, std::optional, which are nullable in sqlite_orm. + */ + template + struct type_is_nullable, +#endif + polyfill::is_specialization_of, + polyfill::is_specialization_of>::value>> : std::true_type { + bool operator()(const T& t) const { + return static_cast(t); + } + }; +} +#pragma once + +#include // std::false_type, std::true_type +#include // std::move + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "is_base_of_template.h" + +#include // std::true_type, std::false_type, std::declval + +namespace sqlite_orm { + + namespace internal { + + /* + * This is because of bug in MSVC, for more information, please visit + * https://stackoverflow.com/questions/34672441/stdis-base-of-for-template-classes/34672753#34672753 + */ +#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION + template class Base> + struct is_base_of_template_impl { + template + static constexpr std::true_type test(const Base&); + + static constexpr std::false_type test(...); + }; + + template class C> + using is_base_of_template = decltype(is_base_of_template_impl::test(std::declval())); +#else + template class C, typename... Ts> + std::true_type is_base_of_template_impl(const C&); + + template class C> + std::false_type is_base_of_template_impl(...); + + template class C> + using is_base_of_template = decltype(is_base_of_template_impl(std::declval())); +#endif + + template class C> + SQLITE_ORM_INLINE_VAR constexpr bool is_base_of_template_v = is_base_of_template::value; } +} + +// #include "tags.h" + +// #include "functional/cxx_functional_polyfill.h" + +#include +#if __cpp_lib_invoke < 201411L +#include // std::enable_if, std::is_member_object_pointer, std::is_member_function_pointer +#endif +#include // std::forward + +// #include "cxx_type_traits_polyfill.h" + +// #include "../member_traits/member_traits.h" + +#include // std::enable_if, std::is_function, std::true_type, std::false_type + +// #include "../functional/cxx_universal.h" + +// #include "../functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm { + namespace internal { + // SFINAE friendly trait to get a member object pointer's field type + template + struct object_field_type {}; + + template + using object_field_type_t = typename object_field_type::type; + + template + struct object_field_type : std::enable_if::value, F> {}; + + // SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified return type) + template + struct getter_field_type {}; + + template + using getter_field_type_t = typename getter_field_type::type; + + template + struct getter_field_type : getter_field_type {}; + + template + struct getter_field_type : polyfill::remove_cvref {}; + + template + struct getter_field_type : polyfill::remove_cvref {}; + +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct getter_field_type : polyfill::remove_cvref {}; + + template + struct getter_field_type : polyfill::remove_cvref {}; +#endif + + // SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified parameter type) + template + struct setter_field_type {}; + + template + using setter_field_type_t = typename setter_field_type::type; + + template + struct setter_field_type : setter_field_type {}; + + template + struct setter_field_type : polyfill::remove_cvref {}; + +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct setter_field_type : polyfill::remove_cvref {}; +#endif + + template + struct is_getter : std::false_type {}; + template + struct is_getter>> : std::true_type {}; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_getter_v = is_getter::value; - inline internal::primary_key_t<> primary_key() { - return {{}}; - } + template + struct is_setter : std::false_type {}; + template + struct is_setter>> : std::true_type {}; - template - internal::default_t default_value(T t) { - return {std::move(t)}; - } + template + SQLITE_ORM_INLINE_VAR constexpr bool is_setter_v = is_setter::value; - inline internal::collate_constraint_t collate_nocase() { - return {internal::collate_argument::nocase}; - } + template + struct member_field_type : object_field_type, getter_field_type, setter_field_type {}; - inline internal::collate_constraint_t collate_binary() { - return {internal::collate_argument::binary}; - } + template + using member_field_type_t = typename member_field_type::type; - inline internal::collate_constraint_t collate_rtrim() { - return {internal::collate_argument::rtrim}; - } + template + struct member_object_type {}; - template - internal::check_t check(T t) { - return {std::move(t)}; + template + struct member_object_type : polyfill::type_identity {}; + + template + using member_object_type_t = typename member_object_type::type; } } -#pragma once -#include // std::false_type, std::true_type, std::enable_if -#include // std::shared_ptr, std::unique_ptr -// #include "functional/cxx_optional.h" +namespace sqlite_orm { + namespace internal { + namespace polyfill { + // C++20 or later (unfortunately there's no feature test macro). + // Stupidly, clang says C++20, but `std::identity` was only implemented in libc++ 13 and libstd++-v3 10 + // (the latter is used on Linux). + // gcc got it right and reports C++20 only starting with v10. + // The check here doesn't care and checks the library versions in use. + // + // Another way of detection would be the constrained algorithms feature-test macro __cpp_lib_ranges +#if(__cplusplus >= 202002L) && \ + ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13000) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) + using std::identity; +#else + struct identity { + template + constexpr T&& operator()(T&& v) const noexcept { + return std::forward(v); + } -// #include "functional/cxx_type_traits_polyfill.h" + using is_transparent = int; + }; +#endif -namespace sqlite_orm { +#if __cpp_lib_invoke >= 201411L + using std::invoke; +#else + // pointer-to-data-member+object + template, + std::enable_if_t::value, bool> = true> + decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) { + return std::forward(object).*callable; + } - /** - * This is class that tells `sqlite_orm` that type is nullable. Nullable types - * are mapped to sqlite database as `NULL` and not-nullable are mapped as `NOT NULL`. - * Default nullability status for all types is `NOT NULL`. So if you want to map - * custom type as `NULL` (for example: boost::optional) you have to create a specialiation - * of type_is_nullable for your type and derive from `std::true_type`. - */ - template - struct type_is_nullable : std::false_type { - bool operator()(const T&) const { - return true; - } - }; + // pointer-to-member-function+object + template, + std::enable_if_t::value, bool> = true> + decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) { + return (std::forward(object).*callable)(std::forward(args)...); + } - /** - * This is a specialization for std::shared_ptr, std::unique_ptr, std::optional, which are nullable in sqlite_orm. - */ - template - struct type_is_nullable, + // pointer-to-member+reference-wrapped object (expect `reference_wrapper::*`) + template>, + std::reference_wrapper>>::value, + bool> = true> + decltype(auto) invoke(Callable&& callable, std::reference_wrapper wrapper, Args&&... args) { + return invoke(std::forward(callable), wrapper.get(), std::forward(args)...); + } + + // functor + template + decltype(auto) invoke(Callable&& callable, Args&&... args) { + return std::forward(callable)(std::forward(args)...); + } #endif - polyfill::is_specialization_of, - polyfill::is_specialization_of>>> : std::true_type { - bool operator()(const T& t) const { - return static_cast(t); } - }; + } + namespace polyfill = internal::polyfill; } -#pragma once - -#include // std::false_type, std::true_type -#include // std::move -// #include "functional/cxx_optional.h" - -// #include "tags.h" namespace sqlite_orm { namespace internal { struct negatable_t {}; + /** + * Inherit from this class to support arithmetic types overloading + */ + struct arithmetic_t {}; + /** * Inherit from this class if target class can be chained with other conditions with '&&' and '||' operators */ struct condition_t {}; + + /** + * Specialize if a type participates as an argument to overloaded operators (arithmetic, conditional, negation, chaining) + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_operator_argument_v = false; + + template + using is_operator_argument = polyfill::bool_constant>; } } @@ -1880,11 +2772,6 @@ namespace sqlite_orm { namespace internal { - /** - * Inherit this class to support arithmetic types overloading - */ - struct arithmetic_t {}; - template struct binary_operator : Ds... { using left_type = L; @@ -1896,6 +2783,12 @@ namespace sqlite_orm { binary_operator(left_type lhs_, right_type rhs_) : lhs(std::move(lhs_)), rhs(std::move(rhs_)) {} }; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_binary_operator_v = is_base_of_template::value; + + template + using is_binary_operator = polyfill::bool_constant>; + struct conc_string { serialize_result_type serialize() const { return "||"; @@ -2057,10 +2950,6 @@ namespace sqlite_orm { */ template struct is_assign_t> : public std::true_type {}; - - template - struct in_t; - } /** @@ -2097,169 +2986,77 @@ namespace sqlite_orm { } /** - * Public interface for / operator. Example: `select(div(&User::salary, 3));` => SELECT salary / 3 FROM users - * @note Please notice that ::div function already exists in pure C standard library inside header. - * If you use `using namespace sqlite_orm` directive you an specify which `div` you call explicitly using `::div` or `sqlite_orm::div` statements. - */ - template - internal::div_t div(L l, R r) { - return {std::move(l), std::move(r)}; - } - - /** - * Public interface for % operator. Example: `select(mod(&User::age, 5));` => SELECT age % 5 FROM users - */ - template - internal::mod_t mod(L l, R r) { - return {std::move(l), std::move(r)}; - } - - template - internal::bitwise_shift_left_t bitwise_shift_left(L l, R r) { - return {std::move(l), std::move(r)}; - } - - template - internal::bitwise_shift_right_t bitwise_shift_right(L l, R r) { - return {std::move(l), std::move(r)}; - } - - template - internal::bitwise_and_t bitwise_and(L l, R r) { - return {std::move(l), std::move(r)}; - } - - template - internal::bitwise_or_t bitwise_or(L l, R r) { - return {std::move(l), std::move(r)}; - } - - template - internal::bitwise_not_t bitwise_not(T t) { - return {std::move(t)}; - } - - template - internal::assign_t assign(L l, R r) { - return {std::move(l), std::move(r)}; - } - -} -#pragma once - -#include // std::tuple -#include // std::string -#include // std::unique_ptr -#include // std::is_same, std::is_member_object_pointer - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "tuple_helper/tuple_traits.h" - -// #include "tuple_helper/tuple_filter.h" - -// #include "type_traits.h" - -// #include "member_traits/member_traits.h" - -#include // std::enable_if, std::is_function, std::true_type, std::false_type - -// #include "../functional/cxx_universal.h" - -// #include "../functional/cxx_type_traits_polyfill.h" - -namespace sqlite_orm { - namespace internal { - // SFINAE friendly trait to get a member object pointer's field type - template - struct object_field_type {}; - - template - using object_field_type_t = typename object_field_type::type; - - template - struct object_field_type : std::enable_if::value, F> {}; - - // SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified return type) - template - struct getter_field_type {}; - - template - using getter_field_type_t = typename getter_field_type::type; - - template - struct getter_field_type : getter_field_type {}; - - template - struct getter_field_type : polyfill::remove_cvref {}; - - template - struct getter_field_type : polyfill::remove_cvref {}; - -#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED - template - struct getter_field_type : polyfill::remove_cvref {}; + * Public interface for / operator. Example: `select(div(&User::salary, 3));` => SELECT salary / 3 FROM users + * @note Please notice that ::div function already exists in pure C standard library inside header. + * If you use `using namespace sqlite_orm` directive you an specify which `div` you call explicitly using `::div` or `sqlite_orm::div` statements. + */ + template + internal::div_t div(L l, R r) { + return {std::move(l), std::move(r)}; + } - template - struct getter_field_type : polyfill::remove_cvref {}; -#endif + /** + * Public interface for % operator. Example: `select(mod(&User::age, 5));` => SELECT age % 5 FROM users + */ + template + internal::mod_t mod(L l, R r) { + return {std::move(l), std::move(r)}; + } - // SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified parameter type) - template - struct setter_field_type {}; + template + internal::bitwise_shift_left_t bitwise_shift_left(L l, R r) { + return {std::move(l), std::move(r)}; + } - template - using setter_field_type_t = typename setter_field_type::type; + template + internal::bitwise_shift_right_t bitwise_shift_right(L l, R r) { + return {std::move(l), std::move(r)}; + } - template - struct setter_field_type : setter_field_type {}; + template + internal::bitwise_and_t bitwise_and(L l, R r) { + return {std::move(l), std::move(r)}; + } - template - struct setter_field_type : polyfill::remove_cvref {}; + template + internal::bitwise_or_t bitwise_or(L l, R r) { + return {std::move(l), std::move(r)}; + } -#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED - template - struct setter_field_type : polyfill::remove_cvref {}; -#endif + template + internal::bitwise_not_t bitwise_not(T t) { + return {std::move(t)}; + } - template - struct is_getter : std::false_type {}; - template - struct is_getter>> : std::true_type {}; + template + internal::assign_t assign(L l, R r) { + return {std::move(l), std::move(r)}; + } - template - SQLITE_ORM_INLINE_VAR constexpr bool is_getter_v = is_getter::value; +} +#pragma once - template - struct is_setter : std::false_type {}; - template - struct is_setter>> : std::true_type {}; +#include // std::tuple +#include // std::string +#include // std::unique_ptr +#include // std::is_same, std::is_member_object_pointer +#include // std::move - template - SQLITE_ORM_INLINE_VAR constexpr bool is_setter_v = is_setter::value; +// #include "../functional/cxx_universal.h" - template - struct member_field_type : object_field_type, getter_field_type, setter_field_type {}; +// #include "../functional/cxx_type_traits_polyfill.h" - template - using member_field_type_t = typename member_field_type::type; +// #include "../tuple_helper/tuple_traits.h" - template - struct member_object_type {}; +// #include "../tuple_helper/tuple_filter.h" - template - struct member_object_type : polyfill::type_identity {}; +// #include "../type_traits.h" - template - using member_object_type_t = typename member_object_type::type; - } -} +// #include "../member_traits/member_traits.h" -// #include "type_is_nullable.h" +// #include "../type_is_nullable.h" -// #include "constraints.h" +// #include "../constraints.h" namespace sqlite_orm { @@ -2322,19 +3119,11 @@ namespace sqlite_orm { constraints_type constraints; /** - * Checks whether contraints are of trait `Trait` + * Checks whether contraints contain specified type. */ template class Trait> - constexpr bool is() const { - return tuple_has::value; - } - - constexpr bool is_generated() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - return is(); -#else - return false; -#endif + constexpr static bool is() { + return tuple_has::value; } /** @@ -2358,8 +3147,21 @@ namespace sqlite_orm { #endif }; + template + struct column_field_expression { + using type = void; + }; + + template + struct column_field_expression, void> { + using type = typename column_t::member_pointer_t; + }; + + template + using column_field_expression_t = typename column_field_expression::type; + template - SQLITE_ORM_INLINE_VAR constexpr bool is_column_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_column_v = polyfill::is_specialization_of::value; template using is_column = polyfill::bool_constant>; @@ -2373,222 +3175,68 @@ namespace sqlite_orm { template class TraitFn> using col_index_sequence_with = filter_tuple_sequence_t::template fn, + check_if_has::template fn, constraints_type_t, filter_tuple_sequence_t>; template class TraitFn> using col_index_sequence_excluding = filter_tuple_sequence_t::template fn, + check_if_has_not::template fn, constraints_type_t, filter_tuple_sequence_t>; } /** - * Column builder function. You should use it to create columns instead of constructor + * Factory function for a column definition from a member object pointer of the object to be mapped. */ template = true> - internal::column_t make_column(std::string name, M m, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), m, {}, std::make_tuple(constraints...)}); - } - - /** - * Column builder function with setter and getter. You should use it to create columns instead of constructor - */ - template = true, - internal::satisfies = true> - internal::column_t make_column(std::string name, S setter, G getter, Op... constraints) { - static_assert(std::is_same, internal::getter_field_type_t>::value, - "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::make_tuple(constraints...)}); - } - - /** - * Column builder function with getter and setter (reverse order). You should use it to create columns instead of - * constructor - */ - template = true, - internal::satisfies = true> - internal::column_t make_column(std::string name, G getter, S setter, Op... constraints) { - static_assert(std::is_same, internal::getter_field_type_t>::value, - "Getter and setter must get and set same data type"); - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + internal::column_t + make_column(std::string name, M memberPointer, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::make_tuple(constraints...)}); - } -} -#pragma once - -#include // std::wstring_convert -#include // std::string -#include // std::stringstream -#include // std::vector -#include // std::shared_ptr, std::unique_ptr -#ifndef SQLITE_ORM_OMITS_CODECVT -#include // std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT -// #include "functional/cxx_optional.h" - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "is_std_ptr.h" - -namespace sqlite_orm { - - /** - * Is used to print members mapped to objects in storage_t::dump member function. - * Other developers can create own specialization to map custom types - */ - template - struct field_printer; - - namespace internal { - template - SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v = false; - template - SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v{})>> = true - // Also see implementation note for `is_bindable_v` - ; - template - using is_printable = polyfill::bool_constant>; + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); } - template - struct field_printer::value>> { - std::string operator()(const T& t) const { - std::stringstream ss; - ss << t; - return ss.str(); - } - }; - - /** - * Upgrade to integer is required when using unsigned char(uint8_t) - */ - template<> - struct field_printer { - std::string operator()(const unsigned char& t) const { - std::stringstream ss; - ss << +t; - return ss.str(); - } - }; - - /** - * Upgrade to integer is required when using signed char(int8_t) - */ - template<> - struct field_printer { - std::string operator()(const signed char& t) const { - std::stringstream ss; - ss << +t; - return ss.str(); - } - }; - - /** - * char is neither signed char nor unsigned char so it has its own specialization - */ - template<> - struct field_printer { - std::string operator()(const char& t) const { - std::stringstream ss; - ss << +t; - return ss.str(); - } - }; - - template - struct field_printer::value>> { - std::string operator()(std::string string) const { - return string; - } - }; - - template<> - struct field_printer, void> { - std::string operator()(const std::vector& t) const { - std::stringstream ss; - ss << std::hex; - for(auto c: t) { - ss << c; - } - return ss.str(); - } - }; -#ifndef SQLITE_ORM_OMITS_CODECVT - /** - * Specialization for std::wstring (UTF-16 assumed). - */ - template - struct field_printer::value>> { - std::string operator()(const std::wstring& wideString) const { - std::wstring_convert> converter; - return converter.to_bytes(wideString); - } - }; -#endif // SQLITE_ORM_OMITS_CODECVT - template<> - struct field_printer { - std::string operator()(const nullptr_t&) const { - return "null"; - } - }; -#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED - template<> - struct field_printer { - std::string operator()(const std::nullopt_t&) const { - return "null"; - } - }; -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED - template - struct field_printer< - T, - std::enable_if_t, - internal::is_printable>>>> { - using unqualified_type = std::remove_cv_t; + /** + * Factory function for a column definition from "setter" and "getter" member function pointers of the object to be mapped. + */ + template = true, + internal::satisfies = true> + internal::column_t make_column(std::string name, S setter, G getter, Op... constraints) { + static_assert(std::is_same, internal::getter_field_type_t>::value, + "Getter and setter must get and set same data type"); + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - std::string operator()(const T& t) const { - if(t) { - return field_printer()(*t); - } else { - return field_printer{}(nullptr); - } - } - }; + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + } -#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED - template - struct field_printer< - T, - std::enable_if_t, - internal::is_printable>>>> { - using unqualified_type = std::remove_cv_t; + /** + * Factory function for a column definition from "getter" and "setter" member function pointers of the object to be mapped. + */ + template = true, + internal::satisfies = true> + internal::column_t make_column(std::string name, G getter, S setter, Op... constraints) { + static_assert(std::is_same, internal::getter_field_type_t>::value, + "Getter and setter must get and set same data type"); + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - std::string operator()(const T& t) const { - if(t.has_value()) { - return field_printer()(*t); - } else { - return field_printer{}(std::nullopt); - } - } - }; -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + } } #pragma once @@ -2596,6 +3244,7 @@ namespace sqlite_orm { #include // std::enable_if, std::is_same, std::remove_const #include // std::vector #include // std::tuple +#include // std::move, std::forward #include // std::stringstream // #include "functional/cxx_universal.h" @@ -2604,43 +3253,6 @@ namespace sqlite_orm { // #include "is_base_of_template.h" -#include // std::true_type, std::false_type, std::declval - -namespace sqlite_orm { - - namespace internal { - - /* - * This is because of bug in MSVC, for more information, please visit - * https://stackoverflow.com/questions/34672441/stdis-base-of-for-template-classes/34672753#34672753 - */ -#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - template class Base> - struct is_base_of_template_impl { - template - static constexpr std::true_type test(const Base&); - - static constexpr std::false_type test(...); - }; - - template class C> - using is_base_of_template = decltype(is_base_of_template_impl::test(std::declval())); -#else - template class C, typename... Ts> - std::true_type is_base_of_template_impl(const C&); - - template class C> - std::false_type is_base_of_template_impl(...); - - template class C> - using is_base_of_template = decltype(is_base_of_template_impl(std::declval())); -#endif - - template class C> - SQLITE_ORM_INLINE_VAR constexpr bool is_base_of_template_v = is_base_of_template::value; - } -} - // #include "type_traits.h" // #include "collate_argument.h" @@ -2691,6 +3303,7 @@ namespace sqlite_orm { bool replace_bindable_with_question = false; bool skip_table_name = true; bool use_parentheses = true; + bool fts5_columns = false; }; template @@ -2715,16 +3328,70 @@ namespace sqlite_orm { const storage_type& storage; }; - } } +// #include "serialize_result_type.h" + // #include "tags.h" +// #include "table_reference.h" + +#include // std::remove_const, std::type_identity +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm { + namespace internal { + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. + */ + template + struct table_reference : polyfill::type_identity {}; + + template + struct decay_table_ref : std::remove_const {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + + template + using decay_table_ref_t = typename decay_table_ref::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_decay_table_ref_t = typename decay_table_ref::type; +#endif + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_table_reference_v = + polyfill::is_specialization_of_v, table_reference>; + + template + struct is_table_reference : polyfill::bool_constant> {}; + } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; +#endif +} + // #include "alias_traits.h" -#include // std::remove_const, std::is_base_of, std::is_same +#include // std::is_base_of, std::is_same +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif // #include "functional/cxx_universal.h" @@ -2732,6 +3399,8 @@ namespace sqlite_orm { // #include "type_traits.h" +// #include "table_reference.h" + namespace sqlite_orm { /** @short Base class for a custom table alias, column alias or expression alias. @@ -2744,52 +3413,128 @@ namespace sqlite_orm { SQLITE_ORM_INLINE_VAR constexpr bool is_alias_v = std::is_base_of::value; template - using is_alias = polyfill::bool_constant>; + struct is_alias : polyfill::bool_constant> {}; /** @short Alias of a column in a record set, see `orm_column_alias`. */ template SQLITE_ORM_INLINE_VAR constexpr bool is_column_alias_v = - polyfill::conjunction_v, polyfill::negation>>; + polyfill::conjunction, polyfill::negation>>::value; template - using is_column_alias = is_alias; + struct is_column_alias : is_alias {}; /** @short Alias of any type of record set, see `orm_recordset_alias`. */ template SQLITE_ORM_INLINE_VAR constexpr bool is_recordset_alias_v = - polyfill::conjunction_v, polyfill::is_detected>; + polyfill::conjunction, polyfill::is_detected>::value; template - using is_recordset_alias = polyfill::bool_constant>; + struct is_recordset_alias : polyfill::bool_constant> {}; /** @short Alias of a concrete table, see `orm_table_alias`. */ template - SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction_v< + SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction< is_recordset_alias, - polyfill::negation, std::remove_const_t>>>; + polyfill::negation, std::remove_const_t>>>::value; + + template + struct is_table_alias : polyfill::bool_constant> {}; + + /** @short Moniker of a CTE, see `orm_cte_moniker`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_cte_moniker_v = +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + polyfill::conjunction_v, + std::is_same, std::remove_const_t>>; +#else + false; +#endif template - using is_table_alias = polyfill::bool_constant>; + using is_cte_moniker = polyfill::bool_constant>; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + concept orm_alias = std::derived_from; + + /** @short Specifies that a type is an alias of a column in a record set. + * + * A column alias has the following traits: + * - is derived from `alias_tag` + * - must not have a nested `type` typename + */ + template + concept orm_column_alias = (orm_alias && !orm_names_type); + + /** @short Specifies that a type is an alias of any type of record set. + * + * A record set alias has the following traits: + * - is derived from `alias_tag`. + * - has a nested `type` typename, which refers to a mapped object. + */ + template + concept orm_recordset_alias = (orm_alias && orm_names_type); + + /** @short Specifies that a type is an alias of a concrete table. + * + * A concrete table alias has the following traits: + * - is derived from `alias_tag`. + * - has a `type` typename, which refers to another mapped object (i.e. doesn't refer to itself). + */ + template + concept orm_table_alias = (orm_recordset_alias && !std::same_as>); + + /** @short Moniker of a CTE. + * + * A CTE moniker has the following traits: + * - is derived from `alias_tag`. + * - has a `type` typename, which refers to itself. + */ + template + concept orm_cte_moniker = (orm_recordset_alias && std::same_as>); + + /** @short Specifies that a type refers to a mapped table (possibly aliased). + */ + template + concept orm_refers_to_table = (orm_table_reference || orm_table_alias); + + /** @short Specifies that a type refers to a recordset. + */ + template + concept orm_refers_to_recordset = (orm_table_reference || orm_recordset_alias); + + /** @short Specifies that a type is a mapped recordset (table reference). + */ + template + concept orm_mapped_recordset = (orm_table_reference || orm_cte_moniker); +#endif } // #include "expression.h" #include -#include // std::move, std::forward +#include // std::enable_if +#include // std::move, std::forward, std::declval // #include "functional/cxx_optional.h" // #include "functional/cxx_universal.h" -// #include "operators.h" +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "tags.h" namespace sqlite_orm { namespace internal { + template + struct in_t; + template struct and_condition_t; @@ -2797,14 +3542,12 @@ namespace sqlite_orm { struct or_condition_t; /** - * Is not an operator but a result of c(...) function. Has operator= overloaded which returns assign_t + * Result of c(...) function. Has operator= overloaded which returns assign_t */ template - struct expression_t : condition_t { + struct expression_t { T value; - expression_t(T value_) : value(std::move(value_)) {} - template assign_t operator=(R r) const { return {this->value, std::move(r)}; @@ -2820,12 +3563,12 @@ namespace sqlite_orm { #endif template in_t in(Args... args) const { - return {this->value, std::make_tuple(std::forward(args)...), false}; + return {this->value, {std::forward(args)...}, false}; } template in_t not_in(Args... args) const { - return {this->value, std::make_tuple(std::forward(args)...), true}; + return {this->value, {std::forward(args)...}, true}; } template @@ -2839,6 +3582,10 @@ namespace sqlite_orm { } }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + template T get_from_expression(T value) { return std::move(value); @@ -2848,17 +3595,189 @@ namespace sqlite_orm { T get_from_expression(expression_t expression) { return std::move(expression.value); } + + template + using unwrap_expression_t = decltype(get_from_expression(std::declval())); + } + + /** + * Public interface for syntax sugar for columns. Example: `where(c(&User::id) == 5)` or + * `storage.update(set(c(&User::name) = "Dua Lipa")); + */ + template + internal::expression_t c(T value) { + return {std::move(value)}; + } +} + +// #include "column_pointer.h" + +#include // std::enable_if, std::is_convertible +#include // std::move + +// #include "functional/cxx_core_features.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "type_traits.h" + +// #include "table_reference.h" + +// #include "alias_traits.h" + +// #include "tags.h" + +namespace sqlite_orm { + namespace internal { + /** + * This class is used to store explicit mapped type T and its column descriptor (member pointer/getter/setter). + * Is useful when mapped type is derived from other type and base class has members mapped to a storage. + */ + template + struct column_pointer { + using type = T; + using field_type = F; + + field_type field; + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_column_pointer_v = + polyfill::is_specialization_of::value; + + template + struct is_column_pointer : polyfill::bool_constant> {}; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_operator_argument_v::value>> = + true; + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct alias_holder; +#endif + } + + /** + * Explicitly refer to a column, used in contexts + * where the automatic object mapping deduction needs to be overridden. + * + * Example: + * struct BaseType : { int64 id; }; + * struct MyType : BaseType { ... }; + * storage.select(column(&BaseType::id)); + */ + template = true> + constexpr internal::column_pointer column(F Base::*field) { + static_assert(std::is_convertible::value, "Field must be from derived class"); + return {field}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column. + */ + template + constexpr auto column(F O::*field) { + return column>(field); + } + + // Intentionally place pointer-to-member operator for table references in the internal namespace + // to facilitate ADL (Argument Dependent Lookup) + namespace internal { + /** + * Explicitly refer to a column. + */ + template + constexpr auto operator->*(const R& /*table*/, F O::*field) { + return column(field); + } + } + + /** + * Make a table reference. + */ + template + requires(!orm_recordset_alias) + consteval internal::table_reference column() { + return {}; + } + + /** + * Make a table reference. + */ + template + requires(!orm_recordset_alias) + consteval internal::table_reference c() { + return {}; + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Explicitly refer to a column alias mapped into a CTE or subquery. + * + * Example: + * struct Object { ... }; + * using cte_1 = decltype(1_ctealias); + * storage.with(cte()(select(&Object::id)), select(column(&Object::id))); + * storage.with(cte()(select(&Object::id)), select(column(1_colalias))); + * storage.with(cte()(select(as(&Object::id))), select(column(colalias_a{}))); + * storage.with(cte(colalias_a{})(select(&Object::id)), select(column(colalias_a{}))); + * storage.with(cte()(select(as(&Object::id))), select(column(get()))); + */ + template = true> + constexpr auto column(F field) { + using namespace ::sqlite_orm::internal; + + static_assert(is_cte_moniker_v, "`Moniker' must be a CTE moniker"); + + if constexpr(polyfill::is_specialization_of_v) { + static_assert(is_column_alias_v>); + return column_pointer{{}}; + } else if constexpr(is_column_alias_v) { + return column_pointer>{{}}; + } else { + return column_pointer{std::move(field)}; + } + (void)field; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicitly refer to a column mapped into a CTE or subquery. + * + * Example: + * struct Object { ... }; + * storage.with(cte<"z"_cte>()(select(&Object::id)), select(column<"z"_cte>(&Object::id))); + * storage.with(cte<"z"_cte>()(select(&Object::id)), select(column<"z"_cte>(1_colalias))); + */ + template + constexpr auto column(F field) { + using Moniker = std::remove_const_t; + return column(std::forward(field)); } /** - * Public interface for syntax sugar for columns. Example: `where(c(&User::id) == 5)` or - * `storage.update(set(c(&User::name) = "Dua Lipa")); + * Explicitly refer to a column mapped into a CTE or subquery. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + * + * Example: + * struct Object { ... }; + * using cte_1 = decltype(1_ctealias); + * storage.with(cte()(select(&Object::id)), select(1_ctealias->*&Object::id)); + * storage.with(cte()(select(&Object::id)), select(1_ctealias->*1_colalias)); */ - template - internal::expression_t c(T value) { - return {std::move(value)}; + template + constexpr auto operator->*(const Moniker& /*moniker*/, F field) { + return column(std::forward(field)); } +#endif +#endif } +// #include "tags.h" // #include "type_printer.h" @@ -2984,22 +3903,22 @@ namespace sqlite_orm { using right_type = R; using result_type = Res; - left_type l; - right_type r; + left_type lhs; + right_type rhs; binary_condition() = default; - binary_condition(left_type l_, right_type r_) : l(std::move(l_)), r(std::move(r_)) {} + binary_condition(left_type l_, right_type r_) : lhs(std::move(l_)), rhs(std::move(r_)) {} }; template SQLITE_ORM_INLINE_VAR constexpr bool is_binary_condition_v = is_base_of_template_v; template - using is_binary_condition = polyfill::bool_constant>; + struct is_binary_condition : polyfill::bool_constant> {}; struct and_condition_string { - operator std::string() const { + serialize_result_type serialize() const { return "AND"; } }; @@ -3014,13 +3933,8 @@ namespace sqlite_orm { using super::super; }; - template - and_condition_t make_and_condition(L left, R right) { - return {std::move(left), std::move(right)}; - } - struct or_condition_string { - operator std::string() const { + serialize_result_type serialize() const { return "OR"; } }; @@ -3035,13 +3949,8 @@ namespace sqlite_orm { using super::super; }; - template - or_condition_t make_or_condition(L left, R right) { - return {std::move(left), std::move(right)}; - } - struct is_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return "="; } }; @@ -3079,8 +3988,18 @@ namespace sqlite_orm { } }; + template + struct is_equal_with_table_t : negatable_t { + using left_type = L; + using right_type = R; + + right_type rhs; + + is_equal_with_table_t(right_type rhs) : rhs(std::move(rhs)) {} + }; + struct is_not_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return "!="; } }; @@ -3108,7 +4027,7 @@ namespace sqlite_orm { }; struct greater_than_string { - operator std::string() const { + serialize_result_type serialize() const { return ">"; } }; @@ -3136,7 +4055,7 @@ namespace sqlite_orm { }; struct greater_or_equal_string { - operator std::string() const { + serialize_result_type serialize() const { return ">="; } }; @@ -3163,8 +4082,8 @@ namespace sqlite_orm { } }; - struct lesser_than_string { - operator std::string() const { + struct less_than_string { + serialize_result_type serialize() const { return "<"; } }; @@ -3173,10 +4092,10 @@ namespace sqlite_orm { * < operator object. */ template - struct lesser_than_t : binary_condition, negatable_t { - using self = lesser_than_t; + struct less_than_t : binary_condition, negatable_t { + using self = less_than_t; - using binary_condition::binary_condition; + using binary_condition::binary_condition; collate_t collate_binary() const { return {*this, collate_argument::binary}; @@ -3191,8 +4110,8 @@ namespace sqlite_orm { } }; - struct lesser_or_equal_string { - operator std::string() const { + struct less_or_equal_string { + serialize_result_type serialize() const { return "<="; } }; @@ -3201,10 +4120,10 @@ namespace sqlite_orm { * <= operator object. */ template - struct lesser_or_equal_t : binary_condition, negatable_t { - using self = lesser_or_equal_t; + struct less_or_equal_t : binary_condition, negatable_t { + using self = less_or_equal_t; - using binary_condition::binary_condition; + using binary_condition::binary_condition; collate_t collate_binary() const { return {*this, collate_argument::binary}; @@ -3419,12 +4338,12 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_order_by_v = - polyfill::disjunction_v, - polyfill::is_specialization_of, - polyfill::is_specialization_of>; + polyfill::disjunction, + polyfill::is_specialization_of, + polyfill::is_specialization_of>::value; template - using is_order_by = polyfill::bool_constant>; + struct is_order_by : polyfill::bool_constant> {}; struct between_string { operator std::string() const { @@ -3486,7 +4405,7 @@ namespace sqlite_orm { }; template - struct glob_t : condition_t, glob_string, internal::negatable_t { + struct glob_t : condition_t, glob_string, negatable_t { using self = glob_t; using arg_t = A; using pattern_t = T; @@ -3686,185 +4605,142 @@ namespace sqlite_orm { return {}; } - template = true> - internal::negated_condition_t operator!(T arg) { - return {std::move(arg)}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Explicit FROM function. Usage: + * `storage.select(&User::id, from<"a"_alias.for_>());` + */ + template + auto from() { + return from...>(); } +#endif - // Deliberately put operators for `expression_t` into the internal namespace + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { - /** - * Cute operators for columns - */ - template - lesser_than_t operator<(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - lesser_than_t operator<(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - lesser_or_equal_t operator<=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - lesser_or_equal_t operator<=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - greater_than_t operator>(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - greater_than_t operator>(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - greater_or_equal_t operator>=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - greater_or_equal_t operator>=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - is_equal_t operator==(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - is_equal_t operator==(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - is_not_equal_t operator!=(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - is_not_equal_t operator!=(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - conc_t operator||(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - conc_t operator||(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - conc_t operator||(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template = true> - conc_t operator||(E expr, R r) { - return {std::move(expr), std::move(r)}; - } - - template = true> - conc_t operator||(L l, E expr) { - return {std::move(l), std::move(expr)}; - } - - template - add_t operator+(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - add_t operator+(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - add_t operator+(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - sub_t operator-(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - sub_t operator-(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - sub_t operator-(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - mul_t operator*(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - mul_t operator*(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - mul_t operator*(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - div_t operator/(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - div_t operator/(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - div_t operator/(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; - } - - template - mod_t operator%(expression_t expr, R r) { - return {std::move(expr.value), std::move(r)}; - } - - template - mod_t operator%(L l, expression_t expr) { - return {std::move(l), std::move(expr.value)}; - } - - template - mod_t operator%(expression_t l, expression_t r) { - return {std::move(l.value), std::move(r.value)}; + template< + class T, + std::enable_if_t, is_operator_argument>::value, + bool> = true> + negated_condition_t operator!(T arg) { + return {std::move(arg)}; + } + + template, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + less_than_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> + less_or_equal_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> + greater_than_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> + greater_or_equal_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, + std::is_base_of, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + is_equal_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, + std::is_base_of, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + is_not_equal_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> + and_condition_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>::value, + bool> = true> + or_condition_t, unwrap_expression_t> operator||(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + + template< + class L, + class R, + std::enable_if_t, + std::is_base_of, + is_operator_argument, + is_operator_argument>, + // exclude conditions + polyfill::negation, + std::is_base_of>>>::value, + bool> = true> + conc_t, unwrap_expression_t> operator||(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; } } template - internal::using_t using_(F O::*p) { - return {p}; + internal::using_t using_(F O::*field) { + return {field}; } template - internal::using_t using_(internal::column_pointer cp) { - return {std::move(cp)}; + internal::using_t using_(internal::column_pointer field) { + return {std::move(field)}; } template @@ -3887,21 +4763,49 @@ namespace sqlite_orm { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto left_join(On on) { + return left_join, On>(std::move(on)); + } +#endif + template internal::join_t join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto join(On on) { + return join, On>(std::move(on)); + } +#endif + template internal::left_outer_join_t left_outer_join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto left_outer_join(On on) { + return left_outer_join, On>(std::move(on)); + } +#endif + template internal::inner_join_t inner_join(O o) { return {std::move(o)}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto inner_join(On on) { + return inner_join, On>(std::move(on)); + } +#endif + template internal::offset_t offset(T off) { return {std::move(off)}; @@ -3922,36 +4826,18 @@ namespace sqlite_orm { return {std::move(lim), {std::move(offt.off)}}; } - template, - std::is_base_of>, - bool> = true> - auto operator&&(L l, R r) { - using internal::get_from_expression; - return internal::make_and_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); - } - template auto and_(L l, R r) { - using internal::get_from_expression; - return internal::make_and_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); - } - - template, - std::is_base_of>, - bool> = true> - auto operator||(L l, R r) { - using internal::get_from_expression; - return internal::make_or_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); + using namespace ::sqlite_orm::internal; + return and_condition_t, unwrap_expression_t>{get_from_expression(std::forward(l)), + get_from_expression(std::forward(r))}; } template auto or_(L l, R r) { - using internal::get_from_expression; - return internal::make_or_condition(std::move(get_from_expression(l)), std::move(get_from_expression(r))); + using namespace ::sqlite_orm::internal; + return or_condition_t, unwrap_expression_t>{get_from_expression(std::forward(l)), + get_from_expression(std::forward(r))}; } template @@ -4004,6 +4890,11 @@ namespace sqlite_orm { return {std::move(l), std::move(r)}; } + template + internal::is_equal_with_table_t is_equal(R rhs) { + return {std::move(rhs)}; + } + template internal::is_not_equal_t is_not_equal(L l, R r) { return {std::move(l), std::move(r)}; @@ -4035,22 +4926,40 @@ namespace sqlite_orm { } template - internal::lesser_than_t lesser_than(L l, R r) { + internal::less_than_t less_than(L l, R r) { + return {std::move(l), std::move(r)}; + } + + /** + * [Deprecation notice] This function is deprecated and will be removed in v1.10. Use the accurately named function `less_than(...)` instead. + */ + template + [[deprecated("Use the accurately named function `less_than(...)` instead")]] internal::less_than_t + lesser_than(L l, R r) { + return {std::move(l), std::move(r)}; + } + + template + internal::less_than_t lt(L l, R r) { return {std::move(l), std::move(r)}; } template - internal::lesser_than_t lt(L l, R r) { + internal::less_or_equal_t less_or_equal(L l, R r) { return {std::move(l), std::move(r)}; } + /** + * [Deprecation notice] This function is deprecated and will be removed in v1.10. Use the accurately named function `less_or_equal(...)` instead. + */ template - internal::lesser_or_equal_t lesser_or_equal(L l, R r) { + [[deprecated("Use the accurately named function `less_or_equal(...)` instead")]] internal::less_or_equal_t + lesser_or_equal(L l, R r) { return {std::move(l), std::move(r)}; } template - internal::lesser_or_equal_t le(L l, R r) { + internal::less_or_equal_t le(L l, R r) { return {std::move(l), std::move(r)}; } @@ -4082,8 +4991,8 @@ namespace sqlite_orm { * Example: storage.get_all(multi_order_by(order_by(&Singer::name).asc(), order_by(&Singer::gender).desc()) */ template - internal::multi_order_by_t multi_order_by(Args&&... args) { - return {std::make_tuple(std::forward(args)...)}; + internal::multi_order_by_t multi_order_by(Args... args) { + return {{std::forward(args)...}}; } /** @@ -4152,23 +5061,71 @@ namespace sqlite_orm { } #pragma once -#include // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const -#include // std::index_sequence, std::make_index_sequence +#include // std::enable_if, std::is_same, std::conditional +#include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "functional/mpl/conditional.h" + +// #include "functional/cstring_literal.h" + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::index_sequence #include // std::copy_n +#endif -// #include "functional/cxx_universal.h" +// #include "cxx_universal.h" // ::size_t -// #include "functional/cxx_type_traits_polyfill.h" + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +namespace sqlite_orm::internal { + /* + * Wraps a C string of fixed size. + * Its main purpose is to enable the user-defined string literal operator template. + */ + template + struct cstring_literal { + static constexpr size_t size() { + return N - 1; + } + + constexpr cstring_literal(const char (&cstr)[N]) { + std::copy_n(cstr, N, this->cstr); + } + + char cstr[N]; + }; + + template class Template, cstring_literal literal, size_t... Idx> + consteval auto explode_into(std::index_sequence) { + return Template{}; + } +} +#endif // #include "type_traits.h" // #include "alias_traits.h" +// #include "table_type_of.h" + +// #include "tags.h" + +// #include "column_pointer.h" + namespace sqlite_orm { namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + inline constexpr bool is_operator_argument_v>> = true; +#endif /** * This is a common built-in class used for character based table aliases. @@ -4195,8 +5152,19 @@ namespace sqlite_orm { column_type column; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = + true; + + struct basic_table; + /* * Encapsulates extracting the alias identifier of a non-alias. + * + * `extract()` always returns the empty string. + * `as_alias()` is used in contexts where a table might be aliased, and the empty string is returned. + * `as_qualifier()` is used in contexts where a table might be aliased, and the given table's name is returned. */ template struct alias_extractor { @@ -4207,13 +5175,19 @@ namespace sqlite_orm { static std::string as_alias() { return {}; } + + template + static const std::string& as_qualifier(const X& table) { + return table.name; + } }; /* * Encapsulates extracting the alias identifier of an alias. * - * `extract()` always returns the alias identifier. - * `as_alias()` is used in contexts where a table is aliased. + * `extract()` always returns the alias identifier or CTE moniker. + * `as_alias()` is used in contexts where a recordset is aliased, and the alias identifier is returned. + * `as_qualifier()` is used in contexts where a table is aliased, and the alias identifier is returned. */ template struct alias_extractor> { @@ -4228,6 +5202,20 @@ namespace sqlite_orm { static std::string as_alias() { return alias_extractor::extract(); } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // for CTE monikers -> empty + template, A> = true> + static std::string as_alias() { + return {}; + } +#endif + + // for regular table aliases -> alias identifier + template = true> + static std::string as_qualifier(const basic_table&) { + return alias_extractor::extract(); + } }; /** @@ -4242,7 +5230,7 @@ namespace sqlite_orm { }; /** - * This is a common built-in class used for custom single-character column aliases. + * Built-in column alias. * For convenience there exist type aliases `colalias_a`, `colalias_b`, ... * The easiest way to create a column alias is using `"xyz"_col`. */ @@ -4257,21 +5245,191 @@ namespace sqlite_orm { struct alias_holder { using type = T; - alias_holder() = default; - }; + alias_holder() = default; + // CTE feature needs it to implicitly convert a column alias to an alias_holder; see `cte()` factory function + alias_holder(const T&) noexcept {} + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct recordset_alias_builder { + template + [[nodiscard]] consteval recordset_alias for_() const { + return {}; + } + + template + [[nodiscard]] consteval auto for_() const { + using T = std::remove_const_t; + return recordset_alias{}; + } + }; +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + SQLITE_ORM_CONSTEVAL auto n_to_colalias() { + constexpr column_alias<'1' + n % 10, C...> colalias{}; + if constexpr(n > 10) { + return n_to_colalias(); + } else { + return colalias; + } + } + + template + inline constexpr bool is_builtin_numeric_column_alias_v = false; + template + inline constexpr bool is_builtin_numeric_column_alias_v> = ((C >= '0' && C <= '9') && ...); +#endif + } + + /** + * Using a column pointer, create a column reference to an aliased table column. + * + * Example: + * using als = alias_u; + * select(alias_column(column(&User::id))) + */ + template, + polyfill::negation>>>::value, + bool> = true> + constexpr auto alias_column(C field) { + using namespace ::sqlite_orm::internal; + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + return alias_column_t{std::move(field)}; + } + + /** + * Using an object member field, create a column reference to an aliased table column. + * + * @note The object member pointer can be from a derived class without explicitly forming a column pointer. + * + * Example: + * using als = alias_u; + * select(alias_column(&User::id)) + */ + template, + polyfill::negation>>>::value, + bool> = true> + constexpr auto alias_column(F O::*field) { + using namespace ::sqlite_orm::internal; + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + using C1 = + mpl::conditional_t::value, F O::*, column_pointer>; + return alias_column_t{C1{field}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a column reference to an aliased table column. + * + * @note An object member pointer can be from a derived class without explicitly forming a column pointer. + * + * Example: + * constexpr orm_table_alias auto als = "u"_alias.for_(); + * select(alias_column(&User::id)) + */ + template + requires(!orm_cte_moniker>) + constexpr auto alias_column(C field) { + using namespace ::sqlite_orm::internal; + using A = decltype(als); + using aliased_type = type_t; + static_assert(is_field_of_v, "Column must be from aliased table"); + + if constexpr(is_column_pointer_v) { + return alias_column_t{std::move(field)}; + } else if constexpr(std::is_same_v, aliased_type>) { + return alias_column_t{field}; + } else { + // wrap in column_pointer + using C1 = column_pointer; + return alias_column_t{{field}}; + } + } + + /** + * Create a column reference to an aliased table column. + * + * @note An object member pointer can be from a derived class without explicitly forming a column pointer. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + * + * Example: + * constexpr auto als = "u"_alias.for_(); + * select(als->*&User::id) + */ + template + requires(!orm_cte_moniker>) + constexpr auto operator->*(const A& /*tableAlias*/, F field) { + return alias_column(std::move(field)); + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Create a column reference to an aliased CTE column. + */ + template, internal::is_cte_moniker>>, + bool> = true> + constexpr auto alias_column(C c) { + using namespace internal; + using cte_moniker_t = type_t; + + if constexpr(is_column_pointer_v) { + static_assert(std::is_same, cte_moniker_t>::value, + "Column pointer must match aliased CTE"); + return alias_column_t{c}; + } else { + auto cp = column(c); + return alias_column_t{std::move(cp)}; + } + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a column reference to an aliased CTE column. + * + * @note (internal) Intentionally place in the sqlite_orm namespace for ADL (Argument Dependent Lookup) + * because recordset aliases are derived from `sqlite_orm::alias_tag` + */ + template + requires(orm_cte_moniker>) + constexpr auto operator->*(const A& /*tableAlias*/, C c) { + return alias_column(std::move(c)); } /** - * @return column with table alias attached. Place it instead of a column statement in case you need to specify a - * column with table alias prefix like 'a.column'. + * Create a column reference to an aliased CTE column. */ - template, bool> = true> - internal::alias_column_t alias_column(C c) { - using aliased_type = internal::type_t; - static_assert(std::is_same, aliased_type>::value, - "Column must be from aliased table"); - return {c}; + template + requires(orm_cte_moniker>) + constexpr auto alias_column(C c) { + using A = std::remove_const_t; + return alias_column(std::move(c)); } +#endif +#endif /** * Alias a column expression. @@ -4281,11 +5439,48 @@ namespace sqlite_orm { return {std::move(expression)}; } - template = true> - internal::alias_holder get() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Alias a column expression. + */ + template + auto as(E expression) { + return internal::as_t{std::move(expression)}; + } + + /** + * Alias a column expression. + */ + template + internal::as_t operator>>=(E expression, const A&) { + return {std::move(expression)}; + } +#else + /** + * Alias a column expression. + */ + template = true> + internal::as_t operator>>=(E expression, const A&) { + return {std::move(expression)}; + } +#endif + + /** + * Wrap a column alias in an alias holder. + */ + template + internal::alias_holder get() { + static_assert(internal::is_column_alias_v, ""); return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto get() { + return internal::alias_holder{}; + } +#endif + template using alias_a = internal::recordset_alias; template @@ -4348,6 +5543,54 @@ namespace sqlite_orm { using colalias_g = internal::column_alias<'g'>; using colalias_h = internal::column_alias<'h'>; using colalias_i = internal::column_alias<'i'>; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Create a table alias. + * + * Examples: + * constexpr orm_table_alias auto z_alias = alias<'z'>.for_(); + */ + template + inline constexpr internal::recordset_alias_builder alias{}; + + inline namespace literals { + /** @short Create a table alias. + * + * Examples: + * constexpr orm_table_alias auto z_alias = "z"_alias.for_(); + */ + template + [[nodiscard]] consteval auto operator"" _alias() { + return internal::explode_into( + std::make_index_sequence{}); + } + + /** @short Create a column alias. + * column_alias<'a'[, ...]> from a string literal. + * E.g. "a"_col, "b"_col + */ + template + [[nodiscard]] consteval auto operator"" _col() { + return internal::explode_into(std::make_index_sequence{}); + } + } +#endif + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + inline namespace literals { + /** + * column_alias<'1'[, ...]> from a numeric literal. + * E.g. 1_colalias, 2_colalias + */ + template + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _colalias() { + // numeric identifiers are used for automatically assigning implicit aliases to unaliased column expressions, + // which start at "1". + static_assert(std::array{Chars...}[0] > '0'); + return internal::column_alias{}; + } + } +#endif } #pragma once @@ -4359,16 +5602,22 @@ namespace sqlite_orm { // #include "functional/cxx_type_traits_polyfill.h" -// #include "conditions.h" +// #include "functional/mpl/conditional.h" // #include "is_base_of_template.h" -// #include "tuple_helper/tuple_filter.h" +// #include "tuple_helper/tuple_traits.h" + +// #include "conditions.h" // #include "serialize_result_type.h" // #include "operators.h" +// #include "tags.h" + +// #include "table_reference.h" + // #include "ast/into.h" // #include "../functional/cxx_type_traits_polyfill.h" @@ -4421,10 +5670,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_built_in_function_v = is_base_of_template_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_built_in_function_v = + is_base_of_template::value; template - using is_built_in_function = polyfill::bool_constant>; + struct is_built_in_function : polyfill::bool_constant> {}; template struct filtered_aggregate_function { @@ -4559,7 +5809,6 @@ namespace sqlite_orm { }; #if SQLITE_VERSION_NUMBER >= 3007016 - struct char_string { serialize_result_type serialize() const { return "CHAR"; @@ -4988,40 +6237,23 @@ namespace sqlite_orm { template using field_type_or_type_t = polyfill::detected_or_t>; - } - - /** - * Cute operators for core functions - */ - template = true> - internal::lesser_than_t operator<(F f, R r) { - return {std::move(f), std::move(r)}; - } - - template = true> - internal::lesser_or_equal_t operator<=(F f, R r) { - return {std::move(f), std::move(r)}; - } - template = true> - internal::greater_than_t operator>(F f, R r) { - return {std::move(f), std::move(r)}; - } + template + struct highlight_t { + using table_type = T; + using argument0_type = X; + using argument1_type = Y; + using argument2_type = Z; - template = true> - internal::greater_or_equal_t operator>=(F f, R r) { - return {std::move(f), std::move(r)}; - } + argument0_type argument0; + argument1_type argument1; + argument2_type argument2; - template = true> - internal::is_equal_t operator==(F f, R r) { - return {std::move(f), std::move(r)}; + highlight_t(argument0_type argument0, argument1_type argument1, argument2_type argument2) : + argument0(std::move(argument0)), argument1(std::move(argument1)), argument2(std::move(argument2)) {} + }; } - template = true> - internal::is_not_equal_t operator!=(F f, R r) { - return {std::move(f), std::move(r)}; - } #ifdef SQLITE_ENABLE_MATH_FUNCTIONS /** @@ -6040,7 +7272,6 @@ namespace sqlite_orm { } #if SQLITE_VERSION_NUMBER >= 3007016 - /** * CHAR(X1,X2,...,XN) function https://sqlite.org/lang_corefunc.html#char */ @@ -6055,7 +7286,6 @@ namespace sqlite_orm { inline internal::built_in_function_t random() { return {{}}; } - #endif /** @@ -6063,7 +7293,7 @@ namespace sqlite_orm { */ template auto coalesce(Args... args) - -> internal::built_in_function_t internal::built_in_function_t::value, std::common_type...>, polyfill::type_identity>::type, @@ -6077,7 +7307,7 @@ namespace sqlite_orm { */ template auto ifnull(X x, Y y) -> internal::built_in_function_t< - typename std::conditional_t< // choose R or common type + typename mpl::conditional_t< // choose R or common type std::is_void::value, std::common_type, internal::field_type_or_type_t>, polyfill::type_identity>::type, @@ -6230,7 +7460,7 @@ namespace sqlite_orm { } /** - * COUNT(*) with FROM function. Specified type T will be serializeed as + * COUNT(*) with FROM function. Specified type T will be serialized as * a from argument. */ template @@ -6238,6 +7468,17 @@ namespace sqlite_orm { return {}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * COUNT(*) with FROM function. Specified recordset will be serialized as + * a from argument. + */ + template + auto count() { + return count>(); + } +#endif + /** * AVG(X) aggregate function. */ @@ -6418,84 +7659,300 @@ namespace sqlite_orm { internal::built_in_function_t json_group_object(X x, Y y) { return {std::tuple{std::forward(x), std::forward(y)}}; } - #endif // SQLITE_ENABLE_JSON1 - template, - std::is_base_of>, - bool> = true> - internal::add_t operator+(L l, R r) { - return {std::move(l), std::move(r)}; - } - template, - std::is_base_of>, - bool> = true> - internal::sub_t operator-(L l, R r) { - return {std::move(l), std::move(r)}; + // 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, + std::is_base_of, + is_operator_argument, + is_operator_argument>::value, + bool> = true> + add_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> + sub_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> + mul_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> + div_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> + mod_t, unwrap_expression_t> operator%(L l, R r) { + return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; + } + } + + template + internal::highlight_t highlight(X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; } +} +#pragma once - template, - std::is_base_of>, - bool> = true> - internal::mul_t operator*(L l, R r) { - return {std::move(l), std::move(r)}; - } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif +#include // std::remove_const +#include // std::string +#include // std::move +#include // std::tuple, std::get, std::tuple_size +// #include "functional/cxx_optional.h" - template, - std::is_base_of>, - bool> = true> - internal::div_t operator/(L l, R r) { - return {std::move(l), std::move(r)}; - } +// #include "functional/cxx_universal.h" +// ::size_t +// #include "functional/cxx_type_traits_polyfill.h" - template, - std::is_base_of>, - bool> = true> - internal::mod_t operator%(L l, R r) { - return {std::move(l), std::move(r)}; +// #include "is_base_of_template.h" + +// #include "tuple_helper/tuple_traits.h" + +// #include "tuple_helper/tuple_transformer.h" + +#include // std::remove_reference, std::common_type, std::index_sequence, std::make_index_sequence, std::forward, std::move, std::integral_constant, std::declval +#include // std::tuple_size, std::get + +// #include "../functional/cxx_universal.h" +// ::size_t +// #include "../functional/cxx_type_traits_polyfill.h" + +// #include "../functional/cxx_functional_polyfill.h" + +// #include "../functional/mpl.h" + +namespace sqlite_orm { + namespace internal { + + template class Op> + struct tuple_transformer; + + template class Pack, class... Types, template class Op> + struct tuple_transformer, Op> { + using type = Pack...>; + }; + + /* + * Transform specified tuple. + * + * `Op` is a metafunction. + */ + template class Op> + using transform_tuple_t = typename tuple_transformer::type; + + // note: applying a combiner like `plus_fold_integrals` needs fold expressions +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + /* + * Apply a projection to a tuple's elements filtered by the specified indexes, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + * + * @note `project` is called using `std::invoke`, which is `constexpr` since C++20. + */ + template + SQLITE_ORM_CONSTEXPR_CPP20 auto recombine_tuple(CombineOp combine, + const Tpl& tpl, + std::index_sequence, + Projector project, + Init initial) { + return combine(initial, polyfill::invoke(project, std::get(tpl))...); + } + + /* + * Apply a projection to a tuple's elements, and combine the results. + * + * @note It's a glorified version of `std::apply()` and a variant of `std::accumulate()`. + * It combines filtering the tuple (indexes), transforming the elements (projection) and finally applying the callable (combine). + * + * @note `project` is called using `std::invoke`, which is `constexpr` since C++20. + */ + template + SQLITE_ORM_CONSTEXPR_CPP20 auto + recombine_tuple(CombineOp combine, const Tpl& tpl, Projector project, Init initial) { + return recombine_tuple(std::move(combine), + std::forward(tpl), + std::make_index_sequence::value>{}, + std::move(project), + std::move(initial)); + } + + /* + * Function object that takes integral constants and returns the sum of their values as an integral constant. + * Because it's a "transparent" functor, it must be called with at least one argument, otherwise it cannot deduce the integral constant type. + */ + struct plus_fold_integrals { + template + constexpr auto operator()(const Integrals&...) const { + using integral_type = std::common_type_t; + return std::integral_constant{}; + } + }; + + /* + * Function object that takes a type, applies a projection on it, and returns the tuple size of the projected type (as an integral constant). + * The projection is applied on the argument type, not the argument value/object. + */ + template class NestedProject> + struct project_nested_tuple_size { + template + constexpr auto operator()(const T&) const { + return typename std::tuple_size>::type{}; + } + }; + + template class NestedProject, class Tpl, class IdxSeq> + using nested_tuple_size_for_t = decltype(recombine_tuple(plus_fold_integrals{}, + std::declval(), + IdxSeq{}, + project_nested_tuple_size{}, + std::integral_constant{})); +#endif + + template + constexpr R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { + return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; + } + + /* + * Like `std::make_from_tuple`, but using a projection on the tuple elements. + */ + template + constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) { + return create_from_tuple( + std::forward(tpl), + std::make_index_sequence>::value>{}, + std::forward(project)); + } } } -#pragma once -namespace sqlite_orm { +// #include "tuple_helper/tuple_iteration.h" + +#include // std::get, std::tuple_element, std::tuple_size +#include // std::index_sequence, std::make_index_sequence +#include // std::forward, std::move + +// #include "../functional/cxx_universal.h" +// ::size_t +namespace sqlite_orm { namespace internal { +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) + template + void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { + if constexpr(reversed) { + // nifty fold expression trick: make use of guaranteed right-to-left evaluation order when folding over operator= + int sink; + // note: `(void)` cast silences warning 'expression result unused' + (void)((lambda(std::get(tpl)), sink) = ... = 0); + } else { + (lambda(std::get(tpl)), ...); + } + } +#else + template + void iterate_tuple(Tpl& /*tpl*/, std::index_sequence<>, L&& /*lambda*/) {} - template - bool compare_any(const L& /*lhs*/, const R& /*rhs*/) { - return false; + template + void iterate_tuple(Tpl& tpl, std::index_sequence, L&& lambda) { + if SQLITE_ORM_CONSTEXPR_IF(reversed) { + iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); + lambda(std::get(tpl)); + } else { + lambda(std::get(tpl)); + iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); + } } - template - bool compare_any(const O& lhs, const O& rhs) { - return lhs == rhs; +#endif + template + void iterate_tuple(Tpl&& tpl, L&& lambda) { + iterate_tuple(tpl, + std::make_index_sequence>::value>{}, + std::forward(lambda)); + } + +#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED + template + void iterate_tuple(std::index_sequence, L&& lambda) { + (lambda((std::tuple_element_t*)nullptr), ...); + } +#else + template + void iterate_tuple(std::index_sequence, L&& lambda) { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(lambda((std::tuple_element_t*)nullptr), 0)...}; + } +#endif + template + void iterate_tuple(L&& lambda) { + iterate_tuple(std::make_index_sequence::value>{}, std::forward(lambda)); + } + + template class Base, class L> + struct lambda_as_template_base : L { +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + lambda_as_template_base(L&& lambda) : L{std::move(lambda)} {} +#endif + template + decltype(auto) operator()(const Base& object) { + return L::operator()(object); + } + }; + + /* + * This method wraps the specified callable in another function object, + * which in turn implicitly casts its single argument to the specified template base class, + * then passes the converted argument to the lambda. + * + * Note: This method is useful for reducing combinatorial instantiation of template lambdas, + * as long as this library supports compilers that do not implement + * explicit template parameters in generic lambdas [SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED]. + * Unfortunately it doesn't work with user-defined conversion operators in order to extract + * parts of a class. In other words, the destination type must be a direct template base class. + */ + template class Base, class L> + lambda_as_template_base call_as_template_base(L lambda) { + return {std::move(lambda)}; } } } -#pragma once - -#include // std::remove_const -#include // std::string -#include // std::move -#include // std::tuple, std::get, std::tuple_size -// #include "functional/cxx_optional.h" - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "is_base_of_template.h" - -// #include "tuple_helper/tuple_filter.h" // #include "optional_container.h" @@ -6534,10 +7991,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_where_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_where_v = polyfill::is_specialization_of::value; template - using is_where = polyfill::bool_constant>; + struct is_where : polyfill::bool_constant> {}; } /** @@ -6593,20 +8050,6 @@ namespace sqlite_orm { template using is_group_by = polyfill::disjunction, polyfill::is_specialization_of>; - - /** - * HAVING holder. - * T is having argument type. - */ - template - struct having_t { - using expression_type = T; - - expression_type expression; - }; - - template - using is_having = polyfill::is_specialization_of; } /** @@ -6614,19 +8057,8 @@ namespace sqlite_orm { * Example: storage.get_all(group_by(&Employee::name)) */ template - internal::group_by_t group_by(Args&&... args) { - return {std::make_tuple(std::forward(args)...)}; - } - - /** - * [Deprecation notice]: this function is deprecated and will be removed in v1.9. Please use `group_by(...).having(...)` instead. - * - * HAVING(expression). - * Example: storage.get_all(group_by(&Employee::name), having(greater_than(count(&Employee::name), 2))); - */ - template - [[deprecated("Use group_by(...).having(...) instead")]] internal::having_t having(T expression) { - return {std::move(expression)}; + internal::group_by_t group_by(Args... args) { + return {{std::forward(args)...}}; } } @@ -6634,70 +8066,98 @@ namespace sqlite_orm { // #include "alias_traits.h" -// #include "column_pointer.h" +// #include "cte_moniker.h" -#include // std::string -#include // std::move +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#include // std::make_index_sequence +#endif +#include // std::enable_if, std::is_member_pointer, std::is_same, std::is_convertible +#include // std::ignore +#include +#endif -// #include "functional/cxx_core_features.h" +// #include "functional/cxx_universal.h" -// #include "conditions.h" +// #include "functional/cstring_literal.h" -// #include "alias_traits.h" +// #include "alias.h" +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) namespace sqlite_orm { + namespace internal { - /** - * This class is used to store explicit mapped type T and its column descriptor (member pointer/getter/setter). - * Is useful when mapped type is derived from other type and base class has members mapped to a storage. + /** + * A special record set alias that is both, a storage lookup type (mapping type) and an alias. */ - template - struct column_pointer { - using self = column_pointer; - using type = T; - using field_type = F; - - field_type field; - - template - is_equal_t operator==(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - is_not_equal_t operator!=(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - lesser_than_t operator<(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - lesser_or_equal_t operator<=(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - greater_than_t operator>(R rhs) const { - return {*this, std::move(rhs)}; - } - - template - greater_or_equal_t operator>=(R rhs) const { - return {*this, std::move(rhs)}; - } + template + struct cte_moniker + : recordset_alias< + cte_moniker /* refer to self, since a moniker is both, an alias and a mapped type */, + A, + X...> { + /** + * Introduce the construction of a common table expression using this moniker. + * + * The list of explicit columns is optional; + * if provided the number of columns must match the number of columns of the subselect. + * The column names will be merged with the subselect: + * 1. column names of subselect + * 2. explicit columns + * 3. fill in empty column names with column index + * + * Example: + * 1_ctealias()(select(&Object::id)); + * 1_ctealias(&Object::name)(select("object")); + * + * @return A `cte_builder` instance. + * @note (internal): Defined in select_constraints.h in order to keep this member function in the same place as the named factory function `cte()`, + * and to keep the actual creation of the builder in one place. + */ +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires((is_column_alias_v || std::is_member_pointer_v || + std::same_as> || + std::convertible_to) && + ...) + auto operator()(ExplicitCols... explicitColumns) const; +#else + template, + std::is_member_pointer, + std::is_same>, + std::is_convertible>...>, + bool> = true> + auto operator()(ExplicitCols... explicitColumns) const; +#endif }; + } - template - SQLITE_ORM_INLINE_VAR constexpr bool is_column_pointer_v = polyfill::is_specialization_of_v; - - template - using is_column_pointer = polyfill::bool_constant>; + inline namespace literals { + /** + * cte_moniker<'n'> from a numeric literal. + * E.g. 1_ctealias, 2_ctealias + */ + template + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _ctealias() { + return internal::cte_moniker{}; + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * cte_moniker<'1'[, ...]> from a string literal. + * E.g. "1"_cte, "2"_cte + */ + template + [[nodiscard]] consteval auto operator"" _cte() { + return internal::explode_into(std::make_index_sequence{}); + } +#endif } } +#endif namespace sqlite_orm { @@ -6760,11 +8220,36 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_columns_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_columns_v = polyfill::is_specialization_of::value; template using is_columns = polyfill::bool_constant>; + /* + * Captures the type of an aggregate/structure/object and column expressions, such that + * `T` can be constructed in-place as part of a result row. + * `T` must be constructible using direct-list-initialization. + */ + template + struct struct_t { + using columns_type = std::tuple; + + columns_type columns; + bool distinct = false; + + static constexpr int count = std::tuple_size::value; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + struct_t(columns_type columns) : columns{std::move(columns)} {} +#endif + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_struct_v = polyfill::is_specialization_of::value; + + template + using is_struct = polyfill::bool_constant>; + /** * Subselect object type. */ @@ -6784,7 +8269,7 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_select_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_select_v = polyfill::is_specialization_of::value; template using is_select = polyfill::bool_constant>; @@ -6792,22 +8277,21 @@ namespace sqlite_orm { /** * Base for UNION, UNION ALL, EXCEPT and INTERSECT */ - template + template struct compound_operator { - using left_type = L; - using right_type = R; + using expressions_tuple = std::tuple; - left_type left; - right_type right; + expressions_tuple compound; - compound_operator(left_type l, right_type r) : left(std::move(l)), right(std::move(r)) { - this->left.highest_level = true; - this->right.highest_level = true; + compound_operator(expressions_tuple compound) : compound{std::move(compound)} { + iterate_tuple(this->compound, [](auto& expression) { + expression.highest_level = true; + }); } }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_compound_operator_v = is_base_of_template_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_compound_operator_v = is_base_of_template::value; template using is_compound_operator = polyfill::bool_constant>; @@ -6831,15 +8315,12 @@ namespace sqlite_orm { /** * UNION object type. */ - template - struct union_t : public compound_operator, union_base { - using left_type = typename compound_operator::left_type; - using right_type = typename compound_operator::right_type; - - union_t(left_type l, right_type r, bool all_) : - compound_operator{std::move(l), std::move(r)}, union_base{all_} {} + template + struct union_t : public compound_operator, union_base { + using typename compound_operator::expressions_tuple; - union_t(left_type l, right_type r) : union_t{std::move(l), std::move(r), false} {} + union_t(expressions_tuple compound, bool all) : + compound_operator{std::move(compound)}, union_base{all} {} }; struct except_string { @@ -6851,11 +8332,9 @@ namespace sqlite_orm { /** * EXCEPT object type. */ - template - struct except_t : compound_operator, except_string { - using super = compound_operator; - using left_type = typename super::left_type; - using right_type = typename super::right_type; + template + struct except_t : compound_operator, except_string { + using super = compound_operator; using super::super; }; @@ -6868,15 +8347,117 @@ namespace sqlite_orm { /** * INTERSECT object type. */ - template - struct intersect_t : compound_operator, intersect_string { - using super = compound_operator; - using left_type = typename super::left_type; - using right_type = typename super::right_type; + template + struct intersect_t : compound_operator, intersect_string { + using super = compound_operator; using super::super; }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /* + * Turn explicit columns for a CTE into types that the CTE backend understands + */ + template + struct decay_explicit_column { + using type = T; + }; + template + struct decay_explicit_column> { + using type = alias_holder; + }; + template + struct decay_explicit_column> { + using type = std::string; + }; + template + using decay_explicit_column_t = typename decay_explicit_column::type; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Materialization hint to instruct SQLite to materialize the select statement of a CTE into an ephemeral table as an "optimization fence". + */ + struct materialized_t {}; + + /* + * Materialization hint to instruct SQLite to substitute a CTE's select statement as a subquery subject to optimization. + */ + struct not_materialized_t {}; +#endif + + /** + * Monikered (aliased) CTE expression. + */ + template + struct common_table_expression { + using cte_moniker_type = Moniker; + using expression_type = Select; + using explicit_colrefs_tuple = ExplicitCols; + using hints_tuple = Hints; + static constexpr size_t explicit_colref_count = std::tuple_size_v; + + SQLITE_ORM_NOUNIQUEADDRESS hints_tuple hints; + explicit_colrefs_tuple explicitColumns; + expression_type subselect; + + common_table_expression(explicit_colrefs_tuple explicitColumns, expression_type subselect) : + explicitColumns{std::move(explicitColumns)}, subselect{std::move(subselect)} { + this->subselect.highest_level = true; + } + }; + + template + using common_table_expressions = std::tuple; + + template + struct cte_builder { + ExplicitCols explicitColumns; + +#if SQLITE_VERSION_NUMBER >= 3035000 && defined(SQLITE_ORM_WITH_CPP20_ALIASES) + template = true> + common_table_expression, Select> as(Select sel) && { + return {std::move(this->explicitColumns), std::move(sel)}; + } + + template = true> + common_table_expression, select_t> + as(Compound sel) && { + return {std::move(this->explicitColumns), {std::move(sel)}}; + } +#else + template = true> + common_table_expression, Select> as(Select sel) && { + return {std::move(this->explicitColumns), std::move(sel)}; + } + + template = true> + common_table_expression, select_t> as(Compound sel) && { + return {std::move(this->explicitColumns), {std::move(sel)}}; + } +#endif + }; + + /** + * WITH object type - expression with prepended CTEs. + */ + template + struct with_t { + using cte_type = common_table_expressions; + using expression_type = E; + + bool recursiveIndicated; + cte_type cte; + expression_type expression; + + with_t(bool recursiveIndicated, cte_type cte, expression_type expression) : + recursiveIndicated{recursiveIndicated}, cte{std::move(cte)}, expression{std::move(expression)} { + if constexpr(is_select_v) { + this->expression.highest_level = true; + } + } + }; +#endif + /** * Generic way to get DISTINCT value from any type. */ @@ -6890,6 +8471,11 @@ namespace sqlite_orm { return cols.distinct; } + template + bool get_distinct(const struct_t& cols) { + return cols.distinct; + } + template struct asterisk_t { using type = T; @@ -7013,19 +8599,21 @@ namespace sqlite_orm { return cols; } + /* + * Combine multiple columns in a tuple. + */ template - internal::columns_t columns(Args... args) { + constexpr internal::columns_t columns(Args... args) { return {std::make_tuple(std::forward(args)...)}; } - /** - * Use it like this: - * struct MyType : BaseType { ... }; - * storage.select(column(&BaseType::id)); + /* + * Construct an unmapped structure ad-hoc from multiple columns. + * `T` must be constructible from the column results using direct-list-initialization. */ - template - internal::column_pointer column(F f) { - return {std::move(f)}; + template + constexpr internal::struct_t struct_(Args... args) { + return {std::make_tuple(std::forward(args)...)}; } /** @@ -7035,43 +8623,262 @@ namespace sqlite_orm { internal::select_t select(T t, Args... args) { using args_tuple = std::tuple; internal::validate_conditions(); - return {std::move(t), std::make_tuple(std::forward(args)...)}; + return {std::move(t), {std::forward(args)...}}; } /** * Public function for UNION operator. - * lhs and rhs are subselect objects. + * Expressions are subselect objects. + * Look through example in examples/union.cpp + */ + template + internal::union_t union_(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}, false}; + } + + /** + * Public function for UNION ALL operator. + * Expressions are subselect objects. * Look through example in examples/union.cpp */ - template - internal::union_t union_(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + template + internal::union_t union_all(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}, true}; + } + + /** + * Public function for EXCEPT operator. + * Expressions are subselect objects. + * Look through example in examples/except.cpp + */ + template + internal::except_t except(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}}; + } + + template + internal::intersect_t intersect(E... expressions) { + static_assert(sizeof...(E) >= 2, "Compound operators must have at least 2 select statements"); + return {{std::forward(expressions)...}}; + } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#if SQLITE_VERSION_NUMBER >= 3035003 +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Materialization hint to instruct SQLite to materialize the select statement of a CTE into an ephemeral table as an "optimization fence". + * + * Example: + * 1_ctealias().as(select(1)); + */ + inline consteval internal::materialized_t materialized() { + return {}; + } + + /* + * Materialization hint to instruct SQLite to substitute a CTE's select statement as a subquery subject to optimization. + * + * Example: + * 1_ctealias().as(select(1)); + */ + inline consteval internal::not_materialized_t not_materialized() { + return {}; + } +#endif +#endif + + /** + * Introduce the construction of a common table expression using the specified moniker. + * + * The list of explicit columns is optional; + * if provided the number of columns must match the number of columns of the subselect. + * The column names will be merged with the subselect: + * 1. column names of subselect + * 2. explicit columns + * 3. fill in empty column names with column index + * + * Example: + * using cte_1 = decltype(1_ctealias); + * cte()(select(&Object::id)); + * cte(&Object::name)(select("object")); + */ + template, + std::is_member_pointer, + internal::is_column, + std::is_same>, + std::is_convertible>...>, + bool> = true> + auto cte(ExplicitCols... explicitColumns) { + using namespace ::sqlite_orm::internal; + static_assert(is_cte_moniker_v, "Moniker must be a CTE moniker"); + static_assert((!is_builtin_numeric_column_alias_v && ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + + using builder_type = + cte_builder, decay_explicit_column_t>>; + return builder_type{{std::move(explicitColumns)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires((internal::is_column_alias_v || std::is_member_pointer_v || + internal::is_column_v || + std::same_as> || + std::convertible_to) && + ...) + auto cte(ExplicitCols... explicitColumns) { + using namespace ::sqlite_orm::internal; + static_assert((!is_builtin_numeric_column_alias_v && ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); + + using builder_type = + cte_builder, decay_explicit_column_t>>; + return builder_type{{std::move(explicitColumns)...}}; + } +#endif + + namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + template + requires((is_column_alias_v || std::is_member_pointer_v || + std::same_as> || + std::convertible_to) && + ...) + auto cte_moniker::operator()(ExplicitCols... explicitColumns) const { + return cte>(std::forward(explicitColumns)...); + } +#else + template + template, + std::is_member_pointer, + std::is_same>, + std::is_convertible>...>, + bool>> + auto cte_moniker::operator()(ExplicitCols... explicitColumns) const { + return cte>(std::forward(explicitColumns)...); + } +#endif + } + + /** + * With-clause for a tuple of ordinary CTEs. + * + * Despite the missing RECURSIVE keyword, the CTEs can be recursive. + */ + template = true> + internal::with_t with(internal::common_table_expressions ctes, E expression) { + return {false, std::move(ctes), std::move(expression)}; + } + + /** + * With-clause for a tuple of ordinary CTEs. + * + * Despite the missing RECURSIVE keyword, the CTEs can be recursive. + */ + template = true> + internal::with_t, CTEs...> with(internal::common_table_expressions ctes, + Compound sel) { + return {false, std::move(ctes), sqlite_orm::select(std::move(sel))}; + } + + /** + * With-clause for a single ordinary CTE. + * + * Despite the missing `RECURSIVE` keyword, the CTE can be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies_not = true> + internal::with_t with(CTE cte, E expression) { + return {false, {std::move(cte)}, std::move(expression)}; + } + + /** + * With-clause for a single ordinary CTE. + * + * Despite the missing `RECURSIVE` keyword, the CTE can be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies = true> + internal::with_t, CTE> with(CTE cte, Compound sel) { + return {false, {std::move(cte)}, sqlite_orm::select(std::move(sel))}; + } + + /** + * With-clause for a tuple of potentially recursive CTEs. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + */ + template = true> + internal::with_t with_recursive(internal::common_table_expressions ctes, E expression) { + return {true, std::move(ctes), std::move(expression)}; } - /** - * Public function for EXCEPT operator. - * lhs and rhs are subselect objects. - * Look through example in examples/except.cpp + /** + * With-clause for a tuple of potentially recursive CTEs. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. */ - template - internal::except_t except(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + template = true> + internal::with_t, CTEs...> + with_recursive(internal::common_table_expressions ctes, Compound sel) { + return {true, std::move(ctes), sqlite_orm::select(std::move(sel))}; } - template - internal::intersect_t intersect(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs)}; + /** + * With-clause for a single potentially recursive CTE. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with_recursive(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); + */ + template = true, + internal::satisfies_not = true> + internal::with_t with_recursive(CTE cte, E expression) { + return {true, {std::move(cte)}, std::move(expression)}; } - /** - * Public function for UNION ALL operator. - * lhs and rhs are subselect objects. - * Look through example in examples/union.cpp + /** + * With-clause for a single potentially recursive CTE. + * + * @note The use of RECURSIVE does not force common table expressions to be recursive. + * + * Example: + * constexpr orm_cte_moniker auto cte_1 = 1_ctealias; + * with_recursive(cte_1().as(select(&Object::id)), select(cte_1->*1_colalias)); */ - template - internal::union_t union_all(L lhs, R rhs) { - return {std::move(lhs), std::move(rhs), true}; + template = true, + internal::satisfies = true> + internal::with_t, CTE> with_recursive(CTE cte, Compound sel) { + return {true, {std::move(cte)}, sqlite_orm::select(std::move(sel))}; } +#endif /** * `SELECT * FROM T` expression that fetches results as tuples. @@ -7097,6 +8904,19 @@ namespace sqlite_orm { return {definedOrder}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Example: + * constexpr orm_table_alias auto m = "m"_alias.for_(); + * auto reportingTo = + * storage.select(asterisk(), inner_join(on(m->*&Employee::reportsTo == &Employee::employeeId))); + */ + template + auto asterisk(bool definedOrder = false) { + return asterisk>(definedOrder); + } +#endif + /** * `SELECT * FROM T` expression that fetches results as objects of type T. * T is a type mapped to a storage, or an alias of it. @@ -7112,6 +8932,13 @@ namespace sqlite_orm { internal::object_t object(bool definedOrder = false) { return {definedOrder}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto object(bool definedOrder = false) { + return object>(definedOrder); + } +#endif } #pragma once @@ -7172,9 +8999,9 @@ namespace sqlite_orm { #include #include -// #include "functional/cxx_universal.h" +// #include "../functional/cxx_universal.h" -// #include "optional_container.h" +// #include "../optional_container.h" // NOTE Idea : Maybe also implement a custom trigger system to call a c++ callback when a trigger triggers ? // (Could be implemented with a normal trigger that insert or update an internal table and then retreive @@ -7458,19 +9285,40 @@ namespace sqlite_orm { #pragma once #include -#include // std::unique_ptr -#include // std::integral_constant +#include // std::enable_if_t, std::is_arithmetic, std::is_same, std::true_type, std::false_type, std::make_index_sequence, std::index_sequence +#include // std::default_delete +#include // std::string, std::wstring +#include // std::vector +#include // ::strncpy, ::strlen +// #include "functional/cxx_string_view.h" -namespace sqlite_orm { +#ifndef SQLITE_ORM_STRING_VIEW_SUPPORTED +#include // ::wcsncpy, ::wcslen +#endif +#ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert +#include // std::codecvt_utf8_utf16 +#endif - /** - * Guard class which finalizes `sqlite3_stmt` in dtor - */ - using statement_finalizer = - std::unique_ptr>; -} -#pragma once -#include +// #include "functional/cxx_universal.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "functional/cxx_functional_polyfill.h" + +// #include "is_std_ptr.h" + +// #include "tuple_helper/tuple_filter.h" + +// #include "type_traits.h" + +// #include "error_code.h" + +// #include "arithmetic_tag.h" + +#include // std::is_integral + +// #include "functional/mpl/conditional.h" namespace sqlite_orm { @@ -7483,24 +9331,17 @@ namespace sqlite_orm { template using arithmetic_tag_t = - std::conditional_t::value, + mpl::conditional_t::value, // Integer class - std::conditional_t, + mpl::conditional_t, // Floating-point class real_tag>; } -#pragma once - -#include -#include -#include - -// #include "functional/cxx_universal.h" // #include "xdestroy_handling.h" #include // std::integral_constant -#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && SQLITE_ORM_HAS_INCLUDE() +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include #endif @@ -7529,23 +9370,23 @@ namespace sqlite_orm { */ template concept integral_fp_c = requires { - typename D::value_type; - D::value; - requires std::is_function_v>; - }; + typename D::value_type; + D::value; + requires std::is_function_v>; + }; /** * Constrains a deleter to be or to yield a function pointer. */ template concept yields_fp = requires(D d) { - // yielding function pointer by using the plus trick - { +d }; - requires std::is_function_v>; - }; + // yielding function pointer by using the plus trick + { +d }; + requires std::is_function_v>; + }; #endif -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Yield a deleter's function pointer. */ @@ -7595,10 +9436,10 @@ namespace sqlite_orm { template using yielded_fn_t = typename yield_fp_of::type; -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template - concept is_unusable_for_xdestroy = (!stateless_deleter && - (yields_fp && !std::convertible_to, xdestroy_fn_t>)); + concept is_unusable_for_xdestroy = + (!stateless_deleter && (yields_fp && !std::convertible_to, xdestroy_fn_t>)); /** * This concept tests whether a deleter yields a function pointer, which is convertible to an xdestroy function pointer. @@ -7608,8 +9449,8 @@ namespace sqlite_orm { concept yields_xdestroy = yields_fp && std::convertible_to, xdestroy_fn_t>; template - concept needs_xdestroy_proxy = (stateless_deleter && - (!yields_fp || !std::convertible_to, xdestroy_fn_t>)); + concept needs_xdestroy_proxy = + (stateless_deleter && (!yields_fp || !std::convertible_to, xdestroy_fn_t>)); /** * xDestroy function that constructs and invokes the stateless deleter. @@ -7675,14 +9516,14 @@ namespace sqlite_orm { namespace sqlite_orm { -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Prohibits using a yielded function pointer, which is not of type xdestroy_fn_t. * * Explicitly declared for better error messages. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::is_unusable_for_xdestroy) { static_assert(polyfill::always_false_v, @@ -7702,8 +9543,8 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept requires(internal::needs_xdestroy_proxy) { return internal::xdestroy_proxy; @@ -7723,33 +9564,73 @@ namespace sqlite_orm { * Type-safety is garanteed by checking whether the deleter or yielded function pointer * is invocable with the non-q-qualified pointer value. */ - template - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept + template + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept requires(internal::yields_xdestroy) { return d; } #else - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) { static_assert(polyfill::always_false_v, "A function pointer, which is not of type xdestroy_fn_t, is prohibited."); return nullptr; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D, P* = nullptr) noexcept { return internal::xdestroy_proxy; } - template, bool> = true> - constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P*) noexcept { + template, bool> = true> + constexpr xdestroy_fn_t obtain_xdestroy_for(D d, P* = nullptr) noexcept { return d; } #endif } +// #include "pointer_value.h" + +#if SQLITE_VERSION_NUMBER >= 3020000 +#include +#include +#include +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif +#endif + +// #include "functional/cstring_literal.h" + +// #include "xdestroy_handling.h" + +#if SQLITE_VERSION_NUMBER >= 3020000 namespace sqlite_orm { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + namespace internal { + template + struct pointer_type { + using value_type = const char[sizeof...(C) + 1]; + static inline constexpr value_type value = {C..., '\0'}; + }; + } + + inline namespace literals { + template + [[nodiscard]] consteval auto operator"" _pointer_type() { + return internal::explode_into(std::make_index_sequence{}); + } + } + + /** @short Specifies that a type is an integral constant string usable as a pointer type. + */ + template + concept orm_pointer_type = requires { + typename T::value_type; + { T::value } -> std::convertible_to; + }; +#endif /** * Wraps a pointer and tags it with a pointer type, @@ -7758,16 +9639,24 @@ namespace sqlite_orm { * * Template parameters: * - P: The value type, possibly const-qualified. - * - T: An integral constant string denoting the pointer type, e.g. `carray_pvt_name`. + * - T: An integral constant string denoting the pointer type, e.g. `"carray"_pointer_type`. * */ template struct pointer_arg { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // note (internal): this is currently a static assertion instead of a type constraint because + // of forward declarations in other places (e.g. function.h) + static_assert(orm_pointer_type, "T must be a pointer type (tag)"); +#else static_assert(std::is_convertible::value, - "`std::integral_constant<>` must be convertible to `const char*`"); + "The pointer type (tag) must be convertible to `const char*`"); +#endif using tag = T; + using qualified_type = P; + P* p_; P* ptr() const noexcept { @@ -7785,6 +9674,8 @@ namespace sqlite_orm { * as part of facilitating the 'pointer-passing interface'. * * Template parameters: + * - P: The value type, possibly const-qualified. + * - T: An integral constant string denoting the pointer type, e.g. `carray_pointer_type`. * - D: The deleter for the pointer value; * can be one of: * - function pointer @@ -7810,11 +9701,16 @@ namespace sqlite_orm { D d_; protected: - // Constructing pointer bindings must go through bindable_pointer() + // Constructing pointer bindings must go through bind_pointer() template - friend auto bindable_pointer(P2*, D2) noexcept -> pointer_binding; + friend auto bind_pointer(P2*, D2) noexcept -> pointer_binding; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + // Constructing pointer bindings must go through bind_pointer() + template + friend auto bind_pointer(P2*, D2) noexcept -> pointer_binding; +#endif template - friend B bindable_pointer(typename B::qualified_type*, typename B::deleter_type) noexcept; + friend B bind_pointer(typename B::qualified_type*, typename B::deleter_type) noexcept; // Construct from pointer and deleter. // Transfers ownership of the passed in object. @@ -7855,17 +9751,33 @@ namespace sqlite_orm { }; /** - * Template alias for a static pointer value binding. + * Alias template for a static pointer value binding. * 'Static' means that ownership won't be transferred to sqlite, * sqlite doesn't delete it, and sqlite assumes the object * pointed to is valid throughout the lifetime of a statement. */ template using static_pointer_binding = pointer_binding; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using pointer_arg_t = pointer_arg; + + template + using pointer_binding_t = pointer_binding; + + /** + * Alias template for a static pointer value binding. + * 'Static' means that ownership won't be transferred to sqlite, + * sqlite doesn't delete it, and sqlite assumes the object + * pointed to is valid throughout the lifetime of a statement. + */ + template + using static_pointer_binding_t = pointer_binding_t; +#endif } namespace sqlite_orm { - /** * Wrap a pointer, its type and its deleter function for binding it to a statement. * @@ -7874,159 +9786,109 @@ namespace sqlite_orm { * the deleter when the statement finishes. */ template - auto bindable_pointer(P* p, D d) noexcept -> pointer_binding { + auto bind_pointer(P* p, D d) noexcept -> pointer_binding { return {p, std::move(d)}; } template - auto bindable_pointer(std::unique_ptr p) noexcept -> pointer_binding { - return bindable_pointer(p.release(), p.get_deleter()); + auto bind_pointer(std::unique_ptr p) noexcept -> pointer_binding { + return bind_pointer(p.release(), p.get_deleter()); } template - B bindable_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept { + auto bind_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept -> B { return B{p, std::move(d)}; } - /** - * Wrap a pointer and its type for binding it to a statement. - * - * Note: 'Static' means that ownership of the pointed-to-object won't be transferred - * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. - */ - template - auto statically_bindable_pointer(P* p) noexcept -> static_pointer_binding { - return bindable_pointer(p, null_xdestroy_f); + template + [[deprecated("Use the better named function `bind_pointer(...)`")]] pointer_binding + bindable_pointer(P* p, D d) noexcept { + return bind_pointer(p, std::move(d)); + } + + template + [[deprecated("Use the better named function `bind_pointer(...)`")]] pointer_binding + bindable_pointer(std::unique_ptr p) noexcept { + return bind_pointer(p.release(), p.get_deleter()); } template - B statically_bindable_pointer(typename B::qualified_type* p, - typename B::deleter_type* /*exposition*/ = nullptr) noexcept { - return bindable_pointer(p); + [[deprecated("Use the better named function `bind_pointer(...)`")]] B + bindable_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept { + return bind_pointer(p, std::move(d)); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Forward a pointer value from an argument. + * Wrap a pointer, its type (tag) and its deleter function for binding it to a statement. + * + * Unless the deleter yields a nullptr 'xDestroy' function the ownership of the pointed-to-object + * is transferred to the pointer binding, which will delete it through + * the deleter when the statement finishes. */ - template - auto rebind_statically(const pointer_arg& pv) noexcept -> static_pointer_binding { - return statically_bindable_pointer(pv.ptr()); + template + auto bind_pointer(P* p, D d) noexcept -> pointer_binding { + return {p, std::move(d)}; } -} -#pragma once - -#include -#include // std::enable_if_t, std::is_arithmetic, std::is_same, std::true_type, std::false_type, std::make_index_sequence, std::index_sequence -#include // std::default_delete -#include // std::string, std::wstring -#include // std::vector -#include // ::strncpy, ::strlen -// #include "functional/cxx_string_view.h" - -#ifndef SQLITE_ORM_STRING_VIEW_SUPPORTED -#include // ::wcsncpy, ::wcslen -#endif - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "functional/cxx_functional_polyfill.h" - -#include -#if __cpp_lib_invoke < 201411L -#include // std::enable_if, std::is_member_object_pointer, std::is_member_function_pointer -#endif -#include // std::forward - -#if __cpp_lib_invoke < 201411L -// #include "cxx_type_traits_polyfill.h" - -#endif -// #include "../member_traits/member_traits.h" - -namespace sqlite_orm { - namespace internal { - namespace polyfill { - // C++20 or later (unfortunately there's no feature test macro). - // Stupidly, clang says C++20, but `std::identity` was only implemented in libc++ 13 and libstd++-v3 10 - // (the latter is used on Linux). - // gcc got it right and reports C++20 only starting with v10. - // The check here doesn't care and checks the library versions in use. - // - // Another way of detection would be the constrained algorithms feature macro __cpp_lib_ranges -#if(__cplusplus >= 202002L) && \ - ((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10)) - using std::identity; -#else - struct identity { - template - constexpr T&& operator()(T&& v) const noexcept { - return std::forward(v); - } - using is_transparent = int; - }; + template + auto bind_pointer(std::unique_ptr p) noexcept -> pointer_binding { + return bind_pointer(p.release(), p.get_deleter()); + } #endif -#if __cpp_lib_invoke >= 201411L - using std::invoke; -#else - // pointer-to-data-member+object - template, - std::enable_if_t::value, bool> = true> - decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) { - return std::forward(object).*callable; - } - - // pointer-to-member-function+object - template, - std::enable_if_t::value, bool> = true> - decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) { - return (std::forward(object).*callable)(std::forward(args)...); - } - - // pointer-to-member+reference-wrapped object (expect `reference_wrapper::*`) - template>, - std::reference_wrapper>>, - bool> = true> - decltype(auto) invoke(Callable&& callable, std::reference_wrapper wrapper, Args&&... args) { - return invoke(std::forward(callable), wrapper.get(), std::forward(args)...); - } - - // functor - template - decltype(auto) invoke(Callable&& callable, Args&&... args) { - return std::forward(callable)(std::forward(args)...); - } -#endif - } + /** + * Wrap a pointer and its type for binding it to a statement. + * + * Note: 'Static' means that ownership of the pointed-to-object won't be transferred + * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. + */ + template + auto bind_pointer_statically(P* p) noexcept -> static_pointer_binding { + return bind_pointer(p, null_xdestroy_f); } - namespace polyfill = internal::polyfill; -} - -// #include "is_std_ptr.h" - -// #include "tuple_helper/tuple_filter.h" + template + B bind_pointer_statically(typename B::qualified_type* p, + typename B::deleter_type* /*exposition*/ = nullptr) noexcept { + return bind_pointer(p); + } -// #include "error_code.h" + template + [[deprecated("Use the better named function `bind_pointer_statically(...)`")]] static_pointer_binding + statically_bindable_pointer(P* p) noexcept { + return bind_pointer(p, null_xdestroy_f); + } -// #include "arithmetic_tag.h" + template + [[deprecated("Use the better named function `bind_pointer_statically(...)`")]] B + statically_bindable_pointer(typename B::qualified_type* p, + typename B::deleter_type* /*exposition*/ = nullptr) noexcept { + return bind_pointer(p); + } -// #include "xdestroy_handling.h" +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Wrap a pointer and its type (tag) for binding it to a statement. + * + * Note: 'Static' means that ownership of the pointed-to-object won't be transferred + * and sqlite assumes the object pointed to is valid throughout the lifetime of a statement. + */ + template + auto bind_pointer_statically(P* p) noexcept -> static_pointer_binding { + return bind_pointer(p, null_xdestroy_f); + } +#endif -// #include "pointer_value.h" + /** + * Forward a pointer value from an argument. + */ + template + auto rebind_statically(const pointer_arg& pv) noexcept -> static_pointer_binding { + return bind_pointer_statically(pv.ptr()); + } +} +#endif namespace sqlite_orm { @@ -8037,22 +9899,27 @@ namespace sqlite_orm { struct statement_binder; namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_preparable_v`. + */ + template + struct indirectly_test_bindable; template SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v())>> = true - // note : msvc 14.0 needs the parentheses constructor, otherwise `is_bindable` isn't recognised. - // The strangest thing is that this is mutually exclusive with `is_printable_v`. - ; + SQLITE_ORM_INLINE_VAR constexpr bool + is_bindable_v{})>>> = true; template - using is_bindable = polyfill::bool_constant>; - + struct is_bindable : polyfill::bool_constant> {}; } +#if SQLITE_VERSION_NUMBER >= 3020000 /** - * Specialization for 'pointer-passing interface'. + * Specialization for pointer bindings (part of the 'pointer-passing interface'). */ template struct statement_binder, void> { @@ -8071,12 +9938,13 @@ namespace sqlite_orm { sqlite3_result_pointer(context, (void*)value.take_ptr(), T::value, value.get_xdestroy()); } }; +#endif /** * Specialization for arithmetic types. */ template - struct statement_binder::value>> { + struct statement_binder> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { return this->bind(stmt, index, value, tag()); @@ -8119,13 +9987,13 @@ namespace sqlite_orm { */ template struct statement_binder, - std::is_same + std::enable_if_t, + std::is_same #ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED - , - std::is_same + , + std::is_same #endif - >>> { + >::value>> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { auto stringData = this->string_data(value); @@ -8159,13 +10027,13 @@ namespace sqlite_orm { #ifndef SQLITE_ORM_OMITS_CODECVT template struct statement_binder, - std::is_same + std::enable_if_t, + std::is_same #ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED - , - std::is_same + , + std::is_same #endif - >>> { + >::value>> { int bind(sqlite3_stmt* stmt, int index, const V& value) const { auto stringData = this->string_data(value); @@ -8229,7 +10097,8 @@ namespace sqlite_orm { template struct statement_binder< V, - std::enable_if_t::value && internal::is_bindable_v>>> { + std::enable_if_t::value && + internal::is_bindable>::value>> { using unqualified_type = std::remove_cv_t; int bind(sqlite3_stmt* stmt, int index, const V& value) const { @@ -8335,14 +10204,11 @@ namespace sqlite_orm { (this->bind(polyfill::invoke(project, std::get(tpl)), Idx), ...); } #else - template - void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { - this->bind(polyfill::invoke(project, std::get(tpl)), I); - (*this)(tpl, std::index_sequence{}, std::forward(project)); + template + void operator()(const Tpl& tpl, std::index_sequence, Projection project) const { + using Sink = int[sizeof...(Idx)]; + (void)Sink{(this->bind(polyfill::invoke(project, std::get(tpl)), Idx), 0)...}; } - - template - void operator()(const Tpl&, std::index_sequence<>, Projection) const {} #endif template @@ -8374,17 +10240,147 @@ namespace sqlite_orm { #include // std::system_error #include // std::string, std::wstring #ifndef SQLITE_ORM_OMITS_CODECVT -#include // std::wstring_convert, std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert +#include // std::codecvt_utf8_utf16 +#endif #include // std::vector #include // strlen -#include #include // std::copy #include // std::back_inserter #include // std::tuple, std::tuple_size, std::tuple_element +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif // #include "functional/cxx_universal.h" +// #include "functional/cxx_functional_polyfill.h" + +// #include "functional/static_magic.h" + +#ifndef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#include // std::false_type, std::true_type, std::integral_constant +#endif +#include // std::forward + +namespace sqlite_orm { + + // got from here + // https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co + namespace internal { + + // note: this is a class template accompanied with a variable template because older compilers (e.g. VC 2017) + // cannot handle a static lambda variable inside a template function + template + struct empty_callable_t { + template + R operator()(Args&&...) const { + return R(); + } + }; + template + constexpr empty_callable_t empty_callable{}; + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + template + decltype(auto) static_if([[maybe_unused]] T&& trueFn, [[maybe_unused]] F&& falseFn) { + if constexpr(B) { + return std::forward(trueFn); + } else { + return std::forward(falseFn); + } + } + + template + decltype(auto) static_if([[maybe_unused]] T&& trueFn) { + if constexpr(B) { + return std::forward(trueFn); + } else { + return empty_callable<>; + } + } + + template + void call_if_constexpr([[maybe_unused]] L&& lambda, [[maybe_unused]] Args&&... args) { + if constexpr(B) { + lambda(std::forward(args)...); + } + } +#else + template + decltype(auto) static_if(std::true_type, T&& trueFn, const F&) { + return std::forward(trueFn); + } + + template + decltype(auto) static_if(std::false_type, const T&, F&& falseFn) { + return std::forward(falseFn); + } + + template + decltype(auto) static_if(T&& trueFn, F&& falseFn) { + return static_if(std::integral_constant{}, std::forward(trueFn), std::forward(falseFn)); + } + + template + decltype(auto) static_if(T&& trueFn) { + return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable<>); + } + + template + void call_if_constexpr(L&& lambda, Args&&... args) { + static_if(std::forward(lambda))(std::forward(args)...); + } +#endif + } + +} + +// #include "tuple_helper/tuple_transformer.h" + +// #include "column_result_proxy.h" + +// #include "type_traits.h" + +// #include "table_reference.h" + +namespace sqlite_orm { + namespace internal { + + /* + * Holder for the type of an unmapped aggregate/structure/object to be constructed ad-hoc from column results. + * `T` must be constructible using direct-list-initialization. + */ + template + struct structure { + using type = T; + }; + } +} + +namespace sqlite_orm { + namespace internal { + + template + struct column_result_proxy : std::remove_const {}; + + /* + * Unwrap `table_reference` + */ + template + struct column_result_proxy> : decay_table_ref

{}; + + /* + * Pass through `structure` + */ + template + struct column_result_proxy> : P {}; + + template + using column_result_proxy_t = typename column_result_proxy::type; + } +} + // #include "arithmetic_tag.h" // #include "pointer_value.h" @@ -8467,34 +10463,106 @@ namespace sqlite_orm { // #include "is_std_ptr.h" +// #include "type_traits.h" + namespace sqlite_orm { /** - * Helper class used to cast values from argv to V class - * which depends from column type. - * + * Helper for casting values originating from SQL to C++ typed values, usually from rows of a result set. + * + * sqlite_orm provides specializations for known C++ types, users may define their custom specialization + * of this helper. + * + * @note (internal): Since row extractors are used in certain contexts with only one purpose at a time + * (e.g., converting a row result set but not function values or column text), + * there are factory functions that perform conceptual checking that should be used + * instead of directly creating row extractors. + * + * */ template struct row_extractor { - // used in sqlite3_exec (select) - V extract(const char* row_value) const = delete; + /* + * Called during one-step query execution (one result row) for each column of a result row. + */ + V extract(const char* columnText) const = delete; - // used in sqlite_column (iteration, get_all) + /* + * Called during multi-step query execution (result set) for each column of a result row. + */ V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; - // used in user defined functions + /* + * Called before invocation of user-defined scalar or aggregate functions, + * in order to unbox dynamically typed SQL function values into a tuple of C++ function arguments. + */ V extract(sqlite3_value* value) const = delete; }; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_column_text_extractable = requires(const row_extractor& extractor, const char* columnText) { + { extractor.extract(columnText) } -> std::same_as; + }; + + template + concept orm_row_value_extractable = + requires(const row_extractor& extractor, sqlite3_stmt* stmt, int columnIndex) { + { extractor.extract(stmt, columnIndex) } -> std::same_as; + }; + + template + concept orm_boxed_value_extractable = requires(const row_extractor& extractor, sqlite3_value* value) { + { extractor.extract(value) } -> std::same_as; + }; +#endif + + namespace internal { + /* + * Make a row extractor to be used for casting SQL column text to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + row_extractor column_text_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for converting a value from a SQL result row set to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + row_extractor row_value_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for unboxing a dynamically typed SQL value to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + row_extractor boxed_value_extractor() { + return {}; + } + } + template int extract_single_value(void* data, int argc, char** argv, char**) { auto& res = *(R*)data; if(argc) { - res = row_extractor{}.extract(argv[0]); + const auto rowExtractor = internal::column_text_extractor(); + res = rowExtractor.extract(argv[0]); } return 0; } +#if SQLITE_VERSION_NUMBER >= 3020000 /** * Specialization for the 'pointer-passing interface'. * @@ -8505,6 +10573,10 @@ namespace sqlite_orm { struct row_extractor, void> { using V = pointer_arg; + V extract(const char* columnText) const = delete; + + V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; + V extract(sqlite3_value* value) const { return {(P*)sqlite3_value_pointer(value, T::value)}; } @@ -8515,14 +10587,15 @@ namespace sqlite_orm { */ template struct row_extractor, void>; +#endif /** * Specialization for arithmetic types. */ template struct row_extractor::value>> { - V extract(const char* row_value) const { - return this->extract(row_value, tag()); + V extract(const char* columnText) const { + return this->extract(columnText, tag()); } V extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -8536,8 +10609,8 @@ namespace sqlite_orm { private: using tag = arithmetic_tag_t; - V extract(const char* row_value, const int_or_smaller_tag&) const { - return static_cast(atoi(row_value)); + V extract(const char* columnText, const int_or_smaller_tag&) const { + return static_cast(atoi(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const int_or_smaller_tag&) const { @@ -8548,8 +10621,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int(value)); } - V extract(const char* row_value, const bigint_tag&) const { - return static_cast(atoll(row_value)); + V extract(const char* columnText, const bigint_tag&) const { + return static_cast(atoll(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const bigint_tag&) const { @@ -8560,8 +10633,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int64(value)); } - V extract(const char* row_value, const real_tag&) const { - return static_cast(atof(row_value)); + V extract(const char* columnText, const real_tag&) const { + return static_cast(atof(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const real_tag&) const { @@ -8576,17 +10649,17 @@ namespace sqlite_orm { /** * Specialization for std::string. */ - template<> - struct row_extractor { - std::string extract(const char* row_value) const { - if(row_value) { - return row_value; + template + struct row_extractor::value>> { + T extract(const char* columnText) const { + if(columnText) { + return columnText; } else { return {}; } } - std::string extract(sqlite3_stmt* stmt, int columnIndex) const { + T extract(sqlite3_stmt* stmt, int columnIndex) const { if(auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex)) { return cStr; } else { @@ -8594,7 +10667,7 @@ namespace sqlite_orm { } } - std::string extract(sqlite3_value* value) const { + T extract(sqlite3_value* value) const { if(auto cStr = (const char*)sqlite3_value_text(value)) { return cStr; } else { @@ -8608,10 +10681,10 @@ namespace sqlite_orm { */ template<> struct row_extractor { - std::wstring extract(const char* row_value) const { - if(row_value) { + std::wstring extract(const char* columnText) const { + if(columnText) { std::wstring_convert> converter; - return converter.from_bytes(row_value); + return converter.from_bytes(columnText); } else { return {}; } @@ -8641,27 +10714,42 @@ namespace sqlite_orm { struct row_extractor::value>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return is_std_ptr::make(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(columnText)); } else { return {}; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(stmt, columnIndex)); } else { return {}; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(value)); } else { return {}; } @@ -8673,27 +10761,42 @@ namespace sqlite_orm { struct row_extractor>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return std::make_optional(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(columnText)); } else { return std::nullopt; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(stmt, columnIndex)); } else { return std::nullopt; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(value)); } else { return std::nullopt; } @@ -8702,8 +10805,8 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template<> - struct row_extractor { - nullptr_t extract(const char* /*row_value*/) const { + struct row_extractor { + nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -8719,9 +10822,9 @@ namespace sqlite_orm { * Specialization for std::vector. */ template<> - struct row_extractor> { - std::vector extract(const char* row_value) const { - return {row_value, row_value + (row_value ? ::strlen(row_value) : 0)}; + struct row_extractor, void> { + std::vector extract(const char* columnText) const { + return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } std::vector extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -8737,37 +10840,14 @@ namespace sqlite_orm { } }; - template - struct row_extractor> { - - std::tuple extract(char** argv) const { - return this->extract(argv, std::make_index_sequence{}); - } - - std::tuple extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - return this->extract(stmt, std::make_index_sequence{}); - } - - protected: - template - std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(stmt, Idx)...}; - } - - template - std::tuple extract(char** argv, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(argv[Idx])...}; - } - }; - /** * Specialization for journal_mode. */ template<> struct row_extractor { - journal_mode extract(const char* row_value) const { - if(row_value) { - if(auto res = internal::journal_mode_from_string(row_value)) { + journal_mode extract(const char* columnText) const { + if(columnText) { + if(auto res = internal::journal_mode_from_string(columnText)) { return std::move(*res); } else { throw std::system_error{orm_error_code::incorrect_journal_mode_string}; @@ -8780,137 +10860,130 @@ namespace sqlite_orm { journal_mode extract(sqlite3_stmt* stmt, int columnIndex) const { auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex); return this->extract(cStr); - } - }; -} -#pragma once - -#include -#include // std::string -#include // std::move - -// #include "error_code.h" - -namespace sqlite_orm { - - /** - * Escape the provided character in the given string by doubling it. - * @param str A copy of the original string - * @param char2Escape The character to escape - */ - inline std::string sql_escape(std::string str, char char2Escape) { - for(size_t pos = 0; (pos = str.find(char2Escape, pos)) != str.npos; pos += 2) { - str.replace(pos, 1, 2, char2Escape); - } - - return str; - } - - /** - * Quote the given string value using single quotes, - * escape containing single quotes by doubling them. - */ - inline std::string quote_string_literal(std::string v) { - constexpr char quoteChar = '\''; - return quoteChar + sql_escape(std::move(v), quoteChar) + quoteChar; - } - - /** - * Quote the given string value using single quotes, - * escape containing single quotes by doubling them. - */ - inline std::string quote_blob_literal(std::string v) { - constexpr char quoteChar = '\''; - return std::string{char('x'), quoteChar} + std::move(v) + quoteChar; - } + } - /** - * Quote the given identifier using double quotes, - * escape containing double quotes by doubling them. - */ - inline std::string quote_identifier(std::string identifier) { - constexpr char quoteChar = '"'; - return quoteChar + sql_escape(std::move(identifier), quoteChar) + quoteChar; - } + journal_mode extract(sqlite3_value* value) const = delete; + }; namespace internal { - // Wrapper to reduce boiler-plate code - inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { - sqlite3_reset(stmt); - return stmt; - } - // note: query is deliberately taken by value, such that it is thrown away early - inline sqlite3_stmt* prepare_stmt(sqlite3* db, std::string query) { - sqlite3_stmt* stmt; - if(sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { - throw_translated_sqlite_error(db); + /* + * Helper to extract a structure from a rowset. + */ + template + struct struct_extractor; + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + /* + * Returns a value-based row extractor for an unmapped type, + * returns a structure extractor for a table reference, tuple or named struct. + */ + template + auto make_row_extractor([[maybe_unused]] const DBOs& dbObjects) { + if constexpr(polyfill::is_specialization_of_v || + polyfill::is_specialization_of_v || is_table_reference_v) { + return struct_extractor{dbObjects}; + } else { + return row_value_extractor(); } - return stmt; } +#else + /* + * Overload for an unmapped type returns a common row extractor. + */ + template< + class R, + class DBOs, + std::enable_if_t, + polyfill::is_specialization_of, + is_table_reference>>::value, + bool> = true> + auto make_row_extractor(const DBOs& /*dbObjects*/) { + return row_value_extractor(); + } + + /* + * Overload for a table reference, tuple or aggregate of column results returns a structure extractor. + */ + template, + polyfill::is_specialization_of, + is_table_reference>::value, + bool> = true> + struct_extractor make_row_extractor(const DBOs& dbObjects) { + return {dbObjects}; + } +#endif - inline void perform_void_exec(sqlite3* db, const std::string& query) { - int rc = sqlite3_exec(db, query.c_str(), nullptr, nullptr, nullptr); - if(rc != SQLITE_OK) { - throw_translated_sqlite_error(db); + /** + * Specialization for a tuple of top-level column results. + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + std::tuple extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + std::tuple...> extract(sqlite3_stmt* stmt, + int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return {make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; } - } - inline void perform_exec(sqlite3* db, - const char* query, - int (*callback)(void* data, int argc, char** argv, char**), - void* user_data) { - int rc = sqlite3_exec(db, query, callback, user_data, nullptr); - if(rc != SQLITE_OK) { - throw_translated_sqlite_error(db); + // unused to date + std::tuple...> extract(sqlite3_stmt* stmt, int& columnIndex) const = delete; + + std::tuple extract(sqlite3_value* value) const = delete; + }; + + /** + * Specialization for an unmapped structure to be constructed ad-hoc from column results. + * + * This plays together with `column_result_of_t`, which returns `struct_t` as `structure` + */ + template + struct struct_extractor>, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + // see unit test tests/prepared_statement_tests/select.cpp/TEST_CASE("Prepared select")/SECTION("non-aggregate struct") + template = true> + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; } - } - inline void perform_exec(sqlite3* db, - const std::string& query, - int (*callback)(void* data, int argc, char** argv, char**), - void* user_data) { - return perform_exec(db, query.c_str(), callback, user_data); - } + template = true> + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + std::tuple t{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + return create_from_tuple(std::move(t), std::index_sequence_for{}); + } - template - void perform_step(sqlite3_stmt* stmt) { - int rc = sqlite3_step(stmt); - if(rc != expected) { - throw_translated_sqlite_error(stmt); + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + // see unit test tests/prepared_statement_tests/select.cpp/TEST_CASE("Prepared select")/SECTION("non-aggregate struct") + template = true> + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; } - } - template - void perform_step(sqlite3_stmt* stmt, L&& lambda) { - switch(int rc = sqlite3_step(stmt)) { - case SQLITE_ROW: { - lambda(stmt); - } break; - case SQLITE_DONE: - break; - default: { - throw_translated_sqlite_error(stmt); - } + template = true> + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + // note: brace-init-list initialization guarantees order of evaluation, but only for aggregates and variadic constructors it seems. + std::tuple t{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + return create_from_tuple(std::move(t), std::index_sequence_for{}); } - } - template - void perform_steps(sqlite3_stmt* stmt, L&& lambda) { - int rc; - do { - switch(rc = sqlite3_step(stmt)) { - case SQLITE_ROW: { - lambda(stmt); - } break; - case SQLITE_DONE: - break; - default: { - throw_translated_sqlite_error(stmt); - } - } - } while(rc != SQLITE_DONE); - } + O extract(sqlite3_value* value) const = delete; + }; } } #pragma once @@ -8980,11 +11053,11 @@ namespace sqlite_orm { #include // std::string #include // std::forward -// #include "functional/cxx_universal.h" +// #include "../functional/cxx_universal.h" -// #include "tuple_helper/tuple_filter.h" +// #include "../tuple_helper/tuple_traits.h" -// #include "indexed_column.h" +// #include "../indexed_column.h" #include // std::string #include // std::move @@ -9056,7 +11129,7 @@ namespace sqlite_orm { } -// #include "table_type_of.h" +// #include "../table_type_of.h" namespace sqlite_orm { @@ -9190,243 +11263,57 @@ namespace sqlite_orm { #pragma once #include // std::string -#include // std::remove_reference, std::is_same, std::decay +#include // std::remove_const, std::is_member_pointer, std::true_type, std::false_type #include // std::vector -#include // std::tuple_size, std::tuple_element -#include // std::forward, std::move - -// #include "functional/cxx_universal.h" - -// #include "functional/cxx_type_traits_polyfill.h" - -// #include "functional/cxx_functional_polyfill.h" - -// #include "functional/static_magic.h" - -#ifndef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED -#include // std::false_type, std::true_type, std::integral_constant -#endif -#include // std::forward - -namespace sqlite_orm { - - // got from here - // https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co - namespace internal { - - template - decltype(auto) empty_callable() { - static auto res = [](auto&&...) -> R { - return R(); - }; - return (res); - } - -#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED - template - decltype(auto) static_if([[maybe_unused]] T&& trueFn, [[maybe_unused]] F&& falseFn) { - if constexpr(B) { - return std::forward(trueFn); - } else { - return std::forward(falseFn); - } - } - - template - decltype(auto) static_if([[maybe_unused]] T&& trueFn) { - if constexpr(B) { - return std::forward(trueFn); - } else { - return empty_callable(); - } - } - - template - void call_if_constexpr([[maybe_unused]] L&& lambda, [[maybe_unused]] Args&&... args) { - if constexpr(B) { - lambda(std::forward(args)...); - } - } -#else - template - decltype(auto) static_if(std::true_type, T&& trueFn, const F&) { - return std::forward(trueFn); - } - - template - decltype(auto) static_if(std::false_type, const T&, F&& falseFn) { - return std::forward(falseFn); - } - - template - decltype(auto) static_if(T&& trueFn, F&& falseFn) { - return static_if(std::integral_constant{}, std::forward(trueFn), std::forward(falseFn)); - } - - template - decltype(auto) static_if(T&& trueFn) { - return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable()); - } - - template - void call_if_constexpr(L&& lambda, Args&&... args) { - static_if(std::forward(lambda))(std::forward(args)...); - } -#endif - } - -} - -// #include "functional/mpl.h" - -// #include "functional/index_sequence_util.h" - -// #include "tuple_helper/tuple_filter.h" - -// #include "tuple_helper/tuple_traits.h" - -// #include "tuple_helper/tuple_iteration.h" - -#include // std::tuple, std::get, std::tuple_element, std::tuple_size -#include // std::index_sequence, std::make_index_sequence +#include // std::tuple_element #include // std::forward, std::move // #include "../functional/cxx_universal.h" - +// ::size_t // #include "../functional/cxx_type_traits_polyfill.h" // #include "../functional/cxx_functional_polyfill.h" -// #include "../functional/index_sequence_util.h" - -namespace sqlite_orm { - namespace internal { +// #include "../functional/static_magic.h" - // got it form here https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer - template - auto call_impl(Function& f, FunctionPointer functionPointer, Tuple t, std::index_sequence) { - return (f.*functionPointer)(std::get(std::move(t))...); - } +// #include "../functional/mpl.h" - template - auto call(Function& f, FunctionPointer functionPointer, Tuple t) { - constexpr size_t size = std::tuple_size::value; - return call_impl(f, functionPointer, std::move(t), std::make_index_sequence{}); - } +// #include "../functional/index_sequence_util.h" - template - auto call(Function& f, Tuple t) { - return call(f, &Function::operator(), std::move(t)); - } +// #include "../tuple_helper/tuple_filter.h" -#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) - template - void iterate_tuple(const Tpl& tpl, std::index_sequence, L&& lambda) { - if constexpr(reversed) { - // nifty fold expression trick: make use of guaranteed right-to-left evaluation order when folding over operator= - int sink; - ((lambda(std::get(tpl)), sink) = ... = 0); - } else { - (lambda(std::get(tpl)), ...); - } - } -#else - template - void iterate_tuple(const Tpl& /*tpl*/, std::index_sequence<>, L&& /*lambda*/) {} +// #include "../tuple_helper/tuple_traits.h" - template - void iterate_tuple(const Tpl& tpl, std::index_sequence, L&& lambda) { -#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED - if constexpr(reversed) { -#else - if(reversed) { -#endif - iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); - lambda(std::get(tpl)); - } else { - lambda(std::get(tpl)); - iterate_tuple(tpl, std::index_sequence{}, std::forward(lambda)); - } - } -#endif - template - void iterate_tuple(const Tpl& tpl, L&& lambda) { - iterate_tuple(tpl, - std::make_index_sequence::value>{}, - std::forward(lambda)); - } +// #include "../tuple_helper/tuple_iteration.h" -#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED - template - void iterate_tuple(std::index_sequence, L&& lambda) { - (lambda((std::tuple_element_t*)nullptr), ...); - } -#else - template - void iterate_tuple(std::index_sequence<>, L&& /*lambda*/) {} +// #include "../tuple_helper/tuple_transformer.h" - template - void iterate_tuple(std::index_sequence, L&& lambda) { - lambda((std::tuple_element_t*)nullptr); - iterate_tuple(std::index_sequence{}, std::forward(lambda)); - } -#endif - template - void iterate_tuple(L&& lambda) { - iterate_tuple(std::make_index_sequence::value>{}, std::forward(lambda)); - } +// #include "../member_traits/member_traits.h" - template - R create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { - return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; - } +// #include "../typed_comparator.h" - template - R create_from_tuple(Tpl&& tpl, Projection project = {}) { - return create_from_tuple( - std::forward(tpl), - std::make_index_sequence>::value>{}, - std::forward(project)); - } +namespace sqlite_orm { - template class Base, class L> - struct lambda_as_template_base : L { -#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED - lambda_as_template_base(L&& lambda) : L{std::move(lambda)} {} -#endif - template - decltype(auto) operator()(const Base& object) { - return L::operator()(object); - } - }; + namespace internal { - /* - * This method wraps the specified callable in another function object, - * which in turn implicitly casts its single argument to the specified template base class, - * then passes the converted argument to the lambda. - * - * Note: This method is useful for reducing combinatorial instantiation of template lambdas, - * as long as this library supports compilers that do not implement - * explicit template parameters in generic lambdas [SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED]. - * Unfortunately it doesn't work with user-defined conversion operators in order to extract - * parts of a class. In other words, the destination type must be a direct template base class. - */ - template class Base, class L> - lambda_as_template_base call_as_template_base(L lambda) { - return {std::move(lambda)}; + template + bool compare_any(const L& /*lhs*/, const R& /*rhs*/) { + return false; + } + template + bool compare_any(const O& lhs, const O& rhs) { + return lhs == rhs; } } } -// #include "member_traits/member_traits.h" - -// #include "typed_comparator.h" +// #include "../type_traits.h" -// #include "type_traits.h" +// #include "../alias_traits.h" -// #include "constraints.h" +// #include "../constraints.h" -// #include "table_info.h" +// #include "../table_info.h" // #include "column.h" @@ -9434,6 +11321,34 @@ namespace sqlite_orm { namespace internal { + template + using is_table_element_or_constraint = mpl::invoke_t, + check_if, + check_if, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template>, + T>; + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * A subselect mapper's CTE moniker, void otherwise. + */ + template + using moniker_of_or_void_t = polyfill::detected_or_t; + + /** + * If O is a subselect_mapper then returns its nested type name O::cte_moniker_type, + * otherwise O itself is a regular object type to be mapped. + */ + template + using mapped_object_type_for_t = polyfill::detected_or_t; +#endif + struct basic_table { /** @@ -9447,10 +11362,19 @@ namespace sqlite_orm { */ template struct table_t : basic_table { +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // this typename is used in contexts where it is known that the 'table' holds a subselect_mapper + // instead of a regular object type + using cte_mapper_type = O; + using cte_moniker_type = moniker_of_or_void_t; + using object_type = mapped_object_type_for_t; +#else using object_type = O; +#endif using elements_type = std::tuple; static constexpr bool is_without_rowid_v = WithoutRowId; + using is_without_rowid = polyfill::bool_constant; elements_type elements; @@ -9464,16 +11388,31 @@ namespace sqlite_orm { return {this->name, this->elements}; } - /** - * Returns foreign keys count in table definition + /* + * Returns the number of elements of the specified type. */ - constexpr int foreign_keys_count() const { -#if SQLITE_VERSION_NUMBER >= 3006019 - using fk_index_sequence = filter_tuple_sequence_t; - return int(fk_index_sequence::size()); -#else - return 0; -#endif + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using filtered_index_sequence = col_index_sequence_with; + return int(filtered_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); } /** @@ -9515,8 +11454,8 @@ namespace sqlite_orm { using generated_op_index_sequence = filter_tuple_sequence_t, is_generated_always>; - constexpr size_t opIndex = first_index_sequence_value(generated_op_index_sequence{}); - result = &get(column.constraints).storage; + constexpr size_t opIndex = index_sequence_value_at<0>(generated_op_index_sequence{}); + result = &std::get(column.constraints).storage; }); #else (void)name; @@ -9524,25 +11463,6 @@ namespace sqlite_orm { return result; } - template - bool exists_in_composite_primary_key(const column_field& column) const { - bool res = false; - this->for_each_primary_key([&column, &res](auto& primaryKey) { - using colrefs_tuple = decltype(primaryKey.columns); - using same_type_index_sequence = - filter_tuple_sequence_t>::template fn, - member_field_type_t>; - iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { - if(compare_any(memberPointer, column.member_pointer) || - compare_any(memberPointer, column.setter)) { - res = true; - } - }); - }); - return res; - } - /** * Call passed lambda with all defined primary keys. */ @@ -9615,27 +11535,6 @@ namespace sqlite_orm { return res; } - /** - * Counts and returns amount of columns without GENERATED ALWAYS constraints. Skips table constraints. - */ - constexpr int non_generated_columns_count() const { -#if SQLITE_VERSION_NUMBER >= 3031000 - using non_generated_col_index_sequence = - col_index_sequence_excluding; - return int(non_generated_col_index_sequence::size()); -#else - return this->count_columns_amount(); -#endif - } - - /** - * Counts and returns amount of columns. Skips constraints. - */ - constexpr int count_columns_amount() const { - using col_index_sequence = filter_tuple_sequence_t; - return int(col_index_sequence::size()); - } - /** * Call passed lambda with all defined foreign keys. * @param lambda Lambda called for each column. Function signature: `void(auto& column)` @@ -9679,9 +11578,9 @@ namespace sqlite_orm { * Call passed lambda with columns not having the specified constraint trait `OpTrait`. * @param lambda Lambda called for each column. */ - template = true> + template = true> void for_each_column_excluding(L&& lambda) const { - this->for_each_column_excluding(lambda); + this->template for_each_column_excluding(lambda); } std::vector get_table_info() const; @@ -9692,38 +11591,198 @@ namespace sqlite_orm { template struct is_table> : std::true_type {}; + + template + struct virtual_table_t : basic_table { + using module_details_type = M; + using object_type = typename module_details_type::object_type; + using elements_type = typename module_details_type::columns_type; + + static constexpr bool is_without_rowid_v = false; + using is_without_rowid = polyfill::bool_constant; + + module_details_type module_details; + +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + virtual_table_t(std::string name, module_details_type module_details) : + basic_table{std::move(name)}, module_details{std::move(module_details)} {} +#endif + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + this->module_details.template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->module_details.template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + this->module_details.for_each_column(lambda); + } + }; + + template + struct is_virtual_table : std::false_type {}; + + template + struct is_virtual_table> : std::true_type {}; + +#if SQLITE_VERSION_NUMBER >= 3009000 + template + struct using_fts5_t { + using object_type = T; + using columns_type = std::tuple; + + columns_type columns; + + using_fts5_t(columns_type columns) : columns(std::move(columns)) {} + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->columns, col_index_sequence_excluding{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + using col_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->columns, col_index_sequence{}, lambda); + } + }; +#endif + + template + bool exists_in_composite_primary_key(const table_t& table, + const column_field& column) { + bool res = false; + table.for_each_primary_key([&column, &res](auto& primaryKey) { + using colrefs_tuple = decltype(primaryKey.columns); + using same_type_index_sequence = + filter_tuple_sequence_t>::template fn, + member_field_type_t>; + iterate_tuple(primaryKey.columns, same_type_index_sequence{}, [&res, &column](auto& memberPointer) { + if(compare_any(memberPointer, column.member_pointer) || compare_any(memberPointer, column.setter)) { + res = true; + } + }); + }); + return res; + } + + template + bool exists_in_composite_primary_key(const virtual_table_t& /*virtualTable*/, + const column_field& /*column*/) { + return false; + } + } + +#if SQLITE_VERSION_NUMBER >= 3009000 + template>::object_type> + internal::using_fts5_t using_fts5(Cs... columns) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(columns)...)}); } + template + internal::using_fts5_t using_fts5(Cs... columns) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(columns)...)}); + } +#endif + /** * Factory function for a table definition. - * + * * The mapped object type is determined implicitly from the first column definition. */ template>::object_type> internal::table_t make_table(std::string name, Cs... args) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( return {std::move(name), std::make_tuple(std::forward(args)...)}); } /** * Factory function for a table definition. - * + * * The mapped object type is explicitly specified. */ template internal::table_t make_table(std::string name, Cs... args) { + static_assert(polyfill::conjunction_v...>, + "Incorrect table elements or constraints"); + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( return {std::move(name), std::make_tuple(std::forward(args)...)}); } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a table definition. + * + * The mapped object type is explicitly specified. + */ + template + auto make_table(std::string name, Cs... args) { + return make_table>(std::move(name), std::forward(args)...); + } +#endif + + template + internal::virtual_table_t make_virtual_table(std::string name, M module_details) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); + } } #pragma once #include // std::string // #include "functional/cxx_universal.h" -// ::nullptr_t +// ::size_t // #include "functional/static_magic.h" +// #include "functional/index_sequence_util.h" + +// #include "tuple_helper/tuple_traits.h" + // #include "tuple_helper/tuple_filter.h" // #include "tuple_helper/tuple_iteration.h" @@ -9732,6 +11791,90 @@ namespace sqlite_orm { // #include "select_constraints.h" +// #include "cte_types.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#endif + +// #include "functional/cxx_core_features.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "tuple_helper/tuple_fy.h" + +#include + +namespace sqlite_orm { + + namespace internal { + + template + struct tuplify { + using type = std::tuple; + }; + template + struct tuplify> { + using type = std::tuple; + }; + + template + using tuplify_t = typename tuplify::type; + } +} + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +namespace sqlite_orm { + + namespace internal { + + /** + * Aliased column expression mapped into a CTE, stored as a field in a table column. + */ + template + struct aliased_field { + ~aliased_field() = delete; + aliased_field(const aliased_field&) = delete; + void operator=(const aliased_field&) = delete; + + F field; + }; + + /** + * This class captures various properties and aspects of a subselect's column expression, + * and is used as a proxy in table_t<>. + */ + template + class subselect_mapper { + public: + subselect_mapper() = delete; + + // this type name is used to detect the mapping from moniker to object + using cte_moniker_type = Moniker; + using fields_type = std::tuple; + // this type captures the expressions forming the columns in a subselect; + // it is currently unused, however proves to be useful in compilation errors, + // as it simplifies recognizing errors in column expressions + using expressions_tuple = tuplify_t; + // this type captures column reference expressions specified at CTE construction; + // those are: member pointers, alias holders + using explicit_colrefs_tuple = ExplicitColRefs; + // this type captures column reference expressions from the subselect; + // those are: member pointers, alias holders + using subselect_colrefs_tuple = SubselectColRefs; + // this type captures column reference expressions merged from SubselectColRefs and ExplicitColRefs + using final_colrefs_tuple = FinalColRefs; + }; + } +} +#endif + // #include "storage_lookup.h" #include // std::true_type, std::false_type, std::remove_const, std::enable_if, std::is_base_of, std::is_void @@ -9739,7 +11882,7 @@ namespace sqlite_orm { #include // std::index_sequence, std::make_index_sequence // #include "functional/cxx_universal.h" - +// ::size_t // #include "functional/cxx_type_traits_polyfill.h" // #include "type_traits.h" @@ -9753,6 +11896,10 @@ namespace sqlite_orm { template using db_objects_tuple = std::tuple; + struct basic_table; + struct index_base; + struct base_trigger; + template struct is_storage : std::false_type {}; @@ -9765,12 +11912,14 @@ namespace sqlite_orm { struct is_db_objects : std::false_type {}; template - struct is_db_objects> : std::true_type {}; + struct is_db_objects> : std::true_type {}; + // note: cannot use `db_objects_tuple` alias template because older compilers have problems + // to match `const db_objects_tuple`. template - struct is_db_objects> : std::true_type {}; + struct is_db_objects> : std::true_type {}; /** - * std::true_type if given object is mapped, std::false_type otherwise. + * `std::true_type` if given object is mapped, `std::false_type` otherwise. * * Note: unlike table_t<>, index_t<>::object_type and trigger_t<>::object_type is always void. */ @@ -9779,10 +11928,10 @@ namespace sqlite_orm { std::is_same>> {}; /** - * std::true_type if given lookup type (object) is mapped, std::false_type otherwise. + * `std::true_type` if given lookup type (object or moniker) is mapped, `std::false_type` otherwise. */ template - struct lookup_type_matches : polyfill::disjunction> {}; + using lookup_type_matches = object_type_matches; } // pick/lookup metafunctions @@ -9827,7 +11976,7 @@ namespace sqlite_orm { * Lookup - mapped data type */ template - struct storage_find_table : polyfill::detected_or {}; + struct storage_find_table : polyfill::detected {}; /** * Find a table definition (`table_t`) from a tuple of database objects; @@ -9870,12 +12019,16 @@ namespace sqlite_orm { return std::get(dbObjects); } - template = true> - auto lookup_table(const DBOs& dbObjects); + /** + * Return passed in DBOs. + */ + template = true> + decltype(auto) db_objects_for_expression(DBOs& dbObjects, const E&) { + return dbObjects; + } template = true> decltype(auto) lookup_table_name(const DBOs& dbObjects); - } } @@ -9890,27 +12043,18 @@ namespace sqlite_orm { int foreign_keys_count(const DBOs& dbObjects) { int res = 0; iterate_tuple(dbObjects, tables_index_sequence{}, [&res](const auto& table) { - res += table.foreign_keys_count(); + res += table.template count_of(); }); - return res; - } - - template> - auto lookup_table(const DBOs& dbObjects) { - return static_if>( - [](const auto& dbObjects) { - return &pick_table(dbObjects); - }, - empty_callable())(dbObjects); + return res; } template> decltype(auto) lookup_table_name(const DBOs& dbObjects) { - return static_if>( + return static_if::value>( [](const auto& dbObjects) -> const std::string& { return pick_table(dbObjects).name; }, - empty_callable())(dbObjects); + empty_callable)(dbObjects); } /** @@ -9924,21 +12068,76 @@ namespace sqlite_orm { /** * Materialize column pointer: * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer& cp) { return cp.field; } +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Materialize column pointer: + * 3. by moniker and alias_holder<>. + * + * internal note: there's an overload for `find_column_name()` that avoids going through `table_t<>::find_column_name()` + */ + template = true> + constexpr decltype(auto) materialize_column_pointer(const DBOs&, + const column_pointer>&) { + using table_type = storage_pick_table_t; + using cte_mapper_type = cte_mapper_type_t; + + // lookup ColAlias in the final column references + using colalias_index = + find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, + "No such column mapped into the CTE."); + + return &aliased_field< + ColAlias, + std::tuple_element_t>::field; + } +#endif + /** * Find column name by: * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. */ template = true> const std::string* find_column_name(const DBOs& dbObjects, const column_pointer& cp) { auto field = materialize_column_pointer(dbObjects, cp); return pick_table(dbObjects).find_column_name(field); } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /** + * Find column name by: + * 3. by moniker and alias_holder<>. + */ + template = true> + constexpr decltype(auto) find_column_name(const DBOs& dboObjects, + const column_pointer>&) { + using table_type = storage_pick_table_t; + using cte_mapper_type = cte_mapper_type_t; + using column_index_sequence = filter_tuple_sequence_t, is_column>; + + // note: even though the columns contain the [`aliased_field<>::*`] we perform the lookup using the column references. + // lookup ColAlias in the final column references + using colalias_index = + find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, + "No such column mapped into the CTE."); + + // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `table_t<>::find_column_name()` mechanism; + // however we have the column index already. + // lookup column in table_t<>'s elements + constexpr size_t ColIdx = index_sequence_value_at(column_index_sequence{}); + auto& table = pick_table(dboObjects); + return &std::get(table.elements).name; + } +#endif } } #pragma once @@ -9955,8 +12154,8 @@ namespace sqlite_orm { namespace internal { - template - std::string serialize(const T&, const serializer_context&); + template + auto serialize(const T& t, const C& context); /** * Serialize default value of a column's default valu @@ -9999,125 +12198,191 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_filter.h" +// #include "tuple_helper/tuple_transformer.h" + // #include "tuple_helper/tuple_iteration.h" // #include "type_traits.h" // #include "alias.h" -// #include "row_extractor_builder.h" - -// #include "functional/cxx_universal.h" - -// #include "row_extractor.h" - -// #include "mapped_row_extractor.h" - -#include - -// #include "object_from_column_builder.h" - -#include -#include // std::is_member_object_pointer - -// #include "functional/static_magic.h" - -// #include "row_extractor.h" +// #include "error_code.h" -namespace sqlite_orm { +// #include "type_printer.h" - namespace internal { +// #include "constraints.h" - struct object_from_column_builder_base { - sqlite3_stmt* stmt = nullptr; - int index = 0; +// #include "field_printer.h" -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} +#include // std::string +#include // std::stringstream +#include // std::vector +#include // std::shared_ptr, std::unique_ptr +#ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert +#include // std::codecvt_utf8_utf16 #endif - }; +// #include "functional/cxx_optional.h" - /** - * This is a cute lambda replacement which is used in several places. - */ - template - struct object_from_column_builder : object_from_column_builder_base { - using object_type = O; +// #include "functional/cxx_universal.h" - object_type& object; +// #include "functional/cxx_type_traits_polyfill.h" - object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : - object_from_column_builder_base{stmt_}, object(object_) {} +// #include "is_std_ptr.h" - template - void operator()(const column_field& column) { - auto value = row_extractor>().extract(this->stmt, this->index++); - static_if::value>( - [&value, &object = this->object](const auto& column) { - object.*column.member_pointer = std::move(value); - }, - [&value, &object = this->object](const auto& column) { - (object.*column.setter)(std::move(value)); - })(column); - } - }; - } -} +// #include "type_traits.h" namespace sqlite_orm { - namespace internal { + /** + * Is used to print members mapped to objects in storage_t::dump member function. + * Other developers can create own specialization to map custom types + */ + template + struct field_printer; - /** - * This is a private row extractor class. It is used for extracting rows as objects instead of tuple. - * Main difference from regular `row_extractor` is that this class takes table info which is required - * for constructing objects by member pointers. To construct please use `make_row_extractor()`. - * Type arguments: - * V is value type just like regular `row_extractor` has - * T is table info class `table_t` + namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_preparable_v`, `is_bindable_v`. */ - template - struct mapped_row_extractor { - using table_type = Table; - - V extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - V res; - object_from_column_builder builder{res, stmt}; - this->tableInfo.for_each_column(builder); - return res; - } + template + struct indirectly_test_printable; - const table_type& tableInfo; - }; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_printable_v{})>>> = true; + template + struct is_printable : polyfill::bool_constant> {}; } -} - -namespace sqlite_orm { + template + struct field_printer> { + std::string operator()(const T& t) const { + std::stringstream ss; + ss << t; + return ss.str(); + } + }; - namespace internal { + /** + * Upgrade to integer is required when using unsigned char(uint8_t) + */ + template<> + struct field_printer { + std::string operator()(const unsigned char& t) const { + std::stringstream ss; + ss << +t; + return ss.str(); + } + }; - template - row_extractor make_row_extractor(nullptr_t) { - return {}; + /** + * Upgrade to integer is required when using signed char(int8_t) + */ + template<> + struct field_printer { + std::string operator()(const signed char& t) const { + std::stringstream ss; + ss << +t; + return ss.str(); } + }; - template - mapped_row_extractor make_row_extractor(const Table* table) { - return {*table}; + /** + * char is neither signed char nor unsigned char so it has its own specialization + */ + template<> + struct field_printer { + std::string operator()(const char& t) const { + std::stringstream ss; + ss << +t; + return ss.str(); } - } + }; -} + template + struct field_printer> { + std::string operator()(std::string string) const { + return string; + } + }; -// #include "error_code.h" + template<> + struct field_printer, void> { + std::string operator()(const std::vector& t) const { + std::stringstream ss; + ss << std::hex; + for(auto c: t) { + ss << c; + } + return ss.str(); + } + }; +#ifndef SQLITE_ORM_OMITS_CODECVT + /** + * Specialization for std::wstring (UTF-16 assumed). + */ + template + struct field_printer> { + std::string operator()(const std::wstring& wideString) const { + std::wstring_convert> converter; + return converter.to_bytes(wideString); + } + }; +#endif // SQLITE_ORM_OMITS_CODECVT + template<> + struct field_printer { + std::string operator()(const nullptr_t&) const { + return "NULL"; + } + }; +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + template<> + struct field_printer { + std::string operator()(const std::nullopt_t&) const { + return "NULL"; + } + }; +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED + template + struct field_printer, + internal::is_printable>>::value>> { + using unqualified_type = std::remove_cv_t; -// #include "type_printer.h" + std::string operator()(const T& t) const { + if(t) { + return field_printer()(*t); + } else { + return field_printer{}(nullptr); + } + } + }; -// #include "constraints.h" +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + template + struct field_printer< + T, + std::enable_if_t, + internal::is_printable>>>> { + using unqualified_type = std::remove_cv_t; -// #include "field_printer.h" + std::string operator()(const T& t) const { + if(t.has_value()) { + return field_printer()(*t); + } else { + return field_printer{}(std::nullopt); + } + } + }; +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +} // #include "rowid.h" @@ -10134,36 +12399,23 @@ namespace sqlite_orm { // #include "column_result.h" #include // std::enable_if, std::is_same, std::decay, std::is_arithmetic, std::is_base_of -#include // std::tuple #include // std::reference_wrapper // #include "functional/cxx_universal.h" +// ::nullptr_t +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "functional/mpl.h" // #include "tuple_helper/tuple_traits.h" // #include "tuple_helper/tuple_fy.h" -#include - -namespace sqlite_orm { - - namespace internal { - - template - struct tuplify { - using type = std::tuple; - }; - template - struct tuplify> { - using type = std::tuple; - }; +// #include "tuple_helper/tuple_filter.h" - template - using tuplify_t = typename tuplify::type; - } -} +// #include "tuple_helper/tuple_transformer.h" -// #include "tuple_helper/tuple_filter.h" +// #include "tuple_helper/same_or_void.h" // #include "type_traits.h" @@ -10175,6 +12427,8 @@ namespace sqlite_orm { // #include "type_traits.h" +// #include "table_reference.h" + // #include "alias_traits.h" namespace sqlite_orm { @@ -10182,14 +12436,19 @@ namespace sqlite_orm { namespace internal { /** - * If T is a recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, + * If T is a table reference or recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, * otherwise unqualified T. */ template struct mapped_type_proxy : std::remove_const {}; - template - struct mapped_type_proxy> : std::remove_const> {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + struct mapped_type_proxy : R {}; +#endif + + template + struct mapped_type_proxy> : std::remove_const> {}; template using mapped_type_proxy_t = typename mapped_type_proxy::type; @@ -10204,8 +12463,12 @@ namespace sqlite_orm { // #include "rowid.h" +// #include "column_result_proxy.h" + // #include "alias.h" +// #include "cte_types.h" + // #include "storage_traits.h" #include // std::tuple @@ -10214,39 +12477,15 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_filter.h" -// #include "tuple_helper/tuple_transformer.h" - -#include // std::tuple - -// #include "../functional/mpl.h" - -namespace sqlite_orm { - namespace internal { - - template class Op> - struct tuple_transformer; - - template class Op> - struct tuple_transformer, Op> { - using type = std::tuple...>; - }; - - /* - * Transform specified tuple. - * - * `Op` is a metafunction operation. - */ - template class Op> - using transform_tuple_t = typename tuple_transformer::type; - } -} +// #include "tuple_helper/tuple_transformer.h" // #include "type_traits.h" // #include "storage_lookup.h" -namespace sqlite_orm { +// #include "schema/column.h" +namespace sqlite_orm { namespace internal { namespace storage_traits { @@ -10269,81 +12508,149 @@ namespace sqlite_orm { */ template struct storage_mapped_columns : storage_mapped_columns_impl> {}; + + /** + * DBO - db object (table) + */ + template + struct storage_mapped_column_expressions_impl + : tuple_transformer, is_column>, column_field_expression_t> {}; + + template<> + struct storage_mapped_column_expressions_impl { + using type = std::tuple<>; + }; + + /** + * DBOs - db_objects_tuple type + * Lookup - mapped or unmapped data type + */ + template + struct storage_mapped_column_expressions + : storage_mapped_column_expressions_impl> {}; } } } // #include "function.h" -#include -#include -#include // std::string -#include // std::tuple -#include // std::function -#include // std::min +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::copy_constructible +#endif +#include // std::tuple, std::tuple_size, std::tuple_element +#include // std::min, std::copy_n #include // std::move, std::forward // #include "functional/cxx_universal.h" - +// ::size_t, ::nullptr_t // #include "functional/cxx_type_traits_polyfill.h" -namespace sqlite_orm { +// #include "functional/cstring_literal.h" - struct arg_values; +// #include "functional/function_traits.h" - template - struct pointer_arg; - template - class pointer_binding; +// #include "cxx_type_traits_polyfill.h" + +// #include "mpl.h" +namespace sqlite_orm { namespace internal { + /* + * Define nested typenames: + * - return_type + * - arguments_tuple + * - signature_type + */ + template + struct function_traits; - struct user_defined_function_base { - using func_call = std::function< - void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>; - using final_call = std::function; + /* + * A function's return type + */ + template + using function_return_type_t = typename function_traits::return_type; - std::string name; - int argumentsCount = 0; - std::function create; - void (*destroy)(int*) = nullptr; + /* + * A function's arguments tuple + */ + template + class Tuple, + template class ProjectOp = polyfill::type_identity_t> + using function_arguments = typename function_traits::template arguments_tuple; -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - user_defined_function_base(decltype(name) name_, - decltype(argumentsCount) argumentsCount_, - decltype(create) create_, - decltype(destroy) destroy_) : - name(std::move(name_)), - argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {} -#endif + /* + * A function's signature + */ + template + using function_signature_type_t = typename function_traits::signature_type; + + template + struct function_traits { + using return_type = R; + + template class Tuple, template class ProjectOp> + using arguments_tuple = Tuple...>; + + using signature_type = R(Args...); }; - struct user_defined_scalar_function_t : user_defined_function_base { - func_call run; + // non-exhaustive partial specializations of `function_traits` - user_defined_scalar_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(run) run_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - run(std::move(run_)) {} + template + struct function_traits : function_traits { + using signature_type = R(Args...) const; }; - struct user_defined_aggregate_function_t : user_defined_function_base { - func_call step; - final_call finalCall; +#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED + template + struct function_traits : function_traits { + using signature_type = R(Args...) noexcept; + }; - user_defined_aggregate_function_t(decltype(name) name_, - int argumentsCount_, - decltype(create) create_, - decltype(step) step_, - decltype(finalCall) finalCall_, - decltype(destroy) destroy_) : - user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_}, - step(std::move(step_)), finalCall(std::move(finalCall_)) {} + template + struct function_traits : function_traits { + using signature_type = R(Args...) const noexcept; }; +#endif + + /* + * Pick signature of function pointer + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of function reference + */ + template + struct function_traits : function_traits {}; + + /* + * Pick signature of pointer-to-member function + */ + template + struct function_traits : function_traits {}; + } +} + +// #include "type_traits.h" + +// #include "tags.h" + +namespace sqlite_orm { + + struct arg_values; + + // note (internal): forward declare even if `SQLITE_VERSION_NUMBER < 3020000` in order to simplify coding below + template + struct pointer_arg; + // note (internal): forward declare even if `SQLITE_VERSION_NUMBER < 3020000` in order to simplify coding below + template + class pointer_binding; + namespace internal { template using scalar_call_function_t = decltype(&F::operator()); @@ -10353,16 +12660,18 @@ namespace sqlite_orm { template using aggregate_fin_function_t = decltype(&F::fin); - template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_udf_v>> = true; + template - SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v>> = - true; + struct is_scalar_udf : polyfill::bool_constant> {}; - template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false; + template + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v = false; template - SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v< + SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_udf_v< F, polyfill::void_t, aggregate_fin_function_t, @@ -10370,155 +12679,523 @@ namespace sqlite_orm { std::enable_if_t>::value>>> = true; - template - struct member_function_arguments; + template + struct is_aggregate_udf : polyfill::bool_constant> {}; - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...) const; - using tuple_type = std::tuple...>; - using return_type = R; - }; + template + struct function; + } - template - struct member_function_arguments { - using member_function_type = R (O::*)(Args...); - using tuple_type = std::tuple...>; - using return_type = R; - }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** @short Specifies that a type is a function signature (i.e. a function in the C++ type system). + */ + template + concept orm_function_sig = std::is_function_v; + + /** @short Specifies that a type is a classic function object. + * + * A classic function object meets the following requirements: + * - defines a single call operator `F::operator()` + * - isn't a traditional sqlite_orm scalar function (having a static `F::name()` function + */ + template + concept orm_classic_function_object = + ((!requires { typename F::is_transparent; }) && (requires { &F::operator(); }) && + /*rule out sqlite_orm scalar function*/ + (!requires { F::name(); })); + + /** @short Specifies that a type is a user-defined scalar function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::operator()()` call operator + */ + template + concept orm_scalar_udf = requires { + UDF::name(); + typename internal::scalar_call_function_t; + }; + + /** @short Specifies that a type is a user-defined aggregate function. + * + * `UDF` must meet the following requirements: + * - `UDF::name()` static function + * - `UDF::step()` member function + * - `UDF::fin()` member function + */ + template + concept orm_aggregate_udf = requires { + UDF::name(); + typename internal::aggregate_step_function_t; + typename internal::aggregate_fin_function_t; + requires std::is_member_function_pointer_v>; + requires std::is_member_function_pointer_v>; + }; + + /** @short Specifies that a type is a framed user-defined scalar function. + */ + template + concept orm_scalar_function = (polyfill::is_specialization_of_v, internal::function> && + orm_scalar_udf); + + /** @short Specifies that a type is a framed user-defined aggregate function. + */ + template + concept orm_aggregate_function = (polyfill::is_specialization_of_v, internal::function> && + orm_aggregate_udf); + + /** @short Specifies that a type is a framed and quoted user-defined scalar function. + */ + template + concept orm_quoted_scalar_function = requires(const Q& quotedF) { + quotedF.name(); + quotedF.callable(); + }; +#endif + namespace internal { template struct callable_arguments_impl; template - struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + struct callable_arguments_impl> { + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; template - struct callable_arguments_impl>> { - using args_tuple = typename member_function_arguments>::tuple_type; - using return_type = typename member_function_arguments>::return_type; + struct callable_arguments_impl> { + using args_tuple = function_arguments, std::tuple, std::decay_t>; + using return_type = function_return_type_t>; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + requires(std::is_function_v) + struct callable_arguments_impl { + using args_tuple = function_arguments; + using return_type = std::decay_t>; + }; +#endif + template struct callable_arguments : callable_arguments_impl {}; - template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a quoted user-defined function. + */ + template + struct udf_holder : private std::string { + using udf_type = UDF; + + using std::string::basic_string; + + const std::string& operator()() const { + return *this; + } + }; +#endif + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + requires(requires { UDF::name(); }) + struct udf_holder +#else + /* + * Bundle of type and name of a traditional sqlite_orm user-defined function. + */ + template + struct udf_holder +#endif + { + using udf_type = UDF; + + template>::value, bool> = true> + decltype(auto) operator()() const { + return UDF::name(); + } + + template::value, bool> = true> + std::string operator()() const { + return std::string{UDF::name()}; + } + }; + + /* + * Represents a call of a user-defined function. + */ + template struct function_call { - using function_type = F; - using args_tuple = std::tuple; + using udf_type = UDF; + using args_tuple = std::tuple; - args_tuple args; + udf_holder name; + args_tuple callArgs; }; + template + SQLITE_ORM_INLINE_VAR constexpr bool + is_operator_argument_v::value>> = true; + template struct unpacked_arg { using type = T; }; - template - struct unpacked_arg> { + template + struct unpacked_arg> { using type = typename callable_arguments::return_type; }; template using unpacked_arg_t = typename unpacked_arg::type; - template + template SQLITE_ORM_CONSTEVAL bool expected_pointer_value() { - static_assert(polyfill::always_false_v, "Expected a pointer value for I-th argument"); + static_assert(polyfill::always_false_v, "Expected a pointer value for I-th argument"); return false; } - template - constexpr bool is_same_pvt_v = expected_pointer_value(); + template + constexpr bool is_same_pvt_v = expected_pointer_value(); // Always allow binding nullptr to a pointer argument template constexpr bool is_same_pvt_v> = true; + // Always allow binding nullptr to a pointer argument + template + constexpr bool is_same_pvt_v, pointer_binding, void> = true; + + template + SQLITE_ORM_CONSTEVAL bool assert_same_pointer_data_type() { + constexpr bool valid = std::is_convertible::value; + static_assert(valid, "Pointer data types of I-th argument do not match"); + return valid; + } #if __cplusplus >= 201703L // C++17 or later template - SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() { + SQLITE_ORM_CONSTEVAL bool assert_same_pointer_tag() { constexpr bool valid = Binding == PointerArg; - static_assert(valid, "Pointer value types of I-th argument do not match"); + static_assert(valid, "Pointer types (tags) of I-th argument do not match"); return valid; } - template constexpr bool is_same_pvt_v> = - assert_same_pointer_type(); + assert_same_pointer_tag() && + assert_same_pointer_data_type(); #else template - SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() { + constexpr bool assert_same_pointer_tag() { constexpr bool valid = Binding::value == PointerArg::value; - static_assert(valid, "Pointer value types of I-th argument do not match"); + static_assert(valid, "Pointer types (tags) of I-th argument do not match"); return valid; } template constexpr bool is_same_pvt_v> = - assert_same_pointer_type(); + assert_same_pointer_tag(); #endif - template + // not a pointer value, currently leave it unchecked + template SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::false_type) { return true; } - template - SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::true_type) { - return is_same_pvt_v; + // check the type of pointer values + template + SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::true_type) { + return is_same_pvt_v; + } + + template + SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { + return true; + } + template + SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { + using func_param_type = std::tuple_element_t; + using call_arg_type = unpacked_arg_t>; + +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + constexpr bool valid = validate_pointer_value_type, + unpacked_arg_t>>( + polyfill::bool_constant < (polyfill::is_specialization_of_v) || + (polyfill::is_specialization_of_v) > {}); + + return validate_pointer_value_types(polyfill::index_constant{}) && valid; +#else + return validate_pointer_value_types(polyfill::index_constant{}) && + validate_pointer_value_type, + unpacked_arg_t>>( + polyfill::bool_constant < (polyfill::is_specialization_of_v) || + (polyfill::is_specialization_of_v) > {}); +#endif + } + + /* + * Note: Currently the number of call arguments is checked and whether the types of pointer values match, + * but other call argument types are not checked against the parameter types of the function. + */ + template +#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED + SQLITE_ORM_CONSTEVAL void check_function_call() { +#else + void check_function_call() { +#endif + using call_args_tuple = std::tuple; + using function_params_tuple = typename callable_arguments::args_tuple; + constexpr size_t callArgsCount = std::tuple_size::value; + constexpr size_t functionParamsCount = std::tuple_size::value; + static_assert(std::is_same>::value || + (callArgsCount == functionParamsCount && + validate_pointer_value_types( + polyfill::index_constant{})), + "Check the number and types of the function call arguments"); + } + + /* + * Generator of a user-defined function call in a sql query expression. + * + * Use the variable template `func<>` to instantiate. + * + * Calling the function captures the parameters in a `function_call` node. + */ + template + struct function { + using udf_type = UDF; + using callable_type = UDF; + + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } + + constexpr auto udf_holder() const { + return internal::udf_holder{}; + } + + // returns a character range + constexpr auto name() const { + return this->udf_holder()(); + } + }; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /* + * Generator of a user-defined function call in a sql query expression. + * + * Use the string literal operator template `""_scalar.quote()` to quote + * a freestanding function, stateless lambda or function object. + * + * Calling the function captures the parameters in a `function_call` node. + * + * Internal note: + * 1. Captures and represents a function [pointer or object], especially one without side effects. + * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, + * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. + * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the user-defined function. + */ + template + struct quoted_scalar_function { + using udf_type = Sig; + using callable_type = F; + + /* + * Generates the SQL function call. + */ + template + function_call operator()(CallArgs... callArgs) const { + check_function_call(); + return {this->udf_holder(), {std::forward(callArgs)...}}; + } + + /* + * Return original `udf` if stateless or a copy of it otherwise + */ + constexpr decltype(auto) callable() const { + if constexpr(stateless) { + return (this->udf); + } else { + // non-const copy + return F(this->udf); + } + } + + constexpr auto udf_holder() const { + return internal::udf_holder{this->name()}; + } + + constexpr auto name() const { + return this->nme; + } + + template + consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : + udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, this->nme); + } + + F udf; + char nme[N]; + }; + + template + struct quoted_function_builder : cstring_literal { + using cstring_literal::cstring_literal; + + /* + * From a freestanding function, possibly overloaded. + */ + template + [[nodiscard]] consteval auto quote(F* callable) const { + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object instance. + */ + template + requires(orm_classic_function_object && (stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(F callable) const { + using Sig = function_signature_type_t; + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a function object instance, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(F callable) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::move(callable)}; + } + + /* + * From a classic function object type. + */ + template + requires(stateless || std::copy_constructible) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + using Sig = function_signature_type_t; + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + + /* + * From a function object type, picking the overloaded call operator. + */ + template + requires((stateless || std::copy_constructible)) + [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { + // detect whether overloaded call operator can be picked using `Sig` + using call_operator_type = decltype(static_cast(&F::operator())); + return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; + } + }; +#endif + } + + /** @short Call a user-defined function. + * + * Note: Currently the number of call arguments is checked and whether the types of pointer values match, + * but other call argument types are not checked against the parameter types of the function. + * + * Example: + * struct IdFunc { int oeprator(int arg)() const { return arg; } }; + * // inline: + * select(func(42)); + * // As this is a variable template, you can frame the user-defined function and define a variable for syntactic sugar and legibility: + * inline constexpr orm_scalar_function auto idfunc = func; + * select(idfunc(42)); + * + */ + template +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + requires(orm_scalar_udf || orm_aggregate_udf) +#endif + SQLITE_ORM_INLINE_VAR constexpr internal::function func{}; + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + inline namespace literals { + /* @short Create a scalar function from a freestanding function, stateless lambda or function object, + * and call such a user-defined function. + * + * If you need to pick a function or method from an overload set, or pick a template function you can + * specify an explicit function signature in the call to `from()`. + * + * Examples: + * // freestanding function from a library + * constexpr orm_quoted_scalar_function auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); + * // stateless lambda + * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { + * return errcode != 0; + * }); + * // function object instance + * constexpr orm_quoted_scalar_function auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); + * // function object + * constexpr orm_quoted_scalar_function auto equal_to_int_2_f = "equal_to"_scalar.quote>(); + * // pick function object's template call operator + * constexpr orm_quoted_scalar_function auto equal_to_int_3_f = "equal_to"_scalar.quote(std::equal_to{}); + * + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * storage.create_scalar_function(); + * + * auto rows = storage.select(clamp_int_f(0, 1, 1)); + * auto rows = storage.select(is_fatal_error_f(1)); + * auto rows = storage.select(equal_to_int_f(1, 1)); + * auto rows = storage.select(equal_to_int_2_f(1, 1)); + * auto rows = storage.select(equal_to_int_3_f(1, 1)); + */ + template + [[nodiscard]] consteval auto operator"" _scalar() { + return builder; } + } +#endif +} - template - SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { - return true; - } - template - SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant) { - using func_arg_t = std::tuple_element_t; - using passed_arg_t = unpacked_arg_t>; +// #include "ast/special_keywords.h" -#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED - constexpr bool valid = validate_pointer_value_type, - unpacked_arg_t>>( - polyfill::bool_constant < (polyfill::is_specialization_of_v) || - (polyfill::is_specialization_of_v) > {}); +namespace sqlite_orm { + namespace internal { + struct current_time_t {}; + struct current_date_t {}; + struct current_timestamp_t {}; + } - return validate_pointer_value_types(polyfill::index_constant{}) && valid; -#else - return validate_pointer_value_types(polyfill::index_constant{}) && - validate_pointer_value_type, - unpacked_arg_t>>( - polyfill::bool_constant < (polyfill::is_specialization_of_v) || - (polyfill::is_specialization_of_v) > {}); -#endif - } + inline internal::current_time_t current_time() { + return {}; } - /** - * Used to call user defined function: `func(...);` - */ - template - internal::function_call func(Args... args) { - using args_tuple = std::tuple; - using function_args_tuple = typename internal::callable_arguments::args_tuple; - constexpr auto argsCount = std::tuple_size::value; - constexpr auto functionArgsCount = std::tuple_size::value; - static_assert((argsCount == functionArgsCount && - !std::is_same>::value && - internal::validate_pointer_value_types( - polyfill::index_constant(functionArgsCount, argsCount) - 1>{})) || - std::is_same>::value, - "Number of arguments does not match"); - return {std::make_tuple(std::forward(args)...)}; + inline internal::current_date_t current_date() { + return {}; } + inline internal::current_timestamp_t current_timestamp() { + return {}; + } } namespace sqlite_orm { @@ -10538,11 +13215,23 @@ namespace sqlite_orm { * SFINAE - sfinae argument */ template - struct column_result_t; + struct column_result_t { +#ifdef __FUNCTION__ + // produce an error message that reveals `T` and `DBOs` + static constexpr bool reveal() { + static_assert(polyfill::always_false_v, "T not found in DBOs - " __FUNCTION__); + } + static constexpr bool trigger = reveal(); +#endif + }; template using column_result_of_t = typename column_result_t::type; + template + using column_result_for_tuple_t = + transform_tuple_t::template fn>; + #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template struct column_result_t, void> { @@ -10565,6 +13254,21 @@ namespace sqlite_orm { using type = bool; }; + template + struct column_result_t { + using type = std::string; + }; + + template + struct column_result_t { + using type = std::string; + }; + + template + struct column_result_t { + using type = std::string; + }; + template struct column_result_t> : member_field_type {}; @@ -10705,9 +13409,28 @@ namespace sqlite_orm { template struct column_result_t, void> : column_result_t {}; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct column_result_t>, void> { + using table_type = storage_pick_table_t; + using cte_mapper_type = cte_mapper_type_t; + + // lookup ColAlias in the final column references + using colalias_index = + find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, + "No such column mapped into the CTE."); + using type = std::tuple_element_t; + }; +#endif + template - struct column_result_t, void> { - using type = tuple_cat_t>>...>; + struct column_result_t, void> + : conc_tuple>>...> {}; + + template + struct column_result_t, void> { + using type = structure>>...>>; }; template @@ -10715,11 +13438,10 @@ namespace sqlite_orm { template struct column_result_t> { - using left_result = column_result_of_t; - using right_result = column_result_of_t; - static_assert(std::is_same::value, - "Compound subselect queries must return same types"); - using type = left_result; + using type = + polyfill::detected_t>; + static_assert(!std::is_same::value, + "Compound select statements must return a common type"); }; template @@ -10727,6 +13449,11 @@ namespace sqlite_orm { using type = typename T::result_type; }; + template + struct column_result_t, void> { + using type = std::string; + }; + /** * Result for the most simple queries like `SELECT 1` */ @@ -10757,7 +13484,7 @@ namespace sqlite_orm { template struct column_result_t, void> { - using type = T; + using type = table_reference; }; template @@ -10800,120 +13527,363 @@ namespace sqlite_orm { // #include "journal_mode.h" -// #include "view.h" +// #include "mapped_view.h" #include -#include // std::string #include // std::forward, std::move -#include // std::tuple, std::make_tuple // #include "row_extractor.h" -// #include "error_code.h" - -// #include "iterator.h" +// #include "mapped_iterator.h" #include -#include // std::shared_ptr, std::unique_ptr, std::make_shared -#include // std::decay +#include // std::shared_ptr, std::make_shared #include // std::move #include // std::input_iterator_tag #include // std::system_error #include // std::bind // #include "functional/cxx_universal.h" - +// ::ptrdiff_t // #include "statement_finalizer.h" +#include +#include // std::unique_ptr +#include // std::integral_constant + +namespace sqlite_orm { + + /** + * Guard class which finalizes `sqlite3_stmt` in dtor + */ + using statement_finalizer = + std::unique_ptr>; +} + // #include "error_code.h" // #include "object_from_column_builder.h" +#include +#include // std::is_member_object_pointer +#include // std::move + +// #include "functional/static_magic.h" + +// #include "member_traits/member_traits.h" + +// #include "table_reference.h" + +// #include "row_extractor.h" + +// #include "schema/column.h" + +// #include "storage_lookup.h" + +namespace sqlite_orm { + + namespace internal { + + struct object_from_column_builder_base { + sqlite3_stmt* stmt = nullptr; + int columnIndex = -1; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + object_from_column_builder_base(sqlite3_stmt* stmt, int columnIndex = -1) : + stmt{stmt}, columnIndex{columnIndex} {} +#endif + }; + + /** + * Function object for building an object from a result row. + */ + template + struct object_from_column_builder : object_from_column_builder_base { + using object_type = O; + + object_type& object; + + object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_, int nextColumnIndex = 0) : + object_from_column_builder_base{stmt_, nextColumnIndex - 1}, object(object_) {} + + template + void operator()(const column_field& column) { + const auto rowExtractor = row_value_extractor>(); + auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); + static_if::value>( + [&value, &object = this->object](const auto& column) { + object.*column.member_pointer = std::move(value); + }, + [&value, &object = this->object](const auto& column) { + (object.*column.setter)(std::move(value)); + })(column); + } + }; + + /** + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_referenece` + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = 0; + return this->extract(stmt, columnIndex); + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + O obj; + object_from_column_builder builder{obj, stmt, columnIndex}; + auto& table = pick_table(this->db_objects); + table.for_each_column(builder); + columnIndex = builder.columnIndex; + return obj; + } + + O extract(sqlite3_value* value) const = delete; + }; + } +} + // #include "storage_lookup.h" // #include "util.h" +#include +#include // std::string +#include // std::move + +// #include "error_code.h" + +namespace sqlite_orm { + + /** + * Escape the provided character in the given string by doubling it. + * @param str A copy of the original string + * @param char2Escape The character to escape + */ + inline std::string sql_escape(std::string str, char char2Escape) { + for(size_t pos = 0; (pos = str.find(char2Escape, pos)) != str.npos; pos += 2) { + str.replace(pos, 1, 2, char2Escape); + } + + return str; + } + + /** + * Quote the given string value using single quotes, + * escape containing single quotes by doubling them. + */ + inline std::string quote_string_literal(std::string v) { + constexpr char quoteChar = '\''; + return quoteChar + sql_escape(std::move(v), quoteChar) + quoteChar; + } + + /** + * Quote the given string value using single quotes, + * escape containing single quotes by doubling them. + */ + inline std::string quote_blob_literal(std::string v) { + constexpr char quoteChar = '\''; + return std::string{char('x'), quoteChar} + std::move(v) + quoteChar; + } + + /** + * Quote the given identifier using double quotes, + * escape containing double quotes by doubling them. + */ + inline std::string quote_identifier(std::string identifier) { + constexpr char quoteChar = '"'; + return quoteChar + sql_escape(std::move(identifier), quoteChar) + quoteChar; + } + + namespace internal { + // Wrapper to reduce boiler-plate code + inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { + sqlite3_reset(stmt); + return stmt; + } + + // note: query is deliberately taken by value, such that it is thrown away early + inline sqlite3_stmt* prepare_stmt(sqlite3* db, std::string query) { + sqlite3_stmt* stmt; + if(sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { + throw_translated_sqlite_error(db); + } + return stmt; + } + + inline void perform_void_exec(sqlite3* db, const std::string& query) { + int rc = sqlite3_exec(db, query.c_str(), nullptr, nullptr, nullptr); + if(rc != SQLITE_OK) { + throw_translated_sqlite_error(db); + } + } + + inline void perform_exec(sqlite3* db, + const char* query, + int (*callback)(void* data, int argc, char** argv, char**), + void* user_data) { + int rc = sqlite3_exec(db, query, callback, user_data, nullptr); + if(rc != SQLITE_OK) { + throw_translated_sqlite_error(db); + } + } + + inline void perform_exec(sqlite3* db, + const std::string& query, + int (*callback)(void* data, int argc, char** argv, char**), + void* user_data) { + return perform_exec(db, query.c_str(), callback, user_data); + } + + template + void perform_step(sqlite3_stmt* stmt) { + int rc = sqlite3_step(stmt); + if(rc != expected) { + throw_translated_sqlite_error(stmt); + } + } + + template + void perform_step(sqlite3_stmt* stmt, L&& lambda) { + switch(int rc = sqlite3_step(stmt)) { + case SQLITE_ROW: { + lambda(stmt); + } break; + case SQLITE_DONE: + break; + default: { + throw_translated_sqlite_error(stmt); + } + } + } + + template + void perform_steps(sqlite3_stmt* stmt, L&& lambda) { + int rc; + do { + switch(rc = sqlite3_step(stmt)) { + case SQLITE_ROW: { + lambda(stmt); + } break; + case SQLITE_DONE: + break; + default: { + throw_translated_sqlite_error(stmt); + } + } + } while(rc != SQLITE_DONE); + } + } +} + namespace sqlite_orm { - namespace internal { - template - struct iterator_t { - using view_type = V; - using value_type = typename view_type::mapped_type; + /* + * (Legacy) Input iterator over a result set for a mapped object. + */ + template + class mapped_iterator { + public: + using db_objects_type = DBOs; + + using iterator_category = std::input_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = O; + using reference = O&; + using pointer = O*; + + private: + /** + pointer to the db objects. + only null for the default constructed iterator. + */ + const db_objects_type* db_objects = nullptr; - protected: /** * shared_ptr is used over unique_ptr here * so that the iterator can be copyable. */ std::shared_ptr stmt; - // only null for the default constructed iterator - view_type* view = nullptr; - /** * shared_ptr is used over unique_ptr here * so that the iterator can be copyable. */ std::shared_ptr current; - void extract_value() { - auto& dbObjects = obtain_db_objects(this->view->storage); + void extract_object() { this->current = std::make_shared(); object_from_column_builder builder{*this->current, this->stmt.get()}; - pick_table(dbObjects).for_each_column(builder); + auto& table = pick_table(*this->db_objects); + table.for_each_column(builder); + } + + void step() { + perform_step(this->stmt.get(), std::bind(&mapped_iterator::extract_object, this)); + if(!this->current) { + this->stmt.reset(); + } } void next() { this->current.reset(); - if(sqlite3_stmt* stmt = this->stmt.get()) { - perform_step(stmt, std::bind(&iterator_t::extract_value, this)); - if(!this->current) { - this->stmt.reset(); - } - } + this->step(); } public: - using difference_type = ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::input_iterator_tag; - - iterator_t(){}; + mapped_iterator() = default; - iterator_t(statement_finalizer stmt_, view_type& view_) : stmt{std::move(stmt_)}, view{&view_} { - next(); + mapped_iterator(const db_objects_type& dbObjects, statement_finalizer stmt) : + db_objects{&dbObjects}, stmt{std::move(stmt)} { + this->step(); } - const value_type& operator*() const { - if(!this->stmt || !this->current) { + mapped_iterator(const mapped_iterator&) = default; + mapped_iterator& operator=(const mapped_iterator&) = default; + mapped_iterator(mapped_iterator&&) = default; + mapped_iterator& operator=(mapped_iterator&&) = default; + + value_type& operator*() const { + if(!this->stmt) SQLITE_ORM_CPP_UNLIKELY { throw std::system_error{orm_error_code::trying_to_dereference_null_iterator}; } return *this->current; } - const value_type* operator->() const { + // note: should actually be only present for contiguous iterators + value_type* operator->() const { return &(this->operator*()); } - iterator_t& operator++() { + mapped_iterator& operator++() { next(); return *this; } - void operator++(int) { - this->operator++(); + mapped_iterator operator++(int) { + auto tmp = *this; + ++*this; + return tmp; } - bool operator==(const iterator_t& other) const { - return this->current == other.current; + friend bool operator==(const mapped_iterator& lhs, const mapped_iterator& rhs) { + return lhs.current == rhs.current; } - bool operator!=(const iterator_t& other) const { - return !(*this == other); +#ifndef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + friend bool operator!=(const mapped_iterator& lhs, const mapped_iterator& rhs) { + return !(lhs == rhs); } +#endif }; } } @@ -10944,7 +13914,8 @@ namespace sqlite_orm { #include // std::iterator_traits #include // std::string #include // std::integral_constant, std::declval -#include // std::pair +#include // std::move, std::forward, std::pair +#include // std::tuple // #include "functional/cxx_universal.h" @@ -10952,7 +13923,7 @@ namespace sqlite_orm { // #include "functional/cxx_functional_polyfill.h" -// #include "tuple_helper/tuple_filter.h" +// #include "tuple_helper/tuple_traits.h" // #include "connection_holder.h" @@ -11004,28 +13975,35 @@ namespace sqlite_orm { }; struct connection_ref { - connection_ref(connection_holder& holder_) : holder(holder_) { - this->holder.retain(); + connection_ref(connection_holder& holder) : holder(&holder) { + this->holder->retain(); } connection_ref(const connection_ref& other) : holder(other.holder) { - this->holder.retain(); + this->holder->retain(); } - connection_ref(connection_ref&& other) : holder(other.holder) { - this->holder.retain(); + // rebind connection reference + connection_ref& operator=(const connection_ref& other) { + if(other.holder != this->holder) { + this->holder->release(); + this->holder = other.holder; + this->holder->retain(); + } + + return *this; } ~connection_ref() { - this->holder.release(); + this->holder->release(); } sqlite3* get() const { - return this->holder.get(); + return this->holder->get(); } - protected: - connection_holder& holder; + private: + connection_holder* holder = nullptr; }; } } @@ -11054,7 +14032,7 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_values_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_values_v = polyfill::is_specialization_of::value; template using is_values = polyfill::bool_constant>; @@ -11078,6 +14056,10 @@ namespace sqlite_orm { } +// #include "table_reference.h" + +// #include "mapped_type_proxy.h" + // #include "ast/upsert_clause.h" #if SQLITE_VERSION_NUMBER >= 3024000 @@ -11118,13 +14100,18 @@ namespace sqlite_orm { actions_tuple actions; }; +#endif template - using is_upsert_clause = polyfill::is_specialization_of; + SQLITE_ORM_INLINE_VAR constexpr bool is_upsert_clause_v = +#if SQLITE_VERSION_NUMBER >= 3024000 + polyfill::is_specialization_of::value; #else - template - struct is_upsert_clause : polyfill::bool_constant {}; + false; #endif + + template + using is_upsert_clause = polyfill::bool_constant>; } #if SQLITE_VERSION_NUMBER >= 3024000 @@ -11142,7 +14129,7 @@ namespace sqlite_orm { */ template internal::conflict_target on_conflict(Args... args) { - return {std::tuple(std::forward(args)...)}; + return {{std::forward(args)...}}; } #endif } @@ -11155,6 +14142,8 @@ namespace sqlite_orm { #include // std::stringstream #include // std::false_type, std::true_type +// #include "../tuple_helper/tuple_traits.h" + // #include "../table_name_collector.h" #include // std::set @@ -11205,7 +14194,8 @@ namespace sqlite_orm { template void operator()(const column_pointer&) { - this->table_names.emplace(lookup_table_name(this->db_objects), ""); + auto tableName = lookup_table_name>(this->db_objects); + this->table_names.emplace(std::move(tableName), alias_extractor::as_alias()); } template @@ -11248,6 +14238,11 @@ namespace sqlite_orm { void operator()(const table__rowid_t&) { this->table_names.emplace(lookup_table_name(this->db_objects), ""); } + + template + void operator()(const highlight_t&) { + this->table_names.emplace(lookup_table_name(this->db_objects), ""); + } }; template = true> @@ -11430,10 +14425,10 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_prepared_statement_v = - polyfill::is_specialization_of_v; + polyfill::is_specialization_of::value; template - using is_prepared_statement = polyfill::bool_constant>; + struct is_prepared_statement : polyfill::bool_constant> {}; /** * T - type of object to obtain from a database @@ -11538,10 +14533,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_v = polyfill::is_specialization_of::value; template - using is_insert = polyfill::bool_constant>; + struct is_insert : polyfill::bool_constant> {}; template struct insert_explicit { @@ -11560,10 +14555,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_v = polyfill::is_specialization_of::value; template - using is_replace = polyfill::bool_constant>; + struct is_replace : polyfill::bool_constant> {}; template struct insert_range_t { @@ -11576,10 +14571,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_range_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_range_v = + polyfill::is_specialization_of::value; template - using is_insert_range = polyfill::bool_constant>; + struct is_insert_range : polyfill::bool_constant> {}; template struct replace_range_t { @@ -11592,10 +14588,11 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_range_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_range_v = + polyfill::is_specialization_of::value; template - using is_replace_range = polyfill::bool_constant>; + struct is_replace_range : polyfill::bool_constant> {}; template struct insert_raw_t { @@ -11605,10 +14602,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_insert_raw_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_insert_raw_v = polyfill::is_specialization_of::value; template - using is_insert_raw = polyfill::bool_constant>; + struct is_insert_raw : polyfill::bool_constant> {}; template struct replace_raw_t { @@ -11618,10 +14615,10 @@ namespace sqlite_orm { }; template - SQLITE_ORM_INLINE_VAR constexpr bool is_replace_raw_v = polyfill::is_specialization_of_v; + SQLITE_ORM_INLINE_VAR constexpr bool is_replace_raw_v = polyfill::is_specialization_of::value; template - using is_replace_raw = polyfill::bool_constant>; + struct is_replace_raw : polyfill::bool_constant> {}; struct default_values_t {}; @@ -11933,9 +14930,20 @@ namespace sqlite_orm { */ template internal::remove_t remove(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a remove statement + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: remove(5); + */ + template + auto remove(Ids... ids) { + return remove>(std::forward(ids)...); } +#endif /** * Create an update statement. @@ -11956,9 +14964,20 @@ namespace sqlite_orm { */ template internal::get_t get(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get(5); + */ + template + auto get(Ids... ids) { + return get>(std::forward(ids)...); } +#endif /** * Create a get pointer statement. @@ -11967,9 +14986,20 @@ namespace sqlite_orm { */ template internal::get_pointer_t get_pointer(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get pointer statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get_pointer(5); + */ + template + auto get_pointer(Ids... ids) { + return get_pointer>(std::forward(ids)...); } +#endif #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** @@ -11979,11 +15009,22 @@ namespace sqlite_orm { */ template internal::get_optional_t get_optional(Ids... ids) { - std::tuple idsTuple{std::forward(ids)...}; - return {std::move(idsTuple)}; + return {{std::forward(ids)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get optional statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: get_optional(5); + */ + template + auto get_optional(Ids... ids) { + return get_optional>(std::forward(ids)...); + } +#endif + /** * Create a remove all statement. * T is an object type mapped to a storage. @@ -11993,36 +15034,48 @@ namespace sqlite_orm { internal::remove_all_t remove_all(Args... args) { using args_tuple = std::tuple; internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + return {{std::forward(args)...}}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create a get all statement. - * T is an object type mapped to a storage. - * Usage: storage.get_all(...); + * Create a remove all statement. + * `table` is an explicitly specified table reference of a mapped object to be extracted. + * Usage: storage.remove_all(...); */ - template - internal::get_all_t, Args...> get_all(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template + auto remove_all(Args... args) { + return remove_all>(std::forward(args)...); } +#endif /** * Create a get all statement. - * T is an object type mapped to a storage. + * T is an explicitly specified object mapped to a storage or a table alias. * R is a container type. std::vector is default - * Usage: storage.get_all(...); - */ - template - internal::get_all_t get_all(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + * Usage: storage.prepare(get_all(...)); + */ + template>, class... Args> + internal::get_all_t get_all(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create a get all statement. + * `mapped` is an explicitly specified table reference or table alias to be extracted. + * `R` is the container return type, which must have a `R::push_back(T&&)` method, and defaults to `std::vector` + * Usage: storage.get_all(...); + */ + template>, + class... Args> + auto get_all(Args&&... conditions) { + return get_all, R>(std::forward(conditions)...); } +#endif /** * Create an update all statement. @@ -12033,64 +15086,66 @@ namespace sqlite_orm { static_assert(internal::is_set::value, "first argument in update_all can be either set or dynamic_set"); using args_tuple = std::tuple; internal::validate_conditions(); - args_tuple conditions{std::forward(wh)...}; - return {std::move(set), std::move(conditions)}; + return {std::move(set), {std::forward(wh)...}}; } /** * Create a get all pointer statement. * T is an object type mapped to a storage. - * Usage: storage.get_all_pointer(...); + * R is a container return type. std::vector> is default + * Usage: storage.prepare(get_all_pointer(...)); */ - template - internal::get_all_pointer_t>, Args...> get_all_pointer(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, class... Args> + internal::get_all_pointer_t get_all_pointer(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all pointer statement. - * T is an object type mapped to a storage. + * `table` is an explicitly specified table reference of a mapped object to be extracted. * R is a container return type. std::vector> is default - * Usage: storage.get_all_pointer(...); - */ - template - internal::get_all_pointer_t get_all_pointer(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + * Usage: storage.prepare(get_all_pointer(...)); + */ + template>, + class... Args> + auto get_all_pointer(Args... conditions) { + return get_all_pointer, R>(std::forward(conditions)...); } +#endif #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** * Create a get all optional statement. * T is an object type mapped to a storage. + * R is a container return type. std::vector> is default * Usage: storage.get_all_optional(...); */ - template - internal::get_all_optional_t>, Args...> get_all_optional(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, class... Args> + internal::get_all_optional_t get_all_optional(Args... conditions) { + using conditions_tuple = std::tuple; + internal::validate_conditions(); + return {{std::forward(conditions)...}}; } +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create a get all optional statement. - * T is an object type mapped to a storage. + * `table` is an explicitly specified table reference of a mapped object to be extracted. * R is a container return type. std::vector> is default - * Usage: storage.get_all_optional(...); + * Usage: storage.get_all_optional(...); */ - template - internal::get_all_optional_t get_all_optional(Args... args) { - using args_tuple = std::tuple; - internal::validate_conditions(); - args_tuple conditions{std::forward(args)...}; - return {std::move(conditions)}; + template>, + class... Args> + auto get_all_optional(Args&&... conditions) { + return get_all_optional, R>(std::forward(conditions)...); } -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#endif } // #include "values.h" @@ -12162,6 +15217,28 @@ namespace sqlite_orm { // #include "ast/set.h" +// #include "ast/match.h" + +namespace sqlite_orm { + namespace internal { + + template + struct match_t { + using mapped_type = T; + using argument_type = X; + + argument_type argument; + + match_t(argument_type argument) : argument(std::move(argument)) {} + }; + } + + template + internal::match_t match(X argument) { + return {std::move(argument)}; + } +} + namespace sqlite_orm { namespace internal { @@ -12226,6 +15303,16 @@ namespace sqlite_orm { } }; + template + struct ast_iterator, void> { + using node_type = match_t; + + template + void operator()(const node_type& node, L& lambda) const { + iterate_ast(node.argument, lambda); + } + }; + template struct ast_iterator, void> { using node_type = group_by_t; @@ -12236,6 +15323,19 @@ namespace sqlite_orm { } }; + template + struct ast_iterator, void> { + using node_type = highlight_t; + + template + void operator()(const node_type& expression, L& lambda) const { + lambda(expression); + iterate_ast(expression.argument0, lambda); + iterate_ast(expression.argument1, lambda); + iterate_ast(expression.argument2, lambda); + } + }; + template struct ast_iterator, void> { using node_type = excluded_t; @@ -12267,30 +15367,31 @@ namespace sqlite_orm { }; template - struct ast_iterator> { + struct ast_iterator< + T, + std::enable_if_t, is_binary_operator>::value>> { using node_type = T; template - void operator()(const node_type& binaryCondition, L& lambda) const { - iterate_ast(binaryCondition.l, lambda); - iterate_ast(binaryCondition.r, lambda); + void operator()(const node_type& node, L& lambda) const { + iterate_ast(node.lhs, lambda); + iterate_ast(node.rhs, lambda); } }; - template - struct ast_iterator, void> { - using node_type = binary_operator; + template + struct ast_iterator, void> { + using node_type = is_equal_with_table_t; template - void operator()(const node_type& binaryOperator, C& lambda) const { - iterate_ast(binaryOperator.lhs, lambda); - iterate_ast(binaryOperator.rhs, lambda); + void operator()(const node_type& node, C& lambda) const { + iterate_ast(node.rhs, lambda); } }; - template - struct ast_iterator, void> { - using node_type = columns_t; + template + struct ast_iterator, is_struct>::value>> { + using node_type = C; template void operator()(const node_type& cols, L& lambda) const { @@ -12342,14 +15443,36 @@ namespace sqlite_orm { } }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + struct ast_iterator> { + using node_type = CTE; + + template + void operator()(const node_type& c, L& lambda) const { + iterate_ast(c.subselect, lambda); + } + }; + + template + struct ast_iterator> { + using node_type = With; + + template + void operator()(const node_type& c, L& lambda) const { + iterate_ast(c.cte, lambda); + iterate_ast(c.expression, lambda); + } + }; +#endif + template struct ast_iterator> { using node_type = T; template void operator()(const node_type& c, L& lambda) const { - iterate_ast(c.left, lambda); - iterate_ast(c.right, lambda); + iterate_ast(c.compound, lambda); } }; @@ -12490,16 +15613,6 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = having_t; - - template - void operator()(const node_type& node, L& lambda) const { - iterate_ast(node.expression, lambda); - } - }; - template struct ast_iterator, void> { using node_type = cast_t; @@ -12597,13 +15710,13 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = function_call; + template + struct ast_iterator, void> { + using node_type = function_call; template void operator()(const node_type& f, L& lambda) const { - iterate_ast(f.args, lambda); + iterate_ast(f.callArgs, lambda); } }; @@ -12661,7 +15774,7 @@ namespace sqlite_orm { // note: not strictly necessary as there's no binding support for USING; // we provide it nevertheless, in line with on_t. template - struct ast_iterator>> { + struct ast_iterator::value>> { using node_type = T; template @@ -12790,9 +15903,9 @@ namespace sqlite_orm { */ template struct ast_iterator, - polyfill::is_specialization_of, - is_column_alias>>> { + std::enable_if_t, + polyfill::is_specialization_of, + is_column_alias>::value>> { using node_type = T; template @@ -12833,73 +15946,299 @@ namespace sqlite_orm { namespace internal { /** - * This class does not related to SQL view. This is a container like class which is returned by - * by storage_t::iterate function. This class contains STL functions: + * A C++ view-like class which is returned + * by `storage_t::iterate()` function. This class contains STL functions: * - size_t size() * - bool empty() * - iterator end() * - iterator begin() * All these functions are not right const cause all of them may open SQLite connections. + * + * `mapped_view` is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. */ template - struct view_t { + struct mapped_view { using mapped_type = T; using storage_type = S; - using self = view_t; + using db_objects_type = typename S::db_objects_type; + + storage_type& storage; + connection_ref connection; + get_all_t expression; + + mapped_view(storage_type& storage, connection_ref conn, Args&&... args) : + storage(storage), connection(std::move(conn)), expression{std::forward(args)...} {} + + size_t size() const { + return this->storage.template count(); + } + + bool empty() const { + return !this->size(); + } + + mapped_iterator begin() { + using context_t = serializer_context; + auto& dbObjects = obtain_db_objects(this->storage); + context_t context{dbObjects}; + context.skip_table_name = false; + context.replace_bindable_with_question = true; + + statement_finalizer stmt{prepare_stmt(this->connection.get(), serialize(this->expression, context))}; + iterate_ast(this->expression.conditions, conditional_binder{stmt.get()}); + return {dbObjects, std::move(stmt)}; + } + + mapped_iterator end() { + return {}; + } + }; + } +} + +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; +#endif + +// #include "result_set_view.h" + +#include +#include // std::move, std::remove_cvref +#include // std::reference_wrapper +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) && \ + defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED) +#include // std::ranges::view_interface +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "row_extractor.h" + +// #include "result_set_iterator.h" + +#include +#include // std::move +#include // std::input_iterator_tag, std::default_sentinel_t +#include // std::reference_wrapper + +// #include "functional/cxx_universal.h" +// ::ptrdiff_t +// #include "statement_finalizer.h" + +// #include "row_extractor.h" + +// #include "column_result_proxy.h" + +// #include "util.h" + +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) +namespace sqlite_orm::internal { + + template + class result_set_iterator; + +#ifdef SQLITE_ORM_STL_HAS_DEFAULT_SENTINEL + using result_set_sentinel_t = std::default_sentinel_t; +#else + // sentinel + template<> + class result_set_iterator {}; + + using result_set_sentinel_t = result_set_iterator; +#endif + + /* + * Input iterator over a result set for a select statement. + */ + template + class result_set_iterator { + public: + using db_objects_type = DBOs; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + using iterator_concept = std::input_iterator_tag; +#else + using iterator_category = std::input_iterator_tag; +#endif + using difference_type = ptrdiff_t; + using value_type = column_result_proxy_t; + + public: + result_set_iterator(const db_objects_type& dbObjects, statement_finalizer stmt) : + db_objects{dbObjects}, stmt{std::move(stmt)} { + this->step(); + } + result_set_iterator(result_set_iterator&&) = default; + result_set_iterator& operator=(result_set_iterator&&) = default; + result_set_iterator(const result_set_iterator&) = delete; + result_set_iterator& operator=(const result_set_iterator&) = delete; + + /** @pre `*this != std::default_sentinel` */ + value_type operator*() const { + return this->extract(); + } + + result_set_iterator& operator++() { + this->step(); + return *this; + } + + void operator++(int) { + ++*this; + } + + friend bool operator==(const result_set_iterator& it, const result_set_sentinel_t&) noexcept { + return sqlite3_data_count(it.stmt.get()) == 0; + } + + private: + void step() { + perform_step(this->stmt.get(), [](sqlite3_stmt*) {}); + } + + value_type extract() const { + const auto rowExtractor = make_row_extractor(this->db_objects.get()); + return rowExtractor.extract(this->stmt.get(), 0); + } - storage_type& storage; - connection_ref connection; - get_all_t, Args...> args; + private: + std::reference_wrapper db_objects; + statement_finalizer stmt; + }; +} +#endif - view_t(storage_type& stor, decltype(connection) conn, Args&&... args_) : - storage(stor), connection(std::move(conn)), args{std::make_tuple(std::forward(args_)...)} {} +// #include "ast_iterator.h" - size_t size() const { - return this->storage.template count(); - } +// #include "connection_holder.h" - bool empty() const { - return !this->size(); - } +// #include "util.h" - iterator_t begin() { - using context_t = serializer_context; - context_t context{obtain_db_objects(this->storage)}; - context.skip_table_name = false; - context.replace_bindable_with_question = true; +// #include "type_traits.h" - statement_finalizer stmt{prepare_stmt(this->connection.get(), serialize(this->args, context))}; - iterate_ast(this->args.conditions, conditional_binder{stmt.get()}); - return {std::move(stmt), *this}; - } +// #include "storage_lookup.h" - iterator_t end() { - return {}; - } - }; - } +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) +namespace sqlite_orm::internal { + /* + * A C++ view over a result set of a select statement, returned by `storage_t::iterate()`. + * + * `result_set_view` is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ + template + struct result_set_view +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED + : std::ranges::view_interface> +#endif + { + using db_objects_type = DBOs; + using expression_type = Select; + + result_set_view(const db_objects_type& dbObjects, connection_ref conn, Select expression) : + db_objects{dbObjects}, connection{std::move(conn)}, expression{std::move(expression)} {} + + result_set_view(result_set_view&&) = default; + result_set_view& operator=(result_set_view&&) = default; + result_set_view(const result_set_view&) = default; + result_set_view& operator=(const result_set_view&) = default; + + auto begin() { + const auto& exprDBOs = db_objects_for_expression(this->db_objects.get(), this->expression); + using ExprDBOs = std::remove_cvref_t; + // note: Select can be `select_t` or `with_t` + using select_type = polyfill::detected_or_t; + using column_result_type = column_result_of_t; + using context_t = serializer_context; + context_t context{exprDBOs}; + context.skip_table_name = false; + context.replace_bindable_with_question = true; + + statement_finalizer stmt{prepare_stmt(this->connection.get(), serialize(this->expression, context))}; + iterate_ast(this->expression, conditional_binder{stmt.get()}); + + // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; + // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. + using iterator_type = result_set_iterator; + return iterator_type{this->db_objects, std::move(stmt)}; + } + + result_set_sentinel_t end() { + return {}; + } + + private: + std::reference_wrapper db_objects; + connection_ref connection; + expression_type expression; + }; } +#ifdef SQLITE_ORM_CPP20_RANGES_SUPPORTED +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; +#endif +#endif + // #include "ast_iterator.h" // #include "storage_base.h" #include -#include // std::function, std::bind +#include // std::allocator +#include // std::function, std::bind, std::bind_front #include // std::string #include // std::stringstream #include // std::move #include // std::system_error #include // std::vector +#include // std::list #include // std::make_unique, std::unique_ptr #include // std::map -#include // std::decay, std::is_same -#include // std::find_if +#include // std::is_same +#include // std::find_if, std::ranges::find // #include "functional/cxx_universal.h" +// ::size_t +// #include "functional/cxx_tuple_polyfill.h" -// #include "functional/static_magic.h" +#include // std::apply; std::tuple_size +#if __cpp_lib_apply < 201603L +#include // std::forward, std::index_sequence, std::make_index_sequence +#endif + +// #include "../functional/cxx_universal.h" +// ::size_t +// #include "../functional/cxx_functional_polyfill.h" +// std::invoke + +namespace sqlite_orm { + namespace internal { + namespace polyfill { +#if __cpp_lib_apply >= 201603L + using std::apply; +#else + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl, std::index_sequence) { + return polyfill::invoke(std::forward(callable), std::get(std::forward(tpl))...); + } + + template + decltype(auto) apply(Callable&& callable, Tpl&& tpl) { + constexpr size_t size = std::tuple_size>::value; + return apply(std::forward(callable), + std::forward(tpl), + std::make_index_sequence{}); + } +#endif + } + } + namespace polyfill = internal::polyfill; +} +// std::apply // #include "tuple_helper/tuple_iteration.h" // #include "pragma.h" @@ -12949,8 +16288,8 @@ namespace sqlite_orm { template struct order_by_t; - template - std::string serialize(const T&, const serializer_context&); + template + auto serialize(const T& t, const C& context); template std::string serialize_order_by(const T&, const Ctx&); @@ -12959,7 +16298,7 @@ namespace sqlite_orm { for(size_t offset = 0, next; true; offset = next + 1) { next = str.find(char2Escape, offset); - if(next == str.npos) { + if(next == str.npos) SQLITE_ORM_CPP_LIKELY { os.write(str.data() + offset, str.size() - offset); break; } @@ -13011,7 +16350,8 @@ namespace sqlite_orm { return stream_identifier(ss, std::get(tpl)...); } - template>, bool> = true> + template>::value, bool> = true> void stream_identifier(std::ostream& ss, const Tpl& tpl) { return stream_identifier(ss, tpl, std::make_index_sequence::value>{}); } @@ -13021,6 +16361,7 @@ namespace sqlite_orm { actions_tuple, expressions_tuple, dynamic_expressions, + compound_expressions, serialized, identifier, identifiers, @@ -13030,6 +16371,7 @@ namespace sqlite_orm { field_values_excluding, mapped_columns_expressions, column_constraints, + constraints_tuple, }; template @@ -13048,6 +16390,7 @@ namespace sqlite_orm { constexpr streaming streaming_actions_tuple{}; constexpr streaming streaming_expressions_tuple{}; constexpr streaming streaming_dynamic_expressions{}; + constexpr streaming streaming_compound_expressions{}; constexpr streaming streaming_serialized{}; constexpr streaming streaming_identifier{}; constexpr streaming streaming_identifiers{}; @@ -13056,6 +16399,7 @@ namespace sqlite_orm { constexpr streaming streaming_non_generated_column_names{}; constexpr streaming streaming_field_values_excluding{}; constexpr streaming streaming_mapped_columns_expressions{}; + constexpr streaming streaming_constraints_tuple{}; constexpr streaming streaming_column_constraints{}; // serialize and stream a tuple of condition expressions; @@ -13063,8 +16407,8 @@ namespace sqlite_orm { template std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { - const auto& conditions = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& conditions = std::get<1>(tpl); + auto& context = std::get<2>(tpl); iterate_tuple(conditions, [&ss, &context](auto& c) { ss << " " << serialize(c, context); @@ -13076,8 +16420,8 @@ namespace sqlite_orm { // space-separated template std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { - const auto& actions = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& actions = std::get<1>(tpl); + auto& context = std::get<2>(tpl); iterate_tuple(actions, [&ss, &context, first = true](auto& action) mutable { constexpr std::array sep = {" ", ""}; @@ -13091,8 +16435,8 @@ namespace sqlite_orm { template std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { constexpr std::array sep = {", ", ""}; @@ -13101,14 +16445,33 @@ namespace sqlite_orm { return ss; } + // serialize and stream expressions of a compound statement; + // separated by compound operator + template + std::ostream& + operator<<(std::ostream& ss, + std::tuple&, T, const std::string&, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + const std::string& opString = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + iterate_tuple(args, [&ss, &opString, &context, first = true](auto& arg) mutable { + if(!std::exchange(first, false)) { + ss << ' ' << opString << ' '; + } + ss << serialize(arg, context); + }); + return ss; + } + // serialize and stream multi_order_by arguments; // comma-separated template std::ostream& operator<<( std::ostream& ss, std::tuple&, const std::tuple...>&, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable { constexpr std::array sep = {", ", ""}; @@ -13117,17 +16480,18 @@ namespace sqlite_orm { return ss; } - // serialize and stream a vector of expressions; + // serialize and stream a vector or any other STL container of expressions; // comma-separated template std::ostream& operator<<(std::ostream& ss, std::tuple&, C, Ctx> tpl) { - const auto& args = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& args = std::get<1>(tpl); + auto& context = std::get<2>(tpl); constexpr std::array sep = {", ", ""}; - for(size_t i = 0, first = true; i < args.size(); ++i) { - ss << sep[std::exchange(first, false)] << serialize(args[i], context); + bool first = true; + for(auto& argument: args) { + ss << sep[std::exchange(first, false)] << serialize(argument, context); } return ss; } @@ -13136,7 +16500,7 @@ namespace sqlite_orm { // comma-separated template std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { - const auto& strings = get<1>(tpl); + const auto& strings = std::get<1>(tpl); constexpr std::array sep = {", ", ""}; for(size_t i = 0, first = true; i < strings.size(); ++i) { @@ -13165,7 +16529,7 @@ namespace sqlite_orm { // comma-separated template std::ostream& operator<<(std::ostream& ss, std::tuple&, C> tpl) { - const auto& identifiers = get<1>(tpl); + const auto& identifiers = std::get<1>(tpl); constexpr std::array sep = {", ", ""}; bool first = true; @@ -13180,8 +16544,8 @@ namespace sqlite_orm { template std::ostream& operator<<(std::ostream& ss, std::tuple&, Ts...> tpl) { - const size_t& columnsCount = get<1>(tpl); - const ptrdiff_t& valuesCount = get<2>(tpl); + const size_t& columnsCount = std::get<1>(tpl); + const ptrdiff_t& valuesCount = std::get<2>(tpl); if(!valuesCount || !columnsCount) { return ss; @@ -13207,16 +16571,16 @@ namespace sqlite_orm { // stream a table's column identifiers, possibly qualified; // comma-separated template - std::ostream& operator<<(std::ostream& ss, - std::tuple&, Table, const bool&> tpl) { - const auto& table = get<1>(tpl); - const bool& qualified = get<2>(tpl); + std::ostream& + operator<<(std::ostream& ss, + std::tuple&, Table, const std::string&> tpl) { + const auto& table = std::get<1>(tpl); + const std::string& qualifier = std::get<2>(tpl); - table.for_each_column([&ss, &tableName = qualified ? table.name : std::string{}, first = true]( - const column_identifier& column) mutable { + table.for_each_column([&ss, &qualifier, first = true](const column_identifier& column) mutable { constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)]; - stream_identifier(ss, tableName, column.name, std::string{}); + stream_identifier(ss, qualifier, column.name, std::string{}); }); return ss; } @@ -13226,7 +16590,7 @@ namespace sqlite_orm { template std::ostream& operator<<(std::ostream& ss, std::tuple&, Table> tpl) { - const auto& table = get<1>(tpl); + const auto& table = std::get<1>(tpl); table.template for_each_column_excluding( [&ss, first = true](const column_identifier& column) mutable { @@ -13244,9 +16608,9 @@ namespace sqlite_orm { operator<<(std::ostream& ss, std::tuple&, PredFnCls, L, Ctx, Obj> tpl) { using check_if_excluded = polyfill::remove_cvref_t>; - auto& excluded = get<2>(tpl); - auto& context = get<3>(tpl); - auto& object = get<4>(tpl); + auto& excluded = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + auto& object = std::get<4>(tpl); using object_type = polyfill::remove_cvref_t; auto& table = pick_table(context.db_objects); @@ -13268,8 +16632,8 @@ namespace sqlite_orm { template std::ostream& operator<<(std::ostream& ss, std::tuple&, T, Ctx> tpl) { - const auto& columns = get<1>(tpl); - auto& context = get<2>(tpl); + const auto& columns = std::get<1>(tpl); + auto& context = std::get<2>(tpl); iterate_tuple(columns, [&ss, &context, first = true](auto& colRef) mutable { const std::string* columnName = find_column_name(context.db_objects, colRef); @@ -13284,47 +16648,49 @@ namespace sqlite_orm { return ss; } + // serialize and stream a tuple of conditions or hints; + // space + space-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, T, Ctx> tpl) { + const auto& constraints = get<1>(tpl); + auto& context = get<2>(tpl); + + iterate_tuple(constraints, [&ss, &context](auto& constraint) mutable { + ss << ' ' << serialize(constraint, context); + }); + return ss; + } + + // serialize and stream a tuple of column constraints; + // space + space-separated template std::ostream& operator<<(std::ostream& ss, std::tuple&, const column_constraints&, const bool&, Ctx> tpl) { - const auto& column = get<1>(tpl); - const bool& isNotNull = get<2>(tpl); - auto& context = get<3>(tpl); - - using constraints_type = constraints_type_t>; - constexpr size_t constraintsCount = std::tuple_size::value; - if(constraintsCount) { - std::vector constraintsStrings; - constraintsStrings.reserve(constraintsCount); - int primaryKeyIndex = -1; - int autoincrementIndex = -1; - int tupleIndex = 0; - iterate_tuple(column.constraints, - [&constraintsStrings, &primaryKeyIndex, &autoincrementIndex, &tupleIndex, &context]( - auto& constraint) { - using constraint_type = std::decay_t; - constraintsStrings.push_back(serialize(constraint, context)); - if(is_primary_key_v) { - primaryKeyIndex = tupleIndex; - } else if(is_autoincrement_v) { - autoincrementIndex = tupleIndex; - } - ++tupleIndex; - }); - if(primaryKeyIndex != -1 && autoincrementIndex != -1 && autoincrementIndex < primaryKeyIndex) { - iter_swap(constraintsStrings.begin() + primaryKeyIndex, - constraintsStrings.begin() + autoincrementIndex); - } - for(auto& str: constraintsStrings) { - ss << str << ' '; + const auto& column = std::get<1>(tpl); + const bool& isNotNull = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + using constraints_tuple = decltype(column.constraints); + iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { + ss << ' ' << serialize(constraint, context); + }); + // add implicit null constraint + if(!context.fts5_columns) { + constexpr bool hasExplicitNullableConstraint = + mpl::invoke_t, check_if_has_type>, + constraints_tuple>::value; + if SQLITE_ORM_CONSTEXPR_IF(!hasExplicitNullableConstraint) { + if(isNotNull) { + ss << " NOT NULL"; + } else { + ss << " NULL"; + } } } - if(isNotNull) { - ss << "NOT NULL "; - } return ss; } @@ -13345,8 +16711,9 @@ namespace sqlite_orm { inline int getPragmaCallback>(void* data, int argc, char** argv, char**) { auto& res = *(std::vector*)data; res.reserve(argc); - for(decltype(argc) i = 0; i < argc; ++i) { - auto rowString = row_extractor().extract(argv[i]); + const auto rowExtractor = column_text_extractor(); + for(int i = 0; i < argc; ++i) { + auto rowString = rowExtractor.extract(argv[i]); res.push_back(std::move(rowString)); } return 0; @@ -13357,6 +16724,18 @@ namespace sqlite_orm { pragma_t(get_connection_t get_connection_) : get_connection(std::move(get_connection_)) {} + std::vector module_list() { + return this->get_pragma>("module_list"); + } + + bool recursive_triggers() { + return bool(this->get_pragma("recursive_triggers")); + } + + void recursive_triggers(bool value) { + this->set_pragma("recursive_triggers", int(value)); + } + void busy_timeout(int value) { this->set_pragma("busy_timeout", value); } @@ -13432,6 +16811,10 @@ namespace sqlite_orm { return this->get_pragma>(ss.str()); } + std::vector quick_check() { + return this->get_pragma>("quick_check"); + } + // will include generated columns in response as opposed to table_info std::vector table_xinfo(const std::string& tableName) const { auto connection = this->get_connection(); @@ -13848,10 +17231,14 @@ namespace sqlite_orm { // #include "values_to_tuple.h" #include -#include // std::index_sequence, std::make_index_sequence -#include // std::tuple, std::tuple_size, std::get +#include // std::enable_if, std::is_same, std::index_sequence, std::make_index_sequence +#include // std::tuple, std::tuple_size, std::tuple_element // #include "functional/cxx_universal.h" +// ::size_t +// #include "functional/cxx_functional_polyfill.h" + +// #include "type_traits.h" // #include "row_extractor.h" @@ -13863,6 +17250,8 @@ namespace sqlite_orm { namespace sqlite_orm { + /** @short Wrapper around a dynamically typed value object. + */ struct arg_value { arg_value() : arg_value(nullptr) {} @@ -13871,7 +17260,8 @@ namespace sqlite_orm { template T get() const { - return row_extractor().extract(this->value); + const auto rowExtractor = internal::boxed_value_extractor(); + return rowExtractor.extract(this->value); } bool is_null() const { @@ -14005,34 +17395,28 @@ namespace sqlite_orm { namespace internal { - struct values_to_tuple { - template - void operator()(sqlite3_value** values, Tpl& tuple, int /*argsCount*/) const { - (*this)(values, tuple, std::make_index_sequence::value>{}); + template + struct tuple_from_values { + template> = true> + R operator()(sqlite3_value** values, int /*argsCount*/) const { + return this->create_from(values, std::make_index_sequence::value>{}); } - void operator()(sqlite3_value** values, std::tuple& tuple, int argsCount) const { - std::get<0>(tuple) = arg_values(argsCount, values); + template> = true> + R operator()(sqlite3_value** values, int argsCount) const { + return {arg_values(argsCount, values)}; } private: -#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - (this->extract(values[Idx], std::get(tuple)), ...); - } -#else - template - void operator()(sqlite3_value** values, Tpl& tuple, std::index_sequence) const { - this->extract(values[I], std::get(tuple)); - (*this)(values, tuple, std::index_sequence{}); + template + Tpl create_from(sqlite3_value** values, std::index_sequence) const { + return {this->extract>(values[Idx])...}; } - template - void operator()(sqlite3_value** /*values*/, Tpl&, std::index_sequence) const {} -#endif + template - void extract(sqlite3_value* value, T& t) const { - t = row_extractor{}.extract(value); + T extract(sqlite3_value* value) const { + const auto rowExtractor = boxed_value_extractor(); + return rowExtractor.extract(value); } }; } @@ -14042,6 +17426,240 @@ namespace sqlite_orm { // #include "util.h" +// #include "xdestroy_handling.h" + +// #include "udf_proxy.h" + +#include +#include // assert +#include // std::true_type, std::false_type +#include // std::bad_alloc +#include // std::allocator, std::allocator_traits, std::unique_ptr +#include // std::string +#include // std::function +#include // std::move, std::pair + +// #include "error_code.h" + +namespace sqlite_orm { + namespace internal { + /* + * Returns properly allocated memory space for the specified application-defined function object + * paired with an accompanying deallocation function. + */ + template + std::pair preallocate_udf_memory() { + std::allocator allocator; + using traits = std::allocator_traits; + + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + + return {traits::allocate(allocator, 1), deallocate}; + } + + /* + * Returns a pair of functions to allocate/deallocate properly aligned memory space for the specified application-defined function object. + */ + template + std::pair obtain_udf_allocator() { + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto allocate = []() { + std::allocator allocator; + using traits = std::allocator_traits; + return (void*)traits::allocate(allocator, 1); + }; + + SQLITE_ORM_CONSTEXPR_LAMBDA_CPP17 auto deallocate = [](void* location) noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::deallocate(allocator, (UDF*)location, 1); + }; + + return {allocate, deallocate}; + } + + /* + * A deleter that only destroys the application-defined function object. + */ + struct udf_destruct_only_deleter { + template + void operator()(UDF* f) const noexcept { + std::allocator allocator; + using traits = std::allocator_traits; + traits::destroy(allocator, f); + } + }; + + /* + * Stores type-erased information in relation to an application-defined scalar or aggregate function object: + * - name and argument count + * - function dispatch (step, final) + * - either preallocated memory with a possibly a priori constructed function object [scalar], + * - or memory allocation/deallocation functions [aggregate] + */ + struct udf_proxy { + using sqlite_func_t = void (*)(sqlite3_context* context, int argsCount, sqlite3_value** values); + using final_call_fn_t = void (*)(void* udfHandle, sqlite3_context* context); + using memory_alloc = std::pair; + using memory_space = std::pair; + + std::string name; + int argumentsCount; + std::function constructAt; + xdestroy_fn_t destroy; + sqlite_func_t func; + final_call_fn_t finalAggregateCall; + + // allocator/deallocator function pair for aggregate UDF + const memory_alloc udfAllocator; + // pointer to preallocated memory space for scalar UDF, already constructed by caller if stateless + const memory_space udfMemorySpace; + + udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + sqlite_func_t func, + memory_space udfMemorySpace) : + name{std::move(name)}, + argumentsCount{argumentsCount}, constructAt{std::move(constructAt)}, destroy{destroy}, func{func}, + finalAggregateCall{nullptr}, udfAllocator{}, udfMemorySpace{udfMemorySpace} {} + + udf_proxy(std::string name, + int argumentsCount, + std::function constructAt, + xdestroy_fn_t destroy, + sqlite_func_t func, + final_call_fn_t finalAggregateCall, + memory_alloc udfAllocator) : + name{std::move(name)}, + argumentsCount{argumentsCount}, constructAt{std::move(constructAt)}, destroy{destroy}, func{func}, + finalAggregateCall{finalAggregateCall}, udfAllocator{udfAllocator}, udfMemorySpace{} {} + + ~udf_proxy() { + // destruct + if(/*bool aprioriConstructed = */ !constructAt && destroy) { + destroy(udfMemorySpace.first); + } + // deallocate + if(udfMemorySpace.second) { + udfMemorySpace.second(udfMemorySpace.first); + } + } + + udf_proxy(const udf_proxy&) = delete; + udf_proxy& operator=(const udf_proxy&) = delete; + + // convenience accessors for better legibility; + // [`friend` is intentional - it ensures that these are core accessors (only found via ADL), yet still be an out-of-class interface] + + friend void* preallocated_udf_handle(udf_proxy* proxy) { + return proxy->udfMemorySpace.first; + } + + friend void* allocate_udf(udf_proxy* proxy) { + return proxy->udfAllocator.first(); + } + + friend void deallocate_udf(udf_proxy* proxy, void* udfHandle) { + proxy->udfAllocator.second(udfHandle); + } + }; + + // safety net of doing a triple check at runtime + inline void assert_args_count(const udf_proxy* proxy, int argsCount) { + assert((proxy->argumentsCount == -1) || (proxy->argumentsCount == argsCount || + /*check fin call*/ argsCount == -1)); + (void)proxy; + (void)argsCount; + } + + // safety net of doing a triple check at runtime + inline void proxy_assert_args_count(sqlite3_context* context, int argsCount) { + udf_proxy* proxy; + assert((proxy = static_cast(sqlite3_user_data(context))) != nullptr); + assert_args_count(proxy, argsCount); + (void)context; + } + + // note: may throw `std::bad_alloc` in case memory space for the aggregate function object cannot be allocated + inline void* ensure_aggregate_udf(sqlite3_context* context, udf_proxy* proxy, int argsCount) { + // reserve memory for storing a void pointer (which is the `udfHandle`, i.e. address of the aggregate function object) + void* ctxMemory = sqlite3_aggregate_context(context, sizeof(void*)); + if(!ctxMemory) SQLITE_ORM_CPP_UNLIKELY { + throw std::bad_alloc(); + } + void*& udfHandle = *static_cast(ctxMemory); + + if(udfHandle) SQLITE_ORM_CPP_LIKELY { + return udfHandle; + } else { + assert_args_count(proxy, argsCount); + udfHandle = allocate_udf(proxy); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. + proxy->constructAt(udfHandle); + return udfHandle; + } + } + + inline void delete_aggregate_udf(udf_proxy* proxy, void* udfHandle) { + proxy->destroy(udfHandle); + deallocate_udf(proxy, udfHandle); + } + + // Return C pointer to preallocated and a priori constructed UDF + template + inline UDF* + proxy_get_scalar_udf(std::true_type /*is_stateless*/, sqlite3_context* context, int argsCount) noexcept { + proxy_assert_args_count(context, argsCount); + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + return static_cast(preallocated_udf_handle(proxy)); + } + + // Return unique pointer to newly constructed UDF at preallocated memory space + template + inline auto proxy_get_scalar_udf(std::false_type /*is_stateless*/, sqlite3_context* context, int argsCount) { + proxy_assert_args_count(context, argsCount); + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + // Note on the use of the `udfHandle` pointer after the object construction: + // since we only ever cast between void* and UDF* pointer types and + // only use the memory space for one type during the entire lifetime of a proxy, + // we can use `udfHandle` interconvertibly without laundering its provenance. + proxy->constructAt(preallocated_udf_handle(proxy)); + return std::unique_ptr{static_cast(preallocated_udf_handle(proxy)), + proxy->destroy}; + } + + // note: may throw `std::bad_alloc` in case memory space for the aggregate function object cannot be allocated + template + inline UDF* proxy_get_aggregate_step_udf(sqlite3_context* context, int argsCount) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + void* udfHandle = ensure_aggregate_udf(context, proxy, argsCount); + return static_cast(udfHandle); + } + + inline void aggregate_function_final_callback(sqlite3_context* context) { + udf_proxy* proxy = static_cast(sqlite3_user_data(context)); + void* udfHandle; + try { + // note: it is possible that the 'step' function was never called + udfHandle = ensure_aggregate_udf(context, proxy, -1); + } catch(const std::bad_alloc&) { + sqlite3_result_error_nomem(context); + return; + } + proxy->finalAggregateCall(udfHandle, context); + delete_aggregate_udf(proxy, udfHandle); + } + } +} + // #include "serializing_util.h" namespace sqlite_orm { @@ -14062,6 +17680,27 @@ namespace sqlite_orm { std::bind(&storage_base::rollback, this)}; } + transaction_guard_t deferred_transaction_guard() { + this->begin_deferred_transaction(); + return {this->get_connection(), + std::bind(&storage_base::commit, this), + std::bind(&storage_base::rollback, this)}; + } + + transaction_guard_t immediate_transaction_guard() { + this->begin_immediate_transaction(); + return {this->get_connection(), + std::bind(&storage_base::commit, this), + std::bind(&storage_base::rollback, this)}; + } + + transaction_guard_t exclusive_transaction_guard() { + this->begin_exclusive_transaction(); + return {this->get_connection(), + std::bind(&storage_base::commit, this), + std::bind(&storage_base::rollback, this)}; + } + void drop_index(const std::string& indexName) { std::stringstream ss; ss << "DROP INDEX " << quote_identifier(indexName) << std::flush; @@ -14113,7 +17752,7 @@ namespace sqlite_orm { bool table_exists(sqlite3* db, const std::string& tableName) const { bool result = false; std::stringstream ss; - ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << streaming_identifier("table") + ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("table") << " AND name = " << quote_string_literal(tableName) << std::flush; perform_exec( db, @@ -14178,6 +17817,16 @@ namespace sqlite_orm { return guard.commit_on_destroy = f(); } + std::string current_time() { + auto con = this->get_connection(); + return this->current_time(con.get()); + } + + std::string current_date() { + auto con = this->get_connection(); + return this->current_date(con.get()); + } + std::string current_timestamp() { auto con = this->get_connection(); return this->current_timestamp(con.get()); @@ -14218,9 +17867,19 @@ namespace sqlite_orm { return 0; }, &tableNames); + tableNames.shrink_to_fit(); return tableNames; } + /** + * Call it once during storage lifetime to make it keeping its connection opened till dtor call. + * By default if storage is not in-memory it calls `sqlite3_open` only when the connection is really + * needed and closes when it is not needed. This function breaks this rule. In memory storage always + * keeps connection opened so calling this for in-memory storage changes nothing. + * Note about multithreading: in multithreading context avoiding using this function for not in-memory + * storage may lead to data races. If you have data races in such a configuration try to call `open_forever` + * before accessing your storage - it may fix data races. + */ void open_forever() { this->isOpenedForever = true; this->connection->retain(); @@ -14230,7 +17889,15 @@ namespace sqlite_orm { } /** - * Call this to create user defined scalar function. Can be called at any time no matter connection is opened or no. + * Create an application-defined scalar SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * * T - function class. T must have operator() overload and static name function like this: * ``` * struct SqrtFunction { @@ -14244,47 +17911,91 @@ namespace sqlite_orm { * } * }; * ``` + */ + template + void create_scalar_function(Args&&... constructorArgs) { + static_assert(is_scalar_udf_v, "F must be a scalar function"); + + this->create_scalar_function_impl( + udf_holder{}, +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. */ - template - void create_scalar_function() { - static_assert(is_scalar_function_v, "F can't be an aggregate function"); + template + void create_scalar_function(Args&&... constructorArgs) { + return this->create_scalar_function>(std::forward(constructorArgs)...); + } - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; + /** + * Create an application-defined scalar function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, + * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; + * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. + */ + template + void create_scalar_function() { + using Sig = auto_udf_type_t; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; constexpr auto argsCount = std::is_same>::value ? -1 : int(std::tuple_size::value); - this->scalarFunctions.emplace_back(new user_defined_scalar_function_t{ - std::move(name), + this->scalarFunctions.emplace_back( + std::string{quotedF.name()}, argsCount, - []() -> int* { - return (int*)(new F()); - }, + /* constructAt = */ + nullptr, + /* destroy = */ + nullptr, /* call = */ - [](sqlite3_context* context, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - auto result = call(function, std::move(argsTuple)); + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + proxy_assert_args_count(context, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(quotedF.callable(), std::move(argsTuple)); statement_binder().result(context, result); }, - delete_function_callback, - }); + /* finalCall = */ + nullptr, + std::pair{nullptr, null_xdestroy_f}); if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - try_to_create_function(db, - static_cast(*this->scalarFunctions.back())); + try_to_create_scalar_function(db, this->scalarFunctions.back()); } } +#endif /** - * Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no. + * Create an application-defined aggregate SQL function. + * Can be called at any time no matter whether the database connection is opened or not. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * * T - function class. T must have step member function, fin member function and static name function like this: * ``` * struct MeanFunction { @@ -14305,73 +18016,89 @@ namespace sqlite_orm { * } * }; * ``` + */ + template + void create_aggregate_function(Args&&... constructorArgs) { + static_assert(is_aggregate_udf_v, "F must be an aggregate function"); + + this->create_aggregate_function_impl( + udf_holder{}, /* constructAt = */ +#ifdef SQLITE_ORM_PACK_EXPANSION_IN_INIT_CAPTURE_SUPPORTED + /* constructAt */ [... constructorArgs = std::move(constructorArgs)](void* location) { +#else + /* constructAt */ + [constructorArgs...](void* location) { +#endif + std::allocator allocator; + using traits = std::allocator_traits; + traits::construct(allocator, (F*)location, constructorArgs...); + }); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Create an application-defined aggregate function. + * Can be called at any time no matter whether the database connection is opened or not. * - * Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted. + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. */ - template - void create_aggregate_function() { - static_assert(is_aggregate_function_v, "F can't be a scalar function"); + template + void create_aggregate_function(Args&&... constructorArgs) { + return this->create_aggregate_function>(std::forward(constructorArgs)...); + } +#endif - std::stringstream ss; - ss << F::name() << std::flush; - auto name = ss.str(); - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); - this->aggregateFunctions.emplace_back(new user_defined_aggregate_function_t{ - std::move(name), - argsCount, - /* create = */ - []() -> int* { - return (int*)(new F()); - }, - /* step = */ - [](sqlite3_context*, void* functionVoidPointer, int argsCount, sqlite3_value** values) { - auto& function = *static_cast(functionVoidPointer); - args_tuple argsTuple; - values_to_tuple{}(values, argsTuple, argsCount); - call(function, &F::step, std::move(argsTuple)); - }, - /* finalCall = */ - [](sqlite3_context* context, void* functionVoidPointer) { - auto& function = *static_cast(functionVoidPointer); - auto result = function.fin(); - statement_binder().result(context, result); - }, - delete_function_callback, - }); + /** + * Delete a scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ + template + void delete_scalar_function() { + static_assert(is_scalar_udf_v, "F must be a scalar function"); + udf_holder udfName; + this->delete_function_impl(udfName(), this->scalarFunctions); + } - if(this->connection->retain_count() > 0) { - sqlite3* db = this->connection->get(); - try_to_create_function( - db, - static_cast(*this->aggregateFunctions.back())); - } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Delete a scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. + */ + template + void delete_scalar_function() { + this->delete_function_impl(f.name(), this->scalarFunctions); } /** - * Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no. + * Delete a quoted scalar function you created before. + * Can be called at any time no matter whether the database connection is open or not. */ - template + template void delete_scalar_function() { - static_assert(is_scalar_function_v, "F cannot be an aggregate function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->scalarFunctions); + this->delete_function_impl(quotedF.name(), this->scalarFunctions); } +#endif /** - * Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no. + * Delete aggregate function you created before. + * Can be called at any time no matter whether the database connection is open or not. */ template void delete_aggregate_function() { - static_assert(is_aggregate_function_v, "F cannot be a scalar function"); - std::stringstream ss; - ss << F::name() << std::flush; - this->delete_function_impl(ss.str(), this->aggregateFunctions); + static_assert(is_aggregate_udf_v, "F must be an aggregate function"); + udf_holder udfName; + this->delete_function_impl(udfName(), this->aggregateFunctions); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + void delete_aggregate_function() { + this->delete_function_impl(f.name(), this->aggregateFunctions); } +#endif template void create_collation() { @@ -14385,26 +18112,28 @@ namespace sqlite_orm { } void create_collation(const std::string& name, collating_function f) { - collating_function* function = nullptr; const auto functionExists = bool(f); + collating_function* function = nullptr; if(functionExists) { function = &(collatingFunctions[name] = std::move(f)); - } else { - collatingFunctions.erase(name); } // create collations if db is open if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_collation(db, - name.c_str(), - SQLITE_UTF8, - function, - functionExists ? collate_callback : nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, + name.c_str(), + SQLITE_UTF8, + function, + functionExists ? collate_callback : nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } + + if(!functionExists) { + collatingFunctions.erase(name); + } } template @@ -14573,7 +18302,6 @@ namespace sqlite_orm { } #if SQLITE_VERSION_NUMBER >= 3006019 - void foreign_keys(sqlite3* db, bool value) { std::stringstream ss; ss << "PRAGMA foreign_keys = " << value << std::flush; @@ -14603,9 +18331,8 @@ namespace sqlite_orm { } for(auto& p: this->collatingFunctions) { - auto resultCode = - sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } @@ -14618,12 +18345,12 @@ namespace sqlite_orm { sqlite3_busy_handler(this->connection->get(), busy_handler_callback, this); } - for(auto& functionPointer: this->scalarFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->scalarFunctions) { + try_to_create_scalar_function(db, udfProxy); } - for(auto& functionPointer: this->aggregateFunctions) { - try_to_create_function(db, static_cast(*functionPointer)); + for(auto& udfProxy: this->aggregateFunctions) { + try_to_create_aggregate_function(db, udfProxy); } if(this->on_open) { @@ -14631,101 +18358,156 @@ namespace sqlite_orm { } } - void delete_function_impl(const std::string& name, - std::vector>& functionsVector) const { - auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& functionPointer) { - return functionPointer->name == name; - }); - if(it != functionsVector.end()) { - functionsVector.erase(it); - it = functionsVector.end(); + template + void create_scalar_function_impl(udf_holder udfName, std::function constructAt) { + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr auto argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); + using is_stateless = std::is_empty; + auto udfMemorySpace = preallocate_udf_memory(); + if SQLITE_ORM_CONSTEXPR_IF(is_stateless::value) { + constructAt(udfMemorySpace.first); + } + this->scalarFunctions.emplace_back( + udfName(), + argsCount, + is_stateless::value ? nullptr : std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_destruct_only_deleter{}), + /* call = */ + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, argsCount); + auto result = polyfill::apply(*udfPointer, std::move(argsTuple)); + statement_binder().result(context, result); + }, + udfMemorySpace); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_scalar_function(db, this->scalarFunctions.back()); + } + } + + template + void create_aggregate_function_impl(udf_holder udfName, + std::function constructAt) { + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr auto argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); + this->aggregateFunctions.emplace_back( + udfName(), + argsCount, + std::move(constructAt), + /* destroy = */ + obtain_xdestroy_for(udf_destruct_only_deleter{}), + /* step = */ + [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + F* udfPointer; + try { + udfPointer = proxy_get_aggregate_step_udf(context, argsCount); + } catch(const std::bad_alloc&) { + sqlite3_result_error_nomem(context); + return; + } + args_tuple argsTuple = tuple_from_values{}(values, argsCount); +#if __cpp_lib_bind_front >= 201907L + std::apply(std::bind_front(&F::step, udfPointer), std::move(argsTuple)); +#else + polyfill::apply( + [udfPointer](auto&&... args) { + udfPointer->step(std::forward(args)...); + }, + std::move(argsTuple)); +#endif + }, + /* finalCall = */ + [](void* udfHandle, sqlite3_context* context) { + F& udf = *static_cast(udfHandle); + auto result = udf.fin(); + statement_binder().result(context, result); + }, + obtain_udf_allocator()); + + if(this->connection->retain_count() > 0) { + sqlite3* db = this->connection->get(); + try_to_create_aggregate_function(db, this->aggregateFunctions.back()); + } + } + void delete_function_impl(const std::string& name, std::list& functions) const { +#if __cpp_lib_ranges >= 201911L + auto it = std::ranges::find(functions, name, &udf_proxy::name); +#else + auto it = std::find_if(functions.begin(), functions.end(), [&name](auto& udfProxy) { + return udfProxy.name == name; + }); +#endif + if(it != functions.end()) { if(this->connection->retain_count() > 0) { sqlite3* db = this->connection->get(); - auto resultCode = sqlite3_create_function_v2(db, - name.c_str(), - 0, - SQLITE_UTF8, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + int rc = sqlite3_create_function_v2(db, + name.c_str(), + it->argumentsCount, + SQLITE_UTF8, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } + it = functions.erase(it); } else { throw std::system_error{orm_error_code::function_not_found}; } } - void try_to_create_function(sqlite3* db, user_defined_scalar_function_t& function) { - auto resultCode = sqlite3_create_function_v2(db, - function.name.c_str(), - function.argumentsCount, - SQLITE_UTF8, - &function, - scalar_function_callback, - nullptr, - nullptr, - nullptr); - if(resultCode != SQLITE_OK) { + static void try_to_create_scalar_function(sqlite3* db, udf_proxy& udfProxy) { + int rc = sqlite3_create_function_v2(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + udfProxy.func, + nullptr, + nullptr, + nullptr); + if(rc != SQLITE_OK) { throw_translated_sqlite_error(db); } } - void try_to_create_function(sqlite3* db, user_defined_aggregate_function_t& function) { - auto resultCode = sqlite3_create_function(db, - function.name.c_str(), - function.argumentsCount, - SQLITE_UTF8, - &function, - nullptr, - aggregate_function_step_callback, - aggregate_function_final_callback); - if(resultCode != SQLITE_OK) { - throw_translated_sqlite_error(resultCode); + static void try_to_create_aggregate_function(sqlite3* db, udf_proxy& udfProxy) { + int rc = sqlite3_create_function(db, + udfProxy.name.c_str(), + udfProxy.argumentsCount, + SQLITE_UTF8, + &udfProxy, + nullptr, + udfProxy.func, + aggregate_function_final_callback); + if(rc != SQLITE_OK) { + throw_translated_sqlite_error(rc); } } - static void - aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - if(*aggregateContextIntPointer == nullptr) { - *aggregateContextIntPointer = functionPointer->create(); - } - functionPointer->step(context, *aggregateContextIntPointer, argsCount, values); - } - - static void aggregate_function_final_callback(sqlite3_context* context) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); - auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**)); - auto aggregateContextIntPointer = static_cast(aggregateContextVoidPointer); - functionPointer->finalCall(context, *aggregateContextIntPointer); - functionPointer->destroy(*aggregateContextIntPointer); - } - - static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto functionVoidPointer = sqlite3_user_data(context); - auto functionPointer = static_cast(functionVoidPointer); - std::unique_ptr callablePointer(functionPointer->create(), - functionPointer->destroy); - if(functionPointer->argumentsCount != -1 && functionPointer->argumentsCount != argsCount) { - throw std::system_error{orm_error_code::arguments_count_does_not_match}; - } - functionPointer->run(context, functionPointer, argsCount, values); + std::string current_time(sqlite3* db) { + std::string result; + perform_exec(db, "SELECT CURRENT_TIME", extract_single_value, &result); + return result; } - template - static void delete_function_callback(int* pointer) { - auto voidPointer = static_cast(pointer); - auto fPointer = static_cast(voidPointer); - delete fPointer; + std::string current_date(sqlite3* db) { + std::string result; + perform_exec(db, "SELECT CURRENT_DATE", extract_single_value, &result); + return result; } std::string current_timestamp(sqlite3* db) { @@ -14764,13 +18546,17 @@ namespace sqlite_orm { ++storageColumnInfoIndex) { // get storage's column info - auto& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; - auto& columnName = storageColumnInfo.name; + table_xinfo& storageColumnInfo = storageTableInfo[storageColumnInfoIndex]; + const std::string& columnName = storageColumnInfo.name; - // search for a column in db eith the same name + // search for a column in db with the same name +#if __cpp_lib_ranges >= 201911L + auto dbColumnInfoIt = std::ranges::find(dbTableInfo, columnName, &table_xinfo::name); +#else auto dbColumnInfoIt = std::find_if(dbTableInfo.begin(), dbTableInfo.end(), [&columnName](auto& ti) { return ti.name == columnName; }); +#endif if(dbColumnInfoIt != dbTableInfo.end()) { auto& dbColumnInfo = *dbColumnInfoIt; auto columnsAreEqual = @@ -14800,8 +18586,8 @@ namespace sqlite_orm { std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; - std::vector> scalarFunctions; - std::vector> aggregateFunctions; + std::list scalarFunctions; + std::list aggregateFunctions; }; } } @@ -14810,9 +18596,11 @@ namespace sqlite_orm { // #include "expression_object_type.h" -#include // std::decay +#include // std::decay, std::remove_reference #include // std::reference_wrapper +// #include "type_traits.h" + // #include "prepared_statement.h" namespace sqlite_orm { @@ -14823,58 +18611,35 @@ namespace sqlite_orm { struct expression_object_type; template - struct expression_object_type> : std::decay {}; + using expression_object_type_t = typename expression_object_type::type; - template - struct expression_object_type>> : std::decay {}; + template + using statement_object_type_t = expression_object_type_t>>; template - struct expression_object_type> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type>> : std::decay {}; - - template - struct expression_object_type> { - using type = typename replace_range_t::object_type; - }; + struct expression_object_type, void> : value_unref_type {}; - template - struct expression_object_type, L, O>> { - using type = typename replace_range_t, L, O>::object_type; - }; - - template - struct expression_object_type> { - using type = T; + template + struct expression_object_type> { + using type = object_type_t; }; template - struct expression_object_type, Ids...>> { - using type = T; - }; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template - struct expression_object_type>> : std::decay {}; - - template - struct expression_object_type> { - using type = typename insert_range_t::object_type; - }; - - template - struct expression_object_type, L, O>> { - using type = typename insert_range_t, L, O>::object_type; + struct expression_object_type> { + using type = object_type_t; }; template - struct expression_object_type> : std::decay {}; - - template - struct expression_object_type, Cols...>> : std::decay {}; + struct expression_object_type, void> : value_unref_type {}; template struct get_ref_t { @@ -14952,12 +18717,13 @@ namespace sqlite_orm { #include // std::string #include // std::enable_if, std::remove_pointer #include // std::vector -#include // std::iter_swap #ifndef SQLITE_ORM_OMITS_CODECVT +#include // std::wstring_convert #include // std::codecvt_utf8_utf16 -#endif // SQLITE_ORM_OMITS_CODECVT +#endif #include #include +#include // std::list // #include "functional/cxx_string_view.h" // #include "functional/cxx_optional.h" @@ -14978,13 +18744,29 @@ namespace sqlite_orm { // #include "ast/into.h" +// #include "ast/match.h" + +// #include "ast/rank.h" + +namespace sqlite_orm { + namespace internal { + struct rank_t {}; + } + + inline internal::rank_t rank() { + return {}; + } +} + +// #include "ast/special_keywords.h" + // #include "core_functions.h" // #include "constraints.h" // #include "conditions.h" -// #include "column.h" +// #include "schema/column.h" // #include "indexed_column.h" @@ -15035,8 +18817,8 @@ namespace sqlite_orm { namespace internal { - template - std::string serialize(const T&, const serializer_context&); + template + auto serialize(const T& t, const C& context); template std::vector& collect_table_column_names(std::vector& collectedExpressions, @@ -15044,11 +18826,11 @@ namespace sqlite_orm { const Ctx& context) { if(definedOrder) { auto& table = pick_table>(context.db_objects); - collectedExpressions.reserve(collectedExpressions.size() + table.count_columns_amount()); + collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); table.for_each_column([qualified = !context.skip_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { - if(is_alias_v) { + if(is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + "." + quote_identifier(column.name)); } else if(qualified) { @@ -15060,7 +18842,7 @@ namespace sqlite_orm { }); } else { collectedExpressions.reserve(collectedExpressions.size() + 1); - if(is_alias_v) { + if(is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); } else if(!context.skip_table_name) { const basic_table& table = pick_table>(context.db_objects); @@ -15073,63 +18855,294 @@ namespace sqlite_orm { return collectedExpressions; } - /** @short Column expression collector. - */ - struct column_names_getter { - /** - * The default implementation simply serializes the passed argument. - */ - template - std::vector& operator()(const E& t, const Ctx& context) { - auto columnExpression = serialize(t, context); - if(columnExpression.empty()) { + /** @short Column expression collector. + */ + struct column_names_getter { + /** + * The default implementation simply serializes the passed argument. + */ + template + std::vector& operator()(const E& t, const Ctx& context) { + auto columnExpression = serialize(t, context); + if(columnExpression.empty()) { + throw std::system_error{orm_error_code::column_not_found}; + } + this->collectedExpressions.reserve(this->collectedExpressions.size() + 1); + this->collectedExpressions.push_back(std::move(columnExpression)); + return this->collectedExpressions; + } + + template + std::vector& operator()(const std::reference_wrapper& expression, const Ctx& context) { + return (*this)(expression.get(), context); + } + + template + std::vector& operator()(const asterisk_t& expression, const Ctx& context) { + return collect_table_column_names(this->collectedExpressions, expression.defined_order, context); + } + + template + std::vector& operator()(const object_t& expression, const Ctx& context) { + return collect_table_column_names(this->collectedExpressions, expression.defined_order, context); + } + + template + std::vector& operator()(const columns_t& cols, const Ctx& context) { + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); + iterate_tuple(cols.columns, [this, &context](auto& colExpr) { + (*this)(colExpr, context); + }); + // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); + } + return this->collectedExpressions; + } + + template + std::vector& operator()(const struct_t& cols, const Ctx& context) { + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); + iterate_tuple(cols.columns, [this, &context](auto& colExpr) { + (*this)(colExpr, context); + }); + // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); + } + return this->collectedExpressions; + } + + std::vector collectedExpressions; + }; + + template + std::vector get_column_names(const T& t, const Ctx& context) { + column_names_getter serializer; + return serializer(t, context); + } + } +} + +// #include "cte_column_names_collector.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#include // std::reference_wrapper +#include +#endif + +// #include "functional/cxx_universal.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "type_traits.h" + +// #include "member_traits/member_traits.h" + +// #include "error_code.h" + +// #include "alias.h" + +// #include "select_constraints.h" + +// #include "serializer_context.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +namespace sqlite_orm { + namespace internal { + // collecting column names utilizes the statement serializer + template + auto serialize(const T& t, const C& context); + + inline void unquote_identifier(std::string& identifier) { + if(!identifier.empty()) { + constexpr char quoteChar = '"'; + constexpr char sqlEscaped[] = {quoteChar, quoteChar}; + identifier.erase(identifier.end() - 1); + identifier.erase(identifier.begin()); + for(size_t pos = 0; (pos = identifier.find(sqlEscaped, pos, 2)) != identifier.npos; ++pos) { + identifier.erase(pos, 1); + } + } + } + + inline void unquote_or_erase(std::string& name) { + constexpr char quoteChar = '"'; + if(name.front() == quoteChar) { + unquote_identifier(name); + } else { + // unaliased expression - see 3. below + name.clear(); + } + } + + template + struct cte_column_names_collector { + using expression_type = T; + + // Compound statements are never passed in by db_objects_for_expression() + static_assert(!is_compound_operator_v); + + template + std::vector operator()(const expression_type& t, const Ctx& context) const { + auto newContext = context; + newContext.skip_table_name = true; + std::string columnName = serialize(t, newContext); + if(columnName.empty()) { + throw std::system_error{orm_error_code::column_not_found}; + } + unquote_or_erase(columnName); + return {std::move(columnName)}; + } + }; + + template + std::vector get_cte_column_names(const T& t, const Ctx& context) { + cte_column_names_collector collector; + return collector(t, context); + } + + template + struct cte_column_names_collector> { + using expression_type = As; + + template + std::vector operator()(const expression_type& /*expression*/, const Ctx& /*context*/) const { + return {alias_extractor>::extract()}; + } + }; + + template + struct cte_column_names_collector> { + using expression_type = Wrapper; + + template + std::vector operator()(const expression_type& expression, const Ctx& context) const { + return get_cte_column_names(expression.get(), context); + } + }; + + template + struct cte_column_names_collector> { + using expression_type = Asterisk; + using T = typename Asterisk::type; + + template + std::vector operator()(const expression_type&, const Ctx& context) const { + auto& table = pick_table(context.db_objects); + + std::vector columnNames; + columnNames.reserve(size_t(table.template count_of())); + + table.for_each_column([&columnNames](const column_identifier& column) { + columnNames.push_back(column.name); + }); + return columnNames; + } + }; + + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + }; + + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + }; + + template + struct cte_column_names_collector> { + using expression_type = Columns; + + template + std::vector operator()(const expression_type& cols, const Ctx& context) const { + std::vector columnNames; + columnNames.reserve(size_t(cols.count)); + auto newContext = context; + newContext.skip_table_name = true; + iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { + using value_type = polyfill::remove_cvref_t; + + if constexpr(polyfill::is_specialization_of_v) { + columnNames.push_back(alias_extractor>::extract()); + } else { + std::string columnName = serialize(m, newContext); + if(!columnName.empty()) { + columnNames.push_back(std::move(columnName)); + } else { + throw std::system_error{orm_error_code::column_not_found}; + } + unquote_or_erase(columnNames.back()); + } + }); + return columnNames; + } + }; + + template = true> + std::vector + collect_cte_column_names(const E& sel, const ExplicitColRefs& explicitColRefs, const Ctx& context) { + // 1. determine column names from subselect + std::vector columnNames = get_cte_column_names(sel.col, context); + + // 2. override column names from cte expression + if(size_t n = std::tuple_size_v) { + if(n != columnNames.size()) { throw std::system_error{orm_error_code::column_not_found}; } - collectedExpressions.reserve(collectedExpressions.size() + 1); - collectedExpressions.push_back(std::move(columnExpression)); - return collectedExpressions; - } - - template - std::vector& operator()(const std::reference_wrapper& expression, const Ctx& context) { - return (*this)(expression.get(), context); - } - template - std::vector& operator()(const asterisk_t& expression, const Ctx& context) { - return collect_table_column_names(collectedExpressions, expression.defined_order, context); - } + size_t idx = 0; + iterate_tuple(explicitColRefs, [&idx, &columnNames, &context](auto& colRef) { + using ColRef = polyfill::remove_cvref_t; - template - std::vector& operator()(const object_t& expression, const Ctx& context) { - return collect_table_column_names(collectedExpressions, expression.defined_order, context); + if constexpr(polyfill::is_specialization_of_v) { + columnNames[idx] = alias_extractor>::extract(); + } else if constexpr(std::is_member_pointer::value) { + using O = table_type_of_t; + if(auto* columnName = find_column_name(context.db_objects, colRef)) { + columnNames[idx] = *columnName; + } else { + // relaxed: allow any member pointer as column reference + columnNames[idx] = typeid(ColRef).name(); + } + } else if constexpr(polyfill::is_specialization_of_v) { + columnNames[idx] = colRef.name; + } else if constexpr(std::is_same_v) { + if(!colRef.empty()) { + columnNames[idx] = colRef; + } + } else if constexpr(std::is_same_v>) { + if(columnNames[idx].empty()) { + columnNames[idx] = std::to_string(idx + 1); + } + } else { + static_assert(polyfill::always_false_v, "Invalid explicit column reference specified"); + } + ++idx; + }); } - template - std::vector& operator()(const columns_t& cols, const Ctx& context) { - collectedExpressions.reserve(collectedExpressions.size() + cols.count); - iterate_tuple(cols.columns, [this, &context](auto& colExpr) { - (*this)(colExpr, context); - }); - // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order - if(mpl::instantiate, - typename columns_t::columns_type>::value && - collectedExpressions.capacity() > collectedExpressions.size()) { - collectedExpressions.shrink_to_fit(); + // 3. fill in blanks with numerical column identifiers + { + for(size_t i = 0, n = columnNames.size(); i < n; ++i) { + if(columnNames[i].empty()) { + columnNames[i] = std::to_string(i + 1); + } } - return collectedExpressions; } - std::vector collectedExpressions; - }; - - template - std::vector get_column_names(const T& t, const Ctx& context) { - column_names_getter serializer; - return serializer(t, context); + return columnNames; } } } +#endif // #include "order_by_serializer.h" @@ -15212,18 +19225,24 @@ namespace sqlite_orm { // #include "serializing_util.h" +// #include "serialize_result_type.h" + // #include "statement_binder.h" // #include "values.h" -// #include "triggers.h" +// #include "schema/triggers.h" // #include "table_type_of.h" -// #include "index.h" +// #include "schema/index.h" + +// #include "schema/table.h" // #include "util.h" +// #include "error_code.h" + namespace sqlite_orm { namespace internal { @@ -15231,8 +19250,8 @@ namespace sqlite_orm { template struct statement_serializer; - template - std::string serialize(const T& t, const serializer_context& context) { + template + auto serialize(const T& t, const C& context) { statement_serializer serializer; return serializer(t, context); } @@ -15255,7 +19274,7 @@ namespace sqlite_orm { private: template && !std::is_base_of::value + std::enable_if_t::value && !std::is_base_of::value #ifndef SQLITE_ORM_OMITS_CODECVT && !std::is_base_of::value #endif @@ -15305,11 +19324,80 @@ namespace sqlite_orm { return quote_blob_literal(field_printer>{}(t)); } +#if SQLITE_VERSION_NUMBER >= 3020000 template std::string do_serialize(const pointer_binding&) const { // always serialize null (security reasons) return field_printer{}(nullptr); } +#endif + }; + + template + struct statement_serializer, void> { + using statement_type = table_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) { + return this->serialize(statement, context, statement.name); + } + + template + auto serialize(const statement_type& statement, const Ctx& context, const std::string& tableName) { + std::stringstream ss; + ss << "CREATE TABLE " << streaming_identifier(tableName) << " (" + << streaming_expressions_tuple(statement.elements, context) << ")"; + if(statement_type::is_without_rowid_v) { + ss << " WITHOUT ROWID"; + } + return ss.str(); + } + }; + + template<> + struct statement_serializer { + using statement_type = current_time_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_TIME"; + } + }; + + template<> + struct statement_serializer { + using statement_type = current_date_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_DATE"; + } + }; + + template<> + struct statement_serializer { + using statement_type = current_timestamp_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) { + return "CURRENT_TIMESTAMP"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = highlight_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) { + std::stringstream ss; + auto& tableName = lookup_table_name(context.db_objects); + ss << "HIGHLIGHT (" << streaming_identifier(tableName); + ss << ", " << serialize(statement.argument0, context); + ss << ", " << serialize(statement.argument1, context); + ss << ", " << serialize(statement.argument2, context) << ")"; + return ss.str(); + } }; /** @@ -15320,12 +19408,12 @@ namespace sqlite_orm { using statement_type = T; template - std::string operator()(const T& literal, const Ctx& context) const { - static_assert(is_bindable_v>, "A literal value must be also bindable"); + std::string operator()(const statement_type& literal, const Ctx& context) const { + static_assert(is_bindable_v>, "A literal value must be also bindable"); Ctx literalCtx = context; literalCtx.replace_bindable_with_question = false; - statement_serializer> serializer{}; + statement_serializer> serializer{}; return serializer(literal.value, literalCtx); } }; @@ -15392,6 +19480,19 @@ namespace sqlite_orm { } }; + template + struct statement_serializer, void> { + using statement_type = match_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + auto& table = pick_table(context.db_objects); + std::stringstream ss; + ss << streaming_identifier(table.name) << " MATCH " << serialize(statement.argument, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = column_alias; @@ -15443,13 +19544,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } ss << statement.serialize() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; - if(context.use_parentheses) { - ss << ')'; - } return ss.str(); } }; @@ -15458,14 +19553,15 @@ namespace sqlite_orm { struct statement_serializer, void> : statement_serializer, void> {}; - template - struct statement_serializer, void> { - using statement_type = function_call; + template + struct statement_serializer, void> { + using statement_type = function_call; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - ss << F::name() << "(" << streaming_expressions_tuple(statement.args, context) << ")"; + stream_identifier(ss, "", statement.name(), ""); + ss << "(" << streaming_expressions_tuple(statement.callArgs, context) << ")"; return ss.str(); } }; @@ -15502,7 +19598,7 @@ namespace sqlite_orm { template struct statement_serializer< E, - std::enable_if_t, is_column_pointer>>> { + std::enable_if_t, is_column_pointer>::value>> { using statement_type = E; template @@ -15520,13 +19616,23 @@ namespace sqlite_orm { } }; + template<> + struct statement_serializer { + using statement_type = rank_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx&) const { + return "rank"; + } + }; + template<> struct statement_serializer { using statement_type = rowid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -15535,8 +19641,8 @@ namespace sqlite_orm { using statement_type = oid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -15545,8 +19651,8 @@ namespace sqlite_orm { using statement_type = _rowid_t; template - std::string operator()(const statement_type& s, const Ctx&) const { - return static_cast(s); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -15555,12 +19661,12 @@ namespace sqlite_orm { using statement_type = table_rowid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; @@ -15570,12 +19676,12 @@ namespace sqlite_orm { using statement_type = table_oid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; @@ -15585,32 +19691,27 @@ namespace sqlite_orm { using statement_type = table__rowid_t; template - std::string operator()(const statement_type& s, const Ctx& context) const { + std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; if(!context.skip_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } - ss << static_cast(s); + ss << static_cast(statement); return ss.str(); } }; - template - struct statement_serializer, void> { - using statement_type = binary_operator; + template + struct statement_serializer, void> { + using statement_type = is_equal_with_table_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { - auto lhs = serialize(statement.lhs, context); - auto rhs = serialize(statement.rhs, context); std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } - ss << lhs << " " << statement.serialize() << " " << rhs; - if(context.use_parentheses) { - ss << ')'; - } + const auto tableName = lookup_table_name(context.db_objects); + ss << streaming_identifier(tableName); + ss << " = "; + ss << serialize(statement.rhs, context); return ss.str(); } }; @@ -15677,6 +19778,77 @@ namespace sqlite_orm { } }; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#if SQLITE_VERSION_NUMBER >= 3035003 +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template<> + struct statement_serializer { + using statement_type = materialized_t; + + template + std::string_view operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "MATERIALIZED"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_materialized_t; + + template + std::string_view operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NOT MATERIALIZED"; + } + }; +#endif +#endif + + template + struct statement_serializer> { + using statement_type = CTE; + + template + std::string operator()(const statement_type& cte, const Ctx& context) const { + // A CTE always starts a new 'highest level' context + Ctx cteContext = context; + cteContext.use_parentheses = false; + + std::stringstream ss; + ss << streaming_identifier(alias_extractor>::extract()); + { + std::vector columnNames = + collect_cte_column_names(get_cte_driving_subselect(cte.subselect), + cte.explicitColumns, + context); + ss << '(' << streaming_identifiers(columnNames) << ')'; + } + ss << " AS" << streaming_constraints_tuple(cte.hints, context) << " (" + << serialize(cte.subselect, cteContext) << ')'; + return ss.str(); + } + }; + + template + struct statement_serializer> { + using statement_type = With; + + template + std::string operator()(const statement_type& c, const Ctx& context) const { + Ctx tupleContext = context; + tupleContext.use_parentheses = false; + + std::stringstream ss; + ss << "WITH"; + if(c.recursiveIndicated) { + ss << " RECURSIVE"; + } + ss << " " << serialize(c.cte, tupleContext); + ss << " " << serialize(c.expression, context); + return ss.str(); + } + }; +#endif + template struct statement_serializer> { using statement_type = T; @@ -15684,9 +19856,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& c, const Ctx& context) const { std::stringstream ss; - ss << serialize(c.left, context) << " "; - ss << static_cast(c) << " "; - ss << serialize(c.right, context); + ss << streaming_compound_expressions(c.compound, static_cast(c), context); return ss.str(); } }; @@ -15767,19 +19937,36 @@ namespace sqlite_orm { }; template - struct statement_serializer> { + struct statement_serializer< + T, + std::enable_if_t, is_binary_operator>::value>> { using statement_type = T; template - std::string operator()(const statement_type& c, const Ctx& context) const { - auto leftString = serialize(c.l, context); - auto rightString = serialize(c.r, context); + std::string operator()(const statement_type& statement, 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 parenthesizeLeft = is_binary_condition>::value || + is_binary_operator>::value; + constexpr bool parenthesizeRight = is_binary_condition>::value || + is_binary_operator>::value; + std::stringstream ss; - if(context.use_parentheses) { + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeLeft) { ss << "("; } - ss << leftString << " " << static_cast(c) << " " << rightString; - if(context.use_parentheses) { + ss << serialize(statement.lhs, subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeLeft) { + ss << ")"; + } + ss << " " << statement.serialize() << " "; + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeRight) { + ss << "("; + } + ss << serialize(statement.rhs, subCtx); + if SQLITE_ORM_CONSTEXPR_IF(parenthesizeRight) { ss << ")"; } return ss.str(); @@ -15812,9 +19999,12 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = dynamic_in_t; + template + struct statement_serializer< + dynamic_in_t, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = dynamic_in_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { @@ -15827,22 +20017,25 @@ namespace sqlite_orm { ss << "NOT IN"; } ss << " "; - if(is_compound_operator_v) { + if(is_compound_operator::value) { ss << '('; } auto newContext = context; newContext.use_parentheses = true; ss << serialize(statement.argument, newContext); - if(is_compound_operator_v) { + if(is_compound_operator::value) { ss << ')'; } return ss.str(); } }; - template - struct statement_serializer>, void> { - using statement_type = dynamic_in_t>; + template + struct statement_serializer< + dynamic_in_t, + std::enable_if_t, + polyfill::is_specialization_of>::value>> { + using statement_type = dynamic_in_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { @@ -15876,7 +20069,7 @@ namespace sqlite_orm { ss << " "; using args_type = std::tuple; constexpr bool theOnlySelect = - std::tuple_size::value == 1 && is_select_v>; + std::tuple_size::value == 1 && is_select>::value; if(!theOnlySelect) { ss << "("; } @@ -15949,22 +20142,12 @@ namespace sqlite_orm { } }; - template<> - struct statement_serializer { - using statement_type = autoincrement_t; - - template - std::string operator()(const statement_type&, const Ctx&) const { - return "AUTOINCREMENT"; - } - }; - template<> struct statement_serializer { using statement_type = conflict_clause_t; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case conflict_clause_t::rollback: return "ROLLBACK"; @@ -15981,13 +20164,33 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = primary_key_with_autoincrement; + template + struct statement_serializer, void> { + using statement_type = primary_key_with_autoincrement; template std::string operator()(const statement_type& statement, const Ctx& context) const { - return serialize(statement.primary_key, context) + " AUTOINCREMENT"; + return serialize(statement.as_base(), context) + " AUTOINCREMENT"; + } + }; + + template<> + struct statement_serializer { + using statement_type = null_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NULL"; + } + }; + + template<> + struct statement_serializer { + using statement_type = not_null_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "NOT NULL"; } }; @@ -16038,13 +20241,77 @@ namespace sqlite_orm { } }; +#if SQLITE_VERSION_NUMBER >= 3009000 + template<> + struct statement_serializer { + using statement_type = unindexed_t; + + template + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + return "UNINDEXED"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = prefix_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "prefix=" << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = tokenize_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "tokenize = " << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = content_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "content=" << serialize(statement.value, context); + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = table_content_t; + + template + std::string operator()(const statement_type& /*statement*/, const Ctx& context) const { + using mapped_type = typename statement_type::mapped_type; + + auto& table = pick_table(context.db_objects); + + std::stringstream ss; + ss << "content=" << streaming_identifier(table.name); + return ss.str(); + } + }; +#endif + template<> struct statement_serializer { using statement_type = collate_constraint_t; template - std::string operator()(const statement_type& c, const Ctx&) const { - return static_cast(c); + std::string operator()(const statement_type& statement, const Ctx&) const { + return static_cast(statement); } }; @@ -16053,11 +20320,12 @@ namespace sqlite_orm { using statement_type = default_t; template - std::string operator()(const statement_type& c, const Ctx& context) const { - return static_cast(c) + " (" + serialize(c.value, context) + ")"; + std::string operator()(const statement_type& statement, const Ctx& context) const { + return static_cast(statement) + " (" + serialize(statement.value, context) + ")"; } }; +#if SQLITE_VERSION_NUMBER >= 3006019 template struct statement_serializer, std::tuple>, void> { using statement_type = foreign_key_t, std::tuple>; @@ -16083,6 +20351,7 @@ namespace sqlite_orm { return ss.str(); } }; +#endif template struct statement_serializer, void> { @@ -16129,15 +20398,15 @@ namespace sqlite_orm { template std::string operator()(const statement_type& column, const Ctx& context) const { - using column_type = statement_type; - std::stringstream ss; - ss << streaming_identifier(column.name) << " " << type_printer>().print() - << " " - << streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - column.is_not_null(), - context); + ss << streaming_identifier(column.name); + if(!context.fts5_columns) { + ss << " " << type_printer>>().print(); + } + ss << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + column.is_not_null(), + context); return ss.str(); } }; @@ -16163,15 +20432,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")" << " VALUES (" << streaming_field_values_excluding(check_if{}, - empty_callable(), // don't exclude + empty_callable, // don't exclude context, get_ref(statement.object)) << ")"; @@ -16187,8 +20455,7 @@ namespace sqlite_orm { std::string operator()(const statement_type& ins, const Ctx& context) const { constexpr size_t colsCount = std::tuple_size>::value; static_assert(colsCount > 0, "Use insert or replace with 1 argument instead"); - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "INSERT INTO " << streaming_identifier(table.name) << " "; @@ -16215,15 +20482,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "UPDATE " << streaming_identifier(table.name) << " SET "; table.template for_each_column_excluding>( [&table, &ss, &context, &object = get_ref(statement.object), first = true](auto& column) mutable { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -16234,7 +20500,7 @@ namespace sqlite_orm { ss << " WHERE "; table.for_each_column( [&table, &context, &ss, &object = get_ref(statement.object), first = true](auto& column) mutable { - if(!column.template is() && !table.exists_in_composite_primary_key(column)) { + if(!column.template is() && !exists_in_composite_primary_key(table, column)) { return; } @@ -16306,11 +20572,6 @@ namespace sqlite_orm { return std::move(collector.table_names); } - template = true> - std::set> collect_table_names(const T& table, const Ctx&) { - return {{table.name, ""}}; - } - template struct statement_serializer, void> { using statement_type = update_all_t; @@ -16336,7 +20597,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); using is_without_rowid = typename std::decay_t::is_without_rowid; @@ -16345,7 +20606,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -16367,7 +20628,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>{}, [&table](auto& column) { - return table.exists_in_composite_primary_key(column); + return exists_in_composite_primary_key(table, column); }, context, get_ref(statement.object)) @@ -16392,18 +20653,21 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = columns_t; + template + struct statement_serializer, is_struct>::value>> { + using statement_type = C; template std::string operator()(const statement_type& statement, const Ctx& context) const { + // subqueries should always use parentheses in column names + auto subCtx = context; + subCtx.use_parentheses = true; + std::stringstream ss; if(context.use_parentheses) { ss << '('; } - // note: pass `statement` itself - ss << streaming_serialized(get_column_names(statement, context)); + ss << streaming_serialized(get_column_names(statement, subCtx)); if(context.use_parentheses) { ss << ')'; } @@ -16412,13 +20676,15 @@ namespace sqlite_orm { }; template - struct statement_serializer, is_replace_raw>>> { + struct statement_serializer< + T, + std::enable_if_t, is_replace_raw>::value>> { using statement_type = T; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - if(is_insert_raw_v) { + if(is_insert_raw::value) { ss << "INSERT"; } else { ss << "REPLACE"; @@ -16426,12 +20692,12 @@ namespace sqlite_orm { iterate_tuple(statement.args, [&context, &ss](auto& value) { using value_type = std::decay_t; ss << ' '; - if(is_columns_v) { + if(is_columns::value) { auto newContext = context; newContext.skip_table_name = true; newContext.use_parentheses = true; ss << serialize(value, newContext); - } else if(is_values_v || is_select_v) { + } else if(is_values::value || is_select::value) { auto newContext = context; newContext.use_parentheses = false; ss << serialize(value, newContext); @@ -16478,15 +20744,14 @@ namespace sqlite_orm { template std::string operator()(const statement_type& rep, const Ctx& context) const { - using expression_type = std::decay_t; - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; ss << "REPLACE INTO " << streaming_identifier(table.name) << " (" << streaming_non_generated_column_names(table) << ")"; const auto valuesCount = std::distance(rep.range.first, rep.range.second); - const auto columnsCount = table.non_generated_columns_count(); + const auto columnsCount = table.template count_of_columns_excluding(); ss << " VALUES " << streaming_values_placeholders(columnsCount, valuesCount); return ss.str(); } @@ -16498,7 +20763,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - using object_type = typename expression_object_type::type; + using object_type = expression_object_type_t; auto& table = pick_table(context.db_objects); using is_without_rowid = typename std::decay_t::is_without_rowid; @@ -16507,7 +20772,7 @@ namespace sqlite_orm { mpl::conjunction>, mpl::disjunction_fn>>( [&table, &columnNames](auto& column) { - if(table.exists_in_composite_primary_key(column)) { + if(exists_in_composite_primary_key(table, column)) { return; } @@ -16534,15 +20799,16 @@ namespace sqlite_orm { }; template - std::string serialize_get_all_impl(const T& get, const Ctx& context) { - using primary_type = type_t; + std::string serialize_get_all_impl(const T& getAll, const Ctx& context) { + using table_type = type_t; + using mapped_type = mapped_type_proxy_t; - auto& table = pick_table(context.db_objects); - auto tableNames = collect_table_names(table, context); + auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, true) << " FROM " - << streaming_identifiers(tableNames) << streaming_conditions_tuple(get.conditions, context); + ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)) + << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()) + << streaming_conditions_tuple(getAll.conditions, context); return ss.str(); } @@ -16583,7 +20849,7 @@ namespace sqlite_orm { using primary_type = type_t; auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, false) << " FROM " + ss << "SELECT " << streaming_table_column_names(table, std::string{}) << " FROM " << streaming_identifier(table.name) << " WHERE "; auto primaryKeyColumnNames = table.primary_key_column_names(); @@ -16625,7 +20891,7 @@ namespace sqlite_orm { using statement_type = conflict_action; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case conflict_action::replace: return "REPLACE"; @@ -16648,7 +20914,10 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) const { - return "OR " + serialize(statement.action, context); + std::stringstream ss; + + ss << "OR " << serialize(statement.action, context); + return ss.str(); } }; @@ -16670,9 +20939,12 @@ namespace sqlite_orm { template std::string operator()(const statement_type& sel, Ctx context) const { context.skip_table_name = false; + // subqueries should always use parentheses in column names + auto subCtx = context; + subCtx.use_parentheses = true; std::stringstream ss; - if(!is_compound_operator_v) { + if(!is_compound_operator::value) { if(!sel.highest_level && context.use_parentheses) { ss << "("; } @@ -16681,9 +20953,9 @@ namespace sqlite_orm { if(get_distinct(sel.col)) { ss << static_cast(distinct(0)) << " "; } - ss << streaming_serialized(get_column_names(sel.col, context)); + ss << streaming_serialized(get_column_names(sel.col, subCtx)); using conditions_tuple = typename statement_type::conditions_type; - constexpr bool hasExplicitFrom = tuple_has::value; + constexpr bool hasExplicitFrom = tuple_has::value; if(!hasExplicitFrom) { auto tableNames = collect_table_names(sel, context); using joins_index_sequence = filter_tuple_sequence_t; @@ -16696,12 +20968,12 @@ namespace sqlite_orm { alias_extractor::as_alias()}; tableNames.erase(tableNameWithAlias); }); - if(!tableNames.empty() && !is_compound_operator_v) { + if(!tableNames.empty() && !is_compound_operator::value) { ss << " FROM " << streaming_identifiers(tableNames); } } ss << streaming_conditions_tuple(sel.conditions, context); - if(!is_compound_operator_v) { + if(!is_compound_operator::value) { if(!sel.highest_level && context.use_parentheses) { ss << ")"; } @@ -16737,6 +21009,37 @@ namespace sqlite_orm { } }; +#if SQLITE_VERSION_NUMBER >= 3009000 + template + struct statement_serializer, void> { + using statement_type = using_fts5_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "USING FTS5("; + auto subContext = context; + subContext.fts5_columns = true; + ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; + return ss.str(); + } + }; +#endif + + template + struct statement_serializer, void> { + using statement_type = virtual_table_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + ss << "CREATE VIRTUAL TABLE IF NOT EXISTS "; + ss << streaming_identifier(statement.name) << ' '; + ss << serialize(statement.module_details, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = index_t; @@ -16755,7 +21058,7 @@ namespace sqlite_orm { std::string whereString; iterate_tuple(statement.elements, [&columnNames, &context, &whereString](auto& value) { using value_type = std::decay_t; - if(!is_where_v) { + if(!is_where::value) { auto newContext = context; newContext.use_parentheses = false; auto whereString = serialize(value, newContext); @@ -16773,23 +21076,21 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = from_t; + template + struct statement_serializer> { + using statement_type = From; template std::string operator()(const statement_type&, const Ctx& context) const { - using tuple = std::tuple; - std::stringstream ss; ss << "FROM "; - iterate_tuple([&context, &ss, first = true](auto* item) mutable { - using from_type = std::remove_pointer_t; + iterate_tuple([&context, &ss, first = true](auto* dummyItem) mutable { + using table_type = std::remove_pointer_t; constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] - << streaming_identifier(lookup_table_name>(context.db_objects), - alias_extractor::as_alias()); + << streaming_identifier(lookup_table_name>(context.db_objects), + alias_extractor::as_alias()); }); return ss.str(); } @@ -16853,7 +21154,7 @@ namespace sqlite_orm { using statement_type = trigger_timing; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case trigger_timing::trigger_before: return "BEFORE"; @@ -16871,7 +21172,7 @@ namespace sqlite_orm { using statement_type = trigger_type; template - std::string operator()(const statement_type& statement, const Ctx&) const { + serialize_result_type operator()(const statement_type& statement, const Ctx&) const { switch(statement) { case trigger_type::trigger_delete: return "DELETE"; @@ -16945,7 +21246,7 @@ namespace sqlite_orm { ss << " BEGIN "; iterate_tuple(statement.elements, [&ss, &context](auto& element) { using element_type = std::decay_t; - if(is_select_v) { + if(is_select::value) { auto newContext = context; newContext.use_parentheses = false; ss << serialize(element, newContext); @@ -17012,8 +21313,8 @@ namespace sqlite_orm { template struct statement_serializer< Join, - std::enable_if_t, - polyfill::is_specialization_of>>> { + std::enable_if_t, + polyfill::is_specialization_of>::value>> { using statement_type = Join; template @@ -17083,146 +21384,585 @@ namespace sqlite_orm { } }; + /** + * HO - has offset + * OI - offset is implicit + */ + template + struct statement_serializer, void> { + using statement_type = limit_t; + + template + std::string operator()(const statement_type& limt, const Ctx& context) const { + auto newContext = context; + newContext.skip_table_name = false; + std::stringstream ss; + ss << static_cast(limt) << " "; + if(HO) { + if(OI) { + limt.off.apply([&newContext, &ss](auto& value) { + ss << serialize(value, newContext); + }); + ss << ", "; + ss << serialize(limt.lim, newContext); + } else { + ss << serialize(limt.lim, newContext) << " OFFSET "; + limt.off.apply([&newContext, &ss](auto& value) { + ss << serialize(value, newContext); + }); + } + } else { + ss << serialize(limt.lim, newContext); + } + return ss.str(); + } + }; + + template<> + struct statement_serializer { + using statement_type = default_values_t; + + template + serialize_result_type operator()(const statement_type&, const Ctx&) const { + return "DEFAULT VALUES"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = using_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + auto newContext = context; + newContext.skip_table_name = true; + return static_cast(statement) + " (" + serialize(statement.column, newContext) + ")"; + } + }; + + template + struct statement_serializer, void> { + using statement_type = std::tuple; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + if(context.use_parentheses) { + ss << '('; + } + ss << streaming_expressions_tuple(statement, context); + if(context.use_parentheses) { + ss << ')'; + } + return ss.str(); + } + }; + + template + struct statement_serializer, void> { + using statement_type = values_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) const { + std::stringstream ss; + if(context.use_parentheses) { + ss << '('; + } + ss << "VALUES "; + { + Ctx tupleContext = context; + tupleContext.use_parentheses = true; + ss << streaming_expressions_tuple(statement.tuple, tupleContext); + } + if(context.use_parentheses) { + ss << ')'; + } + return ss.str(); + } + }; + template - struct statement_serializer, void> { - using statement_type = having_t; + struct statement_serializer, void> { + using statement_type = dynamic_values_t; template std::string operator()(const statement_type& statement, const Ctx& context) const { std::stringstream ss; - auto newContext = context; - newContext.skip_table_name = false; - ss << "HAVING " << serialize(statement.expression, newContext); + if(context.use_parentheses) { + ss << '('; + } + ss << "VALUES " << streaming_dynamic_expressions(statement.vector, context); + if(context.use_parentheses) { + ss << ')'; + } return ss.str(); } }; + } +} + +// #include "serializer_context.h" + +// #include "schema/triggers.h" + +// #include "object_from_column_builder.h" + +// #include "row_extractor.h" + +// #include "schema/table.h" + +// #include "schema/column.h" + +// #include "schema/index.h" + +// #include "cte_storage.h" + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include +#include +#include +#include +#endif + +// #include "functional/cxx_universal.h" +// ::size_t +// #include "tuple_helper/tuple_fy.h" + +// #include "table_type_of.h" + +// #include "column_result.h" + +// #include "select_constraints.h" + +// #include "schema/table.h" + +// #include "alias.h" + +// #include "cte_types.h" + +// #include "cte_column_names_collector.h" + +// #include "column_expression.h" + +#include // std::enable_if, std::is_same, std::decay, std::is_arithmetic +#include // std::tuple +#include // std::reference_wrapper + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "tuple_helper/tuple_transformer.h" + +// #include "type_traits.h" + +// #include "select_constraints.h" + +// #include "alias.h" + +// #include "storage_traits.h" + +namespace sqlite_orm { + + namespace internal { + + template + struct column_expression_type; + + /** + * Obains the expressions that form the columns of a subselect statement. + */ + template + using column_expression_of_t = typename column_expression_type::type; + + /** + * Identity. + */ + template + struct column_expression_type { + using type = E; + }; /** - * HO - has offset - * OI - offset is implicit + * Resolve column alias. + * as_t -> as_t */ - template - struct statement_serializer, void> { - using statement_type = limit_t; + template + struct column_expression_type> { + using type = as_t, column_expression_of_t>>; + }; + + /** + * Resolve reference wrapper. + * reference_wrapper -> T& + */ + template + struct column_expression_type, void> + : std::add_lvalue_reference> {}; + + // No CTE for object expression. + template + struct column_expression_type, void> { + static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); + }; + + /** + * Resolve all columns of a mapped object or CTE. + * asterisk_t -> tuple + */ + template + struct column_expression_type, + std::enable_if_t>, + is_cte_moniker>::value>> + : storage_traits::storage_mapped_column_expressions {}; + + template + struct add_column_alias { + template + using apply_t = alias_column_t; + }; + /** + * Resolve all columns of an aliased object. + * asterisk_t -> tuple...> + */ + template + struct column_expression_type, match_if> + : tuple_transformer>::type, + add_column_alias::template apply_t> {}; + + /** + * Resolve multiple columns. + * columns_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + + /** + * Resolve multiple columns. + * struct_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + + /** + * Resolve column(s) of subselect. + * select_t -> ColExpr, tuple + */ + template + struct column_expression_type> : column_expression_type {}; + } +} + +// #include "storage_lookup.h" + +namespace sqlite_orm { + namespace internal { + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + // F = field_type + template + struct create_cte_mapper { + using type = subselect_mapper; + }; + + // std::tuple + template + struct create_cte_mapper> { + using type = subselect_mapper; + }; + + template + using create_cte_mapper_t = + typename create_cte_mapper:: + type; + + // aliased column expressions, explicit or implicitly numbered + template = true> + static auto make_cte_column(std::string name, const ColRef& /*finalColRef*/) { + using object_type = aliased_field, F>; + + return sqlite_orm::make_column<>(std::move(name), &object_type::field); + } + + // F O::* + template = true> + static auto make_cte_column(std::string name, const ColRef& finalColRef) { + using object_type = table_type_of_t; + using column_type = column_t; + + return column_type{std::move(name), finalColRef, empty_setter{}}; + } + + /** + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ + template + auto db_objects_cat(const DBOs& dbObjects, std::index_sequence, CTETables&&... cteTables) { + return std::tuple{std::forward(cteTables)..., get(dbObjects)...}; + } + + /** + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ + template + auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { + return db_objects_cat(dbObjects, + std::make_index_sequence>{}, + std::forward(cteTables)...); + } + + /** + * This function returns the expression contained in a subselect that is relevant for + * creating the definition of a CTE table. + * Because CTEs can recursively refer to themselves in a compound statement, parsing + * the whole compound statement would lead to compiler errors if a column_pointer<> + * can't be resolved. Therefore, at the time of building a CTE table, we are only + * interested in the column results of the left-most select expression. + */ + template + decltype(auto) get_cte_driving_subselect(const Select& subSelect); + + /** + * Return given select expression. + */ + template + decltype(auto) get_cte_driving_subselect(const Select& subSelect) { + return subSelect; + } + + /** + * Return left-most select expression of compound statement. + */ + template, bool> = true> + decltype(auto) get_cte_driving_subselect(const select_t& subSelect) { + return std::get<0>(subSelect.col.compound); + } - template - std::string operator()(const statement_type& limt, const Ctx& context) const { - auto newContext = context; - newContext.skip_table_name = false; - std::stringstream ss; - ss << static_cast(limt) << " "; - if(HO) { - if(OI) { - limt.off.apply([&newContext, &ss](auto& value) { - ss << serialize(value, newContext); - }); - ss << ", "; - ss << serialize(limt.lim, newContext); - } else { - ss << serialize(limt.lim, newContext) << " OFFSET "; - limt.off.apply([&newContext, &ss](auto& value) { - ss << serialize(value, newContext); - }); - } - } else { - ss << serialize(limt.lim, newContext); - } - return ss.str(); - } - }; + /** + * Return a tuple of member pointers of all columns + */ + template + auto get_table_columns_fields(const C& coldef, std::index_sequence) { + return std::make_tuple(get(coldef).member_pointer...); + } + + // any expression -> numeric column alias + template>, bool> = true> + auto extract_colref_expressions(const DBOs& /*dbObjects*/, const E& /*col*/, std::index_sequence = {}) + -> std::tuple())>> { + return {}; + } - template<> - struct statement_serializer { - using statement_type = default_values_t; + // expression_t<> + template + auto + extract_colref_expressions(const DBOs& dbObjects, const expression_t& col, std::index_sequence s = {}) { + return extract_colref_expressions(dbObjects, col.value, s); + } - template - std::string operator()(const statement_type&, const Ctx&) const { - return "DEFAULT VALUES"; - } - }; + // F O::* (field/getter) -> field/getter + template + auto extract_colref_expressions(const DBOs& /*dbObjects*/, F O::*col, std::index_sequence = {}) { + return std::make_tuple(col); + } - template - struct statement_serializer, void> { - using statement_type = using_t; + // as_t<> (aliased expression) -> alias_holder + template + std::tuple> extract_colref_expressions(const DBOs& /*dbObjects*/, + const as_t& /*col*/, + std::index_sequence = {}) { + return {}; + } - template - std::string operator()(const statement_type& statement, const Ctx& context) const { - auto newContext = context; - newContext.skip_table_name = true; - return static_cast(statement) + " (" + serialize(statement.column, newContext) + ")"; - } - }; + // alias_holder<> (colref) -> alias_holder + template + std::tuple> extract_colref_expressions(const DBOs& /*dbObjects*/, + const alias_holder& /*col*/, + std::index_sequence = {}) { + return {}; + } - template - struct statement_serializer, void> { - using statement_type = std::tuple; + // column_pointer<> + template + auto extract_colref_expressions(const DBOs& dbObjects, + const column_pointer& col, + std::index_sequence s = {}) { + return extract_colref_expressions(dbObjects, col.field, s); + } - template - std::string operator()(const statement_type& statement, const Ctx& context) const { - std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } - ss << streaming_expressions_tuple(statement, context); - if(context.use_parentheses) { - ss << ')'; - } - return ss.str(); - } - }; + // column expression tuple + template + auto extract_colref_expressions(const DBOs& dbObjects, + const std::tuple& cols, + std::index_sequence) { + return std::tuple_cat(extract_colref_expressions(dbObjects, get(cols), std::index_sequence{})...); + } - template - struct statement_serializer, void> { - using statement_type = values_t; + // columns_t<> + template + auto extract_colref_expressions(const DBOs& dbObjects, const columns_t& cols) { + return extract_colref_expressions(dbObjects, cols.columns, std::index_sequence_for{}); + } - template - std::string operator()(const statement_type& statement, const Ctx& context) const { - std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } - ss << "VALUES "; - { - Ctx tupleContext = context; - tupleContext.use_parentheses = true; - ss << streaming_expressions_tuple(statement.tuple, tupleContext); - } - if(context.use_parentheses) { - ss << ')'; - } - return ss.str(); - } - }; + // asterisk_t<> -> fields + template + auto extract_colref_expressions(const DBOs& dbObjects, const asterisk_t& /*col*/) { + using table_type = storage_pick_table_t; + using elements_t = typename table_type::elements_type; + using column_idxs = filter_tuple_sequence_t; - template - struct statement_serializer, void> { - using statement_type = dynamic_values_t; + auto& table = pick_table(dbObjects); + return get_table_columns_fields(table.elements, column_idxs{}); + } - template - std::string operator()(const statement_type& statement, const Ctx& context) const { - std::stringstream ss; - if(context.use_parentheses) { - ss << '('; - } - ss << "VALUES " << streaming_dynamic_expressions(statement.vector, context); - if(context.use_parentheses) { - ss << ')'; - } - return ss.str(); - } - }; - } -} + template + void extract_colref_expressions(const DBOs& /*dbObjects*/, const select_t& /*subSelect*/) = delete; -// #include "triggers.h" + template, bool> = true> + void extract_colref_expressions(const DBOs& /*dbObjects*/, const Compound& /*subSelect*/) = delete; -// #include "object_from_column_builder.h" + /* + * Depending on ExplicitColRef's type returns either the explicit column reference + * or the expression's column reference otherwise. + */ + template + auto determine_cte_colref(const DBOs& /*dbObjects*/, + const SubselectColRef& subselectColRef, + const ExplicitColRef& explicitColRef) { + if constexpr(polyfill::is_specialization_of_v) { + return explicitColRef; + } else if constexpr(std::is_member_pointer::value) { + return explicitColRef; + } else if constexpr(std::is_base_of_v) { + return explicitColRef.member_pointer; + } else if constexpr(std::is_same_v) { + return subselectColRef; + } else if constexpr(std::is_same_v>) { + return subselectColRef; + } else { + static_assert(polyfill::always_false_v, "Invalid explicit column reference specified"); + } + } -// #include "table.h" + template + auto determine_cte_colrefs([[maybe_unused]] const DBOs& dbObjects, + const SubselectColRefs& subselectColRefs, + [[maybe_unused]] const ExplicitColRefs& explicitColRefs, + std::index_sequence) { + if constexpr(std::tuple_size_v != 0) { + static_assert( + (!is_builtin_numeric_column_alias_v< + alias_holder_type_or_none_t>> && + ...), + "Numeric column aliases are reserved for referencing columns locally within a single CTE."); -// #include "column.h" + return std::tuple{ + determine_cte_colref(dbObjects, get(subselectColRefs), get(explicitColRefs))...}; + } else { + return subselectColRefs; + } + } + + template + auto make_cte_table_using_column_indices(const DBOs& /*dbObjects*/, + std::string tableName, + std::vector columnNames, + const ColRefs& finalColRefs, + std::index_sequence) { + return make_table( + std::move(tableName), + make_cte_column>(std::move(columnNames.at(CIs)), + get(finalColRefs))...); + } + + template + auto make_cte_table(const DBOs& dbObjects, const CTE& cte) { + using cte_type = CTE; + + auto subSelect = get_cte_driving_subselect(cte.subselect); + + using subselect_type = decltype(subSelect); + using column_results = column_result_of_t; + using index_sequence = std::make_index_sequence>>; + static_assert(cte_type::explicit_colref_count == 0 || + cte_type::explicit_colref_count == index_sequence::size(), + "Number of explicit columns of common table expression doesn't match the number of columns " + "in the subselect."); + + std::string tableName = alias_extractor>::extract(); + auto subselectColRefs = extract_colref_expressions(dbObjects, subSelect.col); + const auto& finalColRefs = + determine_cte_colrefs(dbObjects, subselectColRefs, cte.explicitColumns, index_sequence{}); + + serializer_context context{dbObjects}; + std::vector columnNames = collect_cte_column_names(subSelect, cte.explicitColumns, context); + + using mapper_type = create_cte_mapper_t, + typename cte_type::explicit_colrefs_tuple, + column_expression_of_t, + decltype(subselectColRefs), + polyfill::remove_cvref_t, + column_results>; + return make_cte_table_using_column_indices(dbObjects, + std::move(tableName), + std::move(columnNames), + finalColRefs, + index_sequence{}); + } + + template + decltype(auto) make_recursive_cte_db_objects(const DBOs& dbObjects, + const common_table_expressions& cte, + std::index_sequence) { + auto tbl = make_cte_table(dbObjects, get(cte)); + + if constexpr(sizeof...(In) > 0) { + return make_recursive_cte_db_objects( + // Because CTEs can depend on their predecessor we recursively pass in a new set of DBOs + db_objects_cat(dbObjects, std::move(tbl)), + cte, + std::index_sequence{}); + } else { + return db_objects_cat(dbObjects, std::move(tbl)); + } + } -// #include "index.h" + /** + * Return new DBOs for CTE expressions. + */ + template = true> + decltype(auto) db_objects_for_expression(DBOs& dbObjects, const with_t& e) { + return make_recursive_cte_db_objects(dbObjects, e.cte, std::index_sequence_for{}); + } +#endif + } +} // #include "util.h" @@ -17231,13 +21971,22 @@ namespace sqlite_orm { namespace sqlite_orm { namespace internal { + /* + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. + */ + template + struct indirectly_test_preparable; template SQLITE_ORM_INLINE_VAR constexpr bool is_preparable_v = false; - template - SQLITE_ORM_INLINE_VAR constexpr bool - is_preparable_v().prepare(std::declval()))>> = true; + SQLITE_ORM_INLINE_VAR constexpr bool is_preparable_v< + S, + E, + polyfill::void_t().prepare(std::declval()))>>> = + true; /** * Storage class itself. Create an instanse to use it as an interfacto to sqlite db by calling `make_storage` @@ -17255,6 +22004,8 @@ namespace sqlite_orm { storage_t(std::string filename, db_objects_type dbObjects) : storage_base{std::move(filename), foreign_keys_count(dbObjects)}, db_objects{std::move(dbObjects)} {} + storage_t(const storage_t&) = default; + private: db_objects_type db_objects; @@ -17279,21 +22030,16 @@ namespace sqlite_orm { using table_type = std::decay_t; using context_t = serializer_context; - std::stringstream ss; context_t context{this->db_objects}; - ss << "CREATE TABLE " << streaming_identifier(tableName) << " ( " - << streaming_expressions_tuple(table.elements, context) << ")"; - if(table_type::is_without_rowid_v) { - ss << " WITHOUT ROWID"; - } - ss.flush(); - perform_void_exec(db, ss.str()); + statement_serializer serializer; + std::string sql = serializer.serialize(table, context, tableName); + perform_void_exec(db, std::move(sql)); } /** - * Copies sourceTableName to another table with name: destinationTableName - * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% - */ + * Copies sourceTableName to another table with name: destinationTableName + * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% + */ template void copy_table(sqlite3* db, const std::string& sourceTableName, @@ -17347,19 +22093,39 @@ namespace sqlite_orm { template void assert_mapped_type() const { - using mapped_types_tuple = std::tuple; - static_assert(mpl::invoke_t, mapped_types_tuple>::value, - "type is not mapped to a storage"); + static_assert(tuple_has_type::value, + "type is not mapped to storage"); + } + + template + void assert_updatable_type() const { +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + using Table = storage_pick_table_t; + using elements_type = elements_type_t

; + using col_index_sequence = filter_tuple_sequence_t; + using pk_index_sequence = filter_tuple_sequence_t; + using pkcol_index_sequence = col_index_sequence_with; + constexpr size_t dedicatedPrimaryKeyColumnsCount = + nested_tuple_size_for_t::value; + + constexpr size_t primaryKeyColumnsCount = + dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); + static_assert( + nonPrimaryKeysColumnsCount > 0, + "A table with only primary keys cannot be updated. You need at least 1 non-primary key column"); +#endif } template, - std::enable_if_t = true> + std::enable_if_t = true> void assert_insertable_type() const {} template, - std::enable_if_t = true> + std::enable_if_t = true> void assert_insertable_type() const { using elements_type = elements_type_t
; using pkcol_index_sequence = col_index_sequence_with; @@ -17387,14 +22153,44 @@ namespace sqlite_orm { } public: - template - view_t iterate(Args&&... args) { - this->assert_mapped_type(); + template, class... Args> + mapped_view iterate(Args&&... args) { + this->assert_mapped_type(); auto con = this->get_connection(); return {*this, std::move(con), std::forward(args)...}; } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + auto iterate(Args&&... args) { + return this->iterate(std::forward(args)...); + } +#endif + +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) + template +#ifdef SQLITE_ORM_CONCEPTS_SUPPORTED + requires(is_select_v
(42) } -> same_as>; + { get_pointer
(42) } -> same_as>; + { get_optional
(42) } -> same_as>; + { get_all
() } -> same_as>>; + { get_all_pointer
() } -> same_as>>; + { get_all_optional
() } -> same_as>>; + { remove
(42) } -> same_as>; + { remove_all
() } -> same_as>; + { count
() } -> same_as>; +}; + +template> +concept storage_refers_to_table_callable = requires(S& storage) { + { storage.get_all() } -> same_as>; + { storage.count() } -> same_as; + { storage.iterate() } -> same_as>; +}; + +template> +concept storage_table_reference_callable = requires(S& storage) { + { storage.get
(42) } -> same_as; + { storage.get_pointer
(42) } -> same_as>; + { storage.get_optional
(42) } -> same_as>; + { storage.get_all
() } -> same_as>; + { storage.get_all_pointer
() } -> same_as>>; + { storage.get_all_optional
() } -> same_as>>; + { storage.remove
(42) } -> same_as; + { storage.remove_all
() } -> same_as; + { storage.count
() } -> same_as; + { storage.iterate
() } -> same_as>; +}; +#endif + +TEST_CASE("column pointers") { + struct User { + int id; + }; + struct DerivedUser : User {}; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto derived_user = c(); +#endif + + SECTION("table reference") { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(orm_table_reference); + STATIC_REQUIRE_FALSE(is_table_alias_v); + STATIC_REQUIRE_FALSE(is_recordset_alias_v); + STATIC_REQUIRE_FALSE(orm_table_alias); + STATIC_REQUIRE_FALSE(orm_recordset_alias); + runTest>(derived_user); + runTest(internal::auto_decay_table_ref_t{}); +#endif + } + SECTION("column pointer expressions") { + runTest>(column(&User::id)); + runTest>(column(&DerivedUser::id)); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runTest>(derived_user->*&DerivedUser::id); + STATIC_REQUIRE(field_callable); + STATIC_REQUIRE(field_callable*&DerivedUser::id)>); + + using storage_type = decltype(make_storage( + "", + make_table("user", make_column("id", &User::id, primary_key())), + make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); + + STATIC_REQUIRE(storage_field_callable); + STATIC_REQUIRE(storage_field_callable*&DerivedUser::id)>); +#endif + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("table reference expressions") { + runTest>(make_table("derived_user")); + runTest>(from()); + runTest>(asterisk()); + runTest>(object()); + runTest>(count()); + runTest>(get(42)); + runTest>>(get_all()); + runTest>>( + left_join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + left_outer_join(using_(derived_user->*&DerivedUser::id))); + runTest>>( + inner_join(using_(derived_user->*&DerivedUser::id))); + + STATIC_REQUIRE(refers_to_recordset_callable); + STATIC_REQUIRE(refers_to_table_callable); + STATIC_REQUIRE(table_reference_callable); + STATIC_REQUIRE(refers_to_recordset_callable); + STATIC_REQUIRE(refers_to_table_callable); + STATIC_REQUIRE(table_reference_callable); + STATIC_REQUIRE(refers_to_recordset_callable); + STATIC_REQUIRE(refers_to_table_callable); + + using storage_type = decltype(make_storage( + "", + make_sqlite_schema_table(), + make_table("derived_user", make_column("id", &DerivedUser::id, primary_key())))); + + STATIC_REQUIRE(storage_refers_to_table_callable); + STATIC_REQUIRE(storage_table_reference_callable); + STATIC_REQUIRE(storage_refers_to_table_callable); + STATIC_REQUIRE(storage_table_reference_callable); + STATIC_REQUIRE(storage_refers_to_table_callable); + } +#endif +} diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index dbbe96395..0a914afa0 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -2,7 +2,10 @@ #include #include // std::is_same +using std::tuple; using namespace sqlite_orm; +using internal::structure; +using internal::table_reference; template void do_assert() { @@ -47,6 +50,10 @@ TEST_CASE("column_result_of_t") { runTest(&User::id); runTest(&User::name); + { + int n = 0; + runTest(std::ref(n)); + } runTest(in(&User::id, {1, 2, 3})); { std::vector vector; @@ -75,6 +82,7 @@ TEST_CASE("column_result_of_t") { runTest(count()); { struct RandomFunc { + static const char* name(); int operator()() const { return 4; } @@ -108,11 +116,42 @@ TEST_CASE("column_result_of_t") { runTest(rowid()); runTest(oid()); runTest(_rowid_()); - runTest>(columns(&User::id, &User::name)); - runTest>(asterisk()); - runTest>(asterisk>()); - runTest>(columns(asterisk(), asterisk())); + runTest>(columns(&User::id, &User::name)); + runTest>(asterisk()); + runTest>(asterisk>()); + runTest>(columns(asterisk(), asterisk())); runTest(column(&User::id)); runTest(alias_column>(&User::id)); - runTest(object()); + runTest>(object()); + runTest, table_reference>>(columns(object(), object())); + runTest>>(struct_(&User::id, &User::name)); + runTest>>(struct_(asterisk())); + runTest>, structure>>>( + columns(struct_(asterisk()), struct_(asterisk()))); + runTest(union_all(select(1), select(2))); + runTest(union_all(select(1ll), select(2))); +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + using cte_1 = decltype(1_ctealias); + // note: even though used with the CTE, &User::id doesn't need to be mapped into the CTE to make column results work; + // this is because the result type is taken from the member pointer just because we can't look it up in the storage definition + auto dbObjects2 = + internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cte().as(select(1)))); + using db_objects2_t = decltype(dbObjects2); + runTest(column(&User::id)); + runTest(column(1_colalias)); + runTest(column(get>())); + runTest(alias_column>(&User::id)); + runTest(alias_column>(1_colalias)); + runTest>(asterisk()); + runTest>(asterisk>()); + runTest(count()); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cte1 = 1_ctealias; + runTest(column(&User::id)); + runTest(column(1_colalias)); + runTest(column(get<1_colalias>())); + runTest(cte1->*&User::id); + runTest(count()); +#endif +#endif } diff --git a/tests/static_tests/compound_operators.cpp b/tests/static_tests/compound_operators.cpp index 164502e3b..b71f5c7f3 100644 --- a/tests/static_tests/compound_operators.cpp +++ b/tests/static_tests/compound_operators.cpp @@ -1,17 +1,22 @@ #include #include +#include // std::tuple_size #include "static_tests_common.h" using namespace sqlite_orm; +using internal::is_compound_operator_v; +using std::tuple_size; + +template +void runTest(Compound) { + STATIC_REQUIRE(is_compound_operator_v); + STATIC_REQUIRE(tuple_size::value == 3); +} TEST_CASE("Compound operators") { - auto unionValue = union_(select(&User::id), select(&Token::id)); - STATIC_REQUIRE(internal::is_base_of_template::value); - auto unionAllValue = union_all(select(&User::id), select(&Token::id)); - STATIC_REQUIRE(internal::is_base_of_template::value); - auto exceptValue = except(select(&User::id), select(&Token::id)); - STATIC_REQUIRE(internal::is_base_of_template::value); - auto intersectValue = intersect(select(&User::id), select(&Token::id)); - STATIC_REQUIRE(internal::is_base_of_template::value); + runTest(union_(select(&User::id), select(&User::id), select(&Token::id))); + runTest(union_all(select(&User::id), select(&User::id), select(&Token::id))); + runTest(except(select(&User::id), select(&User::id), select(&Token::id))); + runTest(intersect(select(&User::id), select(&User::id), select(&Token::id))); } diff --git a/tests/static_tests/cte.cpp b/tests/static_tests/cte.cpp new file mode 100644 index 000000000..b55231f6c --- /dev/null +++ b/tests/static_tests/cte.cpp @@ -0,0 +1,120 @@ +#include +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#include // std::is_same, std::is_constructible +#include // std::ignore +#include // std::string +#include + +using namespace sqlite_orm; +using internal::alias_holder, internal::column_alias; +using internal::column_t; +using std::is_same, std::is_constructible; +using std::tuple; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +using internal::using_t; +#endif + +template +void do_assert() { + STATIC_REQUIRE(is_same::value); +} + +template +void runDecay(ColRef /*colRef*/) { + do_assert, internal::decay_explicit_column_t>, E>(); +} + +template +void runTest(T /*test*/) { + do_assert(); +} + +TEST_CASE("CTE type traits") { + SECTION("decay_explicit_column") { + struct Org { + int64 id = 0; + int64 getId() const { + return this->id; + } + }; + + STATIC_REQUIRE(is_constructible>, column_alias<'c'>>::value); + runDecay>(&Org::id); + runDecay>(&Org::getId); + runDecay>>>(1_colalias); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runDecay>>>("a"_col); +#endif + runDecay>>(make_column("id", &Org::id)); + runDecay>>(std::ignore); + runDecay>(""); + } +} + +TEST_CASE("CTE building") { + SECTION("moniker") { + constexpr auto cte1 = 1_ctealias; + using cte_1 = decltype(1_ctealias); + runTest>(1_ctealias); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runTest>("z"_cte); + STATIC_REQUIRE(internal::is_cte_moniker_v); + STATIC_REQUIRE(orm_cte_moniker); + STATIC_REQUIRE(internal::is_recordset_alias_v); + STATIC_REQUIRE(orm_recordset_alias); + STATIC_REQUIRE_FALSE(internal::is_table_alias_v); + STATIC_REQUIRE_FALSE(orm_table_alias); +#endif + } + SECTION("builder") { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cte1 = 1_ctealias; + auto builder1 = cte<1_ctealias>(); +#else + using cte_1 = decltype(1_ctealias); + auto builder1 = cte(); +#endif + auto builder2 = 1_ctealias(); + STATIC_REQUIRE(std::is_same::value); + } +} + +TEST_CASE("CTE storage") { + SECTION("db_objects_cat") { + struct Org { + int64 id = 0; + }; + + auto table = make_table("org", make_column("id", &Org::id)); + auto idx1 = make_unique_index("idx1_org", &Org::id); + auto idx2 = make_index("idx2_org", &Org::id); + auto dbObjects = tuple{idx1, idx2, table}; + auto cteTable = internal::make_cte_table(dbObjects, 1_ctealias().as(select(1))); + auto dbObjects2 = internal::db_objects_cat(dbObjects, cteTable); + + // note: deliberately make indexes resulting in the same index_t<> type, such that we know `db_objects_cat()` is working properly + STATIC_REQUIRE(is_same::value); + STATIC_REQUIRE(is_same>::value); + } +} + +TEST_CASE("CTE expressions") { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cte1 = 1_ctealias; + using cte_1 = decltype(1_ctealias); + constexpr auto x = 1_colalias; + using x_t = decltype(1_colalias); + SECTION("moniker expressions") { + runTest>(from()); + runTest>(asterisk()); + runTest>(count()); + runTest>>>(left_join(using_(cte1->*x))); + runTest>>>(join(using_(cte1->*x))); + runTest>>>( + left_outer_join(using_(cte1->*x))); + runTest>>>(inner_join(using_(cte1->*x))); + } +#endif +} +#endif diff --git a/tests/static_tests/foreign_key.cpp b/tests/static_tests/foreign_key.cpp index 60ed3fe8a..c869e304e 100644 --- a/tests/static_tests/foreign_key.cpp +++ b/tests/static_tests/foreign_key.cpp @@ -7,6 +7,7 @@ #include "static_tests_storage_traits.h" +#if SQLITE_VERSION_NUMBER >= 3006019 using namespace sqlite_orm; TEST_CASE("foreign key static") { @@ -135,3 +136,4 @@ TEST_CASE("foreign key static") { STATIC_REQUIRE(std::is_same::value); } } +#endif diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index 9217d3a89..ee7c387c7 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -3,8 +3,28 @@ #include // std::is_same using namespace sqlite_orm; -using internal::is_aggregate_function_v; -using internal::is_scalar_function_v; +using internal::callable_arguments; +using internal::function; +using internal::function_call; +using internal::is_aggregate_udf_v; +using internal::is_scalar_udf_v; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +using internal::quoted_scalar_function; +#endif + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +template +concept storage_scalar_callable = requires(S& storage) { + { storage.create_scalar_function() }; + { storage.delete_scalar_function() }; +}; + +template +concept storage_aggregate_callable = requires(S& storage) { + { storage.create_aggregate_function() }; + { storage.delete_aggregate_function() }; +}; +#endif TEST_CASE("function static") { SECTION("scalar") { @@ -35,20 +55,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = double (Function::*)(double) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, double>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, double>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("double(double)") { struct Function { @@ -57,20 +76,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = double (Function::*)(double); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, double>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, double>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("int(std::string) const") { struct Function { @@ -79,20 +97,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = int (Function::*)(std::string) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("int(std::string)") { struct Function { @@ -101,20 +118,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = int (Function::*)(std::string); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("std::string(const std::string &, const std::string &) const") { struct Function { @@ -123,19 +139,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = std::string (Function::*)(const std::string&, const std::string&) const; STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE(std::is_same::args_tuple, + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("std::string(const std::string &, const std::string &)") { @@ -145,19 +161,19 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_scalar_function_v); - STATIC_REQUIRE(!is_aggregate_function_v); + STATIC_REQUIRE(is_scalar_udf_v); + STATIC_REQUIRE_FALSE(is_aggregate_udf_v); using RunMemberFunctionPointer = internal::scalar_call_function_t; using ExpectedType = std::string (Function::*)(const std::string&, const std::string&); STATIC_REQUIRE(std::is_same::value); - using ArgumentsTuple = internal::member_function_arguments::tuple_type; + using ArgumentsTuple = internal::function_arguments; using ExpectedArgumentsTuple = std::tuple; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE(std::is_same::args_tuple, + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } } @@ -178,8 +194,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_aggregate_function_v); - STATIC_REQUIRE(!is_scalar_function_v); + STATIC_REQUIRE(is_aggregate_udf_v); + STATIC_REQUIRE_FALSE(is_scalar_udf_v); using StepMemberFunctionPointer = internal::aggregate_step_function_t; using ExpectedStepType = void (Function::*)(int); @@ -189,8 +205,8 @@ TEST_CASE("function static") { using ExpectedFinType = int (Function::*)() const; STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, int>::value); - STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, int>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } SECTION("void(std::string) const & std::string()") { struct Function { @@ -205,8 +221,8 @@ TEST_CASE("function static") { } }; - STATIC_REQUIRE(is_aggregate_function_v); - STATIC_REQUIRE(!is_scalar_function_v); + STATIC_REQUIRE(is_aggregate_udf_v); + STATIC_REQUIRE_FALSE(is_scalar_udf_v); using StepMemberFunctionPointer = internal::aggregate_step_function_t; using ExpectedStepType = void (Function::*)(std::string) const; @@ -216,9 +232,75 @@ TEST_CASE("function static") { using ExpectedFinType = std::string (Function::*)(); STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same::return_type, std::string>::value); - STATIC_REQUIRE( - std::is_same::args_tuple, std::tuple>::value); + STATIC_REQUIRE(std::is_same::return_type, std::string>::value); + STATIC_REQUIRE(std::is_same::args_tuple, std::tuple>::value); } } + SECTION("function call expressions") { + struct SFunction { + static const char* name() { + return ""; + } + int operator()(int) const; + }; + struct AFunction { + static const char* name() { + return ""; + } + void step(int); + int fin() const; + }; + + constexpr auto scalar = func; + constexpr auto aggregate = func; + + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + + STATIC_REQUIRE(std::is_same::callable_type, SFunction>::value); + STATIC_REQUIRE(std::is_same::udf_type, SFunction>::value); + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(orm_scalar_function); + STATIC_REQUIRE_FALSE(orm_aggregate_function); + STATIC_REQUIRE(orm_aggregate_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + STATIC_REQUIRE_FALSE(storage_aggregate_callable); + STATIC_REQUIRE(storage_aggregate_callable); + STATIC_REQUIRE_FALSE(storage_scalar_callable); +#endif + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("quoted") { + constexpr auto quotedScalar = "f"_scalar.quote(std::clamp); + using quoted_type = decltype("f"_scalar.quote(std::clamp)); + + STATIC_REQUIRE(quotedScalar.nme[0] == 'f' && quotedScalar.nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v)*, + const int&(const int&, const int&, const int&), + 2>>); + + STATIC_REQUIRE(std::is_same_v)*>); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, int>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#endif } diff --git a/tests/static_tests/functional/index_sequence_util.cpp b/tests/static_tests/functional/index_sequence_util.cpp new file mode 100644 index 000000000..0b3d37695 --- /dev/null +++ b/tests/static_tests/functional/index_sequence_util.cpp @@ -0,0 +1,23 @@ +#include +#include + +using std::index_sequence; +using namespace sqlite_orm; +using internal::flatten_idxseq_t; +using internal::index_sequence_value_at; + +TEST_CASE("index sequence value at") { + STATIC_REQUIRE(index_sequence_value_at<0>(index_sequence<1, 3>{}) == 1); +#if defined(SQLITE_ORM_PACK_INDEXING_SUPPORTED) || defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(index_sequence_value_at<1>(index_sequence<1, 3>{}) == 3); +#endif +} + +TEST_CASE("flatten index sequence") { + STATIC_REQUIRE(std::is_same, index_sequence<>>::value); + STATIC_REQUIRE(std::is_same>, index_sequence<1, 3>>::value); + STATIC_REQUIRE( + std::is_same, index_sequence<1, 3>>, index_sequence<1, 3, 1, 3>>::value); + STATIC_REQUIRE(std::is_same, index_sequence<1, 3>, index_sequence<1, 3>>, + index_sequence<1, 3, 1, 3, 1, 3>>::value); +} diff --git a/tests/static_tests/functional/mpl.cpp b/tests/static_tests/functional/mpl.cpp index 928238547..719c310df 100644 --- a/tests/static_tests/functional/mpl.cpp +++ b/tests/static_tests/functional/mpl.cpp @@ -1,22 +1,77 @@ #include #include +#include +#include +#include // std::less +namespace mpl = sqlite_orm::internal::mpl; using namespace sqlite_orm; +using internal::literal_holder; + +template +using transparent_of_t = typename TransparentFunction::is_transparent; + +template +using make_literal_holder = literal_holder; + +template +using value_type_t = typename T::value_type; + +template +using check_if_same_type = mpl::bind_front_fn; TEST_CASE("mpl") { using mpl_is_same = mpl::quote_fn; + using check_if_same_template = + mpl::pass_extracted_fn_to>>; + using check_if_names_value_type = mpl::bind_front_higherorder_fn; + using predicate_type = std::less; - STATIC_REQUIRE(!mpl::is_metafunction_class_v); - STATIC_REQUIRE(mpl::is_metafunction_class_v); - STATIC_REQUIRE(mpl::invoke_fn_t::value); + STATIC_REQUIRE_FALSE(mpl::is_quoted_metafuntion_v); + STATIC_REQUIRE(mpl::is_quoted_metafuntion_v); + STATIC_REQUIRE(std::is_same, int>::value); + STATIC_REQUIRE(std::is_same, literal_holder>::value); + STATIC_REQUIRE( + std::is_same::fn>, literal_holder>::value); STATIC_REQUIRE(mpl::invoke_t::value); - STATIC_REQUIRE(!mpl::invoke_t, int, int>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t, int, int>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t, int, int>::value); STATIC_REQUIRE(mpl::invoke_t>, size_t, int>::value); STATIC_REQUIRE(mpl::invoke_t, std::vector>::value); STATIC_REQUIRE(mpl::invoke_t>::value); - STATIC_REQUIRE(!mpl::invoke_t>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t>::value); STATIC_REQUIRE(mpl::invoke_t, int, int>::value); - STATIC_REQUIRE(!mpl::invoke_t>, int, int>::value); + STATIC_REQUIRE(mpl::invoke_t, int, long>::value); + STATIC_REQUIRE(mpl::invoke_t>::value); + STATIC_REQUIRE(mpl::invoke_t>, int>::value); + STATIC_REQUIRE(mpl::invoke_t>, check_if_names_value_type>, + std::index_sequence<>>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t, check_if_names_value_type>, + std::index_sequence<>>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t>, + std::index_sequence<>>::value); + STATIC_REQUIRE_FALSE( + mpl::invoke_t>, int>::value); + // test short-circuited evaluation: `int` doesn't have a nested `value_type` typename + STATIC_REQUIRE_FALSE( + mpl::invoke_t< + mpl::conjunction, mpl::pass_result_of_fn, value_type_t>>, + int>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t>::value); + STATIC_REQUIRE(mpl::invoke_t>, int>::value); + STATIC_REQUIRE(mpl::invoke_t, + std::index_sequence<>>::value); + STATIC_REQUIRE(mpl::invoke_t, check_if_names_value_type>, int>::value); + STATIC_REQUIRE(mpl::invoke_t>, int>::value); + STATIC_REQUIRE(mpl::invoke_t>, + std::index_sequence<>>::value); + STATIC_REQUIRE(mpl::invoke_t, check_if_names_value_type>, + std::index_sequence<>>::value); + // test short-circuited evaluation: `int` doesn't have a nested `value_type` typename + STATIC_REQUIRE( + mpl::invoke_t< + mpl::disjunction, mpl::pass_result_of_fn, value_type_t>>, + int>::value); STATIC_REQUIRE(mpl::invoke_t::value); STATIC_REQUIRE(mpl::invoke_t>::value); STATIC_REQUIRE(std::is_same, std::is_void>, @@ -24,12 +79,34 @@ TEST_CASE("mpl") { STATIC_REQUIRE(mpl::invoke_t, mpl::quote_fn, mpl::quote_fn>::value); - using check_if_same_type = mpl::bind_front_fn; - STATIC_REQUIRE(mpl::invoke_t::value); - using check_if_same_template = - mpl::pass_extracted_fn_to>>; + STATIC_REQUIRE(mpl::invoke_t, int>::value); STATIC_REQUIRE(mpl::invoke_t>::value); - using check_if_has_type_t = mpl::bind_front_higherorder_fn; - STATIC_REQUIRE(!mpl::invoke_t::value); - STATIC_REQUIRE(mpl::invoke_t::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t::value); + STATIC_REQUIRE(mpl::invoke_t::value); + STATIC_REQUIRE(std::is_same, predicate_type>, + predicate_type::is_transparent>::value); + + STATIC_REQUIRE(mpl::invoke_t, int, int>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t, int, int>::value); + STATIC_REQUIRE(mpl::invoke_t, predicate_type>::value); + STATIC_REQUIRE(mpl::invoke_t, predicate_type>::value); + STATIC_REQUIRE(mpl::invoke_t, std::vector>::value); + + STATIC_REQUIRE(mpl::invoke_t, std::tuple>::value); + STATIC_REQUIRE_FALSE(mpl::invoke_t, std::tuple>::value); + STATIC_REQUIRE(mpl::invoke_t, std::tuple>::value); + STATIC_REQUIRE(mpl::invoke_t, + std::tuple, + mpl::quote_fn>::value); + STATIC_REQUIRE( + mpl::invoke_t, std::tuple>>::value); + STATIC_REQUIRE(mpl::invoke_t, std::tuple>::value == 0); + STATIC_REQUIRE(mpl::invoke_t, std::tuple>::value == 1); + STATIC_REQUIRE(mpl::invoke_t, + std::tuple, + mpl::quote_fn>::value == 0); + STATIC_REQUIRE(mpl::invoke_t, std::tuple>>::value == + 0); + STATIC_REQUIRE(mpl::invoke_t, std::tuple>::value == + 1); } diff --git a/tests/static_tests/functional/same_or_void.cpp b/tests/static_tests/functional/same_or_void.cpp index 1f58d4a1d..a83c2dd6a 100644 --- a/tests/static_tests/functional/same_or_void.cpp +++ b/tests/static_tests/functional/same_or_void.cpp @@ -3,10 +3,12 @@ #include // std::is_same #include // std::string +#include // std::tuple using namespace sqlite_orm; TEST_CASE("same_or_void") { + using internal::common_type_of_t; using internal::same_or_void; // one argument @@ -31,4 +33,10 @@ TEST_CASE("same_or_void") { STATIC_REQUIRE(std::is_same::type, int>::value); STATIC_REQUIRE(std::is_same::type, long>::value); STATIC_REQUIRE(std::is_same::type, void>::value); + + // type pack, e.g. tuple + STATIC_REQUIRE(std::is_same>, int>::value); + STATIC_REQUIRE(std::is_same>, long>::value); + STATIC_REQUIRE( + std::is_same>, polyfill::nonesuch>::value); } diff --git a/tests/static_tests/functional/static_if_tests.cpp b/tests/static_tests/functional/static_if_tests.cpp index e9157337b..feacbc500 100644 --- a/tests/static_tests/functional/static_if_tests.cpp +++ b/tests/static_tests/functional/static_if_tests.cpp @@ -57,7 +57,7 @@ TEST_CASE("static_if") { std::string name; }; auto ch = check(length(&User::name) > 5); - STATIC_REQUIRE(!internal::is_column_v); + STATIC_REQUIRE_FALSE(internal::is_column_v); called = {}; static_if>( [&called] { diff --git a/tests/static_tests/functional/tuple_filter.cpp b/tests/static_tests/functional/tuple_filter.cpp index 16c16107f..0e016c649 100644 --- a/tests/static_tests/functional/tuple_filter.cpp +++ b/tests/static_tests/functional/tuple_filter.cpp @@ -43,8 +43,7 @@ TEST_CASE("count_tuple") { STATIC_REQUIRE(count_tuple::value == 1); } { - auto t = - make_tuple(where(lesser_than(&User::id, 10)), where(greater_than(&User::id, 5)), group_by(&User::name)); + auto t = make_tuple(where(less_than(&User::id, 10)), where(greater_than(&User::id, 5)), group_by(&User::name)); using T = decltype(t); STATIC_REQUIRE(count_tuple::value == 2); STATIC_REQUIRE(count_tuple::value == 1); diff --git a/tests/static_tests/functional/tuple_traits.cpp b/tests/static_tests/functional/tuple_traits.cpp index d11d43a7f..f35cd84d7 100644 --- a/tests/static_tests/functional/tuple_traits.cpp +++ b/tests/static_tests/functional/tuple_traits.cpp @@ -2,20 +2,30 @@ #include using namespace sqlite_orm; -using internal::check_if_tuple_has; -using internal::check_if_tuple_has_template; -using internal::check_if_tuple_has_type; +using internal::asterisk_t; +using internal::count_tuple; using internal::default_t; +using internal::find_tuple_type; using internal::is_primary_key; using internal::primary_key_t; +using internal::tuple_has; +using internal::tuple_has_template; +using internal::tuple_has_type; TEST_CASE("tuple traits") { using empty_tuple_type = std::tuple<>; using tuple_type = std::tuple, primary_key_t<>, std::string>; - STATIC_REQUIRE(mpl::invoke_t, tuple_type>::value); - STATIC_REQUIRE(mpl::invoke_t, tuple_type>::value); - STATIC_REQUIRE(mpl::invoke_t, tuple_type>::value); - STATIC_REQUIRE(!mpl::invoke_t, tuple_type>::value); - STATIC_REQUIRE(!mpl::invoke_t, empty_tuple_type>::value); + STATIC_REQUIRE(internal::tuple_has::value); + STATIC_REQUIRE_FALSE(internal::tuple_has::value); + STATIC_REQUIRE(internal::tuple_has_type::value); + STATIC_REQUIRE(internal::tuple_has_type::value); + STATIC_REQUIRE_FALSE(internal::tuple_has_type::value); + STATIC_REQUIRE(internal::tuple_has_template::value); + STATIC_REQUIRE_FALSE(internal::tuple_has_template::value); + STATIC_REQUIRE(internal::find_tuple_type::value == 1); + STATIC_REQUIRE(internal::find_tuple_type::value == 1); + STATIC_REQUIRE(internal::find_tuple_type::value == std::tuple_size::value); + STATIC_REQUIRE(internal::find_tuple_template::value == 2); + STATIC_REQUIRE(internal::count_tuple::value == 1); } diff --git a/tests/static_tests/functional/tuple_transform.cpp b/tests/static_tests/functional/tuple_transform.cpp index d2f61788f..d67539098 100644 --- a/tests/static_tests/functional/tuple_transform.cpp +++ b/tests/static_tests/functional/tuple_transform.cpp @@ -1,8 +1,29 @@ #include #include #include // std::is_same +#include using namespace sqlite_orm; +using internal::create_from_tuple; +using internal::field_type_t; +using internal::literal_holder; +using internal::transform_tuple_t; +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) +using internal::nested_tuple_size_for_t; +using internal::recombine_tuple; +#endif + +template +using make_literal_holder = literal_holder; + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && (__cpp_lib_constexpr_functional >= 201907L) +struct tuple_maker { + template + constexpr auto operator()(Types&&... types) const { + return std::make_tuple(std::forward(types)...); + } +}; +#endif TEST_CASE("tuple_helper static") { SECTION("tuple_transformer") { @@ -12,18 +33,31 @@ TEST_CASE("tuple_helper static") { std::string b; std::string c; }; - auto column1 = make_column("id", &Table::id); - auto column2 = make_column("a", &Table::a); - auto column3 = make_column("b", &Table::b); - auto column4 = make_column("c", &Table::c); - - using Column1 = decltype(column1); - using Column2 = decltype(column2); - using Column3 = decltype(column3); - using Column4 = decltype(column4); - using ColumnsTuple = std::tuple; - using ColumnsMappedTypes = internal::transform_tuple_t; + + auto columnsTuple = std::make_tuple(make_column("id", &Table::id), + make_column("a", &Table::a), + make_column("b", &Table::b), + make_column("c", &Table::c)); + using ColumnsTuple = decltype(columnsTuple); + using ColumnsMappedTypes = transform_tuple_t; using Expected = std::tuple; STATIC_REQUIRE(std::is_same::value); + + STATIC_REQUIRE(std::is_same, make_literal_holder>, + std::tuple, literal_holder>>::value); + +#if __cpp_lib_constexpr_algorithms >= 201806L + STATIC_REQUIRE(create_from_tuple>(std::make_tuple(1, 2), polyfill::identity{}) == + std::array{1, 2}); +#endif +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && (__cpp_lib_constexpr_functional >= 201907L) + STATIC_REQUIRE(recombine_tuple(tuple_maker{}, std::make_tuple(1, 2), polyfill::identity{}, 3) == + std::make_tuple(3, 1, 2)); + + // make tuples out of tuple elements, and sump up the size of the first 2 of 3 resulting tuples + STATIC_REQUIRE( + std::is_same, std::make_index_sequence<2>>, + std::integral_constant>::value); +#endif } } diff --git a/tests/static_tests/is_bindable.cpp b/tests/static_tests/is_bindable.cpp index d9d054e97..4dc6ab529 100644 --- a/tests/static_tests/is_bindable.cpp +++ b/tests/static_tests/is_bindable.cpp @@ -65,46 +65,52 @@ TEST_CASE("is_bindable") { STATIC_REQUIRE(is_bindable_v); STATIC_REQUIRE(is_bindable_v>); STATIC_REQUIRE(is_bindable_v>); - STATIC_REQUIRE(!is_bindable_v>); + STATIC_REQUIRE_FALSE(is_bindable_v>); #endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#if SQLITE_VERSION_NUMBER >= 3020000 #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED - STATIC_REQUIRE(is_bindable_v>); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(is_bindable_v>); +#else + STATIC_REQUIRE(is_bindable_v>); +#endif +#endif #endif STATIC_REQUIRE(is_bindable_v); STATIC_REQUIRE(is_bindable_v>); - STATIC_REQUIRE(!is_bindable_v); - STATIC_REQUIRE(!is_bindable_v>); - STATIC_REQUIRE(!is_bindable_v); - STATIC_REQUIRE(!is_bindable_v>); + STATIC_REQUIRE_FALSE(is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v>); + STATIC_REQUIRE_FALSE(is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v>); { auto isEqual = is_equal(&User::id, 5); - STATIC_REQUIRE(!is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v); } { auto notEqual = is_not_equal(&User::id, 10); - STATIC_REQUIRE(!is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v); } { - auto lesserThan = lesser_than(&User::id, 10); - STATIC_REQUIRE(!is_bindable_v); + auto lessThan = less_than(&User::id, 10); + STATIC_REQUIRE_FALSE(is_bindable_v); } { - auto lesserOrEqual = lesser_or_equal(&User::id, 5); - STATIC_REQUIRE(!is_bindable_v); + auto lessOrEqual = less_or_equal(&User::id, 5); + STATIC_REQUIRE_FALSE(is_bindable_v); } { auto greaterThan = greater_than(&User::id, 5); - STATIC_REQUIRE(!is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v); } { auto greaterOrEqual = greater_or_equal(&User::id, 5); - STATIC_REQUIRE(!is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v); } { auto func = datetime("now"); - STATIC_REQUIRE(!is_bindable_v); + STATIC_REQUIRE_FALSE(is_bindable_v); bool trueCalled = false; bool falseCalled = false; auto dummy = 5; // for gcc compilation @@ -115,7 +121,7 @@ TEST_CASE("is_bindable") { [&falseCalled](int&) { falseCalled = true; })(dummy); - REQUIRE(!trueCalled); + REQUIRE_FALSE(trueCalled); REQUIRE(falseCalled); } } diff --git a/tests/static_tests/is_primary_key_insertable.cpp b/tests/static_tests/is_primary_key_insertable.cpp index 274b0a4c8..e5f4428e5 100644 --- a/tests/static_tests/is_primary_key_insertable.cpp +++ b/tests/static_tests/is_primary_key_insertable.cpp @@ -1,6 +1,6 @@ #include #include -#include // std::std::decay +#include // std::decay using namespace sqlite_orm; @@ -27,6 +27,6 @@ TEST_CASE("is_primary_key_insertable") { }); iterate_tuple(noninsertable, [](auto& v) { - STATIC_REQUIRE(!internal::is_primary_key_insertable>::value); + STATIC_REQUIRE_FALSE(internal::is_primary_key_insertable>::value); }); } \ No newline at end of file diff --git a/tests/static_tests/is_printable.cpp b/tests/static_tests/is_printable.cpp index 86d7ef9a4..ba30bb469 100644 --- a/tests/static_tests/is_printable.cpp +++ b/tests/static_tests/is_printable.cpp @@ -42,11 +42,11 @@ TEST_CASE("is_printable") { STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v); - STATIC_REQUIRE(!is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v); STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v>); #ifndef SQLITE_ORM_OMITS_CODECVT - STATIC_REQUIRE(!is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v); STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v>); #endif @@ -54,23 +54,29 @@ TEST_CASE("is_printable") { STATIC_REQUIRE(is_printable_v>); STATIC_REQUIRE(is_printable_v>); #ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED - STATIC_REQUIRE(!is_printable_v); - STATIC_REQUIRE(!is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v); #endif #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v>); STATIC_REQUIRE(is_printable_v>); - STATIC_REQUIRE(!is_printable_v>); + STATIC_REQUIRE_FALSE(is_printable_v>); #endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#if SQLITE_VERSION_NUMBER >= 3020000 #ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED - STATIC_REQUIRE(!is_printable_v>); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE_FALSE(is_printable_v>); +#else + STATIC_REQUIRE_FALSE(is_printable_v>); +#endif +#endif #endif STATIC_REQUIRE(is_printable_v); STATIC_REQUIRE(is_printable_v>); - STATIC_REQUIRE(!is_printable_v); - STATIC_REQUIRE(!is_printable_v); - STATIC_REQUIRE(!is_printable_v>); + STATIC_REQUIRE_FALSE(is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v); + STATIC_REQUIRE_FALSE(is_printable_v>); } diff --git a/tests/static_tests/iterator_t.cpp b/tests/static_tests/iterator_t.cpp index 39c424dc4..5902c925e 100644 --- a/tests/static_tests/iterator_t.cpp +++ b/tests/static_tests/iterator_t.cpp @@ -1,35 +1,195 @@ -#include -#include #include #include +#include +#include +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif using namespace sqlite_orm; +using internal::mapped_iterator; +using internal::mapped_view; +using internal::structure; +#if defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) +using internal::result_set_iterator; +using internal::result_set_sentinel_t; +using internal::result_set_view; +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +using internal::table_reference; +#endif -namespace { - struct User { - int id = 0; - std::string name; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +template +using with_reference = T&; + +template +concept can_reference = requires { typename with_reference; }; + +template +concept names_difference_type = requires { typename T::difference_type; }; + +template +concept names_value_type = requires { typename T::value_type; }; + +// named concept of a legacy iterator +template +concept LegacyIterator = requires(Iter it) { + { *it } -> can_reference; + { ++it } -> std::same_as; + { *it++ } -> can_reference; +} && std::copyable; + +// named concept of a legacy input iterator +template +concept LegacyInputIterator = + LegacyIterator && std::equality_comparable && names_difference_type> && + names_value_type> && requires(Iter it) { + typename std::common_reference_t&&, + typename std::indirectly_readable_traits::value_type&>; + typename std::common_reference_t::value_type&>; + requires std::signed_integral::difference_type>; }; + +template +concept can_iterate_mapped = requires(Iter it) { + requires LegacyInputIterator; + // explicit check of the sentinel role, since `std::ranges::borrowed_range` in `can_view_mapped` + // would not tell us why exactly the end iterator cannot be a sentinel + requires std::sentinel_for; + { *it } -> std::same_as; + // note: should actually be only present for contiguous iterators + { it.operator->() } -> std::same_as; +}; + +template +concept can_view_mapped = requires(V view) { + requires std::ranges::borrowed_range; + { view.begin() } -> std::same_as>; + { view.end() } -> std::same_as>; +}; + +template +concept storage_iterate_mapped = requires(S& storage_type) { + { storage_type.iterate() } -> std::same_as>; + { storage_type.iterate() } -> can_view_mapped; +}; +#endif + +#if(defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED)) && \ + defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED) +template +concept can_iterate_result_set = requires(Iter it) { + requires std::input_iterator; + // explicit check of the sentinel role, since `std::ranges::borrowed_range` in `can_view_mapped` + // would not tell us why exactly the end iterator cannot be a sentinel + requires std::sentinel_for; +#ifdef SQLITE_ORM_STL_HAS_DEFAULT_SENTINEL + requires std::same_as; +#endif + { *it } -> std::same_as; +}; + +template +concept can_view_result_set = requires(V view) { + requires std::ranges::view; + requires std::ranges::borrowed_range; + { view.begin() } -> std::same_as>; + { view.end() } -> std::same_as; +}; + +template +concept storage_iterate_result_set = requires(S& storage_type, Select select) { + { storage_type.iterate(select) } -> std::same_as>; + { storage_type.iterate(select) } -> can_view_result_set; +}; +#endif + +namespace { + struct Object {}; } -TEST_CASE("iterator_t") { - using storage = decltype(make_storage( - "aPath", - make_table("users", make_column("id", &User::id, primary_key()), make_column("name", &User::name)))); - using iter = decltype(std::declval().iterate().begin()); +TEST_CASE("can view and iterate mapped") { + using storage_type = decltype(make_storage("", make_table(""))); + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + using iter = mapped_iterator; + STATIC_REQUIRE(can_iterate_mapped); + // check default initializability at runtime + [[maybe_unused]] const iter end; +#else + using iter = decltype(std::declval().iterate().begin()); + iter it; + const iter end; + + // LegacyInputIterator + { + // LegacyIterator + { + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); + // copyable (partially, as it is a rather extensive concept) + { STATIC_REQUIRE(std::is_copy_constructible::value); } + } + // equality_comparable (sentinel) + { + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); + } + STATIC_REQUIRE(std::is_same::iterator_category, std::input_iterator_tag>::value); + STATIC_REQUIRE(std::is_same::value_type, Object>::value); + STATIC_REQUIRE(std::is_same::difference_type, ptrdiff_t>::value); + } + // semiregular (actually sentinel_for, but the other concepts were verified above) + { STATIC_REQUIRE(std::is_default_constructible::value); } + STATIC_REQUIRE(std::is_same::pointer, Object*>::value); + // note: should actually be only present for contiguous iterators + STATIC_REQUIRE(std::is_same()), Object*>::value); +#endif + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + STATIC_REQUIRE(storage_iterate_mapped); +#endif +} - // weakly_incrementable - STATIC_REQUIRE(std::is_default_constructible::value); - STATIC_REQUIRE(std::is_same::value); - STATIC_REQUIRE(std::is_same()), iter&>::value); - using check = decltype(std::declval()++); +#if(defined(SQLITE_ORM_SENTINEL_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED)) && \ + defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED) +TEST_CASE("can view and iterate result set") { + struct Object {}; + using empty_storage_type = decltype(make_storage("")); + using empty_db_objects_type = empty_storage_type::db_objects_type; + using storage_type = decltype(make_storage("", make_table(""))); + using db_objects_type = storage_type::db_objects_type; - // indirectly_readable - STATIC_REQUIRE(std::is_same()), const User&>::value); + STATIC_REQUIRE(can_iterate_result_set, int>); + STATIC_REQUIRE( + can_iterate_result_set, empty_db_objects_type>, std::tuple>); + STATIC_REQUIRE( + can_iterate_result_set, empty_db_objects_type>, + Object>); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(can_iterate_result_set, db_objects_type>, Object>); +#endif - // input_iterator - STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(storage_iterate_result_set); + STATIC_REQUIRE( + storage_iterate_result_set>); + STATIC_REQUIRE(storage_iterate_result_set())), + structure>>); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE( + storage_iterate_result_set())), table_reference>); +#endif - // sentinel (equality comparable) - STATIC_REQUIRE(std::is_same() == std::declval()), bool>::value); +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_cte_moniker auto x = "x"_cte; + constexpr orm_column_alias auto i = "i"_col; + STATIC_REQUIRE(storage_iterate_result_set*i))), int>); +#endif +#endif } +#endif diff --git a/tests/static_tests/member_traits_tests.cpp b/tests/static_tests/member_traits_tests.cpp index be1ed63f2..597fa9fa2 100644 --- a/tests/static_tests/member_traits_tests.cpp +++ b/tests/static_tests/member_traits_tests.cpp @@ -105,7 +105,7 @@ TEST_CASE("member_traits_tests") { } }; - STATIC_REQUIRE(!is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); STATIC_REQUIRE(is_getter::value); STATIC_REQUIRE(is_getter::value); STATIC_REQUIRE(is_getter::value); @@ -119,25 +119,25 @@ TEST_CASE("member_traits_tests") { STATIC_REQUIRE(is_getter::value); STATIC_REQUIRE(is_getter::value); #endif - STATIC_REQUIRE(!is_getter::value); - STATIC_REQUIRE(!is_getter::value); - STATIC_REQUIRE(!is_getter::value); - STATIC_REQUIRE(!is_getter::value); - STATIC_REQUIRE(!is_getter::value); - STATIC_REQUIRE(!is_getter::value); - - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); - STATIC_REQUIRE(!is_setter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + STATIC_REQUIRE_FALSE(is_getter::value); + + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); + STATIC_REQUIRE_FALSE(is_setter::value); STATIC_REQUIRE(is_setter::value); STATIC_REQUIRE(is_setter::value); STATIC_REQUIRE(is_setter::value); diff --git a/tests/static_tests/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index 5d28c4c50..59b4e6c90 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -31,6 +31,20 @@ TEST_CASE("Node tuple") { std::string name; }; + SECTION("wrapper types") { + SECTION("reference wrapper") { + using Tuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + } +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + { + SECTION("as_optional") { + using Tuple = node_tuple_t>; + STATIC_REQUIRE(is_same>::value); + } + } +#endif + } SECTION("bindables") { SECTION("int") { using Tuple = node_tuple_t; @@ -52,38 +66,46 @@ TEST_CASE("Node tuple") { static_assert(is_same::value, "literal int"); STATIC_REQUIRE(is_same, tuple<>>::value); } + SECTION("is_equal_with_table_t") { + auto node = is_equal(std::string("Claude")); + using Node = decltype(node); + using Tuple = node_tuple_t; + using Expected = tuple; + static_assert(is_same::value, "is_equal_with_table_t"); + STATIC_REQUIRE(is_same, tuple>::value); + } SECTION("binary_condition") { using namespace internal; SECTION("5 < 6.0f") { - auto c = lesser_than(5, 6.0f); + auto c = less_than(5, 6.0f); using C = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "lesser_than_t"); + static_assert(is_same::value, "less_than_t"); STATIC_REQUIRE(is_same, tuple>::value); } SECTION("id < 10") { - auto c = lesser_than(&User::id, 10); + auto c = less_than(&User::id, 10); using C = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "lesser_than_t"); + static_assert(is_same::value, "less_than_t"); STATIC_REQUIRE(is_same, tuple>::value); } SECTION("5 <= 6.0f") { - auto c = lesser_or_equal(5, 6.0f); + auto c = less_or_equal(5, 6.0f); using C = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "lesser_or_equal_t"); + static_assert(is_same::value, "less_or_equal_t"); STATIC_REQUIRE(is_same, tuple>::value); } SECTION("id <= 10.0") { - auto c = lesser_or_equal(&User::id, 10.0); + auto c = less_or_equal(&User::id, 10.0); using C = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "lesser_or_equal_t"); + static_assert(is_same::value, "less_or_equal_t"); STATIC_REQUIRE(is_same, tuple>::value); } SECTION("5 > 6.0f") { @@ -228,6 +250,24 @@ TEST_CASE("Node tuple") { using Expected = tuple; static_assert(is_same::value, "count(*)"); } +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + SECTION("count(*) cte") { + auto node = count(); + using Node = decltype(node); + using Tuple = node_tuple_t; + using Expected = tuple; + static_assert(is_same::value, "count(*) cte"); + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("count(*) cte 2") { + auto node = count<1_ctealias>(); + using Node = decltype(node); + using Tuple = node_tuple_t; + using Expected = tuple; + static_assert(is_same::value, "count(*) cte 2"); + } +#endif +#endif SECTION("count(*) filter") { auto node = count().filter(where(length(&User::name) > 5)); using Node = decltype(node); @@ -433,6 +473,12 @@ TEST_CASE("Node tuple") { using Tuple = node_tuple_t; STATIC_REQUIRE(is_same>::value); } + SECTION("match") { + auto expression = match(std::string("Plazma")); + using Expression = decltype(expression); + using Tuple = node_tuple_t; + STATIC_REQUIRE(is_same>::value); + } SECTION("select") { SECTION("select(&User::id)") { auto sel = select(&User::id); @@ -453,24 +499,24 @@ TEST_CASE("Node tuple") { static_assert(is_same>::value, "select(&User::id, where(is_equal(&User::id, 5)))"); } - SECTION("select(&User::name, where(lesser_than(&User::id, 10)))") { - auto sel = select(&User::name, where(lesser_than(&User::id, 10))); + SECTION("select(&User::name, where(less_than(&User::id, 10)))") { + auto sel = select(&User::name, where(less_than(&User::id, 10))); using Sel = decltype(sel); using Tuple = node_tuple_t; static_assert(is_same>::value, - "select(&User::name, where(lesser_than(&User::id, 10)))"); + "select(&User::name, where(less_than(&User::id, 10)))"); } SECTION("select(columns(&User::id, &User::name), where(greater_or_equal(&User::id, 10) and " - "lesser_or_equal(&User::id, 20)))") { + "less_or_equal(&User::id, 20)))") { auto sel = select(columns(&User::id, &User::name), - where(greater_or_equal(&User::id, 10) and lesser_or_equal(&User::id, 20))); + where(greater_or_equal(&User::id, 10) and less_or_equal(&User::id, 20))); using Sel = decltype(sel); using Tuple = node_tuple_t; using Expected = std:: tuple; static_assert(is_same::value, "select(columns(&User::id, &User::name), where(greater_or_equal(&User::id, 10) and " - "lesser_or_equal(&User::id, 20)))"); + "less_or_equal(&User::id, 20)))"); } SECTION("select(columns('ototo', 25))") { auto statement = select(columns("ototo", 25)); @@ -502,14 +548,6 @@ TEST_CASE("Node tuple") { "get_all(where(is_equal(5.0, &User::id)))"); } } - SECTION("having_t") { - using namespace internal; - auto hav = having(greater_or_equal(&User::id, 10)); - using Having = decltype(hav); - using Tuple = node_tuple_t; - static_assert(is_same>::value, - "having(greater_or_equal(&User::id, 10))"); - } SECTION("cast_t") { auto sel = select(columns(cast(&User::id), cast(&User::name))); using Select = decltype(sel); @@ -534,19 +572,9 @@ TEST_CASE("Node tuple") { SECTION("like(&User::name, 'S%')") { auto lk = like(&User::name, "S%"); using Like = decltype(lk); - using NodeTuple = node_tuple; - using ArgTuple = NodeTuple::arg_tuple; - static_assert(is_same>::value, "arg_tuple"); - using PatternTuple = NodeTuple::pattern_tuple; - static_assert(is_same>::value, "pattern_tuple"); - using EscapeTuple = NodeTuple::escape_tuple; - static_assert(is_same>::value, "escape_tuple"); - using Tuple = NodeTuple::type; - static_assert(std::tuple_size::value == 2, "like(&User::name, \"S%\") size"); - using Tuple0 = std::tuple_element_t<0, Tuple>; - static_assert(is_same::value, "like(&User::name, \"S%\") type 0"); - static_assert(is_same>::value, - "like(&User::name, \"S%\")"); + using NodeTuple = node_tuple_t; + using Expected = tuple; + static_assert(is_same::value, "like(&User::name, \"S%\") type 0"); } SECTION("like(&User::name, std::string('pattern'), '%')") { auto lk = like(&User::name, std::string("pattern"), "%"); @@ -579,7 +607,7 @@ TEST_CASE("Node tuple") { STATIC_REQUIRE(is_same()))>, tuple<>>::value); } SECTION("column alias in expression") { - STATIC_REQUIRE(is_same() > c(1)))>, tuple>::value); + STATIC_REQUIRE(is_same() > 1))>, tuple>::value); } } SECTION("glob_t") { @@ -630,19 +658,19 @@ TEST_CASE("Node tuple") { using Expected = tuple; static_assert(is_same::value, "not greater_or_equal(&User::id, 5)"); } - SECTION("not lesser_than(&User::id, std::string('6'))") { - auto c = not lesser_than(&User::id, std::string("6")); + SECTION("not less_than(&User::id, std::string('6'))") { + auto c = not less_than(&User::id, std::string("6")); using Con = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "not lesser_than(&User::id, std::string(\"6\"))"); + static_assert(is_same::value, "not less_than(&User::id, std::string(\"6\"))"); } - SECTION("not lesser_or_equal(&User::id, 10)") { - auto c = not lesser_or_equal(&User::id, 10); + SECTION("not less_or_equal(&User::id, 10)") { + auto c = not less_or_equal(&User::id, 10); using Con = decltype(c); using Tuple = node_tuple_t; using Expected = tuple; - static_assert(is_same::value, "not lesser_or_equal(&User::id, 10)"); + static_assert(is_same::value, "not less_or_equal(&User::id, 10)"); } SECTION("not in(&User::id, {1, 2, 3})") { auto c = not in(&User::id, {1, 2, 3}); @@ -874,9 +902,9 @@ TEST_CASE("Node tuple") { STATIC_REQUIRE(is_tuple>::value); STATIC_REQUIRE(is_tuple>::value); - STATIC_REQUIRE(!is_tuple::value); + STATIC_REQUIRE_FALSE(is_tuple::value); STATIC_REQUIRE(is_pair>::value); - STATIC_REQUIRE(!is_pair::value); + STATIC_REQUIRE_FALSE(is_pair::value); using ArgsType = Case::args_type; STATIC_REQUIRE(is_tuple::value); @@ -908,6 +936,7 @@ TEST_CASE("Node tuple") { } SECTION("function_call") { struct Func { + static const char* name(); bool operator()(int value) const { return value % 2; } @@ -941,4 +970,56 @@ TEST_CASE("Node tuple") { using ExpectedTuple = tuple; STATIC_REQUIRE(std::is_same::value); } + SECTION("set assign") { + auto statement = set(assign(&User::name, conc(&User::name, "_"))); + using Tuple = node_tuple_t; + using ExpectedTuple = tuple; + STATIC_REQUIRE(std::is_same::value); + } +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + SECTION("with ordinary") { + using cte_1 = decltype(1_ctealias); + auto expression = with(1_ctealias().as(select(1)), select(column(1_colalias))); + using Tuple = node_tuple_t; + using ExpectedTuple = tuple>>>; + STATIC_REQUIRE(std::is_same_v); + } + SECTION("with not enforced recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with(1_ctealias().as(select(1)), select(column(1_colalias))); + using Tuple = node_tuple_t; + using ExpectedTuple = tuple>>>; + STATIC_REQUIRE(std::is_same_v); + } + SECTION("with optional recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with_recursive( + 1_ctealias().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + using Tuple = node_tuple_t; + using ExpectedTuple = tuple>>, + int, + column_pointer>>, + int, + column_pointer>>>; + STATIC_REQUIRE(std::is_same_v); + } + SECTION("with recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with_recursive( + 1_ctealias().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + using Tuple = node_tuple_t; + using ExpectedTuple = tuple>>, + int, + column_pointer>>, + int, + column_pointer>>>; + STATIC_REQUIRE(std::is_same_v); + } +#endif } diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index bca8f8e65..74c5df20d 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -1,64 +1,165 @@ #include #include +#if defined(SQLITE_ORM_WITH_CPP20_ALIASES) || defined(SQLITE_ORM_WITH_CTE) +using namespace sqlite_orm::literals; +#endif +using sqlite_orm::alias_a; +using sqlite_orm::alias_column; +using sqlite_orm::and_; using sqlite_orm::c; +using sqlite_orm::colalias_a; +using sqlite_orm::column; +using sqlite_orm::func; +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::greater_or_equal_t; using sqlite_orm::internal::greater_than_t; using sqlite_orm::internal::is_equal_t; using sqlite_orm::internal::is_not_equal_t; -using sqlite_orm::internal::lesser_or_equal_t; -using sqlite_orm::internal::lesser_than_t; +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::polyfill::is_specialization_of_v; -TEST_CASE("ADL and expression operators") { - struct User { - int id; - }; +template +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 42), greater_than_t>); + STATIC_REQUIRE(is_specialization_of_v expression), greater_than_t>); + STATIC_REQUIRE(is_specialization_of_v expression), greater_than_t>); - STATIC_REQUIRE(is_specialization_of_v); - STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v= 42), greater_or_equal_t>); + STATIC_REQUIRE(is_specialization_of_v= expression), greater_or_equal_t>); + STATIC_REQUIRE(is_specialization_of_v= expression), greater_or_equal_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); + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); - STATIC_REQUIRE(is_specialization_of_v 42), greater_than_t>); - STATIC_REQUIRE(is_specialization_of_v c(&User::id)), greater_than_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); - STATIC_REQUIRE(is_specialization_of_v= 42), greater_or_equal_t>); - STATIC_REQUIRE(is_specialization_of_v= c(&User::id)), greater_or_equal_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); + 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); + 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); - 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); - 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); - 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); - 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); + 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); - STATIC_REQUIRE(is_specialization_of_v); + // conc_t + condition_t yield or_condition_t + STATIC_REQUIRE(is_specialization_of_v); + STATIC_REQUIRE(is_specialization_of_v); +} + +TEST_CASE("inline namespace literals expressions") { +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + constexpr auto col1 = 1_colalias; + constexpr auto cte1 = 1_ctealias; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cte_mnkr = "1"_cte; +#endif +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto u_alias_builder = "u"_alias; + constexpr auto c_col = "c"_col; + constexpr auto f_scalar_builder = "f"_scalar; +#if SQLITE_VERSION_NUMBER >= 3020000 + constexpr auto domain_ptr_tag = "domain"_pointer_type; +#endif +#endif +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +TEST_CASE("ADL and pointer-to-member expressions") { + struct User { + int id; + }; + constexpr auto user_table = c(); + constexpr auto u_alias = "u"_alias.for_(); +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + constexpr auto cte = "1"_cte; +#endif + + user_table->*&User::id; + u_alias->*&User::id; +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + cte->*&User::id; +#endif +} +#endif + +TEST_CASE("ADL and expression operators") { + struct User { + int id; + }; + struct ScalarFunction { + static const char* name(); + int operator()() const; + }; - STATIC_REQUIRE(is_specialization_of_v); - STATIC_REQUIRE(is_specialization_of_v); - STATIC_REQUIRE(is_specialization_of_v); + runTests(c(&User::id)); + runTests(column(&User::id)); + runTests(get()); + runTests(alias_column>(&User::id)); + runTests(func()); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runTests("a"_col); +#endif } diff --git a/tests/static_tests/row_extractor.cpp b/tests/static_tests/row_extractor.cpp new file mode 100644 index 000000000..a7fc39fd2 --- /dev/null +++ b/tests/static_tests/row_extractor.cpp @@ -0,0 +1,120 @@ +#include +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#define ENABLE_THIS_UT +#endif + +#ifdef ENABLE_THIS_UT +#include +#include // std::unique_ptr, std::shared_ptr +#include // std::string +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED +#include // std::optional +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED +#include +#endif + +using namespace sqlite_orm; + +template +static void check_extractable() { + STATIC_CHECK(orm_column_text_extractable); + STATIC_CHECK(orm_row_value_extractable); + STATIC_CHECK(orm_boxed_value_extractable); +} + +template +static void check_not_extractable() { + STATIC_CHECK_FALSE(orm_column_text_extractable); + STATIC_CHECK_FALSE(orm_row_value_extractable); + STATIC_CHECK_FALSE(orm_boxed_value_extractable); +} + +namespace { + enum class custom_enum { custom }; + + template + class StringVeneer : public std::basic_string {}; + + struct User { + int id; + }; +} + +template<> +struct sqlite_orm::row_extractor { + custom_enum extract(const char* /*columnText*/) const { + return custom_enum::custom; + } + custom_enum extract(sqlite3_stmt*, int /*columnIndex*/) const { + return custom_enum::custom; + } + custom_enum extract(sqlite3_value*) const { + return custom_enum::custom; + } +}; + +TEST_CASE("is_extractable") { + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_not_extractable(); + check_extractable(); + check_extractable>(); +#ifndef SQLITE_ORM_OMITS_CODECVT + check_not_extractable(); + check_extractable(); + check_not_extractable>(); +#endif + check_extractable(); + check_extractable>(); + check_extractable>(); +#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED + check_not_extractable(); +#ifndef SQLITE_ORM_OMITS_CODECVT + check_not_extractable(); +#endif +#endif +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + check_not_extractable(); + check_extractable>(); + check_extractable>(); + check_not_extractable>(); +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#if SQLITE_VERSION_NUMBER >= 3020000 +#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + check_not_extractable>(); +#else + check_not_extractable>(); +#endif + // pointer arguments are special: they can only be passed to and from functions, but casting is prohibited + { + using int64_pointer_arg = carray_pointer_arg; + STATIC_CHECK_FALSE(orm_column_text_extractable); + STATIC_CHECK_FALSE(orm_row_value_extractable); + STATIC_CHECK(orm_boxed_value_extractable); + } +#endif +#endif + + check_extractable(); + check_extractable>(); + + check_not_extractable(); + check_not_extractable>(); + check_not_extractable(); + check_not_extractable>(); +} +#endif diff --git a/tests/static_tests/select_return_type.cpp b/tests/static_tests/select_return_type.cpp index 86d40e5c9..537683f20 100644 --- a/tests/static_tests/select_return_type.cpp +++ b/tests/static_tests/select_return_type.cpp @@ -31,13 +31,13 @@ TEST_CASE("Select return types") { // test is_mapped STATIC_REQUIRE(internal::is_mapped_v); - STATIC_REQUIRE(!internal::is_mapped_v); + STATIC_REQUIRE_FALSE(internal::is_mapped_v); // test is_storage STATIC_REQUIRE(internal::is_storage::value); - STATIC_REQUIRE(!internal::is_storage::value); - STATIC_REQUIRE(!internal::is_storage::value); - STATIC_REQUIRE(!internal::is_storage::value); + STATIC_REQUIRE_FALSE(internal::is_storage::value); + STATIC_REQUIRE_FALSE(internal::is_storage::value); + STATIC_REQUIRE_FALSE(internal::is_storage::value); auto storage2 = make_storage( "", diff --git a/tests/static_tests/statements.cpp b/tests/static_tests/statements.cpp new file mode 100644 index 000000000..5049d651c --- /dev/null +++ b/tests/static_tests/statements.cpp @@ -0,0 +1,35 @@ +#include +#include + +using namespace sqlite_orm; +using internal::expression_object_type_t; +using internal::statement_object_type_t; + +template +static void runExpressionTest(Expression /*expression*/) { + using Statement = internal::prepared_statement_t; + STATIC_REQUIRE(std::is_same, Expected>::value); + STATIC_REQUIRE(std::is_same, Expected>::value); +} + +TEST_CASE("statements") { + struct Object { + int64 id; + }; + constexpr Object obj{}; + constexpr std::array objs{}; + + SECTION("expression object") { + runExpressionTest(insert(obj)); + runExpressionTest(insert(std::cref(obj))); + runExpressionTest(insert(obj, columns(&Object::id))); + runExpressionTest(insert(std::cref(obj), columns(&Object::id))); + runExpressionTest(insert_range(objs.cbegin(), objs.cend())); + runExpressionTest(replace(obj)); + runExpressionTest(replace(std::cref(obj))); + runExpressionTest(replace_range(objs.cbegin(), objs.cend())); + runExpressionTest(update(obj)); + runExpressionTest(update(std::cref(obj))); + runExpressionTest(remove(0)); + } +} diff --git a/tests/static_tests/static_tests_common.h b/tests/static_tests/static_tests_common.h index 366440f31..6e5c70451 100644 --- a/tests/static_tests/static_tests_common.h +++ b/tests/static_tests/static_tests_common.h @@ -1,7 +1,11 @@ #pragma once +#include // std::unique_ptr +#include // std::string + struct User { int id; + std::unique_ptr name; const int& getIdByRefConst() const { return this->id; diff --git a/tests/static_tests/static_tests_storage_traits.h b/tests/static_tests/static_tests_storage_traits.h index b5247aae6..8c3ad7092 100644 --- a/tests/static_tests/static_tests_storage_traits.h +++ b/tests/static_tests/static_tests_storage_traits.h @@ -80,7 +80,7 @@ namespace sqlite_orm { filter_tuple_sequence_t, is_foreign_key>>; /* - * Implementation note: must be a struct instead of an template alias because the foreign keys tuple + * Implementation note: must be a struct instead of an alias template because the foreign keys tuple * must be hoisted into a named alias, otherwise type replacement may fail for legacy compilers * if an alias template has a dependent expression in it [SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION]. */ diff --git a/tests/static_tests/table_static_tests.cpp b/tests/static_tests/table_static_tests.cpp new file mode 100644 index 000000000..ff782f178 --- /dev/null +++ b/tests/static_tests/table_static_tests.cpp @@ -0,0 +1,125 @@ +#include +#include + +using namespace sqlite_orm; +using internal::is_column; +using internal::is_primary_key; + +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) +template +using dedicated_pk_columns_count_t = + internal::nested_tuple_size_for_t>; +#endif + +TEST_CASE("table static count_of()") { + struct User { + int id = 0; + std::string name; + }; + { // 1 column no pk + auto table = make_table("users", make_column("id", &User::id)); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 inline pk + auto table = make_table("users", make_column("id", &User::id, primary_key())); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 inline pk autoincrement + auto table = make_table("users", make_column("id", &User::id, primary_key().autoincrement())); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 1 column with 1 dedicated pk + auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id)); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 1 column with 1 dedicated pk autoincrement + auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id).autoincrement()); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns no pk + auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 inline id pk + auto table = make_table("users", make_column("id", &User::id, primary_key()), make_column("id", &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 inline name pk + auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name, primary_key())); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 0); + STATIC_REQUIRE(table.count_of_columns_with() == 1); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); +#endif + } + { // 2 columns with 1 dedicated id pk + auto table = + make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::id)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns with 1 dedicated name pk + auto table = + make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); +#endif + } + { // 2 columns with 2 dedicated pks + auto table = make_table("users", + make_column("id", &User::id), + make_column("id", &User::name), + primary_key(&User::id, &User::name)); + STATIC_REQUIRE(table.count_of() == 2); + STATIC_REQUIRE(table.count_of() == 1); + STATIC_REQUIRE(table.count_of_columns_with() == 0); +#if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 2); +#endif + } +} diff --git a/tests/storage_non_crud_tests.cpp b/tests/storage_non_crud_tests.cpp index 100bffdd6..f4167dce2 100644 --- a/tests/storage_non_crud_tests.cpp +++ b/tests/storage_non_crud_tests.cpp @@ -101,7 +101,7 @@ TEST_CASE("update set null") { { auto rows = storage.get_all(); REQUIRE(rows.size() == 1); - REQUIRE(!rows.front().name); + REQUIRE_FALSE(rows.front().name); } storage.update_all(set(assign(&User::name, "ototo"))); @@ -116,7 +116,7 @@ TEST_CASE("update set null") { { auto rows = storage.get_all(); REQUIRE(rows.size() == 1); - REQUIRE(!rows.front().name); + REQUIRE_FALSE(rows.front().name); } } diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index e1feeb49b..40e1104ce 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -3,6 +3,58 @@ using namespace sqlite_orm; +TEST_CASE("Current time/date/timestamp") { + auto storage = make_storage(""); + SECTION("time") { + SECTION("strict") { + REQUIRE(storage.current_time().size()); + } + SECTION("select") { + auto rows = storage.select(current_time()); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + SECTION("prepared statement") { + auto preparedStatement = storage.prepare(select(current_time())); + auto rows = storage.execute(preparedStatement); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + } + SECTION("date") { + SECTION("strict") { + REQUIRE(storage.current_date().size()); + } + SECTION("select") { + auto rows = storage.select(current_date()); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + SECTION("prepared statement") { + auto preparedStatement = storage.prepare(select(current_date())); + auto rows = storage.execute(preparedStatement); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + } + SECTION("timestamp") { + SECTION("strict") { + REQUIRE(storage.current_timestamp().size()); + } + SECTION("select") { + auto rows = storage.select(current_timestamp()); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + SECTION("prepared statement") { + auto preparedStatement = storage.prepare(select(current_timestamp())); + auto rows = storage.execute(preparedStatement); + REQUIRE(rows.size() == 1); + REQUIRE(rows.at(0).size()); + } + } +} + TEST_CASE("busy handler") { auto storage = make_storage({}); storage.busy_handler([](int /*timesCount*/) { @@ -25,20 +77,20 @@ TEST_CASE("drop table") { {}, make_table(usersTableName, make_column("id", &User::id, primary_key()), make_column("name", &User::name)), make_table(visitsTableName, make_column("id", &Visit::id, primary_key()), make_column("date", &Visit::date))); - REQUIRE(!storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(visitsTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(visitsTableName)); storage.sync_schema(); REQUIRE(storage.table_exists(usersTableName)); REQUIRE(storage.table_exists(visitsTableName)); storage.drop_table(usersTableName); - REQUIRE(!storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); REQUIRE(storage.table_exists(visitsTableName)); storage.drop_table(visitsTableName); - REQUIRE(!storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(visitsTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(visitsTableName)); } TEST_CASE("rename table") { @@ -59,10 +111,10 @@ TEST_CASE("rename table") { make_table(usersTableName, make_column("id", &User::id, primary_key()), make_column("name", &User::name)), make_table(visitsTableName, make_column("id", &Visit::id, primary_key()), make_column("date", &Visit::date))); - REQUIRE(!storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(userNewTableName)); - REQUIRE(!storage.table_exists(visitsTableName)); - REQUIRE(!storage.table_exists(visitsNewTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(userNewTableName)); + REQUIRE_FALSE(storage.table_exists(visitsTableName)); + REQUIRE_FALSE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() == usersTableName); REQUIRE(storage.tablename() != userNewTableName); REQUIRE(storage.tablename() == visitsTableName); @@ -70,9 +122,9 @@ TEST_CASE("rename table") { storage.sync_schema(); REQUIRE(storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(userNewTableName)); + REQUIRE_FALSE(storage.table_exists(userNewTableName)); REQUIRE(storage.table_exists(visitsTableName)); - REQUIRE(!storage.table_exists(visitsNewTableName)); + REQUIRE_FALSE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() == usersTableName); REQUIRE(storage.tablename() != userNewTableName); REQUIRE(storage.tablename() == visitsTableName); @@ -81,9 +133,9 @@ TEST_CASE("rename table") { SECTION("with 1 argument") { storage.rename_table(userNewTableName); REQUIRE(storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(userNewTableName)); + REQUIRE_FALSE(storage.table_exists(userNewTableName)); REQUIRE(storage.table_exists(visitsTableName)); - REQUIRE(!storage.table_exists(visitsNewTableName)); + REQUIRE_FALSE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() != usersTableName); REQUIRE(storage.tablename() == userNewTableName); REQUIRE(storage.tablename() == visitsTableName); @@ -91,9 +143,9 @@ TEST_CASE("rename table") { storage.rename_table(visitsNewTableName); REQUIRE(storage.table_exists(usersTableName)); - REQUIRE(!storage.table_exists(userNewTableName)); + REQUIRE_FALSE(storage.table_exists(userNewTableName)); REQUIRE(storage.table_exists(visitsTableName)); - REQUIRE(!storage.table_exists(visitsNewTableName)); + REQUIRE_FALSE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() != usersTableName); REQUIRE(storage.tablename() == userNewTableName); REQUIRE(storage.tablename() != visitsTableName); @@ -102,19 +154,19 @@ TEST_CASE("rename table") { SECTION("with 2 arguments") { storage.rename_table(usersTableName, userNewTableName); - REQUIRE(!storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); REQUIRE(storage.table_exists(userNewTableName)); REQUIRE(storage.table_exists(visitsTableName)); - REQUIRE(!storage.table_exists(visitsNewTableName)); + REQUIRE_FALSE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() == usersTableName); REQUIRE(storage.tablename() != userNewTableName); REQUIRE(storage.tablename() == visitsTableName); REQUIRE(storage.tablename() != visitsNewTableName); storage.rename_table(visitsTableName, visitsNewTableName); - REQUIRE(!storage.table_exists(usersTableName)); + REQUIRE_FALSE(storage.table_exists(usersTableName)); REQUIRE(storage.table_exists(userNewTableName)); - REQUIRE(!storage.table_exists(visitsTableName)); + REQUIRE_FALSE(storage.table_exists(visitsTableName)); REQUIRE(storage.table_exists(visitsNewTableName)); REQUIRE(storage.tablename() == usersTableName); REQUIRE(storage.tablename() != userNewTableName); @@ -151,6 +203,7 @@ TEST_CASE("Storage copy") { storageCopy.remove_all(); } +#if SQLITE_VERSION_NUMBER >= 3006019 TEST_CASE("column_name") { struct User { int id = 0; @@ -178,6 +231,7 @@ TEST_CASE("column_name") { REQUIRE(*storage.find_column_name(&Visit::date) == "date"); REQUIRE(storage.find_column_name(&Visit::notUsed) == nullptr); } +#endif namespace { class Record final { @@ -185,17 +239,17 @@ namespace { using ID = std::uint64_t; using TimeMs = std::uint64_t; - inline ID id() const noexcept { + ID id() const noexcept { return m_id; - }; - inline void setId(ID val) noexcept { + } + void setId(ID val) noexcept { m_id = val; } - inline TimeMs time() const noexcept { + TimeMs time() const noexcept { return m_time; } - inline void setTime(const TimeMs& val) noexcept { + void setTime(const TimeMs& val) noexcept { m_time = val; } @@ -216,3 +270,184 @@ TEST_CASE("non-unique DBOs") { make_column("time", &Record::setTime, &Record::time))); db.sync_schema(); } + +TEST_CASE("insert") { + struct Object { + int id; + std::string name; + }; + + struct ObjectWithoutRowid { + int id; + std::string name; + }; + + auto storage = make_storage( + "test_insert.sqlite", + make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name)), + make_table("objects_without_rowid", + make_column("id", &ObjectWithoutRowid::id, primary_key()), + make_column("name", &ObjectWithoutRowid::name)) + .without_rowid()); + + storage.sync_schema(); + storage.remove_all(); + storage.remove_all(); + + for(auto i = 0; i < 100; ++i) { + storage.insert(Object{ + 0, + "Skillet", + }); + REQUIRE(storage.count() == i + 1); + } + + auto initList = { + Object{ + 0, + "Insane", + }, + Object{ + 0, + "Super", + }, + Object{ + 0, + "Sun", + }, + }; + + auto countBefore = storage.count(); + SECTION("straight") { + storage.insert_range(initList.begin(), initList.end()); + REQUIRE(storage.count() == countBefore + static_cast(initList.size())); + + // test empty container + std::vector emptyVector; + REQUIRE_NOTHROW(storage.insert_range(emptyVector.begin(), emptyVector.end())); + } + SECTION("pointers") { + std::vector> pointers; + pointers.reserve(initList.size()); + std::transform(initList.begin(), initList.end(), std::back_inserter(pointers), [](const Object& object) { + return std::make_unique(Object{object}); + }); + storage.insert_range(pointers.begin(), pointers.end(), &std::unique_ptr::operator*); + + // test empty container + std::vector> emptyVector; + REQUIRE_NOTHROW( + storage.insert_range(emptyVector.begin(), emptyVector.end(), &std::unique_ptr::operator*)); + } + + // test insert without rowid + storage.insert(ObjectWithoutRowid{10, "Life"}); + REQUIRE(storage.get(10).name == "Life"); + storage.insert(ObjectWithoutRowid{20, "Death"}); + REQUIRE(storage.get(20).name == "Death"); +} + +TEST_CASE("Empty storage") { + auto storage = make_storage("empty.sqlite"); + storage.table_exists("table"); +} + +TEST_CASE("Remove") { + struct Object { + int id; + std::string name; + }; + + { + auto storage = make_storage( + "test_remove.sqlite", + make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name))); + + storage.sync_schema(); + storage.remove_all(); + + auto id1 = storage.insert(Object{0, "Skillet"}); + REQUIRE(storage.count() == 1); + storage.remove(id1); + REQUIRE(storage.count() == 0); + } + { + auto storage = make_storage("test_remove.sqlite", + make_table("objects", + make_column("id", &Object::id), + make_column("name", &Object::name), + primary_key(&Object::id))); + storage.sync_schema(); + storage.remove_all(); + + auto id1 = storage.insert(Object{0, "Skillet"}); + REQUIRE(storage.count() == 1); + storage.remove(id1); + REQUIRE(storage.count() == 0); + } + { + auto storage = make_storage("", + make_table("objects", + make_column("id", &Object::id), + make_column("name", &Object::name), + primary_key(&Object::id, &Object::name))); + storage.sync_schema(); + storage.replace(Object{1, "Skillet"}); + REQUIRE(storage.count() == 1); + storage.remove(1, "Skillet"); + REQUIRE(storage.count() == 0); + + storage.replace(Object{1, "Skillet"}); + storage.replace(Object{2, "Paul Cless"}); + REQUIRE(storage.count() == 2); + storage.remove(1, "Skillet"); + REQUIRE(storage.count() == 1); + } +} + +#if SQLITE_VERSION_NUMBER >= 3031000 +TEST_CASE("insert with generated column") { + struct Product { + std::string name; + double price = 0; + double discount = 0; + double tax = 0; + double netPrice = 0; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + Product() = default; + Product(std::string name, double price, double discount, double tax, double netPrice) : + name{std::move(name)}, price{price}, discount{discount}, tax{tax}, netPrice{netPrice} {} +#endif + + bool operator==(const Product& other) const { + return this->name == other.name && this->price == other.price && this->discount == other.discount && + this->tax == other.tax && this->netPrice == other.netPrice; + } + }; + auto storage = + make_storage({}, + make_table("products", + make_column("name", &Product::name), + make_column("price", &Product::price), + make_column("discount", &Product::discount), + make_column("tax", &Product::tax), + make_column("net_price", + &Product::netPrice, + generated_always_as(c(&Product::price) * (1 - c(&Product::discount)) * + (1 + c(&Product::tax)))))); + storage.sync_schema(); + Product product{"ABC Widget", 100, 0.05, 0.07, -100}; + SECTION("insert") { + storage.insert(product); + } + SECTION("replace") { + storage.replace(product); + } + + auto allProducts = storage.get_all(); + decltype(allProducts) expectedProducts; + expectedProducts.push_back({"ABC Widget", 100, 0.05, 0.07, 101.65}); + REQUIRE(allProducts == expectedProducts); +} +#endif diff --git a/tests/sync_schema_tests.cpp b/tests/sync_schema_tests.cpp index a7e1d21c3..eb245a520 100644 --- a/tests/sync_schema_tests.cpp +++ b/tests/sync_schema_tests.cpp @@ -1,5 +1,4 @@ #include -#include #include using namespace sqlite_orm; @@ -262,7 +261,7 @@ TEST_CASE("issue521") { REQUIRE(pocoFromDb.id == oldPoco.id); REQUIRE(pocoFromDb.name == oldPoco.name); - REQUIRE(!(pocoFromDb.beta < 1)); + REQUIRE_FALSE((pocoFromDb.beta < 1)); } } } @@ -357,7 +356,7 @@ TEST_CASE("sync_schema") { REQUIRE(syncSchemaRes == expected); auto users = storage.get_all(); #if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0) - REQUIRE(!users.empty()); + REQUIRE_FALSE(users.empty()); #else REQUIRE(users.empty()); #endif @@ -419,7 +418,7 @@ TEST_CASE("sync_schema") { REQUIRE(syncSchemaRes == expected); auto users = storage.get_all(); #if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0) - REQUIRE(!users.empty()); + REQUIRE_FALSE(users.empty()); #else REQUIRE(users.empty()); #endif @@ -466,7 +465,7 @@ TEST_CASE("sync_schema") { REQUIRE(syncSchemaRes == expected); auto users = storage.get_all(); #if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0) - REQUIRE(!users.empty()); + REQUIRE_FALSE(users.empty()); #else REQUIRE(users.empty()); #endif diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index 3a5b813ed..a89896962 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -15,12 +15,11 @@ TEST_CASE("table name collector") { auto dbObjects = db_objects_t{table}; internal::table_name_collector_base::table_name_set expected; - SECTION("from table") { - internal::serializer_context context{dbObjects}; - auto collector = internal::make_table_name_collector(context.db_objects); + internal::serializer_context context{dbObjects}; + auto collector = internal::make_table_name_collector(context.db_objects); + SECTION("from table") { SECTION("regular column") { - using als = alias_z; auto expression = &User::id; expected.emplace(table.name, ""); iterate_ast(expression, collector); @@ -42,6 +41,75 @@ TEST_CASE("table name collector") { expected.emplace(table.name, "z"); iterate_ast(expression, collector); } + SECTION("count asterisk") { + auto expression = count(); + expected.emplace(table.name, ""); + iterate_ast(expression, collector); + } REQUIRE(collector.table_names == expected); } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + SECTION("from CTE") { + auto dbObjects2 = + internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, 1_ctealias().as(select(1)))); + using context_t = internal::serializer_context; + context_t context{dbObjects2}; + auto collector = internal::make_table_name_collector(context.db_objects); + + SECTION("CTE column") { + using cte_1 = decltype(1_ctealias); + auto expression = column(&User::id); + expected.emplace(alias_extractor::extract(), ""); + iterate_ast(expression, collector); + } + SECTION("CTE column alias") { + using cte_1 = decltype(1_ctealias); + auto expression = column(1_colalias); + expected.emplace(alias_extractor::extract(), ""); + iterate_ast(expression, collector); + } + SECTION("CTE count asterisk") { + using cte_1 = decltype(1_ctealias); + auto expression = count(); + expected.emplace(alias_extractor::extract(), ""); + iterate_ast(expression, collector); + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("aliased CTE column") { + constexpr auto c = "1"_cte; + constexpr auto z_alias = "z"_alias.for_(); + auto expression = z_alias->*&User::id; + expected.emplace(alias_extractor::extract(), "z"); + iterate_ast(expression, collector); + } + SECTION("aliased CTE column alias") { + constexpr auto c = "1"_cte; + constexpr auto z_alias = "z"_alias.for_(); + auto expression = z_alias->*1_colalias; + expected.emplace(alias_extractor::extract(), "z"); + iterate_ast(expression, collector); + } + SECTION("CTE count asterisk 2") { + constexpr auto c = 1_ctealias; + auto expression = count(); + expected.emplace(alias_extractor::extract(), ""); + iterate_ast(expression, collector); + } +#endif + REQUIRE(collector.table_names == expected); + } +#endif + SECTION("highlight") { + SECTION("simple") { + auto expression = highlight(0, "", ""); + expected.emplace(table.name, ""); + iterate_ast(expression, collector); + } + SECTION("in columns") { + auto expression = columns(highlight(0, "", "")); + expected.emplace(table.name, ""); + iterate_ast(expression, collector); + } + } } diff --git a/tests/tests.cpp b/tests/tests.cpp index d0e13dc57..dd5bdc63d 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -3,10 +3,7 @@ #include // std::vector #include // std::string -#include // std::unique_ptr #include // remove -#include // std::iota -#include // std::fill using namespace sqlite_orm; diff --git a/tests/tests2.cpp b/tests/tests2.cpp deleted file mode 100644 index 871b9c3b5..000000000 --- a/tests/tests2.cpp +++ /dev/null @@ -1,634 +0,0 @@ -#include -#include -#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED -#include // std::optional -#endif // SQLITE_ORM_OPTIONAL_SUPPORTED -#include // std::default_delete -#include // free() - -using namespace sqlite_orm; -using std::default_delete; -using std::unique_ptr; - -TEST_CASE("Empty storage") { - auto storage = make_storage("empty.sqlite"); - storage.table_exists("table"); -} - -TEST_CASE("Remove") { - struct Object { - int id; - std::string name; - }; - - { - auto storage = make_storage( - "test_remove.sqlite", - make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name))); - - storage.sync_schema(); - storage.remove_all(); - - auto id1 = storage.insert(Object{0, "Skillet"}); - REQUIRE(storage.count() == 1); - storage.remove(id1); - REQUIRE(storage.count() == 0); - } - { - auto storage = make_storage("test_remove.sqlite", - make_table("objects", - make_column("id", &Object::id), - make_column("name", &Object::name), - primary_key(&Object::id))); - storage.sync_schema(); - storage.remove_all(); - - auto id1 = storage.insert(Object{0, "Skillet"}); - REQUIRE(storage.count() == 1); - storage.remove(id1); - REQUIRE(storage.count() == 0); - } - { - auto storage = make_storage("", - make_table("objects", - make_column("id", &Object::id), - make_column("name", &Object::name), - primary_key(&Object::id, &Object::name))); - storage.sync_schema(); - storage.replace(Object{1, "Skillet"}); - REQUIRE(storage.count() == 1); - storage.remove(1, "Skillet"); - REQUIRE(storage.count() == 0); - - storage.replace(Object{1, "Skillet"}); - storage.replace(Object{2, "Paul Cless"}); - REQUIRE(storage.count() == 2); - storage.remove(1, "Skillet"); - REQUIRE(storage.count() == 1); - } -} -#if SQLITE_VERSION_NUMBER >= 3031000 -TEST_CASE("insert with generated column") { - struct Product { - std::string name; - double price = 0; - double discount = 0; - double tax = 0; - double netPrice = 0; - -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - Product() = default; - Product(std::string name, double price, double discount, double tax, double netPrice) : - name{std::move(name)}, price{price}, discount{discount}, tax{tax}, netPrice{netPrice} {} -#endif - - bool operator==(const Product& other) const { - return this->name == other.name && this->price == other.price && this->discount == other.discount && - this->tax == other.tax && this->netPrice == other.netPrice; - } - }; - auto storage = - make_storage({}, - make_table("products", - make_column("name", &Product::name), - make_column("price", &Product::price), - make_column("discount", &Product::discount), - make_column("tax", &Product::tax), - make_column("net_price", - &Product::netPrice, - generated_always_as(c(&Product::price) * (1 - c(&Product::discount)) * - (1 + c(&Product::tax)))))); - storage.sync_schema(); - Product product{"ABC Widget", 100, 0.05, 0.07, -100}; - SECTION("insert") { - storage.insert(product); - } - SECTION("replace") { - storage.replace(product); - } - - auto allProducts = storage.get_all(); - decltype(allProducts) expectedProducts; - expectedProducts.push_back({"ABC Widget", 100, 0.05, 0.07, 101.65}); - REQUIRE(allProducts == expectedProducts); -} -#endif -TEST_CASE("insert") { - struct Object { - int id; - std::string name; - }; - - struct ObjectWithoutRowid { - int id; - std::string name; - }; - - auto storage = make_storage( - "test_insert.sqlite", - make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name)), - make_table("objects_without_rowid", - make_column("id", &ObjectWithoutRowid::id, primary_key()), - make_column("name", &ObjectWithoutRowid::name)) - .without_rowid()); - - storage.sync_schema(); - storage.remove_all(); - storage.remove_all(); - - for(auto i = 0; i < 100; ++i) { - storage.insert(Object{ - 0, - "Skillet", - }); - REQUIRE(storage.count() == i + 1); - } - - auto initList = { - Object{ - 0, - "Insane", - }, - Object{ - 0, - "Super", - }, - Object{ - 0, - "Sun", - }, - }; - - auto countBefore = storage.count(); - SECTION("straight") { - storage.insert_range(initList.begin(), initList.end()); - REQUIRE(storage.count() == countBefore + static_cast(initList.size())); - - // test empty container - std::vector emptyVector; - REQUIRE_NOTHROW(storage.insert_range(emptyVector.begin(), emptyVector.end())); - } - SECTION("pointers") { - std::vector> pointers; - pointers.reserve(initList.size()); - std::transform(initList.begin(), initList.end(), std::back_inserter(pointers), [](const Object& object) { - return std::make_unique(Object{object}); - }); - storage.insert_range(pointers.begin(), pointers.end(), &std::unique_ptr::operator*); - - // test empty container - std::vector> emptyVector; - REQUIRE_NOTHROW( - storage.insert_range(emptyVector.begin(), emptyVector.end(), &std::unique_ptr::operator*)); - } - - // test insert without rowid - storage.insert(ObjectWithoutRowid{10, "Life"}); - REQUIRE(storage.get(10).name == "Life"); - storage.insert(ObjectWithoutRowid{20, "Death"}); - REQUIRE(storage.get(20).name == "Death"); -} - -struct SqrtFunction { - static int callsCount; - - double operator()(double arg) const { - ++callsCount; - return std::sqrt(arg); - } - - static const char* name() { - return "SQRT_CUSTOM"; - } -}; - -int SqrtFunction::callsCount = 0; - -struct HasPrefixFunction { - static int callsCount; - static int objectsCount; - - HasPrefixFunction() { - ++objectsCount; - } - - HasPrefixFunction(const HasPrefixFunction&) { - ++objectsCount; - } - - HasPrefixFunction(HasPrefixFunction&&) { - ++objectsCount; - } - - ~HasPrefixFunction() { - --objectsCount; - } - - bool operator()(const std::string& str, const std::string& prefix) { - ++callsCount; - return str.compare(0, prefix.size(), prefix) == 0; - } - - static std::string name() { - return "HAS_PREFIX"; - } -}; - -int HasPrefixFunction::callsCount = 0; -int HasPrefixFunction::objectsCount = 0; - -struct MeanFunction { - double total = 0; - int count = 0; - - static int objectsCount; - - MeanFunction() { - ++objectsCount; - } - - MeanFunction(const MeanFunction&) { - ++objectsCount; - } - - MeanFunction(MeanFunction&&) { - ++objectsCount; - } - - ~MeanFunction() { - --objectsCount; - } - - void step(double value) { - total += value; - ++count; - } - - double fin() const { - return total / count; - } - - static std::string name() { - return "MEAN"; - } -}; - -int MeanFunction::objectsCount = 0; - -struct FirstFunction { - static int objectsCount; - static int callsCount; - - FirstFunction() { - ++objectsCount; - } - - FirstFunction(const MeanFunction&) { - ++objectsCount; - } - - FirstFunction(MeanFunction&&) { - ++objectsCount; - } - - ~FirstFunction() { - --objectsCount; - } - - std::string operator()(const arg_values& args) const { - ++callsCount; - std::string res; - res.reserve(args.size()); - for(auto value: args) { - auto stringValue = value.get(); - if(!stringValue.empty()) { - res += stringValue.front(); - } - } - return res; - } - - static const char* name() { - return "FIRST"; - } -}; - -struct MultiSum { - double sum = 0; - - static int objectsCount; - - MultiSum() { - ++objectsCount; - } - - MultiSum(const MeanFunction&) { - ++objectsCount; - } - - MultiSum(MeanFunction&&) { - ++objectsCount; - } - - ~MultiSum() { - --objectsCount; - } - - void step(const arg_values& args) { - for(auto it = args.begin(); it != args.end(); ++it) { - if(!it->empty() && (it->is_integer() || it->is_float())) { - this->sum += it->get(); - } - } - } - - double fin() const { - return this->sum; - } - - static const char* name() { - return "MULTI_SUM"; - } -}; - -int MultiSum::objectsCount = 0; - -int FirstFunction::objectsCount = 0; -int FirstFunction::callsCount = 0; - -TEST_CASE("custom functions") { - using Catch::Matchers::ContainsSubstring; - - SqrtFunction::callsCount = 0; - HasPrefixFunction::callsCount = 0; - FirstFunction::callsCount = 0; - - std::string path; - SECTION("in memory") { - path = {}; - } - SECTION("file") { - path = "custom_function.sqlite"; - ::remove(path.c_str()); - } - struct User { - int id = 0; - -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - User() = default; - User(int id) : id{id} {} -#endif - }; - auto storage = make_storage(path, make_table("users", make_column("id", &User::id))); - storage.sync_schema(); - // call before creation - REQUIRE_THROWS_WITH(storage.select(func(4)), ContainsSubstring("no such function")); - - // create function - REQUIRE(SqrtFunction::callsCount == 0); - - storage.create_scalar_function(); - - REQUIRE(SqrtFunction::callsCount == 0); - - // call after creation - { - auto rows = storage.select(func(4)); - REQUIRE(SqrtFunction::callsCount == 1); - decltype(rows) expected; - expected.push_back(2); - REQUIRE(rows == expected); - } - - // create function - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - storage.create_scalar_function(); - REQUIRE(HasPrefixFunction::callsCount == 0); - REQUIRE(HasPrefixFunction::objectsCount == 0); - - // call after creation - { - auto rows = storage.select(func("one", "o")); - decltype(rows) expected; - expected.push_back(true); - REQUIRE(rows == expected); - } - REQUIRE(HasPrefixFunction::callsCount == 1); - REQUIRE(HasPrefixFunction::objectsCount == 0); - { - auto rows = storage.select(func("two", "b")); - decltype(rows) expected; - expected.push_back(false); - REQUIRE(rows == expected); - } - REQUIRE(HasPrefixFunction::callsCount == 2); - REQUIRE(HasPrefixFunction::objectsCount == 0); - - // delete function - storage.delete_scalar_function(); - - // delete function - storage.delete_scalar_function(); - - storage.create_aggregate_function(); - - storage.replace(User{1}); - storage.replace(User{2}); - storage.replace(User{3}); - REQUIRE(storage.count() == 3); - { - REQUIRE(MeanFunction::objectsCount == 0); - auto rows = storage.select(func(&User::id)); - REQUIRE(MeanFunction::objectsCount == 0); - decltype(rows) expected; - expected.push_back(2); - REQUIRE(rows == expected); - } - storage.delete_aggregate_function(); - - storage.create_scalar_function(); - { - auto rows = storage.select(func("Vanotek", "Tinashe", "Pitbull")); - decltype(rows) expected; - expected.push_back("VTP"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 1); - } - { - auto rows = storage.select(func("Charli XCX", "Rita Ora")); - decltype(rows) expected; - expected.push_back("CR"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 2); - } - { - auto rows = storage.select(func("Ted")); - decltype(rows) expected; - expected.push_back("T"); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 3); - } - { - auto rows = storage.select(func()); - decltype(rows) expected; - expected.push_back(""); - REQUIRE(rows == expected); - REQUIRE(FirstFunction::objectsCount == 0); - REQUIRE(FirstFunction::callsCount == 4); - } - storage.delete_scalar_function(); - - storage.create_aggregate_function(); - { - REQUIRE(MultiSum::objectsCount == 0); - auto rows = storage.select(func(&User::id, 5)); - decltype(rows) expected; - expected.push_back(21); - REQUIRE(rows == expected); - REQUIRE(MultiSum::objectsCount == 0); - } - storage.delete_aggregate_function(); -} - -// Wrap std::default_delete in a function -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION -template -void delete_default(std::conditional_t::value, std::decay_t, T*> o) noexcept( - noexcept(std::default_delete{}(o))) { - std::default_delete{}(o); -} - -// Integral function constant for default deletion -template -using delete_default_t = std::integral_constant), delete_default>; -// Integral function constant variable for default deletion -template -SQLITE_ORM_INLINE_VAR constexpr delete_default_t delete_default_f{}; -#endif - -using free_t = std::integral_constant; -SQLITE_ORM_INLINE_VAR constexpr free_t free_f{}; - -TEST_CASE("obtain_xdestroy_for") { - - using internal::xdestroy_proxy; - - // class yielding a 'xDestroy' function pointer - struct xdestroy_holder { - xdestroy_fn_t xDestroy = free; - - constexpr operator xdestroy_fn_t() const noexcept { - return xDestroy; - } - }; - - // class yielding a function pointer not of type xdestroy_fn_t - struct int_destroy_holder { - using destroy_fn_t = void (*)(int*); - - destroy_fn_t destroy = nullptr; - - constexpr operator destroy_fn_t() const noexcept { - return destroy; - } - }; - - { - constexpr int* int_nullptr = nullptr; -#if !defined(SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION) || \ - (__cpp_constexpr >= 201907L) // Trivial default initialization in constexpr functions - constexpr const int* const_int_nullptr = nullptr; -#endif - - // null_xdestroy_f(int*) - constexpr xdestroy_fn_t xDestroy1 = obtain_xdestroy_for(null_xdestroy_f, int_nullptr); - STATIC_REQUIRE(xDestroy1 == nullptr); - REQUIRE(xDestroy1 == nullptr); - - // free(int*) - constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); - STATIC_REQUIRE(xDestroy2 == &free); - REQUIRE(xDestroy2 == &free); - - // free_f(int*) - constexpr xdestroy_fn_t xDestroy3 = obtain_xdestroy_for(free_f, int_nullptr); - STATIC_REQUIRE(xDestroy3 == &free); - REQUIRE(xDestroy3 == &free); - -#if __cpp_constexpr >= 201603L // constexpr lambda - // [](void* p){} - constexpr auto lambda4_1 = [](void*) {}; - constexpr xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); - STATIC_REQUIRE(xDestroy4_1 == lambda4_1); - REQUIRE(xDestroy4_1 == lambda4_1); -#else -#if !defined(_MSC_VER) || (_MSC_VER >= 1914) // conversion of lambda closure to function pointer using `+` - // [](void* p){} - auto lambda4_1 = [](void*) {}; - xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); - REQUIRE(xDestroy4_1 == lambda4_1); -#endif -#endif - - // [](int* p) { delete p; } -#if __cplusplus >= 202002L // default-constructible non-capturing lambdas - constexpr auto lambda4_2 = [](int* p) { - delete p; - }; - using lambda4_2_t = std::remove_const_t; - constexpr xdestroy_fn_t xDestroy4_2 = obtain_xdestroy_for(lambda4_2, int_nullptr); - STATIC_REQUIRE(xDestroy4_2 == &xdestroy_proxy); - REQUIRE((xDestroy4_2 == &xdestroy_proxy)); -#endif - - // default_delete(int*) - constexpr xdestroy_fn_t xDestroy5 = obtain_xdestroy_for(default_delete{}, int_nullptr); - STATIC_REQUIRE(xDestroy5 == &xdestroy_proxy, int>); - REQUIRE((xDestroy5 == &xdestroy_proxy, int>)); - -#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION - // delete_default_f(int*) - constexpr xdestroy_fn_t xDestroy6 = obtain_xdestroy_for(delete_default_f, int_nullptr); - STATIC_REQUIRE(xDestroy6 == &xdestroy_proxy, int>); - REQUIRE((xDestroy6 == &xdestroy_proxy, int>)); - - // delete_default_f(const int*) - constexpr xdestroy_fn_t xDestroy7 = obtain_xdestroy_for(delete_default_f, const_int_nullptr); - STATIC_REQUIRE(xDestroy7 == &xdestroy_proxy, const int>); - REQUIRE((xDestroy7 == &xdestroy_proxy, const int>)); -#endif - -#if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions - // xdestroy_holder{ free }(int*) - constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); - STATIC_REQUIRE(xDestroy8 == &free); - REQUIRE(xDestroy8 == &free); - - // xdestroy_holder{ free }(const int*) - constexpr xdestroy_fn_t xDestroy9 = obtain_xdestroy_for(xdestroy_holder{free}, const_int_nullptr); - STATIC_REQUIRE(xDestroy9 == &free); - REQUIRE(xDestroy9 == &free); - - // xdestroy_holder{ nullptr }(const int*) - constexpr xdestroy_fn_t xDestroy10 = obtain_xdestroy_for(xdestroy_holder{nullptr}, const_int_nullptr); - STATIC_REQUIRE(xDestroy10 == nullptr); - REQUIRE(xDestroy10 == nullptr); -#endif - - // expressions that do not work -#if 0 - // can't use functions that differ from xdestroy_fn_t - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(delete_default, int_nullptr); - // can't use object yielding a function pointer that differs from xdestroy_fn_t - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(int_destroy_holder{}, int_nullptr); - // successfully takes default_delete, but default_delete statically asserts on a non-complete type `void*` - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, int_nullptr); - // successfully takes default_delete, but xdestroy_proxy can't call the deleter with a `const int*` - constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, const_int_nullptr); -#endif - } -} diff --git a/tests/tests3.cpp b/tests/tests3.cpp index 2f945fa69..366ccc238 100644 --- a/tests/tests3.cpp +++ b/tests/tests3.cpp @@ -121,26 +121,7 @@ TEST_CASE("Wide string") { } } #endif // SQLITE_ORM_OMITS_CODECVT -#ifdef SQLITE_ENABLE_DBSTAT_VTAB -TEST_CASE("dbstat") { - struct User { - int id = 0; - std::string name; - }; - auto storage = - make_storage("dbstat.sqlite", - make_table("users", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_dbstat_table()); - storage.sync_schema(); - - storage.remove_all(); - - storage.replace(User{1, "Dua Lipa"}); - auto dbstatRows = storage.get_all(); - std::ignore = dbstatRows; -} -#endif // SQLITE_ENABLE_DBSTAT_VTAB TEST_CASE("Busy timeout") { auto storage = make_storage("testBusyTimeout.sqlite"); storage.busy_timeout(500); @@ -265,15 +246,6 @@ TEST_CASE("Aggregate functions") { } } -TEST_CASE("Current timestamp") { - auto storage = make_storage(""); - REQUIRE(storage.current_timestamp().size()); - - storage.begin_transaction(); - REQUIRE(storage.current_timestamp().size()); - storage.commit(); -} - TEST_CASE("Open forever") { struct User { int id; @@ -413,3 +385,62 @@ TEST_CASE("Escape chars") { storage.update(selena); storage.remove(10); } + +#if(SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +TEST_CASE("With select") { + using Catch::Matchers::Equals; + + using cnt = decltype(1_ctealias); + auto storage = make_storage(""); + SECTION("with ordinary") { + auto rows = storage.with(cte().as(select(1)), select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1})); + } + SECTION("with ordinary, compound") { + auto rows = storage.with(cte().as(select(1)), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + REQUIRE_THAT(rows, Equals(std::vector{1, 1})); + } + SECTION("with not enforced recursive") { + auto rows = storage.with_recursive(cte().as(select(1)), select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1})); + } + SECTION("with not enforced recursive, compound") { + auto rows = storage.with_recursive(cte().as(select(1)), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + REQUIRE_THAT(rows, Equals(std::vector{1, 1})); + } + SECTION("with ordinary, multiple") { + auto rows = storage.with(std::make_tuple(cte().as(select(1))), select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1})); + } + SECTION("with ordinary, multiple, compound") { + auto rows = storage.with(std::make_tuple(cte().as(select(1))), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + REQUIRE_THAT(rows, Equals(std::vector{1, 1})); + } + SECTION("with not enforced recursive, multiple") { + auto rows = storage.with_recursive(std::make_tuple(cte().as(select(1))), select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1})); + } + SECTION("with not enforced recursive, multiple, compound") { + auto rows = storage.with_recursive(std::make_tuple(cte().as(select(1))), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + REQUIRE_THAT(rows, Equals(std::vector{1, 1})); + } + SECTION("with optional recursive") { + auto rows = storage.with( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 2)))), + select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1, 2})); + } + SECTION("with recursive") { + auto rows = storage.with_recursive( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 2)))), + select(column(1_colalias))); + REQUIRE_THAT(rows, Equals(std::vector{1, 2})); + } +} +#endif diff --git a/tests/tests4.cpp b/tests/tests4.cpp index 3a690df26..f2626497e 100644 --- a/tests/tests4.cpp +++ b/tests/tests4.cpp @@ -14,9 +14,9 @@ TEST_CASE("Unique ptr in update") { std::unique_ptr name; }; - auto storage = make_storage( - {}, - make_table("users", make_column("id", &User::id, primary_key()), make_column("name", &User::name))); + auto nameColumn = make_column("name", &User::name); + auto nameTable = make_table("users", make_column("id", &User::id, primary_key()), nameColumn); + auto storage = make_storage({}, nameTable); storage.sync_schema(); storage.insert(User{}); @@ -144,6 +144,7 @@ TEST_CASE("join") { } } +#if SQLITE_VERSION_NUMBER >= 3006019 TEST_CASE("two joins") { struct Statement { int id_statement; @@ -265,26 +266,27 @@ TEST_CASE("two joins") { alias_column(&Transaccion::fkey_account_own), alias_column(&Account::id_account), alias_column(&Account::id_account)), - left_outer_join(on(c(alias_column(&Transaccion::fkey_account_other)) == + left_outer_join(on(alias_column(&Transaccion::fkey_account_other) == alias_column(&Account::id_account))), - inner_join(on(c(alias_column(&Transaccion::fkey_account_own)) == + inner_join(on(alias_column(&Transaccion::fkey_account_own) == alias_column(&Account::id_account)))); std::ignore = storage.select(columns(alias_column(&Transaccion::fkey_account_other), alias_column(&Transaccion::fkey_account_own), alias_column(&Account::id_account), alias_column(&Account::id_account)), - left_join(on(c(alias_column(&Transaccion::fkey_account_other)) == + left_join(on(alias_column(&Transaccion::fkey_account_other) == alias_column(&Account::id_account))), - inner_join(on(c(alias_column(&Transaccion::fkey_account_own)) == + inner_join(on(alias_column(&Transaccion::fkey_account_own) == alias_column(&Account::id_account)))); std::ignore = storage.select(columns(alias_column(&Transaccion::fkey_account_other), alias_column(&Transaccion::fkey_account_own), alias_column(&Account::id_account), alias_column(&Account::id_account)), - join(on(c(alias_column(&Transaccion::fkey_account_other)) == + join(on(alias_column(&Transaccion::fkey_account_other) == alias_column(&Account::id_account))), - inner_join(on(c(alias_column(&Transaccion::fkey_account_own)) == + inner_join(on(alias_column(&Transaccion::fkey_account_own) == alias_column(&Account::id_account)))); } +#endif diff --git a/tests/tests5.cpp b/tests/tests5.cpp index fc683d186..a4ce1bf28 100644 --- a/tests/tests5.cpp +++ b/tests/tests5.cpp @@ -10,12 +10,6 @@ TEST_CASE("Iterate blob") { std::vector key; }; - struct TestComparator { - bool operator()(const Test& lhs, const Test& rhs) const { - return lhs.id == rhs.id && lhs.key == rhs.key; - } - }; - auto db = make_storage("", make_table("Test", make_column("key", &Test::key), make_column("id", &Test::id, primary_key()))); @@ -28,15 +22,6 @@ TEST_CASE("Iterate blob") { db.replace(v); - TestComparator testComparator; - for(auto& obj: db.iterate()) { - REQUIRE(testComparator(obj, v)); - } // test that view_t and iterator_t compile - - for(const auto& obj: db.iterate()) { - REQUIRE(testComparator(obj, v)); - } // test that view_t and iterator_t compile - { auto keysCount = db.count(where(c(&Test::key) == key)); auto keysCountRows = db.select(count(), where(c(&Test::key) == key)); @@ -45,14 +30,6 @@ TEST_CASE("Iterate blob") { REQUIRE(keysCount == keysCountRows.front()); REQUIRE(db.get_all(where(c(&Test::key) == key)).size() == 1); } - { - int iterationsCount = 0; - for(auto& w: db.iterate(where(c(&Test::key) == key))) { - REQUIRE(testComparator(w, v)); - ++iterationsCount; - } - REQUIRE(iterationsCount == 1); - } } TEST_CASE("Threadsafe") { @@ -248,13 +225,13 @@ TEST_CASE("Dump") { auto rows = storage.select(&User::carYear, where(is_equal(&User::id, userId_1))); REQUIRE(rows.size() == 1); - REQUIRE(!rows.front().has_value()); + REQUIRE_FALSE(rows.front().has_value()); auto allUsers = storage.get_all(); REQUIRE(allUsers.size() == 2); const std::string dumpUser1 = storage.dump(allUsers[0]); - REQUIRE(dumpUser1 == std::string{"{ id : '1', car_year : 'null' }"}); + REQUIRE(dumpUser1 == std::string{"{ id : '1', car_year : 'NULL' }"}); const std::string dumpUser2 = storage.dump(allUsers[1]); REQUIRE(dumpUser2 == std::string{"{ id : '2', car_year : '2006' }"}); diff --git a/tests/transaction_tests.cpp b/tests/transaction_tests.cpp index 6df82d22e..b40be81a2 100644 --- a/tests/transaction_tests.cpp +++ b/tests/transaction_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include "catch_matchers.h" using namespace sqlite_orm; @@ -25,41 +26,14 @@ TEST_CASE("transaction") { auto storage = make_storage( "test_transaction_guard.sqlite", make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name))); - REQUIRE(!storage.is_opened()); + REQUIRE_FALSE(storage.is_opened()); storage.sync_schema(); - REQUIRE(!storage.is_opened()); + REQUIRE_FALSE(storage.is_opened()); storage.transaction([&] { storage.insert(Object{0, "Jack"}); return true; }); - REQUIRE(!storage.is_opened()); -} - -TEST_CASE("transaction_rollback") { - auto storage = make_storage( - "test_transaction_guard.sqlite", - make_table("objects", make_column("id", &Object::id, primary_key()), make_column("name", &Object::name))); - - storage.sync_schema(); - storage.remove_all(); - - storage.insert(Object{0, "Jack"}); - - SECTION("insert, call make a storage to call an exception and check that rollback was fired") { - auto countBefore = storage.count(); - try { - storage.transaction([&] { - storage.insert(Object{0, "John"}); - storage.get(-1); - REQUIRE(false); - return true; - }); - } catch(const std::system_error& e) { - REQUIRE(e.code() == orm_error_code::not_found); - auto countNow = storage.count(); - REQUIRE(countBefore == countNow); - } - } + REQUIRE_FALSE(storage.is_opened()); } TEST_CASE("begin_transaction") { @@ -100,22 +74,51 @@ TEST_CASE("Transaction guard") { storage.insert(Object{0, "Jack"}); + const ErrorCodeExceptionMatcher notFoundExceptionMatcher(orm_error_code::not_found); SECTION("insert, call make a storage to call an exception and check that rollback was fired") { auto countBefore = storage.count(); - try { - auto guard = storage.transaction_guard(); - - storage.insert(Object{0, "John"}); - - storage.get(-1); - - REQUIRE(false); - } catch(const std::system_error& e) { - REQUIRE(e.code() == orm_error_code::not_found); - auto countNow = storage.count(); - - REQUIRE(countBefore == countNow); + SECTION("transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.transaction_guard(); + storage.insert(Object{0, "John"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); } + SECTION("deferred_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.deferred_transaction_guard(); + storage.insert(Object{0, "John"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("exclusive_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.exclusive_transaction_guard(); + storage.insert(Object{0, "John"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("immediate_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.immediate_transaction_guard(); + storage.insert(Object{0, "John"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + auto countNow = storage.count(); + REQUIRE(countBefore == countNow); } SECTION("check that one can call other transaction functions without exceptions") { REQUIRE_NOTHROW(storage.transaction([] { @@ -124,66 +127,203 @@ TEST_CASE("Transaction guard") { } SECTION("commit explicitly and check that after exception data was saved") { auto countBefore = storage.count(); - try { - auto guard = storage.transaction_guard(); - storage.insert(Object{0, "John"}); - guard.commit(); - storage.get(-1); - REQUIRE(false); - } catch(const std::system_error& e) { - REQUIRE(e.code() == orm_error_code::not_found); - auto countNow = storage.count(); - REQUIRE(countNow == countBefore + 1); + SECTION("transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.transaction_guard(); + storage.insert(Object{0, "John"}); + guard.commit(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("deferred_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.deferred_transaction_guard(); + storage.insert(Object{0, "John"}); + guard.commit(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("exclusive_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.exclusive_transaction_guard(); + storage.insert(Object{0, "John"}); + guard.commit(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("immediate_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.immediate_transaction_guard(); + storage.insert(Object{0, "John"}); + guard.commit(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); } + auto countNow = storage.count(); + REQUIRE(countNow == countBefore + 1); } SECTION("rollback explicitly") { auto countBefore = storage.count(); - try { - auto guard = storage.transaction_guard(); - storage.insert(Object{0, "Michael"}); - guard.rollback(); - storage.get(-1); - REQUIRE(false); - } catch(const std::system_error& e) { - REQUIRE(e.code() == orm_error_code::not_found); - auto countNow = storage.count(); - REQUIRE(countNow == countBefore); + SECTION("transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.transaction_guard(); + storage.insert(Object{0, "Michael"}); + guard.rollback(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("deferred_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.deferred_transaction_guard(); + storage.insert(Object{0, "Michael"}); + guard.rollback(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); } + SECTION("exclusive_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.exclusive_transaction_guard(); + storage.insert(Object{0, "Michael"}); + guard.rollback(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("immediate_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.immediate_transaction_guard(); + storage.insert(Object{0, "Michael"}); + guard.rollback(); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + auto countNow = storage.count(); + REQUIRE(countNow == countBefore); } SECTION("commit on exception") { auto countBefore = storage.count(); - try { - auto guard = storage.transaction_guard(); - guard.commit_on_destroy = true; - storage.insert(Object{0, "Michael"}); - storage.get(-1); - REQUIRE(false); - } catch(const std::system_error& e) { - REQUIRE(e.code() == orm_error_code::not_found); - auto countNow = storage.count(); - REQUIRE(countNow == countBefore + 1); + SECTION("transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.transaction_guard(); + guard.commit_on_destroy = true; + storage.insert(Object{0, "Michael"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("deferred_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.deferred_transaction_guard(); + guard.commit_on_destroy = true; + storage.insert(Object{0, "Michael"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("exclusive_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.exclusive_transaction_guard(); + guard.commit_on_destroy = true; + storage.insert(Object{0, "Michael"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); + } + SECTION("immediate_transaction_guard") { + REQUIRE_THROWS_MATCHES( + [&storage] { + auto guard = storage.immediate_transaction_guard(); + guard.commit_on_destroy = true; + storage.insert(Object{0, "Michael"}); + storage.get(-1); + }(), + std::system_error, + notFoundExceptionMatcher); } + auto countNow = storage.count(); + REQUIRE(countNow == countBefore + 1); } SECTION("work without exception") { auto countBefore = storage.count(); - // transaction scope - { + SECTION("transaction_guard") { auto guard = storage.transaction_guard(); guard.commit_on_destroy = true; REQUIRE_NOTHROW(storage.insert(Object{0, "Lincoln"})); } + SECTION("deferred_transaction_guard") { + auto guard = storage.deferred_transaction_guard(); + guard.commit_on_destroy = true; + REQUIRE_NOTHROW(storage.insert(Object{0, "Lincoln"})); + } + SECTION("exclusive_transaction_guard") { + auto guard = storage.exclusive_transaction_guard(); + guard.commit_on_destroy = true; + REQUIRE_NOTHROW(storage.insert(Object{0, "Lincoln"})); + } + SECTION("immediate_transaction_guard") { + auto guard = storage.immediate_transaction_guard(); + guard.commit_on_destroy = true; + REQUIRE_NOTHROW(storage.insert(Object{0, "Lincoln"})); + } auto countNow = storage.count(); REQUIRE(countNow == countBefore + 1); } SECTION("std::move ctor") { std::vector guards; auto countBefore = storage.count(); - { + SECTION("transaction_guard") { auto guard = storage.transaction_guard(); storage.insert(Object{0, "Lincoln"}); guards.push_back(std::move(guard)); REQUIRE(storage.count() == countBefore + 1); } + SECTION("deferred_transaction_guard") { + auto guard = storage.deferred_transaction_guard(); + storage.insert(Object{0, "Lincoln"}); + guards.push_back(std::move(guard)); + REQUIRE(storage.count() == countBefore + 1); + } + SECTION("exclusive_transaction_guard") { + auto guard = storage.exclusive_transaction_guard(); + storage.insert(Object{0, "Lincoln"}); + guards.push_back(std::move(guard)); + REQUIRE(storage.count() == countBefore + 1); + } + SECTION("immediate_transaction_guard") { + auto guard = storage.immediate_transaction_guard(); + storage.insert(Object{0, "Lincoln"}); + guards.push_back(std::move(guard)); + REQUIRE(storage.count() == countBefore + 1); + } REQUIRE(storage.count() == countBefore + 1); guards.clear(); REQUIRE(storage.count() == countBefore); diff --git a/tests/trigger_tests.cpp b/tests/trigger_tests.cpp index a5946ac80..aad420adc 100644 --- a/tests/trigger_tests.cpp +++ b/tests/trigger_tests.cpp @@ -118,3 +118,15 @@ TEST_CASE("triggers_basics") { REQUIRE(storage.count() == 2); } } + +TEST_CASE("issue1280") { + struct X { + int test = 0; + }; + auto storage = make_storage( + "", + make_trigger("table_insert_InsertTest", after().insert().on().begin(update_all(set(c(&X::test) = 5))).end()), + make_table("x", make_column("test", &X::test))); + storage.sync_schema(); + storage.sync_schema_simulate(); +} diff --git a/tests/unique_cases/get_all_with_two_tables.cpp b/tests/unique_cases/get_all_with_two_tables.cpp index 91d17acde..ddf7d6ad2 100644 --- a/tests/unique_cases/get_all_with_two_tables.cpp +++ b/tests/unique_cases/get_all_with_two_tables.cpp @@ -71,18 +71,17 @@ TEST_CASE("get_all with two tables") { } { storage.replace(Item{4, "nwa"}); - auto rows = storage.select(&Item::id, - where(like(&Item::attributes, conc(conc("%", &Pattern::value), "%"))), - group_by(&Item::id), - having(is_equal(count(&Pattern::value), select(count())))); + auto rows = + storage.select(&Item::id, + where(like(&Item::attributes, conc(conc("%", &Pattern::value), "%"))), + group_by(&Item::id).having(is_equal(count(&Pattern::value), select(count())))); REQUIRE_THAT(rows, UnorderedEquals({4})); auto items = storage.get_all( where(in(&Item::id, select(&Item::id, where(like(&Item::attributes, conc(conc("%", &Pattern::value), "%"))), - group_by(&Item::id), - having(is_equal(count(&Pattern::value), select(count()))))))); + group_by(&Item::id).having(is_equal(count(&Pattern::value), select(count()))))))); REQUIRE_THAT(items, UnorderedEquals({item4})); } storage.rollback(); diff --git a/tests/unique_cases/index_named_table_with_fk.cpp b/tests/unique_cases/index_named_table_with_fk.cpp index 7d3b18edb..2d2191f77 100644 --- a/tests/unique_cases/index_named_table_with_fk.cpp +++ b/tests/unique_cases/index_named_table_with_fk.cpp @@ -1,6 +1,7 @@ #include #include +#if SQLITE_VERSION_NUMBER >= 3006019 using namespace sqlite_orm; TEST_CASE("index named table") { @@ -77,3 +78,4 @@ TEST_CASE("index named table") { foreign_key(&pay_info::order_number).references(&OrderTable::order_number))); storage.sync_schema(); } +#endif diff --git a/tests/unique_cases/issue937.cpp b/tests/unique_cases/issue937.cpp index 51ece11b5..b0baf876a 100644 --- a/tests/unique_cases/issue937.cpp +++ b/tests/unique_cases/issue937.cpp @@ -1,7 +1,9 @@ #include #include +#if SQLITE_VERSION_NUMBER >= 3006019 using namespace sqlite_orm; + #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED TEST_CASE("issue937") { struct Employee { @@ -65,7 +67,9 @@ TEST_CASE("issue937") { select(columns(as(&Department::m_deptname), as_optional(&Department::m_deptno))), select(union_all(select(columns(quote("--------------------"), std::optional())), select(columns(as(&Employee::m_ename), as_optional(&Employee::m_depno)))))))); +#if SQLITE_VERSION_NUMBER >= 3014000 auto sql = statement.expanded_sql(); +#endif auto rows = storage.execute(statement); { // issue953 auto expression = select( @@ -86,3 +90,4 @@ TEST_CASE("issue937") { } } #endif +#endif diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp new file mode 100644 index 000000000..6aec5ef6b --- /dev/null +++ b/tests/user_defined_functions.cpp @@ -0,0 +1,647 @@ +#include +#include +#include "catch_matchers.h" + +using namespace sqlite_orm; + +struct SqrtFunction { + static int callsCount; + + double operator()(double arg) const { + ++callsCount; + return std::sqrt(arg); + } + + static const char* name() { + return "SQRT_CUSTOM"; + } +}; + +int SqrtFunction::callsCount = 0; + +struct StatelessHasPrefixFunction { + static int callsCount; + static int objectsCount; + + StatelessHasPrefixFunction() { + ++objectsCount; + } + + StatelessHasPrefixFunction(const StatelessHasPrefixFunction&) = delete; + + ~StatelessHasPrefixFunction() { + --objectsCount; + } + + bool operator()(const std::string& str, const std::string& prefix) { + ++callsCount; + return str.compare(0, prefix.size(), prefix) == 0; + } + + static std::string name() { + return "STATELESS_HAS_PREFIX"; + } +}; + +int StatelessHasPrefixFunction::callsCount = 0; +int StatelessHasPrefixFunction::objectsCount = 0; + +struct HasPrefixFunction { + static int callsCount; + static int objectsCount; + + int& staticCallsCount; + int& staticObjectsCount; + + HasPrefixFunction() : staticCallsCount{callsCount}, staticObjectsCount{objectsCount} { + ++staticObjectsCount; + } + + HasPrefixFunction(const HasPrefixFunction&) = delete; + + ~HasPrefixFunction() { + --staticObjectsCount; + } + + bool operator()(const std::string& str, const std::string& prefix) { + ++staticCallsCount; + return str.compare(0, prefix.size(), prefix) == 0; + } + + static std::string name() { + return "HAS_PREFIX"; + } +}; + +int HasPrefixFunction::callsCount = 0; +int HasPrefixFunction::objectsCount = 0; + +struct MeanFunction { + double total = 0; + int count = 0; + + static int ctorCallsCount; + static int objectsCount; + + MeanFunction() { + ++ctorCallsCount; + ++objectsCount; + } + + MeanFunction(const MeanFunction&) = delete; + + ~MeanFunction() { + --objectsCount; + } + + void step(double value) { + total += value; + ++count; + } + + double fin() const { + return total / count; + } + + static std::string name() { + return "MEAN"; + } +}; + +int MeanFunction::ctorCallsCount = 0; +int MeanFunction::objectsCount = 0; + +struct FirstFunction { + static int objectsCount; + static int callsCount; + + int& staticObjectsCount; + int& staticCallsCount; + + FirstFunction() : staticObjectsCount{objectsCount}, staticCallsCount{callsCount} { + ++staticObjectsCount; + } + + FirstFunction(const FirstFunction&) = delete; + + ~FirstFunction() { + --staticObjectsCount; + } + + std::string operator()(const arg_values& args) const { + ++staticCallsCount; + std::string res; + res.reserve(args.size()); + for(auto value: args) { + auto stringValue = value.get(); + if(!stringValue.empty()) { + res += stringValue.front(); + } + } + return res; + } + + static const char* name() { + return "FIRST"; + } +}; + +struct MultiSum { + double sum = 0; + + static int objectsCount; + + MultiSum() { + ++objectsCount; + } + + MultiSum(const MeanFunction&) { + ++objectsCount; + } + + MultiSum(MeanFunction&&) { + ++objectsCount; + } + + ~MultiSum() { + --objectsCount; + } + + void step(const arg_values& args) { + for(auto it = args.begin(); it != args.end(); ++it) { + if(!it->empty() && (it->is_integer() || it->is_float())) { + this->sum += it->get(); + } + } + } + + double fin() const { + return this->sum; + } + + static const char* name() { + return "MULTI_SUM"; + } +}; + +int MultiSum::objectsCount = 0; + +int FirstFunction::objectsCount = 0; +int FirstFunction::callsCount = 0; + +#if __cpp_aligned_new >= 201606L +struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedScalarFunction { + int operator()(int arg) const { + return arg; + } + + static const char* name() { + return "OVERALIGNED1"; + } +}; + +struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedAggregateFunction { + double sum = 0; + + void step(double arg) { + sum += arg; + } + double fin() const { + return sum; + } + + static const char* name() { + return "OVERALIGNED2"; + } +}; +#endif + +struct NonAllocatableAggregateFunction { + void step(double /*arg*/) {} + + double fin() const { + return 0; + } + + static const char* name() { + return "NONALLOCATABLE"; + } +}; + +template<> +struct std::allocator { + using value_type = NonAllocatableAggregateFunction; + + NonAllocatableAggregateFunction* allocate(size_t /*count*/) { + throw std::bad_alloc(); + } + + void deallocate(NonAllocatableAggregateFunction*, size_t /*count*/) {} + + // legacy allocator members + + template + void construct(NonAllocatableAggregateFunction*, Args&&...) {} + + void destroy(NonAllocatableAggregateFunction*) {} +}; + +struct NonDefaultCtorScalarFunction { + const int multiplier; + + NonDefaultCtorScalarFunction(int multiplier) : multiplier{multiplier} {} + + int operator()(int arg) const { + return multiplier * arg; + } + + static const char* name() { + return "CTORTEST1"; + } +}; + +struct NonDefaultCtorAggregateFunction { + int sum; + + NonDefaultCtorAggregateFunction(int initialValue) : sum{initialValue} {} + + void step(int arg) { + sum += arg; + } + int fin() const { + return sum; + } + + static const char* name() { + return "CTORTEST2"; + } +}; + +TEST_CASE("custom functions") { + using Catch::Matchers::ContainsSubstring; + + SqrtFunction::callsCount = 0; + StatelessHasPrefixFunction::callsCount = 0; + HasPrefixFunction::callsCount = 0; + FirstFunction::callsCount = 0; + + std::string path; + SECTION("in memory") { + path = {}; + } + SECTION("file") { + path = "custom_function.sqlite"; + ::remove(path.c_str()); + } + struct User { + int id = 0; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + User() = default; + User(int id) : id{id} {} +#endif + }; + auto storage = make_storage(path, make_table("users", make_column("id", &User::id))); + storage.sync_schema(); + + storage.create_aggregate_function(); + // test w/o a result set, i.e when the final aggregate call is the first to require the aggregate function + REQUIRE_NOTHROW(storage.select(func(&User::id))); + storage.delete_aggregate_function(); + + storage.create_aggregate_function(); + // test w/o a result set, i.e when the final aggregate call is the first to require the aggregate function + REQUIRE_THROWS_MATCHES(storage.select(func(&User::id)), + std::system_error, + ErrorCodeExceptionMatcher(sqlite_errc(SQLITE_NOMEM))); + storage.delete_aggregate_function(); + + // call before creation + REQUIRE_THROWS_WITH(storage.select(func(4)), ContainsSubstring("no such function")); + + // create function + REQUIRE(SqrtFunction::callsCount == 0); + + storage.create_scalar_function(); + + REQUIRE(SqrtFunction::callsCount == 0); + + // call after creation + { + auto rows = storage.select(func(4)); + REQUIRE(SqrtFunction::callsCount == 1); + decltype(rows) expected{2}; + REQUIRE(rows == expected); + } + + { + // create function + REQUIRE(StatelessHasPrefixFunction::callsCount == 0); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 0); + storage.create_scalar_function(); + REQUIRE(StatelessHasPrefixFunction::callsCount == 0); + // function object created + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + + // call after creation + { + auto rows = storage.select(func("one", "o")); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + REQUIRE(StatelessHasPrefixFunction::callsCount == 1); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + { + auto rows = storage.select(func("two", "b")); + decltype(rows) expected{false}; + REQUIRE(rows == expected); + } + REQUIRE(StatelessHasPrefixFunction::callsCount == 2); + REQUIRE(StatelessHasPrefixFunction::objectsCount == 1); + + // delete function + storage.delete_scalar_function(); + // function object destroyed + REQUIRE(StatelessHasPrefixFunction::objectsCount == 0); + } + + { + // create function + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + storage.create_scalar_function(); + REQUIRE(HasPrefixFunction::callsCount == 0); + REQUIRE(HasPrefixFunction::objectsCount == 0); + + // call after creation + { + auto rows = storage.select(func("one", "o")); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 1); + REQUIRE(HasPrefixFunction::objectsCount == 0); + { + auto rows = storage.select(func("two", "b")); + decltype(rows) expected{false}; + REQUIRE(rows == expected); + } + REQUIRE(HasPrefixFunction::callsCount == 2); + REQUIRE(HasPrefixFunction::objectsCount == 0); + + // delete function + storage.delete_scalar_function(); + } + + // delete function + storage.delete_scalar_function(); + + storage.create_aggregate_function(); + + storage.replace(User{1}); + storage.replace(User{2}); + storage.replace(User{3}); + REQUIRE(storage.count() == 3); + { + REQUIRE(MeanFunction::objectsCount == 0); + auto rows = storage.select(func(&User::id)); + REQUIRE(MeanFunction::objectsCount == 0); + decltype(rows) expected{2}; + REQUIRE(rows == expected); + } + storage.delete_aggregate_function(); + + storage.create_aggregate_function(); + // expect two different aggregate function objects to be created, which provide two different results; + // This ensures that `proxy_get_aggregate_step_udf()` uses `sqlite3_aggregate_context()` correctly + { + MeanFunction::ctorCallsCount = 0; + REQUIRE(MeanFunction::objectsCount == 0); + REQUIRE(MeanFunction::ctorCallsCount == 0); + auto rows = storage.select(columns(func(&User::id), func(c(&User::id) * 2))); + REQUIRE(MeanFunction::objectsCount == 0); + REQUIRE(MeanFunction::ctorCallsCount == 2); + REQUIRE(int(std::get<0>(rows[0])) == 2); + REQUIRE(int(std::get<1>(rows[0])) == 4); + } + storage.delete_aggregate_function(); + + storage.create_aggregate_function(); + REQUIRE_THROWS_MATCHES(storage.select(func(&User::id)), + std::system_error, + ErrorCodeExceptionMatcher(sqlite_errc(SQLITE_NOMEM))); + storage.delete_aggregate_function(); + + storage.create_scalar_function(); + { + auto rows = storage.select(func("Vanotek", "Tinashe", "Pitbull")); + decltype(rows) expected{"VTP"}; + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 1); + } + { + auto rows = storage.select(func("Charli XCX", "Rita Ora")); + decltype(rows) expected{"CR"}; + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 2); + } + { + auto rows = storage.select(func("Ted")); + decltype(rows) expected{"T"}; + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 3); + } + { + auto rows = storage.select(func()); + decltype(rows) expected{""}; + REQUIRE(rows == expected); + REQUIRE(FirstFunction::objectsCount == 0); + REQUIRE(FirstFunction::callsCount == 4); + } + storage.delete_scalar_function(); + + storage.create_aggregate_function(); + { + REQUIRE(MultiSum::objectsCount == 0); + auto rows = storage.select(func(&User::id, 5)); + decltype(rows) expected{21}; + REQUIRE(rows == expected); + REQUIRE(MultiSum::objectsCount == 0); + } + storage.delete_aggregate_function(); + +#if __cpp_aligned_new >= 201606L + { + storage.create_scalar_function(); + REQUIRE_NOTHROW(storage.delete_scalar_function()); + storage.create_aggregate_function(); + REQUIRE_NOTHROW(storage.delete_aggregate_function()); + } +#endif + + storage.create_scalar_function(42); + { + auto rows = storage.select(func(1)); + decltype(rows) expected{42}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + + storage.create_aggregate_function(42); + { + auto rows = storage.select(func(1)); + decltype(rows) expected{43}; + REQUIRE(rows == expected); + } + storage.delete_aggregate_function(); +} + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +inline int ERR_FATAL_ERROR(unsigned long errcode) { + return errcode != 0; +} + +struct noncopyable_scalar { + int operator()(int x) const noexcept { + return x; + } + + constexpr noncopyable_scalar() = default; + noncopyable_scalar(const noncopyable_scalar&) = delete; +}; + +struct stateful_scalar { + int offset; + + int operator()(int x) noexcept { + return offset += x; + } + + constexpr stateful_scalar(int offset = 0) : offset{offset} {} +}; +inline constexpr stateful_scalar offset0{}; + +TEST_CASE("generalized scalar udf") { + auto storage = make_storage(""); + storage.sync_schema(); + + SECTION("freestanding function") { + constexpr auto err_fatal_error_f = "ERR_FATAL_ERROR"_scalar.quote(ERR_FATAL_ERROR); + storage.create_scalar_function(); + { + auto rows = storage.select(err_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("stateless lambda") { + constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.quote([](unsigned long errcode) { + return errcode != 0; + }); + storage.create_scalar_function(); + { + auto rows = storage.select(is_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("function object instance") { + constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("explicit function object type") { + constexpr auto equal_to_int_f = "equal_to"_scalar.quote>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("'transparent' function object instance") { + constexpr auto equal_to_int_f = + "equal_to"_scalar.quote(std::equal_to{}); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("explicit 'transparent' function object type") { + constexpr auto equal_to_int_f = + "equal_to"_scalar.quote>(); + storage.create_scalar_function(); + { + auto rows = storage.select(equal_to_int_f(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("specialized template function") { + constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_int_f(0, 1, 1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("overloaded template function") { + constexpr auto clamp_int_f = + "clamp_int"_scalar.quote(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_int_f(0, 1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("non-copyable function object") { + constexpr auto idfunc_f = "idfunc"_scalar.quote(); + storage.create_scalar_function(); + { + auto rows = storage.select(idfunc_f(1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("stateful function object") { + constexpr auto offset0_f = "offset0"_scalar.quote(offset0); + storage.create_scalar_function(); + { + auto rows = storage.select(columns(offset0_f(1), offset0_f(1))); + decltype(rows) expected{{1, 1}}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } + SECTION("escaped function identifier") { + constexpr auto clamp_f = R"("clamp int")"_scalar.quote(std::clamp); + storage.create_scalar_function(); + { + auto rows = storage.select(clamp_f(0, 1, 1)); + decltype(rows) expected{1}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } +} +#endif diff --git a/tests/xdestroy_handling.cpp b/tests/xdestroy_handling.cpp new file mode 100644 index 000000000..7735ce06c --- /dev/null +++ b/tests/xdestroy_handling.cpp @@ -0,0 +1,147 @@ +#include +#include +#include // std::default_delete +#include // free() + +using namespace sqlite_orm; +using std::default_delete; +using std::unique_ptr; + +// Wrap std::default_delete in a function +#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION +template +void delete_default(mpl::conditional_t::value, std::decay_t, T*> o) noexcept( + noexcept(std::default_delete{}(o))) { + std::default_delete{}(o); +} + +// Integral function constant for default deletion +template +using delete_default_t = std::integral_constant), delete_default>; +// Integral function constant variable for default deletion +template +SQLITE_ORM_INLINE_VAR constexpr delete_default_t delete_default_f{}; +#endif + +using free_t = std::integral_constant; +SQLITE_ORM_INLINE_VAR constexpr free_t free_f{}; + +TEST_CASE("obtain_xdestroy_for") { + + using internal::xdestroy_proxy; + + // class yielding a 'xDestroy' function pointer + struct xdestroy_holder { + xdestroy_fn_t xDestroy = free; + + constexpr operator xdestroy_fn_t() const noexcept { + return xDestroy; + } + }; + + // class yielding a function pointer not of type xdestroy_fn_t + struct int_destroy_holder { + using destroy_fn_t = void (*)(int*); + + destroy_fn_t destroy = nullptr; + + constexpr operator destroy_fn_t() const noexcept { + return destroy; + } + }; + + { + constexpr int* int_nullptr = nullptr; +#if !defined(SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION) || \ + (__cpp_constexpr >= 201907L) // Trivial default initialization in constexpr functions + constexpr const int* const_int_nullptr = nullptr; +#endif + + // null_xdestroy_f(int*) + constexpr xdestroy_fn_t xDestroy1 = obtain_xdestroy_for(null_xdestroy_f, int_nullptr); + STATIC_REQUIRE(xDestroy1 == nullptr); + REQUIRE(xDestroy1 == nullptr); + + // free(int*) + constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); + STATIC_REQUIRE(xDestroy2 == &free); + REQUIRE(xDestroy2 == &free); + + // free_f(int*) + constexpr xdestroy_fn_t xDestroy3 = obtain_xdestroy_for(free_f, int_nullptr); + STATIC_REQUIRE(xDestroy3 == &free); + REQUIRE(xDestroy3 == &free); + +#if __cpp_constexpr >= 201603L // constexpr lambda + // [](void* p){} + constexpr auto lambda4_1 = [](void*) {}; + constexpr xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); + STATIC_REQUIRE(xDestroy4_1 == lambda4_1); + REQUIRE(xDestroy4_1 == lambda4_1); +#else +#if !defined(_MSC_VER) || (_MSC_VER >= 1914) // conversion of lambda closure to function pointer using `+` + // [](void* p){} + auto lambda4_1 = [](void*) {}; + xdestroy_fn_t xDestroy4_1 = obtain_xdestroy_for(lambda4_1, int_nullptr); + REQUIRE(xDestroy4_1 == lambda4_1); +#endif +#endif + + // [](int* p) { delete p; } +#if __cplusplus >= 202002L // default-constructible non-capturing lambdas + constexpr auto lambda4_2 = [](int* p) { + delete p; + }; + using lambda4_2_t = std::remove_const_t; + constexpr xdestroy_fn_t xDestroy4_2 = obtain_xdestroy_for(lambda4_2, int_nullptr); + STATIC_REQUIRE(xDestroy4_2 == &xdestroy_proxy); + REQUIRE((xDestroy4_2 == &xdestroy_proxy)); +#endif + + // default_delete(int*) + constexpr xdestroy_fn_t xDestroy5 = obtain_xdestroy_for(default_delete{}, int_nullptr); + STATIC_REQUIRE(xDestroy5 == &xdestroy_proxy, int>); + REQUIRE((xDestroy5 == &xdestroy_proxy, int>)); + +#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION + // delete_default_f(int*) + constexpr xdestroy_fn_t xDestroy6 = obtain_xdestroy_for(delete_default_f, int_nullptr); + STATIC_REQUIRE(xDestroy6 == &xdestroy_proxy, int>); + REQUIRE((xDestroy6 == &xdestroy_proxy, int>)); + + // delete_default_f(const int*) + constexpr xdestroy_fn_t xDestroy7 = obtain_xdestroy_for(delete_default_f, const_int_nullptr); + STATIC_REQUIRE(xDestroy7 == &xdestroy_proxy, const int>); + REQUIRE((xDestroy7 == &xdestroy_proxy, const int>)); +#endif + +#if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions + // xdestroy_holder{ free }(int*) + constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); + STATIC_REQUIRE(xDestroy8 == &free); + REQUIRE(xDestroy8 == &free); + + // xdestroy_holder{ free }(const int*) + constexpr xdestroy_fn_t xDestroy9 = obtain_xdestroy_for(xdestroy_holder{free}, const_int_nullptr); + STATIC_REQUIRE(xDestroy9 == &free); + REQUIRE(xDestroy9 == &free); + + // xdestroy_holder{ nullptr }(const int*) + constexpr xdestroy_fn_t xDestroy10 = obtain_xdestroy_for(xdestroy_holder{nullptr}, const_int_nullptr); + STATIC_REQUIRE(xDestroy10 == nullptr); + REQUIRE(xDestroy10 == nullptr); +#endif + + // expressions that do not work +#if 0 + // can't use functions that differ from xdestroy_fn_t + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(delete_default, int_nullptr); + // can't use object yielding a function pointer that differs from xdestroy_fn_t + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(int_destroy_holder{}, int_nullptr); + // successfully takes default_delete, but default_delete statically asserts on a non-complete type `void*` + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, int_nullptr); + // successfully takes default_delete, but xdestroy_proxy can't call the deleter with a `const int*` + constexpr xdestroy_fn_t xDestroy = obtain_xdestroy_for(default_delete{}, const_int_nullptr); +#endif + } +} diff --git a/third_party/amalgamate/config.json b/third_party/amalgamate/config.json index 11a7c2399..cdea2f87a 100755 --- a/third_party/amalgamate/config.json +++ b/third_party/amalgamate/config.json @@ -3,39 +3,33 @@ "target": "include/sqlite_orm/sqlite_orm.h", "sources": [ "dev/functional/start_macros.h", + "dev/functional/sqlite3_config.h", "dev/functional/config.h", "dev/type_traits.h", - "dev/error_code.h", - "dev/type_printer.h", "dev/collate_argument.h", "dev/constraints.h", "dev/type_is_nullable.h", "dev/operators.h", - "dev/column.h", - "dev/field_printer.h", + "dev/schema/column.h", "dev/conditions.h", "dev/alias.h", "dev/core_functions.h", - "dev/typed_comparator.h", "dev/select_constraints.h", "dev/table_info.h", - "dev/triggers.h", - "dev/statement_finalizer.h", - "dev/arithmetic_tag.h", - "dev/pointer_value.h", + "dev/schema/triggers.h", "dev/statement_binder.h", "dev/row_extractor.h", - "dev/util.h", "dev/sync_schema_result.h", - "dev/index.h", + "dev/schema/index.h", "dev/rowid.h", - "dev/table.h", + "dev/schema/table.h", "dev/storage_impl.h", "dev/default_value_extractor.h", "dev/storage.h", "dev/get_prepared_statement.h", "dev/carray.h", - "dev/dbstat.h", + "dev/sqlite_schema_table.h", + "dev/eponymous_vtabs/dbstat.h", "dev/interface_definitions.h", "dev/functional/finish_macros.h" ],