[RFC] Property-based testing for Postgres

From: Greg Burd <greg(at)burd(dot)me>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Cc: postgres(at)jeltef(dot)nl, andrew(at)dunslane(dot)net
Subject: [RFC] Property-based testing for Postgres
Date: 2026-11-02 21:28:10
Message-ID: auj7NW8APDaUVJCG@floki
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hello,

This proposes an opt-in, default-off, property-based testing (PBT) [1]
framework under src/test/prop/, gated on an external PBT engine
(libhegel) the way TAP tests are gated on Perl and IPC::Run. I want to
gauge interest before polishing and then proposing it for commit.

Why bring it up at all? It finds bugs and then reduces the reproduction
to the minimal set of steps required to trigger them. It does this by
exploring the various properties that could be acceptable and comparing
them against some "oracle", like comparing qsort() vs my_sort() across
all list sizes and various contents each time checking that the two
functions produce the same outcome.

I was excited to find that Hegel[2] is a credible open source
implementation of this testing technique. Credible in my book means
fast, functional, and efficient when finding the minimal reproduction.

Also, I had used PBT back in the early 2000s with Erlang when it first
arrived on the scene from Quviq called QuickCheck and it was a huge
help.

So I took it for a spin and it did indeed find real new bugs as well as
help me to classify commit-fest items listed as "ready for commit" into
"really ready" or "there's still an identifiable issues to work out." I
can think of other ways to use this, but I'll stick to the basics in
this intro email.

Rather than argue the value of PBT in the abstract, here is what this
method turned up on recent master:

- A SERIALIZABLE write-skew that commits after a savepoint swallows the
serialization failure (SSI doom-before-error). Reproduced fail-on-master,
pass-on-fix:
https://www.postgresql.org/message-id/165342c0-0c75-461e-b334-b997639ad48d@aphyr.com

- IS NOT DISTINCT FROM join selectivity off by ~4900x with NULLs on both
sides (BUG #17545):
https://www.postgresql.org/message-id/17545-a0ca4de888953169@postgresql.org

- var_eq_const undercounting by stopping at the first matching MCV entry
under a nondeterministic collation:
https://www.postgresql.org/message-id/tencent_A15A9D89A86F2E4B086ABA462578B9B64307@qq.com

I posted the independent reproductions into those threads; the finds are
the authors', the corroboration is from this method. These are just a
few examples.

At its core, the PBT engine generates many inputs, and on failure
shrinks to a minimal counterexample and prints a seed that replays it.

A property is an ordinary C function that draws inputs and asserts an
invariant; a round-trip, an algebraic law, or a differential check that
two ways of computing the same answer agree.

What's in this RFC (the two attached patches) is deliberately small:

0001: build plumbing. --with-hegel for configure, -Dprop_tests for meson.
Default off. When enabled, a missing libhegel is a hard error (so the
opt-in cannot silently do nothing); when not enabled, nothing in the tree
changes and CI is unaffected.

0002: the harness (pg_prop.c/.h, a thin binding to libhegel's stable C ABI,
a TAP driver) plus a curated set of six single-table areas whose oracles
are round-trips and algebraic laws: base64, bytea, uuid, numeric,
interval I/O, jsonb round-trip (37 properties). These replay
deterministically from a printed seed, which is the property a buildfarm
test needs.

I have deliberately left out the larger parts I have locally, and I've
kept the first set of example property tests very basic as a place to
start not as examples of the best ways to apply this type of testing.
Testing the properties of UUIDs, bytea, etc. are not very exciting or
likely to find bugs at this stage of the game. They are meant as a
starting point for a discussion around adding PBT as a new category in
core.

I have explored much more complicated/useful PBTs such as a multi-table
query generator with a differential oracle (the thing that found the
selectivity bugs above), and a few hundred more scalar properties. This
is the generator where the interesting finds I pointed to earlier come
from, but its deep multi-table cases are not yet reproducible to the
standard a committed buildfarm test demands, so not in the proposed
patches/examples just yet.

I would rather present a small, sound, reproducible core and grow it
from there if there is interest.

So, is there appetite for a PBT framework in the tree on these terms? If
this is a non-starter, that is useful to know now. If it's worth
pursuing, I'd like to hear from you.

best,

-greg

[1] https://www.cis.upenn.edu/~bcpierce/papers/icse24-pbt-in-practice
https://antithesis.com/docs/resources/property_based_testing/
https://drmaciver.com/notebook/2018/12/etymology-of-property-based-testing/
https://wyounas.github.io//2026/10/26/finding-bugs-in-duckdb-with-property-based-testing/
[2] https://hegel.dev/
https://github.com/hegeldev
https://github.com/hegeldev/hegel-cpp

Attachment Content-Type Size
v1-0001-Add-build-plumbing-for-property-based-tests-src-t.patch text/plain 10.5 KB
v1-0002-Add-property-based-test-harness-and-a-curated-set.patch text/plain 134.9 KB

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Masahiko Sawada 2026-11-02 21:42:44 Re: First draft of PG 19 release notes
Previous Message Andrey Rachitskiy 2026-11-02 21:12:42 Re: inet/cidr: IPv4 prefix length overflows int and passes the range check