diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 7be347c3d7..db1d7d3366 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -525,6 +525,9 @@ rewriteRuleAction(Query *parsetree, * * This could possibly be fixed by using some sort of internally * generated ID, instead of names, to link CTE RTEs to their CTEs. + * However, decompiling the results would be quite confusing; note the + * merge of hasRecursive flags below, which could change the apparent + * semantics of such redundantly-named CTEs. */ foreach(lc, parsetree->cteList) { @@ -546,6 +549,26 @@ rewriteRuleAction(Query *parsetree, /* OK, it's safe to combine the CTE lists */ sub_action->cteList = list_concat(sub_action->cteList, copyObject(parsetree->cteList)); + /* ... and don't forget about the associated flags */ + sub_action->hasRecursive |= parsetree->hasRecursive; + sub_action->hasModifyingCTE |= parsetree->hasModifyingCTE; + + /* + * If rule_action is different from sub_action (i.e., the rule action + * is an INSERT...SELECT), then we might have just added some + * data-modifying CTEs that are not at the top query level. This is + * disallowed by the parser and we mustn't generate such trees here + * either, so throw an error. + * + * Conceivably such cases could be supported by attaching the original + * query's CTEs to rule_action not sub_action. But to do that, we'd + * have to increment ctelevelsup in RTEs and SubLinks copied from the + * original query. For now, it doesn't seem worth the trouble. + */ + if (sub_action->hasModifyingCTE && rule_action != sub_action) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH"))); } /* diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 9b8c040dbe..b81620136a 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -1656,7 +1656,7 @@ SELECT * FROM bug6051; CREATE TEMP TABLE bug6051_2 (i int); CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD INSERT INTO bug6051_2 - SELECT NEW.i; + VALUES(NEW.i); WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) INSERT INTO bug6051 SELECT * FROM t1; SELECT * FROM bug6051; @@ -1672,6 +1672,41 @@ SELECT * FROM bug6051_2; 3 (3 rows) +-- check INSERT...SELECT rule actions are disallowed on commands +-- that have modifyingCTEs +CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + SELECT NEW.i; +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; +ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; +BEGIN; SET LOCAL force_parallel_mode = on; +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + i +--- + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 +(9 rows) + +COMMIT; +SELECT * FROM bug6051_3; + a +--- +(0 rows) + -- a truly recursive CTE in the same list WITH RECURSIVE t(a) AS ( SELECT 0 diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index b47880fa7b..bc6360eb9b 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -765,7 +765,7 @@ CREATE TEMP TABLE bug6051_2 (i int); CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD INSERT INTO bug6051_2 - SELECT NEW.i; + VALUES(NEW.i); WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) INSERT INTO bug6051 SELECT * FROM t1; @@ -773,6 +773,31 @@ INSERT INTO bug6051 SELECT * FROM t1; SELECT * FROM bug6051; SELECT * FROM bug6051_2; +-- check INSERT...SELECT rule actions are disallowed on commands +-- that have modifyingCTEs +CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + SELECT NEW.i; + +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; + +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; + +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; + +BEGIN; SET LOCAL force_parallel_mode = on; + +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + +COMMIT; + +SELECT * FROM bug6051_3; + -- a truly recursive CTE in the same list WITH RECURSIVE t(a) AS ( SELECT 0