Skip to content

postgresql: support OLD and NEW in RETURNING clauses - #4562

Merged
kyleconroy merged 1 commit into
mainfrom
claude/sqlc-issue-4556-qrklbh
Aug 17, 2026
Merged

postgresql: support OLD and NEW in RETURNING clauses#4562
kyleconroy merged 1 commit into
mainfrom
claude/sqlc-issue-4556-qrklbh

Conversation

@kyleconroy

Copy link
Copy Markdown
Collaborator

PostgreSQL 18 allows the RETURNING clause of INSERT, UPDATE and DELETE statements to reference the row before and after modification through the OLD and NEW aliases, optionally renamed with RETURNING WITH (OLD AS ..., NEW AS ...). sqlc rejected such queries with column "..." does not exist because neither alias resolved to a table.

Fixes #4556

Changes

  • Compiler (internal/compiler/returning.go): builds virtual tables for the OLD and NEW aliases from the statement's target table, consulted when computing output columns and expanding star references (RETURNING old.*). The virtual tables are only used for references qualified with an alias name, so unqualified columns cannot become ambiguous and bare RETURNING * still expands once. A source table already known under the alias name shadows the virtual table, matching PostgreSQL.
  • Nullability: columns reached through OLD in an INSERT and through NEW in a DELETE become nullable, since no old row exists on insert (barring ON CONFLICT DO UPDATE) and no new row exists after delete. UPDATE keeps catalog nullability for both aliases.
  • AST and converter: InsertStmt, UpdateStmt and DeleteStmt gained ReturningOldAlias/ReturningNewAlias fields, populated from the parser's ReturningClause options and emitted by the SQL formatter, so renamed aliases like RETURNING WITH (OLD AS o, NEW AS n) o.name, n.name resolve and round-trip through formatting.

Testing

New end-to-end case internal/endtoend/testdata/returning_old_new (pgx/v5 and stdlib) covering OLD/NEW on all three statement types, old.* expansion, and renamed aliases. The case is restricted to the base context because the live test databases run PostgreSQL 16, which lacks this syntax. Full TestReplay/base, TestFormat, unit tests and go vet pass.

The experimental core analyzer path (SQLCEXPERIMENT=coreanalyzer) does not gain this support yet; the test case excludes the opt-in core context accordingly.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4yURXfrJTANespBca2wDE


Generated by Claude Code

PostgreSQL 18 allows the RETURNING clause of INSERT, UPDATE and DELETE
statements to reference the row before and after modification through
the OLD and NEW aliases, optionally renamed with
RETURNING WITH (OLD AS ..., NEW AS ...). sqlc rejected such queries
with 'column does not exist' because neither alias resolved to a table.

Resolve both aliases to virtual copies of the statement's target table
when computing output columns and expanding star references. Columns
reached through OLD in an INSERT and through NEW in a DELETE become
nullable, since no old or new row exists for those statements. A source
table already known under the alias name shadows the virtual table,
matching PostgreSQL. The WITH (...) option list is carried through the
AST so renamed aliases resolve and format correctly.

Fixes #4556

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4yURXfrJTANespBca2wDE
@kyleconroy
kyleconroy merged commit 7bdd11b into main Aug 17, 2026
13 checks passed
@kyleconroy
kyleconroy deleted the claude/sqlc-issue-4556-qrklbh branch August 17, 2026 22:59
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.

postgresql18 supports returning OLD NEW

2 participants