Skip to content

test: round-trip the PostgreSQL regression corpus with a known-failures ratchet - #366

Open
pyramation wants to merge 2 commits into
mainfrom
devin/1792899233-regress-corpus
Open

pyramation wants to merge 2 commits into
mainfrom
devin/1792899233-regress-corpus

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

Summary

Runs every statement of the SQL that libpg_query vendors from PostgreSQL through parse → deparse → parse, inside the existing pgsql-deparser and plpgsql-deparser test jobs. There is no new CI job. A deparser change that breaks any of the 38k statements now fails CI. Today only the hand-picked kitchen-sink statements are checked.

Corpus. __fixtures__/regress/statements.json.gz (714 KB) holds 38,269 unique parseable statements from 400 files:

  • test/sql/postgres_regress
  • test/sql/plpgsql_regress
  • test/sql/deparse*
  • test/plpgsql_samples.sql

They are taken from libpg_query at b0f5f33, the x-publish.libpgQueryCommit of the pinned libpg-query@18.2.1. Keys match generated.json (postgres_regress/arrays-12.sql). To regenerate:

cd packages/deparser && npm run fixtures:regress -- /path/to/libpg_query

make-regress-fixtures.ts splits each file on top-level ; tokens from scanSync, so quoting and comments are handled and CREATE RULE (…;…) / BEGIN ATOMIC … END stay whole. It then drops duplicate and unparseable statements (psql meta-commands, intentional errors).

Ratchet.

  • __tests__/regress/regress-corpus.test.ts (deparser) has one test per source file and calls TestUtils.expectAstMatch, both pretty and non-pretty. It compares the failing keys with known-failures.deparser.json: 594 statements in 47 files.
  • plpgsql-deparser/__tests__/regress-corpus.test.ts does the same for PL/pgSQL functions and DO blocks against known-failures.plpgsql-deparser.json (72 statements).

A new failure fails the test, and so does a listed statement that now passes, which forces the list to shrink as bugs are fixed. To refresh after a deliberate change:

UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress

The workflow is documented in .agents/skills/testing-fixtures/SKILL.md.

Cost: ~20 s in the deparser job and ~3 s in the plpgsql-deparser job. Fixture size is ~0.75 MB in total.

Evidence

  • Both suites pass locally: 400/400 and 1/1.
  • With a deliberate deparser break (TruncateStmt emitting CONTINUE IDENTITY for RESTART IDENTITY): ✕ postgres_regress/truncate.sql, Tests: 1 failed, 399 passed.
  • The known failures are existing deparser gaps, not new ones. In a side-by-side run, pgsql-deparser 18.4.0 and 18.4.1 failed exactly the same statements. Examples:
    • JsonFuncExpr inside a RangeFunction (JSON_TABLE / JSON_QUERY in FROM)
    • ALTER OPERATOR FAMILY … ADD FUNCTION n (types) f(args) argument order
    • array subscript / slice forms in arrays.sql

Link to Devin session: https://app.devin.ai/sessions/91c28df261ac4b318f39d1c7242d008c
Open in Devin Desktop: https://app.devin.ai/desktop/session/91c28df261ac4b318f39d1c7242d008c?variant=devin
Requested by: @pyramation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review complete. 🟡 4 medium

💬 Inline comments (4)

🧹 Nitpicks (1) — 🟢 1 low
  • 🟢 Stale known-failure entries never flagged by deparser ratchet (regress-corpus.test.ts:45) — The ratchet only iterates corpus files (it.each([...byFile.keys()]) at packages/deparser/tests/regress/regress-corpus.test.ts:45), so a known-failures.deparser.json entry whose source file was removed or renamed is silently ignored: the list keeps growing stale without the test failing, which weakens the ratchet the SKILL.md describes.

The change introduces a corpus pipeline built from PostgreSQL's regression test SQL: a new make-regress-fixtures.ts script clones libpg-query, splits and parses the deparse-depesz corpus into a gzipped statements.json.gz, and both deparser and plpgsql-deparser gain corpus tests that compare actual failures against committed known-failure ratchet files, with an UPDATE_KNOWN_FAILURES mode to refresh them.

Files Change
packages/deparser/scripts/make-regress-fixtures.ts New generator: clones libpg-query at a pinned commit, scanner-based statement splitting with regex fallback, parses and dedups statements, writes the gzipped corpus.
packages/deparser/__tests__/regress/regress-corpus.test.ts, packages/plpgsql-deparser/__tests__/regress-corpus.test.ts New corpus tests with per-file known-failure ratchets and an update mode that rewrites the ratchet JSON.
__fixtures__/regress/known-failures.*.json Committed ratchet lists of currently failing corpus entries for both deparsers.
packages/deparser/package.json, .agents/skills/testing-fixtures/SKILL.md Adds the fixtures:regress script and documents the corpus workflow.

Reviewed commit: b05c71d

@tenki-reviewer tenki-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds regression-corpus fixture generation and ratcheted known-failure tests for the deparser and plpgsql-deparser, with a handful of tooling robustness gaps to address.

Key findings

Comment thread packages/deparser/__tests__/regress/regress-corpus.test.ts
Comment thread packages/deparser/scripts/make-regress-fixtures.ts
Comment on lines +64 to +70
} catch {
// e.g. strings.sql has intentionally invalid escapes the scanner rejects
pieces = sql.split(/;[ \t]*\r?\n/);
}
return pieces
.map((s) => s.replace(/^(\s*--[^\n]*\n|\s+)+/, '').trim())
.filter((s) => s.length > 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 bug · medium

Fallback regex split corrupts dollar-quoted strings

When scanSync rejects a file (e.g. strings.sql with intentionally invalid escapes), splitStatements falls back to sql.split(/;[ \t]*\r? /) (make-regress-fixtures.ts:66), which is quote- and dollar-quote-blind. Any dollar-quoted body ($$...; ...$$, $_$...$_$) or a string literal containing ; is cut in half; each fragment then fails parseSync and is silently counted as 'skipped' (make-regress-fixtures.ts:92), so whole functions vanish from the corpus with no warning. The fragments also shift the per-file index i, so subsequent keys contain different statements than the generator's numbering implies.

📋 Prompt for AI Agents

In packages/deparser/scripts/make-regress-fixtures.ts, in splitStatements (lines 60-71), make the catch-block fallback dollar-quote aware: before sql.split(/;[ \t]*\r?\n/), replace every dollar-quoted span (regex /($[A-Za-z_]$)[\s\S]?\1/g) with a placeholder of identical byte length, split, then restore — or record the file as skipped with a log line instead of using the regex fallback. Otherwise fragments of a split dollar-quoted body fail parseSync and are silently dropped from the regress corpus, hiding entire functions from the fixture set.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback only runs for files the scanner rejects outright (just strings.sql at this pin). A piece is kept only if it parses as a full statement on its own, so a dollar-quoted body split in the middle gets dropped and never stored corrupted. Left as is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback sql.split(/;[ \t]*\r?\n/) is still present unchanged in the current diff (lines 63-67 of the catch block). The argument that 'a split fragment gets dropped and never stored corrupted' does not address the finding: the problem is that whole statements (e.g. entire PL/pgSQL functions with dollar-quoted bodies) are silently lost from the fixture corpus, and the index i is shifted for all subsequent statements in that file, corrupting their keys. The fix requires either making the fallback dollar-quote-aware (placeholder-swap approach) or logging and skipping the file entirely — neither is present here.

*/
import { execFileSync } from 'child_process';
import * as fs from 'fs';
import { sync as globSync } from 'glob';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 bug · medium

glob undeclared in deparser package

packages/deparser/scripts/make-regress-fixtures.ts:14 imports { sync as globSync } from 'glob', but glob is only declared in the root package.json and packages/pgsql-cli/package.json — not in packages/deparser/package.json. Under pnpm's default isolated node_modules layout, the new fixtures:regress script fails at import time with "Cannot find module 'glob'", so the corpus cannot be regenerated from a fresh clone.

📋 Prompt for AI Agents

In packages/deparser/package.json, add a devDependencies entry for glob matching the version already used at the repo root (^11.0.0), because packages/deparser/scripts/make-regress-fixtures.ts line 14 imports globSync from glob and pnpm will not resolve an undeclared transitive dependency from a package's own scripts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glob comes from the root package.json (^11.0.0) and is hoisted. The existing make-fixtures.ts, make-fixtures-ast.ts and make-fixtures-sql.ts scripts import it the same way, so I kept the convention.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The candidate diff for 'packages/deparser/package.json' only shows the addition of the 'fixtures:regress' script entry — it does not include a 'glob' entry under 'devDependencies'. The fix described (adding 'glob': '^11.0.0' to devDependencies) is not visible in any of the evidence provided. The user's claim that hoisting from the root covers this is unreliable under pnpm's isolated node_modules layout, and the other scripts cited as precedent have the same latent issue. The original finding remains unresolved.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Folded into #371

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