BUG #19632: RULE rewriting crashes with XX000 when RETURNING old/new references a system column

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: hackerzheng666(at)gmail(dot)com
Subject: BUG #19632: RULE rewriting crashes with XX000 when RETURNING old/new references a system column
Date: 2026-08-19 03:52:11
Message-ID: 19632-9155d9baec763c8c@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19632
Logged by: Zheng Hacker
Email address: hackerzheng666(at)gmail(dot)com
PostgreSQL version: 19beta3
Operating system: Linux x86_64
Description:

PostgreSQL version: 20devel (commit bdbf662, 2026-08-19)
OS: Linux x86_64

When a DML query with RETURNING old.<system_column> or
RETURNING new.<system_column> (PG 20 new syntax) is rewritten
through a RULE, the query rewriter cannot find replacement
targetlist entries for system columns, hitting elog(ERROR) in
rewriteManip.c.

Reproducer:

CREATE TABLE t (a int);
INSERT INTO t VALUES (1);
CREATE RULE t_del AS ON DELETE TO t
DO INSTEAD UPDATE t SET a = -1 WHERE a = OLD.a RETURNING *;

-- All of these crash with XX000:
DELETE FROM t WHERE a = 1 RETURNING old.tableoid;
-- ERROR: XX000: could not find replacement targetlist entry for attno -6
-- LOCATION: ReplaceVarFromTargetList, rewriteManip.c:1884

DELETE FROM t WHERE a = 1 RETURNING old.ctid; -- attno -1
DELETE FROM t WHERE a = 1 RETURNING new.tableoid; -- attno -6

-- Without the RULE, the same RETURNING clause works correctly:
DROP RULE t_del ON t;
DELETE FROM t WHERE a = 1 RETURNING old.tableoid; -- works fine

Root cause: src/backend/rewrite/rewriteManip.c, function
ReplaceVarFromTargetList (line 1884). When the rewriter processes
the RULE's action to replace Vars, it iterates over the action's
target list looking for an entry with matching resno. System
columns have negative attribute numbers (e.g. tableoid = -6), but
the RULE's RETURNING target list only contains user-defined columns
(with positive resnos), so no match is found.

The PG 20 old/new RETURNING syntax (var->varreturningtype !=
VAR_RETURNING_DEFAULT) is handled AFTER the targetlist entry lookup
succeeds (lines 1894-1910), so the code never reaches that logic
for system columns.

Affects all system columns (tableoid, ctid, xmin, cmin, xmax)
through any RULE that uses DO INSTEAD.

Found by automated SQL fuzzing.
Credit: Zheng Wang, Yanjie Zhao, Yiyang Liu

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Michael Paquier 2026-08-19 05:22:10 Re: BUG #19627: 32,768 trigger arguments wrap `tgnargs` and are silently lost at runtime
Previous Message PG Bug reporting form 2026-08-19 03:51:52 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000