Skip to content

Advance the pin to libpg_query 18.0.0 (PostgreSQL 18.4) - #10

Merged
kyleconroy merged 12 commits into
mainfrom
claude/lib-query-go-v18-upgrade-682woj
Aug 17, 2026
Merged

Advance the pin to libpg_query 18.0.0 (PostgreSQL 18.4)#10
kyleconroy merged 12 commits into
mainfrom
claude/lib-query-go-v18-upgrade-682woj

Conversation

@kyleconroy

Copy link
Copy Markdown
Contributor

Advances the pin from libpg_query 17-6.2.2 (PostgreSQL 17.7) to libpg_query 18.0.0 (PostgreSQL 18.4), following PLAN.md § Regeneration. ParseResult.Version is now 180004. Every corpus suite is green: 0 todos across 2,130 files / 330,817 cases, and go test ./... (including -race over the corpus) passes.

The oracle

pg_query_go has no PG 18 release yet (latest tag is v6.2.2 on libpg_query 17-6.2.2), so the cgo oracle now builds pg_query_go's wrapper against the 18.0.0 sources locally: oracle/update-pg-query-go.sh reproduces pg_query_go's own make update_source against the pinned tag into a gitignored tree that oracle/go.mod picks up via a replace directive. The weekly regenerate workflow installs protoc and runs the script before verifying golden reproducibility. When pg_query_go cuts a PG 18 release, the script and replace can be dropped for a normal module requirement.

Re-vendored inputs

  • srcdata/*.json + pg_query.proto verbatim from the 18.0.0 tag; internal/reference re-derived from the postgresql-18.4 tarball with the tag's patches (patch 04 now also touches gram.y).
  • New generator inputs: pg_query_pg_type.c + pg_type_d.h (the mini pg_type catalog libpg_query 18's PL/pgSQL mocks serve); cmd/generate -plpgsql emits the 193-entry builtin type table.
  • cmd/regenerate learned to strip // line comments from fingerprint.json (the 18.0.0 file carries one).

Grammar and behavior changes ported

  • RETURNING WITH (OLD/NEW AS alias): returningList → the new ReturningClause node on all four DML statements (parser, deparser, walkers, fingerprint). Upstream did not extend pg_query_raw_tree_walker_supports to it, so the summary walks visit but never descend into RETURNING.
  • Statement locations: stmt_location now points at each statement's first token, and PG 18's rewritten YYLLOC_DEFAULT scans for the first valid RHS location — giving ConstraintAttributeSpec, insertSelectOptions, processCASbits, and parsePartitionStrategy real error cursors.
  • Constraints: ENFORCED/NOT ENFORCED (CAS bits + processCASbits signature), table-level NOT NULL colname, temporal keys (WITHOUT OVERLAPS, FK PERIOD columns), GENERATED ... VIRTUAL (the new default), and the ATAlterConstraint node for ALTER TABLE ALTER CONSTRAINT (incl. the INHERIT variant).
  • Fingerprints: 18.0.0's rewritten RangeVar jumbling (aliases contribute and suppress relnames in DML contexts, schema names dropped there), new skip fields (list_start/list_end, rexpr list bounds, jumble_args). The proto3-unrepresentable COMMENT ... IS '' vs IS NULL distinction is preserved by tracking present-but-empty strings at parse time and threading them into the fingerprint and summary-truncation walks.
  • PL/pgSQL (the largest piece): libpg_query 18 replaced its mocks with the real compile path — forged pg_proc tuple, real interpret_function_parameter_list/compute_return_type/do_compile, and a builtin pg_type catalog. Types resolve to canonical names (intint4), unknown public-namespace types become RECORD, trigger functions get new/old records plus the ten tg_* promise variables (serialized as bare {}), RETURN simple_var captures a varno, and the real SETOF/VOID/OUT-parameter RETURN errors apply. The core parser gained RAW_PARSE_TYPE_NAME mode for typeStringToTypeName.
  • Smaller items: VACUUM/ANALYZE ONLY (vacuum_relation → relation_expr), ALTER DEFAULT PRIVILEGES ... ON LARGE OBJECTS, VariableSetStmt location/jumble_args fields (and the deparser keying its TIME ZONE / XML OPTION forms off them), array-literal list_start/list_end, scalar IN's rexpr list bounds (in_expr was inlined into a_expr), the JSON emitter omitting all empty strings (matching 18's WRITE_STRING_FIELD), FunctionParameter.location, JOIN_RIGHT_SEMI, and COPY (freeze) staying in WITH (...) form.

Corpus

Regenerated from the 18.0.0 oracle: 2,130 files / 330,817 cases (~113 MB). The 17→18 diff put 187,735 cases back on the todo list; all were driven to zero through the next-test/-check-parse loop. The ported pg_query_go compat literals (parse_test.go) were updated to the 18 shapes, verified against the oracle.

Still open from milestone 12 (unchanged by this PR): difftest mutation fuzzing as a scheduled job and the wasilibs comparison.

🤖 Generated with Claude Code

https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip


Generated by Claude Code

claude added 12 commits August 17, 2026 14:05
No pg_query_go release ships libpg_query 18 yet, so the oracle module now
builds against a locally generated tree: pg_query_go's Go wrapper (main,
pinned commit) with its vendored C sources replaced by libpg_query 18.0.0,
reproducing pg_query_go's own 'make update_source'. The tree is populated
by oracle/update-pg-query-go.sh (gitignored) and wired in with a replace
directive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
…eSQL 18.4)

srcdata/*.json and pg_query.proto verbatim from the 18.0.0 tag;
internal/reference re-derived from the postgresql-18.4 tarball with the
tag's patches 01/03/04/09 applied (patch 04 now also touches gram.y —
the comment-token declarations moved there); kwlist.h, pl_*_kwlist.h,
plerrcodes.h copied from the tag's extracted includes. kwlist.h grows to
494 keywords.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
cmd/generate over the 18.0.0 inputs: ast/pg_query.pb.go, aliases.go,
keyword tables (494 keywords; RECHECK is gone), plpgsql tables.

Ports for the PG 18 node changes, from the pinned gram.y /
postgres_deparse.c / nodeFuncs.c:
- returningList becomes ReturningClause on the four DML statements, with
  the new RETURNING WITH (OLD/NEW AS alias) options (gram.y
  returning_clause/returning_with_clause/returning_option); parser,
  deparser (deparseReturningClause), rawwalk and normalize walkers
  updated, ReturningOption walked as a primitive node.
- AT_CheckNotNull no longer exists (removed no-op deparse arm).
- opclass_item no longer accepts RECHECK.
- IntoClause.viewQuery is typed Query in the PG 18 proto; the walkers
  treat a non-NULL value as unknown (C default arm), as before it was
  always NULL in raw trees.
- ParseResult.Version is 180004; ported test literals updated to match
  (verified against the PG 18 oracle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
cmd/regenerate over the 18.0.0 corpora (postgres_regress and friends at
PostgreSQL 18.4): 2,130 files / 330,817 cases across the eleven suites.
Cases whose input+expectation are unchanged keep their passing status;
the 17→18 diff (187,735 cases, dominated by the version field in every
parse golden) returns to the todo list to be driven down.

cmd/regenerate learns to strip // line comments from
pg_query_go/testdata/fingerprint.json — the 18.0.0 file carries a
comment, which encoding/json rejects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
…ates

- stmtmulti now sets each RawStmt's stmt_location to its own first
  token's position (makeRawStmt($3, @3)); the PL/pgSQL raw-parse modes
  follow (makeRawStmt($2, @2)).
- VariableSetStmt gains location and jumble_args per the 18.4 grammar:
  generic SET/RESET, the SQL-standard special syntaxes, ALTER SYSTEM
  SET/RESET, and TRANSACTION SNAPSHOT all ported with their exact @n or
  -1 values.
- Fingerprint walk follows 18.0.0's generator: list_start/list_end and
  A_Expr rexpr list bounds are no longer jumbled, jumble_args is
  ignored, and RangeVar gets the hand-written body that mirrors
  PostgreSQL's post-analysis query jumble (alias contributes and
  suppresses relname in DML contexts, schemaname dropped there).
- Version constant in the internal parser is 180004 as well.

Todo count: 51,951 → 5,664.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
…poral keys, ATAlterConstraint

- ConstraintAttributeSpec/Elem gain ENFORCED / NOT ENFORCED (CAS bits
  0x40/0x80) with the conflicting-properties check; processCASbits takes
  the is_enforced output (defaulting true, NOT ENFORCED also marks the
  constraint not valid) and its callers pass the 18.4 argument sets.
- Column constraints: NOT NULL takes opt_no_inherit and records
  is_enforced/initially_valid; CHECK and REFERENCES record is_enforced;
  GENERATED ... AS (expr) takes opt_virtual_or_stored (VIRTUAL default)
  into generated_kind.
- Table constraints: new NOT NULL ColId ConstraintAttributeSpec
  production (NOT joins the constraint dispatch sets); UNIQUE/PRIMARY
  KEY accept WITHOUT OVERLAPS; FOREIGN KEY accepts PERIOD columns on
  both sides (fk_with_period/pk_with_period), disambiguated one token
  after PERIOD exactly as the LALR tables do.
- Domain constraints follow the new processCASbits shapes (NOT NULL no
  longer supports NO INHERIT; CHECK records is_enforced).
- ALTER TABLE ALTER CONSTRAINT builds the new ATAlterConstraint node
  (alter_enforceability/deferrability/inheritability, the INHERIT
  variant, and the NOT VALID error); deparser gains
  deparseATAlterConstraint.
- FunctionParameter records location = @1 (func_arg, table_func_column).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
…string JSON omission

- makeAArrayExpr records list_start/list_end; scalar IN/NOT IN records
  rexpr_list_start/rexpr_list_end (in_expr is inlined into a_expr at 18).
- SelectLimit carries offsetLoc/countLoc/optionLoc and
  insertSelectOptions reports its errors at them.
- ConstraintAttributeSpec's location is its first element's (PG 18's
  YYLLOC_DEFAULT scans for the first valid RHS location), so
  processCASbits errors carry cursors now.
- vacuum_relation uses relation_expr: VACUUM/ANALYZE ONLY tbl (and
  trailing *) parse, with inh set accordingly.
- opt_target_list treats RETURNING as a follow token (INSERT ... SELECT
  RETURNING with an empty target list).
- unrecognized JSON encoding reports parser_errposition(@4).
- The JSON emitter omits empty strings entirely, as 18.0.0's
  WRITE_STRING_FIELD does (alwaysEmitString special cases removed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
The C fingerprint sees the original tree, where COMMENT/SECURITY LABEL
... IS '' is a non-NULL empty string and IS NULL is NULL — a distinction
proto3 drops. Since Fingerprint re-parses its input, the parser now
records present-but-empty strings (ParseTracked) and the walk emits the
field name for those nodes (TreeWithEmpties).

parsePartitionStrategy reports its cursor at the strategy token (its
PG 18 signature gained the location).

Parse, scan, normalize, normalize_utility, fingerprint, and both split
suites are green again; deparse, summary, and plpgsql remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
- deparseConstraint takes a context: the domain VALUE key elides only in
  ALTER DOMAIN, NOT NULL/NULL table constraints print bare column names,
  UNIQUE/PRIMARY KEY print WITHOUT OVERLAPS, FK sides print PERIOD
  columns (deparseColumnListWithPeriod), GENERATED prints
  STORED/VIRTUAL by generated_kind, ENFORCED/NOT ENFORCED constraint
  attributes and the FK/CHECK NOT ENFORCED trailer print.
- VariableSetStmt deparse keys the special TIME ZONE / XML OPTION /
  TIME ZONE DEFAULT forms off jumble_args (isSetTimeZoneInterval is
  gone upstream).
- ALTER DEFAULT PRIVILEGES ... ON LARGE OBJECTS parses and deparses.
- JOIN_RIGHT_SEMI joins the planner-only jointype arm; A_Indirection
  parenthesizes an A_ArrayExpr argument; DETACH PARTITION FINALIZE
  spacing fix; COPY (freeze) with no argument stays in WITH form.
- The summary truncation deparse receives the parser's
  present-but-empty string set so COMMENT/SECURITY LABEL ... IS ''
  round-trips (the C summary deparses the original tree); rawwalk keeps
  ReturningClause out of its switch — 18.0.0 did not extend
  pg_query_raw_tree_walker_supports, so summary walks must not descend
  into RETURNING.

Only the plpgsql suite (298 todos) remains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
libpg_query 18.0.0 rebuilt its PL/pgSQL support around the real compile
path: plpgsql_compile_callback runs against a forged pg_proc tuple, with
a mini pg_type catalog served by mocked syscache lookups. Ported:

- internal/reference vendors pg_query_pg_type.c + pg_type_d.h;
  cmd/generate -plpgsql emits the builtinTypes table (193 entries).
- Real parse_datatype: typeStringToTypeName via the core parser's new
  RAW_PARSE_TYPE_NAME mode (parse.ParseTypeName), the mocked
  LookupTypeName pipeline (search path [pg_catalog, public]; unknown
  public-namespace names resolve to RECORD), and build_datatype with
  canonical catalog typenames (int -> int4, bool, ...). %TYPE/%ROWTYPE
  stubs keep the reference text; array decoration uses the real
  plpgsql_build_datatype_arrayof (whose syscache miss on a stub's
  InvalidOid reproduces the mock's 'Not implemented' error).
- compileCreateFunctionStmt follows pg_query_create_function +
  do_compile: interpret_function_parameter_list (typed, moded, VARIADIC
  must-be-array via the always-empty mocked typelem), compute_return_type,
  OUT-parameter row building, polymorphic resolution in validator mode,
  pseudo-type rejections, trigger/event-trigger datums (new/old records
  plus the tg_* promise variables, serialized as bare {}), and the real
  add_dummy_return condition.
- make_return_stmt is the real one: SETOF/VOID/OUT-parameter errors, and
  RETURN of a simple var/rec/row datum captures retvarno (not expr).
- Records honor CONSTANT; format_type_be renders SQL-standard spellings
  with element[] only for base-type arrays.
- Parameter-list and return-type errors escape before the compile error
  context is installed, so they carry no 'near line' context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
- CLAUDE.md/PLAN.md/README.md and the per-file attribution comments now
  name libpg_query 18.0.0 (PostgreSQL 18.4); PLAN.md § pin records the
  2026-08-17 advance and the local-oracle arrangement.
- The regenerate workflow clones the 18.0.0 tag, installs protoc, and
  builds the oracle's pg_query_go dependency via
  oracle/update-pg-query-go.sh before regenerating.
- internal/reference/README.md documents the two new generator inputs
  (pg_query_pg_type.c, pg_type_d.h).
- The ported pg_query_go compat literals updated to the 18 shapes
  (rexpr list bounds on scalar IN, canonical PL/pgSQL type names,
  RETURN-variable statements), verified against the PG 18 oracle.

go build/vet clean; go test ./... green; corpus todo lists empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
Both jobs of the previous run died in action download (503/429 from
codeload.github.com) before any step ran.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxEHH1UHZXsnhGpYQ9Fip
@kyleconroy
kyleconroy merged commit 6aaeaa7 into main Aug 17, 2026
2 checks passed
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.

2 participants