diff --git a/dev/alias_traits.h b/dev/alias_traits.h index 542f257d..37b6ad7d 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -1,15 +1,14 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::is_base_of, std::is_same -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::is_base_of, std::is_same, std::remove_const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include #endif #endif #include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" -#include "table_reference.h" SQLITE_ORM_EXPORT namespace sqlite_orm { @@ -20,6 +19,22 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { namespace sqlite_orm { namespace internal { + template + struct table_reference; + + template + struct decay_table_ref : std::remove_const {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + + template + using decay_table_ref_t = typename decay_table_ref::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_decay_table_ref_t = typename decay_table_ref::type; +#endif template inline constexpr bool is_alias_v = std::is_base_of::value; @@ -36,6 +51,13 @@ namespace sqlite_orm { template struct is_column_alias : is_alias {}; + template + inline constexpr bool is_table_reference_v = + polyfill::is_specialization_of_v, table_reference>; + + template + struct is_table_reference : polyfill::bool_constant> {}; + /** @short Alias of any type of record set, see `orm_recordset_alias`. */ template @@ -68,11 +90,20 @@ namespace sqlite_orm { template using is_cte_moniker = polyfill::bool_constant>; + + /** @short Referring to a recordset. + */ + template + inline constexpr bool is_referring_to_recordset_v = + polyfill::disjunction_v, is_recordset_alias>; + + template + using is_referring_to_recordset = polyfill::bool_constant>; } } SQLITE_ORM_EXPORT namespace sqlite_orm { -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept orm_alias = std::derived_from; @@ -85,6 +116,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template concept orm_column_alias = (orm_alias && !orm_names_type); + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @short Specifies that a type is an alias of any type of record set. * * A record set alias has the following traits: diff --git a/dev/ast/match.h b/dev/ast/match.h index 2059c38c..c311608a 100644 --- a/dev/ast/match.h +++ b/dev/ast/match.h @@ -1,25 +1,51 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include +#include // std::move #endif -namespace sqlite_orm { - namespace internal { +#include "../type_traits.h" - template - struct match_t { - using mapped_type = T; - using argument_type = X; +namespace sqlite_orm::internal { + template + struct match_with_table_t { + using mapped_type = T; + using argument_type = X; - argument_type argument; - }; - } + argument_type argument; + }; + + /* + * Alternative equality comparison where the left side is always a field. + */ + template + struct match_t { + using field_type = Field; + using argument_type = X; + + field_type field; + argument_type argument; + }; } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ template - internal::match_t match(X argument) { + [[deprecated( + "Use the `match` function accepting the hidden FTS5 'any' field or a field of your FTS table instead")]] + constexpr internal::match_with_table_t match(X argument) { return {std::move(argument)}; } + + template + constexpr internal::match_t match(CP field, X argument) { + return {std::move(field), std::move(argument)}; + } + + template + constexpr internal::match_t match(F O::* field, X argument) { + return {field, std::move(argument)}; + } } diff --git a/dev/ast/rank.h b/dev/ast/rank.h index 28387d0a..16bcf1d5 100644 --- a/dev/ast/rank.h +++ b/dev/ast/rank.h @@ -7,6 +7,10 @@ namespace sqlite_orm { } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + [[deprecated("Use the hidden FTS5 rank column instead")]] inline internal::rank_t rank() { return {}; } diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index 927b5f44..a21919d7 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -103,8 +103,18 @@ namespace sqlite_orm { }; template - struct ast_iterator, void> { - using node_type = match_t; + struct ast_iterator, void> { + using node_type = match_with_table_t; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& node, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(node.argument, lambda); + } + }; + + template + struct ast_iterator, void> { + using node_type = match_t; template SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& node, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { @@ -158,6 +168,27 @@ namespace sqlite_orm { } }; + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& expression, + L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(expression.table_values, lambda); + } + }; + + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& from, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(from.table_expressions, lambda); + } + }; + template struct ast_iterator, void> { using node_type = where_t; diff --git a/dev/column_pointer.h b/dev/column_pointer.h index bbef4ebb..3b8dec2c 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -1,7 +1,7 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_convertible +#include // std::enable_if, std::is_convertible, std::is_base_of #include // std::move #endif @@ -53,7 +53,11 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template = true> constexpr internal::column_pointer column(F Base::* field) { - static_assert(std::is_convertible::value, "Field must be from derived class"); + static_assert(std::is_convertible::value || + std::is_same, O>::value || + // trust the `enclosing_type` alias template defined in the virtual table's data struct + polyfill::is_detected_v, + "Field must be from derived or related class"); return {field}; } @@ -61,41 +65,33 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Explicitly refer to a column. */ - template - constexpr auto column(F O::* field) { + template + constexpr auto column(F Base::* field) { return column>(field); } +#endif // Intentionally place pointer-to-member operator for table references in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Explicitly refer to a column. */ - template - constexpr auto operator->*(const R& /*table*/, F O::* field) { + template + constexpr auto operator->*(const R& /*table*/, F Base::* field) { + return column(field); + } +#else + /** + * Explicitly refer to a column. + */ + template, bool> = true> + constexpr auto operator->*(const R& /*table*/, F Base::* field) { return column(field); } - } - - /** - * Make a table reference. - */ - template - requires (!orm_recordset_alias) - consteval internal::table_reference column() { - return {}; - } - - /** - * Make a table reference. - */ - template - requires (!orm_recordset_alias) - consteval internal::table_reference c() { - return {}; - } #endif + } #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** diff --git a/dev/conditions.h b/dev/conditions.h index 1c315f44..c40cc0db 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -719,14 +719,6 @@ namespace sqlite_orm { inner_join_t(on_type constraint_) : constraint(std::move(constraint_)) {} }; - template - struct from_t { - using tuple_type = std::tuple; - }; - - template - using is_from = polyfill::is_specialization_of; - template using is_constrained_join = polyfill::is_detected; @@ -736,6 +728,23 @@ namespace sqlite_orm { check_if_is_template>, T>; + template + struct from_t { + using tuple_type = std::tuple; + }; + + template + using is_from = polyfill::is_specialization_of; + + template + struct from2_t { + using tuple_type = std::tuple; + + tuple_type table_expressions; + }; + + template + using is_from2 = polyfill::is_specialization_of; } } @@ -745,8 +754,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * `storage.select(&User::id, from());` */ template - internal::from_t from() { - static_assert(sizeof...(Tables) > 0, ""); + constexpr internal::from_t from() { + static_assert(sizeof...(Tables) > 0); return {}; } @@ -756,11 +765,35 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * `storage.select(&User::id, from<"a"_alias.for_>());` */ template - auto from() { + constexpr auto from() { return from...>(); } #endif +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Explicit FROM for an eponymous virtual table used as a table-valued function. Usage: + * `storage.select(asterisk(), from(dbstat_table("main", true)));` + */ + template + requires ((orm_refers_to_recordset || orm_table_valued_expression) && ...) + constexpr internal::from2_t from(TableExpr... tableExpressions) { + return {{std::move(tableExpressions)...}}; + } +#else + /** + * Explicit FROM for an eponymous virtual table used as a table-valued function. Usage: + * `storage.select(asterisk(), from(dbstat_table("main", true)));` + */ + template + constexpr internal::from2_t from(TableExpr... tableExpressions) { + static_assert( + ((internal::is_referring_to_recordset_v || internal::is_table_valued_expression_v) && + ...)); + return {{std::move(tableExpressions)...}}; + } +#endif + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { @@ -823,7 +856,11 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::is_base_of, std::is_base_of, is_operator_argument, - is_operator_argument>::value, + is_operator_argument>::value +#ifndef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + && !is_table_reference_v +#endif + , bool> = true> constexpr is_equal_t, unwrap_expression_t> operator==(L l, R r) { return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; @@ -1024,8 +1061,13 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(l), std::move(r)}; } - template - constexpr internal::is_equal_with_table_t is_equal(R rhs) { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + template, bool> = true> + [[deprecated("Use the usual `is_equal` function to compare the hidden FTS5 'any' field or a field of your FTS " + "table instead")]] + constexpr internal::is_equal_with_table_t is_equal(R rhs) { return {std::move(rhs)}; } diff --git a/dev/constraints.h b/dev/constraints.h index eba1543b..3c45749b 100644 --- a/dev/constraints.h +++ b/dev/constraints.h @@ -19,7 +19,6 @@ #include "field_of.h" #include "table_type_of.h" #include "type_printer.h" -#include "table_reference.h" namespace sqlite_orm { @@ -169,7 +168,7 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3024000 /** - * Auxiliary virtual table column + * Auxiliary virtual table column constraint */ struct auxiliary_t {}; diff --git a/dev/core_functions.h b/dev/core_functions.h index 94d8cd2d..fd984dc5 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -16,8 +16,10 @@ #include "serialize_result_type.h" #include "operators.h" #include "tags.h" -#include "table_reference.h" +#include "field_traits.h" +#include "alias_traits.h" #include "ast/into.h" +#include "field_of.h" namespace sqlite_orm { namespace internal { @@ -2156,8 +2158,68 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } - template - internal::highlight_t highlight(X x, Y y, Z z) { + struct fts5; + + /** + * The FTS5 highlight function. The table type is specified as a template argument. + * See https://www.sqlite.org/fts5.html#the_highlight_function + * + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + template, bool> = true> + [[deprecated("Use the `highlight` function accepting the hidden FTS5 'any' field instead")]] + constexpr internal::highlight_t highlight(X x, Y y, Z z) { return {std::move(x), std::move(y), std::move(z)}; } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template + requires (internal::hidden_column_of_vtab) + constexpr internal::highlight_t highlight(const CP& /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } + + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template + requires (internal::hidden_field_of_vtab) + constexpr internal::highlight_t + highlight(F Hidden::* /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } +#else + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template, bool> = true> + constexpr internal::highlight_t highlight(const CP& /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } + + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template, bool> = true> + constexpr internal::highlight_t + highlight(F Hidden::* /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } +#endif } diff --git a/dev/field_of.h b/dev/field_of.h index da79b8a8..53fdee1d 100644 --- a/dev/field_of.h +++ b/dev/field_of.h @@ -4,6 +4,7 @@ #include // std::enable_if, std::is_convertible, std::bool_constant #endif +#include "functional/cxx_type_traits_polyfill.h" #include "member_traits/member_traits.h" namespace sqlite_orm { diff --git a/dev/field_traits.h b/dev/field_traits.h new file mode 100644 index 00000000..8d189ff5 --- /dev/null +++ b/dev/field_traits.h @@ -0,0 +1,37 @@ +#pragma once + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::enable_if, std::is_same +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::same_as +#endif +#endif + +#include "member_traits/member_traits.h" +#include "type_traits.h" +#include "field_of.h" + +namespace sqlite_orm::internal { +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept hidden_field_of_vtab = + is_field_of_v>; + + template + concept hidden_column_of_vtab = std::same_as>, typename VTabStruct::hidden>; +#else + template + inline constexpr bool is_hidden_column_of_vtab_v = false; + + template + inline constexpr bool is_hidden_column_of_vtab_v< + CP, + VTabStruct, + std::enable_if_t>, typename VTabStruct::hidden>::value>> = + true; + + template + inline constexpr bool is_hidden_field_of_vtab_v = + is_field_of_v>; +#endif +} diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index 175e73b2..37bcc0d7 100644 --- a/dev/functional/mpl.h +++ b/dev/functional/mpl.h @@ -525,5 +525,8 @@ namespace sqlite_orm { */ template class Template> using check_if_has_template = mpl::contains>; + + template class ProjOp, class T> + using check_if_projected_is_type = mpl::pass_result_of_fn, ProjOp>; } } diff --git a/dev/literal.h b/dev/literal.h index 65cb1b26..c27a2578 100644 --- a/dev/literal.h +++ b/dev/literal.h @@ -1,17 +1,22 @@ #pragma once -namespace sqlite_orm { - namespace internal { +#include "functional/cxx_type_traits_polyfill.h" - /* - * Protect an otherwise bindable element so that it is always serialized as a literal value. - */ - template - struct literal_holder { - using type = T; +namespace sqlite_orm::internal { - type value; - }; + /* + * Protect an otherwise bindable element so that it is always serialized as a literal value. + */ + template + struct literal_holder { + using type = T; - } + type value; + }; + + template + inline constexpr bool is_literal_v = polyfill::is_specialization_of_v; + + template + using is_literal = polyfill::bool_constant>; } diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index a043daf6..bce46d66 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -4,6 +4,7 @@ #include // std::remove_const #endif +#include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" #include "table_reference.h" #include "alias_traits.h" @@ -12,17 +13,23 @@ namespace sqlite_orm { namespace internal { - /** - * If T is a table reference or recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, - * otherwise unqualified T. + /** + * Defines the `type` typename to be: + * - The unqualified unwrapped table reference type if T is a table reference. + * - The unqualified aliased type if T is a recordset alias. + * - The enclosing data struct for eponymous virtual tables with hidden columns. + * - ... otherwise unqualified T. */ template struct mapped_type_proxy : std::remove_const {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template - struct mapped_type_proxy : R {}; -#endif + template + struct mapped_type_proxy> { + using type = enclosing_type_t; + }; + + template + struct mapped_type_proxy> : R {}; template struct mapped_type_proxy> : std::remove_const> {}; diff --git a/dev/node_tuple.h b/dev/node_tuple.h index b3fd9d57..d64ad1aa 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -84,7 +84,10 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> : node_tuple {}; + struct node_tuple, void> : node_tuple {}; + + template + struct node_tuple, void> : node_tuple {}; /** * Column alias @@ -98,12 +101,6 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; - /** - * Literal - */ - template - struct node_tuple, void> : node_tuple {}; - template struct node_tuple, void> : node_tuple {}; @@ -261,5 +258,17 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple_for {}; + + template + struct node_tuple, void> : node_tuple_for {}; + + /* + * Table reference as part of FROM clause: skip + */ + template + struct node_tuple> : node_tuple {}; + + template + struct node_tuple, void> : node_tuple_for {}; } } diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index 9cfd8477..d5a6bc98 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -13,6 +13,7 @@ #include "functional/cxx_functional_polyfill.h" #include "functional/gsl.h" #include "tuple_helper/tuple_traits.h" +#include "type_traits.h" #include "connection_holder.h" #include "select_constraints.h" #include "values.h" @@ -352,6 +353,17 @@ namespace sqlite_orm { template using main_dml_t = polyfill::remove_cvref_t()))>; + + template + constexpr void validate_get_all_conditions() { + using from2_index_sequence = filter_tuple_sequence_t; + if constexpr (from2_index_sequence::size() > 0) { + using from_type = std::tuple_element_t(from2_index_sequence{}), Tpl>; + // check whether one of table expressions' type is the same as the requested table type + static_assert(mpl::invoke_t>, from_type>::value, + "Requested object type must be listed in explicit FROM clause"); + } + } } } @@ -770,6 +782,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_t get_all(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } @@ -810,6 +823,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_pointer_t get_all_pointer(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } @@ -839,6 +853,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_optional_t get_all_optional(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED diff --git a/dev/schema/column.h b/dev/schema/column.h index 9f1750ff..35e05949 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -99,6 +99,15 @@ namespace sqlite_orm { template struct column_t : column_identifier, column_field, column_constraints {}; + /** + * Definition of a hidden column. + * + * Implementation note: it is a separate type to make coding easier - hidden columns do not participate in normal column handling, + * e.g. they are not counted as columns when constructing objects, and are only needed when finding columns or for table-valued functions. + */ + template + struct hidden_column : column_identifier, column_field, column_constraints {}; + template struct column_field_expression { using type = void; @@ -118,6 +127,12 @@ namespace sqlite_orm { template using is_column = polyfill::bool_constant>; + template + inline constexpr bool is_hidden_column_v = polyfill::is_specialization_of::value; + + template + using is_hidden_column = polyfill::bool_constant>; + template using col_index_sequence_of = filter_tuple_sequence_t; @@ -139,6 +154,31 @@ namespace sqlite_orm { check_if_has_not::template fn, constraints_type_t, filter_tuple_sequence_t>; + + template + using hidden_col_index_sequence_of = filter_tuple_sequence_t; + + template + using all_col_index_sequence_with_field_type = filter_tuple_sequence_t< + Elements, + check_if_is_type::template fn, + field_type_t, + filter_tuple_sequence_t::template fn>>; + +#if SQLITE_VERSION_NUMBER >= 3031000 + /** + * Factory function for a column definition from a member object pointer for hidden virtual table columns. + */ + template = true> + hidden_column make_hidden_column(std::string name, M memberPointer, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + } +#endif } } diff --git a/dev/schema/table_base.h b/dev/schema/table_base.h index e40455b5..73197b5f 100644 --- a/dev/schema/table_base.h +++ b/dev/schema/table_base.h @@ -99,7 +99,7 @@ namespace sqlite_orm::internal { const std::string* res = nullptr; iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, + all_col_index_sequence_with_field_type{}, [&res, memberPointer](auto& column) { if (compare_fields(column.member_pointer, memberPointer) || compare_fields(column.setter, memberPointer)) { diff --git a/dev/schema/virtual_table.h b/dev/schema/virtual_table.h index 6306b9d4..dbf6dbf4 100644 --- a/dev/schema/virtual_table.h +++ b/dev/schema/virtual_table.h @@ -119,9 +119,9 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Factory function for a virtual table. * - * The mapped object type is determined implicitly from the first column definition. + * The mapped object type is explicitly specified. */ - template>::object_type> + template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); @@ -130,12 +130,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Factory function for a virtual table. * - * The mapped object type is explicitly specified. + * The mapped object type is determined implicitly from the first column definition. */ - template + template>::object_type> internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); + return make_virtual_table(std::move(name), std::move(definition)); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/dev/select_constraints.h b/dev/select_constraints.h index b374f6e6..1aa3a522 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -448,7 +448,8 @@ namespace sqlite_orm { static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 GROUP BY blocks"); static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 ORDER BY blocks"); static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 LIMIT blocks"); - static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 FROM blocks"); + static_assert(mpl::invoke_t>, T>::value <= 1, + "a single query cannot contain > 1 FROM blocks"); } } } diff --git a/dev/serializing_util.h b/dev/serializing_util.h index cf2efb21..080519ca 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -107,6 +107,7 @@ namespace sqlite_orm { conditions_tuple, actions_tuple, expressions_tuple, + filtered_expressions_tuple, dynamic_expressions, compound_expressions, serialized, @@ -137,6 +138,7 @@ namespace sqlite_orm { constexpr streaming streaming_conditions_tuple{}; constexpr streaming streaming_actions_tuple{}; constexpr streaming streaming_expressions_tuple{}; + constexpr streaming streaming_filtered_expressions_tuple{}; constexpr streaming streaming_dynamic_expressions{}; constexpr streaming streaming_compound_expressions{}; constexpr streaming streaming_serialized{}; @@ -193,6 +195,22 @@ namespace sqlite_orm { return ss; } + // serialize and stream a tuple of expressions including only those specified by an index sequence; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, Seq, T, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + const auto& included_index_sequence = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + iterate_tuple(args, included_index_sequence, [&ss, &context, first = true](auto& arg) mutable { + static constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] << serialize(arg, context); + }); + return ss; + } + // serialize and stream expressions of a compound statement; // separated by compound operator template @@ -416,7 +434,7 @@ namespace sqlite_orm { return ss; } - // serialize and stream a tuple of column constraints; + // serialize and stream a tuple of column constraints excluding auxiliary columns; // space + space-separated template std::ostream& operator<<(std::ostream& ss, diff --git a/dev/sqlite_schema_table.h b/dev/sqlite_schema_table.h index 1dd48dab..7cd61ecf 100644 --- a/dev/sqlite_schema_table.h +++ b/dev/sqlite_schema_table.h @@ -6,7 +6,7 @@ #include "schema/column.h" #include "schema/table.h" -#include "column_pointer.h" +#include "table_reference.h" #include "alias.h" SQLITE_ORM_EXPORT namespace sqlite_orm { @@ -40,8 +40,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { make_column("sql", &sqlite_master::sql)); } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED inline constexpr orm_table_reference auto sqlite_master_table = c(); +#else + inline constexpr auto sqlite_master_table = c(); +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline constexpr orm_table_alias auto sqlite_schema = "sqlite_schema"_alias.for_(); #endif } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index c008311d..18290978 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -175,11 +175,20 @@ namespace sqlite_orm { // Eponymous virtual tables serialize only table values. Their definition is built-in, fixed and implicit template = true> - std::string serialize_virtual_table_definition(const Definition&, const Ctx&) { - return {}; + std::string serialize_virtual_table_definition(const Elements& elements, const Ctx& context) { + using table_values_index_sequence = filter_tuple_sequence_t; + + if constexpr (table_values_index_sequence::size() == 0) { + return {}; + } else { + std::stringstream ss; + ss << "(" << streaming_filtered_expressions_tuple(elements, table_values_index_sequence{}, context) + << ")"; + return ss.str(); + } } template = true> std::string serialize_virtual_table_definition(const Elements& elements, const Ctx& context) { using traits_type = ModTraits; + using excluding_hidden_index_sequence = + filter_tuple_sequence_t::template fn>; auto subContext = context; subContext.omit_column_type = traits_type::omit_column_type::value; std::stringstream ss; - ss << "(" << streaming_expressions_tuple(elements, subContext) << ")"; + ss << "(" << streaming_filtered_expressions_tuple(elements, excluding_hidden_index_sequence{}, subContext) + << ")"; return ss.str(); } @@ -276,10 +288,9 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { static_assert(is_bindable_v>, "A literal value must be also bindable"); - Ctx literalCtx = context; + auto literalCtx = context; literalCtx.replace_bindable_with_question = false; - statement_serializer> serializer{}; - return serializer(literal.value, literalCtx); + return serialize(literal.value, literalCtx); } }; @@ -351,8 +362,8 @@ namespace sqlite_orm { }; template - struct statement_serializer, void> { - using statement_type = match_t; + struct statement_serializer, void> { + using statement_type = match_with_table_t; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -364,6 +375,19 @@ namespace sqlite_orm { } }; + template + struct statement_serializer, void> { + using statement_type = match_t; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + std::stringstream ss; + ss << serialize(statement.field, context) << " MATCH " << serialize(statement.argument, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = column_alias; @@ -479,10 +503,10 @@ namespace sqlite_orm { using statement_type = E; template - SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& e, + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& expression, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (auto* columnName = find_column_name(context.db_objects, e)) { + if (auto* columnName = find_column_name(context.db_objects, expression)) { ss << streaming_identifier( !context.omit_table_name ? lookup_table_name>(context.db_objects) : "", *columnName, @@ -1770,13 +1794,16 @@ namespace sqlite_orm { std::string serialize_get_all_impl(const T& getAll, const Ctx& context) { using table_type = type_t; using mapped_type = mapped_type_proxy_t; + constexpr bool hasExplicitFrom2 = tuple_has::value; auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)) - << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()) - << streaming_conditions_tuple(getAll.conditions, context); + ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)); + if constexpr (!hasExplicitFrom2) { + ss << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()); + } + ss << streaming_conditions_tuple(getAll.conditions, context); return ss.str(); } @@ -1944,7 +1971,8 @@ namespace sqlite_orm { ss << streaming_serialized(get_column_names(sel.col, subCtx)); using conditions_tuple = typename statement_type::conditions_type; - constexpr bool hasExplicitFrom = tuple_has::value; + constexpr bool hasExplicitFrom = + tuple_has::template fn>::value; if constexpr (!hasExplicitFrom) { using joins_index_sequence = filter_tuple_sequence_t; @@ -2050,8 +2078,8 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; ss << "FROM "; - iterate_tuple([&context, &ss, first = true](auto* dummyItem) mutable { - using table_type = std::remove_pointer_t; + iterate_tuple([&context, &ss, first = true](auto* dummy) mutable { + using table_type = std::remove_pointer_t; static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] @@ -2062,6 +2090,35 @@ namespace sqlite_orm { } }; + template + struct statement_serializer> { + using statement_type = From; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& from, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + std::stringstream ss; + ss << "FROM "; + iterate_tuple(from.table_expressions, + [&context, &ss, first = true](const auto& tableExpression) mutable { + using expression_type = polyfill::remove_cvref_t; + using table_type = type_t; + + static constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] + << streaming_identifier( + lookup_table_name>(context.db_objects), + alias_extractor::as_alias()); + + if constexpr (is_table_valued_expression_v) { + ss << '(' << streaming_expressions_tuple(tableExpression.table_values, context) + << ')'; + } + }); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = old_t; diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 38c8f7f9..0ad475f3 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -44,9 +44,9 @@ namespace sqlite_orm { /** * Find column name by its type and member pointer. */ - template = true> - const std::string* find_column_name(const DBOs& dbObjects, F O::* field) { - return pick_table(dbObjects).find_column_name(field); + template = true> + const std::string* find_column_name(const DBOs& dbObjects, F Lookup::* field) { + return pick_table>(dbObjects).find_column_name(field); } /** diff --git a/dev/table_name_collector.h b/dev/table_name_collector.h index 4e93e58d..5b03d128 100644 --- a/dev/table_name_collector.h +++ b/dev/table_name_collector.h @@ -35,9 +35,9 @@ namespace sqlite_orm { template SQLITE_ORM_STATIC_CALLOP void operator()(const T&) SQLITE_ORM_OR_CONST_CALLOP {} - template - void operator()(F O::*) { - this->table_names.emplace(lookup_table_name(this->db_objects), ""); + template + void operator()(F Lookup::*) { + this->table_names.emplace(lookup_table_name>(this->db_objects), ""); } template diff --git a/dev/table_reference.h b/dev/table_reference.h index 0c8073ed..7819d1ff 100644 --- a/dev/table_reference.h +++ b/dev/table_reference.h @@ -1,50 +1,80 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::remove_const, std::type_identity +#include // std::enable_if, std::remove_const, std::type_identity +#include // std::move +#include #endif #include "functional/cxx_type_traits_polyfill.h" +#include "alias_traits.h" +#include "literal.h" -namespace sqlite_orm { - namespace internal { - /* - * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. - */ - template - struct table_reference : polyfill::type_identity {}; - - template - struct decay_table_ref : std::remove_const {}; - template - struct decay_table_ref> : polyfill::type_identity {}; - template - struct decay_table_ref> : polyfill::type_identity {}; - - template - using decay_table_ref_t = typename decay_table_ref::type; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template - using auto_decay_table_ref_t = typename decay_table_ref::type; -#endif +namespace sqlite_orm::internal { + /* + * Bound input arguments for eponymous virtual tables used in table-valued function calls. + */ + template + struct table_valued_expression { + using type = Table; + using constraints_type = std::tuple; - template - inline constexpr bool is_table_reference_v = - polyfill::is_specialization_of_v, table_reference>; + constraints_type table_values; + }; - template - struct is_table_reference : polyfill::bool_constant> {}; - } + template + inline constexpr bool is_table_valued_expression_v = polyfill::is_specialization_of_v; + + template + using is_table_valued_expression = polyfill::bool_constant>; + + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions and virtual tables usable as table-valued functions. + */ + template + struct table_reference : polyfill::type_identity { +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Make a table-valued function call. + */ + template + constexpr table_valued_expression operator()(Args... arguments) const { + return {{ {std::move(arguments)}... }}; + } +#else + /** + * Make a table-valued function call. + */ + template + constexpr table_valued_expression operator()(Args... arguments) const { + return {{{std::move(arguments)}...}}; + } +#endif + }; } SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED - /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. - * - * A concrete table reference has the following traits: - * - specialization of `table_reference`, whose `type` typename references a mapped object. + template + concept orm_table_valued_expression = internal::is_table_valued_expression_v; +#endif + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Make a table reference. + */ + template + requires (!orm_recordset_alias) + consteval internal::table_reference c() { + return {}; + } +#else + /** + * Make a table reference. */ - template - concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + template, bool> = true> + constexpr internal::table_reference c() { + return {}; + } #endif } diff --git a/dev/type_traits.h b/dev/type_traits.h index 32b801d8..63f22c1b 100644 --- a/dev/type_traits.h +++ b/dev/type_traits.h @@ -88,9 +88,6 @@ 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; @@ -109,6 +106,12 @@ namespace sqlite_orm { template using alias_type_t = typename As::alias_type; + template + using enclosing_type_t = typename T::enclosing_type; + + template + using enclosing_type_of_t = typename T::template _of::enclosing_type; + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template using udf_type_t = typename T::udf_type; diff --git a/dev/vtabs/dbstat.h b/dev/vtabs/dbstat.h index 742ab0cc..bcc9b0b5 100644 --- a/dev/vtabs/dbstat.h +++ b/dev/vtabs/dbstat.h @@ -2,16 +2,20 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ENABLE_DBSTAT_VTAB -#include // std::false_type, std::true_type -#include // std::make_tuple -#include // std::move +#include // std::false_type, std::true_type, std::is_convertible +#include // std::tuple_size, std::make_tuple, std::tuple +#include // std::move, std::index_sequence_for +#include // std::string #endif #endif #include "../functional/gsl.h" +#include "../tuple_helper/tuple_filter.h" +#include "../member_traits/member_traits.h" #include "../schema/virtual_table.h" #include "../schema/column.h" -#include "../column_pointer.h" +#include "../literal.h" +#include "../table_reference.h" #ifdef SQLITE_ENABLE_DBSTAT_VTAB namespace sqlite_orm::internal { @@ -39,12 +43,15 @@ namespace sqlite_orm::internal { }; template - inline virtual_table_definition using_dbstat(Cs... columns) { + inline virtual_table_definition make_dbstat_definition(Cs... columns) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); } } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Data structure for the `dbstat` eponymous virtual table. + */ struct dbstat { std::string name; std::string path; @@ -56,21 +63,55 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { int mx_payload = 0; int pgoffset = 0; int pgsize = 0; + + /** + * Hidden columns of the `dbstat` virtual table, which can be referred to using a 'column pointer'. + * See `hidden_columns`. + */ + struct hidden { + std::string schema; +#if SQLITE_VERSION_NUMBER >= 3031000 + bool aggregate = false; +#endif + + using enclosing_type = dbstat; + }; }; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED inline constexpr orm_table_reference auto dbstat_table = c(); +#else + inline constexpr auto dbstat_table = c(); #endif /** * Factory function for a DBSTAT virtual table definition. * If no schema is specified then the main schema is used. * + * The hidden DBSTAT columns 'schema' and 'aggregate' are mapped into each table definition. + * * Though the DBSTAT virtual table is an eponymous table SQLite allows to create a virtual table instance with a different name. - * This is mostly useful with binding input arguments, e.g. a different schema than "main", which is yet unimplemented. + * This is mostly useful with binding input arguments (so-called table values), e.g. a different schema than "main" or whether to query aggregated results. */ - inline auto using_dbstat() { - return internal::using_dbstat(make_column("name", &dbstat::name), + template + auto using_dbstat(Value... tableValues) { + using namespace ::sqlite_orm::internal; + using expected_hidden_types = std::tuple +#if SQLITE_VERSION_NUMBER >= 3031000 + , + member_field_type_t +#endif + >; + static_assert(sizeof...(Value) <= std::tuple_size::value, + "You may only pass the schema name and the aggregation flag"); + using input_value_types = std::tuple; + // make a tuple of types from expected types limited to the number of passed in table values + using final_expected_types = + tuple_from_index_sequence_t>; + static_assert(std::is_convertible::value, + "The schema name must be a string value, the aggregate flag a boolean value"); + + return make_dbstat_definition(make_column("name", &dbstat::name), make_column("path", &dbstat::path), make_column("pageno", &dbstat::pageno), make_column("pagetype", &dbstat::pagetype), @@ -79,7 +120,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { make_column("unused", &dbstat::unused), make_column("mx_payload", &dbstat::mx_payload), make_column("pgoffset", &dbstat::pgoffset), - make_column("pgsize", &dbstat::pgsize)); + make_column("pgsize", &dbstat::pgsize), + make_hidden_column("schema", &dbstat::hidden::schema), +#if SQLITE_VERSION_NUMBER >= 3031000 + make_hidden_column("aggregate", &dbstat::hidden::aggregate), +#endif + literal_holder{std::move(tableValues)}...); } /** diff --git a/dev/vtabs/fts5.h b/dev/vtabs/fts5.h index 372df833..1857c939 100644 --- a/dev/vtabs/fts5.h +++ b/dev/vtabs/fts5.h @@ -2,14 +2,16 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #if SQLITE_VERSION_NUMBER >= 3009000 -#include // std::make_tuple -#include // std::forward +#include // std::tuple_size, std::make_tuple, std::get +#include // std::forward, std::move +#include #endif #endif -#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cxx_optional.h" #include "../functional/gsl.h" #include "../functional/mpl.h" +#include "../field_of.h" #include "../schema/virtual_table.h" #include "../schema/column.h" #include "../constraints.h" @@ -17,12 +19,14 @@ #if SQLITE_VERSION_NUMBER >= 3009000 namespace sqlite_orm::internal { template - using is_fts5_table_element_or_constraint = mpl::invoke_t, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template>, - T>; + inline constexpr bool is_fts5_table_element_or_constraint_v = + mpl::invoke_t, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template>, + T>::value; + struct fts5_module_tag { // simplify conceptual/meta programming using module_type = fts5_module_tag; @@ -31,18 +35,77 @@ namespace sqlite_orm::internal { return "fts5"; } }; + + template + inline virtual_table_definition make_fts5_definition(Cs... definition) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(definition)...)}}); + } } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Class namespace for hidden `fts5` columns. + */ + struct fts5 { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + /** + Hidden columns of the `fts5` virtual table, which can be referred to using a 'column pointer' or using `hidden_fields_of<>` fields. + */ + struct hidden { + /// Hidden column with the same name as the table itself. It is a blob only meaningful when matching any of the other FTS5 columns. + std::vector any; + std::optional rank; + + /** + * Internal meta programming helper to rebind hidden columns to a specific object type. + */ + template + struct _of; + }; + + /** + Map hidden columns to a specific object type to simplify programming. + + Example: + struct Post { + using hidden = fts5::hidden_fields_of; + }; + */ + template + struct hidden_fields_of { + static constexpr auto any_field = internal::as_field_of>(&fts5::hidden::any); + static constexpr auto rank_field = internal::as_field_of>(&fts5::hidden::rank); + + hidden_fields_of() = delete; + }; +#endif + + fts5() = delete; + }; + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + template + struct fts5::hidden::_of : fts5::hidden { + using enclosing_type = O; + }; +#endif + /** * Factory function for a FTS5 virtual table definition. + * + * The hidden FTS5 columns 'any' [as a placeholder for the column with the same name as the table] and 'rank' are mapped into each table definition. */ template - internal::virtual_table_definition using_fts5(Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + auto using_fts5(Cs... definition) { + using namespace ::sqlite_orm::internal; + return make_fts5_definition(std::forward(definition)... +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + , + // note: eponymous column name is filled in later in `make_virtual_table()` + make_hidden_column("", &fts5::hidden::any), + make_hidden_column("rank", &fts5::hidden::rank) +#endif + ); } /** @@ -55,10 +118,26 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template [[deprecated("Specify the explicit object type when calling `make_virtual_table()`.")]] internal::virtual_table_description using_fts5(Cs... definition) { - static_assert(polyfill::conjunction_v...>, + static_assert((internal::is_fts5_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); } + + template + internal::virtual_table + make_virtual_table(std::string tableName, + internal::virtual_table_definition definition) { + using namespace ::sqlite_orm::internal; + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + constexpr size_t eponymous_column_index = + std::tuple_size>>::value - 2; + // fill in the eponymous hidden column name + std::get(definition.elements).name = tableName; +#endif + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(tableName), std::move(definition)}); + } } #endif diff --git a/dev/vtabs/generate_series.h b/dev/vtabs/generate_series.h new file mode 100644 index 00000000..b8d4f040 --- /dev/null +++ b/dev/vtabs/generate_series.h @@ -0,0 +1,96 @@ +#pragma once + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#if SQLITE_VERSION_NUMBER >= 3008012 +#include // std::tuple_element, std::make_tuple +#include // std::forward +#endif +#endif + +#include "../functional/gsl.h" +#include "../schema/virtual_table.h" +#include "../schema/column.h" +#include "../table_reference.h" + +#if SQLITE_VERSION_NUMBER >= 3008012 +namespace sqlite_orm::internal { + struct generate_series_module_tag { + // simplify conceptual/meta programming + using module_type = generate_series_module_tag; + + static constexpr orm_gsl::czstring name() { + return "generate_series"; + } + }; + + template<> + struct virtual_table_module_traits { + using module_type = generate_series_module_tag; + using is_eponymous = std::true_type; + using is_without_rowid = std::false_type; + using omit_column_type = std::true_type; + }; + + template + struct virtual_table_traits + : virtual_table_module_traits { + using definition_type = table_definition; + using elements_type = typename definition_type::elements_type; + }; + + template + inline virtual_table_definition make_generate_series_definition(Cs... columns) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Data structure for the `generate_series` eponymous virtual table. + */ + struct generate_series { + int value = 0; + + /** + * Hidden columns of the `generate_series` virtual table. + */ + struct hidden { + int start; + int stop; + int step; + + using enclosing_type = generate_series; + }; + }; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + inline constexpr orm_table_reference auto generate_series_table = c(); +#else + inline constexpr auto generate_series_table = c(); +#endif +} + +namespace sqlite_orm::internal { + /** + * Factory function for a `generate_series` virtual table definition. + * + * The hidden generate_series columns 'start', 'stop' and 'step' are mapped into each table definition. + */ + inline auto using_generate_series() { + return make_generate_series_definition(make_column("value", &generate_series::value), + make_hidden_column("start", &generate_series::hidden::start), + make_hidden_column("stop", &generate_series::hidden::stop), + make_hidden_column("step", &generate_series::hidden::step)); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Factory function for the `generate_series` default eponymous virtual table. + */ + inline auto make_generate_series_table() { + return make_virtual_table(internal::generate_series_module_tag::name(), + internal::using_generate_series()); + } +} +#endif diff --git a/dev/vtabs/rtree.h b/dev/vtabs/rtree.h index ddd2d0ca..01093660 100644 --- a/dev/vtabs/rtree.h +++ b/dev/vtabs/rtree.h @@ -9,16 +9,18 @@ #endif #endif -#include "../functional/cxx_type_traits_polyfill.h" #include "../functional/gsl.h" #include "../functional/mpl.h" +#include "../tuple_helper/tuple_filter.h" +#include "../type_traits.h" #include "../schema/virtual_table.h" #include "../schema/column.h" #ifdef SQLITE_ENABLE_RTREE namespace sqlite_orm::internal { template - using is_rtree_table_element_or_constraint = mpl::invoke_t>, T>; + inline constexpr bool is_rtree_table_element_or_constraint_v = + mpl::invoke_t>, T>::value; struct rtree_module_tag { // simplify conceptual/meta programming @@ -49,14 +51,13 @@ namespace sqlite_orm::internal { rtree_col_index_sequence, field_type_t>::value; - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); + static_assert((is_rtree_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); static_assert(nRTreeColumns >= 3 && nRTreeColumns <= 11 && nRTreeColumns % 2 == 1, "An RTREE table must have between 1 and 5 dimensions consisting of min/max-value pair columns"); static_assert( nRTreeColumnsOfExpectedType == nRTreeColumns - 1, R"(The min/max-value pair columns need to be 32-bit floating point values for RTREE virtual tables and 32-bit signed integers for RTREE_I32 virtual tables, as they are stored as such)"); - static_assert(std::is_same::field_type, int64>::value, + static_assert(std::is_same>, int64>::value, "The type of the first column must be a 64-bit integer"); } } diff --git a/dev/vtabs/vtabs.h b/dev/vtabs/vtabs.h index ca745e2e..9a28ffff 100644 --- a/dev/vtabs/vtabs.h +++ b/dev/vtabs/vtabs.h @@ -1,5 +1,6 @@ #pragma once #include "dbstat.h" +#include "generate_series.h" #include "fts5.h" #include "rtree.h" diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e33981e6..a929f52f 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1395,6 +1395,9 @@ namespace sqlite_orm { */ template class Template> using check_if_has_template = mpl::contains>; + + template class ProjOp, class T> + using check_if_projected_is_type = mpl::pass_result_of_fn, ProjOp>; } } @@ -1928,9 +1931,6 @@ 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; @@ -1949,6 +1949,12 @@ namespace sqlite_orm { template using alias_type_t = typename As::alias_type; + template + using enclosing_type_t = typename T::enclosing_type; + + template + using enclosing_type_of_t = typename T::template _of::enclosing_type; + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template using udf_type_t = typename T::udf_type; @@ -2042,8 +2048,8 @@ namespace sqlite_orm::internal { // #include "alias_traits.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::is_base_of, std::is_same -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#include // std::is_base_of, std::is_same, std::remove_const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include #endif #endif @@ -2052,21 +2058,17 @@ namespace sqlite_orm::internal { // #include "type_traits.h" -// #include "table_reference.h" - -#ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::remove_const, std::type_identity -#endif +SQLITE_ORM_EXPORT namespace sqlite_orm { -// #include "functional/cxx_type_traits_polyfill.h" + /** @short Base class for a custom table alias, column alias or expression alias. + */ + struct alias_tag {}; +} namespace sqlite_orm { namespace internal { - /* - * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. - */ template - struct table_reference : polyfill::type_identity {}; + struct table_reference; template struct decay_table_ref : std::remove_const {}; @@ -2082,37 +2084,6 @@ namespace sqlite_orm { using auto_decay_table_ref_t = typename decay_table_ref::type; #endif - template - inline constexpr bool is_table_reference_v = - polyfill::is_specialization_of_v, table_reference>; - - template - struct is_table_reference : polyfill::bool_constant> {}; - } -} - -SQLITE_ORM_EXPORT namespace sqlite_orm { -#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED - /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. - * - * A concrete table reference has the following traits: - * - specialization of `table_reference`, whose `type` typename references a mapped object. - */ - template - concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; -#endif -} - -SQLITE_ORM_EXPORT namespace sqlite_orm { - - /** @short Base class for a custom table alias, column alias or expression alias. - */ - struct alias_tag {}; -} - -namespace sqlite_orm { - namespace internal { - template inline constexpr bool is_alias_v = std::is_base_of::value; @@ -2128,6 +2099,13 @@ namespace sqlite_orm { template struct is_column_alias : is_alias {}; + template + inline constexpr bool is_table_reference_v = + polyfill::is_specialization_of_v, table_reference>; + + template + struct is_table_reference : polyfill::bool_constant> {}; + /** @short Alias of any type of record set, see `orm_recordset_alias`. */ template @@ -2160,11 +2138,20 @@ namespace sqlite_orm { template using is_cte_moniker = polyfill::bool_constant>; + + /** @short Referring to a recordset. + */ + template + inline constexpr bool is_referring_to_recordset_v = + polyfill::disjunction_v, is_recordset_alias>; + + template + using is_referring_to_recordset = polyfill::bool_constant>; } } SQLITE_ORM_EXPORT namespace sqlite_orm { -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept orm_alias = std::derived_from; @@ -2177,6 +2164,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template concept orm_column_alias = (orm_alias && !orm_names_type); + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; + /** @short Specifies that a type is an alias of any type of record set. * * A record set alias has the following traits: @@ -2227,6 +2222,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #include // std::enable_if, std::is_convertible, std::bool_constant #endif +// #include "functional/cxx_type_traits_polyfill.h" + // #include "member_traits/member_traits.h" namespace sqlite_orm { @@ -2354,7 +2351,7 @@ namespace sqlite_orm { // #include "column_pointer.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_convertible +#include // std::enable_if, std::is_convertible, std::is_base_of #include // std::move #endif @@ -2364,6 +2361,108 @@ namespace sqlite_orm { // #include "table_reference.h" +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::enable_if, std::remove_const, std::type_identity +#include // std::move +#include +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +// #include "alias_traits.h" + +// #include "literal.h" + +// #include "functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm::internal { + + /* + * Protect an otherwise bindable element so that it is always serialized as a literal value. + */ + template + struct literal_holder { + using type = T; + + type value; + }; + + template + inline constexpr bool is_literal_v = polyfill::is_specialization_of_v; + + template + using is_literal = polyfill::bool_constant>; +} + +namespace sqlite_orm::internal { + /* + * Bound input arguments for eponymous virtual tables used in table-valued function calls. + */ + template + struct table_valued_expression { + using type = Table; + using constraints_type = std::tuple; + + constraints_type table_values; + }; + + template + inline constexpr bool is_table_valued_expression_v = polyfill::is_specialization_of_v; + + template + using is_table_valued_expression = polyfill::bool_constant>; + + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions and virtual tables usable as table-valued functions. + */ + template + struct table_reference : polyfill::type_identity { +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Make a table-valued function call. + */ + template + constexpr table_valued_expression operator()(Args... arguments) const { + return {{ {std::move(arguments)}... }}; + } +#else + /** + * Make a table-valued function call. + */ + template + constexpr table_valued_expression operator()(Args... arguments) const { + return {{ {std::move(arguments)}... }}; + } +#endif + }; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_table_valued_expression = internal::is_table_valued_expression_v; +#endif + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Make a table reference. + */ + template + requires (!orm_recordset_alias) + consteval internal::table_reference c() { + return {}; + } +#else + /** + * Make a table reference. + */ + template, bool> = true> + constexpr internal::table_reference c() { + return {}; + } +#endif +} + // #include "alias_traits.h" // #include "tags.h" @@ -2410,7 +2509,11 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template = true> constexpr internal::column_pointer column(F Base::* field) { - static_assert(std::is_convertible::value, "Field must be from derived class"); + static_assert(std::is_convertible::value || + std::is_same, O>::value || + // trust the `enclosing_type` alias template defined in the virtual table's data struct + polyfill::is_detected_v, + "Field must be from derived or related class"); return {field}; } @@ -2418,41 +2521,33 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Explicitly refer to a column. */ - template - constexpr auto column(F O::* field) { + template + constexpr auto column(F Base::* field) { return column>(field); } +#endif // Intentionally place pointer-to-member operator for table references in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Explicitly refer to a column. */ - template - constexpr auto operator->*(const R& /*table*/, F O::* field) { + template + constexpr auto operator->*(const R& /*table*/, F Base::* field) { + return column(field); + } +#else + /** + * Explicitly refer to a column. + */ + template, bool> = true> + constexpr auto operator->*(const R& /*table*/, F Base::* field) { return column(field); } - } - - /** - * Make a table reference. - */ - template - requires (!orm_recordset_alias) - consteval internal::table_reference column() { - return {}; - } - - /** - * Make a table reference. - */ - template - requires (!orm_recordset_alias) - consteval internal::table_reference c() { - return {}; - } #endif + } #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** @@ -3395,8 +3490,6 @@ namespace sqlite_orm { // #include "type_printer.h" -// #include "table_reference.h" - namespace sqlite_orm { namespace internal { @@ -3545,7 +3638,7 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3024000 /** - * Auxiliary virtual table column + * Auxiliary virtual table column constraint */ struct auxiliary_t {}; @@ -5202,22 +5295,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "literal.h" -namespace sqlite_orm { - namespace internal { - - /* - * Protect an otherwise bindable element so that it is always serialized as a literal value. - */ - template - struct literal_holder { - using type = T; - - type value; - }; - - } -} - // #include "ast/cross_join.h" // #include "../functional/cxx_type_traits_polyfill.h" @@ -5938,14 +6015,6 @@ namespace sqlite_orm { inner_join_t(on_type constraint_) : constraint(std::move(constraint_)) {} }; - template - struct from_t { - using tuple_type = std::tuple; - }; - - template - using is_from = polyfill::is_specialization_of; - template using is_constrained_join = polyfill::is_detected; @@ -5955,6 +6024,23 @@ namespace sqlite_orm { check_if_is_template>, T>; + template + struct from_t { + using tuple_type = std::tuple; + }; + + template + using is_from = polyfill::is_specialization_of; + + template + struct from2_t { + using tuple_type = std::tuple; + + tuple_type table_expressions; + }; + + template + using is_from2 = polyfill::is_specialization_of; } } @@ -5964,8 +6050,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * `storage.select(&User::id, from());` */ template - internal::from_t from() { - static_assert(sizeof...(Tables) > 0, ""); + constexpr internal::from_t from() { + static_assert(sizeof...(Tables) > 0); return {}; } @@ -5975,11 +6061,35 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * `storage.select(&User::id, from<"a"_alias.for_>());` */ template - auto from() { + constexpr auto from() { return from...>(); } #endif +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * Explicit FROM for an eponymous virtual table used as a table-valued function. Usage: + * `storage.select(asterisk(), from(dbstat_table("main", true)));` + */ + template + requires ((orm_refers_to_recordset || orm_table_valued_expression) && ...) + constexpr internal::from2_t from(TableExpr... tableExpressions) { + return {{std::move(tableExpressions)...}}; + } +#else + /** + * Explicit FROM for an eponymous virtual table used as a table-valued function. Usage: + * `storage.select(asterisk(), from(dbstat_table("main", true)));` + */ + template + constexpr internal::from2_t from(TableExpr... tableExpressions) { + static_assert( + ((internal::is_referring_to_recordset_v || internal::is_table_valued_expression_v) && + ...)); + return {{std::move(tableExpressions)...}}; + } +#endif + // Intentionally place operators for types classified as arithmetic or general operator arguments in the internal namespace // to facilitate ADL (Argument Dependent Lookup) namespace internal { @@ -6042,7 +6152,11 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::is_base_of, std::is_base_of, is_operator_argument, - is_operator_argument>::value, + is_operator_argument>::value +#ifndef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + && !is_table_reference_v +#endif + , bool> = true> constexpr is_equal_t, unwrap_expression_t> operator==(L l, R r) { return {get_from_expression(std::forward(l)), get_from_expression(std::forward(r))}; @@ -6243,8 +6357,13 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(l), std::move(r)}; } - template - constexpr internal::is_equal_with_table_t is_equal(R rhs) { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + template, bool> = true> + [[deprecated("Use the usual `is_equal` function to compare the hidden FTS5 'any' field or a field of your FTS " + "table instead")]] + constexpr internal::is_equal_with_table_t is_equal(R rhs) { return {std::move(rhs)}; } @@ -6409,7 +6528,47 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "tags.h" -// #include "table_reference.h" +// #include "field_traits.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::enable_if, std::is_same +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include // std::same_as +#endif +#endif + +// #include "member_traits/member_traits.h" + +// #include "type_traits.h" + +// #include "field_of.h" + +namespace sqlite_orm::internal { +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept hidden_field_of_vtab = + is_field_of_v>; + + template + concept hidden_column_of_vtab = std::same_as>, typename VTabStruct::hidden>; +#else + template + inline constexpr bool is_hidden_column_of_vtab_v = false; + + template + inline constexpr bool is_hidden_column_of_vtab_v< + CP, + VTabStruct, + std::enable_if_t>, typename VTabStruct::hidden>::value>> = + true; + + template + inline constexpr bool is_hidden_field_of_vtab_v = + is_field_of_v>; +#endif +} + +// #include "alias_traits.h" // #include "ast/into.h" @@ -6444,6 +6603,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif } +// #include "field_of.h" + namespace sqlite_orm { namespace internal { @@ -8581,10 +8742,70 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } - template - internal::highlight_t highlight(X x, Y y, Z z) { + struct fts5; + + /** + * The FTS5 highlight function. The table type is specified as a template argument. + * See https://www.sqlite.org/fts5.html#the_highlight_function + * + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + template, bool> = true> + [[deprecated("Use the `highlight` function accepting the hidden FTS5 'any' field instead")]] + constexpr internal::highlight_t highlight(X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template + requires (internal::hidden_column_of_vtab) + constexpr internal::highlight_t highlight(const CP& /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } + + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template + requires (internal::hidden_field_of_vtab) + constexpr internal::highlight_t + highlight(F Hidden::* /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } +#else + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template, bool> = true> + constexpr internal::highlight_t highlight(const CP& /*theAnyField*/, X x, Y y, Z z) { + return {std::move(x), std::move(y), std::move(z)}; + } + + /** + * The FTS5 highlight function. + * See https://www.sqlite.org/fts5.html#the_highlight_function + */ + template, bool> = true> + constexpr internal::highlight_t + highlight(F Hidden::* /*theAnyField*/, X x, Y y, Z z) { return {std::move(x), std::move(y), std::move(z)}; } +#endif } // #include "alias_traits.h" @@ -8834,6 +9055,15 @@ namespace sqlite_orm { template struct column_t : column_identifier, column_field, column_constraints {}; + /** + * Definition of a hidden column. + * + * Implementation note: it is a separate type to make coding easier - hidden columns do not participate in normal column handling, + * e.g. they are not counted as columns when constructing objects, and are only needed when finding columns or for table-valued functions. + */ + template + struct hidden_column : column_identifier, column_field, column_constraints {}; + template struct column_field_expression { using type = void; @@ -8853,6 +9083,12 @@ namespace sqlite_orm { template using is_column = polyfill::bool_constant>; + template + inline constexpr bool is_hidden_column_v = polyfill::is_specialization_of::value; + + template + using is_hidden_column = polyfill::bool_constant>; + template using col_index_sequence_of = filter_tuple_sequence_t; @@ -8874,6 +9110,31 @@ namespace sqlite_orm { check_if_has_not::template fn, constraints_type_t, filter_tuple_sequence_t>; + + template + using hidden_col_index_sequence_of = filter_tuple_sequence_t; + + template + using all_col_index_sequence_with_field_type = filter_tuple_sequence_t< + Elements, + check_if_is_type::template fn, + field_type_t, + filter_tuple_sequence_t::template fn>>; + +#if SQLITE_VERSION_NUMBER >= 3031000 + /** + * Factory function for a column definition from a member object pointer for hidden virtual table columns. + */ + template = true> + hidden_column make_hidden_column(std::string name, M memberPointer, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + } +#endif } } @@ -9354,7 +9615,8 @@ namespace sqlite_orm { static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 GROUP BY blocks"); static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 ORDER BY blocks"); static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 LIMIT blocks"); - static_assert(count_tuple::value <= 1, "a single query cannot contain > 1 FROM blocks"); + static_assert(mpl::invoke_t>, T>::value <= 1, + "a single query cannot contain > 1 FROM blocks"); } } } @@ -10760,6 +11022,8 @@ namespace sqlite_orm { #include // std::remove_const #endif +// #include "functional/cxx_type_traits_polyfill.h" + // #include "type_traits.h" // #include "table_reference.h" @@ -10770,17 +11034,23 @@ namespace sqlite_orm { namespace internal { - /** - * If T is a table reference or recordset alias then the typename mapped_type_proxy::type is the unqualified aliased type, - * otherwise unqualified T. + /** + * Defines the `type` typename to be: + * - The unqualified unwrapped table reference type if T is a table reference. + * - The unqualified aliased type if T is a recordset alias. + * - The enclosing data struct for eponymous virtual tables with hidden columns. + * - ... otherwise unqualified T. */ template struct mapped_type_proxy : std::remove_const {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - template - struct mapped_type_proxy : R {}; -#endif + template + struct mapped_type_proxy> { + using type = enclosing_type_t; + }; + + template + struct mapped_type_proxy> : R {}; template struct mapped_type_proxy> : std::remove_const> {}; @@ -12472,7 +12742,7 @@ namespace sqlite_orm::internal { const std::string* res = nullptr; iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, + all_col_index_sequence_with_field_type{}, [&res, memberPointer](auto& column) { if (compare_fields(column.member_pointer, memberPointer) || compare_fields(column.setter, memberPointer)) { @@ -12926,9 +13196,9 @@ namespace sqlite_orm { /** * Find column name by its type and member pointer. */ - template = true> - const std::string* find_column_name(const DBOs& dbObjects, F O::* field) { - return pick_table(dbObjects).find_column_name(field); + template = true> + const std::string* find_column_name(const DBOs& dbObjects, F Lookup::* field) { + return pick_table>(dbObjects).find_column_name(field); } /** @@ -14204,6 +14474,8 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_traits.h" +// #include "type_traits.h" + // #include "connection_holder.h" #include @@ -14640,9 +14912,9 @@ namespace sqlite_orm { template SQLITE_ORM_STATIC_CALLOP void operator()(const T&) SQLITE_ORM_OR_CONST_CALLOP {} - template - void operator()(F O::*) { - this->table_names.emplace(lookup_table_name(this->db_objects), ""); + template + void operator()(F Lookup::*) { + this->table_names.emplace(lookup_table_name>(this->db_objects), ""); } template @@ -15133,6 +15405,17 @@ namespace sqlite_orm { template using main_dml_t = polyfill::remove_cvref_t()))>; + + template + constexpr void validate_get_all_conditions() { + using from2_index_sequence = filter_tuple_sequence_t; + if constexpr (from2_index_sequence::size() > 0) { + using from_type = std::tuple_element_t(from2_index_sequence{}), Tpl>; + // check whether one of table expressions' type is the same as the requested table type + static_assert(mpl::invoke_t>, from_type>::value, + "Requested object type must be listed in explicit FROM clause"); + } + } } } @@ -15551,6 +15834,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_t get_all(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } @@ -15591,6 +15875,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_pointer_t get_all_pointer(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } @@ -15620,6 +15905,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::get_all_optional_t get_all_optional(Args... conditions) { using conditions_tuple = std::tuple; internal::validate_conditions(); + internal::validate_get_all_conditions(); return {{std::forward(conditions)...}}; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED @@ -15720,26 +16006,52 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "ast/match.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include +#include // std::move #endif -namespace sqlite_orm { - namespace internal { +// #include "../type_traits.h" - template - struct match_t { - using mapped_type = T; - using argument_type = X; +namespace sqlite_orm::internal { + template + struct match_with_table_t { + using mapped_type = T; + using argument_type = X; - argument_type argument; - }; + argument_type argument; + }; + + /* + * Alternative equality comparison where the left side is always a field. + */ + template + struct match_t { + using field_type = Field; + using argument_type = X; + + field_type field; + argument_type argument; + }; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + template + [[deprecated( + "Use the `match` function accepting the hidden FTS5 'any' field or a field of your FTS table instead")]] + constexpr internal::match_with_table_t match(X argument) { + return {std::move(argument)}; + } + + template + constexpr internal::match_t match(CP field, X argument) { + return {std::move(field), std::move(argument)}; } -} -SQLITE_ORM_EXPORT namespace sqlite_orm { - template - internal::match_t match(X argument) { - return {std::move(argument)}; + template + constexpr internal::match_t match(F O::* field, X argument) { + return {field, std::move(argument)}; } } @@ -15822,8 +16134,18 @@ namespace sqlite_orm { }; template - struct ast_iterator, void> { - using node_type = match_t; + struct ast_iterator, void> { + using node_type = match_with_table_t; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& node, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(node.argument, lambda); + } + }; + + template + struct ast_iterator, void> { + using node_type = match_t; template SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& node, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { @@ -15877,6 +16199,27 @@ namespace sqlite_orm { } }; + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& expression, + L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(expression.table_values, lambda); + } + }; + + template + struct ast_iterator> { + using node_type = T; + + template + SQLITE_ORM_STATIC_CALLOP void operator()(const node_type& from, L& lambda) SQLITE_ORM_OR_CONST_CALLOP { + iterate_ast(from.table_expressions, lambda); + } + }; + template struct ast_iterator, void> { using node_type = where_t; @@ -16919,6 +17262,7 @@ namespace sqlite_orm { conditions_tuple, actions_tuple, expressions_tuple, + filtered_expressions_tuple, dynamic_expressions, compound_expressions, serialized, @@ -16949,6 +17293,7 @@ namespace sqlite_orm { constexpr streaming streaming_conditions_tuple{}; constexpr streaming streaming_actions_tuple{}; constexpr streaming streaming_expressions_tuple{}; + constexpr streaming streaming_filtered_expressions_tuple{}; constexpr streaming streaming_dynamic_expressions{}; constexpr streaming streaming_compound_expressions{}; constexpr streaming streaming_serialized{}; @@ -17005,6 +17350,22 @@ namespace sqlite_orm { return ss; } + // serialize and stream a tuple of expressions including only those specified by an index sequence; + // comma-separated + template + std::ostream& operator<<(std::ostream& ss, + std::tuple&, Seq, T, Ctx> tpl) { + const auto& args = std::get<1>(tpl); + const auto& included_index_sequence = std::get<2>(tpl); + auto& context = std::get<3>(tpl); + + iterate_tuple(args, included_index_sequence, [&ss, &context, first = true](auto& arg) mutable { + static constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] << serialize(arg, context); + }); + return ss; + } + // serialize and stream expressions of a compound statement; // separated by compound operator template @@ -17228,7 +17589,7 @@ namespace sqlite_orm { return ss; } - // serialize and stream a tuple of column constraints; + // serialize and stream a tuple of column constraints excluding auxiliary columns; // space + space-separated template std::ostream& operator<<(std::ostream& ss, @@ -19541,6 +19902,10 @@ namespace sqlite_orm { } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + [[deprecated("Use the hidden FTS5 rank column instead")]] inline internal::rank_t rank() { return {}; } @@ -20463,9 +20828,9 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Factory function for a virtual table. * - * The mapped object type is determined implicitly from the first column definition. + * The mapped object type is explicitly specified. */ - template>::object_type> + template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); @@ -20474,12 +20839,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { /** * Factory function for a virtual table. * - * The mapped object type is explicitly specified. + * The mapped object type is determined implicitly from the first column definition. */ - template + template>::object_type> internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); + return make_virtual_table(std::move(name), std::move(definition)); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -20611,11 +20976,20 @@ namespace sqlite_orm { // Eponymous virtual tables serialize only table values. Their definition is built-in, fixed and implicit template = true> - std::string serialize_virtual_table_definition(const Definition&, const Ctx&) { - return {}; + std::string serialize_virtual_table_definition(const Elements& elements, const Ctx& context) { + using table_values_index_sequence = filter_tuple_sequence_t; + + if constexpr (table_values_index_sequence::size() == 0) { + return {}; + } else { + std::stringstream ss; + ss << "(" << streaming_filtered_expressions_tuple(elements, table_values_index_sequence{}, context) + << ")"; + return ss.str(); + } } template = true> std::string serialize_virtual_table_definition(const Elements& elements, const Ctx& context) { using traits_type = ModTraits; + using excluding_hidden_index_sequence = + filter_tuple_sequence_t::template fn>; auto subContext = context; subContext.omit_column_type = traits_type::omit_column_type::value; std::stringstream ss; - ss << "(" << streaming_expressions_tuple(elements, subContext) << ")"; + ss << "(" << streaming_filtered_expressions_tuple(elements, excluding_hidden_index_sequence{}, subContext) + << ")"; return ss.str(); } @@ -20712,10 +21089,9 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { static_assert(is_bindable_v>, "A literal value must be also bindable"); - Ctx literalCtx = context; + auto literalCtx = context; literalCtx.replace_bindable_with_question = false; - statement_serializer> serializer{}; - return serializer(literal.value, literalCtx); + return serialize(literal.value, literalCtx); } }; @@ -20787,8 +21163,8 @@ namespace sqlite_orm { }; template - struct statement_serializer, void> { - using statement_type = match_t; + struct statement_serializer, void> { + using statement_type = match_with_table_t; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -20800,6 +21176,19 @@ namespace sqlite_orm { } }; + template + struct statement_serializer, void> { + using statement_type = match_t; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + std::stringstream ss; + ss << serialize(statement.field, context) << " MATCH " << serialize(statement.argument, context); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = column_alias; @@ -20915,10 +21304,10 @@ namespace sqlite_orm { using statement_type = E; template - SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& e, + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& expression, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (auto* columnName = find_column_name(context.db_objects, e)) { + if (auto* columnName = find_column_name(context.db_objects, expression)) { ss << streaming_identifier( !context.omit_table_name ? lookup_table_name>(context.db_objects) : "", *columnName, @@ -22206,13 +22595,16 @@ namespace sqlite_orm { std::string serialize_get_all_impl(const T& getAll, const Ctx& context) { using table_type = type_t; using mapped_type = mapped_type_proxy_t; + constexpr bool hasExplicitFrom2 = tuple_has::value; auto& table = pick_table(context.db_objects); std::stringstream ss; - ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)) - << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()) - << streaming_conditions_tuple(getAll.conditions, context); + ss << "SELECT " << streaming_table_column_names(table, alias_extractor::as_qualifier(table)); + if constexpr (!hasExplicitFrom2) { + ss << " FROM " << streaming_identifier(table.name, alias_extractor::as_alias()); + } + ss << streaming_conditions_tuple(getAll.conditions, context); return ss.str(); } @@ -22380,7 +22772,8 @@ namespace sqlite_orm { ss << streaming_serialized(get_column_names(sel.col, subCtx)); using conditions_tuple = typename statement_type::conditions_type; - constexpr bool hasExplicitFrom = tuple_has::value; + constexpr bool hasExplicitFrom = + tuple_has::template fn>::value; if constexpr (!hasExplicitFrom) { using joins_index_sequence = filter_tuple_sequence_t; @@ -22486,8 +22879,8 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; ss << "FROM "; - iterate_tuple([&context, &ss, first = true](auto* dummyItem) mutable { - using table_type = std::remove_pointer_t; + iterate_tuple([&context, &ss, first = true](auto* dummy) mutable { + using table_type = std::remove_pointer_t; static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] @@ -22498,6 +22891,35 @@ namespace sqlite_orm { } }; + template + struct statement_serializer> { + using statement_type = From; + + template + SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& from, + const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { + std::stringstream ss; + ss << "FROM "; + iterate_tuple(from.table_expressions, + [&context, &ss, first = true](const auto& tableExpression) mutable { + using expression_type = polyfill::remove_cvref_t; + using table_type = type_t; + + static constexpr std::array sep = {", ", ""}; + ss << sep[std::exchange(first, false)] + << streaming_identifier( + lookup_table_name>(context.db_objects), + alias_extractor::as_alias()); + + if constexpr (is_table_valued_expression_v) { + ss << '(' << streaming_expressions_tuple(tableExpression.table_values, context) + << ')'; + } + }); + return ss.str(); + } + }; + template struct statement_serializer, void> { using statement_type = old_t; @@ -25419,7 +25841,7 @@ namespace sqlite_orm { // #include "schema/table.h" -// #include "column_pointer.h" +// #include "table_reference.h" // #include "alias.h" @@ -25454,8 +25876,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { make_column("sql", &sqlite_master::sql)); } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED inline constexpr orm_table_reference auto sqlite_master_table = c(); +#else + inline constexpr auto sqlite_master_table = c(); +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline constexpr orm_table_alias auto sqlite_schema = "sqlite_schema"_alias.for_(); #endif } @@ -25726,7 +26152,10 @@ namespace sqlite_orm { struct node_tuple, void> : node_tuple {}; template - struct node_tuple, void> : node_tuple {}; + struct node_tuple, void> : node_tuple {}; + + template + struct node_tuple, void> : node_tuple {}; /** * Column alias @@ -25740,12 +26169,6 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple {}; - /** - * Literal - */ - template - struct node_tuple, void> : node_tuple {}; - template struct node_tuple, void> : node_tuple {}; @@ -25903,6 +26326,18 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple_for {}; + + template + struct node_tuple, void> : node_tuple_for {}; + + /* + * Table reference as part of FROM clause: skip + */ + template + struct node_tuple> : node_tuple {}; + + template + struct node_tuple, void> : node_tuple_for {}; } } @@ -26071,19 +26506,26 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ENABLE_DBSTAT_VTAB -#include // std::false_type, std::true_type -#include // std::make_tuple -#include // std::move +#include // std::false_type, std::true_type, std::is_convertible +#include // std::tuple_size, std::make_tuple, std::tuple +#include // std::move, std::index_sequence_for +#include // std::string #endif #endif // #include "../functional/gsl.h" +// #include "../tuple_helper/tuple_filter.h" + +// #include "../member_traits/member_traits.h" + // #include "../schema/virtual_table.h" // #include "../schema/column.h" -// #include "../column_pointer.h" +// #include "../literal.h" + +// #include "../table_reference.h" #ifdef SQLITE_ENABLE_DBSTAT_VTAB namespace sqlite_orm::internal { @@ -26111,12 +26553,15 @@ namespace sqlite_orm::internal { }; template - inline virtual_table_definition using_dbstat(Cs... columns) { + inline virtual_table_definition make_dbstat_definition(Cs... columns) { SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); } } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Data structure for the `dbstat` eponymous virtual table. + */ struct dbstat { std::string name; std::string path; @@ -26128,21 +26573,55 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { int mx_payload = 0; int pgoffset = 0; int pgsize = 0; + + /** + * Hidden columns of the `dbstat` virtual table, which can be referred to using a 'column pointer'. + * See `hidden_columns`. + */ + struct hidden { + std::string schema; +#if SQLITE_VERSION_NUMBER >= 3031000 + bool aggregate = false; +#endif + + using enclosing_type = dbstat; + }; }; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED inline constexpr orm_table_reference auto dbstat_table = c(); +#else + inline constexpr auto dbstat_table = c(); #endif /** * Factory function for a DBSTAT virtual table definition. * If no schema is specified then the main schema is used. * + * The hidden DBSTAT columns 'schema' and 'aggregate' are mapped into each table definition. + * * Though the DBSTAT virtual table is an eponymous table SQLite allows to create a virtual table instance with a different name. - * This is mostly useful with binding input arguments, e.g. a different schema than "main", which is yet unimplemented. + * This is mostly useful with binding input arguments (so-called table values), e.g. a different schema than "main" or whether to query aggregated results. */ - inline auto using_dbstat() { - return internal::using_dbstat(make_column("name", &dbstat::name), + template + auto using_dbstat(Value... tableValues) { + using namespace ::sqlite_orm::internal; + using expected_hidden_types = std::tuple +#if SQLITE_VERSION_NUMBER >= 3031000 + , + member_field_type_t +#endif + >; + static_assert(sizeof...(Value) <= std::tuple_size::value, + "You may only pass the schema name and the aggregation flag"); + using input_value_types = std::tuple; + // make a tuple of types from expected types limited to the number of passed in table values + using final_expected_types = + tuple_from_index_sequence_t>; + static_assert(std::is_convertible::value, + "The schema name must be a string value, the aggregate flag a boolean value"); + + return make_dbstat_definition(make_column("name", &dbstat::name), make_column("path", &dbstat::path), make_column("pageno", &dbstat::pageno), make_column("pagetype", &dbstat::pagetype), @@ -26151,7 +26630,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { make_column("unused", &dbstat::unused), make_column("mx_payload", &dbstat::mx_payload), make_column("pgoffset", &dbstat::pgoffset), - make_column("pgsize", &dbstat::pgsize)); + make_column("pgsize", &dbstat::pgsize), + make_hidden_column("schema", &dbstat::hidden::schema), +#if SQLITE_VERSION_NUMBER >= 3031000 + make_hidden_column("aggregate", &dbstat::hidden::aggregate), +#endif + literal_holder{std::move(tableValues)}...); } /** @@ -26163,21 +26647,124 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } #endif // SQLITE_ENABLE_DBSTAT_VTAB +// #include "generate_series.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#if SQLITE_VERSION_NUMBER >= 3008012 +#include // std::tuple_element, std::make_tuple +#include // std::forward +#endif +#endif + +// #include "../functional/gsl.h" + +// #include "../schema/virtual_table.h" + +// #include "../schema/column.h" + +// #include "../table_reference.h" + +#if SQLITE_VERSION_NUMBER >= 3008012 +namespace sqlite_orm::internal { + struct generate_series_module_tag { + // simplify conceptual/meta programming + using module_type = generate_series_module_tag; + + static constexpr orm_gsl::czstring name() { + return "generate_series"; + } + }; + + template<> + struct virtual_table_module_traits { + using module_type = generate_series_module_tag; + using is_eponymous = std::true_type; + using is_without_rowid = std::false_type; + using omit_column_type = std::true_type; + }; + + template + struct virtual_table_traits + : virtual_table_module_traits { + using definition_type = table_definition; + using elements_type = typename definition_type::elements_type; + }; + + template + inline virtual_table_definition make_generate_series_definition(Cs... columns) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Data structure for the `generate_series` eponymous virtual table. + */ + struct generate_series { + int value = 0; + + /** + * Hidden columns of the `generate_series` virtual table. + */ + struct hidden { + int start; + int stop; + int step; + + using enclosing_type = generate_series; + }; + }; + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + inline constexpr orm_table_reference auto generate_series_table = c(); +#else + inline constexpr auto generate_series_table = c(); +#endif +} + +namespace sqlite_orm::internal { + /** + * Factory function for a `generate_series` virtual table definition. + * + * The hidden generate_series columns 'start', 'stop' and 'step' are mapped into each table definition. + */ + inline auto using_generate_series() { + return make_generate_series_definition(make_column("value", &generate_series::value), + make_hidden_column("start", &generate_series::hidden::start), + make_hidden_column("stop", &generate_series::hidden::stop), + make_hidden_column("step", &generate_series::hidden::step)); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Factory function for the `generate_series` default eponymous virtual table. + */ + inline auto make_generate_series_table() { + return make_virtual_table(internal::generate_series_module_tag::name(), + internal::using_generate_series()); + } +} +#endif + // #include "fts5.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE #if SQLITE_VERSION_NUMBER >= 3009000 -#include // std::make_tuple -#include // std::forward +#include // std::tuple_size, std::make_tuple, std::get +#include // std::forward, std::move +#include #endif #endif -// #include "../functional/cxx_type_traits_polyfill.h" +// #include "../functional/cxx_optional.h" // #include "../functional/gsl.h" // #include "../functional/mpl.h" +// #include "../field_of.h" + // #include "../schema/virtual_table.h" // #include "../schema/column.h" @@ -26187,12 +26774,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3009000 namespace sqlite_orm::internal { template - using is_fts5_table_element_or_constraint = mpl::invoke_t, - check_if_is_template, - check_if_is_template, - check_if_is_template, - check_if_is_template>, - T>; + inline constexpr bool is_fts5_table_element_or_constraint_v = + mpl::invoke_t, + check_if_is_template, + check_if_is_template, + check_if_is_template, + check_if_is_template>, + T>::value; + struct fts5_module_tag { // simplify conceptual/meta programming using module_type = fts5_module_tag; @@ -26201,18 +26790,77 @@ namespace sqlite_orm::internal { return "fts5"; } }; + + template + inline virtual_table_definition make_fts5_definition(Cs... definition) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(definition)...)}}); + } } SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Class namespace for hidden `fts5` columns. + */ + struct fts5 { +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + /** + Hidden columns of the `fts5` virtual table, which can be referred to using a 'column pointer' or using `hidden_fields_of<>` fields. + */ + struct hidden { + /// Hidden column with the same name as the table itself. It is a blob only meaningful when matching any of the other FTS5 columns. + std::vector any; + std::optional rank; + + /** + * Internal meta programming helper to rebind hidden columns to a specific object type. + */ + template + struct _of; + }; + + /** + Map hidden columns to a specific object type to simplify programming. + + Example: + struct Post { + using hidden = fts5::hidden_fields_of; + }; + */ + template + struct hidden_fields_of { + static constexpr auto any_field = internal::as_field_of>(&fts5::hidden::any); + static constexpr auto rank_field = internal::as_field_of>(&fts5::hidden::rank); + + hidden_fields_of() = delete; + }; +#endif + + fts5() = delete; + }; + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + template + struct fts5::hidden::_of : fts5::hidden { + using enclosing_type = O; + }; +#endif + /** * Factory function for a FTS5 virtual table definition. + * + * The hidden FTS5 columns 'any' [as a placeholder for the column with the same name as the table] and 'rank' are mapped into each table definition. */ template - internal::virtual_table_definition using_fts5(Cs... definition) { - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); - - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + auto using_fts5(Cs... definition) { + using namespace ::sqlite_orm::internal; + return make_fts5_definition(std::forward(definition)... +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + , + // note: eponymous column name is filled in later in `make_virtual_table()` + make_hidden_column("", &fts5::hidden::any), + make_hidden_column("rank", &fts5::hidden::rank) +#endif + ); } /** @@ -26225,11 +26873,27 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template [[deprecated("Specify the explicit object type when calling `make_virtual_table()`.")]] internal::virtual_table_description using_fts5(Cs... definition) { - static_assert(polyfill::conjunction_v...>, + static_assert((internal::is_fts5_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); } + + template + internal::virtual_table + make_virtual_table(std::string tableName, + internal::virtual_table_definition definition) { + using namespace ::sqlite_orm::internal; + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + constexpr size_t eponymous_column_index = + std::tuple_size>>::value - 2; + // fill in the eponymous hidden column name + std::get(definition.elements).name = tableName; +#endif + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(tableName), std::move(definition)}); + } } #endif @@ -26244,12 +26908,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif #endif -// #include "../functional/cxx_type_traits_polyfill.h" - // #include "../functional/gsl.h" // #include "../functional/mpl.h" +// #include "../tuple_helper/tuple_filter.h" + +// #include "../type_traits.h" + // #include "../schema/virtual_table.h" // #include "../schema/column.h" @@ -26257,7 +26923,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ENABLE_RTREE namespace sqlite_orm::internal { template - using is_rtree_table_element_or_constraint = mpl::invoke_t>, T>; + inline constexpr bool is_rtree_table_element_or_constraint_v = + mpl::invoke_t>, T>::value; struct rtree_module_tag { // simplify conceptual/meta programming @@ -26288,14 +26955,13 @@ namespace sqlite_orm::internal { rtree_col_index_sequence, field_type_t>::value; - static_assert(polyfill::conjunction_v...>, - "Incorrect table elements or constraints"); + static_assert((is_rtree_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); static_assert(nRTreeColumns >= 3 && nRTreeColumns <= 11 && nRTreeColumns % 2 == 1, "An RTREE table must have between 1 and 5 dimensions consisting of min/max-value pair columns"); static_assert( nRTreeColumnsOfExpectedType == nRTreeColumns - 1, R"(The min/max-value pair columns need to be 32-bit floating point values for RTREE virtual tables and 32-bit signed integers for RTREE_I32 virtual tables, as they are stored as such)"); - static_assert(std::is_same::field_type, int64>::value, + static_assert(std::is_same>, int64>::value, "The type of the first column must be a 64-bit integer"); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f6fff672..bf1d67db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,8 +47,14 @@ endif() # Glob all .cpp files recursively in the current directory file(GLOB_RECURSE UNIT_TEST_SOURCES "*.cpp") ucm_remove_directories(named_module FROM UNIT_TEST_SOURCES) - -add_executable(unit_tests ${UNIT_TEST_SOURCES}) +# Download .c file for the generate_series module +file(DOWNLOAD "https://raw.githubusercontent.com/sqlite/sqlite/refs/tags/version-3.50.4/ext/misc/series.c" "${CMAKE_CURRENT_SOURCE_DIR}/.sqlite3/ext/misc/series.c") +# Build series.c as a static module, which will be directly initialized and used by the unit tests target +set_source_files_properties(.sqlite3/ext/misc/series.c PROPERTIES + SKIP_PRECOMPILE_HEADERS ON + COMPILE_DEFINITIONS "SQLITE_CORE") + +add_executable(unit_tests ${UNIT_TEST_SOURCES} .sqlite3/ext/misc/series.c) if(SQLITE_ORM_OMITS_CODECVT) message(STATUS "SQLITE_ORM_OMITS_CODECVT is enabled") diff --git a/tests/ast_iterator_tests.cpp b/tests/ast_iterator_tests.cpp index 14345093..66000df9 100644 --- a/tests/ast_iterator_tests.cpp +++ b/tests/ast_iterator_tests.cpp @@ -26,6 +26,8 @@ TEST_CASE("ast_iterator") { int id = 0; std::string name; }; + constexpr auto user_table = c(); + using user_hidden = fts5::hidden_fields_of; std::vector typeIndexes; decltype(typeIndexes) expected; @@ -263,9 +265,24 @@ TEST_CASE("ast_iterator") { auto node = into(); iterate_ast(node, lambda); } - SECTION("match") { - auto node = match(std::string("Plazma")); - expected.push_back(typeid(std::string)); + SECTION("match using explicit template parameter") { + constexpr auto node = match("Plazma"); + expected.push_back(typeid(const char*)); + iterate_ast(node, lambda); + } + SECTION("match any column") { + constexpr auto node = match(user_table->*&fts5::hidden::any, "Plazma"); + expected.push_back(typeid(const char*)); + iterate_ast(node, lambda); + } + SECTION("match any column, rebound") { + constexpr auto node = match(user_hidden::any_field, "Claude"); + expected.push_back(typeid(const char*)); + iterate_ast(node, lambda); + } + SECTION("match specific column") { + constexpr auto node = match(&User::name, "Claude"); + expected.push_back(typeid(const char*)); iterate_ast(node, lambda); } SECTION("replace") { @@ -437,13 +454,27 @@ TEST_CASE("ast_iterator") { } #endif #endif - SECTION("highlight") { + SECTION("highlight using explicit template parameter") { auto expression = highlight(0, std::string(""), std::string("")); expected.push_back(typeid(int)); expected.push_back(typeid(std::string)); expected.push_back(typeid(std::string)); iterate_ast(expression, lambda); } + SECTION("highlight using the any column") { + auto expression = highlight(user_table->*&fts5::hidden::any, 0, std::string(""), std::string("")); + expected.push_back(typeid(int)); + expected.push_back(typeid(std::string)); + expected.push_back(typeid(std::string)); + iterate_ast(expression, lambda); + } + SECTION("highlight using the any column, rebound") { + auto expression = highlight(user_hidden::any_field, 0, std::string(""), std::string("")); + expected.push_back(typeid(int)); + expected.push_back(typeid(std::string)); + expected.push_back(typeid(std::string)); + iterate_ast(expression, lambda); + } #ifdef SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED SECTION("node expressions") { struct Func { @@ -458,6 +489,13 @@ TEST_CASE("ast_iterator") { iterate_ast(expression2, nodeLambda); iterate_ast(expression3, nodeLambda); } +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + SECTION("table-valued") { + auto expression = from(dbstat_table("main")); + expected.push_back(typeid(const char*)); + iterate_ast(expression, lambda); + } #endif REQUIRE(typeIndexes == expected); } diff --git a/tests/schema/virtual_table.cpp b/tests/schema/virtual_table.cpp index 86d98506..dfa1d3af 100644 --- a/tests/schema/virtual_table.cpp +++ b/tests/schema/virtual_table.cpp @@ -1,16 +1,29 @@ +#include #include #include +extern "C" int sqlite3_series_init(sqlite3*, char**, const sqlite3_api_routines*); + +namespace { + constexpr auto compareColumnName = [](const std::string* foundValue, std::string expectedValue) { + if (!foundValue) { + return false; + } + return *foundValue == expectedValue; + }; +} + #if SQLITE_VERSION_NUMBER >= 3009000 using namespace sqlite_orm; TEST_CASE("fts5 virtual table schema") { using Catch::Matchers::UnorderedEquals; - struct Post { std::string title; std::string body; + using hidden = fts5::hidden_fields_of; + #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED bool operator==(const Post&) const = default; #else @@ -19,78 +32,101 @@ TEST_CASE("fts5 virtual table schema") { } #endif }; + constexpr auto post_table = c(); auto virtualTable = make_virtual_table("posts", using_fts5(make_column("title", &Post::title), make_column("body", &Post::body))); - { - constexpr auto compareColumnName = [](const std::string* foundValue, std::string expectedValue) { - if (!foundValue) { - return false; - } - return *foundValue == expectedValue; - }; - REQUIRE(compareColumnName(virtualTable.find_column_name(&Post::title), std::string("title"))); - REQUIRE(compareColumnName(virtualTable.find_column_name(&Post::body), std::string("body"))); + SECTION("eponymous") { + SECTION("definition") { + REQUIRE(compareColumnName(virtualTable.find_column_name(&Post::title), std::string("title"))); + REQUIRE(compareColumnName(virtualTable.find_column_name(&Post::body), std::string("body"))); + REQUIRE(compareColumnName(virtualTable.find_column_name(&fts5::hidden::any), std::string("posts"))); + REQUIRE(compareColumnName(virtualTable.find_column_name(&fts5::hidden::rank), std::string("rank"))); + } } + SECTION("storage") { - /// CREATE VIRTUAL TABLE posts - /// USING FTS5(title, body); - auto storage = make_storage("", std::move(virtualTable)); + /// CREATE VIRTUAL TABLE posts + /// USING FTS5(title, body); + auto storage = make_storage("", std::move(virtualTable)); - storage.sync_schema(); - storage.sync_schema_simulate(); - REQUIRE(storage.table_exists("posts")); + storage.sync_schema(); + storage.sync_schema_simulate(); + REQUIRE(storage.table_exists("posts")); - const std::vector postsToInsert = { - Post{"Learn SQlite FTS5", "This tutorial teaches you how to perform full-text search in SQLite using FTS5"}, - Post{"Advanced SQlite Full-text Search", "Show you some advanced techniques in SQLite full-text searching"}, - Post{"SQLite Tutorial", "Help you learn SQLite quickly and effectively"}, - }; + const std::vector postsToInsert = { + {"Learn SQlite FTS5", "This tutorial teaches you how to perform full-text search in SQLite using FTS5"}, + {"Advanced SQlite Full-text Search", "Show you some advanced techniques in SQLite full-text searching"}, + {"SQLite Tutorial", "Help you learn SQLite quickly and effectively"}, + }; - /// INSERT INTO posts(title,body) - /// VALUES('Learn SQlite FTS5','This tutorial teaches you how to perform full-text search in SQLite using FTS5'), - /// ('Advanced SQlite Full-text Search','Show you some advanced techniques in SQLite full-text searching'), - /// ('SQLite Tutorial','Help you learn SQLite quickly and effectively'); - storage.insert_range(postsToInsert.begin(), postsToInsert.end()); + /// INSERT INTO posts(title,body) + /// VALUES('Learn SQlite FTS5','This tutorial teaches you how to perform full-text search in SQLite using FTS5'), + /// ('Advanced SQlite Full-text Search','Show you some advanced techniques in SQLite full-text searching'), + /// ('SQLite Tutorial','Help you learn SQLite quickly and effectively'); + storage.insert_range(postsToInsert.begin(), postsToInsert.end()); - /// SELECT * FROM posts; - auto posts = storage.get_all(); + /// SELECT * FROM posts; + auto posts = storage.get_all(); - // check that all the posts are there - REQUIRE_THAT(posts, UnorderedEquals(postsToInsert)); + // check that all the posts are there + REQUIRE_THAT(posts, UnorderedEquals(postsToInsert)); - /// SELECT * - /// FROM posts - /// WHERE posts MATCH 'fts5'; - auto specificPosts = storage.get_all(where(match("fts5"))); - decltype(specificPosts) expectedSpecificPosts = { - {"Learn SQlite FTS5", "This tutorial teaches you how to perform full-text search in SQLite using FTS5"}, - }; - REQUIRE(specificPosts == expectedSpecificPosts); - - /// SELECT * - /// FROM posts - /// WHERE posts = 'fts5'; - auto specificPosts2 = storage.get_all(where(is_equal("fts5"))); - REQUIRE(specificPosts2 == specificPosts); - - /// SELECT * - /// FROM posts - /// WHERE posts MATCH 'text' - /// ORDER BY rank; - auto orderedPosts = storage.get_all(where(match("fts5")), order_by(rank())); - - /// SELECT highlight(posts,0, '', ''), - /// highlight(posts,1, '', '') - /// FROM posts - /// WHERE posts MATCH 'SQLite' - /// ORDER BY rank; - /// - auto highlightedPosts = - storage.select(columns(highlight(0, "", ""), highlight(1, "", "")), - where(match("SQLite")), - order_by(rank())); + /// SELECT * + /// FROM posts + /// WHERE posts MATCH 'fts5'; + auto specificPosts = storage.get_all(where(match("fts5"))); + decltype(specificPosts) expectedSpecificPosts = { + {"Learn SQlite FTS5", "This tutorial teaches you how to perform full-text search in SQLite using FTS5"}, + }; + REQUIRE(specificPosts == expectedSpecificPosts); +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + specificPosts = storage.get_all(where(match(post_table->*&fts5::hidden::any, "fts5"))); + REQUIRE(specificPosts == expectedSpecificPosts); + specificPosts = storage.get_all(where(match(Post::hidden::any_field, "fts5"))); + REQUIRE(specificPosts == expectedSpecificPosts); +#endif + specificPosts = storage.get_all(from(post_table("fts5"))); + REQUIRE(specificPosts == expectedSpecificPosts); + + /// SELECT * + /// FROM posts + /// WHERE posts = 'fts5'; + auto specificPosts2 = storage.get_all(where(is_equal("fts5"))); + REQUIRE(specificPosts2 == specificPosts); +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + specificPosts2 = storage.get_all(where(post_table->*Post::hidden::any_field == "fts5")); + REQUIRE(specificPosts2 == specificPosts); +#endif + + /// SELECT * + /// FROM posts + /// WHERE posts MATCH 'text' + /// ORDER BY rank; + auto orderedPosts = storage.get_all(where(match("fts5")), order_by(rank())); +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + orderedPosts = + storage.get_all(where(match(Post::hidden::any_field, "fts5")), order_by(Post::hidden::rank_field)); +#endif + + /// SELECT highlight(posts, 0, '', ''), + /// highlight(posts, 1, '', '') + /// FROM posts + /// WHERE posts MATCH 'SQLite' + /// ORDER BY rank; + /// + auto highlightedPosts = + storage.select(columns(highlight(0, "", ""), highlight(1, "", "")), + where(match("SQLite")), + order_by(rank())); +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + highlightedPosts = storage.select(columns(highlight(Post::hidden::any_field, 0, "", ""), + highlight(Post::hidden::any_field, 1, "", "")), + where(match(Post::hidden::any_field, "SQLite")), + order_by(Post::hidden::rank_field)); +#endif + } } TEST_CASE("issue1410") { @@ -128,18 +164,15 @@ TEST_CASE("issue1410") { #ifdef SQLITE_ENABLE_DBSTAT_VTAB TEST_CASE("dbstat virtual table schema") { - constexpr auto compareColumnName = [](const std::string* foundValue, const std::string& expectedValue) { - if (!foundValue) { - return false; - } - return *foundValue == expectedValue; - }; - - SECTION("epynomous") { + SECTION("eponymous") { SECTION("definition") { auto virtualTable = make_dbstat_table(); REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::name), "name")); REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::pgsize), "pgsize")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::hidden::schema), "schema")); +#if SQLITE_VERSION_NUMBER >= 3031000 + REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::hidden::aggregate), "aggregate")); +#endif } SECTION("storage") { auto storage = make_storage("", make_dbstat_table()); @@ -149,20 +182,29 @@ TEST_CASE("dbstat virtual table schema") { auto dbstatRows = storage.get_all(); REQUIRE(dbstatRows.size() == 0); + + dbstatRows = storage.get_all(where(dbstat_table->*&dbstat::hidden::schema == "main")); + REQUIRE(dbstatRows.size() == 0); + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + dbstatRows = storage.select(object(), from(dbstat_table("main"))); + REQUIRE(dbstatRows.size() == 0); +#endif } } SECTION("virtual table instance") { - struct mystat : sqlite_orm::dbstat {}; + struct mystat : dbstat {}; + constexpr auto mystat_table = c(); SECTION("definition") { auto virtualTable = make_virtual_table("mystat", using_dbstat()); - REQUIRE(compareColumnName(virtualTable.find_column_name(&mystat::name), "name")); - REQUIRE(compareColumnName(virtualTable.find_column_name(&mystat::pgsize), "pgsize")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::name), "name")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::pgsize), "pgsize")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&dbstat::hidden::schema), "schema")); } SECTION("storage") { - auto storage = - make_storage("", make_sqlite_schema_table(), make_virtual_table("mystat", using_dbstat())); + auto storage = make_storage("", make_virtual_table("mystat", using_dbstat())); storage.sync_schema(); storage.sync_schema_simulate(); REQUIRE(storage.table_exists("mystat")); @@ -170,6 +212,82 @@ TEST_CASE("dbstat virtual table schema") { auto mystatRows = storage.get_all(); REQUIRE(mystatRows.size() == 1); + + mystatRows = storage.get_all(where(mystat_table->*&dbstat::hidden::schema == "main")); + REQUIRE(mystatRows.size() == 1); + } + } +} +#endif + +#if SQLITE_VERSION_NUMBER >= 3008012 +TEST_CASE("generate_series virtual table schema") { + using Catch::Matchers::Equals, Catch::Matchers::UnorderedEquals; + + SECTION("eponymous") { + SECTION("definition") { + auto virtualTable = make_generate_series_table(); + REQUIRE(compareColumnName(virtualTable.find_column_name(&generate_series::value), "value")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&generate_series::hidden::step), "step")); + } + SECTION("storage") { + struct Customer { + int64 id = 0; + std::string name; + }; + constexpr auto customer_table = c(); + + auto storage = make_storage("", + make_generate_series_table(), + make_table("customer", + make_column("id", &Customer::id, primary_key()), + make_column("name", &Customer::name)), + on_open([](sqlite3* db) { + sqlite3_series_init(db, nullptr, nullptr); + })); + storage.sync_schema(); + storage.insert(into(), + columns(&Customer::id, &Customer::name), + values(std::tuple(1, "c1"), + std::tuple(100, "c100"), + std::tuple(15000, "c15000"), + std::tuple(15100, "c15100"), + std::tuple(20100, "c20100"))); + SECTION("eponymous") { + // eponymous virtual tables must not get created + REQUIRE_FALSE(storage.table_exists("generate_series")); + } + SECTION("equivalent query") { + auto rows = storage.select(&generate_series::value, + where(c(&generate_series::hidden::start) == 5 and + c(&generate_series::hidden::stop) == 30 and + c(&generate_series::hidden::step) == 5)); + REQUIRE_THAT(rows, Equals(std::vector{5, 10, 15, 20, 25, 30})); + } + SECTION("series") { + auto sql = storage.dump(select(&generate_series::value, from(generate_series_table(5, 30, 5))), true); + auto rows = storage.select(&generate_series::value, from(generate_series_table(5, 30, 5))); + REQUIRE_THAT(rows, Equals(std::vector{5, 10, 15, 20, 25, 30})); + } + SECTION("random") { + auto rows = storage.select(sqlite_orm::random(), from(generate_series_table(1, 6))); + REQUIRE(rows.size() == 6); + } + SECTION("customer 1") { + auto rows = storage.select( + &Customer::name, + from(customer_table, generate_series_table(10000, 20000, 100)), + where(customer_table->*&Customer::id == generate_series_table->*&generate_series::value)); + REQUIRE_THAT(rows, UnorderedEquals(std::vector{"c15000", "c15100"})); + } + SECTION("customer 2") { + auto rows = storage.select(&Customer::name, + from(customer_table), + where(in(customer_table->*&Customer::id, + select(generate_series_table->*&generate_series::value, + from(generate_series_table(10000, 20000, 100)))))); + REQUIRE_THAT(rows, UnorderedEquals(std::vector{"c15000", "c15100"})); + } } } } @@ -189,27 +307,25 @@ TEST_CASE("rtree virtual table schema") { make_column("maxX", &DemoIndex::maxX), make_column("minY", &DemoIndex::minY), make_column("maxY", &DemoIndex::maxY))); - { - constexpr auto compareColumnName = [](const std::string* foundValue, std::string expectedValue) { - if (!foundValue) { - return false; - } - return *foundValue == expectedValue; - }; - REQUIRE(compareColumnName(virtualTable.find_column_name(&DemoIndex::id), "id")); - REQUIRE(compareColumnName(virtualTable.find_column_name(&DemoIndex::maxY), "maxY")); - } - - auto storage = make_storage("", std::move(virtualTable)); - storage.sync_schema(); - storage.sync_schema_simulate(); - REQUIRE(storage.table_exists("demo_index")); - - storage.insert(into(), - columns(&DemoIndex::id, &DemoIndex::minX, &DemoIndex::maxX, &DemoIndex::minY, &DemoIndex::maxY), - values(std::tuple(28269, -80.851471, -80.735718, 35.272560, 35.407925))); - auto rows = storage.select(&DemoIndex::id, where(c(&DemoIndex::id) == 28269)); - REQUIRE(rows == std::vector{28269}); + SECTION("eponymous") { + SECTION("definition") { + REQUIRE(compareColumnName(virtualTable.find_column_name(&DemoIndex::id), "id")); + REQUIRE(compareColumnName(virtualTable.find_column_name(&DemoIndex::maxY), "maxY")); + } + } + SECTION("storage") { + auto storage = make_storage("", std::move(virtualTable)); + storage.sync_schema(); + storage.sync_schema_simulate(); + REQUIRE(storage.table_exists("demo_index")); + + storage.insert(into(), + columns(&DemoIndex::id, &DemoIndex::minX, &DemoIndex::maxX, &DemoIndex::minY, &DemoIndex::maxY), + values(std::tuple(28269, -80.851471, -80.735718, 35.272560, 35.407925))); + + auto rows = storage.select(&DemoIndex::id, where(c(&DemoIndex::id) == 28269)); + REQUIRE(rows == std::vector{28269}); + } } #endif diff --git a/tests/statement_serializer_tests/ast/match.cpp b/tests/statement_serializer_tests/ast/match.cpp index 227ac5fd..7e288969 100644 --- a/tests/statement_serializer_tests/ast/match.cpp +++ b/tests/statement_serializer_tests/ast/match.cpp @@ -8,15 +8,37 @@ TEST_CASE("statement_serializer match") { struct User { int id = 0; std::string name; + + using hidden = fts5::hidden_fields_of; }; + constexpr auto user_table = c(); + auto table = make_virtual_table("users", using_fts5(make_column("id", &User::id), make_column("name", &User::name))); using db_objects_t = internal::db_objects_tuple; auto dbObjects = db_objects_t{table}; using context_t = internal::serializer_context; context_t context{dbObjects}; - auto node = match("Claude"); - auto value = serialize(node, context); - REQUIRE(value == R"("users" MATCH 'Claude')"); + + SECTION("match using explicit template parameter") { + auto node = match("Claude"); + auto value = serialize(node, context); + REQUIRE(value == R"("users" MATCH 'Claude')"); + } + SECTION("match any column") { + auto node = match(user_table->*&fts5::hidden::any, "Claude"); + auto value = serialize(node, context); + REQUIRE(value == R"("users" MATCH 'Claude')"); + } + SECTION("match any column, rebound") { + auto node = match(User::hidden::any_field, "Claude"); + auto value = serialize(node, context); + REQUIRE(value == R"("users" MATCH 'Claude')"); + } + SECTION("match specific column") { + auto node = match(&User::name, "Claude"); + auto value = serialize(node, context); + REQUIRE(value == R"("name" MATCH 'Claude')"); + } } #endif diff --git a/tests/statement_serializer_tests/schema/using_dbstat.cpp b/tests/statement_serializer_tests/schema/using_dbstat.cpp index f368892b..d3a9f7e2 100644 --- a/tests/statement_serializer_tests/schema/using_dbstat.cpp +++ b/tests/statement_serializer_tests/schema/using_dbstat.cpp @@ -5,29 +5,94 @@ using namespace sqlite_orm; #ifdef SQLITE_ENABLE_DBSTAT_VTAB TEST_CASE("statement_serializer dbstat") { - struct mystat : sqlite_orm::dbstat {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - constexpr orm_table_reference auto mystat_table = c(); -#endif + struct mystat : dbstat {}; + constexpr auto mystat_table = c(); std::string value; std::string expected; - using db_objects_t = internal::db_objects_tuple<>; - const db_objects_t dbObjects{}; - using context_t = internal::serializer_context; - context_t context{dbObjects}; - SECTION("default") { - auto expression = make_virtual_table("mystat", using_dbstat()); - value = serialize(expression, context); - expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat")"; - } + + SECTION("create") { + using db_objects_t = internal::db_objects_tuple<>; + const db_objects_t dbObjects{}; + using context_t = internal::serializer_context; + context_t context{dbObjects}; + SECTION("default") { + auto expression = make_virtual_table("mystat", using_dbstat()); + value = serialize(expression, context); + expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat")"; + } + SECTION("table value 1") { + auto expression = make_virtual_table("mystat", using_dbstat("main")); + value = serialize(expression, context); + expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat"('main'))"; + } +#if SQLITE_VERSION_NUMBER >= 3031000 + SECTION("table value 2") { + auto expression = make_virtual_table("mystat", using_dbstat("main", true)); + value = serialize(expression, context); + expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat"('main', 1))"; + } +#endif #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - SECTION("table reference") { - auto expression = make_virtual_table("mystat", using_dbstat()); - value = serialize(expression, context); - expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat")"; + SECTION("table reference") { + auto expression = make_virtual_table("mystat", using_dbstat()); + value = serialize(expression, context); + expected = R"(CREATE VIRTUAL TABLE IF NOT EXISTS "mystat" USING "dbstat")"; + } +#endif } + SECTION("expressions") { + auto table = make_virtual_table("dbstat", using_dbstat()); + using table_type = decltype(table); + using db_objects_t = internal::db_objects_tuple; + const db_objects_t dbObjects{table}; + using context_t = internal::serializer_context; + context_t context{dbObjects}; + context.use_parentheses = false; + SECTION("asterisk, without hidden columns") { + auto expression = select(asterisk(true)); + value = serialize(expression, context); + expected = + R"(SELECT "dbstat"."name", "dbstat"."path", "dbstat"."pageno", "dbstat"."pagetype", "dbstat"."ncell", "dbstat"."payload", "dbstat"."unused", "dbstat"."mx_payload", "dbstat"."pgoffset", "dbstat"."pgsize" FROM "dbstat")"; + } + SECTION("object, without hidden columns") { + auto expression = select(object(true)); + value = serialize(expression, context); + expected = + R"(SELECT "dbstat"."name", "dbstat"."path", "dbstat"."pageno", "dbstat"."pagetype", "dbstat"."ncell", "dbstat"."payload", "dbstat"."unused", "dbstat"."mx_payload", "dbstat"."pgoffset", "dbstat"."pgsize" FROM "dbstat")"; + } + SECTION("with hidden column in query") { + auto expression = select(columns(mystat_table->*&dbstat::name, mystat_table->*&dbstat::hidden::schema), + where(mystat_table->*&dbstat::hidden::schema == "main")); + value = serialize(expression, context); + expected = R"(SELECT "dbstat"."name", "dbstat"."schema" FROM "dbstat" WHERE ("dbstat"."schema" = 'main'))"; + } + SECTION("with hidden column in query v2") { + auto expression = select(columns(mystat_table->*&dbstat::name, mystat_table->*&dbstat::hidden::schema), + where(mystat_table->*&dbstat::hidden::schema == "main")); + value = serialize(expression, context); + expected = R"(SELECT "dbstat"."name", "dbstat"."schema" FROM "dbstat" WHERE ("dbstat"."schema" = 'main'))"; + } + SECTION("table-valued function 0") { + value = serialize(from(mystat_table()), context); + expected = R"(FROM "dbstat"())"; + } + SECTION("table-valued function 1") { + value = serialize(from(mystat_table("main")), context); + expected = R"(FROM "dbstat"('main'))"; + } +#if SQLITE_VERSION_NUMBER >= 3031000 + SECTION("table-valued function 2") { + value = serialize(from(mystat_table("main", true)), context); + expected = R"(FROM "dbstat"('main', 1))"; + } #endif + SECTION("get all from table-valued function") { + value = serialize(get_all(from(mystat_table("main"))), context); + expected = + R"(SELECT "dbstat"."name", "dbstat"."path", "dbstat"."pageno", "dbstat"."pagetype", "dbstat"."ncell", "dbstat"."payload", "dbstat"."unused", "dbstat"."mx_payload", "dbstat"."pgoffset", "dbstat"."pgsize" FROM "dbstat"('main'))"; + } + } REQUIRE(value == expected); } #endif diff --git a/tests/statement_serializer_tests/schema/using_rtree.cpp b/tests/statement_serializer_tests/schema/using_rtree.cpp index 49abb434..3eca7ec3 100644 --- a/tests/statement_serializer_tests/schema/using_rtree.cpp +++ b/tests/statement_serializer_tests/schema/using_rtree.cpp @@ -14,9 +14,6 @@ TEST_CASE("statement_serializer rtree") { std::string objtype; std::vector boundary; }; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - constexpr orm_table_reference auto demo_index = c(); -#endif std::string value; std::string expected; diff --git a/tests/static_tests/alias.cpp b/tests/static_tests/alias.cpp index 5c6d72e3..373b68c8 100644 --- a/tests/static_tests/alias.cpp +++ b/tests/static_tests/alias.cpp @@ -111,6 +111,7 @@ TEST_CASE("aliases") { STATIC_REQUIRE(std::is_same_v); using d_alias_type = decltype("d"_alias.for_()); runTest>(from()); + runTest>(from(d_alias)); runTest>(asterisk()); runTest>(object()); runTest>(count()); diff --git a/tests/static_tests/column_pointer.cpp b/tests/static_tests/column_pointer.cpp index 05c92f09..ae945222 100644 --- a/tests/static_tests/column_pointer.cpp +++ b/tests/static_tests/column_pointer.cpp @@ -7,6 +7,10 @@ using namespace sqlite_orm; using internal::column_pointer; +using internal::is_recordset_alias_v; +using internal::is_table_alias_v; +using internal::table_reference; +using internal::table_valued_expression; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES using internal::count_asterisk_t; using internal::decay_table_ref_t; @@ -16,12 +20,9 @@ using internal::get_all_t; using internal::get_optional_t; using internal::get_pointer_t; using internal::get_t; -using internal::is_recordset_alias_v; -using internal::is_table_alias_v; using internal::mapped_view; using internal::remove_all_t; using internal::remove_t; -using internal::table_reference; using internal::using_t; using std::same_as; #endif @@ -113,15 +114,15 @@ TEST_CASE("column pointers") { int id; }; struct DerivedUser : User {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES constexpr auto derived_user = c(); -#endif SECTION("table reference") { -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - STATIC_REQUIRE(orm_table_reference); STATIC_REQUIRE_FALSE(is_table_alias_v); STATIC_REQUIRE_FALSE(is_recordset_alias_v); +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + STATIC_REQUIRE(orm_table_reference); +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES STATIC_REQUIRE_FALSE(orm_table_alias); STATIC_REQUIRE_FALSE(orm_recordset_alias); runTest>(derived_user); @@ -145,8 +146,13 @@ TEST_CASE("column pointers") { STATIC_REQUIRE(storage_field_callable*&DerivedUser::id)>); #endif } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("table reference expressions") { +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + runTest>>(from(dbstat_table("main"))); + runTest, table_valued_expression>>( + from(dbstat_table, dbstat_table("main"))); +#endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES runTest>(make_table("derived_user")); runTest>(from()); runTest>(into()); @@ -185,6 +191,6 @@ TEST_CASE("column pointers") { STATIC_REQUIRE(storage_refers_to_table_callable); STATIC_REQUIRE(storage_table_reference_callable); STATIC_REQUIRE(storage_refers_to_table_callable); - } #endif + } } diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 6f6b2fa5..fae97dda 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -168,3 +168,18 @@ TEST_CASE("column_result_of_t") { #endif #endif } + +TEST_CASE("mapped type proxy") { + using internal::mapped_type_proxy_t; + + STATIC_REQUIRE(std::is_same, sqlite_master>::value); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + STATIC_REQUIRE(std::is_same, sqlite_master>::value); +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + STATIC_REQUIRE(std::is_same, dbstat>::value); +#endif +#if SQLITE_VERSION_NUMBER >= 3008012 + STATIC_REQUIRE(std::is_same, generate_series>::value); +#endif +} \ No newline at end of file diff --git a/tests/static_tests/cte.cpp b/tests/static_tests/cte.cpp index 4566d2c5..bb19fa04 100644 --- a/tests/static_tests/cte.cpp +++ b/tests/static_tests/cte.cpp @@ -100,12 +100,13 @@ TEST_CASE("CTE storage") { } 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(cte1)); +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES runTest>(from()); runTest>(asterisk()); runTest>(count()); @@ -114,7 +115,7 @@ TEST_CASE("CTE expressions") { 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 89cf2894..b3c5dd00 100644 --- a/tests/static_tests/node_tuple.cpp +++ b/tests/static_tests/node_tuple.cpp @@ -6,6 +6,7 @@ using namespace sqlite_orm; using internal::alias_holder; using internal::column_alias; using internal::column_pointer; +using internal::literal_holder; template struct is_pair : std::false_type {}; @@ -60,9 +61,8 @@ TEST_CASE("Node tuple") { } } SECTION("non-bindable literals") { - using namespace internal; using Tuple = node_tuple_t>; - using Expected = tuple<>; + using Expected = tuple>; static_assert(is_same::value, "literal int"); STATIC_REQUIRE(is_same, tuple<>>::value); } @@ -622,7 +622,7 @@ TEST_CASE("Node tuple") { STATIC_REQUIRE(is_same, tuple>::value); } SECTION("positional ordinal") { - STATIC_REQUIRE(is_same, tuple<>>::value); + STATIC_REQUIRE(is_same, tuple>>::value); } SECTION("sole column alias") { STATIC_REQUIRE(is_same()))>, tuple<>>::value); @@ -1047,4 +1047,11 @@ TEST_CASE("Node tuple") { STATIC_REQUIRE(std::is_same_v); } #endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + SECTION("table-valued") { + auto expression = from(dbstat_table, dbstat_table("main", true)); + using Tuple = typename node_tuple::type; + STATIC_REQUIRE(is_same>::value); + } +#endif } diff --git a/tests/static_tests/operators_adl.cpp b/tests/static_tests/operators_adl.cpp index 300d50ce..8db867a1 100644 --- a/tests/static_tests/operators_adl.cpp +++ b/tests/static_tests/operators_adl.cpp @@ -142,24 +142,25 @@ TEST_CASE("inline namespace literals expressions") { #endif } -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES TEST_CASE("ADL and pointer-to-member expressions") { struct User { int id; }; constexpr auto user_table = c(); + + user_table->*&User::id; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES constexpr auto u_alias = "u"_alias.for_(); #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) constexpr auto cte = "1"_cte; #endif - user_table->*&User::id; u_alias->*&User::id; #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) cte->*&User::id; #endif -} #endif +} TEST_CASE("ADL and expression operators") { struct User { diff --git a/tests/static_tests/virtual_tables.cpp b/tests/static_tests/virtual_tables.cpp index 82e5a47f..10042e0b 100644 --- a/tests/static_tests/virtual_tables.cpp +++ b/tests/static_tests/virtual_tables.cpp @@ -2,32 +2,116 @@ #include using namespace sqlite_orm; -using internal::col_index_sequence_of, internal::col_index_sequence_with, internal::col_index_sequence_with_field_type; -using internal::is_base_template_of_v; +using internal::col_index_sequence_of, internal::col_index_sequence_with, internal::col_index_sequence_with_field_type, + internal::hidden_col_index_sequence_of, internal::all_col_index_sequence_with_field_type; +using internal::is_base_template_of_v, internal::is_table_reference_v; using internal::table_definition, internal::insertable_table_definition; +template +using table_values_index_sequence = internal::filter_tuple_sequence_t; + #ifdef SQLITE_ENABLE_DBSTAT_VTAB -TEST_CASE("generic vtab and dbstat static tests") { +TEST_CASE("generic vtab and dbstat layout tests") { using internal::dbstat_module_tag; - SECTION("table definition") { + struct mystat : dbstat {}; + + STATIC_REQUIRE(is_table_reference_v); +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + STATIC_REQUIRE(orm_table_reference); +#endif + + SECTION("default table definition") { auto table = make_dbstat_table(); using table_type = decltype(table); using elements_type = decltype(table.elements); STATIC_REQUIRE(std::is_same::value); STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); STATIC_REQUIRE(is_base_template_of_v); STATIC_REQUIRE(col_index_sequence_of::size() == 10); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 3); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 7); -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - STATIC_REQUIRE(orm_table_reference); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 3); + STATIC_REQUIRE(all_col_index_sequence_with_field_type::size() == 4); +#if SQLITE_VERSION_NUMBER >= 3031000 + STATIC_REQUIRE(all_col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(hidden_col_index_sequence_of::size() == 2); +#else + STATIC_REQUIRE(hidden_col_index_sequence_of::size() == 1); +#endif + } + SECTION("table definition with table value") { + auto table = make_virtual_table("dbstat", + using_dbstat("main" +#if SQLITE_VERSION_NUMBER >= 3031000 + , + true +#endif + )); + using table_type = decltype(table); + using elements_type = decltype(table.elements); +#if SQLITE_VERSION_NUMBER >= 3031000 + STATIC_REQUIRE(table_values_index_sequence::size() == 2); +#else + STATIC_REQUIRE(table_values_index_sequence::size() == 1); +#endif + } +} +#endif + +#if SQLITE_VERSION_NUMBER >= 3008012 +TEST_CASE("generate_series layout tests") { + using internal::generate_series_module_tag; + + STATIC_REQUIRE(is_table_reference_v); +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + STATIC_REQUIRE(orm_table_reference); +#endif + + SECTION("default table definition") { + auto table = make_generate_series_table(); + using table_type = decltype(table); + using elements_type = decltype(table.elements); + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(is_base_template_of_v); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(all_col_index_sequence_with_field_type::size() == 4); + STATIC_REQUIRE(hidden_col_index_sequence_of::size() == 3); + } +} +#endif + +#if SQLITE_VERSION_NUMBER >= 3009000 +TEST_CASE("fts5 layout tests") { + using internal::fts5_module_tag; + struct Post { + std::string title; + std::string body; + }; + + STATIC_REQUIRE_FALSE(std::is_constructible::value); + + SECTION("table definition") { + auto definition = using_fts5(make_column("title", &Post::title), make_column("body", &Post::body)); + using definition_type = decltype(definition); + using elements_type = decltype(definition.elements); + STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(is_base_template_of_v); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 2); +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + STATIC_REQUIRE(hidden_col_index_sequence_of::size() == 2); + STATIC_REQUIRE(all_col_index_sequence_with_field_type>::size() == 1); + STATIC_REQUIRE(all_col_index_sequence_with_field_type>::size() == 1); #endif } } #endif #ifdef SQLITE_ENABLE_RTREE -TEST_CASE("rtree static tests") { +TEST_CASE("rtree layout tests") { using internal::rtree_module_tag; struct DemoIndex { int64 id; @@ -52,6 +136,7 @@ TEST_CASE("rtree static tests") { STATIC_REQUIRE(col_index_sequence_of::size() == 3); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 2); + STATIC_REQUIRE(hidden_col_index_sequence_of::size() == 0); } SECTION("5 dimensions definition") { auto definition = using_rtree(make_column("id", &DemoIndex::id, primary_key()), @@ -94,6 +179,7 @@ TEST_CASE("rtree static tests") { } TEST_CASE("rtree_i32 static tests") { + using internal::rtree_i32_module_tag; SECTION("table definition") { struct DemoIndex { int64 id; @@ -105,7 +191,7 @@ TEST_CASE("rtree_i32 static tests") { make_column("maxX", &DemoIndex::maxX)); using definition_type = decltype(definition); using elements_type = decltype(definition.elements); - STATIC_REQUIRE(std::is_same::value); + STATIC_REQUIRE(std::is_same::value); STATIC_REQUIRE(is_base_template_of_v); STATIC_REQUIRE(col_index_sequence_of::size() == 3); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index e4449032..4b009ebe 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -52,7 +52,25 @@ TEST_CASE("table name collector") { } REQUIRE(collector.table_names == expected); } +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + SECTION("from hidden") { + const std::tuple dbObjects2{make_dbstat_table()}; + const std::string& tableName = std::get<0>(dbObjects2).name; + internal::table_name_collector collector(dbObjects2); + SECTION("regular column") { + auto expression = &dbstat::hidden::schema; + expected.emplace(tableName, ""); + iterate_ast(expression, collector); + } + SECTION("regular column pointer") { + auto expression = dbstat_table->*&dbstat::hidden::schema; + expected.emplace(tableName, ""); + iterate_ast(expression, collector); + } + REQUIRE(collector.table_names == expected); + } +#endif #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) SECTION("from CTE") { const auto dbObjects2 = @@ -103,20 +121,23 @@ TEST_CASE("table name collector") { REQUIRE(collector.table_names == expected); } #endif +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED SECTION("highlight") { + using user_hidden = fts5::hidden_fields_of; const std::string& tableName = std::get<0>(dbObjects).name; internal::table_name_collector collector(dbObjects); SECTION("simple") { - auto expression = highlight(0, "", ""); + auto expression = highlight(user_hidden::any_field, 0, "", ""); expected.emplace(tableName, ""); iterate_ast(expression, collector); } SECTION("in columns") { - auto expression = columns(highlight(0, "", "")); + auto expression = columns(highlight(user_hidden::any_field, 0, "", "")); expected.emplace(tableName, ""); iterate_ast(expression, collector); } REQUIRE(collector.table_names == expected); } +#endif }