github jtv/libpqxx 8.0.0
libpqxx 8.0.0

16 hours ago

libpqxx 8 is ready for you!

Libpqxx 8 is the library's biggest release ever.

It's been a long trek. On the one hand it was a parallel journey to the
adoption of C++20. We're not relying on all the new C++20 features, but
concepts, std::source_location, spans, and ranges are integral parts of the
design. Also I was hit by a health problem that reduced the amount of work I
could do, and often made it hard for me to trust my own judgment. Luckily that
happened after most of the important design work was done, or you would not see
this release today.

On the other hand, I've also had help: community members reported bugs and
submitted patches, some of very high quality. And people helped each other
out, which is awesome to see. A Copilot subscription on GitHub helped with an
extra layer of code reviews. It found a lot of typos in comments, but also
some genuine and sometimes subtle mistakes. (I didn't let it write actual code
though; it gets too many things wrong, some of which I would probably not have
caught in review. Kernighan's Law applies.) Other large language models, in
particular Claude, helped a lot in solving configuration problems.

Perhaps most of all, my wonderful former employer Eonics brought together a
Mastermind group where a bunch of us discuss our hobby projects, learn, and
share questions and ideas. The group often came up with unexpected and helpful
approaches for my problems. If you have a hobby project, I wholeheartedly
recommend looking for a mastermind group near you! And if you speak Dutch,
have a look at the Eonics website. You'll find podcasts,
hack nights, interviews, and much else to make IT work fun.

Thank you

Several people helped. I'm probably missing somebody, but I wanted to thank...

  • @tt4g for helping lots of people with their
    questions, often before I even see them.
  • Klaus Silveira @klaussilveira), our
    first donor-level sponsor.
  • Hendrik M. @Tosenaeus, who also sponsors us.
  • Eonics, CoffeeSprout,
    ComPosiX, and the whole Eonics Mastermind team.
  • Kirit Sælensminde, for discussing features, ideas, and
    C++ language changes.
  • J.H.M. "Ray" Dassen, much missed, who did a lot to help get libpqxx off the
    ground, all the way back.
  • All those who submitted patches, reported problems, or asked for
    improvements.

What's changed?

Just about everything has changed at least a little bit.

349 files changed, 28826 insertions(+), 24686 deletions(-)

Most "standard" code using libpqxx will still work without changes, but often
you'll find that there are now better ways to do things. Functions and classes
that were deprecated three years ago are now gone. Some other ones are now
deprecated, and will be gone a few years from now.

The API has become better at helping you keep your code memory-safe and
verifiable by static code analysis. There are fewer raw, C-style pointers.
At the same time some things have become more efficient through the use of
views, especially string_view, and internal simplifications. Ranges and
spans have made things clearer and simpler. Concepts have made some things
more flexible.

More details follow. See the NEWS file for a more complete list with less
detail, or UPGRADING.md for advice on how to deal with breaking changes.

Things that can break with old code

If you're just using 8.0 as a drop-in replacement for an older version in an
existing application, this may be the only useful section for you.

C++20 is now the minimum. As per the above it's fine if some C++20
features don't work for you yet, but concepts definitely need to work, and
you'll need a bunch of the new C++20 standard library features.

PostgreSQL 11 is now the absolute minimum. You won't be able to connect to
older servers. The exact version is a fairly arbitrary choice. The oldest
version supported upstream is 14, so hopefully 11 is liberal enough.

Exceptions have changed. The whole class hierarchy is different, so if
you do any detailed catching you'll have to review that. On the bright side,
libpqxx exceptions are now all derived from pqxx::failure so you may not need
as many catch blocks as before. Your handler can figure out much of what it
needs by calling the exception's member functions, rather than by knowing its
type.

Rows and fields have changed. The old row and field classes used to
keep a result object alive in memory, even if you destroyed the result
object. Now, most of the time you'll get a row_ref or field_ref which does
not do that. Be careful with lifetimes, but it rarely becomes an issue, and
the new classes are simpler and more efficient.

Result & row iterators have changed. Indexing an iterator now finally does
what the C++ standard says, even though it's probably less intuitive or useful.
If you previously did i[n], you'd now do (*i)[n].

Comparing results, rows, or fields now compares identity, not contents. I
doubt anyone ever used what these == operators did.

pqxx::bytes is now an alias for std::vector<std::byte>, not a
std::basic_string<std::byte>.
It's not clear that the fine print in the
standard ever quite allowed the string definition. Some code will require
small changes at compile time. If you ever used the c_str() member function,
now you'll use data(). More appropriate for binary data.

The string conversion API has changed. This is the extensible API that
converts between C++-side objects and their SQL text formats. If you wrote
your own to support additional types, those should still work; but the newer
API is generally easier, safer, and more efficient if you want to optimise.

A lot of strings no longer have or need a terminating zero. This includes
the string conversion API.

In some situations, a params will require text encoding information. In
those situations, pass your connection, transaction, or encoding group as the
first argument to the params constructor.

No longer supports std::filesystem. Nor does the build try to figure out
whether it needs to link any additional libraries to make it work.

A zview can no longer contain a null pointer. This used to be valid for
empty strings, but it no longer is.

More classes are now final.

The scripts in tools/ have been revised. Shell scripts now have a ".sh"
suffix to their name. Some old tools are gone: rmlo,pqxxthreadsafety,
test_all.py.

The JOHAB encoding is not supported. As far as we can tell, there is no
single standard for how this encoding was supposed to work, and support on the
server side never fully worked anyway. It will be removed from PostgreSQL.

Some deprecated items are now gone:

  • binarystring (use blob instead).
  • connection_base (use just connection, it's the same thing).
  • encrypt_password() (use the equivalents in connection).
  • dynamic_params (use params instead).
  • transaction_base::unesc_raw() (use unesc_bin() instead).
  • transaction_base::quote_raw() (use quote(bytes_view) instead).
  • Some field constructors.
  • Row slicing. Has anyone ever used this?

Querying continues to evolve

Querying data has been growing simpler and more regular for a while now. But
I'll summarise how the 8.0 way of querying differs from the old ways.

Passing statement parameters: You no longer call separate exec(),
exec_params(), or exec_prepared(). Instead, you just call exec(), and
in addition to the query, pass a params object if the statement needs
parameters. If the query is a prepared statement rather than SQL text, you
use its name, wrapped in a pqxx::prepped object:

tx.exec(
    pqxx::prepped{"my_query"},
    pqxx::params{1, 23, "param");

Executing, querying, streaming: These are the three models for executing a
query, and where possible they look similar. "Executing" a query is the
classic exec() function, and it returns a pqxx::result. "Querying" works
very similar, but you pass template arguments to state which types of result
columns you expect, and then you iterate over the return value. "Streaming"
looks a lot like querying but is optimised for large data sets: it's slower for
small numbers of rows but faster for large numbers. (Sadly a streaming query
can not take parameters.)

Expected result sizes: If you want to make sure that a query returns a
specific number of rows, you no longer use exec0(), exec1(), or exec_n();
you just use exec() and then call a member function on the result such as
no_rows(), one_row(), or expected_rows(). There are similar functions
for the number of columns.

Various new features

This is not a complete list! There's more than I can fit in here. You'll
have to explore the documentation or the code to find them all.

Here are some major ones:

Example code. There is now an examples/ directory containing source for
various toy applications. There will be more in the future. Unlike the tests,
these examples are entirely meant for you, to illustrate what you can do with
libpqxx. But unlike the documentation, they get compiled and run along with
the test, so they won't fall out of date.

Source locations & stack traces: A libpqxx exception can now tell you the
associated std::source_location and, if your compiler supports it, a full
std::stacktrace.

Easier SQL arrays: Single-dimensional SQL arrays are now super easy, barely
an inconvenience. You can now pass a std::vector<double> as a statement
parameter and libpqxx will convert it to an SQL array. You can stream a
std::array<std::string> directly out of an SQL array in a query, read a
result field of SQL array type as a std::inplace_vector<int>, and so on.
Multi-dimensional arrays are a bit harder, but not much: the pqxx::array
class does all the parsing for you.

Generic binary data: Where you need to pass binary data to a libpqxx
function, you can now generally pass any contiguous block of std::byte.

Result & row iterators are now proper random access iterators. This may
allow some algorithms to work more efficiently.

Encoding groups. Sometimes when dealing with text, libpqxx needs to know
its encoding. It doesn't need to know the exact encoding, but it does need
enough information to be able to tell, for example, a closing quote in an SQL
string from a byte in a multibyte character that happens to have the exact same
value. There is an enum to describe the different basic encoding schemes, and
it is now part of the public API.

Combine connection strings and connection parameters. You could already
create a connection based on a connection string, or on key/value parameters.
Now you can pass both.

Nicer connection::port_number() to replace connection::port(). The old
function returned a C string, thanks to the underlying C API. The useful
information is "either a number, or nothing." The new function returns
std::optional<int> as you'd expect.

If available, uses C++ Reflection to obtain type names. This replaces a
bunch of string constants that triggered false-positive alerts in some
verification tools such as Valgrind.

Build improvements

The build has also seen some improvement:

Minimum C++ version. If you're building as C++20, you no longer need to
pass an explicit option like -std=c++20 on the compiler command line. Both
the CMake build and the configure build now default to C++20 if nothing is
specified.

Unity builds. This style of build combines multiple C++ translation units
into a single huge C++ file. This enables some cross-module optimisation, but
(under the right circumstances) can also speed up the build. This is now
supported in the CMake build.

More parallelism in configure build. A new, "flattened" hierarchy of
"make" targets reduces unused CPU time while compiling. The tests are now in a
single directory.

Various MSVC fixes. There were some compilation problems, and lots of
warnings, with Microsoft Visual Studio in particular. There was even an error
(apparently due to a compiler bug) in the 2026 edition. Fixed.

Test against ASCII-only databases. The regular libpqxx CI tests run
against databases that support full Unicode. But some users test against
ASCII-only databases, which broke the more advanced tests. The tests will now
skip those checks if the database does not support Unicode.

Handy function to render a std::source_location as text. Called
source_loc(), this produces a more or less standard format that IDEs seem to
parse well, but which humans can also read.

Only one generated config header. When you configured a build, it used to
generate several config-*.h headers in your build tree's include/pqxx/
directory. Now it's only config-compiler.h. And it contains only relevant
macros, prefixed with "PQXX", even in the CMake build.

If you like it

Libpqxx represents decades of FTE work, mostly done in my spare time. If
you would like to support this, please consider sponsoring by a small amount
that you can spare:

This helps me pay for the costs of maintaining and publishing the library, and
if enough people contribute, I'd like to do something nice for those who
contribute their time, insights, and effort by supporting fellow users or
submitting patches.

Don't miss a new libpqxx release

NewReleases is sending notifications on new releases.