
pt., 12 wrz 2025 o 17:21 Dominique Devienne via Boost <boost@lists.boost.org> napisał(a):
On Fri, Sep 12, 2025 at 4:54 PM Mohammad Nejati via Boost <boost@lists.boost.org> wrote:
On Fri, Sep 12, 2025 at 3:57 PM Vinnie Falco <vinnie.falco@gmail.com> wrote: Regarding the resultset issue, the functionality is usually implemented in the statement class because there is no standalone resultset object in SQLite and it always needs to refer to a statement.
Yes, no resultset, only a statement. But the same applies to row, used in the proposed range-for loop. There's also no row in SQLite. And that pseudo row is also just a statement in disguise, the same very one that was in resultset. So how is that any different? I'm still confused by that aspect.
binding happens on the statement. _step(), i.e. "iterating" happens on the statement, to go to the next row. getting the values ("columns") for that current row happens on the statement.
The pattern for a query is:
auto stmt = conn.prepare("some SQL..."); stmt.bind(1, some_value); // binds are 1-based while (stmt.step() == SQLITE_ROW) { auto v1 = stmt.get_text(0); // gets are 0-based double v2; stmt.get(1, v2); // overloads that support generic code ... // use that row for something }
The above assume RAII on statement, and a move-friend stmt type. But my point is that there's no resultset, no row, no field, nothing. statement is it, nothing else. This is idiomatic SQLite, barely above the official C API equivalent.
So what do you do for a nice range-for API? The LHS of a range-for is supposed to be a value. What's the value here? In the native SQLite model, it's the statement itself! Lifetime confision again.
If you force extracting the values (for the current row) into a tuple (or struct, via PFR or Describe magic), then you do have a real value, and the semantic is clear IMHO. That forces you to say on the RHS of the range-for what the current row (hidding inside the step'd statement...) should be extracted and converted to.
If IMHO, it's some kind of generic sqlite::row, that's internally just a reference to the "outer" statement we're iterating, then the semantic are confusing, just like in the resultset's case IMHO.
This is the tension above that must be resolved IMHO.
BTW, Klemens, the pseudo code above is the low-level wrapper API I was talking about previously. --DD
Having a deeper look at SQLite, I now realize that you actually cannot work with two statements on one connection concurrently (this is not related to multi-threading): ``` auto query1 = conn.prepare("select * FROM table1"); auto query2 = conn.prepare("select * FROM table2"); auto combined = std::views::zip(statement_range<T1>(query1), statement_range<T2>(query2)); for (auto record: combined) // ERROR: two interleaving statements process(record); ``` I would intuitively expect that a Boost-class C++ library would prevent me from easily making this mistake, or at least inform me about this gotcha. Maybe I am fantasizing, but I would imagine an interface that doesn't allow me to create the second statement on a connection until I am done with the previous one: ``` conn.execute_statement( "select * FROM table1", [&](int col1, int col2, std::string_view col3) { // process a table row } ); ``` or alternatively ``` conn.with_statement( "select * FROM table1", [&](boost::sqlite::statement& st) { // do what you will with `st` // but its lifetime is managed within the call to with_statement() } ); ``` Regards, &rzej;