libpqxx 7.10.0: Bug fixes, build fixes, and API overhauls
This is about as much of the radical changes I can make before moving on 8.0 (which will require C++20 as a minimum).
I'll start with the fixes, because some of them are important:
- Since macOS doesn't have
/bin/true
, we'll just calltrue
instead (#885). - Error reporting could crash during non-blocking connection construction (#894).
- There was a potential buffer overrun when converting an array containing nulls to an SQL string (#906).
- The nullness check for
std::optional
was broken if the contained type had its own null value (#907). - An error message for clashes between transaction focuses was misleading (#879).
- Minimum CMake version is now
3.283.12 (#851, #874).
Next, we get to the API overhauls!
SQL execution functions go "orthogonal"
There was an enormous and growing body of special functions for executing SQL and getting a result back: Execute SQL statement. Execute statement and expect 0 rows of data. Execute statement and expect 1 row of data (returning the pqxx::row
instead of the full pqxx::result
). Execute statement and expect n rows of data. All the same functions but with parameters. All the same functions again but with a prepared statement. Not all combinations were actually implemented. And that's not even talking about the various streaming versions.
I didn't touch the streaming calls, but the execute-and-get-a-result functions are more manageable now:
- All of the execution functions are called
pqxx::transaction_base::exec()
. The arguments determine which version you mean. - The difference between a regular statement and a parameterised statement is now simply whether you pass a
pqxx::params
argument. - A prepared statement differs in that you wrap the statement name in a
pqxx::prepped
object, to show that it's not an SQL string itself. - If you want to check for a specific number of rows, you do that on the result:
tx.exec("VACUUM mytable").no_rows();
ormy_row = tx.exec("SELECT 1").one_row();
and so on.
The old functions, such as exec0()
and exec1()
and exec_prepared()
etc. will still be there for the 8.0 release cycle (as well as the rest of 7.x), but marked as deprecated. They'll be gone in 9.0. Trust me, you won't miss them!
Error/notice handlers get with the times
There's a new mechanism for receiving error, warning, and general notice messages from libpq. Where previously you would derive your own class from pqxx::errorhandler
and implement your own virtual function call operator, you can now just register a lambda using a new function, pqxx::connection::set_notice_handler()
. Or a callable object. Or a plain old function. Anything that goes in a std::function
. As you can see they're called notice handlers now.
This is a whole new mechanism. It's less powerful in one way: it does not support chains of handlers. You can install at most one notice handler on a connection. If you install a new one, it overwrites the old one. I figured if you really want a chain of handlers, that's easy enough to write in your own handler function — but I don't think many people will care.
Lifetimes work differently now. That was a bit of a mess before: inside libpq, any result object gets a copy of whatever handler is installed on the connection, in case any operation on the result object generates a notice — because you might destroy the connection before you were done with the result. So it was easy to get into situations where the mechanism would hand a notice to a connection object that no longer existed. To some degree this has actually improved even for the old mechanism. But the new mechanism simply keeps your handler alive for as long as the connection or any of its results exist.
Finally, unlike the old errorhandler
, the new "notice handlers" do not inhibit moving a connection object (e.g. with std::move()
). In libpqxx 9.0 I expect pqxx::connection
to be fully movable.
You will be able to continue using the old mechanism in libpqxx 7.x and 8.x, but it is marked as deprecated. The mechanisms live side by side, independently from each other. If you use errorhandler
, please switch to the new mechanism — and make your own code cleaner and more modern in the process!
Brand-new LISTEN/NOTIFY API
Similar to error handlers, I have replaced notification_receiver
with a more modern, simpler, and lambda-friendly API. Just register your callback using pqxx::connection::listen()
. (Internally I refer to these callbacks as notification handlers.) As you might expect in a modern API, your callback can be anything that fits in a std::function
: a lambda, a function, or a callable object.
Here too you lose a little bit of flexibility: a connection can only have one notification handler per channel at any given time. That means that it's easy to remove or replace handlers after you're done with them — just register a new one to replace it. An empty std::function
disables listening on a channel.
You can only register a handler while no transaction is active (not even a pqxx::nontransaction
). This keeps the internal administration much simpler than it would otherwise be. Conversely there's a new wrapper function to notify a channel, but this lives in the transaction class. (May seem a little strange but it reflects the slightly mind-bending implications of message buses in a transactional environment.)
Exception behaviour is now well-defined, and as with the new notice handlers, the new handlers do not inhibit moving a connection. I hope you'll find the new mechanism easier to work with. Your handler gets a bit more information, including a reference to the connection object, but it can safely ignore any items in which it is not interested.
The old mechanism will still be around in the 8.x release cycle (as well as the rest of 7.x of course), and live side by side with the old one. That means you can register both an old-style notification_receiver
and a new-style notification handler, and get notified twice. However I hope that you will switch to the new style soon.
Phew, that's it! After this there may be 7.10.x bug-fix releases, but I hope to focus on 8.0 which will require C++20 or better and make better use of concepts.