Liking cljdoc? Tell your friends :D

PostgreSQL regression baseline, measured

Measured 2026-09-27 against PostgreSQL 17's own src/test/regress, with bb pg-regress-with-fixtures (fixtures bootstrapped, one isolated database per run) and the diff taken against PostgreSQL's expected output with its source-excerpt presentation lines (LINE n: and the caret) removed from both sides.

The campaign inventory in campaign.edn answers how much of the schedule has been triaged. This answers the other question: how much of it we actually answer the way PostgreSQL does.

What we measure

pg_regress fails a file on any difference, so a per-file pass rate would read zero and say nothing. Agreement is measured instead: the share of PostgreSQL's own expected lines that appear, in order, in ours. A file at 80% answers four fifths of what PostgreSQL prints, which is what a partially-supported area looks like from a client.

100% means every expected line is present and in order, not that the output is byte-identical -- extra lines of ours do not lower it.

The per-file numbers live in test/integration/postgres-regress/agreement.edn and are enforced by bb pg-regress-gate; see "Keeping it" below.

The numbers

application-facing files179 (245 regression files, 65 out of scope, 1 not measurable)
at 100% of PostgreSQL's expected lines9
≥90%22
≥75%42
≥50%117
median agreement59.5%

Measured again on 2026-09-27 after the routine work (SQL functions, plpgsql, triggers, notices). Against the 150 files common to the 2026-09-21 run, 20 improved and 19 lost ground, with the median flat.

Most of that movement is not real. A file whose output is mostly cascade realigns by a point or two whenever anything ahead of its first divergence changes, in either direction, so per-file agreement is noisy at the few-percent level. That is why the gate has a tolerance, and why a real regression is recognised by being several points, or by a file dropping off 100%.

One of the nineteen was real, and worth the whole exercise: date fell 15 points, and the cause was a silent wrong answer that predated this work. '1997-13-01'::date answered the string 1997-13-01 -- a date with a thirteenth month -- and '1997-04-31' quietly became the 30th. Fixed; date is now 60.9%.

The biggest genuine gains since the first measurement:

file2026-09-212026-09-27
select_distinct_on52%72%
select56%72%
expressions63%77%
plancache58%67%
polymorphism47%54%
drop_if_exists65%70%

Keeping it

bb pg-regress-gate      # measure, and fail if any file lost ground
bb pg-regress-measure   # re-measure and rewrite agreement.edn after a gain

Both run all 179 files and take about ninety minutes, which is why the gate is not in per-PR CI: a ninety-minute job on every push would cost more than it catches, given that the differential fuzzer and the unit suite already run there and catch regressions in minutes. Run it before and after a change that touches a broad surface, and whenever a slice of this document's residual is worked on.

What accounts for the other half

Errors we raise across the corpus, by class:

classerrorsfiles
SQL parse error (JSqlParser rejects the syntax)307293
relation does not exist (mostly a cascade from an earlier failure)255572
function does not exist245292
transaction aborted (a cascade of the above)70733
unsupported statement (MERGE, and others JSqlParser parses but we refuse)56428
CREATE FUNCTION54967
type does not exist47440
partitioning (PARTITION BY, ATTACH PARTITION)44222
column does not exist42649
CREATE TRIGGER28918
DROP FUNCTION24142
CREATE RULE13415

The largest single lever is the parser: 3072 statements never reach the translator. The syntax it rejects, by frequency, is SQL/JSON (JSON_EXISTS, JSON_VALUE, JSON_TABLE), XML (xmlserialize, xmlparse), window-frame EXCLUDE, INSERT … DEFAULT VALUES, interval qualifiers (interval '1' minute to second), and the partitioning grammar.

The second is server-side routines: CREATE FUNCTION / TRIGGER / RULE account for 972 errors across 82 files, and they cascade — a file that cannot create its helper function then fails every statement that calls it.

Two smaller, self-contained ones:

  • Notices. PostgreSQL prints 1352 NOTICE / WARNING / INFO lines across 73 files that we never send, because the wire layer has no NoticeResponse. advisory_lock is at 268 of 276 lines and every remaining difference is a missing WARNING.
  • Missing functions, led by range constructors (numrange, int4range, daterange), to_timestamp / to_date (the formatting.c template engine, already Phase 6), and text search (to_tsquery, websearch_to_tsquery).

Known divergences we accept

  • Row order without ORDER BY. A materialised relation -- a derived table, a CTE, a set operation, a function used as a relation -- returns its rows in the order the store scans them, not the order its body produced. SQL does not promise an order without ORDER BY, PostgreSQL happens to preserve one, and its expected output records what PostgreSQL happened to do. Preserving it would mean carrying an ordinal on every materialised relation and sorting by it, on a path that is otherwise a scan. Measured cost of not doing it: row-order-only differences are 28 lines of the 88,321 missing.

Reproducing

bb pg-regress-with-fixtures advisory_lock       # one file
bb pg-regress-wave inventory                    # the classification, not this

The measurement script and per-file table live with the session that produced them; the table below is the state on the date above.

Per-file agreement

The authoritative copy is test/integration/postgres-regress/agreement.edn, which the gate reads. This table is a snapshot for reading.

fileagreement
bit100%
boolean100%
database100%
delete100%
md5100%
portals_p2100%
reindex_catalog100%
select_having100%
select_implicit99%
numeric_big98%
int298%
numeric97%
advisory_lock97%
enum96%
oid96%
int496%
money95%
varchar95%
int894%
comments94%
mvcc90%
transactions90%
case84%
copyencoding82%
create_aggregate82%
type_sanity82%
collate81%
collate.utf881%
async81%
uuid81%
collate.windows.win125280%
create_operator80%
errors80%
char79%
misc_sanity79%
collate.linux.utf878%
lseg78%
expressions77%
lock77%
sequence76%
time76%
drop_operator74%
copydml73%
without_overlaps73%
create_type73%
line73%
temp73%
select_distinct_on73%
select72%
float872%
prepared_xacts72%
truncate72%
fast_default71%
select_distinct71%
namespace71%
drop_if_exists70%
triggers70%
limit69%
euc_kr69%
jsonb68%
alter_generic68%
alter_table67%
plancache67%
alter_operator67%
create_cast67%
foreign_key67%
compression_pglz66%
strings66%
encoding66%
psql65%
text65%
copy65%
tablesample64%
rowtypes64%
stats_import64%
domain64%
collate.icu.utf864%
merge64%
create_table63%
insert_conflict63%
update63%
for_portion_of62%
json_encoding62%
numa62%
date61%
constraints60%
nls60%
copyselect60%
compression_lz460%
planner_est60%
macaddr60%
graph_table60%
json59%
insert59%
stats_rewrite58%
select_into58%
conversion58%
psql_crosstab58%
portals58%
aggregates57%
arrays57%
identity56%
opr_sanity56%
regex56%
prepare56%
sqljson_jsontable55%
polymorphism55%
name54%
psql_pipeline54%
union53%
updatable_views53%
misc52%
window52%
float452%
tstypes51%
create_schema51%
create_procedure51%
create_index51%
path50%
copy250%
with49%
timetz49%
tsearch48%
create_function_sql48%
interval48%
polygon47%
subselect47%
macaddr846%
generated_virtual46%
tsrf46%
pg_ndistinct46%
pg_dependencies46%
inherit46%
random45%
matview45%
returning45%
generated_stored44%
largeobject43%
box43%
misc_functions42%
sqljson_queryfuncs42%
create_view42%
numerology42%
typed_table41%
graph_table_rls41%
create_table_like41%
sqljson41%
xml39%
xid39%
rules39%
create_misc38%
sysviews37%
create_property_graph37%
dbsize36%
rangetypes35%
join35%
jsonpath_encoding34%
circle33%
tsdicts33%
rangefuncs32%
oid832%
pg_lsn31%
inet28%
txid28%
unicode28%
regproc27%
multirangetypes27%
eager_aggregate26%
jsonb_jsonpath26%
timestamptz24%
point22%
jsonpath20%
explain20%
horology20%
timestamp19%
select_views9%
groupingsets8%
geometry8%
xmlmap4%

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close