Skip to content

Run the differential check in CI, and broaden its corpus - #7

Merged
jdatcmd merged 2 commits into
masterfrom
test/differential-ci-and-corpus
Aug 25, 2026
Merged

Run the differential check in CI, and broaden its corpus#7
jdatcmd merged 2 commits into
masterfrom
test/differential-ci-and-corpus

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Two commits: make the check actually run, then give it more to check.

1. Run it in CI

The differential check landed in #4 with no way to run it automatically, so it only ran when someone remembered to type it. That is bad for any test and worse for this one, because it carries a guarantee that decays silently: it fails when a documented divergence stops happening, which is what keeps LIMITATIONS.md honest.

Wiring it in needed a portability fix first. The runner dropped to the postgres account unconditionally, which is correct in the development container (runs as root, server refuses a root connection) and wrong in CI (runs as an unprivileged user who already owns a superuser role). It now drops privileges only when running as root, with PLX_PGUSER as an override, and takes its psql from PG_CONFIG's bindir instead of a hardcoded path.

Both privilege paths were exercised on PG18.4 before wiring: as root via make differentialcheck, and as the unprivileged postgres account, which is the shape CI runs in.

2. Broaden the corpus

Five new cases, into the areas most likely to hide a defect:

Case What it covers
safediv catching a raised error and returning a fallback
arraysum array iteration: empty, NULL element, NULL array
cmp ==/!= against SQL =/<>, with NULL leaving both unknown
strupper a SQL call reaching upper() from every dialect
strupper_native each language's own idiom

strupper_native is the one worth reading. Written with each language's native uppercase idiom, only plxgo agrees with the reference, because plxgo maps a subset of the Go standard library and no other dialect maps anything. Elsewhere s.upcase, s.toUpperCase(), s.upper() and strtoupper($s) transpile without complaint and then fail when the function is called:

strupper_native('hello') plxruby  -> ERROR 42P01
strupper_native('hello') plxphp   -> ERROR 42883
strupper_native('hello') plxjs    -> ERROR 3F000

That is the design (a dialect gives you its syntax over SQL semantics, not its standard library) and not a defect, so it is recorded as a documented divergence rather than fixed. But it was only implied by the per-dialect notes, and it is exactly what someone reaches for out of habit on day one, so it is now stated plainly in the shared constraints. Being a documented divergence means the check fails if any dialect ever gains the mapping and the documentation stops being true.

Where a case cannot cover a dialect it is declared, not skipped: safediv omits plxgo and plxcobol because neither can express catching at all.

Totals

13 cases over 571 comparisons: 542 agree with the reference, 29 documented, 0 unexplained, 0 build errors.

Verification

PG18.4: 13/13 installcheck, differentialcheck clean, and the check still reports and fails on a defect injected into one of the new cases. CI on this PR is itself the proof that the wiring works.

🤖 Generated with Claude Code

plx and others added 2 commits August 24, 2026 18:05
The check landed with no way to run it automatically, so it only ran when
someone remembered to type it. A test nobody runs decays, and this one carries
a guarantee that decays quietly: it fails when a documented divergence stops
happening, which is what keeps LIMITATIONS.md honest. That is worth nothing if
it is never executed.

Wiring it in needed a portability fix first. The runner dropped to the postgres
account unconditionally, which is right in the development container, where it
runs as root and the server will not accept a root connection, and wrong in CI,
where it runs as an unprivileged user who already owns a superuser role. It now
drops privileges only when running as root, and PLX_PGUSER overrides the role
either way. The psql it invokes comes from PG_CONFIG's bindir rather than a
hardcoded container path, so it follows whichever server the rest of the build
is targeting.

The step runs after installcheck, which is what creates contrib_regression and
leaves the extension installed in it.

Verified both privilege paths against PG18.4 before wiring: as root through
make differentialcheck, and as the unprivileged postgres account, which is the
shape CI runs in. Both report 374 matching and 14 documented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first eight cases covered branching, counting loops, integer division and
remainder, three-valued booleans, interpolation and one query loop. Five more
take it into the areas most likely to hide a defect.

safediv catches a raised error and returns a fallback, which is the whole
BEGIN/EXCEPTION path. plxgo and plxcobol cannot express catching at all, since
Go's panic maps to RAISE with no recover() and COBOL has no handler construct,
so both are declared absent rather than skipped quietly.

arraysum iterates an array, including an empty one, one holding a NULL element,
and a NULL array, which has to raise the way the reference raises rather than
iterating nothing.

cmp exercises == and != against SQL = and <>, where a NULL operand leaves both
comparisons unknown and neither branch is taken.

strupper checks that a SQL call reaches upper() from every dialect and that
NULL stays NULL.

strupper_native is the interesting one. Written with each language's own
uppercase idiom, only plxgo agrees with the reference, because plxgo maps a
subset of the Go standard library and no other dialect maps anything. Elsewhere
s.upcase, s.toUpperCase(), s.upper() and strtoupper($s) transpile without
complaint and then fail when the function is called. That is the design, a
dialect gives you its syntax over SQL semantics rather than its standard
library, but it was only implied by the per-dialect notes and is the kind of
thing someone reaches for out of habit on the first day. It is now stated
plainly in the shared constraints and recorded as a documented divergence, so
the check fails if any dialect ever gains the mapping and the documentation
stops being true.

13 cases over 571 comparisons: 542 agree with the reference, 29 are documented,
none unexplained.

Verified on PostgreSQL 18.4: 13/13 installcheck, differentialcheck clean, and
the check still reports and fails on an injected defect in one of the new
cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jdatcmd
jdatcmd merged commit e75c201 into master Aug 25, 2026
6 checks passed
@jdatcmd
jdatcmd deleted the test/differential-ci-and-corpus branch August 25, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant