From 000128bc7f591025d0c1ce539bb53c6ad00ab69c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 25 Feb 2011 23:53:34 -0500
Subject: [PATCH] Fix order of shutdown processing when CTEs contain
 inter-references.

We need ExecutorEnd to run the ModifyTable nodes to completion in
reverse order of initialization, not forward order.  Easily done
by constructing the list back-to-front.
---
 src/backend/executor/nodeModifyTable.c |  9 ++-
 src/test/regress/expected/with.out     | 76 ++++++++++++++++++++++++++
 src/test/regress/sql/with.sql          | 26 +++++++++
 3 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cf32dc56903..f10f70a17d3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1147,11 +1147,14 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
 	 * to estate->es_auxmodifytables so that it will be run to completion by
 	 * ExecPostprocessPlan.  (It'd actually work fine to add the primary
-	 * ModifyTable node too, but there's no need.)
+	 * ModifyTable node too, but there's no need.)  Note the use of lcons
+	 * not lappend: we need later-initialized ModifyTable nodes to be shut
+	 * down before earlier ones.  This ensures that we don't throw away
+	 * RETURNING rows that need to be seen by a later CTE subplan.
 	 */
 	if (!mtstate->canSetTag)
-		estate->es_auxmodifytables = lappend(estate->es_auxmodifytables,
-											 mtstate);
+		estate->es_auxmodifytables = lcons(mtstate,
+										   estate->es_auxmodifytables);
 
 	return mtstate;
 }
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index a82ae137977..3491ce42b5e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1543,6 +1543,82 @@ SELECT * FROM y;
   -400
 (22 rows)
 
+-- check that run to completion happens in proper ordering
+TRUNCATE TABLE y;
+INSERT INTO y SELECT generate_series(1, 3);
+CREATE TEMPORARY TABLE yy (a INTEGER);
+WITH RECURSIVE t1 AS (
+  INSERT INTO y SELECT * FROM y RETURNING *
+), t2 AS (
+  INSERT INTO yy SELECT * FROM t1 RETURNING *
+)
+SELECT 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+SELECT * FROM y;
+ a 
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(6 rows)
+
+SELECT * FROM yy;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+WITH RECURSIVE t1 AS (
+  INSERT INTO yy SELECT * FROM t2 RETURNING *
+), t2 AS (
+  INSERT INTO y SELECT * FROM y RETURNING *
+)
+SELECT 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+SELECT * FROM y;
+ a 
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(12 rows)
+
+SELECT * FROM yy;
+ a 
+---
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+ 1
+ 2
+ 3
+(9 rows)
+
 -- triggers
 TRUNCATE TABLE y;
 INSERT INTO y SELECT generate_series(1, 10);
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f5d5ebe1594..cf036ca2859 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -641,6 +641,32 @@ SELECT * FROM t LIMIT 10;
 
 SELECT * FROM y;
 
+-- check that run to completion happens in proper ordering
+
+TRUNCATE TABLE y;
+INSERT INTO y SELECT generate_series(1, 3);
+CREATE TEMPORARY TABLE yy (a INTEGER);
+
+WITH RECURSIVE t1 AS (
+  INSERT INTO y SELECT * FROM y RETURNING *
+), t2 AS (
+  INSERT INTO yy SELECT * FROM t1 RETURNING *
+)
+SELECT 1;
+
+SELECT * FROM y;
+SELECT * FROM yy;
+
+WITH RECURSIVE t1 AS (
+  INSERT INTO yy SELECT * FROM t2 RETURNING *
+), t2 AS (
+  INSERT INTO y SELECT * FROM y RETURNING *
+)
+SELECT 1;
+
+SELECT * FROM y;
+SELECT * FROM yy;
+
 -- triggers
 
 TRUNCATE TABLE y;
-- 
GitLab