}
}
- /* OK, it's safe to combine the CTE lists */
+ /*
+ * OK, it's safe to combine the CTE lists. Beware that RewriteQuery
+ * knows we concatenate the lists in this order.
+ */
sub_action->cteList = list_concat(sub_action->cteList,
copyObject(parsetree->cteList));
/* ... and don't forget about the associated flags */
* orig_rt_length is the length of the originating query's rtable, for product
* queries created by fireRules(), and 0 otherwise. This is used to skip any
* already-processed VALUES RTEs from the original query.
+ *
+ * num_ctes_processed is the number of CTEs at the end of the query's cteList
+ * that have already been rewritten, and must not be rewritten again.
*/
static List *
-RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
+RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length,
+ int num_ctes_processed)
{
CmdType event = parsetree->commandType;
bool instead = false;
* First, recursively process any insert/update/delete/merge statements in
* WITH clauses. (We have to do this first because the WITH clauses may
* get copied into rule actions below.)
+ *
+ * Any new WITH clauses from rule actions are processed when we recurse
+ * into product queries below. However, when recursing, we must take care
+ * to avoid rewriting a CTE query more than once (because expanding
+ * generated columns in the targetlist more than once would fail). Since
+ * new CTEs from product queries are added to the start of the list (see
+ * rewriteRuleAction), we just skip the last num_ctes_processed items.
*/
foreach(lc1, parsetree->cteList)
{
CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc1);
Query *ctequery = castNode(Query, cte->ctequery);
+ int i = foreach_current_index(lc1);
List *newstuff;
+ /* Skip already-processed CTEs at the end of the list */
+ if (i >= list_length(parsetree->cteList) - num_ctes_processed)
+ break;
+
if (ctequery->commandType == CMD_SELECT)
continue;
- newstuff = RewriteQuery(ctequery, rewrite_events, 0);
+ newstuff = RewriteQuery(ctequery, rewrite_events, 0, 0);
/*
* Currently we can only handle unconditional, single-statement DO
errmsg("multi-statement DO INSTEAD rules are not supported for data-modifying statements in WITH")));
}
}
+ num_ctes_processed = list_length(parsetree->cteList);
/*
* If the statement is an insert, update, delete, or merge, adjust its
newstuff = RewriteQuery(pt, rewrite_events,
pt == parsetree ?
orig_rt_length :
- product_orig_rt_length);
+ product_orig_rt_length,
+ num_ctes_processed);
rewritten = list_concat(rewritten, newstuff);
}
*
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = RewriteQuery(parsetree, NIL, 0);
+ querylist = RewriteQuery(parsetree, NIL, 0, 0);
/*
* Step 2
---
(0 rows)
+-- check that recursive CTE processing doesn't rewrite a CTE more than once
+-- (must not try to expand GENERATED ALWAYS IDENTITY columns more than once)
+CREATE TEMP TABLE id_alw1 (i int GENERATED ALWAYS AS IDENTITY);
+CREATE TEMP TABLE id_alw2 (i int GENERATED ALWAYS AS IDENTITY);
+CREATE TEMP VIEW id_alw2_view AS SELECT * FROM id_alw2;
+CREATE TEMP TABLE id_alw3 (i int GENERATED ALWAYS AS IDENTITY);
+CREATE RULE id_alw3_ins AS ON INSERT TO id_alw3 DO INSTEAD
+ WITH t1 AS (INSERT INTO id_alw1 DEFAULT VALUES RETURNING i)
+ INSERT INTO id_alw2_view DEFAULT VALUES RETURNING i;
+CREATE TEMP VIEW id_alw3_view AS SELECT * FROM id_alw3;
+CREATE TEMP TABLE id_alw4 (i int GENERATED ALWAYS AS IDENTITY);
+WITH t4 AS (INSERT INTO id_alw4 DEFAULT VALUES RETURNING i)
+ INSERT INTO id_alw3_view DEFAULT VALUES RETURNING i;
+ i
+---
+ 1
+(1 row)
+
+SELECT * from id_alw1;
+ i
+---
+ 1
+(1 row)
+
+SELECT * from id_alw2;
+ i
+---
+ 1
+(1 row)
+
+SELECT * from id_alw3;
+ i
+---
+(0 rows)
+
+SELECT * from id_alw4;
+ i
+---
+ 1
+(1 row)
+
-- check case where CTE reference is removed due to optimization
EXPLAIN (VERBOSE, COSTS OFF)
SELECT q1 FROM
SELECT * FROM bug6051_3;
+-- check that recursive CTE processing doesn't rewrite a CTE more than once
+-- (must not try to expand GENERATED ALWAYS IDENTITY columns more than once)
+CREATE TEMP TABLE id_alw1 (i int GENERATED ALWAYS AS IDENTITY);
+
+CREATE TEMP TABLE id_alw2 (i int GENERATED ALWAYS AS IDENTITY);
+CREATE TEMP VIEW id_alw2_view AS SELECT * FROM id_alw2;
+
+CREATE TEMP TABLE id_alw3 (i int GENERATED ALWAYS AS IDENTITY);
+CREATE RULE id_alw3_ins AS ON INSERT TO id_alw3 DO INSTEAD
+ WITH t1 AS (INSERT INTO id_alw1 DEFAULT VALUES RETURNING i)
+ INSERT INTO id_alw2_view DEFAULT VALUES RETURNING i;
+CREATE TEMP VIEW id_alw3_view AS SELECT * FROM id_alw3;
+
+CREATE TEMP TABLE id_alw4 (i int GENERATED ALWAYS AS IDENTITY);
+
+WITH t4 AS (INSERT INTO id_alw4 DEFAULT VALUES RETURNING i)
+ INSERT INTO id_alw3_view DEFAULT VALUES RETURNING i;
+
+SELECT * from id_alw1;
+SELECT * from id_alw2;
+SELECT * from id_alw3;
+SELECT * from id_alw4;
+
-- check case where CTE reference is removed due to optimization
EXPLAIN (VERBOSE, COSTS OFF)
SELECT q1 FROM