0001-Skip-whole-row-projection-shortcut-for-OLD-NEW-retur.patch
application/octet-stream
Filename: 0001-Skip-whole-row-projection-shortcut-for-OLD-NEW-retur.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v1-0001
Subject: [BUG #19516] Skip whole-row projection shortcut for OLD/NEW returning type
| File | + | − |
|---|---|---|
| src/backend/parser/parse_func.c | 7 | 1 |
| src/test/regress/expected/returning.out | 26 | 0 |
| src/test/regress/sql/returning.sql | 12 | 0 |
From 19a58bbf964690ba3e9fae1207e2f4be0887752a Mon Sep 17 00:00:00 2001
From: Marko Grujic <markoog@gmail.com>
Date: Wed, 10 Jun 2026 11:06:59 +0200
Subject: [PATCH v1] [BUG #19516] Skip whole-row projection shortcut for
OLD/NEW returning type
Otherwise we lose varreturningtype, default to VAR_RETURNING_DEFAULT,
and return the wrong values: (old).col yields the NEW value on
INSERT/UPDATE, and (new).col yields the OLD value on DELETE.
Author: Marko Grujic <markoog@gmail.com>
Bug: #19516
---
src/backend/parser/parse_func.c | 8 +++++++-
src/test/regress/expected/returning.out | 26 +++++++++++++++++++++++++
src/test/regress/sql/returning.sql | 12 ++++++++++++
3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index e07e7911f87..16f0733dbfe 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2050,9 +2050,15 @@ ParseComplexProjection(ParseState *pstate, const char *funcname, Node *first_arg
*
* This case could be handled by expandRecordVariable, but it's more
* efficient to do it this way when possible.
+ *
+ * Skip OLD/NEW whole-row Vars: in a RETURNING list multiple namespace
+ * items share (varno, varlevelsup), so the shortcut would drop the
+ * OLD/NEW marker and conflate both with the default-returning entry.
*/
if (IsA(first_arg, Var) &&
- ((Var *) first_arg)->varattno == InvalidAttrNumber)
+ ((Var *) first_arg)->varattno == InvalidAttrNumber &&
+ ((Var *) first_arg)->varreturningtype != VAR_RETURNING_OLD &&
+ ((Var *) first_arg)->varreturningtype != VAR_RETURNING_NEW)
{
ParseNamespaceItem *nsitem;
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index 196829e94fa..f04509c5753 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -542,6 +542,32 @@ DELETE FROM foo WHERE f1 = 5
foo | (0,7) | 5 | ok | 42 | 100 | | | | | | | 5 | ok | 42 | 100
(1 row)
+-- Parenthesized OLD/NEW matches the unparenthesized dot form
+INSERT INTO foo VALUES (6, 'paren-test', 60, 600);
+UPDATE foo SET f4 = 700 WHERE f1 = 6
+ RETURNING old.f4 AS old_dot, (old).f4 AS old_paren,
+ new.f4 AS new_dot, (new).f4 AS new_paren;
+ old_dot | old_paren | new_dot | new_paren
+---------+-----------+---------+-----------
+ 600 | 600 | 700 | 700
+(1 row)
+
+INSERT INTO foo VALUES (7, 'paren-insert')
+ RETURNING (old).f1 AS old_paren_f1, (old).f2 AS old_paren_f2,
+ (new).f1 AS new_paren_f1, (new).f2 AS new_paren_f2;
+ old_paren_f1 | old_paren_f2 | new_paren_f1 | new_paren_f2
+--------------+--------------+--------------+--------------
+ | | 7 | paren-insert
+(1 row)
+
+DELETE FROM foo WHERE f1 = 7
+ RETURNING (old).f2 AS old_paren_f2, (new).f2 AS new_paren_f2;
+ old_paren_f2 | new_paren_f2
+--------------+--------------
+ paren-insert |
+(1 row)
+
+DELETE FROM foo WHERE f1 = 6;
-- RETURNING OLD and NEW from subquery
EXPLAIN (verbose, costs off)
INSERT INTO foo VALUES (5, 'subquery test')
diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql
index b3c8c5df550..8f271f05e7a 100644
--- a/src/test/regress/sql/returning.sql
+++ b/src/test/regress/sql/returning.sql
@@ -243,6 +243,18 @@ DELETE FROM foo WHERE f1 = 5
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
+-- Parenthesized OLD/NEW matches the unparenthesized dot form
+INSERT INTO foo VALUES (6, 'paren-test', 60, 600);
+UPDATE foo SET f4 = 700 WHERE f1 = 6
+ RETURNING old.f4 AS old_dot, (old).f4 AS old_paren,
+ new.f4 AS new_dot, (new).f4 AS new_paren;
+INSERT INTO foo VALUES (7, 'paren-insert')
+ RETURNING (old).f1 AS old_paren_f1, (old).f2 AS old_paren_f2,
+ (new).f1 AS new_paren_f1, (new).f2 AS new_paren_f2;
+DELETE FROM foo WHERE f1 = 7
+ RETURNING (old).f2 AS old_paren_f2, (new).f2 AS new_paren_f2;
+DELETE FROM foo WHERE f1 = 6;
+
-- RETURNING OLD and NEW from subquery
EXPLAIN (verbose, costs off)
INSERT INTO foo VALUES (5, 'subquery test')
base-commit: e18b0cb7344cb4bd28468f6c0aeeb9b9241d30aa
--
2.39.5 (Apple Git-154)