fnc12 / sqlite_orm

❤️ SQLite ORM light header only library for modern C++
GNU Affero General Public License v3.0
2.19k stars 305 forks source link

Iteration of result sets #1311

Closed trueqbit closed 1 week ago

trueqbit commented 2 weeks ago

This PR introduces iteration over result sets of select statements (possibly with common table expressions).

auto db = make_storage("");
std::ranges::view auto int_view = db.iterate(select(1));
for (int i : int_view) {
    // ...
}

Technically speaking, the feature consists of:

The feature currently requires a C++20 compiler, but can also be used with implementations of the C++14 standard library.

Additional changes:

juandent commented 1 week ago

What is the appropriate syntax for using iterate? iterate seems to need a Table type but that does not work for general selects!

    auto rows = storage.iterate(select(columns(&Doctor::id, &Doctor::name, &Visit::patientName, &Visit::vdate),
        left_join<Visit>(on(c(&Doctor::id) == &Visit::doctorId))));
    for( auto& row : rows)
    {
             auto id = get<0>(row);  // does not work!!
     }
trevornagy commented 1 week ago

@trueqbit, maybe a discussion for another place. But I'm trying to create a wrapper around result_set_view so I'm only exposing STL types/don't need to include the ORM code everywhere (type erasure). I'm running into the issue that result_set_iterator doesn't seem to implement the != operator. Am I doing this wrong? Basically, my goal is to have some type, let's call it Iterator where I can do the following:

std::ranges::view auto SomeQuery() const
{
    auto result = storage.iterate<MarvelHero>(where(length(&MarvelHero::name) < 6)));
    return Iterator<MarvelHero>(result.begin(), result.end());
}

The error is: binary '!=': no operator found which takes a left-hand operand of type 'const TIterator' (or there is no acceptable conversion) with TIterator=sqlite_orm::internal::result_set_sentinel_t

Any thoughts?

trueqbit commented 1 week ago
std::ranges::view auto SomeQuery() const
{
    auto result = storage.iterate<MarvelHero>(where(length(&MarvelHero::name) < 6)));
    return Iterator<MarvelHero>(result.begin(), result.end());
}

The error is: binary '!=': no operator found which takes a left-hand operand of type 'const TIterator' (or there is no acceptable conversion) with TIterator=sqlite_orm::internal::result_set_sentinel_t

Any thoughts?

The specific query you posted here does not return a view on a result set of a select statement, but a view on "mapped objects", so there is a mismatch between the error you described and the iterator method you used.

The "mapped objects" view returns the same iterator type for start and end. The view on a result set returns a sentinel as the end of the range (which is std::default_sentinel_t). Since a result set iterator is a single-pass input iterator, you can only compare it to the sentinel.

Of course, I have no insight into the specifics of your application. However, nowadays there is a very nice way of type erasure that usually saves me from writing view or iterator adapters: range generators.

You can have a function that returns a range generator of MarvelHeroS:

std::generator<MarvelHero> query_heroes() {
    for (MarvelHero hero : storage.iterate</*...*/>(/*...*/)) {
        co_yield hero;
    }
}

I use the reference implementation of the C++ WG21 proposal P2168, which is available on Lewis' Repository. There are probably improved implementations since the C++ WG21 proposal P2502.