diff --git a/appveyor.yml b/appveyor.yml index eef5c54af..a59eb6e69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,7 +107,7 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2024.01.12 + git fetch --tags && git checkout 2024.03.25 cd %APPVEYOR_BUILD_FOLDER% C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install @@ -140,7 +140,7 @@ for: install: - |- pushd $HOME/vcpkg - git fetch --tags && git checkout 2024.01.12 + git fetch --tags && git checkout 2024.03.25 popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets @@ -168,7 +168,7 @@ for: # using custom vcpkg triplets for building and linking dynamic dependent libraries install: - |- - git clone --depth 1 --branch 2024.01.12 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2024.03.25 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[core,dbstat,math,json1,fts5,soundex] catch2 --overlay-triplets=vcpkg/triplets diff --git a/dev/alias.h b/dev/alias.h index d9ab7adfa..18b87019c 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -4,6 +4,9 @@ #include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream +#ifdef SQLITE_ORM_WITH_CTE +#include +#endif #include "functional/cxx_type_traits_polyfill.h" #include "functional/cstring_literal.h" @@ -11,6 +14,7 @@ #include "alias_traits.h" #include "table_type_of.h" #include "tags.h" +#include "column_pointer.h" namespace sqlite_orm { @@ -78,8 +82,8 @@ namespace sqlite_orm { /* * 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, and the alias identifier is returned. + * `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 @@ -96,6 +100,14 @@ namespace sqlite_orm { return alias_extractor::extract(); } +#ifdef 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&) { @@ -131,6 +143,8 @@ 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 @@ -144,8 +158,31 @@ namespace sqlite_orm { [[nodiscard]] consteval recordset_alias for_() const { return {}; } + + template + [[nodiscard]] consteval auto for_() const { + using T = std::remove_const_t; + return recordset_alias{}; + } }; #endif + +#ifdef 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 } /** @@ -155,7 +192,12 @@ namespace sqlite_orm { * using als = alias_u; * select(alias_column(column(&User::id))) */ - template::value, bool> = true> + template, + polyfill::negation>>>::value, + bool> = true> constexpr auto alias_column(C field) { using namespace ::sqlite_orm::internal; using aliased_type = type_t; @@ -173,7 +215,13 @@ namespace sqlite_orm { * using als = alias_u; * select(alias_column(&User::id)) */ - template::value, bool> = true> + template, + polyfill::negation>>>::value, + bool> = true> constexpr auto alias_column(F O::*field) { using namespace ::sqlite_orm::internal; using aliased_type = type_t; @@ -195,6 +243,7 @@ namespace sqlite_orm { * 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); @@ -225,11 +274,60 @@ namespace sqlite_orm { * select(als->*&User::id) */ template + requires(!orm_cte_moniker>) constexpr auto operator->*(const A& /*tableAlias*/, F field) { return alias_column(std::move(field)); } #endif +#ifdef 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)); + } + + /** + * Create a column reference to an aliased CTE column. + */ + 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. */ @@ -247,17 +345,29 @@ namespace sqlite_orm { 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 - template = true> - internal::alias_holder get() { + /** + * Wrap a column alias in an alias holder. + */ + template + internal::alias_holder get() { + static_assert(internal::is_column_alias_v, ""); return {}; } @@ -362,4 +472,18 @@ namespace sqlite_orm { } } #endif + +#ifdef SQLITE_ORM_WITH_CTE + /** + * 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 bf0aa64eb..6455bce53 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -44,9 +44,9 @@ namespace sqlite_orm { /** @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> {}; @@ -68,6 +68,20 @@ namespace sqlite_orm { template using decay_table_reference_t = typename decay_table_reference::type; #endif + + /** @short Moniker of a CTE, see `orm_cte_moniker`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_cte_moniker_v = +#ifdef 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 @@ -109,6 +123,15 @@ namespace sqlite_orm { template concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @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 @@ -122,6 +145,6 @@ namespace sqlite_orm { /** @short Specifies that a type is a mapped recordset (table reference). */ template - concept orm_mapped_recordset = (orm_table_reference); + concept orm_mapped_recordset = (orm_table_reference || orm_cte_moniker); #endif } diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index bcaf74dde..6aa5b2f9b 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -226,6 +226,29 @@ namespace sqlite_orm { } }; +#ifdef 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; diff --git a/dev/column_expression.h b/dev/column_expression.h new file mode 100644 index 000000000..d8ce2c64d --- /dev/null +++ b/dev/column_expression.h @@ -0,0 +1,99 @@ +#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 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 18e443a52..3b95f0018 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -22,7 +22,7 @@ namespace sqlite_orm { namespace internal { template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); template std::vector& collect_table_column_names(std::vector& collectedExpressions, diff --git a/dev/column_pointer.h b/dev/column_pointer.h index 0cd3418b5..19d5df6e9 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -3,9 +3,11 @@ #include // std::enable_if #include // std::move +#include "functional/cxx_core_features.h" #include "functional/cxx_type_traits_polyfill.h" -#include "tags.h" +#include "type_traits.h" #include "alias_traits.h" +#include "tags.h" namespace sqlite_orm { namespace internal { @@ -31,10 +33,19 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_operator_argument_v::value>> = true; + +#ifdef SQLITE_ORM_WITH_CTE + template + struct alias_holder; +#endif } /** - * Use it like this: + * 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)); */ @@ -66,7 +77,7 @@ namespace sqlite_orm { } /** - * Make table reference. + * Make a table reference. */ template requires(!orm_recordset_alias) @@ -75,7 +86,7 @@ namespace sqlite_orm { } /** - * Make table reference. + * Make a table reference. */ template requires(!orm_recordset_alias) @@ -83,4 +94,68 @@ namespace sqlite_orm { return {}; } #endif -} + +#ifdef 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 df6d383d0..63d2cab5a 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -19,6 +19,7 @@ #include "operators.h" #include "rowid.h" #include "alias.h" +#include "cte_types.h" #include "storage_traits.h" #include "function.h" #include "ast/special_keywords.h" @@ -226,6 +227,21 @@ namespace sqlite_orm { template struct column_result_t, void> : column_result_t {}; +#ifdef 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> : conc_tuple>>...> {}; diff --git a/dev/core_functions.h b/dev/core_functions.h index 2d9333471..26c8a1b1f 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -1838,7 +1838,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 diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h new file mode 100644 index 000000000..71a2fe58a --- /dev/null +++ b/dev/cte_column_names_collector.h @@ -0,0 +1,204 @@ +#pragma once + +#ifdef 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" + +#ifdef 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."); + }; + + 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..24519fdbb --- /dev/null +++ b/dev/cte_moniker.h @@ -0,0 +1,89 @@ +#pragma once + +#ifdef 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" + +#ifdef 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..60efc82e0 --- /dev/null +++ b/dev/cte_storage.h @@ -0,0 +1,334 @@ +#pragma once + +#ifdef 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 { + +#ifdef 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 + + /** + * Return passed in DBOs. + */ + template = true> + decltype(auto) db_objects_for_expression(DBOs& dbObjects, const E&) { + return dbObjects; + } + } +} diff --git a/dev/cte_types.h b/dev/cte_types.h new file mode 100644 index 000000000..e8deb50d5 --- /dev/null +++ b/dev/cte_types.h @@ -0,0 +1,61 @@ +#pragma once + +#ifdef 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" + +#ifdef 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 071a20387..01002cdc4 100644 --- a/dev/default_value_extractor.h +++ b/dev/default_value_extractor.h @@ -11,7 +11,7 @@ namespace sqlite_orm { namespace internal { template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); /** * Serialize default value of a column's default valu diff --git a/dev/functional/config.h b/dev/functional/config.h index bdfe8ca58..5a3e95b6e 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -59,3 +59,7 @@ (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 diff --git a/dev/functional/index_sequence_util.h b/dev/functional/index_sequence_util.h index 334a33e99..37f244fa5 100644 --- a/dev/functional/index_sequence_util.h +++ b/dev/functional/index_sequence_util.h @@ -14,6 +14,25 @@ namespace sqlite_orm { return I; } +#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED + /** + * Get the value of an index_sequence at a specific position. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value(size_t pos, std::index_sequence) { + static_assert(sizeof...(Idx) > 0); +#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; + } +#endif + template struct flatten_idxseq { using type = std::index_sequence<>; diff --git a/dev/node_tuple.h b/dev/node_tuple.h index 9f32fe566..428a0f92f 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -122,6 +122,16 @@ namespace sqlite_orm { template struct node_tuple> : node_tuple {}; +#ifdef SQLITE_ORM_WITH_CTE + template + struct node_tuple> + : node_tuple {}; + + template + struct node_tuple> + : node_tuple_for {}; +#endif + template struct node_tuple, void> : node_tuple_for {}; diff --git a/dev/schema/column.h b/dev/schema/column.h index 24da50427..3f9f7a719 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -104,6 +104,19 @@ 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::value; diff --git a/dev/schema/table.h b/dev/schema/table.h index 9889e6511..b71a7c083 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -28,6 +28,21 @@ namespace sqlite_orm { namespace internal { +#ifdef 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 { /** @@ -41,7 +56,15 @@ namespace sqlite_orm { */ template struct table_t : basic_table { +#ifdef 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; diff --git a/dev/select_constraints.h b/dev/select_constraints.h index d92938daf..d59e00990 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -1,5 +1,8 @@ #pragma once +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif #include // std::remove_const #include // std::string #include // std::move @@ -10,12 +13,14 @@ #include "functional/cxx_type_traits_polyfill.h" #include "is_base_of_template.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 "cte_moniker.h" namespace sqlite_orm { @@ -187,6 +192,110 @@ namespace sqlite_orm { using super::super; }; +#ifdef 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. */ @@ -377,6 +486,219 @@ namespace sqlite_orm { return {{std::forward(expressions)...}}; } +#ifdef SQLITE_ORM_WITH_CTE +#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 + + /** + * 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 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 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 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 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. diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 9065c3232..9734965a1 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -21,7 +21,7 @@ namespace sqlite_orm { struct order_by_t; template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); template std::string serialize_order_by(const T&, const Ctx&); @@ -103,6 +103,7 @@ namespace sqlite_orm { field_values_excluding, mapped_columns_expressions, column_constraints, + constraints_tuple, }; template @@ -130,6 +131,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; @@ -378,6 +380,22 @@ 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&, @@ -388,22 +406,18 @@ namespace sqlite_orm { const bool& isNotNull = std::get<2>(tpl); auto& context = std::get<3>(tpl); - auto first = true; - constexpr std::array sep = {" ", ""}; - iterate_tuple(column.constraints, [&ss, &context, &first, &sep](auto& constraint) { - ss << sep[std::exchange(first, false)]; - ss << serialize(constraint, context); + iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { + ss << ' ' << serialize(constraint, context); }); using constraints_tuple = decltype(column.constraints); constexpr bool hasExplicitNullableConstraint = mpl::invoke_t, check_if_has_type>, constraints_tuple>::value; if(!hasExplicitNullableConstraint) { - ss << sep[std::exchange(first, false)]; if(isNotNull) { - ss << "NOT NULL"; + ss << " NOT NULL"; } else { - ss << "NULL"; + ss << " NULL"; } } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index e92083957..dc37c1747 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -39,8 +39,10 @@ #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 "schema/triggers.h" @@ -57,7 +59,7 @@ namespace sqlite_orm { struct statement_serializer; template - std::string serialize(const T& t, const C& context) { + auto serialize(const T& t, const C& context) { statement_serializer serializer; return serializer(t, context); } @@ -147,7 +149,7 @@ namespace sqlite_orm { } template - std::string serialize(const statement_type& statement, const Ctx& context, const std::string& tableName) { + 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) << ")"; @@ -425,7 +427,7 @@ namespace sqlite_orm { using statement_type = rank_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx&) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx&) const { return "rank"; } }; @@ -582,6 +584,75 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CTE +#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 + + 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; @@ -880,7 +951,7 @@ namespace sqlite_orm { 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"; @@ -912,7 +983,7 @@ namespace sqlite_orm { using statement_type = null_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { return "NULL"; } }; @@ -922,7 +993,7 @@ namespace sqlite_orm { using statement_type = not_null_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { return "NOT NULL"; } }; @@ -1071,11 +1142,10 @@ namespace sqlite_orm { ss << streaming_identifier(column.name); if(!context.skip_types_and_constraints) { ss << " " << type_printer>().print(); - ss << " " - << streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - column.is_not_null(), - context); + ss << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + column.is_not_null(), + context); } return ss.str(); } @@ -1561,7 +1631,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"; @@ -1584,7 +1654,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(); } }; @@ -1819,7 +1892,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"; @@ -1837,7 +1910,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"; @@ -2088,7 +2161,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 ea5b4385e..cdcc3b63f 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -52,6 +52,7 @@ #include "schema/table.h" #include "schema/column.h" #include "schema/index.h" +#include "cte_storage.h" #include "util.h" #include "serializing_util.h" @@ -91,6 +92,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; @@ -651,6 +654,44 @@ namespace sqlite_orm { return this->execute(statement); } +#ifdef SQLITE_ORM_WITH_CTE + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with(CTE cte, E expression) { + auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with(common_table_expressions cte, E expression) { + auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with_recursive(CTE cte, E expression) { + auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with_recursive(common_table_expressions cte, E expression) { + auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); + return this->execute(statement); + } +#endif + template = true> std::string dump(const T& preparedStatement, bool parametrized = true) const { return this->dump(preparedStatement.expression, parametrized); @@ -671,8 +712,9 @@ namespace sqlite_orm { [](const auto& expression) -> decltype(auto) { return (expression); })(std::forward(expression)); - using context_t = serializer_context; - context_t context{this->db_objects}; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, expression); + using context_t = serializer_context>; + context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() context.skip_table_name = false; @@ -1032,8 +1074,9 @@ namespace sqlite_orm { template prepared_statement_t prepare_impl(S statement) { - using context_t = serializer_context; - context_t context{this->db_objects}; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, statement); + using context_t = serializer_context>; + context_t context{exprDBOs}; context.skip_table_name = false; context.replace_bindable_with_question = true; @@ -1098,6 +1141,15 @@ namespace sqlite_orm { using storage_base::table_exists; // now that it is in storage_base make it into overload set +#ifdef SQLITE_ORM_WITH_CTE + template, is_insert_raw>, bool> = true> + prepared_statement_t> prepare(with_t sel) { + return prepare_impl>(std::move(sel)); + } +#endif + template prepared_statement_t> prepare(select_t statement) { statement.highest_level = true; @@ -1214,14 +1266,23 @@ namespace sqlite_orm { template void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression.args, conditional_binder{statement.stmt}); + iterate_ast(statement.expression, conditional_binder{stmt}); + perform_step(stmt); + } + +#ifdef SQLITE_ORM_WITH_CTE + template = true> + void execute(const prepared_statement_t>& statement) { + sqlite3_stmt* stmt = reset_stmt(statement.stmt); + iterate_ast(statement.expression, conditional_binder{stmt}); perform_step(stmt); } +#endif template void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression.args, conditional_binder{stmt}); + iterate_ast(statement.expression, conditional_binder{stmt}); perform_step(stmt); } @@ -1439,11 +1500,8 @@ namespace sqlite_orm { perform_step(stmt); } - template, - satisfies_not = true> - std::vector execute(const prepared_statement_t>& statement) { + template = true> + std::vector _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); @@ -1458,11 +1516,8 @@ namespace sqlite_orm { return res; } - template, - satisfies = true> - std::vector execute(const prepared_statement_t>& statement) { + template = true> + std::vector _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); @@ -1478,6 +1533,23 @@ namespace sqlite_orm { return res; } +#ifdef SQLITE_ORM_WITH_CTE + template + auto execute(const prepared_statement_t, CTEs...>>& statement) { + using ExprDBOs = + decltype(db_objects_for_expression(this->db_objects, + std::declval, CTEs...>>())); + using R = column_result_of_t; + return _execute_select(statement); + } +#endif + + template + auto execute(const prepared_statement_t>& statement) { + using R = column_result_of_t; + return _execute_select(statement); + } + template> R execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 5325f5253..ba432ce75 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -4,11 +4,13 @@ #include "functional/cxx_universal.h" // ::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" #include "type_traits.h" #include "select_constraints.h" +#include "cte_types.h" #include "storage_lookup.h" // interface functions @@ -47,20 +49,75 @@ 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; } +#ifdef 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); } + +#ifdef 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(colalias_index::value, column_index_sequence{}); + auto& table = pick_table(dboObjects); + return &std::get(table.elements).name; + } +#endif } } diff --git a/dev/storage_lookup.h b/dev/storage_lookup.h index 3dbe90e5e..1e943ea6b 100644 --- a/dev/storage_lookup.h +++ b/dev/storage_lookup.h @@ -4,7 +4,7 @@ #include #include // std::index_sequence, std::make_index_sequence -#include "functional/cxx_universal.h" +#include "functional/cxx_universal.h" // ::size_t #include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" @@ -17,6 +17,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 {}; @@ -34,7 +38,7 @@ namespace sqlite_orm { 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. */ @@ -43,10 +47,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 @@ -91,7 +95,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; diff --git a/dev/storage_traits.h b/dev/storage_traits.h index ef776c0ed..4fc1f33ed 100644 --- a/dev/storage_traits.h +++ b/dev/storage_traits.h @@ -7,9 +7,9 @@ #include "tuple_helper/tuple_transformer.h" #include "type_traits.h" #include "storage_lookup.h" +#include "schema/column.h" namespace sqlite_orm { - namespace internal { namespace storage_traits { @@ -32,6 +32,26 @@ 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> {}; } } } diff --git a/dev/table_name_collector.h b/dev/table_name_collector.h index a85dc51dc..47429675f 100644 --- a/dev/table_name_collector.h +++ b/dev/table_name_collector.h @@ -42,7 +42,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 diff --git a/dev/tuple_helper/tuple_iteration.h b/dev/tuple_helper/tuple_iteration.h index 9fd10015a..afea35007 100644 --- a/dev/tuple_helper/tuple_iteration.h +++ b/dev/tuple_helper/tuple_iteration.h @@ -14,7 +14,8 @@ namespace sqlite_orm { 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); + // note: `(void)` cast silences warning 'expression result unused' + (void)((lambda(std::get(tpl)), sink) = ... = 0); } else { (lambda(std::get(tpl)), ...); } diff --git a/dev/type_traits.h b/dev/type_traits.h index 98e364649..7754d172b 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -7,6 +7,7 @@ #include // std::reference_wrapper #endif +#include "functional/cxx_core_features.h" #include "functional/cxx_type_traits_polyfill.h" namespace sqlite_orm { @@ -60,6 +61,9 @@ namespace sqlite_orm { 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; @@ -75,6 +79,9 @@ namespace sqlite_orm { 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; @@ -90,6 +97,9 @@ namespace sqlite_orm { template using expression_type_t = typename T::expression_type; + template + using alias_type_t = typename As::alias_type; + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template using udf_type_t = typename T::udf_type; @@ -98,6 +108,21 @@ namespace sqlite_orm { using auto_udf_type_t = typename decltype(a)::udf_type; #endif +#ifdef 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; + + // T::alias_type or nonesuch + template + using alias_holder_type_or_none = polyfill::detected; + + 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; diff --git a/examples/common_table_expressions.cpp b/examples/common_table_expressions.cpp new file mode 100644 index 000000000..1410980cc --- /dev/null +++ b/examples/common_table_expressions.cpp @@ -0,0 +1,1079 @@ +/** + * Examples partially from https://sqlite.org/lang_with.html + */ + +#include +#ifdef SQLITE_ORM_WITH_CTE +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED +#include +#endif +#include +#include +#include +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif + +using std::cout; +using std::endl; +using std::make_tuple; +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED +using std::nullopt; +#endif +using std::string; +using std::system_error; +using namespace sqlite_orm; + +void all_integers_between(int from, int end) { + auto storage = make_storage(""); + // variant 1, where-clause, implicitly numbered column + { + //WITH RECURSIVE + // cnt(x) AS(VALUES(1) UNION ALL SELECT x + 1 FROM cnt WHERE x < 1000000) + // SELECT x FROM cnt; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cnt = "cnt"_cte; + auto ast = with_recursive( + cnt().as(union_all(select(from), select(cnt->*1_colalias + 1, where(cnt->*1_colalias < end)))), + select(cnt->*1_colalias)); +#else + using cnt = decltype(1_ctealias); + auto ast = with_recursive( + cte().as( + union_all(select(from), select(column(1_colalias) + 1, where(column(1_colalias) < end)))), + select(column(1_colalias))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + cout << "Integer range (where-clause) [" << from << ", " << end + << "]" + "\n"; + for(int n: storage.execute(stmt)) { + cout << n << ", "; + } + cout << endl; + } + + // variant 2, limit-clause, implicit column + { + //WITH RECURSIVE + // cnt(x) AS( + // SELECT 1 + // UNION ALL + // SELECT x + 1 FROM cnt + // LIMIT 1000000 + // ) + // SELECT x FROM cnt; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cnt = "cnt"_cte; + constexpr auto x = "x"_col; + auto ast = + with_recursive(cnt().as(union_all(select(from >>= x), select(cnt->*x + 1, limit(end)))), select(cnt->*x)); +#else + using cnt = decltype(1_ctealias); + constexpr auto x = colalias_i{}; + auto ast = with_recursive(cte().as(union_all(select(from >>= x), select(column(x) + 1, limit(end)))), + select(column(x))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + cout << "Integer range (limit-clause) [" << from << ", " << end + << "]" + "\n"; + for(int n: storage.execute(stmt)) { + cout << n << ", "; + } + cout << endl; + } + + // variant 3, limit-clause, explicit column spec + { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto cnt = "cnt"_cte; + constexpr auto x = "x"_col; + auto ast = with(cnt(x).as(union_all(select(from), select(cnt->*x + 1, limit(end)))), select(cnt->*x)); +#else + using cnt = decltype(1_ctealias); + constexpr auto x = colalias_i{}; + auto ast = with(cte(x).as(union_all(select(from), select(column(x) + 1, limit(end)))), + select(column(x))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + cout << "Integer range (limit-clause) [" << from << ", " << end + << "]" + "\n"; + for(int n: storage.execute(stmt)) { + cout << n << ", "; + } + cout << endl; + } + + cout << endl; +} + +void supervisor_chain() { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + //CREATE TABLE org( + // name TEXT PRIMARY KEY, + // boss TEXT REFERENCES org + //) WITHOUT ROWID; + //INSERT INTO org VALUES('Alice', NULL); + //INSERT INTO org VALUES('Bob', 'Alice'); + //INSERT INTO org VALUES('Cindy', 'Alice'); + //INSERT INTO org VALUES('Dave', 'Bob'); + //INSERT INTO org VALUES('Emma', 'Bob'); + //INSERT INTO org VALUES('Fred', 'Cindy'); + //INSERT INTO org VALUES('Gail', 'Cindy'); + struct Org { + std::string name; + std::optional boss; + }; + + auto storage = make_storage("", + make_table("org", + make_column("name", &Org::name, primary_key()), + make_column("boss", &Org::boss), + foreign_key(&Org::boss).references(&Org::name))); + storage.sync_schema(); + + storage.replace({"Alice", nullopt}); + storage.replace({"Bob", "Alice"}); + storage.replace({"Cindy", "Alice"}); + storage.replace({"Dave", "Bob"}); + storage.replace({"Emma", "Bob"}); + storage.replace({"Fred", "Cindy"}); + storage.replace({"Gail", "Cindy"}); + + // supervisor chain of Fred + { + //WITH RECURSIVE + // chain AS( + // SELECT * from org WHERE name = 'Fred' + // UNION ALL + // SELECT parent.* FROM org parent, chain + // WHERE parent.name = chain.boss + // ) + // SELECT name FROM chain; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto chain = "chain"_cte; + constexpr auto parent = "parent"_alias.for_(); + auto ast = with_recursive( + chain().as(union_all(select(asterisk(), where(&Org::name == c("Fred"))), + select(asterisk(), where(parent->*&Org::name == chain->*&Org::boss)))), + select(chain->*&Org::name)); +#else + using chain = decltype(1_ctealias); + auto ast = with_recursive(cte().as(union_all(select(asterisk(), where(&Org::name == c("Fred"))), + select(asterisk>(), + where(alias_column>(&Org::name) == + column(&Org::boss))))), + select(column(&Org::name))); +#endif + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + cout << "Hierarchy chain of Fred:\n"; + for(const string& name: results) { + cout << name << ", "; + } + cout << endl; + } + cout << endl; +#endif +} + +void works_for_alice() { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + //CREATE TABLE org( + // name TEXT PRIMARY KEY, + // boss TEXT REFERENCES org, + // height INT, + // -- other content omitted + //); + struct Org { + std::string name; + std::optional boss; + double height; + }; + + auto storage = make_storage("", + make_table("org", + make_column("name", &Org::name, primary_key()), + make_column("boss", &Org::boss), + make_column("height", &Org::height), + foreign_key(&Org::boss).references(&Org::name))); + storage.sync_schema(); + + storage.replace({"Alice", nullopt, 160.}); + storage.replace({"Bob", nullopt, 177.}); + storage.replace({"Dave", "Alice", 169.}); + storage.replace({"Cindy", "Dave", 165.}); + storage.replace({"Bar", "Bob", 159.}); + + // average height of Alice's team + { + //WITH RECURSIVE + // works_for_alice(n) AS( + // VALUES('Alice') + // UNION + // SELECT name FROM org, works_for_alice + // WHERE org.boss = works_for_alice.n + // ) + // SELECT avg(height) FROM org + // WHERE org.name IN works_for_alice; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto works_for_alice = "works_for_alice"_cte; + auto ast = with_recursive( + works_for_alice(&Org::name) + .as(union_(select("Alice"), select(&Org::name, where(&Org::boss == works_for_alice->*&Org::name)))), + select(avg(&Org::height), from(), where(in(&Org::name, select(works_for_alice->*&Org::name))))); +#else + using works_for_alice = decltype(1_ctealias); + auto ast = with_recursive( + cte(&Org::name) + .as(union_(select("Alice"), + select(&Org::name, where(&Org::boss == column(&Org::name))))), + select(avg(&Org::height), from(), where(in(&Org::name, select(column(&Org::name)))))); +#endif + + //WITH cte_1("n") + // AS( + // SELECT 'Alice' + // UNION + // SELECT "org"."name" FROM 'cte_1', 'org' + // WHERE("org"."boss" = 'cte_1'."n") + // ) + // SELECT AVG("org"."height") + // FROM 'org' + // WHERE( + // "name" IN( + // SELECT 'cte_1'."n" FROM 'cte_1' + // ) + // ) + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + cout << "Average height of Alice's team: " << results.at(0) << endl; + } + cout << endl; +#endif +} + +void family_tree() { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + //CREATE TABLE family( + // name TEXT PRIMARY KEY, + // mom TEXT REFERENCES family, + // dad TEXT REFERENCES family, + // born DATETIME, + // died DATETIME, --NULL if still alive + // -- other content + //); + struct Family { + std::string name; + std::optional mom; + std::optional dad; + time_t born; + std::optional died; + }; + + auto storage = make_storage("", + make_table("family", + make_column("name", &Family::name, primary_key()), + make_column("mom", &Family::mom), + make_column("dad", &Family::dad), + make_column("born", &Family::born), + make_column("died", &Family::died), + foreign_key(&Family::mom).references(&Family::name), + foreign_key(&Family::dad).references(&Family::name))); + storage.sync_schema(); + + storage.replace({"Grandma (Mom)", nullopt, nullopt, 0, nullopt}); + storage.replace({"Granddad (Mom)", nullopt, nullopt, 1, 1}); + storage.replace({"Grandma (Dad)", nullopt, nullopt, 0, 0}); + storage.replace({"Granddad (Dad)", nullopt, nullopt, 1, nullopt}); + storage.replace({"Mom", "Grandma (Mom)", "Granddad (Mom)", 2, nullopt}); + storage.replace({"Dad", "Grandma (Dad)", "Granddad (Dad)", 3, nullopt}); + storage.replace({"Alice", "Mom", "Dad", 4, nullopt}); + + //WITH RECURSIVE + // parent_of(name, parent) AS + // (SELECT name, mom FROM family UNION SELECT name, dad FROM family), + // ancestor_of_alice(name) AS + // (SELECT parent FROM parent_of WHERE name = 'Alice' + // UNION ALL + // SELECT parent FROM parent_of JOIN ancestor_of_alice USING(name)) + // SELECT family.name FROM ancestor_of_alice, family + // WHERE ancestor_of_alice.name = family.name + // AND died IS NULL + // ORDER BY born; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto parent_of = "parent_of"_cte; + constexpr auto ancestor_of_alice = "ancestor_of_alice"_cte; + constexpr auto parent = "parent"_col; + constexpr auto name = "name"_col; + auto ast = with_recursive( + make_tuple( + parent_of(&Family::name, parent) + .as(union_(select(columns(&Family::name, &Family::mom)), select(columns(&Family::name, &Family::dad)))), + ancestor_of_alice(name).as( + union_all(select(parent_of->*parent, where(parent_of->*&Family::name == "Alice")), + select(parent_of->*parent, join(using_(parent_of->*&Family::name)))))), + select(&Family::name, + where(ancestor_of_alice->*name == &Family::name && is_null(&Family::died)), + order_by(&Family::born))); +#else + using parent_of = decltype(1_ctealias); + using ancestor_of_alice = decltype(2_ctealias); + constexpr auto parent = colalias_h{}; + constexpr auto name = colalias_f{}; + auto ast = with_recursive( + make_tuple( + cte("name", "parent") + .as(union_(select(columns(&Family::name, &Family::mom >>= parent)), + select(columns(&Family::name, &Family::dad)))), + cte("name").as(union_all( + select(column(parent) >>= name, where(column(&Family::name) == "Alice")), + select(column(parent), join(using_(column(&Family::name))))))), + select(&Family::name, + where(column(name) == &Family::name && is_null(&Family::died)), + order_by(&Family::born))); +#endif + + //WITH cte_1("name", "parent") + // AS( + // SELECT "family"."name", "family"."mom" + // FROM 'family' + // UNION + // SELECT "family"."name", "family"."dad" + // FROM 'family' + // ), + // cte_2("name") + // AS( + // SELECT 'cte_1'."parent" + // FROM 'cte_1' + // WHERE('cte_1'."name" = 'Alice') + // UNION ALL + // SELECT 'cte_1'."name" + // FROM 'cte_1' + // JOIN 'cte_2' USING("name") + // ) + // SELECT "family"."name" + // FROM 'cte_2', + // 'family' + // WHERE( + // ( + // ('cte_2'."name" = 'family'."name") + // AND "family"."died" IS NULL + // ) + // ) + // ORDER BY "family"."born" + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + + cout << "Living ancestor's of Alice:\n"; + for(const string& name: results) { + cout << name << '\n'; + } + cout << endl; +#endif +} + +void depth_or_breadth_first() { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + //CREATE TABLE org( + // name TEXT PRIMARY KEY, + // boss TEXT REFERENCES org + //) WITHOUT ROWID; + //INSERT INTO org VALUES('Alice', NULL); + //INSERT INTO org VALUES('Bob', 'Alice'); + //INSERT INTO org VALUES('Cindy', 'Alice'); + //INSERT INTO org VALUES('Dave', 'Bob'); + //INSERT INTO org VALUES('Emma', 'Bob'); + //INSERT INTO org VALUES('Fred', 'Cindy'); + //INSERT INTO org VALUES('Gail', 'Cindy'); + //CREATE TABLE org( + // name TEXT PRIMARY KEY, + // boss TEXT REFERENCES org, + // height INT, + // -- other content omitted + //); + struct Org { + std::string name; + std::optional boss; + double height; + }; + + auto storage = make_storage("", + make_table("org", + make_column("name", &Org::name, primary_key()), + make_column("boss", &Org::boss), + foreign_key(&Org::boss).references(&Org::name))); + storage.sync_schema(); + + storage.replace({"Alice", nullopt}); + storage.replace({"Bob", "Alice"}); + storage.replace({"Cindy", "Alice"}); + storage.replace({"Dave", "Bob"}); + storage.replace({"Emma", "Bob"}); + storage.replace({"Fred", "Cindy"}); + storage.replace({"Gail", "Cindy"}); + + // breadth-first pattern + { + //WITH RECURSIVE + // under_alice(name, level) AS( + // VALUES('Alice', 0) + // UNION ALL + // SELECT org.name, under_alice.level + 1 + // FROM org JOIN under_alice ON org.boss = under_alice.name + // ORDER BY 2 + // ) + // SELECT substr('..........', 1, level * 3) || name FROM under_alice; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto under_alice = "under_alice"_cte; + constexpr auto level = "level"_col; + auto ast = + with_recursive(under_alice(&Org::name, level) + .as(union_all(select(columns("Alice", 0)), + select(columns(&Org::name, under_alice->*level + 1), + join(on(under_alice->*&Org::name == &Org::boss)), + order_by(2)))), + select(substr("..........", 1, under_alice->*level * 3) || under_alice->*&Org::name)); +#else + using under_alice = decltype(1_ctealias); + auto ast = with_recursive( + cte("name", "level") + .as(union_all(select(columns("Alice", 0)), + select(columns(&Org::name, column(2_colalias) + 1), + join(on(column(1_colalias) == &Org::boss)), + order_by(2)))), + select(substr("..........", 1, column(2_colalias) * 3) || column(1_colalias))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + + cout << "List of organization members, breadth-first:\n"; + for(const string& name: results) { + cout << name << '\n'; + } + } + cout << endl; + + // depth-first pattern + { + //WITH RECURSIVE + // under_alice(name, level) AS( + // VALUES('Alice', 0) + // UNION ALL + // SELECT org.name, under_alice.level + 1 + // FROM org JOIN under_alice ON org.boss = under_alice.name + // ORDER BY 2 + // ) + // SELECT substr('..........', 1, level * 3) || name FROM under_alice; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto under_alice = "under_alice"_cte; + constexpr auto level = "level"_col; + auto ast = + with_recursive(under_alice(&Org::name, level) + .as(union_all(select(columns("Alice", 0)), + select(columns(&Org::name, under_alice->*level + 1), + join(on(under_alice->*&Org::name == &Org::boss)), + order_by(2).desc()))), + select(substr("..........", 1, under_alice->*level * 3) || under_alice->*&Org::name)); +#else + using under_alice = decltype(1_ctealias); + auto ast = with_recursive( + cte("name", "level") + .as(union_all(select(columns("Alice", 0)), + select(columns(&Org::name, column(2_colalias) + 1), + join(on(column(1_colalias) == &Org::boss)), + order_by(2).desc()))), + select(substr("..........", 1, column(2_colalias) * 3) || column(1_colalias))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + + cout << "List of organization members, depth-first:\n"; + for(const string& name: results) { + cout << name << '\n'; + } + } + cout << endl; +#endif +} + +void select_from_subselect() { + struct Employee { + int m_empno; + std::string m_ename; + double m_salary; + std::optional m_commission; + }; + + auto storage = make_storage("", + make_table("Emp", + make_column("empno", &Employee::m_empno, primary_key().autoincrement()), + make_column("ename", &Employee::m_ename), + make_column("salary", &Employee::m_salary), + make_column("comm", &Employee::m_commission))); + storage.sync_schema(); + storage.transaction([&storage]() { + storage.insert({1, "Patel", 4000, nullopt}); + storage.insert({2, "Jariwala", 19300, nullopt}); + return true; + }); + + // alternative way of writing a subselect by using a CTE. + // + // original select from subquery: + // SELECT * FROM (SELECT salary, comm AS commmission FROM emp) WHERE salary < 5000 + // + // with CTE: + // WITH + // sub AS( + // SELECT salary, comm AS commmission FROM emp) WHERE salary < 5000 + // ) + // SELECT * from sub; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto sub = "sub"_cte; + auto expression = with(sub().as(select(columns(&Employee::m_salary, &Employee::m_commission))), + select(asterisk(), where(sub->*&Employee::m_salary < 5000))); +#else + using sub = decltype(1_ctealias); + auto expression = with(cte().as(select(columns(&Employee::m_salary, &Employee::m_commission))), + select(asterisk(), where(column(&Employee::m_salary) < 5000))); +#endif + + string sql = storage.dump(expression); + + auto stmt = storage.prepare(expression); + auto results = storage.execute(stmt); + + cout << "List of employees with a salary less than 5000:\n"; + for(auto& result: results) { + cout << get<0>(result) << '\n'; + } + cout << endl; +} + +void apfelmaennchen() { + auto storage = make_storage(""); + + //WITH RECURSIVE + // xaxis(x) AS(VALUES(-2.0) UNION ALL SELECT x + 0.05 FROM xaxis WHERE x < 1.2), + // yaxis(y) AS(VALUES(-1.0) UNION ALL SELECT y + 0.1 FROM yaxis WHERE y < 1.0), + // m(iter, cx, cy, x, y) AS( + // SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis + // UNION ALL + // SELECT iter + 1, cx, cy, x * x - y * y + cx, 2.0 * x * y + cy FROM m + // WHERE(x * x + y * y) < 4.0 AND iter < 28 + // ), + // m2(iter, cx, cy) AS( + // SELECT max(iter), cx, cy FROM m GROUP BY cx, cy + // ), + // a(t) AS( + // SELECT group_concat(substr(' .+*#', 1 + min(iter / 7, 4), 1), '') + // FROM m2 GROUP BY cy + // ) + // SELECT group_concat(rtrim(t), x'0a') FROM a; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto xaxis = "xaxis"_cte; + constexpr auto yaxis = "yaxis"_cte; + constexpr auto m = "m"_cte; + constexpr auto m2 = "m2"_cte; + constexpr auto a = "string"_cte; + constexpr auto x = "x"_col; + constexpr auto y = "y"_col; + constexpr auto iter = "iter"_col; + constexpr auto cx = "cx"_col; + constexpr auto cy = "cy"_col; + constexpr auto t = "t"_col; + auto ast = with_recursive( + make_tuple( + xaxis(x).as(union_all(select(-2.0), select(xaxis->*x + 0.05, where(xaxis->*x < 1.2)))), + yaxis(y).as(union_all(select(-1.0), select(yaxis->*y + 0.10, where(yaxis->*y < 1.0)))), + m(iter, cx, cy, x, y) + .as(union_all(select(columns(0, xaxis->*x, yaxis->*y, 0.0, 0.0)), + select(columns(m->*iter + 1, + m->*cx, + m->*cy, + m->*x * m->*x - m->*y * m->*y + m->*cx, + 2.0 * m->*x * m->*y + m->*cy), + where((m->*x * m->*x + m->*y * m->*y) < 4.0 && m->*iter < 28)))), + m2(iter, cx, cy).as(select(columns(max<>(m->*iter), m->*cx, m->*cy), group_by(m->*cx, m->*cy))), + a(t).as(select(group_concat(substr(" .+*#", 1 + min<>(m2->*iter / 7.0, 4.0), 1), ""), group_by(m2->*cy)))), + select(group_concat(rtrim(a->*t), "\n"))); +#else + using cte_xaxis = decltype(1_ctealias); + using cte_yaxis = decltype(2_ctealias); + using cte_m = decltype(3_ctealias); + using cte_m2 = decltype(4_ctealias); + using cte_a = decltype(5_ctealias); + constexpr auto x = colalias_a{}; + constexpr auto y = colalias_b{}; + constexpr auto iter = colalias_c{}; + constexpr auto cx = colalias_d{}; + constexpr auto cy = colalias_e{}; + constexpr auto t = colalias_f{}; + auto ast = with_recursive( + make_tuple( + cte("x").as( + union_all(select(-2.0 >>= x), select(column(x) + 0.05, where(column(x) < 1.2)))), + cte("y").as( + union_all(select(-1.0 >>= y), select(column(y) + 0.10, where(column(y) < 1.0)))), + cte("iter", "cx", "cy", "x", "y") + .as(union_all( + select(columns(0 >>= iter, + column(x) >>= cx, + column(y) >>= cy, + 0.0 >>= x, + 0.0 >>= y)), + select(columns(column(iter) + 1, + column(cx), + column(cy), + column(x) * column(x) - column(y) * column(y) + + column(cx), + 2.0 * column(x) * column(y) + column(cy)), + where((column(x) * column(x) + column(y) * column(y)) < 4.0 && + column(iter) < 28)))), + cte("iter", "cx", "cy") + .as(select(columns(max<>(column(iter)) >>= iter, column(cx), column(cy)), + group_by(column(cx), column(cy)))), + cte("t").as( + select(group_concat(substr(" .+*#", 1 + min<>(column(iter) / 7.0, 4.0), 1), "") >>= t, + group_by(column(cy))))), + select(group_concat(rtrim(column(t)), "\n"))); +#endif + + string sql = storage.dump(ast); + + auto stmt = storage.prepare(ast); + auto results = storage.execute(stmt); + + cout << "Apfelmaennchen (Mandelbrot set):\n"; + for(const string& rowString: results) { + cout << rowString << '\n'; + } + cout << endl; +} + +void sudoku() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + auto storage = make_storage(""); + + //WITH RECURSIVE + // input(sud) AS( + // VALUES('53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79') + // ), + // digits(z, lp) AS( + // VALUES('1', 1) + // UNION ALL SELECT + // CAST(lp + 1 AS TEXT), lp + 1 FROM digits WHERE lp < 9 + // ), + // x(s, ind) AS( + // SELECT sud, instr(sud, '.') FROM input + // UNION ALL + // SELECT + // substr(s, 1, ind - 1) || z || substr(s, ind + 1), + // instr(substr(s, 1, ind - 1) || z || substr(s, ind + 1), '.') + // FROM x, digits AS z + // WHERE ind > 0 + // AND NOT EXISTS( + // SELECT 1 + // FROM digits AS lp + // WHERE z.z = substr(s, ((ind - 1) / 9) * 9 + lp, 1) + // OR z.z = substr(s, ((ind - 1) % 9) + (lp - 1) * 9 + 1, 1) + // OR z.z = substr(s, (((ind - 1) / 3) % 3) * 3 + // + ((ind - 1) / 27) * 27 + lp + // + ((lp - 1) / 3) * 6, 1) + // ) + // ) + // SELECT s FROM x WHERE ind = 0; + + constexpr auto input = "input"_cte; + constexpr auto digits = "digits"_cte; + constexpr auto z_alias = "z"_alias.for_(); + constexpr auto x = "x"_cte; + constexpr auto sud = "sud"_col; + constexpr auto z = "z"_col; + constexpr auto lp = "lp"_col; + constexpr auto s = "s"_col; + constexpr auto ind = "ind"_col; + auto ast = with_recursive( + make_tuple( + cte(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() { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + auto storage = make_storage(""); + + { + //WITH + // cnt(x) AS MATERIALIZED(VALUES(1)) + // SELECT x FROM cnt; + constexpr 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 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 +} + +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 auto register_user = "register_user"_cte; + constexpr auto registered_cnt = "registered_cnt"_cte; + constexpr auto register_date = "register_date"_col; + constexpr auto user_count = "user_count"_col; + constexpr 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 auto wnd = "wnd"_cte; + constexpr 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/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 5d998f1e6..95faa7385 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -236,6 +236,10 @@ using std::nullptr_t; (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 #pragma once #include // std::enable_if, std::is_same, std::is_empty @@ -245,6 +249,8 @@ using std::nullptr_t; #include // std::reference_wrapper #endif +// #include "functional/cxx_core_features.h" + // #include "functional/cxx_type_traits_polyfill.h" #include @@ -450,6 +456,9 @@ namespace sqlite_orm { 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; @@ -465,6 +474,9 @@ namespace sqlite_orm { 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; @@ -480,6 +492,9 @@ namespace sqlite_orm { template using expression_type_t = typename T::expression_type; + template + using alias_type_t = typename As::alias_type; + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template using udf_type_t = typename T::udf_type; @@ -488,6 +503,21 @@ namespace sqlite_orm { using auto_udf_type_t = typename decltype(a)::udf_type; #endif +#ifdef 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; + + // T::alias_type or nonesuch + template + using alias_holder_type_or_none = polyfill::detected; + + 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; @@ -1434,6 +1464,25 @@ namespace sqlite_orm { return I; } +#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED + /** + * Get the value of an index_sequence at a specific position. + */ + template + SQLITE_ORM_CONSTEVAL size_t index_sequence_value(size_t pos, std::index_sequence) { + static_assert(sizeof...(Idx) > 0); +#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; + } +#endif + template struct flatten_idxseq { using type = std::index_sequence<>; @@ -2862,6 +2911,19 @@ 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::value; @@ -3257,9 +3319,9 @@ namespace sqlite_orm { /** @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> {}; @@ -3281,6 +3343,20 @@ namespace sqlite_orm { template using decay_table_reference_t = typename decay_table_reference::type; #endif + + /** @short Moniker of a CTE, see `orm_cte_moniker`. + */ + template + SQLITE_ORM_INLINE_VAR constexpr bool is_cte_moniker_v = +#ifdef 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 @@ -3322,6 +3398,15 @@ namespace sqlite_orm { template concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @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 @@ -3335,7 +3420,7 @@ namespace sqlite_orm { /** @short Specifies that a type is a mapped recordset (table reference). */ template - concept orm_mapped_recordset = (orm_table_reference); + concept orm_mapped_recordset = (orm_table_reference || orm_cte_moniker); #endif } @@ -3439,12 +3524,16 @@ namespace sqlite_orm { #include // std::enable_if #include // std::move +// #include "functional/cxx_core_features.h" + // #include "functional/cxx_type_traits_polyfill.h" -// #include "tags.h" +// #include "type_traits.h" // #include "alias_traits.h" +// #include "tags.h" + namespace sqlite_orm { namespace internal { /** @@ -3469,10 +3558,19 @@ namespace sqlite_orm { template SQLITE_ORM_INLINE_VAR constexpr bool is_operator_argument_v::value>> = true; + +#ifdef SQLITE_ORM_WITH_CTE + template + struct alias_holder; +#endif } /** - * Use it like this: + * 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)); */ @@ -3504,7 +3602,7 @@ namespace sqlite_orm { } /** - * Make table reference. + * Make a table reference. */ template requires(!orm_recordset_alias) @@ -3513,7 +3611,7 @@ namespace sqlite_orm { } /** - * Make table reference. + * Make a table reference. */ template requires(!orm_recordset_alias) @@ -3521,8 +3619,71 @@ namespace sqlite_orm { return {}; } #endif -} +#ifdef 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 +} // #include "tags.h" // #include "type_printer.h" @@ -4811,6 +4972,9 @@ namespace sqlite_orm { #include // std::make_index_sequence, std::move #include // std::string #include // std::stringstream +#ifdef SQLITE_ORM_WITH_CTE +#include +#endif // #include "functional/cxx_type_traits_polyfill.h" @@ -4858,6 +5022,8 @@ namespace sqlite_orm::internal { // #include "tags.h" +// #include "column_pointer.h" + namespace sqlite_orm { namespace internal { @@ -4924,8 +5090,8 @@ namespace sqlite_orm { /* * 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, and the alias identifier is returned. + * `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 @@ -4942,6 +5108,14 @@ namespace sqlite_orm { return alias_extractor::extract(); } +#ifdef 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&) { @@ -4977,6 +5151,8 @@ 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 @@ -4990,8 +5166,31 @@ namespace sqlite_orm { [[nodiscard]] consteval recordset_alias for_() const { return {}; } + + template + [[nodiscard]] consteval auto for_() const { + using T = std::remove_const_t; + return recordset_alias{}; + } }; #endif + +#ifdef 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 } /** @@ -5001,7 +5200,12 @@ namespace sqlite_orm { * using als = alias_u; * select(alias_column(column(&User::id))) */ - template::value, bool> = true> + template, + polyfill::negation>>>::value, + bool> = true> constexpr auto alias_column(C field) { using namespace ::sqlite_orm::internal; using aliased_type = type_t; @@ -5019,7 +5223,13 @@ namespace sqlite_orm { * using als = alias_u; * select(alias_column(&User::id)) */ - template::value, bool> = true> + template, + polyfill::negation>>>::value, + bool> = true> constexpr auto alias_column(F O::*field) { using namespace ::sqlite_orm::internal; using aliased_type = type_t; @@ -5041,6 +5251,7 @@ namespace sqlite_orm { * 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); @@ -5071,11 +5282,60 @@ namespace sqlite_orm { * select(als->*&User::id) */ template + requires(!orm_cte_moniker>) constexpr auto operator->*(const A& /*tableAlias*/, F field) { return alias_column(std::move(field)); } #endif +#ifdef 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)); + } + + /** + * Create a column reference to an aliased CTE column. + */ + 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. */ @@ -5093,17 +5353,29 @@ namespace sqlite_orm { 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 - template = true> - internal::alias_holder get() { + /** + * Wrap a column alias in an alias holder. + */ + template + internal::alias_holder get() { + static_assert(internal::is_column_alias_v, ""); return {}; } @@ -5208,6 +5480,20 @@ namespace sqlite_orm { } } #endif + +#ifdef SQLITE_ORM_WITH_CTE + /** + * 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 @@ -7076,7 +7362,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 @@ -7359,6 +7645,9 @@ namespace sqlite_orm { } #pragma once +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include +#endif #include // std::remove_const #include // std::string #include // std::move @@ -7373,108 +7662,228 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_traits.h" -// #include "tuple_helper/tuple_iteration.h" +// #include "tuple_helper/tuple_transformer.h" -#include // std::get, std::tuple_element, std::tuple_size -#include // std::index_sequence, std::make_index_sequence -#include // std::forward, std::move +#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 { -#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; - ((lambda(std::get(tpl)), sink) = ... = 0); - } else { - (lambda(std::get(tpl)), ...); - } - } -#else - template - void iterate_tuple(Tpl& /*tpl*/, std::index_sequence<>, L&& /*lambda*/) {} - - 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)); - } - } -#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 Op> + struct tuple_transformer; - 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); - } + template class Pack, class... Types, template class Op> + struct tuple_transformer, Op> { + using type = Pack...>; }; /* - * 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. + * Transform specified tuple. * - * 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. + * `Op` is a metafunction. */ - template class Base, class L> - lambda_as_template_base call_as_template_base(L lambda) { - return {std::move(lambda)}; - } - } -} - -// #include "optional_container.h" - -// #include "ast/where.h" - -#include // std::false_type, std::true_type -#include // std::move - -// #include "../functional/cxx_universal.h" - -// #include "../functional/cxx_type_traits_polyfill.h" - -// #include "../serialize_result_type.h" + 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)); + } + } +} + +// #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 + 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)); + } + } +#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)}; + } + } +} + +// #include "optional_container.h" + +// #include "ast/where.h" + +#include // std::false_type, std::true_type +#include // std::move + +// #include "../functional/cxx_universal.h" + +// #include "../functional/cxx_type_traits_polyfill.h" + +// #include "../serialize_result_type.h" namespace sqlite_orm { namespace internal { @@ -7575,6 +7984,98 @@ namespace sqlite_orm { // #include "alias_traits.h" +// #include "cte_moniker.h" + +#ifdef 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" + +#ifdef 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 + namespace sqlite_orm { namespace internal { @@ -7745,6 +8246,110 @@ namespace sqlite_orm { using super::super; }; +#ifdef 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. */ @@ -7935,6 +8540,219 @@ namespace sqlite_orm { return {{std::forward(expressions)...}}; } +#ifdef SQLITE_ORM_WITH_CTE +#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 + + /** + * 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 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 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 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 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. @@ -10201,123 +11019,6 @@ namespace sqlite_orm { // #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)); - } - } -} - // #include "../member_traits/member_traits.h" // #include "../typed_comparator.h" @@ -10334,7 +11035,22 @@ namespace sqlite_orm { namespace sqlite_orm { - namespace internal { + namespace internal { + +#ifdef 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 { @@ -10349,7 +11065,15 @@ namespace sqlite_orm { */ template struct table_t : basic_table { +#ifdef 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; @@ -10742,6 +11466,8 @@ namespace sqlite_orm { // ::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" @@ -10752,6 +11478,90 @@ namespace sqlite_orm { // #include "select_constraints.h" +// #include "cte_types.h" + +#ifdef 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; + } +} + +#ifdef 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 @@ -10759,7 +11569,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" @@ -10773,6 +11583,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 {}; @@ -10790,7 +11604,7 @@ namespace sqlite_orm { 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. */ @@ -10799,10 +11613,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 @@ -10847,7 +11661,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; @@ -10932,21 +11746,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; } +#ifdef 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); } + +#ifdef 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(colalias_index::value, column_index_sequence{}); + auto& table = pick_table(dboObjects); + return &std::get(table.elements).name; + } +#endif } } #pragma once @@ -10964,7 +11833,7 @@ namespace sqlite_orm { namespace internal { template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); /** * Serialize default value of a column's default valu @@ -11050,26 +11919,6 @@ namespace sqlite_orm { // #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; - } -} - // #include "tuple_helper/tuple_filter.h" // #include "tuple_helper/tuple_transformer.h" @@ -11122,6 +11971,8 @@ namespace sqlite_orm { // #include "alias.h" +// #include "cte_types.h" + // #include "storage_traits.h" #include // std::tuple @@ -11136,8 +11987,9 @@ namespace sqlite_orm { // #include "storage_lookup.h" -namespace sqlite_orm { +// #include "schema/column.h" +namespace sqlite_orm { namespace internal { namespace storage_traits { @@ -11160,6 +12012,26 @@ 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> {}; } } } @@ -12011,6 +12883,21 @@ namespace sqlite_orm { template struct column_result_t, void> : column_result_t {}; +#ifdef 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> : conc_tuple>>...> {}; @@ -12566,7 +13453,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 @@ -13739,6 +14627,29 @@ namespace sqlite_orm { } }; +#ifdef 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; @@ -14373,7 +15284,7 @@ namespace sqlite_orm { struct order_by_t; template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); template std::string serialize_order_by(const T&, const Ctx&); @@ -14455,6 +15366,7 @@ namespace sqlite_orm { field_values_excluding, mapped_columns_expressions, column_constraints, + constraints_tuple, }; template @@ -14482,6 +15394,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; @@ -14730,6 +15643,22 @@ 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&, @@ -14740,22 +15669,18 @@ namespace sqlite_orm { const bool& isNotNull = std::get<2>(tpl); auto& context = std::get<3>(tpl); - auto first = true; - constexpr std::array sep = {" ", ""}; - iterate_tuple(column.constraints, [&ss, &context, &first, &sep](auto& constraint) { - ss << sep[std::exchange(first, false)]; - ss << serialize(constraint, context); + iterate_tuple(column.constraints, [&ss, &context](auto& constraint) { + ss << ' ' << serialize(constraint, context); }); using constraints_tuple = decltype(column.constraints); constexpr bool hasExplicitNullableConstraint = mpl::invoke_t, check_if_has_type>, constraints_tuple>::value; if(!hasExplicitNullableConstraint) { - ss << sep[std::exchange(first, false)]; if(isNotNull) { - ss << "NOT NULL"; + ss << " NOT NULL"; } else { - ss << "NULL"; + ss << " NULL"; } } @@ -16787,7 +17712,7 @@ namespace sqlite_orm { namespace internal { template - std::string serialize(const T& t, const C& context); + auto serialize(const T& t, const C& context); template std::vector& collect_table_column_names(std::vector& collectedExpressions, @@ -16870,16 +17795,228 @@ namespace sqlite_orm { 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); + 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" + +#ifdef 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" + +#ifdef 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."); + }; + + 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 // #include "order_by_serializer.h" @@ -16962,6 +18099,8 @@ namespace sqlite_orm { // #include "serializing_util.h" +// #include "serialize_result_type.h" + // #include "statement_binder.h" // #include "values.h" @@ -16984,7 +18123,7 @@ namespace sqlite_orm { struct statement_serializer; template - std::string serialize(const T& t, const C& context) { + auto serialize(const T& t, const C& context) { statement_serializer serializer; return serializer(t, context); } @@ -17074,7 +18213,7 @@ namespace sqlite_orm { } template - std::string serialize(const statement_type& statement, const Ctx& context, const std::string& tableName) { + 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) << ")"; @@ -17352,7 +18491,7 @@ namespace sqlite_orm { using statement_type = rank_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx&) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx&) const { return "rank"; } }; @@ -17509,6 +18648,75 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_CTE +#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 + + 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; @@ -17807,7 +19015,7 @@ namespace sqlite_orm { 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"; @@ -17839,7 +19047,7 @@ namespace sqlite_orm { using statement_type = null_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { return "NULL"; } }; @@ -17849,7 +19057,7 @@ namespace sqlite_orm { using statement_type = not_null_t; template - std::string operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { + serialize_result_type operator()(const statement_type& /*statement*/, const Ctx& /*context*/) const { return "NOT NULL"; } }; @@ -17998,11 +19206,10 @@ namespace sqlite_orm { ss << streaming_identifier(column.name); if(!context.skip_types_and_constraints) { ss << " " << type_printer>().print(); - ss << " " - << streaming_column_constraints( - call_as_template_base(polyfill::identity{})(column), - column.is_not_null(), - context); + ss << streaming_column_constraints( + call_as_template_base(polyfill::identity{})(column), + column.is_not_null(), + context); } return ss.str(); } @@ -18488,7 +19695,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"; @@ -18511,7 +19718,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(); } }; @@ -18746,7 +19956,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"; @@ -18764,7 +19974,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"; @@ -19015,7 +20225,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"; } }; @@ -19105,6 +20315,454 @@ namespace sqlite_orm { // #include "schema/index.h" +// #include "cte_storage.h" + +#ifdef 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; + }; + + /** + * 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 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 { + +#ifdef 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 + + /** + * Return passed in DBOs. + */ + template = true> + decltype(auto) db_objects_for_expression(DBOs& dbObjects, const E&) { + return dbObjects; + } + } +} + // #include "util.h" // #include "serializing_util.h" @@ -19145,6 +20803,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; @@ -19705,6 +21365,44 @@ namespace sqlite_orm { return this->execute(statement); } +#ifdef SQLITE_ORM_WITH_CTE + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with(CTE cte, E expression) { + auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with(common_table_expressions cte, E expression) { + auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with_recursive(CTE cte, E expression) { + auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); + return this->execute(statement); + } + + /** + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ + template + auto with_recursive(common_table_expressions cte, E expression) { + auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); + return this->execute(statement); + } +#endif + template = true> std::string dump(const T& preparedStatement, bool parametrized = true) const { return this->dump(preparedStatement.expression, parametrized); @@ -19725,8 +21423,9 @@ namespace sqlite_orm { [](const auto& expression) -> decltype(auto) { return (expression); })(std::forward(expression)); - using context_t = serializer_context; - context_t context{this->db_objects}; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, expression); + using context_t = serializer_context>; + context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() context.skip_table_name = false; @@ -20086,8 +21785,9 @@ namespace sqlite_orm { template prepared_statement_t prepare_impl(S statement) { - using context_t = serializer_context; - context_t context{this->db_objects}; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, statement); + using context_t = serializer_context>; + context_t context{exprDBOs}; context.skip_table_name = false; context.replace_bindable_with_question = true; @@ -20152,6 +21852,15 @@ namespace sqlite_orm { using storage_base::table_exists; // now that it is in storage_base make it into overload set +#ifdef SQLITE_ORM_WITH_CTE + template, is_insert_raw>, bool> = true> + prepared_statement_t> prepare(with_t sel) { + return prepare_impl>(std::move(sel)); + } +#endif + template prepared_statement_t> prepare(select_t statement) { statement.highest_level = true; @@ -20268,14 +21977,23 @@ namespace sqlite_orm { template void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression.args, conditional_binder{statement.stmt}); + iterate_ast(statement.expression, conditional_binder{stmt}); + perform_step(stmt); + } + +#ifdef SQLITE_ORM_WITH_CTE + template = true> + void execute(const prepared_statement_t>& statement) { + sqlite3_stmt* stmt = reset_stmt(statement.stmt); + iterate_ast(statement.expression, conditional_binder{stmt}); perform_step(stmt); } +#endif template void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression.args, conditional_binder{stmt}); + iterate_ast(statement.expression, conditional_binder{stmt}); perform_step(stmt); } @@ -20493,11 +22211,8 @@ namespace sqlite_orm { perform_step(stmt); } - template, - satisfies_not = true> - std::vector execute(const prepared_statement_t>& statement) { + template = true> + std::vector _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); @@ -20512,11 +22227,8 @@ namespace sqlite_orm { return res; } - template, - satisfies = true> - std::vector execute(const prepared_statement_t>& statement) { + template = true> + std::vector _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); @@ -20532,6 +22244,23 @@ namespace sqlite_orm { return res; } +#ifdef SQLITE_ORM_WITH_CTE + template + auto execute(const prepared_statement_t, CTEs...>>& statement) { + using ExprDBOs = + decltype(db_objects_for_expression(this->db_objects, + std::declval, CTEs...>>())); + using R = column_result_of_t; + return _execute_select(statement); + } +#endif + + template + auto execute(const prepared_statement_t>& statement) { + using R = column_result_of_t; + return _execute_select(statement); + } + template> R execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); @@ -20770,6 +22499,16 @@ namespace sqlite_orm { template struct node_tuple> : node_tuple {}; +#ifdef SQLITE_ORM_WITH_CTE + template + struct node_tuple> + : node_tuple {}; + + template + struct node_tuple> + : node_tuple_for {}; +#endif + template struct node_tuple, void> : node_tuple_for {}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index edce671cb..350fd032c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,9 +27,11 @@ add_executable(unit_tests static_tests/function_static_tests.cpp static_tests/aggregate_function_return_types.cpp static_tests/core_function_return_types.cpp + static_tests/column_expression_type.cpp static_tests/column_result_t.cpp static_tests/column_pointer.cpp static_tests/alias.cpp + static_tests/cte.cpp static_tests/is_primary_key_insertable.cpp static_tests/is_column_with_insertable_primary_key.cpp static_tests/operators_adl.cpp diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index 32a5a8a79..632d697b7 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -5,6 +5,8 @@ using namespace sqlite_orm; using internal::alias_column_t; +using internal::alias_holder; +using internal::column_alias; using internal::column_pointer; using internal::iterate_ast; @@ -327,6 +329,69 @@ TEST_CASE("ast_iterator") { expected.push_back(typeid(alias_column_t, column_pointer>)); iterate_ast(expression, lambda); } +#endif +#ifdef SQLITE_ORM_WITH_CTE + SECTION("with ordinary") { + using cte_1 = decltype(1_ctealias); + auto expression = with(cte().as(select(1)), select(column(1_colalias))); + expected.insert(expected.cend(), {typeid(int), typeid(column_pointer>>)}); + iterate_ast(expression, lambda); + } + SECTION("with not enforced recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with_recursive(cte().as(select(1)), select(column(1_colalias))); + expected.insert(expected.cend(), {typeid(int), typeid(column_pointer>>)}); + iterate_ast(expression, lambda); + } + SECTION("with optional recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + expected.insert(expected.cend(), + {typeid(int), + typeid(column_pointer>>), + typeid(int), + typeid(column_pointer>>), + typeid(int), + typeid(column_pointer>>)}); + iterate_ast(expression, lambda); + } + SECTION("with recursive") { + using cte_1 = decltype(1_ctealias); + auto expression = with_recursive( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + expected.insert(expected.cend(), + {typeid(int), + typeid(column_pointer>>), + typeid(int), + typeid(column_pointer>>), + typeid(int), + typeid(column_pointer>>)}); + iterate_ast(expression, lambda); + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("aliased CTE column pointer") { + constexpr auto c = "1"_cte; + using cte_1 = decltype("1"_cte); + constexpr auto z_alias = "z"_alias.for_(); + auto expression = z_alias->*&User::id; + expected.push_back(typeid(alias_column_t, column_pointer>)); + iterate_ast(expression, lambda); + } + SECTION("aliased CTE column alias") { + constexpr auto c = "1"_cte; + using cte_1 = decltype("1"_cte); + constexpr auto z_alias = "z"_alias.for_(); + auto expression = z_alias->*1_colalias; + expected.push_back( + typeid(alias_column_t, column_pointer>>>)); + iterate_ast(expression, lambda); + } +#endif #endif SECTION("highlight") { auto expression = highlight(0, std::string(""), std::string("")); diff --git a/tests/statement_serializer_tests/aggregate_functions.cpp b/tests/statement_serializer_tests/aggregate_functions.cpp index 6e183fb3c..667b93187 100644 --- a/tests/statement_serializer_tests/aggregate_functions.cpp +++ b/tests/statement_serializer_tests/aggregate_functions.cpp @@ -76,12 +76,12 @@ TEST_CASE("statement_serializer aggregate functions") { } SECTION("count(*)") { SECTION("simple") { - SECTION("with filter") { + SECTION("without filter") { auto expression = count(); value = serialize(expression, context); expected = R"(COUNT(*))"; } - SECTION("without filter") { + SECTION("with filter") { auto expression = count().filter(where(less_than(&User::id, 10))); value = serialize(expression, context); expected = R"(COUNT(*) FILTER (WHERE "id" < 10))"; @@ -93,6 +93,15 @@ TEST_CASE("statement_serializer aggregate functions") { value = serialize(expression, context); expected = R"(COUNT(*))"; } +#endif +#ifdef SQLITE_ORM_WITH_CTE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("with CTE") { + auto expression = count<1_ctealias>(); + value = serialize(expression, context); + expected = R"(COUNT(*))"; + } +#endif #endif } } diff --git a/tests/statement_serializer_tests/alias_extractor.cpp b/tests/statement_serializer_tests/alias_extractor.cpp index 462dd893e..aca275ad3 100644 --- a/tests/statement_serializer_tests/alias_extractor.cpp +++ b/tests/statement_serializer_tests/alias_extractor.cpp @@ -19,4 +19,11 @@ TEST_CASE("alias extractor") { REQUIRE(alias_extractor>::extract() == "a"); REQUIRE(alias_extractor>::as_alias() == "a"); } +#ifdef SQLITE_ORM_WITH_CTE + SECTION("cte moniker") { + using cte_1 = decltype(1_ctealias); + REQUIRE(alias_extractor::extract() == "1"); + REQUIRE(alias_extractor::as_alias() == ""); + } +#endif } diff --git a/tests/statement_serializer_tests/column_names.cpp b/tests/statement_serializer_tests/column_names.cpp index 902a986cb..ce0844f4e 100644 --- a/tests/statement_serializer_tests/column_names.cpp +++ b/tests/statement_serializer_tests/column_names.cpp @@ -164,6 +164,20 @@ TEST_CASE("statement_serializer column names") { auto value = serialize(alias_column(&Object::id), context); REQUIRE(value == R"("a"."id")"); } +#ifdef SQLITE_ORM_WITH_CTE +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("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}; + context.skip_table_name = false; + constexpr auto als = "a"_alias.for_<1_ctealias>(); + auto value = serialize(als->*1_colalias, context); + REQUIRE(value == R"("a"."1")"); + } +#endif +#endif } SECTION("escaped identifiers") { struct Object1 { diff --git a/tests/statement_serializer_tests/select_constraints.cpp b/tests/statement_serializer_tests/select_constraints.cpp index 514c7f333..4ed04d060 100644 --- a/tests/statement_serializer_tests/select_constraints.cpp +++ b/tests/statement_serializer_tests/select_constraints.cpp @@ -60,7 +60,7 @@ TEST_CASE("statement_serializer select constraints") { expected = "OR ROLLBACK"; } } - SECTION("from") { + SECTION("from table") { SECTION("without alias") { auto expression = from(); value = serialize(expression, context); @@ -92,6 +92,122 @@ TEST_CASE("statement_serializer select constraints") { } #endif } +#ifdef SQLITE_ORM_WITH_CTE + SECTION("from CTE") { + using cte_1 = decltype(1_ctealias); + auto dbObjects2 = + internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cte().as(select(1)))); + using context_t = internal::serializer_context; + context_t context{dbObjects2}; + SECTION("without alias 1") { + auto expression = from(); + value = serialize(expression, context); + expected = R"(FROM "1")"; + } + SECTION("with alias 1") { + auto expression = from>(); + value = serialize(expression, context); + expected = R"(FROM "1" "z")"; + } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + SECTION("without alias 2") { + auto expression = from<1_ctealias>(); + value = serialize(expression, context); + expected = R"(FROM "1")"; + } + SECTION("with alias 2") { + constexpr auto z_alias = "z"_alias.for_<1_ctealias>(); + auto expression = from(); + value = serialize(expression, context); + expected = R"(FROM "1" "z")"; + } + SECTION("as") { + auto expression = cte().as(select(1)); + value = serialize(expression, context); + expected = R"("1"("1") AS (SELECT 1))"; + } + SECTION("as materialized") { + auto expression = cte().as(select(1)); + value = serialize(expression, context); + expected = R"("1"("1") AS MATERIALIZED (SELECT 1))"; + } + SECTION("as not materialized") { + auto expression = cte().as(select(1)); + value = serialize(expression, context); + expected = R"("1"("1") AS NOT MATERIALIZED (SELECT 1))"; + } +#endif + SECTION("with ordinary") { + auto expression = with(cte().as(select(1)), select(column(1_colalias))); + value = serialize(expression, context); + expected = R"(WITH "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1")"; + } + SECTION("with ordinary, compound") { + auto expression = with(cte().as(select(1)), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + value = serialize(expression, context); + expected = R"(WITH "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1" UNION ALL SELECT "1"."1" FROM "1")"; + } + SECTION("with not enforced recursive") { + auto expression = with_recursive(cte().as(select(1)), select(column(1_colalias))); + value = serialize(expression, context); + expected = R"(WITH RECURSIVE "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1")"; + } + SECTION("with not enforced recursive, compound") { + auto expression = + with_recursive(cte().as(select(1)), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + value = serialize(expression, context); + expected = + R"(WITH RECURSIVE "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1" UNION ALL SELECT "1"."1" FROM "1")"; + } + SECTION("with ordinary, multiple") { + auto expression = with(std::make_tuple(cte().as(select(1)), cte().as(select(1))), + select(column(1_colalias))); + value = serialize(expression, context); + expected = R"(WITH "1"("1") AS (SELECT 1), "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1")"; + } + SECTION("with ordinary, multiple, compound") { + auto expression = with(std::make_tuple(cte().as(select(1)), cte().as(select(1))), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + value = serialize(expression, context); + expected = + R"(WITH "1"("1") AS (SELECT 1), "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1" UNION ALL SELECT "1"."1" FROM "1")"; + } + SECTION("with not enforced recursive, multiple") { + auto expression = with_recursive(std::make_tuple(cte().as(select(1)), cte().as(select(1))), + select(column(1_colalias))); + value = serialize(expression, context); + expected = R"(WITH RECURSIVE "1"("1") AS (SELECT 1), "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1")"; + } + SECTION("with not enforced recursive, multiple, compound") { + auto expression = + with_recursive(std::make_tuple(cte().as(select(1)), cte().as(select(1))), + union_all(select(column(1_colalias)), select(column(1_colalias)))); + value = serialize(expression, context); + expected = + R"(WITH RECURSIVE "1"("1") AS (SELECT 1), "1"("1") AS (SELECT 1) SELECT "1"."1" FROM "1" UNION ALL SELECT "1"."1" FROM "1")"; + } + SECTION("with optional recursive") { + auto expression = with( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + value = serialize(expression, context); + expected = + R"(WITH "1"("1") AS (SELECT 1 UNION ALL SELECT "1"."1" + 1 FROM "1" WHERE ("1"."1" < 10)) SELECT "1"."1" FROM "1")"; + } + SECTION("with recursive") { + auto expression = with_recursive( + cte().as( + union_all(select(1), select(column(1_colalias) + 1, where(column(1_colalias) < 10)))), + select(column(1_colalias))); + value = serialize(expression, context); + expected = + R"(WITH RECURSIVE "1"("1") AS (SELECT 1 UNION ALL SELECT "1"."1" + 1 FROM "1" WHERE ("1"."1" < 10)) SELECT "1"."1" FROM "1")"; + } + } +#endif // tests whether the statement serializer for a select with joins // properly deduplicates the table names when no explicit from is used SECTION("deduplicated table names") { diff --git a/tests/static_tests/alias.cpp b/tests/static_tests/alias.cpp index 51295a1cf..037a2d80c 100644 --- a/tests/static_tests/alias.cpp +++ b/tests/static_tests/alias.cpp @@ -67,6 +67,21 @@ TEST_CASE("aliases") { runTest, column_pointer>>( alias_column(&User::id)); runTest, column_pointer>>(d_alias->*&User::id); +#endif +#ifdef SQLITE_ORM_WITH_CTE + runTest>(1_colalias); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + using cte_1 = decltype(1_ctealias); + constexpr auto c_alias = "c"_alias.for_<"1"_cte>(); + runTest, column_pointer>>>>( + alias_column("a"_col)); + runTest, column_pointer>>>>( + alias_column("1"_cte->*"a"_col)); + runTest, column_pointer>>>>( + c_alias->*"a"_col); + runTest, column_pointer>>>>( + c_alias->*("1"_cte->*"a"_col)); +#endif #endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/tests/static_tests/column_expression_type.cpp b/tests/static_tests/column_expression_type.cpp new file mode 100644 index 000000000..bda360294 --- /dev/null +++ b/tests/static_tests/column_expression_type.cpp @@ -0,0 +1,83 @@ +#include // std::is_same +#include +#include + +using namespace sqlite_orm; +using internal::alias_column_t; +using internal::alias_holder; +using internal::column_alias; +using internal::column_pointer; + +template +void do_assert() { + STATIC_REQUIRE(std::is_same::value); +} + +template +void runTest(V /*value*/) { + do_assert, E>(); +} + +TEST_CASE("column_expression_of_t") { + struct Org { + int64 id = 0; + int64 boss = 0; + }; + struct Derived : Org {}; + + auto dbObjects = std::make_tuple( + make_table("org", make_column("id", &Org::id), make_column("boss", &Org::boss)), + make_table("derived", make_column("id", &Derived::id), make_column("boss", &Derived::boss))); + using db_objects_t = decltype(dbObjects); + + runTest(1); + runTest(rowid()); + runTest(&Org::id); + // std::reference_wrapper + { + const int64 x = 42; + runTest(std::ref(x)); + } + runTest>(columns(&Org::boss)); + runTest>(asterisk()); + runTest, int64 Org::*>, alias_column_t, int64 Org::*>>>( + asterisk>()); + // object_t not allowed + //runTest(object()); + runTest>(as(1)); + runTest, int64 Org::*>>(alias_column>(&Org::id)); + + runTest>(column(&Org::id)); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr auto moniker = c(); + runTest>(moniker->*&Org::id); + runTest>(asterisk()); +#endif +#ifdef SQLITE_ORM_WITH_CTE + using cte_1 = decltype(1_ctealias); + auto dbObjects2 = + internal::db_objects_cat(dbObjects, + internal::make_cte_table(dbObjects, cte().as(select(columns(&Org::id, 1))))); + using db_objects2_t = decltype(dbObjects2); + runTest>(column(&Org::id)); + runTest>>>(column(1_colalias)); + runTest>>(column(colalias_c{})); + runTest, column_pointer>>( + alias_column>(&Org::id)); + runTest, column_pointer>>>>( + alias_column>(1_colalias)); + runTest, int>::*>>( + asterisk()); + runTest, int64 Org::*>, + alias_column_t, int internal::aliased_field, int>::*>>>( + asterisk>()); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + runTest>(column<"1"_cte>(&Org::id)); + runTest>>(column<"1"_cte>("c"_col)); + runTest>>(column<"1"_cte>(get<"c"_col>())); + runTest>>("1"_cte->*"c"_col); +#endif +#endif +} diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 2524951e3..6b261b75c 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -122,4 +122,28 @@ TEST_CASE("column_result_of_t") { runTest(object()); runTest(union_all(select(1), select(2))); runTest(union_all(select(1ll), select(2))); +#ifdef 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/cte.cpp b/tests/static_tests/cte.cpp new file mode 100644 index 000000000..8bde4462e --- /dev/null +++ b/tests/static_tests/cte.cpp @@ -0,0 +1,120 @@ +#include +#ifdef 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/node_tuple.cpp b/tests/static_tests/node_tuple.cpp index ea114f9e7..b5af5747e 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -3,6 +3,8 @@ #include // std::is_same using namespace sqlite_orm; +using internal::alias_holder; +using internal::column_alias; using internal::column_pointer; template @@ -248,6 +250,24 @@ TEST_CASE("Node tuple") { using Expected = tuple; static_assert(is_same::value, "count(*)"); } +#ifdef 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); @@ -956,4 +976,50 @@ TEST_CASE("Node tuple") { using ExpectedTuple = tuple; STATIC_REQUIRE(std::is_same::value); } +#ifdef 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 c09eddc70..215be104f 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -113,6 +113,8 @@ TEST_CASE("inline namespace literals expressions") { constexpr auto u_alias_builder = "u"_alias; constexpr auto c_alias = "c"_col; constexpr auto f_scalar_builder = "f"_scalar; + constexpr auto numeric_cte_alias_builder = 1_ctealias; + constexpr auto cte_alias_builder = "1"_cte; } TEST_CASE("ADL and pointer-to-member expressions") { @@ -121,9 +123,11 @@ TEST_CASE("ADL and pointer-to-member expressions") { }; constexpr auto user_table = c(); constexpr auto u_alias = "u"_alias.for_(); + constexpr auto cte = "1"_cte; user_table->*&User::id; u_alias->*&User::id; + cte->*&User::id; } #endif diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index 7649234bd..af9c5a6c7 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -237,17 +237,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; } diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index ace2aa2c9..3a870392a 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -20,7 +20,6 @@ TEST_CASE("table name collector") { SECTION("from table") { SECTION("regular column") { - using als = alias_z; auto expression = &User::id; expected.emplace(table.name, ""); iterate_ast(expression, collector); @@ -42,8 +41,65 @@ 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); + } + +#ifdef 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, "", ""); diff --git a/tests/tests3.cpp b/tests/tests3.cpp index 234a0eff9..c9d710759 100644 --- a/tests/tests3.cpp +++ b/tests/tests3.cpp @@ -385,3 +385,62 @@ TEST_CASE("Escape chars") { storage.update(selena); storage.remove(10); } + +#ifdef 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