Add OLD/NEW support to RETURNING in DML queries.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Thu, 16 Jan 2025 14:57:35 +0000 (14:57 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Thu, 16 Jan 2025 14:57:35 +0000 (14:57 +0000)
This allows the RETURNING list of INSERT/UPDATE/DELETE/MERGE queries
to explicitly return old and new values by using the special aliases
"old" and "new", which are automatically added to the query (if not
already defined) while parsing its RETURNING list, allowing things
like:

  RETURNING old.colname, new.colname, ...

  RETURNING old.*, new.*

Additionally, a new syntax is supported, allowing the names "old" and
"new" to be changed to user-supplied alias names, e.g.:

  RETURNING WITH (OLD AS o, NEW AS n) o.colname, n.colname, ...

This is useful when the names "old" and "new" are already defined,
such as inside trigger functions, allowing backwards compatibility to
be maintained -- the interpretation of any existing queries that
happen to already refer to relations called "old" or "new", or use
those as aliases for other relations, is not changed.

For an INSERT, old values will generally be NULL, and for a DELETE,
new values will generally be NULL, but that may change for an INSERT
with an ON CONFLICT ... DO UPDATE clause, or if a query rewrite rule
changes the command type. Therefore, we put no restrictions on the use
of old and new in any DML queries.

Dean Rasheed, reviewed by Jian He and Jeff Davis.

Discussion: https://postgr.es/m/CAEZATCWx0J0-v=Qjc6gXzR=KtsdvAE7Ow=D=mu50AgOe+pvisQ@mail.gmail.com

61 files changed:
contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/dml.sgml
doc/src/sgml/ref/delete.sgml
doc/src/sgml/ref/insert.sgml
doc/src/sgml/ref/merge.sgml
doc/src/sgml/ref/update.sgml
doc/src/sgml/rules.sgml
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/executor/nodeModifyTable.c
src/backend/jit/llvm/llvmjit_expr.c
src/backend/nodes/makefuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/appendinfo.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/paramassign.c
src/backend/optimizer/util/plancat.c
src/backend/optimizer/util/var.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_merge.c
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/executor/execExpr.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/optimizer/optimizer.h
src/include/optimizer/paramassign.h
src/include/parser/analyze.h
src/include/parser/parse_node.h
src/include/parser/parse_relation.h
src/include/rewrite/rewriteManip.h
src/interfaces/ecpg/preproc/parse.pl
src/test/isolation/expected/merge-update.out
src/test/isolation/specs/merge-update.spec
src/test/regress/expected/merge.out
src/test/regress/expected/returning.out
src/test/regress/expected/rules.out
src/test/regress/expected/updatable_views.out
src/test/regress/sql/merge.sql
src/test/regress/sql/returning.sql
src/test/regress/sql/rules.sql
src/test/regress/sql/updatable_views.sql
src/tools/pgindent/typedefs.list

index 64aa12ecc484c9cf729765d44701b50778130297..85252cbdbcf14454a747609e1eb61b516d7a3d77 100644 (file)
@@ -4975,12 +4975,12 @@ INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
 
 INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
 INSERT INTO ft2 (c1,c2,c3)
-  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
-  c1  | c2  | c3  | c4 | c5 | c6 |     c7     | c8 
-------+-----+-----+----+----+----+------------+----
- 1101 | 201 | aaa |    |    |    | ft2        | 
- 1102 | 202 | bbb |    |    |    | ft2        | 
- 1103 | 203 | ccc |    |    |    | ft2        | 
+  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING old, new, old.*, new.*;
old |               new               | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 |  c1  | c2  | c3  | c4 | c5 | c6 |     c7     | c8 
+-----+---------------------------------+----+----+----+----+----+----+----+----+------+-----+-----+----+----+----+------------+----
    | (1101,201,aaa,,,,"ft2       ",) |    |    |    |    |    |    |    |    | 1101 | 201 | aaa |    |    |    | ft2        | 
    | (1102,202,bbb,,,,"ft2       ",) |    |    |    |    |    |    |    |    | 1102 | 202 | bbb |    |    |    | ft2        | 
    | (1103,203,ccc,,,,"ft2       ",) |    |    |    |    |    |    |    |    | 1103 | 203 | ccc |    |    |    | ft2        | 
 (3 rows)
 
 INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
@@ -5111,6 +5111,31 @@ UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING
  1017 | 507 | 0001700017_update7 |                              |                          |    | ft2        | 
 (102 rows)
 
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7b' WHERE c1 % 10 = 7 AND c1 < 40
+    RETURNING old.*, new.*;                                                         -- can't be pushed down
+                                                                  QUERY PLAN                                                                  
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.ft2
+   Output: old.c1, old.c2, old.c3, old.c4, old.c5, old.c6, old.c7, old.c8, new.c1, new.c2, new.c3, new.c4, new.c5, new.c6, new.c7, new.c8
+   Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+   ->  Foreign Scan on public.ft2
+         Output: (c2 + 400), (c3 || '_update7b'::text), ctid, ft2.*
+         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" < 40)) AND ((("C 1" % 10) = 7)) FOR UPDATE
+(6 rows)
+
+  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7b' WHERE c1 % 10 = 7 AND c1 < 40
+    RETURNING old.*, new.*;
+ c1 | c2  |      c3       |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2  |           c3           |              c4              |            c5            | c6 |     c7     | c8  
+----+-----+---------------+------------------------------+--------------------------+----+------------+-----+----+-----+------------------------+------------------------------+--------------------------+----+------------+-----
+  7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo |  7 | 807 | 00007_update7_update7b | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+ 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo | 17 | 807 | 00017_update7_update7b | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+ 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo | 27 | 807 | 00027_update7_update7b | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+ 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo | 37 | 807 | 00037_update7_update7b | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+(4 rows)
+
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can be pushed down
@@ -5241,6 +5266,29 @@ DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
  1105 | 
 (103 rows)
 
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  DELETE FROM ft2 WHERE c1 % 10 = 6 AND c1 < 40 RETURNING old.c1, c4;               -- can't be pushed down
+                                                QUERY PLAN                                                 
+-----------------------------------------------------------------------------------------------------------
+ Delete on public.ft2
+   Output: old.c1, c4
+   Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4
+   ->  Foreign Scan on public.ft2
+         Output: ctid
+         Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" < 40)) AND ((("C 1" % 10) = 6)) FOR UPDATE
+(6 rows)
+
+  DELETE FROM ft2 WHERE c1 % 10 = 6 AND c1 < 40 RETURNING old.c1, c4;
+ c1 |              c4              
+----+------------------------------
+  6 | Wed Jan 07 00:00:00 1970 PST
+ 16 | Sat Jan 17 00:00:00 1970 PST
+ 26 | Tue Jan 27 00:00:00 1970 PST
+ 36 | Fri Feb 06 00:00:00 1970 PST
+(4 rows)
+
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can be pushed down
                                                          QUERY PLAN                                                         
@@ -6165,6 +6213,70 @@ UPDATE ft2 SET c3 = 'foo'
  (1296,96,foo,,,,"ft2       ",) | 1296 | 96 | foo |    |    |    | ft2        |    | (96,97,AAA096) | 96 | 97 | AAA096
 (16 rows)
 
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  UPDATE ft2 SET c3 = 'bar'
+    FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
+    WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
+    RETURNING old, new, ft2, ft2.*, ft4, ft4.*;  -- can't be pushed down
+                                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                                     
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.ft2
+   Output: old.*, new.*, ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3
+   Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+   ->  Foreign Scan
+         Output: 'bar'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
+         Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
+         Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r2.c2, r2.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)))) INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) FOR UPDATE OF r1
+         ->  Nested Loop
+               Output: ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
+               Join Filter: (ft4.c1 = ft5.c1)
+               ->  Sort
+                     Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
+                     Sort Key: ft2.c2
+                     ->  Hash Join
+                           Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
+                           Hash Cond: (ft2.c2 = ft4.c1)
+                           ->  Foreign Scan on public.ft2
+                                 Output: ft2.ctid, ft2.*, ft2.c2
+                                 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE
+                           ->  Hash
+                                 Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
+                                 ->  Foreign Scan on public.ft4
+                                       Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
+                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+               ->  Materialize
+                     Output: ft5.*, ft5.c1
+                     ->  Foreign Scan on public.ft5
+                           Output: ft5.*, ft5.c1
+                           Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
+(29 rows)
+
+  UPDATE ft2 SET c3 = 'bar'
+    FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
+    WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
+    RETURNING old, new, ft2, ft2.*, ft4, ft4.*;
+              old               |              new               |              ft2               |  c1  | c2 | c3  | c4 | c5 | c6 |     c7     | c8 |      ft4       | c1 | c2 |   c3   
+--------------------------------+--------------------------------+--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+--------
+ (1206,6,foo,,,,"ft2       ",)  | (1206,6,bar,,,,"ft2       ",)  | (1206,6,bar,,,,"ft2       ",)  | 1206 |  6 | bar |    |    |    | ft2        |    | (6,7,AAA006)   |  6 |  7 | AAA006
+ (1212,12,foo,,,,"ft2       ",) | (1212,12,bar,,,,"ft2       ",) | (1212,12,bar,,,,"ft2       ",) | 1212 | 12 | bar |    |    |    | ft2        |    | (12,13,AAA012) | 12 | 13 | AAA012
+ (1224,24,foo,,,,"ft2       ",) | (1224,24,bar,,,,"ft2       ",) | (1224,24,bar,,,,"ft2       ",) | 1224 | 24 | bar |    |    |    | ft2        |    | (24,25,AAA024) | 24 | 25 | AAA024
+ (1230,30,foo,,,,"ft2       ",) | (1230,30,bar,,,,"ft2       ",) | (1230,30,bar,,,,"ft2       ",) | 1230 | 30 | bar |    |    |    | ft2        |    | (30,31,AAA030) | 30 | 31 | AAA030
+ (1242,42,foo,,,,"ft2       ",) | (1242,42,bar,,,,"ft2       ",) | (1242,42,bar,,,,"ft2       ",) | 1242 | 42 | bar |    |    |    | ft2        |    | (42,43,AAA042) | 42 | 43 | AAA042
+ (1248,48,foo,,,,"ft2       ",) | (1248,48,bar,,,,"ft2       ",) | (1248,48,bar,,,,"ft2       ",) | 1248 | 48 | bar |    |    |    | ft2        |    | (48,49,AAA048) | 48 | 49 | AAA048
+ (1260,60,foo,,,,"ft2       ",) | (1260,60,bar,,,,"ft2       ",) | (1260,60,bar,,,,"ft2       ",) | 1260 | 60 | bar |    |    |    | ft2        |    | (60,61,AAA060) | 60 | 61 | AAA060
+ (1266,66,foo,,,,"ft2       ",) | (1266,66,bar,,,,"ft2       ",) | (1266,66,bar,,,,"ft2       ",) | 1266 | 66 | bar |    |    |    | ft2        |    | (66,67,AAA066) | 66 | 67 | AAA066
+ (1278,78,foo,,,,"ft2       ",) | (1278,78,bar,,,,"ft2       ",) | (1278,78,bar,,,,"ft2       ",) | 1278 | 78 | bar |    |    |    | ft2        |    | (78,79,AAA078) | 78 | 79 | AAA078
+ (1284,84,foo,,,,"ft2       ",) | (1284,84,bar,,,,"ft2       ",) | (1284,84,bar,,,,"ft2       ",) | 1284 | 84 | bar |    |    |    | ft2        |    | (84,85,AAA084) | 84 | 85 | AAA084
+ (1296,96,foo,,,,"ft2       ",) | (1296,96,bar,,,,"ft2       ",) | (1296,96,bar,,,,"ft2       ",) | 1296 | 96 | bar |    |    |    | ft2        |    | (96,97,AAA096) | 96 | 97 | AAA096
+ (1218,18,foo,,,,"ft2       ",) | (1218,18,bar,,,,"ft2       ",) | (1218,18,bar,,,,"ft2       ",) | 1218 | 18 | bar |    |    |    | ft2        |    | (18,19,AAA018) | 18 | 19 | AAA018
+ (1236,36,foo,,,,"ft2       ",) | (1236,36,bar,,,,"ft2       ",) | (1236,36,bar,,,,"ft2       ",) | 1236 | 36 | bar |    |    |    | ft2        |    | (36,37,AAA036) | 36 | 37 | AAA036
+ (1254,54,foo,,,,"ft2       ",) | (1254,54,bar,,,,"ft2       ",) | (1254,54,bar,,,,"ft2       ",) | 1254 | 54 | bar |    |    |    | ft2        |    | (54,55,AAA054) | 54 | 55 | AAA054
+ (1272,72,foo,,,,"ft2       ",) | (1272,72,bar,,,,"ft2       ",) | (1272,72,bar,,,,"ft2       ",) | 1272 | 72 | bar |    |    |    | ft2        |    | (72,73,AAA072) | 72 | 73 | AAA072
+ (1290,90,foo,,,,"ft2       ",) | (1290,90,bar,,,,"ft2       ",) | (1290,90,bar,,,,"ft2       ",) | 1290 | 90 | bar |    |    |    | ft2        |    | (90,91,AAA090) | 90 | 91 | AAA090
+(16 rows)
+
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 DELETE FROM ft2
   USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1)
index 3900522ccb54b0702ba5699b77a061317b4c6e49..b58ab6ee586711a371769bce4ee03b793bff8f0b 100644 (file)
@@ -1469,7 +1469,7 @@ EXPLAIN (verbose, costs off)
 INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
 INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
 INSERT INTO ft2 (c1,c2,c3)
-  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
+  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING old, new, old.*, new.*;
 INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;              -- can be pushed down
@@ -1477,6 +1477,13 @@ UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;  -- can be pushed down
 UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7b' WHERE c1 % 10 = 7 AND c1 < 40
+    RETURNING old.*, new.*;                                                         -- can't be pushed down
+  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7b' WHERE c1 % 10 = 7 AND c1 < 40
+    RETURNING old.*, new.*;
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can be pushed down
@@ -1485,6 +1492,11 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
 EXPLAIN (verbose, costs off)
   DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;                               -- can be pushed down
 DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  DELETE FROM ft2 WHERE c1 % 10 = 6 AND c1 < 40 RETURNING old.c1, c4;               -- can't be pushed down
+  DELETE FROM ft2 WHERE c1 % 10 = 6 AND c1 < 40 RETURNING old.c1, c4;
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can be pushed down
 DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
@@ -1511,6 +1523,17 @@ UPDATE ft2 SET c3 = 'foo'
   FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
   WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
   RETURNING ft2, ft2.*, ft4, ft4.*;
+BEGIN;
+  EXPLAIN (verbose, costs off)
+  UPDATE ft2 SET c3 = 'bar'
+    FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
+    WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
+    RETURNING old, new, ft2, ft2.*, ft4, ft4.*;  -- can't be pushed down
+  UPDATE ft2 SET c3 = 'bar'
+    FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
+    WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
+    RETURNING old, new, ft2, ft2.*, ft4, ft4.*;
+ROLLBACK;
 EXPLAIN (verbose, costs off)
 DELETE FROM ft2
   USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1)
index 3d95bdb94e79d108c59b07dff67026c6b670a271..458aee788b7fbe5e90ad1b9f3cff4e35f4669c33 100644 (file)
@@ -308,7 +308,8 @@ DELETE FROM products;
   </para>
 
   <para>
-   In an <command>INSERT</command>, the data available to <literal>RETURNING</literal> is
+   In an <command>INSERT</command>, the default data available to
+   <literal>RETURNING</literal> is
    the row as it was inserted.  This is not so useful in trivial inserts,
    since it would just repeat the data provided by the client.  But it can
    be very handy when relying on computed default values.  For example,
@@ -325,7 +326,8 @@ INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;
   </para>
 
   <para>
-   In an <command>UPDATE</command>, the data available to <literal>RETURNING</literal> is
+   In an <command>UPDATE</command>, the default data available to
+   <literal>RETURNING</literal> is
    the new content of the modified row.  For example:
 <programlisting>
 UPDATE products SET price = price * 1.10
@@ -335,7 +337,8 @@ UPDATE products SET price = price * 1.10
   </para>
 
   <para>
-   In a <command>DELETE</command>, the data available to <literal>RETURNING</literal> is
+   In a <command>DELETE</command>, the default data available to
+   <literal>RETURNING</literal> is
    the content of the deleted row.  For example:
 <programlisting>
 DELETE FROM products
@@ -345,7 +348,8 @@ DELETE FROM products
   </para>
 
   <para>
-   In a <command>MERGE</command>, the data available to <literal>RETURNING</literal> is
+   In a <command>MERGE</command>, the default data available to
+   <literal>RETURNING</literal> is
    the content of the source row plus the content of the inserted, updated, or
    deleted target row.  Since it is quite common for the source and target to
    have many of the same columns, specifying <literal>RETURNING *</literal>
@@ -359,6 +363,35 @@ MERGE INTO products p USING new_products n ON p.product_no = n.product_no
 </programlisting>
   </para>
 
+  <para>
+   In each of these commands, it is also possible to explicitly return the
+   old and new content of the modified row.  For example:
+<programlisting>
+UPDATE products SET price = price * 1.10
+  WHERE price &lt;= 99.99
+  RETURNING name, old.price AS old_price, new.price AS new_price,
+            new.price - old.price AS price_change;
+</programlisting>
+   In this example, writing <literal>new.price</literal> is the same as
+   just writing <literal>price</literal>, but it makes the meaning clearer.
+  </para>
+
+  <para>
+   This syntax for returning old and new values is available in
+   <command>INSERT</command>, <command>UPDATE</command>,
+   <command>DELETE</command>, and <command>MERGE</command> commands, but
+   typically old values will be <literal>NULL</literal> for an
+   <command>INSERT</command>, and new values will be <literal>NULL</literal>
+   for a <command>DELETE</command>.  However, there are situations where it
+   can still be useful for those commands.  For example, in an
+   <command>INSERT</command> with an
+   <link linkend="sql-on-conflict"><literal>ON CONFLICT DO UPDATE</literal></link>
+   clause, the old values will be non-<literal>NULL</literal> for conflicting
+   rows.  Similarly, if a <command>DELETE</command> is turned into an
+   <command>UPDATE</command> by a <link linkend="sql-createrule">rewrite rule</link>,
+   the new values may be non-<literal>NULL</literal>.
+  </para>
+
   <para>
    If there are triggers (<xref linkend="triggers"/>) on the target table,
    the data available to <literal>RETURNING</literal> is the row as modified by
index 7717855bc9ef4877307b8bde1857ab010c5ff7aa..29649f6afd65c38520653d616717f432fe9aeefb 100644 (file)
@@ -25,7 +25,8 @@ PostgreSQL documentation
 DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
     [ USING <replaceable class="parameter">from_item</replaceable> [, ...] ]
     [ WHERE <replaceable class="parameter">condition</replaceable> | WHERE CURRENT OF <replaceable class="parameter">cursor_name</replaceable> ]
-    [ RETURNING { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
+    [ RETURNING [ WITH ( { OLD | NEW } AS <replaceable class="parameter">output_alias</replaceable> [, ...] ) ]
+                { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -160,6 +161,26 @@ DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ *
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">output_alias</replaceable></term>
+    <listitem>
+     <para>
+      An optional substitute name for <literal>OLD</literal> or
+      <literal>NEW</literal> rows in the <literal>RETURNING</literal> list.
+     </para>
+
+     <para>
+      By default, old values from the target table can be returned by writing
+      <literal>OLD.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>OLD.*</literal>, and new values can be returned by writing
+      <literal>NEW.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>NEW.*</literal>.  When an alias is provided, these names are
+      hidden and the old or new rows must be referred to using the alias.
+      For example <literal>RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">output_expression</replaceable></term>
     <listitem>
@@ -170,6 +191,23 @@ DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ *
       or table(s) listed in <literal>USING</literal>.
       Write <literal>*</literal> to return all columns.
      </para>
+
+     <para>
+      A column name or <literal>*</literal> may be qualified using
+      <literal>OLD</literal> or <literal>NEW</literal>, or the corresponding
+      <replaceable class="parameter">output_alias</replaceable> for
+      <literal>OLD</literal> or <literal>NEW</literal>, to cause old or new
+      values to be returned.  An unqualified column name, or
+      <literal>*</literal>, or a column name or <literal>*</literal> qualified
+      using the target table name or alias will return old values.
+     </para>
+
+     <para>
+      For a simple <command>DELETE</command>, all new values will be
+      <literal>NULL</literal>.  However, if an <literal>ON DELETE</literal>
+      rule causes an <command>INSERT</command> or <command>UPDATE</command>
+      to be executed instead, the new values may be non-<literal>NULL</literal>.
+     </para>
     </listitem>
    </varlistentry>
 
index 6f0adee1a124ab1bf17164d338b32bafce55eacf..3f13991779050ff9d8796cc071ced747ecc8dee6 100644 (file)
@@ -26,7 +26,8 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
     [ OVERRIDING { SYSTEM | USER } VALUE ]
     { DEFAULT VALUES | VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="parameter">query</replaceable> }
     [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
-    [ RETURNING { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
+    [ RETURNING [ WITH ( { OLD | NEW } AS <replaceable class="parameter">output_alias</replaceable> [, ...] ) ]
+                { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
 
 <phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
 
@@ -293,6 +294,26 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">output_alias</replaceable></term>
+      <listitem>
+       <para>
+        An optional substitute name for <literal>OLD</literal> or
+        <literal>NEW</literal> rows in the <literal>RETURNING</literal> list.
+       </para>
+
+       <para>
+        By default, old values from the target table can be returned by writing
+        <literal>OLD.<replaceable class="parameter">column_name</replaceable></literal>
+        or <literal>OLD.*</literal>, and new values can be returned by writing
+        <literal>NEW.<replaceable class="parameter">column_name</replaceable></literal>
+        or <literal>NEW.*</literal>.  When an alias is provided, these names are
+        hidden and the old or new rows must be referred to using the alias.
+        For example <literal>RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">output_expression</replaceable></term>
       <listitem>
@@ -305,6 +326,23 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
         <literal>*</literal> to return all columns of the inserted or updated
         row(s).
        </para>
+
+       <para>
+        A column name or <literal>*</literal> may be qualified using
+        <literal>OLD</literal> or <literal>NEW</literal>, or the corresponding
+        <replaceable class="parameter">output_alias</replaceable> for
+        <literal>OLD</literal> or <literal>NEW</literal>, to cause old or new
+        values to be returned.  An unqualified column name, or
+        <literal>*</literal>, or a column name or <literal>*</literal>
+        qualified using the target table name or alias will return new values.
+       </para>
+
+       <para>
+        For a simple <command>INSERT</command>, all old values will be
+        <literal>NULL</literal>.  However, for an <command>INSERT</command>
+        with an <literal>ON CONFLICT DO UPDATE</literal> clause, the old
+        values may be non-<literal>NULL</literal>.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -711,6 +749,20 @@ INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
 INSERT INTO distributors (did, dname)
     VALUES (5, 'Gizmo Transglobal'), (6, 'Associated Computing, Inc')
     ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname;
+</programlisting>
+  </para>
+  <para>
+   Insert or update new distributors as above, returning information
+   about any existing values that were updated, together with the new data
+   inserted.  Note that the returned values for <literal>old_did</literal>
+   and <literal>old_dname</literal> will be <literal>NULL</literal> for
+   non-conflicting rows:
+<programlisting>
+INSERT INTO distributors (did, dname)
+    VALUES (5, 'Gizmo Transglobal'), (6, 'Associated Computing, Inc')
+    ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname
+    RETURNING old.did AS old_did, old.dname AS old_dname,
+              new.did AS new_did, new.dname AS new_dname;
 </programlisting>
   </para>
   <para>
index d80a5c5cc9b5a87ab5e3fe9a74f03e92c519659d..ecbcd8345d87487f136bce8f585e9f8f04c24dc0 100644 (file)
@@ -25,7 +25,8 @@ PostgreSQL documentation
 MERGE INTO [ ONLY ] <replaceable class="parameter">target_table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">target_alias</replaceable> ]
 USING <replaceable class="parameter">data_source</replaceable> ON <replaceable class="parameter">join_condition</replaceable>
 <replaceable class="parameter">when_clause</replaceable> [...]
-[ RETURNING { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
+[ RETURNING [ WITH ( { OLD | NEW } AS <replaceable class="parameter">output_alias</replaceable> [, ...] ) ]
+            { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
 
 <phrase>where <replaceable class="parameter">data_source</replaceable> is:</phrase>
 
@@ -499,6 +500,25 @@ DELETE
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">output_alias</replaceable></term>
+    <listitem>
+     <para>
+      An optional substitute name for <literal>OLD</literal> or
+      <literal>NEW</literal> rows in the <literal>RETURNING</literal> list.
+     </para>
+     <para>
+      By default, old values from the target table can be returned by writing
+      <literal>OLD.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>OLD.*</literal>, and new values can be returned by writing
+      <literal>NEW.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>NEW.*</literal>.  When an alias is provided, these names are
+      hidden and the old or new rows must be referred to using the alias.
+      For example <literal>RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">output_expression</replaceable></term>
     <listitem>
@@ -517,6 +537,17 @@ DELETE
       qualifying the <literal>*</literal> with the name or alias of the source
       or target table.
      </para>
+     <para>
+      A column name or <literal>*</literal> may also be qualified using
+      <literal>OLD</literal> or <literal>NEW</literal>, or the corresponding
+      <replaceable class="parameter">output_alias</replaceable> for
+      <literal>OLD</literal> or <literal>NEW</literal>, to cause old or new
+      values from the target table to be returned.  An unqualified column
+      name from the target table, or a column name or <literal>*</literal>
+      qualified using the target table name or alias will return new values
+      for <literal>INSERT</literal> and <literal>UPDATE</literal> actions, and
+      old values for <literal>DELETE</literal> actions.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -722,7 +753,7 @@ WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN
   UPDATE SET stock = w.stock + s.stock_delta
 WHEN MATCHED THEN
   DELETE
-RETURNING merge_action(), w.*;
+RETURNING merge_action(), w.winename, old.stock AS old_stock, new.stock AS new_stock;
 </programlisting>
 
    The <literal>wine_stock_changes</literal> table might be, for example, a
index 1c433bec2bb8efcfd848af367c34c4f93daea761..12ec5ba070939bc16d43c0daf92449e3035afe8c 100644 (file)
@@ -29,7 +29,8 @@ UPDATE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [
         } [, ...]
     [ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
     [ WHERE <replaceable class="parameter">condition</replaceable> | WHERE CURRENT OF <replaceable class="parameter">cursor_name</replaceable> ]
-    [ RETURNING { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
+    [ RETURNING [ WITH ( { OLD | NEW } AS <replaceable class="parameter">output_alias</replaceable> [, ...] ) ]
+                { * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -211,6 +212,26 @@ UPDATE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">output_alias</replaceable></term>
+    <listitem>
+     <para>
+      An optional substitute name for <literal>OLD</literal> or
+      <literal>NEW</literal> rows in the <literal>RETURNING</literal> list.
+     </para>
+
+     <para>
+      By default, old values from the target table can be returned by writing
+      <literal>OLD.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>OLD.*</literal>, and new values can be returned by writing
+      <literal>NEW.<replaceable class="parameter">column_name</replaceable></literal>
+      or <literal>NEW.*</literal>.  When an alias is provided, these names are
+      hidden and the old or new rows must be referred to using the alias.
+      For example <literal>RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">output_expression</replaceable></term>
     <listitem>
@@ -221,6 +242,16 @@ UPDATE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [
       or table(s) listed in <literal>FROM</literal>.
       Write <literal>*</literal> to return all columns.
      </para>
+
+     <para>
+      A column name or <literal>*</literal> may be qualified using
+      <literal>OLD</literal> or <literal>NEW</literal>, or the corresponding
+      <replaceable class="parameter">output_alias</replaceable> for
+      <literal>OLD</literal> or <literal>NEW</literal>, to cause old or new
+      values to be returned.  An unqualified column name, or
+      <literal>*</literal>, or a column name or <literal>*</literal> qualified
+      using the target table name or alias will return new values.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -348,12 +379,13 @@ UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
   </para>
 
   <para>
-   Perform the same operation and return the updated entries:
+   Perform the same operation and return the updated entries, and the old
+   precipitation value:
 
 <programlisting>
 UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
   WHERE city = 'San Francisco' AND date = '2003-07-03'
-  RETURNING temp_lo, temp_hi, prcp;
+  RETURNING temp_lo, temp_hi, prcp, old.prcp AS old_prcp;
 </programlisting>
   </para>
 
index 7e98f5694b448ce6b860135e8a8a666a223e93ca..9fdf8b1d9176330dd4a5d70a3a3ef0fd50d4932f 100644 (file)
@@ -1645,6 +1645,23 @@ CREATE RULE shoelace_ins AS ON INSERT TO shoelace
     <literal>RETURNING</literal> clause is simply ignored for <command>INSERT</command>.
    </para>
 
+   <para>
+    Note that in the <literal>RETURNING</literal> clause of a rule,
+    <literal>OLD</literal> and <literal>NEW</literal> refer to the
+    pseudorelations added as extra range table entries to the rewritten
+    query, rather than old/new rows in the result relation.  Thus, for
+    example, in a rule supporting <command>UPDATE</command> queries on this
+    view, if the <literal>RETURNING</literal> clause contained
+    <literal>old.sl_name</literal>, the old name would always be returned,
+    regardless of whether the <literal>RETURNING</literal> clause in the
+    query on the view specified <literal>OLD</literal> or <literal>NEW</literal>,
+    which might be confusing.  To avoid this confusion, and support returning
+    old and new values in queries on the view, the <literal>RETURNING</literal>
+    clause in the rule definition should refer to entries from the result
+    relation such as <literal>shoelace_data.sl_name</literal>, without
+    specifying <literal>OLD</literal> or <literal>NEW</literal>.
+   </para>
+
    <para>
     Now assume that once in a while, a pack of shoelaces arrives at
     the shop and a big parts list along with it.  But you don't want
index 7a800df8cabfa893c6a0c49041514d63a945a6a6..8f28da4bf94562af90bb0ffda5624e76dae487a2 100644 (file)
 
 typedef struct ExprSetupInfo
 {
-       /* Highest attribute numbers fetched from inner/outer/scan tuple slots: */
+       /*
+        * Highest attribute numbers fetched from inner/outer/scan/old/new tuple
+        * slots:
+        */
        AttrNumber      last_inner;
        AttrNumber      last_outer;
        AttrNumber      last_scan;
+       AttrNumber      last_old;
+       AttrNumber      last_new;
        /* MULTIEXPR SubPlan nodes appearing in the expression: */
        List       *multiexpr_subplans;
 } ExprSetupInfo;
@@ -446,8 +451,25 @@ ExecBuildProjectionInfo(List *targetList,
                                        /* INDEX_VAR is handled by default case */
 
                                default:
-                                       /* get the tuple from the relation being scanned */
-                                       scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
+
+                                       /*
+                                        * Get the tuple from the relation being scanned, or the
+                                        * old/new tuple slot, if old/new values were requested.
+                                        */
+                                       switch (variable->varreturningtype)
+                                       {
+                                               case VAR_RETURNING_DEFAULT:
+                                                       scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
+                                                       break;
+                                               case VAR_RETURNING_OLD:
+                                                       scratch.opcode = EEOP_ASSIGN_OLD_VAR;
+                                                       state->flags |= EEO_FLAG_HAS_OLD;
+                                                       break;
+                                               case VAR_RETURNING_NEW:
+                                                       scratch.opcode = EEOP_ASSIGN_NEW_VAR;
+                                                       state->flags |= EEO_FLAG_HAS_NEW;
+                                                       break;
+                                       }
                                        break;
                        }
 
@@ -535,7 +557,7 @@ ExecBuildUpdateProjection(List *targetList,
        int                     nAssignableCols;
        bool            sawJunk;
        Bitmapset  *assignedCols;
-       ExprSetupInfo deform = {0, 0, 0, NIL};
+       ExprSetupInfo deform = {0, 0, 0, 0, 0, NIL};
        ExprEvalStep scratch = {0};
        int                     outerattnum;
        ListCell   *lc,
@@ -924,6 +946,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                        /* system column */
                                        scratch.d.var.attnum = variable->varattno;
                                        scratch.d.var.vartype = variable->vartype;
+                                       scratch.d.var.varreturningtype = variable->varreturningtype;
                                        switch (variable->varno)
                                        {
                                                case INNER_VAR:
@@ -936,7 +959,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                                        /* INDEX_VAR is handled by default case */
 
                                                default:
-                                                       scratch.opcode = EEOP_SCAN_SYSVAR;
+                                                       switch (variable->varreturningtype)
+                                                       {
+                                                               case VAR_RETURNING_DEFAULT:
+                                                                       scratch.opcode = EEOP_SCAN_SYSVAR;
+                                                                       break;
+                                                               case VAR_RETURNING_OLD:
+                                                                       scratch.opcode = EEOP_OLD_SYSVAR;
+                                                                       state->flags |= EEO_FLAG_HAS_OLD;
+                                                                       break;
+                                                               case VAR_RETURNING_NEW:
+                                                                       scratch.opcode = EEOP_NEW_SYSVAR;
+                                                                       state->flags |= EEO_FLAG_HAS_NEW;
+                                                                       break;
+                                                       }
                                                        break;
                                        }
                                }
@@ -945,6 +981,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                        /* regular user column */
                                        scratch.d.var.attnum = variable->varattno - 1;
                                        scratch.d.var.vartype = variable->vartype;
+                                       scratch.d.var.varreturningtype = variable->varreturningtype;
                                        switch (variable->varno)
                                        {
                                                case INNER_VAR:
@@ -957,7 +994,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                                        /* INDEX_VAR is handled by default case */
 
                                                default:
-                                                       scratch.opcode = EEOP_SCAN_VAR;
+                                                       switch (variable->varreturningtype)
+                                                       {
+                                                               case VAR_RETURNING_DEFAULT:
+                                                                       scratch.opcode = EEOP_SCAN_VAR;
+                                                                       break;
+                                                               case VAR_RETURNING_OLD:
+                                                                       scratch.opcode = EEOP_OLD_VAR;
+                                                                       state->flags |= EEO_FLAG_HAS_OLD;
+                                                                       break;
+                                                               case VAR_RETURNING_NEW:
+                                                                       scratch.opcode = EEOP_NEW_VAR;
+                                                                       state->flags |= EEO_FLAG_HAS_NEW;
+                                                                       break;
+                                                       }
                                                        break;
                                        }
                                }
@@ -2575,6 +2625,34 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                break;
                        }
 
+               case T_ReturningExpr:
+                       {
+                               ReturningExpr *rexpr = (ReturningExpr *) node;
+                               int                     retstep;
+
+                               /* Skip expression evaluation if OLD/NEW row doesn't exist */
+                               scratch.opcode = EEOP_RETURNINGEXPR;
+                               scratch.d.returningexpr.nullflag = rexpr->retold ?
+                                       EEO_FLAG_OLD_IS_NULL : EEO_FLAG_NEW_IS_NULL;
+                               scratch.d.returningexpr.jumpdone = -1;  /* set below */
+                               ExprEvalPushStep(state, &scratch);
+                               retstep = state->steps_len - 1;
+
+                               /* Steps to evaluate expression to return */
+                               ExecInitExprRec(rexpr->retexpr, state, resv, resnull);
+
+                               /* Jump target used if OLD/NEW row doesn't exist */
+                               state->steps[retstep].d.returningexpr.jumpdone = state->steps_len;
+
+                               /* Update ExprState flags */
+                               if (rexpr->retold)
+                                       state->flags |= EEO_FLAG_HAS_OLD;
+                               else
+                                       state->flags |= EEO_FLAG_HAS_NEW;
+
+                               break;
+                       }
+
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -2786,7 +2864,7 @@ ExecInitSubPlanExpr(SubPlan *subplan,
 static void
 ExecCreateExprSetupSteps(ExprState *state, Node *node)
 {
-       ExprSetupInfo info = {0, 0, 0, NIL};
+       ExprSetupInfo info = {0, 0, 0, 0, 0, NIL};
 
        /* Prescan to find out what we need. */
        expr_setup_walker(node, &info);
@@ -2809,8 +2887,8 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
        scratch.resnull = NULL;
 
        /*
-        * Add steps deforming the ExprState's inner/outer/scan slots as much as
-        * required by any Vars appearing in the expression.
+        * Add steps deforming the ExprState's inner/outer/scan/old/new slots as
+        * much as required by any Vars appearing in the expression.
         */
        if (info->last_inner > 0)
        {
@@ -2842,6 +2920,26 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
                if (ExecComputeSlotInfo(state, &scratch))
                        ExprEvalPushStep(state, &scratch);
        }
+       if (info->last_old > 0)
+       {
+               scratch.opcode = EEOP_OLD_FETCHSOME;
+               scratch.d.fetch.last_var = info->last_old;
+               scratch.d.fetch.fixed = false;
+               scratch.d.fetch.kind = NULL;
+               scratch.d.fetch.known_desc = NULL;
+               if (ExecComputeSlotInfo(state, &scratch))
+                       ExprEvalPushStep(state, &scratch);
+       }
+       if (info->last_new > 0)
+       {
+               scratch.opcode = EEOP_NEW_FETCHSOME;
+               scratch.d.fetch.last_var = info->last_new;
+               scratch.d.fetch.fixed = false;
+               scratch.d.fetch.kind = NULL;
+               scratch.d.fetch.known_desc = NULL;
+               if (ExecComputeSlotInfo(state, &scratch))
+                       ExprEvalPushStep(state, &scratch);
+       }
 
        /*
         * Add steps to execute any MULTIEXPR SubPlans appearing in the
@@ -2888,7 +2986,18 @@ expr_setup_walker(Node *node, ExprSetupInfo *info)
                                /* INDEX_VAR is handled by default case */
 
                        default:
-                               info->last_scan = Max(info->last_scan, attnum);
+                               switch (variable->varreturningtype)
+                               {
+                                       case VAR_RETURNING_DEFAULT:
+                                               info->last_scan = Max(info->last_scan, attnum);
+                                               break;
+                                       case VAR_RETURNING_OLD:
+                                               info->last_old = Max(info->last_old, attnum);
+                                               break;
+                                       case VAR_RETURNING_NEW:
+                                               info->last_new = Max(info->last_new, attnum);
+                                               break;
+                               }
                                break;
                }
                return false;
@@ -2926,6 +3035,11 @@ expr_setup_walker(Node *node, ExprSetupInfo *info)
  * evaluation of the expression will have the same type of slot, with an
  * equivalent descriptor.
  *
+ * EEOP_OLD_FETCHSOME and EEOP_NEW_FETCHSOME are used to process RETURNING, if
+ * OLD/NEW columns are referred to explicitly.  In both cases, the tuple
+ * descriptor comes from the parent scan node, so we treat them the same as
+ * EEOP_SCAN_FETCHSOME.
+ *
  * Returns true if the deforming step is required, false otherwise.
  */
 static bool
@@ -2939,7 +3053,9 @@ ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op)
 
        Assert(opcode == EEOP_INNER_FETCHSOME ||
                   opcode == EEOP_OUTER_FETCHSOME ||
-                  opcode == EEOP_SCAN_FETCHSOME);
+                  opcode == EEOP_SCAN_FETCHSOME ||
+                  opcode == EEOP_OLD_FETCHSOME ||
+                  opcode == EEOP_NEW_FETCHSOME);
 
        if (op->d.fetch.known_desc != NULL)
        {
@@ -2991,7 +3107,9 @@ ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op)
                        desc = ExecGetResultType(os);
                }
        }
-       else if (opcode == EEOP_SCAN_FETCHSOME)
+       else if (opcode == EEOP_SCAN_FETCHSOME ||
+                        opcode == EEOP_OLD_FETCHSOME ||
+                        opcode == EEOP_NEW_FETCHSOME)
        {
                desc = parent->scandesc;
 
@@ -3039,6 +3157,12 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
        scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */
        scratch->d.wholerow.junkFilter = NULL;
 
+       /* update ExprState flags if Var refers to OLD/NEW */
+       if (variable->varreturningtype == VAR_RETURNING_OLD)
+               state->flags |= EEO_FLAG_HAS_OLD;
+       else if (variable->varreturningtype == VAR_RETURNING_NEW)
+               state->flags |= EEO_FLAG_HAS_NEW;
+
        /*
         * If the input tuple came from a subquery, it might contain "resjunk"
         * columns (such as GROUP BY or ORDER BY columns), which we don't want to
@@ -3541,7 +3665,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
        PlanState  *parent = &aggstate->ss.ps;
        ExprEvalStep scratch = {0};
        bool            isCombine = DO_AGGSPLIT_COMBINE(aggstate->aggsplit);
-       ExprSetupInfo deform = {0, 0, 0, NIL};
+       ExprSetupInfo deform = {0, 0, 0, 0, 0, NIL};
 
        state->expr = (Expr *) aggstate;
        state->parent = parent;
@@ -4082,6 +4206,7 @@ ExecBuildHash32FromAttrs(TupleDesc desc, const TupleTableSlotOps *ops,
                scratch.resnull = &fcinfo->args[0].isnull;
                scratch.d.var.attnum = attnum;
                scratch.d.var.vartype = TupleDescAttr(desc, attnum)->atttypid;
+               scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT;
 
                ExprEvalPushStep(state, &scratch);
 
@@ -4407,6 +4532,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
                scratch.opcode = EEOP_INNER_VAR;
                scratch.d.var.attnum = attno - 1;
                scratch.d.var.vartype = latt->atttypid;
+               scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT;
                scratch.resvalue = &fcinfo->args[0].value;
                scratch.resnull = &fcinfo->args[0].isnull;
                ExprEvalPushStep(state, &scratch);
@@ -4415,6 +4541,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
                scratch.opcode = EEOP_OUTER_VAR;
                scratch.d.var.attnum = attno - 1;
                scratch.d.var.vartype = ratt->atttypid;
+               scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT;
                scratch.resvalue = &fcinfo->args[1].value;
                scratch.resnull = &fcinfo->args[1].isnull;
                ExprEvalPushStep(state, &scratch);
@@ -4541,6 +4668,7 @@ ExecBuildParamSetEqual(TupleDesc desc,
                scratch.opcode = EEOP_INNER_VAR;
                scratch.d.var.attnum = attno;
                scratch.d.var.vartype = att->atttypid;
+               scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT;
                scratch.resvalue = &fcinfo->args[0].value;
                scratch.resnull = &fcinfo->args[0].isnull;
                ExprEvalPushStep(state, &scratch);
@@ -4549,6 +4677,7 @@ ExecBuildParamSetEqual(TupleDesc desc,
                scratch.opcode = EEOP_OUTER_VAR;
                scratch.d.var.attnum = attno;
                scratch.d.var.vartype = att->atttypid;
+               scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT;
                scratch.resvalue = &fcinfo->args[1].value;
                scratch.resnull = &fcinfo->args[1].isnull;
                ExprEvalPushStep(state, &scratch);
index 7dfe17b0a86fdbb0135ec0dcc408d4b3e71e6790..1127e6f11eb854ceba0cc356015598f443142309 100644 (file)
@@ -462,6 +462,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
        TupleTableSlot *innerslot;
        TupleTableSlot *outerslot;
        TupleTableSlot *scanslot;
+       TupleTableSlot *oldslot;
+       TupleTableSlot *newslot;
 
        /*
         * This array has to be in the same order as enum ExprEvalOp.
@@ -472,16 +474,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                &&CASE_EEOP_INNER_FETCHSOME,
                &&CASE_EEOP_OUTER_FETCHSOME,
                &&CASE_EEOP_SCAN_FETCHSOME,
+               &&CASE_EEOP_OLD_FETCHSOME,
+               &&CASE_EEOP_NEW_FETCHSOME,
                &&CASE_EEOP_INNER_VAR,
                &&CASE_EEOP_OUTER_VAR,
                &&CASE_EEOP_SCAN_VAR,
+               &&CASE_EEOP_OLD_VAR,
+               &&CASE_EEOP_NEW_VAR,
                &&CASE_EEOP_INNER_SYSVAR,
                &&CASE_EEOP_OUTER_SYSVAR,
                &&CASE_EEOP_SCAN_SYSVAR,
+               &&CASE_EEOP_OLD_SYSVAR,
+               &&CASE_EEOP_NEW_SYSVAR,
                &&CASE_EEOP_WHOLEROW,
                &&CASE_EEOP_ASSIGN_INNER_VAR,
                &&CASE_EEOP_ASSIGN_OUTER_VAR,
                &&CASE_EEOP_ASSIGN_SCAN_VAR,
+               &&CASE_EEOP_ASSIGN_OLD_VAR,
+               &&CASE_EEOP_ASSIGN_NEW_VAR,
                &&CASE_EEOP_ASSIGN_TMP,
                &&CASE_EEOP_ASSIGN_TMP_MAKE_RO,
                &&CASE_EEOP_CONST,
@@ -523,6 +533,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                &&CASE_EEOP_SQLVALUEFUNCTION,
                &&CASE_EEOP_CURRENTOFEXPR,
                &&CASE_EEOP_NEXTVALUEEXPR,
+               &&CASE_EEOP_RETURNINGEXPR,
                &&CASE_EEOP_ARRAYEXPR,
                &&CASE_EEOP_ARRAYCOERCE,
                &&CASE_EEOP_ROW,
@@ -591,6 +602,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
        innerslot = econtext->ecxt_innertuple;
        outerslot = econtext->ecxt_outertuple;
        scanslot = econtext->ecxt_scantuple;
+       oldslot = econtext->ecxt_oldtuple;
+       newslot = econtext->ecxt_newtuple;
 
 #if defined(EEO_USE_COMPUTED_GOTO)
        EEO_DISPATCH();
@@ -630,6 +643,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_OLD_FETCHSOME)
+               {
+                       CheckOpSlotCompatibility(op, oldslot);
+
+                       slot_getsomeattrs(oldslot, op->d.fetch.last_var);
+
+                       EEO_NEXT();
+               }
+
+               EEO_CASE(EEOP_NEW_FETCHSOME)
+               {
+                       CheckOpSlotCompatibility(op, newslot);
+
+                       slot_getsomeattrs(newslot, op->d.fetch.last_var);
+
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_INNER_VAR)
                {
                        int                     attnum = op->d.var.attnum;
@@ -673,6 +704,32 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_OLD_VAR)
+               {
+                       int                     attnum = op->d.var.attnum;
+
+                       /* See EEOP_INNER_VAR comments */
+
+                       Assert(attnum >= 0 && attnum < oldslot->tts_nvalid);
+                       *op->resvalue = oldslot->tts_values[attnum];
+                       *op->resnull = oldslot->tts_isnull[attnum];
+
+                       EEO_NEXT();
+               }
+
+               EEO_CASE(EEOP_NEW_VAR)
+               {
+                       int                     attnum = op->d.var.attnum;
+
+                       /* See EEOP_INNER_VAR comments */
+
+                       Assert(attnum >= 0 && attnum < newslot->tts_nvalid);
+                       *op->resvalue = newslot->tts_values[attnum];
+                       *op->resnull = newslot->tts_isnull[attnum];
+
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_INNER_SYSVAR)
                {
                        ExecEvalSysVar(state, op, econtext, innerslot);
@@ -691,6 +748,18 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_OLD_SYSVAR)
+               {
+                       ExecEvalSysVar(state, op, econtext, oldslot);
+                       EEO_NEXT();
+               }
+
+               EEO_CASE(EEOP_NEW_SYSVAR)
+               {
+                       ExecEvalSysVar(state, op, econtext, newslot);
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_WHOLEROW)
                {
                        /* too complex for an inline implementation */
@@ -750,6 +819,40 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_ASSIGN_OLD_VAR)
+               {
+                       int                     resultnum = op->d.assign_var.resultnum;
+                       int                     attnum = op->d.assign_var.attnum;
+
+                       /*
+                        * We do not need CheckVarSlotCompatibility here; that was taken
+                        * care of at compilation time.  But see EEOP_INNER_VAR comments.
+                        */
+                       Assert(attnum >= 0 && attnum < oldslot->tts_nvalid);
+                       Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
+                       resultslot->tts_values[resultnum] = oldslot->tts_values[attnum];
+                       resultslot->tts_isnull[resultnum] = oldslot->tts_isnull[attnum];
+
+                       EEO_NEXT();
+               }
+
+               EEO_CASE(EEOP_ASSIGN_NEW_VAR)
+               {
+                       int                     resultnum = op->d.assign_var.resultnum;
+                       int                     attnum = op->d.assign_var.attnum;
+
+                       /*
+                        * We do not need CheckVarSlotCompatibility here; that was taken
+                        * care of at compilation time.  But see EEOP_INNER_VAR comments.
+                        */
+                       Assert(attnum >= 0 && attnum < newslot->tts_nvalid);
+                       Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
+                       resultslot->tts_values[resultnum] = newslot->tts_values[attnum];
+                       resultslot->tts_isnull[resultnum] = newslot->tts_isnull[attnum];
+
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_ASSIGN_TMP)
                {
                        int                     resultnum = op->d.assign_tmp.resultnum;
@@ -1438,6 +1541,23 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        EEO_NEXT();
                }
 
+               EEO_CASE(EEOP_RETURNINGEXPR)
+               {
+                       /*
+                        * The next op actually evaluates the expression.  If the OLD/NEW
+                        * row doesn't exist, skip that and return NULL.
+                        */
+                       if (state->flags & op->d.returningexpr.nullflag)
+                       {
+                               *op->resvalue = (Datum) 0;
+                               *op->resnull = true;
+
+                               EEO_JUMP(op->d.returningexpr.jumpdone);
+                       }
+
+                       EEO_NEXT();
+               }
+
                EEO_CASE(EEOP_ARRAYEXPR)
                {
                        /* too complex for an inline implementation */
@@ -2119,10 +2239,14 @@ CheckExprStillValid(ExprState *state, ExprContext *econtext)
        TupleTableSlot *innerslot;
        TupleTableSlot *outerslot;
        TupleTableSlot *scanslot;
+       TupleTableSlot *oldslot;
+       TupleTableSlot *newslot;
 
        innerslot = econtext->ecxt_innertuple;
        outerslot = econtext->ecxt_outertuple;
        scanslot = econtext->ecxt_scantuple;
+       oldslot = econtext->ecxt_oldtuple;
+       newslot = econtext->ecxt_newtuple;
 
        for (int i = 0; i < state->steps_len; i++)
        {
@@ -2153,6 +2277,22 @@ CheckExprStillValid(ExprState *state, ExprContext *econtext)
                                        CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype);
                                        break;
                                }
+
+                       case EEOP_OLD_VAR:
+                               {
+                                       int                     attnum = op->d.var.attnum;
+
+                                       CheckVarSlotCompatibility(oldslot, attnum + 1, op->d.var.vartype);
+                                       break;
+                               }
+
+                       case EEOP_NEW_VAR:
+                               {
+                                       int                     attnum = op->d.var.attnum;
+
+                                       CheckVarSlotCompatibility(newslot, attnum + 1, op->d.var.vartype);
+                                       break;
+                               }
                        default:
                                break;
                }
@@ -5113,7 +5253,7 @@ void
 ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
        Var                *variable = op->d.wholerow.var;
-       TupleTableSlot *slot;
+       TupleTableSlot *slot = NULL;
        TupleDesc       output_tupdesc;
        MemoryContext oldcontext;
        HeapTupleHeader dtuple;
@@ -5138,8 +5278,40 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
                        /* INDEX_VAR is handled by default case */
 
                default:
-                       /* get the tuple from the relation being scanned */
-                       slot = econtext->ecxt_scantuple;
+
+                       /*
+                        * Get the tuple from the relation being scanned.
+                        *
+                        * By default, this uses the "scan" tuple slot, but a wholerow Var
+                        * in the RETURNING list may explicitly refer to OLD/NEW.  If the
+                        * OLD/NEW row doesn't exist, we just return NULL.
+                        */
+                       switch (variable->varreturningtype)
+                       {
+                               case VAR_RETURNING_DEFAULT:
+                                       slot = econtext->ecxt_scantuple;
+                                       break;
+
+                               case VAR_RETURNING_OLD:
+                                       if (state->flags & EEO_FLAG_OLD_IS_NULL)
+                                       {
+                                               *op->resvalue = (Datum) 0;
+                                               *op->resnull = true;
+                                               return;
+                                       }
+                                       slot = econtext->ecxt_oldtuple;
+                                       break;
+
+                               case VAR_RETURNING_NEW:
+                                       if (state->flags & EEO_FLAG_NEW_IS_NULL)
+                                       {
+                                               *op->resvalue = (Datum) 0;
+                                               *op->resnull = true;
+                                               return;
+                                       }
+                                       slot = econtext->ecxt_newtuple;
+                                       break;
+                       }
                        break;
        }
 
@@ -5342,6 +5514,17 @@ ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
 {
        Datum           d;
 
+       /* OLD/NEW system attribute is NULL if OLD/NEW row is NULL */
+       if ((op->d.var.varreturningtype == VAR_RETURNING_OLD &&
+                state->flags & EEO_FLAG_OLD_IS_NULL) ||
+               (op->d.var.varreturningtype == VAR_RETURNING_NEW &&
+                state->flags & EEO_FLAG_NEW_IS_NULL))
+       {
+               *op->resvalue = (Datum) 0;
+               *op->resnull = true;
+               return;
+       }
+
        /* slot_getsysattr has sufficient defenses against bad attnums */
        d = slot_getsysattr(slot,
                                                op->d.var.attnum,
index 2d28ec65fc43644168e171a19a2c8eb6453f06a3..fb8dba3ab2cbae3f618be682938ebe28f8b74497 100644 (file)
@@ -1257,6 +1257,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
        resultRelInfo->ri_ReturningSlot = NULL;
        resultRelInfo->ri_TrigOldSlot = NULL;
        resultRelInfo->ri_TrigNewSlot = NULL;
+       resultRelInfo->ri_AllNullSlot = NULL;
        resultRelInfo->ri_MergeActions[MERGE_WHEN_MATCHED] = NIL;
        resultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = NIL;
        resultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = NIL;
index f71899463b8b984ba150c23a3cf73fb1b7cc41bb..7c539de5cf23005e7b4b3034491b4885fba7c761 100644 (file)
@@ -1242,6 +1242,34 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
        return relInfo->ri_ReturningSlot;
 }
 
+/*
+ * Return a relInfo's all-NULL tuple slot for processing returning tuples.
+ *
+ * Note: this slot is intentionally filled with NULLs in every column, and
+ * should be considered read-only --- the caller must not update it.
+ */
+TupleTableSlot *
+ExecGetAllNullSlot(EState *estate, ResultRelInfo *relInfo)
+{
+       if (relInfo->ri_AllNullSlot == NULL)
+       {
+               Relation        rel = relInfo->ri_RelationDesc;
+               MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+               TupleTableSlot *slot;
+
+               slot = ExecInitExtraTupleSlot(estate,
+                                                                         RelationGetDescr(rel),
+                                                                         table_slot_callbacks(rel));
+               ExecStoreAllNullTuple(slot);
+
+               relInfo->ri_AllNullSlot = slot;
+
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       return relInfo->ri_AllNullSlot;
+}
+
 /*
  * Return the map needed to convert given child result relation's tuples to
  * the rowtype of the query's main target ("root") relation.  Note that a
index 1af8c9caf6c51e0314775684bf56851a40908718..bc82e035ba281fd8c26ea9322a97a139b7e736f9 100644 (file)
@@ -101,6 +101,13 @@ typedef struct ModifyTableContext
         */
        TM_FailureData tmfd;
 
+       /*
+        * The tuple deleted when doing a cross-partition UPDATE with a RETURNING
+        * clause that refers to OLD columns (converted to the root's tuple
+        * descriptor).
+        */
+       TupleTableSlot *cpDeletedSlot;
+
        /*
         * The tuple projected by the INSERT's RETURNING clause, when doing a
         * cross-partition UPDATE
@@ -243,34 +250,81 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
 /*
  * ExecProcessReturning --- evaluate a RETURNING list
  *
+ * context: context for the ModifyTable operation
  * resultRelInfo: current result rel
- * tupleSlot: slot holding tuple actually inserted/updated/deleted
+ * cmdType: operation/merge action performed (INSERT, UPDATE, or DELETE)
+ * oldSlot: slot holding old tuple deleted or updated
+ * newSlot: slot holding new tuple inserted or updated
  * planSlot: slot holding tuple returned by top subplan node
  *
- * Note: If tupleSlot is NULL, the FDW should have already provided econtext's
- * scan tuple.
+ * Note: If oldSlot and newSlot are NULL, the FDW should have already provided
+ * econtext's scan tuple and its old & new tuples are not needed (FDW direct-
+ * modify is disabled if the RETURNING list refers to any OLD/NEW values).
  *
  * Returns a slot holding the result tuple
  */
 static TupleTableSlot *
-ExecProcessReturning(ResultRelInfo *resultRelInfo,
-                                        TupleTableSlot *tupleSlot,
+ExecProcessReturning(ModifyTableContext *context,
+                                        ResultRelInfo *resultRelInfo,
+                                        CmdType cmdType,
+                                        TupleTableSlot *oldSlot,
+                                        TupleTableSlot *newSlot,
                                         TupleTableSlot *planSlot)
 {
+       EState     *estate = context->estate;
        ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
        ExprContext *econtext = projectReturning->pi_exprContext;
 
        /* Make tuple and any needed join variables available to ExecProject */
-       if (tupleSlot)
-               econtext->ecxt_scantuple = tupleSlot;
+       switch (cmdType)
+       {
+               case CMD_INSERT:
+               case CMD_UPDATE:
+                       /* return new tuple by default */
+                       if (newSlot)
+                               econtext->ecxt_scantuple = newSlot;
+                       break;
+
+               case CMD_DELETE:
+                       /* return old tuple by default */
+                       if (oldSlot)
+                               econtext->ecxt_scantuple = oldSlot;
+                       break;
+
+               default:
+                       elog(ERROR, "unrecognized commandType: %d", (int) cmdType);
+       }
        econtext->ecxt_outertuple = planSlot;
 
+       /* Make old/new tuples available to ExecProject, if required */
+       if (oldSlot)
+               econtext->ecxt_oldtuple = oldSlot;
+       else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)
+               econtext->ecxt_oldtuple = ExecGetAllNullSlot(estate, resultRelInfo);
+       else
+               econtext->ecxt_oldtuple = NULL; /* No references to OLD columns */
+
+       if (newSlot)
+               econtext->ecxt_newtuple = newSlot;
+       else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW)
+               econtext->ecxt_newtuple = ExecGetAllNullSlot(estate, resultRelInfo);
+       else
+               econtext->ecxt_newtuple = NULL; /* No references to NEW columns */
+
        /*
-        * RETURNING expressions might reference the tableoid column, so
-        * reinitialize tts_tableOid before evaluating them.
+        * Tell ExecProject whether or not the OLD/NEW rows actually exist.  This
+        * information is required to evaluate ReturningExpr nodes and also in
+        * ExecEvalSysVar() and ExecEvalWholeRowVar().
         */
-       econtext->ecxt_scantuple->tts_tableOid =
-               RelationGetRelid(resultRelInfo->ri_RelationDesc);
+       if (oldSlot == NULL)
+               projectReturning->pi_state.flags |= EEO_FLAG_OLD_IS_NULL;
+       else
+               projectReturning->pi_state.flags &= ~EEO_FLAG_OLD_IS_NULL;
+
+       if (newSlot == NULL)
+               projectReturning->pi_state.flags |= EEO_FLAG_NEW_IS_NULL;
+       else
+               projectReturning->pi_state.flags &= ~EEO_FLAG_NEW_IS_NULL;
 
        /* Compute the RETURNING expressions */
        return ExecProject(projectReturning);
@@ -1204,7 +1258,56 @@ ExecInsert(ModifyTableContext *context,
 
        /* Process RETURNING if present */
        if (resultRelInfo->ri_projectReturning)
-               result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+       {
+               TupleTableSlot *oldSlot = NULL;
+
+               /*
+                * If this is part of a cross-partition UPDATE, and the RETURNING list
+                * refers to any OLD columns, ExecDelete() will have saved the tuple
+                * deleted from the original partition, which we must use here to
+                * compute the OLD column values.  Otherwise, all OLD column values
+                * will be NULL.
+                */
+               if (context->cpDeletedSlot)
+               {
+                       TupleConversionMap *tupconv_map;
+
+                       /*
+                        * Convert the OLD tuple to the new partition's format/slot, if
+                        * needed.  Note that ExceDelete() already converted it to the
+                        * root's partition's format/slot.
+                        */
+                       oldSlot = context->cpDeletedSlot;
+                       tupconv_map = ExecGetRootToChildMap(resultRelInfo, estate);
+                       if (tupconv_map != NULL)
+                       {
+                               oldSlot = execute_attr_map_slot(tupconv_map->attrMap,
+                                                                                               oldSlot,
+                                                                                               ExecGetReturningSlot(estate,
+                                                                                                                                        resultRelInfo));
+
+                               oldSlot->tts_tableOid = context->cpDeletedSlot->tts_tableOid;
+                               ItemPointerCopy(&context->cpDeletedSlot->tts_tid, &oldSlot->tts_tid);
+                       }
+               }
+
+               result = ExecProcessReturning(context, resultRelInfo, CMD_INSERT,
+                                                                         oldSlot, slot, planSlot);
+
+               /*
+                * For a cross-partition UPDATE, release the old tuple, first making
+                * sure that the result slot has a local copy of any pass-by-reference
+                * values.
+                */
+               if (context->cpDeletedSlot)
+               {
+                       ExecMaterializeSlot(result);
+                       ExecClearTuple(oldSlot);
+                       if (context->cpDeletedSlot != oldSlot)
+                               ExecClearTuple(context->cpDeletedSlot);
+                       context->cpDeletedSlot = NULL;
+               }
+       }
 
        if (inserted_tuple)
                *inserted_tuple = slot;
@@ -1442,6 +1545,7 @@ ExecDelete(ModifyTableContext *context,
        Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
        TupleTableSlot *slot = NULL;
        TM_Result       result;
+       bool            saveOld;
 
        if (tupleDeleted)
                *tupleDeleted = false;
@@ -1676,8 +1780,17 @@ ldelete:
 
        ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);
 
-       /* Process RETURNING if present and if requested */
-       if (processReturning && resultRelInfo->ri_projectReturning)
+       /*
+        * Process RETURNING if present and if requested.
+        *
+        * If this is part of a cross-partition UPDATE, and the RETURNING list
+        * refers to any OLD column values, save the old tuple here for later
+        * processing of the RETURNING list by ExecInsert().
+        */
+       saveOld = changingPart && resultRelInfo->ri_projectReturning &&
+               resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD;
+
+       if (resultRelInfo->ri_projectReturning && (processReturning || saveOld))
        {
                /*
                 * We have to put the target tuple into a slot, which means first we
@@ -1705,7 +1818,41 @@ ldelete:
                        }
                }
 
-               rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot);
+               /*
+                * If required, save the old tuple for later processing of the
+                * RETURNING list by ExecInsert().
+                */
+               if (saveOld)
+               {
+                       TupleConversionMap *tupconv_map;
+
+                       /*
+                        * Convert the tuple into the root partition's format/slot, if
+                        * needed.  ExecInsert() will then convert it to the new
+                        * partition's format/slot, if necessary.
+                        */
+                       tupconv_map = ExecGetChildToRootMap(resultRelInfo);
+                       if (tupconv_map != NULL)
+                       {
+                               ResultRelInfo *rootRelInfo = context->mtstate->rootResultRelInfo;
+                               TupleTableSlot *oldSlot = slot;
+
+                               slot = execute_attr_map_slot(tupconv_map->attrMap,
+                                                                                        slot,
+                                                                                        ExecGetReturningSlot(estate,
+                                                                                                                                 rootRelInfo));
+
+                               slot->tts_tableOid = oldSlot->tts_tableOid;
+                               ItemPointerCopy(&oldSlot->tts_tid, &slot->tts_tid);
+                       }
+
+                       context->cpDeletedSlot = slot;
+
+                       return NULL;
+               }
+
+               rslot = ExecProcessReturning(context, resultRelInfo, CMD_DELETE,
+                                                                        slot, NULL, context->planSlot);
 
                /*
                 * Before releasing the target tuple again, make sure rslot has a
@@ -1758,6 +1905,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
        bool            tuple_deleted;
        TupleTableSlot *epqslot = NULL;
 
+       context->cpDeletedSlot = NULL;
        context->cpUpdateReturningSlot = NULL;
        *retry_slot = NULL;
 
@@ -2258,6 +2406,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
  *             the planSlot.  oldtuple is passed to foreign table triggers; it is
  *             NULL when the foreign table has no relevant triggers.
  *
+ *             oldSlot contains the old tuple value.
  *             slot contains the new tuple value to be stored.
  *             planSlot is the output of the ModifyTable's subplan; we use it
  *             to access values from other input tables (for RETURNING),
@@