diff --git a/appveyor.yml b/appveyor.yml index a59eb6e69..9f269fca7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,7 +107,7 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2024.03.25 + git fetch --tags && git checkout 2024.04.26 cd %APPVEYOR_BUILD_FOLDER% C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install @@ -140,7 +140,7 @@ for: install: - |- pushd $HOME/vcpkg - git fetch --tags && git checkout 2024.03.25 + git fetch --tags && git checkout 2024.04.26 popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets @@ -168,7 +168,7 @@ for: # using custom vcpkg triplets for building and linking dynamic dependent libraries install: - |- - git clone --depth 1 --branch 2024.03.25 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2024.04.26 https://github.com/microsoft/vcpkg.git $HOME/vcpkg $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets vcpkg install sqlite3[core,dbstat,math,json1,fts5,soundex] catch2 --overlay-triplets=vcpkg/triplets diff --git a/dev/alias_traits.h b/dev/alias_traits.h index 6455bce53..4b25470c6 100644 --- a/dev/alias_traits.h +++ b/dev/alias_traits.h @@ -1,6 +1,6 @@ #pragma once -#include // std::remove_const, std::is_base_of, std::is_same, std::type_identity +#include // std::is_base_of, std::is_same #ifdef SQLITE_ORM_WITH_CPP20_ALIASES #include #endif @@ -8,6 +8,7 @@ #include "functional/cxx_universal.h" #include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" +#include "table_reference.h" namespace sqlite_orm { @@ -51,24 +52,6 @@ namespace sqlite_orm { template struct is_table_alias : polyfill::bool_constant> {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* - * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. - */ - template - struct table_reference : std::type_identity {}; - - template - struct decay_table_reference : std::remove_const {}; - template - struct decay_table_reference> : std::type_identity {}; - template - struct decay_table_reference> : std::type_identity {}; - - template - using decay_table_reference_t = typename decay_table_reference::type; -#endif - /** @short Moniker of a CTE, see `orm_cte_moniker`. */ template @@ -115,14 +98,6 @@ namespace sqlite_orm { template concept orm_table_alias = (orm_recordset_alias && !std::same_as>); - /** @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 Moniker of a CTE. * * A CTE moniker has the following traits: diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index 6aa5b2f9b..6c0945092 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -172,9 +172,9 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = columns_t; + template + struct ast_iterator, is_struct>::value>> { + using node_type = C; template void operator()(const node_type& cols, L& lambda) const { diff --git a/dev/column_expression.h b/dev/column_expression.h index d8ce2c64d..e0d099257 100644 --- a/dev/column_expression.h +++ b/dev/column_expression.h @@ -89,6 +89,15 @@ namespace sqlite_orm { using type = std::tuple>...>; }; + /** + * Resolve multiple columns. + * struct_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + /** * Resolve column(s) of subselect. * select_t -> ColExpr, tuple diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index 3b95f0018..1792dad73 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -105,6 +105,20 @@ namespace sqlite_orm { return this->collectedExpressions; } + template + std::vector& operator()(const struct_t& cols, const Ctx& context) { + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); + iterate_tuple(cols.columns, [this, &context](auto& colExpr) { + (*this)(colExpr, context); + }); + // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); + } + return this->collectedExpressions; + } + std::vector collectedExpressions; }; diff --git a/dev/column_pointer.h b/dev/column_pointer.h index 19d5df6e9..00f75e356 100644 --- a/dev/column_pointer.h +++ b/dev/column_pointer.h @@ -6,6 +6,7 @@ #include "functional/cxx_core_features.h" #include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" +#include "table_reference.h" #include "alias_traits.h" #include "tags.h" diff --git a/dev/column_result.h b/dev/column_result.h index 63d2cab5a..4461f3a19 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -18,6 +18,7 @@ #include "select_constraints.h" #include "operators.h" #include "rowid.h" +#include "column_result_proxy.h" #include "alias.h" #include "cte_types.h" #include "storage_traits.h" @@ -246,6 +247,11 @@ namespace sqlite_orm { struct column_result_t, void> : conc_tuple>>...> {}; + template + struct column_result_t, void> { + using type = structure>>...>>; + }; + template struct column_result_t> : column_result_t {}; @@ -297,7 +303,7 @@ namespace sqlite_orm { template struct column_result_t, void> { - using type = T; + using type = table_reference; }; template diff --git a/dev/column_result_proxy.h b/dev/column_result_proxy.h new file mode 100644 index 000000000..6831991aa --- /dev/null +++ b/dev/column_result_proxy.h @@ -0,0 +1,41 @@ +#pragma once + +#include "type_traits.h" +#include "table_reference.h" + +namespace sqlite_orm { + namespace internal { + + /* + * Holder for the type of an unmapped aggregate/structure/object to be constructed ad-hoc from column results. + * `T` must be constructible using direct-list-initialization. + */ + template + struct structure { + using type = T; + }; + } +} + +namespace sqlite_orm { + namespace internal { + + template + struct column_result_proxy : std::remove_const {}; + + /* + * Unwrap `table_reference` + */ + template + struct column_result_proxy> : decay_table_ref

{}; + + /* + * Unwrap `structure` + */ + template + struct column_result_proxy> : P {}; + + template + using column_result_proxy_t = typename column_result_proxy::type; + } +} diff --git a/dev/conditions.h b/dev/conditions.h index dc6fc419e..e1a1fccdc 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -17,6 +17,7 @@ #include "serializer_context.h" #include "serialize_result_type.h" #include "tags.h" +#include "table_reference.h" #include "alias_traits.h" #include "expression.h" #include "column_pointer.h" @@ -837,7 +838,7 @@ namespace sqlite_orm { */ template auto from() { - return from...>(); + return from...>(); } #endif @@ -991,7 +992,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto left_join(On on) { - return left_join, On>(std::move(on)); + return left_join, On>(std::move(on)); } #endif @@ -1003,7 +1004,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto join(On on) { - return join, On>(std::move(on)); + return join, On>(std::move(on)); } #endif @@ -1015,7 +1016,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto left_outer_join(On on) { - return left_outer_join, On>(std::move(on)); + return left_outer_join, On>(std::move(on)); } #endif @@ -1027,7 +1028,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto inner_join(On on) { - return inner_join, On>(std::move(on)); + return inner_join, On>(std::move(on)); } #endif diff --git a/dev/core_functions.h b/dev/core_functions.h index 26c8a1b1f..ee3601610 100644 --- a/dev/core_functions.h +++ b/dev/core_functions.h @@ -13,6 +13,7 @@ #include "serialize_result_type.h" #include "operators.h" #include "tags.h" +#include "table_reference.h" #include "ast/into.h" namespace sqlite_orm { @@ -1853,7 +1854,7 @@ namespace sqlite_orm { */ template auto count() { - return count>(); + return count>(); } #endif diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h index 71a2fe58a..f599e3fef 100644 --- a/dev/cte_column_names_collector.h +++ b/dev/cte_column_names_collector.h @@ -116,6 +116,12 @@ namespace sqlite_orm { static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); }; + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + }; + template struct cte_column_names_collector> { using expression_type = Columns; diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index dca68216d..22a7943ff 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -3,6 +3,7 @@ #include // std::remove_const #include "type_traits.h" +#include "table_reference.h" #include "alias_traits.h" namespace sqlite_orm { diff --git a/dev/node_tuple.h b/dev/node_tuple.h index 428a0f92f..09be0e06d 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -113,6 +113,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple_for {}; + template + struct node_tuple, void> : node_tuple_for {}; + template struct node_tuple, void> : node_tuple_for {}; diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index a3e82f4f3..86aae7f40 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -2,9 +2,14 @@ #include #include // std::is_member_object_pointer +#include // std::move #include "functional/static_magic.h" +#include "member_traits/member_traits.h" +#include "table_reference.h" #include "row_extractor.h" +#include "schema/column.h" +#include "storage_lookup.h" namespace sqlite_orm { @@ -12,10 +17,11 @@ namespace sqlite_orm { struct object_from_column_builder_base { sqlite3_stmt* stmt = nullptr; - int index = 0; + int columnIndex = -1; #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} + object_from_column_builder_base(sqlite3_stmt* stmt, int columnIndex = -1) : + stmt{stmt}, columnIndex{columnIndex} {} #endif }; @@ -28,13 +34,13 @@ namespace sqlite_orm { object_type& object; - object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : - object_from_column_builder_base{stmt_}, object(object_) {} + object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_, int nextColumnIndex = 0) : + object_from_column_builder_base{stmt_, nextColumnIndex - 1}, object(object_) {} template void operator()(const column_field& column) { const auto rowExtractor = row_value_extractor>(); - auto value = rowExtractor.extract(this->stmt, this->index++); + auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); static_if::value>( [&value, &object = this->object](const auto& column) { object.*column.member_pointer = std::move(value); @@ -44,5 +50,34 @@ namespace sqlite_orm { })(column); } }; + + /** + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_referenece` + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = 0; + return this->extract(stmt, columnIndex); + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + O obj; + object_from_column_builder builder{obj, stmt, columnIndex}; + auto& table = pick_table(this->db_objects); + table.for_each_column(builder); + columnIndex = builder.columnIndex; + return obj; + } + + O extract(sqlite3_value* value) const = delete; + }; } } diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index 12bc2ecc3..fc614e9f9 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -14,6 +14,7 @@ #include "connection_holder.h" #include "select_constraints.h" #include "values.h" +#include "table_reference.h" #include "mapped_type_proxy.h" #include "ast/upsert_clause.h" #include "ast/set.h" @@ -692,7 +693,7 @@ namespace sqlite_orm { class R = std::vector>, class... Args> auto get_all(Args&&... conditions) { - return get_all, R>(std::forward(conditions)...); + return get_all, R>(std::forward(conditions)...); } #endif diff --git a/dev/row_extractor.h b/dev/row_extractor.h index b8d3b9c87..6012cee3a 100644 --- a/dev/row_extractor.h +++ b/dev/row_extractor.h @@ -19,6 +19,9 @@ #endif #include "functional/cxx_universal.h" +#include "functional/cxx_functional_polyfill.h" +#include "functional/static_magic.h" +#include "column_result_proxy.h" #include "arithmetic_tag.h" #include "pointer_value.h" #include "journal_mode.h" @@ -363,7 +366,7 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template<> - struct row_extractor { + struct row_extractor { nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -380,7 +383,7 @@ namespace sqlite_orm { * Specialization for std::vector. */ template<> - struct row_extractor> { + struct row_extractor, void> { std::vector extract(const char* columnText) const { return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } @@ -398,32 +401,6 @@ namespace sqlite_orm { } }; - /** - * Specialization for a tuple. - */ - template - struct row_extractor> { - - std::tuple extract(char** argv) const { - return this->extract(argv, std::make_index_sequence{}); - } - - std::tuple extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - return this->extract(stmt, std::make_index_sequence{}); - } - - protected: - template - std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return {row_extractor{}.extract(stmt, Idx)...}; - } - - template - std::tuple extract(char** argv, std::index_sequence) const { - return {row_extractor{}.extract(argv[Idx])...}; - } - }; - /** * Specialization for journal_mode. */ @@ -445,5 +422,106 @@ namespace sqlite_orm { auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex); return this->extract(cStr); } + + journal_mode extract(sqlite3_value* value) const = delete; }; + + namespace internal { + + /* + * Helper to extract a structure from a rowset. + */ + template + struct struct_extractor; + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + /* + * Returns a value-based row extractor for an unmapped type, + * returns a structure extractor for a table reference, tuple or named struct. + */ + template + auto make_row_extractor([[maybe_unused]] const DBOs& dbObjects) { + if constexpr(polyfill::is_specialization_of_v || + polyfill::is_specialization_of_v || is_table_reference_v) { + return struct_extractor{dbObjects}; + } else { + return row_value_extractor(); + } + } +#else + /* + * Overload for an unmapped type returns a common row extractor. + */ + template< + class R, + class DBOs, + std::enable_if_t, + polyfill::is_specialization_of, + is_table_reference>>::value, + bool> = true> + auto make_row_extractor(const DBOs& /*dbObjects*/) { + return row_value_extractor(); + } + + /* + * Overload for a table reference, tuple or aggregate of column results returns a structure extractor. + */ + template, + polyfill::is_specialization_of, + is_table_reference>::value, + bool> = true> + struct_extractor make_row_extractor(const DBOs& dbObjects) { + return {dbObjects}; + } +#endif + + /** + * Specialization for a tuple of top-level column results. + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + std::tuple extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + std::tuple...> extract(sqlite3_stmt* stmt, + int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return {make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + // unused to date + std::tuple...> extract(sqlite3_stmt* stmt, int& columnIndex) const = delete; + + std::tuple extract(sqlite3_value* value) const = delete; + }; + + /** + * Specialization for an unmapped structure to be constructed ad-hoc from column results. + * + * This plays together with `column_result_of_t`, which returns `struct_t` as `structure` + */ + template + struct struct_extractor>, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + O extract(sqlite3_value* value) const = delete; + }; + } } diff --git a/dev/schema/table.h b/dev/schema/table.h index b71a7c083..7f90cab90 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -440,7 +440,7 @@ namespace sqlite_orm { */ template auto make_table(std::string name, Cs... args) { - return make_table>(std::move(name), std::forward(args)...); + return make_table>(std::move(name), std::forward(args)...); } #endif diff --git a/dev/select_constraints.h b/dev/select_constraints.h index d59e00990..a6f5441a6 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -88,6 +88,31 @@ namespace sqlite_orm { template using is_columns = polyfill::bool_constant>; + /* + * Captures the type of an aggregate/structure/object and column expressions, such that + * `T` can be constructed in-place as part of a result row. + * `T` must be constructible using direct-list-initialization. + */ + template + struct struct_t { + using columns_type = std::tuple; + + columns_type columns; + bool distinct = false; + + static constexpr int count = std::tuple_size::value; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + struct_t(columns_type columns) : columns{std::move(columns)} {} +#endif + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_struct_v = polyfill::is_specialization_of::value; + + template + using is_struct = polyfill::bool_constant>; + /** * Subselect object type. */ @@ -309,6 +334,11 @@ namespace sqlite_orm { return cols.distinct; } + template + bool get_distinct(const struct_t& cols) { + return cols.distinct; + } + template struct asterisk_t { using type = T; @@ -432,8 +462,20 @@ namespace sqlite_orm { return cols; } + /* + * Combine multiple columns in a tuple. + */ template - internal::columns_t columns(Args... args) { + constexpr internal::columns_t columns(Args... args) { + return {std::make_tuple(std::forward(args)...)}; + } + + /* + * Construct an unmapped structure ad-hoc from multiple columns. + * `T` must be constructible from the column results using direct-list-initialization. + */ + template + constexpr internal::struct_t struct_(Args... args) { return {std::make_tuple(std::forward(args)...)}; } @@ -732,7 +774,7 @@ namespace sqlite_orm { */ template auto asterisk(bool definedOrder = false) { - return asterisk>(definedOrder); + return asterisk>(definedOrder); } #endif @@ -755,7 +797,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto object(bool definedOrder = false) { - return object>(definedOrder); + return object>(definedOrder); } #endif } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index dc37c1747..791e870dc 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -1393,9 +1393,9 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = columns_t; + template + struct statement_serializer, is_struct>::value>> { + using statement_type = C; template std::string operator()(const statement_type& statement, const Ctx& context) const { diff --git a/dev/storage.h b/dev/storage.h index cdcc3b63f..afe8a072a 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -49,6 +49,7 @@ #include "serializer_context.h" #include "schema/triggers.h" #include "object_from_column_builder.h" +#include "row_extractor.h" #include "schema/table.h" #include "schema/column.h" #include "schema/index.h" @@ -398,7 +399,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto get(Ids... ids) { - return this->get>(std::forward(ids)...); + return this->get>(std::forward(ids)...); } #endif @@ -463,7 +464,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template int count(Args&&... args) { - return this->count>(std::forward(args)...); + return this->count>(std::forward(args)...); } #endif @@ -563,10 +564,10 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr max(F field, Args&&... args) { + std::unique_ptr max(F field, Args&&... args) { this->assert_mapped_type>(); auto rows = this->select(sqlite_orm::max(std::move(field)), std::forward(args)...); if(!rows.empty()) { @@ -583,10 +584,10 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr min(F field, Args&&... args) { + std::unique_ptr min(F field, Args&&... args) { this->assert_mapped_type>(); auto rows = this->select(sqlite_orm::min(std::move(field)), std::forward(args)...); if(!rows.empty()) { @@ -603,16 +604,16 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr sum(F field, Args&&... args) { + std::unique_ptr sum(F field, Args&&... args) { this->assert_mapped_type>(); std::vector> rows = this->select(sqlite_orm::sum(std::move(field)), std::forward(args)...); if(!rows.empty()) { if(rows.front()) { - return std::make_unique(std::move(*rows.front())); + return std::make_unique(std::move(*rows.front())); } else { return {}; } @@ -646,8 +647,8 @@ namespace sqlite_orm { * For a single column use `auto rows = storage.select(&User::id, where(...)); * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); */ - template> - std::vector select(T m, Args... args) { + template + auto select(T m, Args... args) { static_assert(!is_compound_operator_v || sizeof...(Args) == 0, "Cannot use args with a compound operator"); auto statement = this->prepare(sqlite_orm::select(std::move(m), std::forward(args)...)); @@ -1500,35 +1501,19 @@ namespace sqlite_orm { perform_step(stmt); } - template = true> - std::vector _execute_select(const S& statement) { + template + auto _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); + using R = decltype(make_row_extractor(this->db_objects).extract(nullptr, 0)); std::vector res; - perform_steps(stmt, [rowExtractor = row_value_extractor(), &res](sqlite3_stmt* stmt) { - // note: we always pass in the first index, even though a row extractor - // for a tuple ignores it and does its custom iteration of the result row - res.push_back(rowExtractor.extract(stmt, 0)); - }); - res.shrink_to_fit(); - return res; - } - - template = true> - std::vector _execute_select(const S& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - - iterate_ast(statement.expression, conditional_binder{stmt}); - - std::vector res; - perform_steps(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { - O obj; - object_from_column_builder builder{obj, stmt}; - table.for_each_column(builder); - res.push_back(std::move(obj)); - }); + perform_steps( + stmt, + [rowExtractor = make_row_extractor(this->db_objects), &res](sqlite3_stmt* stmt) { + res.push_back(rowExtractor.extract(stmt, 0)); + }); res.shrink_to_fit(); return res; } @@ -1539,15 +1524,15 @@ namespace sqlite_orm { using ExprDBOs = decltype(db_objects_for_expression(this->db_objects, std::declval, CTEs...>>())); - using R = column_result_of_t; - return _execute_select(statement); + using ColResult = column_result_of_t; + return _execute_select(statement); } #endif template auto execute(const prepared_statement_t>& statement) { - using R = column_result_of_t; - return _execute_select(statement); + using ColResult = column_result_of_t; + return _execute_select(statement); } template> diff --git a/dev/table_reference.h b/dev/table_reference.h new file mode 100644 index 000000000..8b20c9162 --- /dev/null +++ b/dev/table_reference.h @@ -0,0 +1,49 @@ +#pragma once + +#include // std::remove_const, std::type_identity +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif + +#include "functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm { + namespace internal { + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. + */ + template + struct table_reference : polyfill::type_identity {}; + + template + struct decay_table_ref : std::remove_const {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + + template + using decay_table_ref_t = typename decay_table_ref::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_decay_table_ref_t = typename decay_table_ref::type; +#endif + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_table_reference_v = + polyfill::is_specialization_of_v, table_reference>; + + template + struct is_table_reference : polyfill::bool_constant> {}; + } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; +#endif +} diff --git a/examples/select.cpp b/examples/select.cpp index eaf6cbf3e..98d2cd003 100644 --- a/examples/select.cpp +++ b/examples/select.cpp @@ -66,7 +66,7 @@ void all_employees() { // now let's select id, name and salary.. auto idsNamesSalarys = storage.select(columns(&Employee::id, &Employee::name, &Employee::salary)); - for(auto& row: idsNamesSalarys) { // row's type is tuple> + for(auto& row: idsNamesSalarys) { // row's type is `tuple>` cout << "id = " << get<0>(row) << ", name = " << get<1>(row) << ", salary = "; if(get<2>(row)) { cout << *get<2>(row); @@ -150,7 +150,7 @@ void all_artists() { // SELECT artists.*, albums.* FROM artists JOIN albums ON albums.artist_id = artist.id cout << "artists.*, albums.*\n"; - // row's type is std::tuple + // row's type is `std::tuple` for(auto& row: storage.select(columns(asterisk(), asterisk()), join(on(c(&Album::artist_id) == &Artist::id)))) { cout << get<0>(row) << '\t' << get<1>(row) << '\t' << get<2>(row) << '\t' << get<3>(row) << '\n'; @@ -158,11 +158,58 @@ void all_artists() { cout << endl; } +void named_adhoc_structs() { + struct Artist { + int id; + std::string name; + }; + + struct Album { + int id; + int artist_id; + std::string name; + }; + + struct Z { + decltype(Album::name) album_name; + decltype(Artist::name) artist_name; + }; + // define SQL expression for ad-hoc construction of Z + constexpr auto z_struct = struct_(&Album::name, &Artist::name); + + auto storage = make_storage("", + make_table("artists", + make_column("id", &Artist::id, primary_key().autoincrement()), + make_column("name", &Artist::name)), + make_table("albums", + make_column("id", &Album::id, primary_key().autoincrement()), + make_column("artist_id", &Album::artist_id), + make_column("name", &Album::name), + foreign_key(&Album::artist_id).references(&Artist::id))); + storage.sync_schema(); + storage.transaction([&storage] { + auto artistPk = storage.insert(Artist{-1, "Artist"}); + storage.insert(Album{-1, artistPk, "Album 1"}); + storage.insert(Album{-1, artistPk, "Album 2"}); + return true; + }); + + // SELECT albums.name, artists.name FROM albums JOIN artists ON artist.id = albums.artist_id + + cout << "albums.name, artists.name\n"; + // row's type is Z + for(auto& row: storage.select(z_struct, join(on(c(&Album::artist_id) == &Artist::id)))) { + cout << row.album_name << '\t' << row.artist_name << '\n'; + } + cout << endl; +} + int main() { try { all_employees(); all_artists(); + named_adhoc_structs(); } catch(const std::system_error& e) { cout << "[" << e.code() << "] " << e.what(); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 194d465ba..90a8e37be 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -3271,9 +3271,59 @@ namespace sqlite_orm { // #include "tags.h" +// #include "table_reference.h" + +#include // std::remove_const, std::type_identity +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif + +// #include "functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm { + namespace internal { + /* + * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. + */ + template + struct table_reference : polyfill::type_identity {}; + + template + struct decay_table_ref : std::remove_const {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + template + struct decay_table_ref> : polyfill::type_identity {}; + + template + using decay_table_ref_t = typename decay_table_ref::type; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using auto_decay_table_ref_t = typename decay_table_ref::type; +#endif + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_table_reference_v = + polyfill::is_specialization_of_v, table_reference>; + + template + struct is_table_reference : polyfill::bool_constant> {}; + } + +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + /** @short Specifies that a type is a reference of a concrete table, especially of a derived class. + * + * A concrete table reference has the following traits: + * - specialization of `table_reference`, whose `type` typename references a mapped object. + */ + template + concept orm_table_reference = polyfill::is_specialization_of_v, internal::table_reference>; +#endif +} + // #include "alias_traits.h" -#include // std::remove_const, std::is_base_of, std::is_same, std::type_identity +#include // std::is_base_of, std::is_same #ifdef SQLITE_ORM_WITH_CPP20_ALIASES #include #endif @@ -3284,6 +3334,8 @@ namespace sqlite_orm { // #include "type_traits.h" +// #include "table_reference.h" + namespace sqlite_orm { /** @short Base class for a custom table alias, column alias or expression alias. @@ -3326,24 +3378,6 @@ namespace sqlite_orm { template struct is_table_alias : polyfill::bool_constant> {}; -#ifdef SQLITE_ORM_WITH_CPP20_ALIASES - /* - * Identity wrapper around a mapped object, facilitating uniform column pointer expressions. - */ - template - struct table_reference : std::type_identity {}; - - template - struct decay_table_reference : std::remove_const {}; - template - struct decay_table_reference> : std::type_identity {}; - template - struct decay_table_reference> : std::type_identity {}; - - template - using decay_table_reference_t = typename decay_table_reference::type; -#endif - /** @short Moniker of a CTE, see `orm_cte_moniker`. */ template @@ -3390,14 +3424,6 @@ namespace sqlite_orm { template concept orm_table_alias = (orm_recordset_alias && !std::same_as>); - /** @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 Moniker of a CTE. * * A CTE moniker has the following traits: @@ -3530,6 +3556,8 @@ namespace sqlite_orm { // #include "type_traits.h" +// #include "table_reference.h" + // #include "alias_traits.h" // #include "tags.h" @@ -4519,7 +4547,7 @@ namespace sqlite_orm { */ template auto from() { - return from...>(); + return from...>(); } #endif @@ -4673,7 +4701,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto left_join(On on) { - return left_join, On>(std::move(on)); + return left_join, On>(std::move(on)); } #endif @@ -4685,7 +4713,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto join(On on) { - return join, On>(std::move(on)); + return join, On>(std::move(on)); } #endif @@ -4697,7 +4725,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto left_outer_join(On on) { - return left_outer_join, On>(std::move(on)); + return left_outer_join, On>(std::move(on)); } #endif @@ -4709,7 +4737,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto inner_join(On on) { - return inner_join, On>(std::move(on)); + return inner_join, On>(std::move(on)); } #endif @@ -5517,6 +5545,8 @@ namespace sqlite_orm { // #include "tags.h" +// #include "table_reference.h" + // #include "ast/into.h" // #include "../functional/cxx_type_traits_polyfill.h" @@ -7377,7 +7407,7 @@ namespace sqlite_orm { */ template auto count() { - return count>(); + return count>(); } #endif @@ -8142,6 +8172,31 @@ namespace sqlite_orm { template using is_columns = polyfill::bool_constant>; + /* + * Captures the type of an aggregate/structure/object and column expressions, such that + * `T` can be constructed in-place as part of a result row. + * `T` must be constructible using direct-list-initialization. + */ + template + struct struct_t { + using columns_type = std::tuple; + + columns_type columns; + bool distinct = false; + + static constexpr int count = std::tuple_size::value; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + struct_t(columns_type columns) : columns{std::move(columns)} {} +#endif + }; + + template + SQLITE_ORM_INLINE_VAR constexpr bool is_struct_v = polyfill::is_specialization_of::value; + + template + using is_struct = polyfill::bool_constant>; + /** * Subselect object type. */ @@ -8363,6 +8418,11 @@ namespace sqlite_orm { return cols.distinct; } + template + bool get_distinct(const struct_t& cols) { + return cols.distinct; + } + template struct asterisk_t { using type = T; @@ -8486,8 +8546,20 @@ namespace sqlite_orm { return cols; } + /* + * Combine multiple columns in a tuple. + */ template - internal::columns_t columns(Args... args) { + constexpr internal::columns_t columns(Args... args) { + return {std::make_tuple(std::forward(args)...)}; + } + + /* + * Construct an unmapped structure ad-hoc from multiple columns. + * `T` must be constructible from the column results using direct-list-initialization. + */ + template + constexpr internal::struct_t struct_(Args... args) { return {std::make_tuple(std::forward(args)...)}; } @@ -8786,7 +8858,7 @@ namespace sqlite_orm { */ template auto asterisk(bool definedOrder = false) { - return asterisk>(definedOrder); + return asterisk>(definedOrder); } #endif @@ -8809,7 +8881,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto object(bool definedOrder = false) { - return object>(definedOrder); + return object>(definedOrder); } #endif } @@ -10009,6 +10081,127 @@ namespace sqlite_orm { // #include "functional/cxx_universal.h" +// #include "functional/cxx_functional_polyfill.h" + +// #include "functional/static_magic.h" + +#ifndef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED +#include // std::false_type, std::true_type, std::integral_constant +#endif +#include // std::forward + +namespace sqlite_orm { + + // got from here + // https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co + namespace internal { + + template + decltype(auto) empty_callable() { + static auto res = [](auto&&...) -> R { + return R(); + }; + return (res); + } + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + template + decltype(auto) static_if([[maybe_unused]] T&& trueFn, [[maybe_unused]] F&& falseFn) { + if constexpr(B) { + return std::forward(trueFn); + } else { + return std::forward(falseFn); + } + } + + template + decltype(auto) static_if([[maybe_unused]] T&& trueFn) { + if constexpr(B) { + return std::forward(trueFn); + } else { + return empty_callable(); + } + } + + template + void call_if_constexpr([[maybe_unused]] L&& lambda, [[maybe_unused]] Args&&... args) { + if constexpr(B) { + lambda(std::forward(args)...); + } + } +#else + template + decltype(auto) static_if(std::true_type, T&& trueFn, const F&) { + return std::forward(trueFn); + } + + template + decltype(auto) static_if(std::false_type, const T&, F&& falseFn) { + return std::forward(falseFn); + } + + template + decltype(auto) static_if(T&& trueFn, F&& falseFn) { + return static_if(std::integral_constant{}, std::forward(trueFn), std::forward(falseFn)); + } + + template + decltype(auto) static_if(T&& trueFn) { + return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable()); + } + + template + void call_if_constexpr(L&& lambda, Args&&... args) { + static_if(std::forward(lambda))(std::forward(args)...); + } +#endif + } + +} + +// #include "column_result_proxy.h" + +// #include "type_traits.h" + +// #include "table_reference.h" + +namespace sqlite_orm { + namespace internal { + + /* + * Holder for the type of an unmapped aggregate/structure/object to be constructed ad-hoc from column results. + * `T` must be constructible using direct-list-initialization. + */ + template + struct structure { + using type = T; + }; + } +} + +namespace sqlite_orm { + namespace internal { + + template + struct column_result_proxy : std::remove_const {}; + + /* + * Unwrap `table_reference` + */ + template + struct column_result_proxy> : decay_table_ref

{}; + + /* + * Unwrap `structure` + */ + template + struct column_result_proxy> : P {}; + + template + using column_result_proxy_t = typename column_result_proxy::type; + } +} + // #include "arithmetic_tag.h" // #include "pointer_value.h" @@ -10429,7 +10622,7 @@ namespace sqlite_orm { #endif // SQLITE_ORM_OPTIONAL_SUPPORTED template<> - struct row_extractor { + struct row_extractor { nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -10446,7 +10639,7 @@ namespace sqlite_orm { * Specialization for std::vector. */ template<> - struct row_extractor> { + struct row_extractor, void> { std::vector extract(const char* columnText) const { return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } @@ -10464,32 +10657,6 @@ namespace sqlite_orm { } }; - /** - * Specialization for a tuple. - */ - template - struct row_extractor> { - - std::tuple extract(char** argv) const { - return this->extract(argv, std::make_index_sequence{}); - } - - std::tuple extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - return this->extract(stmt, std::make_index_sequence{}); - } - - protected: - template - std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return {row_extractor{}.extract(stmt, Idx)...}; - } - - template - std::tuple extract(char** argv, std::index_sequence) const { - return {row_extractor{}.extract(argv[Idx])...}; - } - }; - /** * Specialization for journal_mode. */ @@ -10511,7 +10678,108 @@ namespace sqlite_orm { auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex); return this->extract(cStr); } + + journal_mode extract(sqlite3_value* value) const = delete; }; + + namespace internal { + + /* + * Helper to extract a structure from a rowset. + */ + template + struct struct_extractor; + +#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED + /* + * Returns a value-based row extractor for an unmapped type, + * returns a structure extractor for a table reference, tuple or named struct. + */ + template + auto make_row_extractor([[maybe_unused]] const DBOs& dbObjects) { + if constexpr(polyfill::is_specialization_of_v || + polyfill::is_specialization_of_v || is_table_reference_v) { + return struct_extractor{dbObjects}; + } else { + return row_value_extractor(); + } + } +#else + /* + * Overload for an unmapped type returns a common row extractor. + */ + template< + class R, + class DBOs, + std::enable_if_t, + polyfill::is_specialization_of, + is_table_reference>>::value, + bool> = true> + auto make_row_extractor(const DBOs& /*dbObjects*/) { + return row_value_extractor(); + } + + /* + * Overload for a table reference, tuple or aggregate of column results returns a structure extractor. + */ + template, + polyfill::is_specialization_of, + is_table_reference>::value, + bool> = true> + struct_extractor make_row_extractor(const DBOs& dbObjects) { + return {dbObjects}; + } +#endif + + /** + * Specialization for a tuple of top-level column results. + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + std::tuple extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + std::tuple...> extract(sqlite3_stmt* stmt, + int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return {make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + // unused to date + std::tuple...> extract(sqlite3_stmt* stmt, int& columnIndex) const = delete; + + std::tuple extract(sqlite3_value* value) const = delete; + }; + + /** + * Specialization for an unmapped structure to be constructed ad-hoc from column results. + * + * This plays together with `column_result_of_t`, which returns `struct_t` as `structure` + */ + template + struct struct_extractor>, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = -1; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + --columnIndex; + return O{make_row_extractor(this->db_objects).extract(stmt, ++columnIndex)...}; + } + + O extract(sqlite3_value* value) const = delete; + }; + } } #pragma once @@ -10933,80 +11201,6 @@ namespace sqlite_orm { // #include "../functional/static_magic.h" -#ifndef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED -#include // std::false_type, std::true_type, std::integral_constant -#endif -#include // std::forward - -namespace sqlite_orm { - - // got from here - // https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co - namespace internal { - - template - decltype(auto) empty_callable() { - static auto res = [](auto&&...) -> R { - return R(); - }; - return (res); - } - -#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED - template - decltype(auto) static_if([[maybe_unused]] T&& trueFn, [[maybe_unused]] F&& falseFn) { - if constexpr(B) { - return std::forward(trueFn); - } else { - return std::forward(falseFn); - } - } - - template - decltype(auto) static_if([[maybe_unused]] T&& trueFn) { - if constexpr(B) { - return std::forward(trueFn); - } else { - return empty_callable(); - } - } - - template - void call_if_constexpr([[maybe_unused]] L&& lambda, [[maybe_unused]] Args&&... args) { - if constexpr(B) { - lambda(std::forward(args)...); - } - } -#else - template - decltype(auto) static_if(std::true_type, T&& trueFn, const F&) { - return std::forward(trueFn); - } - - template - decltype(auto) static_if(std::false_type, const T&, F&& falseFn) { - return std::forward(falseFn); - } - - template - decltype(auto) static_if(T&& trueFn, F&& falseFn) { - return static_if(std::integral_constant{}, std::forward(trueFn), std::forward(falseFn)); - } - - template - decltype(auto) static_if(T&& trueFn) { - return static_if(std::integral_constant{}, std::forward(trueFn), empty_callable()); - } - - template - void call_if_constexpr(L&& lambda, Args&&... args) { - static_if(std::forward(lambda))(std::forward(args)...); - } -#endif - } - -} - // #include "../functional/mpl.h" // #include "../functional/index_sequence_util.h" @@ -11449,7 +11643,7 @@ namespace sqlite_orm { */ template auto make_table(std::string name, Cs... args) { - return make_table>(std::move(name), std::forward(args)...); + return make_table>(std::move(name), std::forward(args)...); } #endif @@ -11935,6 +12129,8 @@ namespace sqlite_orm { // #include "type_traits.h" +// #include "table_reference.h" + // #include "alias_traits.h" namespace sqlite_orm { @@ -11969,6 +12165,8 @@ namespace sqlite_orm { // #include "rowid.h" +// #include "column_result_proxy.h" + // #include "alias.h" // #include "cte_types.h" @@ -12902,6 +13100,11 @@ namespace sqlite_orm { struct column_result_t, void> : conc_tuple>>...> {}; + template + struct column_result_t, void> { + using type = structure>>...>>; + }; + template struct column_result_t> : column_result_t {}; @@ -12953,7 +13156,7 @@ namespace sqlite_orm { template struct column_result_t, void> { - using type = T; + using type = table_reference; }; template @@ -13027,21 +13230,31 @@ namespace sqlite_orm { #include #include // std::is_member_object_pointer +#include // std::move // #include "functional/static_magic.h" +// #include "member_traits/member_traits.h" + +// #include "table_reference.h" + // #include "row_extractor.h" +// #include "schema/column.h" + +// #include "storage_lookup.h" + namespace sqlite_orm { namespace internal { struct object_from_column_builder_base { sqlite3_stmt* stmt = nullptr; - int index = 0; + int columnIndex = -1; #ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} + object_from_column_builder_base(sqlite3_stmt* stmt, int columnIndex = -1) : + stmt{stmt}, columnIndex{columnIndex} {} #endif }; @@ -13054,13 +13267,13 @@ namespace sqlite_orm { object_type& object; - object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : - object_from_column_builder_base{stmt_}, object(object_) {} + object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_, int nextColumnIndex = 0) : + object_from_column_builder_base{stmt_, nextColumnIndex - 1}, object(object_) {} template void operator()(const column_field& column) { const auto rowExtractor = row_value_extractor>(); - auto value = rowExtractor.extract(this->stmt, this->index++); + auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); static_if::value>( [&value, &object = this->object](const auto& column) { object.*column.member_pointer = std::move(value); @@ -13070,6 +13283,35 @@ namespace sqlite_orm { })(column); } }; + + /** + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_referenece` + */ + template + struct struct_extractor, DBOs> { + const DBOs& db_objects; + + O extract(const char* columnText) const = delete; + + // note: expects to be called only from the top level, and therefore discards the index + O extract(sqlite3_stmt* stmt, int&& /*nextColumnIndex*/ = 0) const { + int columnIndex = 0; + return this->extract(stmt, columnIndex); + } + + O extract(sqlite3_stmt* stmt, int& columnIndex) const { + O obj; + object_from_column_builder builder{obj, stmt, columnIndex}; + auto& table = pick_table(this->db_objects); + table.for_each_column(builder); + columnIndex = builder.columnIndex; + return obj; + } + + O extract(sqlite3_value* value) const = delete; + }; } } @@ -13322,6 +13564,8 @@ namespace sqlite_orm { } +// #include "table_reference.h" + // #include "mapped_type_proxy.h" // #include "ast/upsert_clause.h" @@ -14286,7 +14530,7 @@ namespace sqlite_orm { class R = std::vector>, class... Args> auto get_all(Args&&... conditions) { - return get_all, R>(std::forward(conditions)...); + return get_all, R>(std::forward(conditions)...); } #endif @@ -14573,9 +14817,9 @@ namespace sqlite_orm { } }; - template - struct ast_iterator, void> { - using node_type = columns_t; + template + struct ast_iterator, is_struct>::value>> { + using node_type = C; template void operator()(const node_type& cols, L& lambda) const { @@ -17795,6 +18039,20 @@ namespace sqlite_orm { return this->collectedExpressions; } + template + std::vector& operator()(const struct_t& cols, const Ctx& context) { + this->collectedExpressions.reserve(this->collectedExpressions.size() + cols.count); + iterate_tuple(cols.columns, [this, &context](auto& colExpr) { + (*this)(colExpr, context); + }); + // note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order + if(tuple_has_template::columns_type, asterisk_t>::value && + this->collectedExpressions.capacity() > this->collectedExpressions.size()) { + this->collectedExpressions.shrink_to_fit(); + } + return this->collectedExpressions; + } + std::vector collectedExpressions; }; @@ -17931,6 +18189,12 @@ namespace sqlite_orm { static_assert(polyfill::always_false_v, "Selecting an object in a subselect is not allowed."); }; + // No CTE for object expressions. + template + struct cte_column_names_collector> { + static_assert(polyfill::always_false_v, "Repacking columns in a subselect is not allowed."); + }; + template struct cte_column_names_collector> { using expression_type = Columns; @@ -19457,9 +19721,9 @@ namespace sqlite_orm { } }; - template - struct statement_serializer, void> { - using statement_type = columns_t; + template + struct statement_serializer, is_struct>::value>> { + using statement_type = C; template std::string operator()(const statement_type& statement, const Ctx& context) const { @@ -20309,6 +20573,8 @@ namespace sqlite_orm { // #include "object_from_column_builder.h" +// #include "row_extractor.h" + // #include "schema/table.h" // #include "schema/column.h" @@ -20438,6 +20704,15 @@ namespace sqlite_orm { using type = std::tuple>...>; }; + /** + * Resolve multiple columns. + * struct_t -> tuple + */ + template + struct column_expression_type, void> { + using type = std::tuple>...>; + }; + /** * Resolve column(s) of subselect. * select_t -> ColExpr, tuple @@ -21109,7 +21384,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template auto get(Ids... ids) { - return this->get>(std::forward(ids)...); + return this->get>(std::forward(ids)...); } #endif @@ -21174,7 +21449,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES template int count(Args&&... args) { - return this->count>(std::forward(args)...); + return this->count>(std::forward(args)...); } #endif @@ -21274,10 +21549,10 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr max(F field, Args&&... args) { + std::unique_ptr max(F field, Args&&... args) { this->assert_mapped_type>(); auto rows = this->select(sqlite_orm::max(std::move(field)), std::forward(args)...); if(!rows.empty()) { @@ -21294,10 +21569,10 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr min(F field, Args&&... args) { + std::unique_ptr min(F field, Args&&... args) { this->assert_mapped_type>(); auto rows = this->select(sqlite_orm::min(std::move(field)), std::forward(args)...); if(!rows.empty()) { @@ -21314,16 +21589,16 @@ namespace sqlite_orm { */ template, + class R = column_result_of_t, std::enable_if_t, is_column_pointer>::value, bool> = true> - std::unique_ptr sum(F field, Args&&... args) { + std::unique_ptr sum(F field, Args&&... args) { this->assert_mapped_type>(); std::vector> rows = this->select(sqlite_orm::sum(std::move(field)), std::forward(args)...); if(!rows.empty()) { if(rows.front()) { - return std::make_unique(std::move(*rows.front())); + return std::make_unique(std::move(*rows.front())); } else { return {}; } @@ -21357,8 +21632,8 @@ namespace sqlite_orm { * For a single column use `auto rows = storage.select(&User::id, where(...)); * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); */ - template> - std::vector select(T m, Args... args) { + template + auto select(T m, Args... args) { static_assert(!is_compound_operator_v || sizeof...(Args) == 0, "Cannot use args with a compound operator"); auto statement = this->prepare(sqlite_orm::select(std::move(m), std::forward(args)...)); @@ -22211,35 +22486,19 @@ namespace sqlite_orm { perform_step(stmt); } - template = true> - std::vector _execute_select(const S& statement) { + template + auto _execute_select(const S& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); + using R = decltype(make_row_extractor(this->db_objects).extract(nullptr, 0)); std::vector res; - perform_steps(stmt, [rowExtractor = row_value_extractor(), &res](sqlite3_stmt* stmt) { - // note: we always pass in the first index, even though a row extractor - // for a tuple ignores it and does its custom iteration of the result row - res.push_back(rowExtractor.extract(stmt, 0)); - }); - res.shrink_to_fit(); - return res; - } - - template = true> - std::vector _execute_select(const S& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - - iterate_ast(statement.expression, conditional_binder{stmt}); - - std::vector res; - perform_steps(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { - O obj; - object_from_column_builder builder{obj, stmt}; - table.for_each_column(builder); - res.push_back(std::move(obj)); - }); + perform_steps( + stmt, + [rowExtractor = make_row_extractor(this->db_objects), &res](sqlite3_stmt* stmt) { + res.push_back(rowExtractor.extract(stmt, 0)); + }); res.shrink_to_fit(); return res; } @@ -22250,15 +22509,15 @@ namespace sqlite_orm { using ExprDBOs = decltype(db_objects_for_expression(this->db_objects, std::declval, CTEs...>>())); - using R = column_result_of_t; - return _execute_select(statement); + using ColResult = column_result_of_t; + return _execute_select(statement); } #endif template auto execute(const prepared_statement_t>& statement) { - using R = column_result_of_t; - return _execute_select(statement); + using ColResult = column_result_of_t; + return _execute_select(statement); } template> @@ -22490,6 +22749,9 @@ namespace sqlite_orm { template struct node_tuple, void> : node_tuple_for {}; + template + struct node_tuple, void> : node_tuple_for {}; + template struct node_tuple, void> : node_tuple_for {}; diff --git a/tests/prepared_statement_tests/select.cpp b/tests/prepared_statement_tests/select.cpp index 0a112e6e3..8318cd2d0 100644 --- a/tests/prepared_statement_tests/select.cpp +++ b/tests/prepared_statement_tests/select.cpp @@ -303,6 +303,90 @@ TEST_CASE("Prepared select") { } } } + SECTION("object") { + auto statement = storage.prepare(select(object(true))); + auto str = storage.dump(statement); + testSerializing(statement); + SECTION("nothing") { + //.. + } + SECTION("execute") { + auto rows = storage.execute(statement); + std::vector expected; + expected.push_back(User{1, "Team BS"}); + expected.push_back(User{2, "Shy'm"}); + expected.push_back(User{3, "Maître Gims"}); + REQUIRE_THAT(rows, UnorderedEquals(expected)); + } + } + SECTION("multi object") { + auto statement = storage.prepare(select(columns(object(true), object(true)))); + auto str = storage.dump(statement); + testSerializing(statement); + SECTION("nothing") { + //.. + } + SECTION("execute") { + auto rows = storage.execute(statement); + std::vector> expected; + expected.push_back({User{1, "Team BS"}, User{1, "Team BS"}}); + expected.push_back({User{2, "Shy'm"}, User{2, "Shy'm"}}); + expected.push_back({User{3, "Maître Gims"}, User{3, "Maître Gims"}}); + REQUIRE_THAT(rows, UnorderedEquals(expected)); + } + } + SECTION("multi object 2") { + auto statement = storage.prepare(select(columns(object(true), asterisk(true), object(true)))); + auto str = storage.dump(statement); + testSerializing(statement); + SECTION("nothing") { + //.. + } + SECTION("execute") { + auto rows = storage.execute(statement); + std::vector> expected; + expected.push_back({User{1, "Team BS"}, 1, "Team BS", User{1, "Team BS"}}); + expected.push_back({User{2, "Shy'm"}, 2, "Shy'm", User{2, "Shy'm"}}); + expected.push_back({User{3, "Maître Gims"}, 3, "Maître Gims", User{3, "Maître Gims"}}); + REQUIRE_THAT(rows, UnorderedEquals(expected)); + } + } + SECTION("struct") { + using Z = User; // for the unit test it is fine to just reuse `User` as an unmapped struct + constexpr auto z_struct = struct_(&User::id, &User::name); + auto statement = storage.prepare(select(z_struct)); + auto str = storage.dump(statement); + testSerializing(statement); + SECTION("nothing") { + //.. + } + SECTION("execute") { + auto rows = storage.execute(statement); + std::vector expected; + expected.push_back(Z{1, "Team BS"}); + expected.push_back(Z{2, "Shy'm"}); + expected.push_back(Z{3, "Maître Gims"}); + REQUIRE_THAT(rows, UnorderedEquals(expected)); + } + } + SECTION("multi struct") { + using Z = User; // for the unit test it is fine to just reuse `User` as an unmapped struct + constexpr auto z_struct = struct_(&User::id, &User::name); + auto statement = storage.prepare(select(columns(z_struct, z_struct))); + auto str = storage.dump(statement); + testSerializing(statement); + SECTION("nothing") { + //.. + } + SECTION("execute") { + auto rows = storage.execute(statement); + std::vector> expected; + expected.push_back({Z{1, "Team BS"}, Z{1, "Team BS"}}); + expected.push_back({Z{2, "Shy'm"}, Z{2, "Shy'm"}}); + expected.push_back({Z{3, "Maître Gims"}, Z{3, "Maître Gims"}}); + REQUIRE_THAT(rows, UnorderedEquals(expected)); + } + } } TEST_CASE("dumping") { diff --git a/tests/statement_serializer_tests/statements/select.cpp b/tests/statement_serializer_tests/statements/select.cpp index 0dd001f38..79ea7ca23 100644 --- a/tests/statement_serializer_tests/statements/select.cpp +++ b/tests/statement_serializer_tests/statements/select.cpp @@ -171,6 +171,24 @@ TEST_CASE("statement_serializer select_t") { stringValue = serialize(expression, context); expected = R"(SELECT "users"."id", "users"."name" FROM "users")"; } + SECTION("multi object, defined select order") { + auto expression = select(columns(object(true), object(true))); + expression.highest_level = true; + stringValue = serialize(expression, context); + expected = R"(SELECT "users"."id", "users"."name", "users"."id", "users"."name" FROM "users")"; + } + SECTION("struct") { + auto expression = select(struct_(asterisk())); + expression.highest_level = true; + stringValue = serialize(expression, context); + expected = R"(SELECT "users".* FROM "users")"; + } + SECTION("multi struct") { + auto expression = select(columns(struct_(asterisk()), struct_(asterisk()))); + expression.highest_level = true; + stringValue = serialize(expression, context); + expected = R"(SELECT "users".*, "users".* FROM "users")"; + } // issue #1106 SECTION("multi") { auto expression = columns(asterisk(), asterisk(true)); diff --git a/tests/static_tests/column_pointer.cpp b/tests/static_tests/column_pointer.cpp index ceac4a6c6..18fc5c017 100644 --- a/tests/static_tests/column_pointer.cpp +++ b/tests/static_tests/column_pointer.cpp @@ -72,7 +72,7 @@ TEST_CASE("column pointers") { STATIC_REQUIRE_FALSE(orm_table_alias); STATIC_REQUIRE_FALSE(orm_recordset_alias); runTest>(derived_user); - runTest(internal::decay_table_reference_t{}); + runTest(internal::auto_decay_table_ref_t{}); #endif } SECTION("column pointer expressions") { diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 6b261b75c..79dba803e 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -2,7 +2,10 @@ #include #include // std::is_same +using std::tuple; using namespace sqlite_orm; +using internal::structure; +using internal::table_reference; template void do_assert() { @@ -113,13 +116,18 @@ TEST_CASE("column_result_of_t") { runTest(rowid()); runTest(oid()); runTest(_rowid_()); - runTest>(columns(&User::id, &User::name)); - runTest>(asterisk()); - runTest>(asterisk>()); - runTest>(columns(asterisk(), asterisk())); + runTest>(columns(&User::id, &User::name)); + runTest>(asterisk()); + runTest>(asterisk>()); + runTest>(columns(asterisk(), asterisk())); runTest(column(&User::id)); runTest(alias_column>(&User::id)); - runTest(object()); + runTest>(object()); + runTest, table_reference>>(columns(object(), object())); + runTest>>(struct_(&User::id, &User::name)); + runTest>>(struct_(asterisk())); + runTest>, structure>>>( + columns(struct_(asterisk()), struct_(asterisk()))); runTest(union_all(select(1), select(2))); runTest(union_all(select(1ll), select(2))); #ifdef SQLITE_ORM_WITH_CTE @@ -134,8 +142,8 @@ TEST_CASE("column_result_of_t") { runTest(column(get>())); runTest(alias_column>(&User::id)); runTest(alias_column>(1_colalias)); - runTest>(asterisk()); - runTest>(asterisk>()); + runTest>(asterisk()); + runTest>(asterisk>()); runTest(count()); #ifdef SQLITE_ORM_WITH_CPP20_ALIASES constexpr auto cte1 = 1_ctealias;