diff --git a/dev/functional/config.h b/dev/functional/config.h index 170e6120e..b50a50e82 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -47,6 +47,10 @@ #define SQLITE_ORM_CPP20_RANGES_SUPPORTED #endif +#if __cpp_lib_generator >= 202207L +#define SQLITE_ORM_CPP23_GENERATOR_SUPPORTED +#endif + #ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED #define SQLITE_ORM_STATIC_CALLOP static #define SQLITE_ORM_OR_CONST_CALLOP diff --git a/dev/storage.h b/dev/storage.h index 4129baefa..15a46a845 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -14,6 +14,9 @@ #include // std::tuple_size, std::tuple, std::make_tuple, std::tie #include // std::forward, std::pair #include // std::for_each, std::ranges::for_each +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED +#include +#endif #endif #include "functional/cxx_optional.h" @@ -328,6 +331,63 @@ namespace sqlite_orm { return {this->db_objects, std::move(connection), std::move(expression)}; } #endif +#endif + +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED + /* + * Iterate over objects of a type mapped as a table, lazily fetched from a result set in a coroutine. + */ + template, class... Args> + std::generator yield(Args&&... args) { + this->assert_mapped_type(); + // implementation note: instead of using `this->iterate()` we iterate over a select statement, + // because a `mapped_view` has a legacy input iterator that returns a reference to an object. + // For a generator we want to yield objects by value that can be moved from. + for (O obj: + this->iterate(sqlite_orm::select(struct_(asterisk(true)), std::forward(args)...))) { + co_yield obj; + } + } + + /* + * Iterate over objects of a type mapped as a table, lazily fetched from a result set in a coroutine. + */ + template, class... Args> + std::generator yield(Args&&... args) { + this->assert_mapped_type(); + // implementation note: instead of using `this->iterate()` we iterate over a select statement, + // because a `mapped_view` has a legacy input iterator that returns a reference to an object. + // For a generator we want to yield objects by value that can be moved from. + for (O obj: this->iterate(sqlite_orm::select(struct_(asterisk(true)), + std::forward(args)...))) { + co_yield obj; + } + } + + /* + * Iterate over a result set of a select statement in a coroutine. + */ + template + requires (is_select_v) + auto yield(Select expression) -> std::generatoriterate(std::move(expression)).begin())> { + for (auto row: this->iterate(std::move(expression))) { + co_yield row; + } + } + +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + /* + * Iterate over a result set of a select statement involving a common table expression in a coroutine. + */ + template + requires (is_select_v) + auto yield(with_t expression) + -> std::generatoriterate(std::move(expression)).begin())> { + for (auto row: this->iterate(std::move(expression))) { + co_yield row; + } + } +#endif #endif /** diff --git a/tests/iterate.cpp b/tests/iterate.cpp index 222c5acf5..7c57324ed 100644 --- a/tests/iterate.cpp +++ b/tests/iterate.cpp @@ -69,6 +69,10 @@ TEST_CASE("Iterate select statement") { bool operator==(const Test&) const = default; }; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + constexpr orm_table_reference auto test_table = c(); + constexpr orm_table_alias auto test_alias = "t"_alias.for_(); +#endif auto db = make_storage("", @@ -105,10 +109,38 @@ TEST_CASE("Iterate select statement") { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - constexpr auto x = "x"_cte; - std::input_iterator auto begin = - db.iterate(with(x().as(select(asterisk())), select(struct_(asterisk())))).begin(); - REQUIRE(*begin == expected); + SECTION("with") { + constexpr auto x = "x"_cte; + std::input_iterator auto begin = + db.iterate(with(x().as(select(asterisk())), select(struct_(asterisk())))).begin(); + REQUIRE(*begin == expected); + } +#endif +#endif + +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED + SECTION("object generator, classic") { + auto view = db.yield(); + REQUIRE(std::vector{std::from_range, view} == expected_vec); + } + SECTION("object generator, table reference") { + auto view = db.yield(); + REQUIRE(std::vector{std::from_range, view} == expected_vec); + } + SECTION("object generator, alias") { + auto view = db.yield(); + REQUIRE(std::vector{std::from_range, view} == expected_vec); + } + SECTION("select generator") { + auto view = db.yield(select(object())); + REQUIRE(std::vector{std::from_range, view} == expected_vec); + } +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + SECTION("with generator") { + constexpr auto x = "x"_cte; + auto view = db.yield(with(x().as(select(asterisk())), select(struct_(asterisk())))); + REQUIRE(std::vector{std::from_range, view} == expected_vec); + } #endif #endif } diff --git a/tests/static_tests/iterator_t.cpp b/tests/static_tests/iterator_t.cpp index 0f15e69ad..540df77ba 100644 --- a/tests/static_tests/iterator_t.cpp +++ b/tests/static_tests/iterator_t.cpp @@ -73,6 +73,12 @@ concept storage_iterate_mapped = requires(S& storage_type) { { storage_type.template iterate() } -> std::same_as>; { storage_type.template iterate() } -> can_view_mapped; }; + +template +concept storage_iterate_mapped_ref = requires(S& storage_type) { + { storage_type.template iterate() } -> std::same_as>; + { storage_type.template iterate() } -> can_view_mapped; +}; #endif #if defined(SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED) && defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED) @@ -103,8 +109,29 @@ concept storage_iterate_result_set = requires(S& storage_type, Select select) { }; #endif +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED +template +concept storage_yield_mapped = requires(S& storage_type) { + { storage_type.template yield() } -> std::same_as>; +}; + +template +concept storage_yield_mapped_ref = requires(S& storage_type) { + { storage_type.template yield() } -> std::same_as>; +}; + +template +concept storage_yield_result_set = requires(S& storage_type, Select select) { + { storage_type.yield(select) } -> std::same_as>; +}; +#endif + namespace { struct Object {}; +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + constexpr orm_table_alias auto object_alias = "o"_alias.for_(); + constexpr orm_table_reference auto object_table = c(); +#endif } TEST_CASE("can view and iterate mapped") { @@ -152,6 +179,14 @@ TEST_CASE("can view and iterate mapped") { #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED STATIC_REQUIRE(storage_iterate_mapped); + STATIC_REQUIRE(storage_iterate_mapped_ref); + STATIC_REQUIRE(storage_iterate_mapped_ref); +#endif + +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED + STATIC_REQUIRE(storage_yield_mapped); + STATIC_REQUIRE(storage_yield_mapped_ref); + STATIC_REQUIRE(storage_yield_mapped_ref); #endif } @@ -195,5 +230,12 @@ TEST_CASE("can view and iterate result set") { STATIC_REQUIRE(storage_iterate_result_set*i))), int>); #endif #endif + +#ifdef SQLITE_ORM_CPP23_GENERATOR_SUPPORTED + STATIC_REQUIRE(storage_yield_result_set); +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + STATIC_REQUIRE(storage_yield_result_set*i))), int>); +#endif +#endif } #endif