diff --git a/appveyor.yml b/appveyor.yml index 82a7dc203..2a8acd69e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,7 +107,7 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2023.04.15 + git fetch --tags && git checkout 2023.06.20 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 2023.04.15 + git fetch --tags && git checkout 2023.06.20 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 2023.04.15 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2023.06.20 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] catch2 --overlay-triplets=vcpkg/triplets diff --git a/dev/arg_values.h b/dev/arg_values.h index 9060460b0..515a1b12f 100644 --- a/dev/arg_values.h +++ b/dev/arg_values.h @@ -6,6 +6,8 @@ namespace sqlite_orm { + /** @short Wrapper around a dynamically typed value object. + */ struct arg_value { arg_value() : arg_value(nullptr) {} @@ -14,7 +16,8 @@ namespace sqlite_orm { template T get() const { - return row_extractor().extract(this->value); + const auto rowExtractor = internal::boxed_value_extractor(); + return rowExtractor.extract(this->value); } bool is_null() const { diff --git a/dev/mapped_row_extractor.h b/dev/mapped_row_extractor.h deleted file mode 100644 index faf47ecdd..000000000 --- a/dev/mapped_row_extractor.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -#include "object_from_column_builder.h" - -namespace sqlite_orm { - - namespace internal { - - /** - * This is a private row extractor class. It is used for extracting rows as objects instead of tuple. - * Main difference from regular `row_extractor` is that this class takes table info which is required - * for constructing objects by member pointers. To construct please use `make_row_extractor()`. - * Type arguments: - * V is value type just like regular `row_extractor` has - * T is table info class `table_t` - */ - template - struct mapped_row_extractor { - using table_type = Table; - - V extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - V res; - object_from_column_builder builder{res, stmt}; - this->tableInfo.for_each_column(builder); - return res; - } - - const table_type& tableInfo; - }; - - } - -} diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index a613262c6..a3e82f4f3 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -20,7 +20,7 @@ namespace sqlite_orm { }; /** - * This is a cute lambda replacement which is used in several places. + * Function object for building an object from a result row. */ template struct object_from_column_builder : object_from_column_builder_base { @@ -33,7 +33,8 @@ namespace sqlite_orm { template void operator()(const column_field& column) { - auto value = row_extractor>().extract(this->stmt, this->index++); + const auto rowExtractor = row_value_extractor>(); + auto value = rowExtractor.extract(this->stmt, this->index++); static_if::value>( [&value, &object = this->object](const auto& column) { object.*column.member_pointer = std::move(value); diff --git a/dev/pragma.h b/dev/pragma.h index ba1b03e88..37de02201 100644 --- a/dev/pragma.h +++ b/dev/pragma.h @@ -28,8 +28,9 @@ namespace sqlite_orm { inline int getPragmaCallback>(void* data, int argc, char** argv, char**) { auto& res = *(std::vector*)data; res.reserve(argc); - for(decltype(argc) i = 0; i < argc; ++i) { - auto rowString = row_extractor().extract(argv[i]); + const auto rowExtractor = column_text_extractor(); + for(int i = 0; i < argc; ++i) { + auto rowString = rowExtractor.extract(argv[i]); res.push_back(std::move(rowString)); } return 0; diff --git a/dev/row_extractor.h b/dev/row_extractor.h index ed60e596a..785c9834e 100644 --- a/dev/row_extractor.h +++ b/dev/row_extractor.h @@ -14,6 +14,9 @@ #include // std::copy #include // std::back_inserter #include // std::tuple, std::tuple_size, std::tuple_element +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif #include "functional/cxx_universal.h" #include "arithmetic_tag.h" @@ -25,27 +28,96 @@ namespace sqlite_orm { /** - * Helper class used to cast values from argv to V class - * which depends from column type. - * + * Helper for casting values originating from SQL to C++ typed values, usually from rows of a result set. + * + * sqlite_orm provides specializations for known C++ types, users may define their custom specialization + * of this helper. + * + * @note (internal): Since row extractors are used in certain contexts with only one purpose at a time + * (e.g., converting a row result set but not function values or column text), + * there are factory functions that perform conceptual checking that should be used + * instead of directly creating row extractors. + * + * */ template struct row_extractor { - // used in sqlite3_exec (select) - V extract(const char* row_value) const = delete; - - // used in sqlite_column (iteration, get_all) + /* + * Called during one-step query execution (one result row) for each column of a result row. + */ + V extract(const char* columnText) const = delete; + + /* + * Called during multi-step query execution (result set) for each column of a result row. + */ V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; - // used in user defined functions + /* + * Called before invocation of user-defined scalar or aggregate functions, + * in order to unbox dynamically typed SQL function values into a tuple of C++ function arguments. + */ V extract(sqlite3_value* value) const = delete; }; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_column_text_extractable = requires(const row_extractor& extractor, const char* columnText) { + { extractor.extract(columnText) } -> std::same_as; + }; + + template + concept orm_row_value_extractable = + requires(const row_extractor& extractor, sqlite3_stmt* stmt, int columnIndex) { + { extractor.extract(stmt, columnIndex) } -> std::same_as; + }; + + template + concept orm_boxed_value_extractable = requires(const row_extractor& extractor, sqlite3_value* value) { + { extractor.extract(value) } -> std::same_as; + }; +#endif + + namespace internal { + /* + * Make a row extractor to be used for casting SQL column text to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + row_extractor column_text_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for converting a value from a SQL result row set to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + row_extractor row_value_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for unboxing a dynamically typed SQL value to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + row_extractor boxed_value_extractor() { + return {}; + } + } + template int extract_single_value(void* data, int argc, char** argv, char**) { auto& res = *(R*)data; if(argc) { - res = row_extractor{}.extract(argv[0]); + const auto rowExtractor = internal::column_text_extractor(); + res = rowExtractor.extract(argv[0]); } return 0; } @@ -60,6 +132,10 @@ namespace sqlite_orm { struct row_extractor, void> { using V = pointer_arg; + V extract(const char* columnText) const = delete; + + V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; + V extract(sqlite3_value* value) const { return {(P*)sqlite3_value_pointer(value, T::value)}; } @@ -76,8 +152,8 @@ namespace sqlite_orm { */ template struct row_extractor::value>> { - V extract(const char* row_value) const { - return this->extract(row_value, tag()); + V extract(const char* columnText) const { + return this->extract(columnText, tag()); } V extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -91,8 +167,8 @@ namespace sqlite_orm { private: using tag = arithmetic_tag_t; - V extract(const char* row_value, const int_or_smaller_tag&) const { - return static_cast(atoi(row_value)); + V extract(const char* columnText, const int_or_smaller_tag&) const { + return static_cast(atoi(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const int_or_smaller_tag&) const { @@ -103,8 +179,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int(value)); } - V extract(const char* row_value, const bigint_tag&) const { - return static_cast(atoll(row_value)); + V extract(const char* columnText, const bigint_tag&) const { + return static_cast(atoll(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const bigint_tag&) const { @@ -115,8 +191,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int64(value)); } - V extract(const char* row_value, const real_tag&) const { - return static_cast(atof(row_value)); + V extract(const char* columnText, const real_tag&) const { + return static_cast(atof(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const real_tag&) const { @@ -131,17 +207,17 @@ namespace sqlite_orm { /** * Specialization for std::string. */ - template<> - struct row_extractor { - std::string extract(const char* row_value) const { - if(row_value) { - return row_value; + template + struct row_extractor::value>> { + T extract(const char* columnText) const { + if(columnText) { + return columnText; } else { return {}; } } - std::string extract(sqlite3_stmt* stmt, int columnIndex) const { + T extract(sqlite3_stmt* stmt, int columnIndex) const { if(auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex)) { return cStr; } else { @@ -149,7 +225,7 @@ namespace sqlite_orm { } } - std::string extract(sqlite3_value* value) const { + T extract(sqlite3_value* value) const { if(auto cStr = (const char*)sqlite3_value_text(value)) { return cStr; } else { @@ -163,10 +239,10 @@ namespace sqlite_orm { */ template<> struct row_extractor { - std::wstring extract(const char* row_value) const { - if(row_value) { + std::wstring extract(const char* columnText) const { + if(columnText) { std::wstring_convert> converter; - return converter.from_bytes(row_value); + return converter.from_bytes(columnText); } else { return {}; } @@ -196,27 +272,42 @@ namespace sqlite_orm { struct row_extractor::value>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return is_std_ptr::make(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(columnText)); } else { return {}; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(stmt, columnIndex)); } else { return {}; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(value)); } else { return {}; } @@ -228,27 +319,42 @@ namespace sqlite_orm { struct row_extractor>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return std::make_optional(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(columnText)); } else { return std::nullopt; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(stmt, columnIndex)); } else { return std::nullopt; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(value)); } else { return std::nullopt; } @@ -258,7 +364,7 @@ namespace sqlite_orm { template<> struct row_extractor { - nullptr_t extract(const char* /*row_value*/) const { + nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -275,8 +381,8 @@ namespace sqlite_orm { */ template<> struct row_extractor> { - std::vector extract(const char* row_value) const { - return {row_value, row_value + (row_value ? ::strlen(row_value) : 0)}; + std::vector extract(const char* columnText) const { + return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } std::vector extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -292,6 +398,9 @@ namespace sqlite_orm { } }; + /** + * Specialization for a tuple. + */ template struct row_extractor> { @@ -306,12 +415,12 @@ namespace sqlite_orm { protected: template std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(stmt, Idx)...}; + return {row_extractor{}.extract(stmt, Idx)...}; } template std::tuple extract(char** argv, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(argv[Idx])...}; + return {row_extractor{}.extract(argv[Idx])...}; } }; @@ -320,9 +429,9 @@ namespace sqlite_orm { */ template<> struct row_extractor { - journal_mode extract(const char* row_value) const { - if(row_value) { - if(auto res = internal::journal_mode_from_string(row_value)) { + journal_mode extract(const char* columnText) const { + if(columnText) { + if(auto res = internal::journal_mode_from_string(columnText)) { return std::move(*res); } else { throw std::system_error{orm_error_code::incorrect_journal_mode_string}; diff --git a/dev/row_extractor_builder.h b/dev/row_extractor_builder.h deleted file mode 100644 index 8ebf796e6..000000000 --- a/dev/row_extractor_builder.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "functional/cxx_universal.h" -#include "row_extractor.h" -#include "mapped_row_extractor.h" - -namespace sqlite_orm { - - namespace internal { - - template - row_extractor make_row_extractor(nullptr_t) { - return {}; - } - - template - mapped_row_extractor make_row_extractor(const Table* table) { - return {*table}; - } - } - -} diff --git a/dev/storage.h b/dev/storage.h index 5cc45d24a..5cc8e196b 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -23,7 +23,6 @@ #include "tuple_helper/tuple_iteration.h" #include "type_traits.h" #include "alias.h" -#include "row_extractor_builder.h" #include "error_code.h" #include "type_printer.h" #include "constraints.h" @@ -1358,18 +1357,41 @@ namespace sqlite_orm { perform_step(stmt); } - template> + template, + satisfies_not = true> std::vector execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); std::vector res; - perform_steps(stmt, - [rowExtractor = make_row_extractor(lookup_table(this->db_objects)), - &res](sqlite3_stmt* stmt) { - res.push_back(rowExtractor.extract(stmt, 0)); - }); + 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, + satisfies = true> + std::vector execute(const prepared_statement_t>& 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)); + }); res.shrink_to_fit(); return res; } diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 0abbffa54..eb0d8ea07 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -26,15 +26,6 @@ namespace sqlite_orm { return res; } - template> - auto lookup_table(const DBOs& dbObjects) { - return static_if>( - [](const auto& dbObjects) { - return &pick_table(dbObjects); - }, - empty_callable())(dbObjects); - } - template> decltype(auto) lookup_table_name(const DBOs& dbObjects) { return static_if>( diff --git a/dev/storage_lookup.h b/dev/storage_lookup.h index 75daa650f..3dbe90e5e 100644 --- a/dev/storage_lookup.h +++ b/dev/storage_lookup.h @@ -134,9 +134,6 @@ namespace sqlite_orm { return std::get(dbObjects); } - template = true> - auto lookup_table(const DBOs& dbObjects); - template = true> decltype(auto) lookup_table_name(const DBOs& dbObjects); diff --git a/dev/values_to_tuple.h b/dev/values_to_tuple.h index a85201d9a..f5a477f6f 100644 --- a/dev/values_to_tuple.h +++ b/dev/values_to_tuple.h @@ -39,7 +39,8 @@ namespace sqlite_orm { #endif template void extract(sqlite3_value* value, T& t) const { - t = row_extractor{}.extract(value); + const auto rowExtractor = boxed_value_extractor(); + t = rowExtractor.extract(value); } }; } diff --git a/dev/xdestroy_handling.h b/dev/xdestroy_handling.h index 75b89c6bb..7746f8402 100644 --- a/dev/xdestroy_handling.h +++ b/dev/xdestroy_handling.h @@ -1,7 +1,7 @@ #pragma once #include // std::integral_constant -#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && SQLITE_ORM_HAS_INCLUDE() +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include #endif @@ -45,7 +45,7 @@ namespace sqlite_orm { }; #endif -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Yield a deleter's function pointer. */ @@ -95,7 +95,7 @@ namespace sqlite_orm { template using yielded_fn_t = typename yield_fp_of::type; -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept is_unusable_for_xdestroy = (!stateless_deleter && (yields_fp && !std::convertible_to, xdestroy_fn_t>)); @@ -175,7 +175,7 @@ namespace sqlite_orm { namespace sqlite_orm { -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Prohibits using a yielded function pointer, which is not of type xdestroy_fn_t. * diff --git a/examples/blob_binding.cpp b/examples/blob_binding.cpp index 3979f1cff..2d7e7fffe 100644 --- a/examples/blob_binding.cpp +++ b/examples/blob_binding.cpp @@ -104,7 +104,7 @@ namespace sqlite_orm { template<> struct row_extractor { - Rect extract(sqlite3_stmt* stmt, int columnIndex) { + Rect extract(sqlite3_stmt* stmt, int columnIndex) const { auto blobPointer = sqlite3_column_blob(stmt, columnIndex); auto charPointer = (const char*)blobPointer; Rect value; diff --git a/examples/chrono_binding.cpp b/examples/chrono_binding.cpp index 5400f48d5..d887b8071 100644 --- a/examples/chrono_binding.cpp +++ b/examples/chrono_binding.cpp @@ -98,31 +98,28 @@ namespace sqlite_orm { * This is a reverse operation: here we have to specify a way to transform string received from * database to our sysdays object. Here we call `sysDaysFromString` and throw `std::runtime_error` if it returns null. * Every `row_extractor` specialization must have `extract(const char*)`, `extract(sqlite3_stmt *stmt, int columnIndex)` - * and `extract(sqlite3_value* value)` - * functions which return a mapped type value. + * and `extract(sqlite3_value* value)` functions which cast to a typed value. */ template<> struct row_extractor { - std::chrono::sys_days extract(const char* row_value) const { - if(row_value) { - auto sd = sysDaysFromString(row_value); - if(sd) { - return sd.value(); - } else { - throw std::runtime_error("incorrect date string (" + std::string(row_value) + ")"); - } - } else { - // ! row_value + std::chrono::sys_days extract(const char* columnText) const { + if(!columnText) { throw std::runtime_error("incorrect date string (nullptr)"); } + + if(auto sd = sysDaysFromString(columnText)) { + return sd.value(); + } else { + throw std::runtime_error("incorrect date string (" + std::string(columnText) + ")"); + } } std::chrono::sys_days extract(sqlite3_stmt* stmt, int columnIndex) const { auto str = sqlite3_column_text(stmt, columnIndex); return this->extract((const char*)str); } - std::chrono::sys_days extract(sqlite3_value* row_value) const { - auto characters = (const char*)(sqlite3_value_text(row_value)); + std::chrono::sys_days extract(sqlite3_value* value) const { + auto characters = (const char*)(sqlite3_value_text(value)); return extract(characters); } }; diff --git a/examples/enum_binding.cpp b/examples/enum_binding.cpp index e8164f730..0bee82ca8 100644 --- a/examples/enum_binding.cpp +++ b/examples/enum_binding.cpp @@ -108,15 +108,15 @@ namespace sqlite_orm { */ template<> struct row_extractor { - Gender extract(const char* row_value) { - if(auto gender = GenderFromString(row_value)) { + Gender extract(const char* columnText) const { + if(auto gender = GenderFromString(columnText)) { return *gender; } else { - throw std::runtime_error("incorrect gender string (" + std::string(row_value) + ")"); + throw std::runtime_error("incorrect gender string (" + std::string(columnText) + ")"); } } - Gender extract(sqlite3_stmt* stmt, int columnIndex) { + Gender extract(sqlite3_stmt* stmt, int columnIndex) const { auto str = sqlite3_column_text(stmt, columnIndex); return this->extract((const char*)str); } diff --git a/examples/nullable_enum_binding.cpp b/examples/nullable_enum_binding.cpp index 8625af059..375097144 100644 --- a/examples/nullable_enum_binding.cpp +++ b/examples/nullable_enum_binding.cpp @@ -76,19 +76,19 @@ namespace sqlite_orm { template<> struct row_extractor { - Gender extract(const char* row_value) { - if(row_value) { - if(auto gender = GenderFromString(row_value)) { + Gender extract(const char* columnText) const { + if(columnText) { + if(auto gender = GenderFromString(columnText)) { return *gender; } else { - throw std::runtime_error("incorrect gender string (" + std::string(row_value) + ")"); + throw std::runtime_error("incorrect gender string (" + std::string(columnText) + ")"); } } else { return Gender::None; } } - Gender extract(sqlite3_stmt* stmt, int columnIndex) { + Gender extract(sqlite3_stmt* stmt, int columnIndex) const { auto str = sqlite3_column_text(stmt, columnIndex); return this->extract((const char*)str); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 5fcda838b..6a5db1dd0 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -7661,7 +7661,7 @@ namespace sqlite_orm { // #include "xdestroy_handling.h" #include // std::integral_constant -#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && SQLITE_ORM_HAS_INCLUDE() +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include #endif @@ -7706,7 +7706,7 @@ namespace sqlite_orm { }; #endif -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Yield a deleter's function pointer. */ @@ -7756,7 +7756,7 @@ namespace sqlite_orm { template using yielded_fn_t = typename yield_fp_of::type; -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED template concept is_unusable_for_xdestroy = (!stateless_deleter && (yields_fp && !std::convertible_to, xdestroy_fn_t>)); @@ -7836,7 +7836,7 @@ namespace sqlite_orm { namespace sqlite_orm { -#if __cpp_lib_concepts >= 201907L +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED /** * Prohibits using a yielded function pointer, which is not of type xdestroy_fn_t. * @@ -8547,6 +8547,9 @@ namespace sqlite_orm { #include // std::copy #include // std::back_inserter #include // std::tuple, std::tuple_size, std::tuple_element +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#include +#endif // #include "functional/cxx_universal.h" @@ -8635,27 +8638,96 @@ namespace sqlite_orm { namespace sqlite_orm { /** - * Helper class used to cast values from argv to V class - * which depends from column type. - * + * Helper for casting values originating from SQL to C++ typed values, usually from rows of a result set. + * + * sqlite_orm provides specializations for known C++ types, users may define their custom specialization + * of this helper. + * + * @note (internal): Since row extractors are used in certain contexts with only one purpose at a time + * (e.g., converting a row result set but not function values or column text), + * there are factory functions that perform conceptual checking that should be used + * instead of directly creating row extractors. + * + * */ template struct row_extractor { - // used in sqlite3_exec (select) - V extract(const char* row_value) const = delete; + /* + * Called during one-step query execution (one result row) for each column of a result row. + */ + V extract(const char* columnText) const = delete; - // used in sqlite_column (iteration, get_all) + /* + * Called during multi-step query execution (result set) for each column of a result row. + */ V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; - // used in user defined functions + /* + * Called before invocation of user-defined scalar or aggregate functions, + * in order to unbox dynamically typed SQL function values into a tuple of C++ function arguments. + */ V extract(sqlite3_value* value) const = delete; }; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + template + concept orm_column_text_extractable = requires(const row_extractor& extractor, const char* columnText) { + { extractor.extract(columnText) } -> std::same_as; + }; + + template + concept orm_row_value_extractable = + requires(const row_extractor& extractor, sqlite3_stmt* stmt, int columnIndex) { + { extractor.extract(stmt, columnIndex) } -> std::same_as; + }; + + template + concept orm_boxed_value_extractable = requires(const row_extractor& extractor, sqlite3_value* value) { + { extractor.extract(value) } -> std::same_as; + }; +#endif + + namespace internal { + /* + * Make a row extractor to be used for casting SQL column text to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + row_extractor column_text_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for converting a value from a SQL result row set to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + row_extractor row_value_extractor() { + return {}; + } + + /* + * Make a row extractor to be used for unboxing a dynamically typed SQL value to a C++ typed value. + */ + template +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + row_extractor boxed_value_extractor() { + return {}; + } + } + template int extract_single_value(void* data, int argc, char** argv, char**) { auto& res = *(R*)data; if(argc) { - res = row_extractor{}.extract(argv[0]); + const auto rowExtractor = internal::column_text_extractor(); + res = rowExtractor.extract(argv[0]); } return 0; } @@ -8670,6 +8742,10 @@ namespace sqlite_orm { struct row_extractor, void> { using V = pointer_arg; + V extract(const char* columnText) const = delete; + + V extract(sqlite3_stmt* stmt, int columnIndex) const = delete; + V extract(sqlite3_value* value) const { return {(P*)sqlite3_value_pointer(value, T::value)}; } @@ -8686,8 +8762,8 @@ namespace sqlite_orm { */ template struct row_extractor::value>> { - V extract(const char* row_value) const { - return this->extract(row_value, tag()); + V extract(const char* columnText) const { + return this->extract(columnText, tag()); } V extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -8701,8 +8777,8 @@ namespace sqlite_orm { private: using tag = arithmetic_tag_t; - V extract(const char* row_value, const int_or_smaller_tag&) const { - return static_cast(atoi(row_value)); + V extract(const char* columnText, const int_or_smaller_tag&) const { + return static_cast(atoi(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const int_or_smaller_tag&) const { @@ -8713,8 +8789,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int(value)); } - V extract(const char* row_value, const bigint_tag&) const { - return static_cast(atoll(row_value)); + V extract(const char* columnText, const bigint_tag&) const { + return static_cast(atoll(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const bigint_tag&) const { @@ -8725,8 +8801,8 @@ namespace sqlite_orm { return static_cast(sqlite3_value_int64(value)); } - V extract(const char* row_value, const real_tag&) const { - return static_cast(atof(row_value)); + V extract(const char* columnText, const real_tag&) const { + return static_cast(atof(columnText)); } V extract(sqlite3_stmt* stmt, int columnIndex, const real_tag&) const { @@ -8741,17 +8817,17 @@ namespace sqlite_orm { /** * Specialization for std::string. */ - template<> - struct row_extractor { - std::string extract(const char* row_value) const { - if(row_value) { - return row_value; + template + struct row_extractor::value>> { + T extract(const char* columnText) const { + if(columnText) { + return columnText; } else { return {}; } } - std::string extract(sqlite3_stmt* stmt, int columnIndex) const { + T extract(sqlite3_stmt* stmt, int columnIndex) const { if(auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex)) { return cStr; } else { @@ -8759,7 +8835,7 @@ namespace sqlite_orm { } } - std::string extract(sqlite3_value* value) const { + T extract(sqlite3_value* value) const { if(auto cStr = (const char*)sqlite3_value_text(value)) { return cStr; } else { @@ -8773,10 +8849,10 @@ namespace sqlite_orm { */ template<> struct row_extractor { - std::wstring extract(const char* row_value) const { - if(row_value) { + std::wstring extract(const char* columnText) const { + if(columnText) { std::wstring_convert> converter; - return converter.from_bytes(row_value); + return converter.from_bytes(columnText); } else { return {}; } @@ -8806,27 +8882,42 @@ namespace sqlite_orm { struct row_extractor::value>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return is_std_ptr::make(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(columnText)); } else { return {}; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(stmt, columnIndex)); } else { return {}; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return is_std_ptr::make(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return is_std_ptr::make(rowExtractor.extract(value)); } else { return {}; } @@ -8838,27 +8929,42 @@ namespace sqlite_orm { struct row_extractor>> { using unqualified_type = std::remove_cv_t; - V extract(const char* row_value) const { - if(row_value) { - return std::make_optional(row_extractor().extract(row_value)); + V extract(const char* columnText) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_column_text_extractable) +#endif + { + if(columnText) { + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(columnText)); } else { return std::nullopt; } } - V extract(sqlite3_stmt* stmt, int columnIndex) const { + V extract(sqlite3_stmt* stmt, int columnIndex) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_row_value_extractable) +#endif + { auto type = sqlite3_column_type(stmt, columnIndex); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(stmt, columnIndex)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(stmt, columnIndex)); } else { return std::nullopt; } } - V extract(sqlite3_value* value) const { + V extract(sqlite3_value* value) const +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires(orm_boxed_value_extractable) +#endif + { auto type = sqlite3_value_type(value); if(type != SQLITE_NULL) { - return std::make_optional(row_extractor().extract(value)); + const row_extractor rowExtractor{}; + return std::make_optional(rowExtractor.extract(value)); } else { return std::nullopt; } @@ -8868,7 +8974,7 @@ namespace sqlite_orm { template<> struct row_extractor { - nullptr_t extract(const char* /*row_value*/) const { + nullptr_t extract(const char* /*columnText*/) const { return nullptr; } @@ -8885,8 +8991,8 @@ namespace sqlite_orm { */ template<> struct row_extractor> { - std::vector extract(const char* row_value) const { - return {row_value, row_value + (row_value ? ::strlen(row_value) : 0)}; + std::vector extract(const char* columnText) const { + return {columnText, columnText + (columnText ? ::strlen(columnText) : 0)}; } std::vector extract(sqlite3_stmt* stmt, int columnIndex) const { @@ -8902,6 +9008,9 @@ namespace sqlite_orm { } }; + /** + * Specialization for a tuple. + */ template struct row_extractor> { @@ -8916,12 +9025,12 @@ namespace sqlite_orm { protected: template std::tuple extract(sqlite3_stmt* stmt, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(stmt, Idx)...}; + return {row_extractor{}.extract(stmt, Idx)...}; } template std::tuple extract(char** argv, std::index_sequence) const { - return std::tuple{row_extractor{}.extract(argv[Idx])...}; + return {row_extractor{}.extract(argv[Idx])...}; } }; @@ -8930,9 +9039,9 @@ namespace sqlite_orm { */ template<> struct row_extractor { - journal_mode extract(const char* row_value) const { - if(row_value) { - if(auto res = internal::journal_mode_from_string(row_value)) { + journal_mode extract(const char* columnText) const { + if(columnText) { + if(auto res = internal::journal_mode_from_string(columnText)) { return std::move(*res); } else { throw std::system_error{orm_error_code::incorrect_journal_mode_string}; @@ -10040,9 +10149,6 @@ namespace sqlite_orm { return std::get(dbObjects); } - template = true> - auto lookup_table(const DBOs& dbObjects); - template = true> decltype(auto) lookup_table_name(const DBOs& dbObjects); @@ -10065,15 +10171,6 @@ namespace sqlite_orm { return res; } - template> - auto lookup_table(const DBOs& dbObjects) { - return static_if>( - [](const auto& dbObjects) { - return &pick_table(dbObjects); - }, - empty_callable())(dbObjects); - } - template> decltype(auto) lookup_table_name(const DBOs& dbObjects) { return static_if>( @@ -10175,112 +10272,6 @@ namespace sqlite_orm { // #include "alias.h" -// #include "row_extractor_builder.h" - -// #include "functional/cxx_universal.h" - -// #include "row_extractor.h" - -// #include "mapped_row_extractor.h" - -#include - -// #include "object_from_column_builder.h" - -#include -#include // std::is_member_object_pointer - -// #include "functional/static_magic.h" - -// #include "row_extractor.h" - -namespace sqlite_orm { - - namespace internal { - - struct object_from_column_builder_base { - sqlite3_stmt* stmt = nullptr; - int index = 0; - -#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED - object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} -#endif - }; - - /** - * This is a cute lambda replacement which is used in several places. - */ - template - struct object_from_column_builder : object_from_column_builder_base { - using object_type = O; - - object_type& object; - - object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : - object_from_column_builder_base{stmt_}, object(object_) {} - - template - void operator()(const column_field& column) { - auto value = row_extractor>().extract(this->stmt, this->index++); - static_if::value>( - [&value, &object = this->object](const auto& column) { - object.*column.member_pointer = std::move(value); - }, - [&value, &object = this->object](const auto& column) { - (object.*column.setter)(std::move(value)); - })(column); - } - }; - } -} - -namespace sqlite_orm { - - namespace internal { - - /** - * This is a private row extractor class. It is used for extracting rows as objects instead of tuple. - * Main difference from regular `row_extractor` is that this class takes table info which is required - * for constructing objects by member pointers. To construct please use `make_row_extractor()`. - * Type arguments: - * V is value type just like regular `row_extractor` has - * T is table info class `table_t` - */ - template - struct mapped_row_extractor { - using table_type = Table; - - V extract(sqlite3_stmt* stmt, int /*columnIndex*/) const { - V res; - object_from_column_builder builder{res, stmt}; - this->tableInfo.for_each_column(builder); - return res; - } - - const table_type& tableInfo; - }; - - } - -} - -namespace sqlite_orm { - - namespace internal { - - template - row_extractor make_row_extractor(nullptr_t) { - return {}; - } - - template - mapped_row_extractor make_row_extractor(const Table* table) { - return {*table}; - } - } - -} - // #include "error_code.h" // #include "type_printer.h" @@ -10999,6 +10990,54 @@ namespace sqlite_orm { // #include "object_from_column_builder.h" +#include +#include // std::is_member_object_pointer + +// #include "functional/static_magic.h" + +// #include "row_extractor.h" + +namespace sqlite_orm { + + namespace internal { + + struct object_from_column_builder_base { + sqlite3_stmt* stmt = nullptr; + int index = 0; + +#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED + object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {} +#endif + }; + + /** + * Function object for building an object from a result row. + */ + template + struct object_from_column_builder : object_from_column_builder_base { + using object_type = O; + + object_type& object; + + object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) : + object_from_column_builder_base{stmt_}, object(object_) {} + + template + void operator()(const column_field& column) { + const auto rowExtractor = row_value_extractor>(); + auto value = rowExtractor.extract(this->stmt, this->index++); + static_if::value>( + [&value, &object = this->object](const auto& column) { + object.*column.member_pointer = std::move(value); + }, + [&value, &object = this->object](const auto& column) { + (object.*column.setter)(std::move(value)); + })(column); + } + }; + } +} + // #include "storage_lookup.h" // #include "util.h" @@ -13456,8 +13495,9 @@ namespace sqlite_orm { inline int getPragmaCallback>(void* data, int argc, char** argv, char**) { auto& res = *(std::vector*)data; res.reserve(argc); - for(decltype(argc) i = 0; i < argc; ++i) { - auto rowString = row_extractor().extract(argv[i]); + const auto rowExtractor = column_text_extractor(); + for(int i = 0; i < argc; ++i) { + auto rowString = rowExtractor.extract(argv[i]); res.push_back(std::move(rowString)); } return 0; @@ -13974,6 +14014,8 @@ namespace sqlite_orm { namespace sqlite_orm { + /** @short Wrapper around a dynamically typed value object. + */ struct arg_value { arg_value() : arg_value(nullptr) {} @@ -13982,7 +14024,8 @@ namespace sqlite_orm { template T get() const { - return row_extractor().extract(this->value); + const auto rowExtractor = internal::boxed_value_extractor(); + return rowExtractor.extract(this->value); } bool is_null() const { @@ -14143,7 +14186,8 @@ namespace sqlite_orm { #endif template void extract(sqlite3_value* value, T& t) const { - t = row_extractor{}.extract(value); + const auto rowExtractor = boxed_value_extractor(); + t = rowExtractor.extract(value); } }; } @@ -18614,18 +18658,41 @@ namespace sqlite_orm { perform_step(stmt); } - template> + template, + satisfies_not = true> std::vector execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); std::vector res; - perform_steps(stmt, - [rowExtractor = make_row_extractor(lookup_table(this->db_objects)), - &res](sqlite3_stmt* stmt) { - res.push_back(rowExtractor.extract(stmt, 0)); - }); + 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, + satisfies = true> + std::vector execute(const prepared_statement_t>& 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)); + }); res.shrink_to_fit(); return res; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fbf3df81e..8b7366452 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(unit_tests static_tests/functional/tuple_transform.cpp static_tests/is_printable.cpp static_tests/is_bindable.cpp + static_tests/row_extractor.cpp static_tests/iterator_t.cpp static_tests/arithmetic_operators_result_type.cpp static_tests/node_tuple.cpp @@ -133,6 +134,7 @@ add_executable(unit_tests ast_iterator_tests.cpp table_name_collector.cpp pointer_passing_interface.cpp + row_extractor.cpp ) if(SQLITE_ORM_OMITS_CODECVT) diff --git a/tests/row_extractor.cpp b/tests/row_extractor.cpp new file mode 100644 index 000000000..a926c08ab --- /dev/null +++ b/tests/row_extractor.cpp @@ -0,0 +1,163 @@ +#include +#include + +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED +using namespace sqlite_orm; + +enum class Gender { + Invalid, + Male, + Female, +}; + +struct SuperHero { + int id; + std::string name; + Gender gender = Gender::Invalid; +}; + +std::string GenderToString(Gender gender) { + switch(gender) { + case Gender::Female: + return "female"; + case Gender::Male: + return "male"; + } + throw std::domain_error("Invalid Gender enum"); +} + +std::optional GenderFromString(const std::string& s) { + if(s == "female") { + return Gender::Female; + } else if(s == "male") { + return Gender::Male; + } + return std::nullopt; +} + +template<> +struct sqlite_orm::type_printer : public text_printer {}; + +template<> +struct sqlite_orm::statement_binder { + + int bind(sqlite3_stmt* stmt, int index, const Gender& value) { + return statement_binder().bind(stmt, index, GenderToString(value)); + // or return sqlite3_bind_text(stmt, index++, GenderToString(value).c_str(), -1, SQLITE_TRANSIENT); + } + void result(sqlite3_context* context, const Gender& value) const { + return statement_binder().result(context, GenderToString(value)); + } +}; + +template<> +struct sqlite_orm::field_printer { + std::string operator()(const Gender& t) const { + return GenderToString(t); + } +}; + +template<> +struct sqlite_orm::row_extractor { + Gender extract(const char* columnText) const { + if(auto gender = GenderFromString(columnText)) { + return *gender; + } else { + throw std::runtime_error("incorrect gender string (" + std::string(columnText) + ")"); + } + } + + Gender extract(sqlite3_stmt* stmt, int columnIndex) const { + auto str = sqlite3_column_text(stmt, columnIndex); + return this->extract((const char*)str); + } + + Gender extract(sqlite3_value* value) const { + const unsigned char* str = sqlite3_value_text(value); + return this->extract((const char*)str); + } +}; + +struct IdentityFunction { + + Gender operator()(Gender gender) const { + return gender; + } + + static const std::string& name() { + static const std::string result = "IDENTITY"; + return result; + } +}; + +struct CustomConcatFunction { + + std::string operator()(const arg_values& args) const { + std::string result; + for(auto arg_value: args) { + result += arg_value.get(); + } + return result; + } + + static const std::string& name() { + static const std::string result = "CUSTOM_CONCAT"; + return result; + } +}; + +// This test case triggers the conceptual checks performed in all functions that use the row_extractor. +// Also it is representative for all stock row extractor specializations in regard to testing +// storage's machinery during select and function calls, and whether they return proper values or build objects with proper values. +TEST_CASE("Custom row extractors") { + remove("cre.sqlite"); + struct fguard { + ~fguard() { + remove("cre.sqlite"); + } + } g; + auto storage = make_storage("cre.sqlite", + make_table("superheros", + make_column("id", &SuperHero::id, primary_key()), + make_column("name", &SuperHero::name), + make_column("gender", &SuperHero::gender))); + sqlite3* db = nullptr; + storage.on_open = [&db](sqlite3* db_) { + db = db_; + }; + storage.open_forever(); + storage.sync_schema(); + + storage.transaction([&storage]() { + storage.insert(SuperHero{-1, "Batman", Gender::Male}); + storage.insert(SuperHero{-1, "Wonder woman", Gender::Female}); + storage.insert(SuperHero{-1, "Superman", Gender::Male}); + return true; + }); + + SECTION("object builder") { + auto allSuperHeros = storage.get_all(order_by(&SuperHero::gender)); + REQUIRE(allSuperHeros.at(0).gender == Gender::Female); + } + SECTION("rowset builder") { + auto allGenders = storage.select(distinct(&SuperHero::gender), order_by(&SuperHero::gender)); + REQUIRE(allGenders.at(0) == Gender::Female); + } + SECTION("column text") { + auto firstGender = select(distinct(&SuperHero::gender), order_by(&SuperHero::gender), limit(1)); + Gender gender = Gender::Invalid; + internal::perform_exec(db, storage.dump(firstGender), &extract_single_value, &gender); + REQUIRE(gender == Gender::Female); + } + SECTION("udf") { + storage.create_scalar_function(); + auto genders = storage.select(func(Gender::Male)); + REQUIRE(genders.at(0) == Gender::Male); + } + SECTION("dynamic-arg udf") { + storage.create_scalar_function(); + auto concatGenders = storage.select(func(Gender::Male, Gender::Female)); + REQUIRE(concatGenders.at(0) == "malefemale"); + } +} +#endif diff --git a/tests/static_tests/row_extractor.cpp b/tests/static_tests/row_extractor.cpp new file mode 100644 index 000000000..4535f0052 --- /dev/null +++ b/tests/static_tests/row_extractor.cpp @@ -0,0 +1,114 @@ +#include +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED +#define ENABLE_THIS_UT +#endif + +#ifdef ENABLE_THIS_UT +#include +#include // std::unique_ptr, std::shared_ptr +#include // std::string +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED +#include // std::optional +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED +#include +#endif + +using namespace sqlite_orm; + +template +static void check_extractable() { + STATIC_CHECK(orm_column_text_extractable); + STATIC_CHECK(orm_row_value_extractable); + STATIC_CHECK(orm_boxed_value_extractable); +} + +template +static void check_not_extractable() { + STATIC_CHECK_FALSE(orm_column_text_extractable); + STATIC_CHECK_FALSE(orm_row_value_extractable); + STATIC_CHECK_FALSE(orm_boxed_value_extractable); +} + +namespace { + enum class custom_enum { custom }; + + template + class StringVeneer : public std::basic_string {}; + + struct User { + int id; + }; +} + +template<> +struct sqlite_orm::row_extractor { + custom_enum extract(const char* /*columnText*/) const { + return custom_enum::custom; + } + custom_enum extract(sqlite3_stmt*, int /*columnIndex*/) const { + return custom_enum::custom; + } + custom_enum extract(sqlite3_value*) const { + return custom_enum::custom; + } +}; + +TEST_CASE("is_extractable") { + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_extractable(); + check_not_extractable(); + check_extractable(); + check_extractable>(); +#ifndef SQLITE_ORM_OMITS_CODECVT + check_not_extractable(); + check_extractable(); + check_not_extractable>(); +#endif + check_extractable(); + check_extractable>(); + check_extractable>(); +#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED + check_not_extractable(); +#ifndef SQLITE_ORM_OMITS_CODECVT + check_not_extractable(); +#endif +#endif +#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED + check_not_extractable(); + check_extractable>(); + check_extractable>(); + check_not_extractable>(); +#endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED + check_not_extractable>(); + // pointer arguments are special: they can only be passed to and from functions, but casting is prohibited + { + using int64_pointer_arg = carray_pointer_arg; + STATIC_CHECK_FALSE(orm_column_text_extractable); + STATIC_CHECK_FALSE(orm_row_value_extractable); + STATIC_CHECK(orm_boxed_value_extractable); + } +#endif + + check_extractable(); + check_extractable>(); + + check_not_extractable(); + check_not_extractable>(); + check_not_extractable(); + check_not_extractable>(); +} +#endif