diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c index 7ab15ac63e1020dd00b3ef27950615dc108b795b..7e10b8a530deb2850938c254e80eae9fe09d4cd9 100644 --- a/src/backend/executor/nodeCtescan.c +++ b/src/backend/executor/nodeCtescan.c @@ -205,7 +205,7 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags) * The Param slot associated with the CTE query is used to hold a pointer * to the CteState of the first CteScan node that initializes for this * CTE. This node will be the one that holds the shared state for all the - * CTEs. + * CTEs, particularly the shared tuplestore. */ prmdata = &(estate->es_param_exec_vals[node->cteParam]); Assert(prmdata->execPlan == NULL); @@ -294,7 +294,10 @@ ExecEndCteScan(CteScanState *node) * If I am the leader, free the tuplestore. */ if (node->leader == node) + { tuplestore_end(node->cte_table); + node->cte_table = NULL; + } } /* ---------------------------------------------------------------- @@ -312,26 +315,26 @@ ExecReScanCteScan(CteScanState *node) ExecScanReScan(&node->ss); - if (node->leader == node) + /* + * Clear the tuplestore if a new scan of the underlying CTE is required. + * This implicitly resets all the tuplestore's read pointers. Note that + * multiple CTE nodes might redundantly clear the tuplestore; that's OK, + * and not unduly expensive. We'll stop taking this path as soon as + * somebody has attempted to read something from the underlying CTE + * (thereby causing its chgParam to be cleared). + */ + if (node->leader->cteplanstate->chgParam != NULL) { - /* - * The leader is responsible for clearing the tuplestore if a new scan - * of the underlying CTE is required. - */ - if (node->cteplanstate->chgParam != NULL) - { - tuplestore_clear(tuplestorestate); - node->eof_cte = false; - } - else - { - tuplestore_select_read_pointer(tuplestorestate, node->readptr); - tuplestore_rescan(tuplestorestate); - } + tuplestore_clear(tuplestorestate); + node->leader->eof_cte = false; } else { - /* Not leader, so just rewind my own pointer */ + /* + * Else, just rewind my own pointer. Either the underlying CTE + * doesn't need a rescan (and we can re-read what's in the tuplestore + * now), or somebody else already took care of it. + */ tuplestore_select_read_pointer(tuplestorestate, node->readptr); tuplestore_rescan(tuplestorestate); } diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index a491b2ca61f82bee3978a1b2bf262bc646fe91bd..a7491bf6b93038b8b23368516d0681a563fa9831 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -1209,6 +1209,81 @@ SELECT * FROM outermost; ERROR: recursive reference to query "outermost" must not appear within a subquery LINE 2: WITH innermost as (SELECT 2 FROM outermost) ^ +-- +-- Test CTEs read in non-initialization orders +-- +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ALL ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + id_key | row_type | link +--------+----------+------ + 0 | base | 17 + 1 | true | 17 + 2 | true | 17 + 3 | true | 17 + 4 | true | 17 + 5 | true | 17 + 6 | true | 17 +(7 rows) + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + id_key | row_type | link +--------+----------+------ + 0 | base | 17 + 1 | true | 17 + 2 | true | 17 + 3 | true | 17 + 4 | true | 17 + 5 | true | 17 + 6 | true | 17 +(7 rows) + -- -- Data-modifying statements in WITH -- diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index 22fdddc4eeafa2de36c1eaf57b20ec0d44d27df3..684c8f13db1a692c78ba4b87355a03377bec83c0 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -574,6 +574,62 @@ WITH RECURSIVE outermost(x) AS ( ) SELECT * FROM outermost; +-- +-- Test CTEs read in non-initialization orders +-- + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ALL ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + -- -- Data-modifying statements in WITH --