
On 8/31/25 18:24, Klemens Morgenstern via Boost wrote:
On Sun, Aug 31, 2025 at 8:44 AM Maximilian Riemensberger via Boost < boost@lists.boost.org> wrote:
* The field type returns sqlite TEXT data as cstring_ref instead of string_view. The sqlite documentation guarantees it's NUL terminated, but it also recommends using e.g. sqlite3_column_bytes to determine the length of text data and does not explicitly forbid embedded NUL characters.
I would recommend using the blob interface then. A string_view with null-terminators in the middle is also generally bad news.
Maybe. But std::string and std::string_view handle that case just fine. sqlite also handles it just fine as a column value (not in expressions). And since sqlite internally tracks, exposes and recommends using the length, I cannot see any good reason not to use string_view.
* The connection type can be owning or non-owning. Why wouldn't one instead have distinct connection (owning) and connection_ref (non-owning) types?
Because otherwise the complexity goes up unnecessarily. A user can write a function just taking `connection&` and not worry about this. In the library I have the `transaction` type for example. It holds a reference to the connection - if there was a second type like `connection_ref`, now I would have a second type of a transaction.
I see this point. However, a user that writes a function that takes `connection&` as an argument likely does not care about whether the connection is owning since the general expection would be that the connection is connected and live before the function is called and remains so until the function returns. All those functions can just take `connection_ref` by value (just like functions take a string_view or span or function_ref) and no duplication is necessary, assuming connection_ref is implicitly constructible from `connection`. For example, the transaction: class transaction { connection_ref conn_; public: explicit transaction(connection_ref conn); // Many more things } And I would then expect both of the following to compile just fine: // 1 connection c{":memory:"}; transaction t{c}; // 2 connection c{":memory:"}; connection_ref cr{c}; transaction t{cr}; and mean the same thing.
I could of course add a connection_base and build a OOP construct, but that would just be a more inefficient & cumbersome version of the current solution.
* The statement type can construct owning and non-owning resultsets. The former automatically finalize, the latter reset the statement. Again why are those two behaviors combined in the same type?
Same reason as above. If a user wrote a manual converter for each row, he'd need to implement it twice. The point is that those two things just work:
T interpret_result(sqlite::resultset rs);
auto rr = interpret_result(conn.prepare("...").execute(...));
auto s = conn.prepare("..."); auto r2 = interpret_result(s.execute(...));
Why should I force a user to write two different implementations of interpret_result? And the answer isn't OOP for the resultset, because that it cannot be passed by value.
Similar as for the connection: If the user function `interpret_result` does not take ownership of the resultset, that is the resultset that is passed into the function must outlive the return of the function, then interpret_result should only be written once and take resultset_ref as argument. resultset is implicitly convertible to resultset_ref. There is also no lifetime issue here since the return value of execute lives at least until the end of the semicolon (after interpret_result returns). T interpret_result(sqlite::resultset_ref rs); auto rr = interpret_result(conn.prepare("...").execute(...)); // Same thing written in two statements sqlite::resultset rs = conn.prepare("...").execute(...) auto rr = interpret_result(rs); sqlite::resultset_ref s = conn.prepare("..."); auto r2 = interpret_result(s.execute(...));
* The library is designed around unique_ptr wrapping the sqlite3 C API handles with optional ownership. In particular, this seems at times quite dangerous for the sqlite3_stmt handle which is shared by statement, resultset, field, etc. Would the library be better served by for example reference counting such handles? If not, why?
There is no obvious place to put the counter. If sqlite had those, I'd use them.
Fair enough.
* What is the general thread safety guarantees of the library types? After all, it contains a mutex implementation wrapper. So multi-threaded application are probably a target for this library.
Same as whatever sqlite is compiled for.
Ok. Searching through the sqlite docs basically gave me this page https://www.sqlite.org/threadsafe.html which kind of explains it. Best regards, Max