Skip to content

Commit 1fedbcc

Browse files
committed
Fix rewriter to set hasModifyingCTE correctly on rewritten queries.
If we copy data-modifying CTEs from the original query to a replacement query (from a DO INSTEAD rule), we must set hasModifyingCTE properly in the replacement query. Failure to do this can cause various unpleasantness, such as unsafe usage of parallel plans. The code also neglected to propagate hasRecursive, though that's only cosmetic at the moment. A difficulty arises if the rule action is an INSERT...SELECT. We attach the original query's RTEs and CTEs to the sub-SELECT Query, but data-modifying CTEs are only allowed to appear in the topmost Query. For the moment, throw an error in such cases. It would probably be possible to avoid this error by attaching the CTEs to the top INSERT Query instead; but that would require a bunch of new code to adjust ctelevelsup references. Given the narrowness of the use-case, and the need to back-patch this fix, it does not seem worth the trouble for now. We can revisit this if we get field complaints. Per report from Greg Nancarrow. Back-patch to all supported branches. (The test case added here does not fail before v10, but there are plenty of places checking top-level hasModifyingCTE in 9.6, so I have no doubt that this code change is necessary there too.) Greg Nancarrow and Tom Lane Discussion: https://postgr.es/m/CAJcOf-f68DT=26YAMz_i0+Au3TcLO5oiHY5=fL6Sfuits6r+_w@mail.gmail.com Discussion: https://postgr.es/m/CAJcOf-fAdj=nDKMsRhQzndm-O13NY4dL6xGcEvdX5Xvbbi0V7g@mail.gmail.com
1 parent 2eb09f2 commit 1fedbcc

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

src/backend/rewrite/rewriteHandler.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ rewriteRuleAction(Query *parsetree,
530530
*
531531
* This could possibly be fixed by using some sort of internally
532532
* generated ID, instead of names, to link CTE RTEs to their CTEs.
533+
* However, decompiling the results would be quite confusing; note the
534+
* merge of hasRecursive flags below, which could change the apparent
535+
* semantics of such redundantly-named CTEs.
533536
*/
534537
foreach(lc, parsetree->cteList)
535538
{
@@ -551,6 +554,26 @@ rewriteRuleAction(Query *parsetree,
551554
/* OK, it's safe to combine the CTE lists */
552555
sub_action->cteList = list_concat(sub_action->cteList,
553556
copyObject(parsetree->cteList));
557+
/* ... and don't forget about the associated flags */
558+
sub_action->hasRecursive |= parsetree->hasRecursive;
559+
sub_action->hasModifyingCTE |= parsetree->hasModifyingCTE;
560+
561+
/*
562+
* If rule_action is different from sub_action (i.e., the rule action
563+
* is an INSERT...SELECT), then we might have just added some
564+
* data-modifying CTEs that are not at the top query level. This is
565+
* disallowed by the parser and we mustn't generate such trees here
566+
* either, so throw an error.
567+
*
568+
* Conceivably such cases could be supported by attaching the original
569+
* query's CTEs to rule_action not sub_action. But to do that, we'd
570+
* have to increment ctelevelsup in RTEs and SubLinks copied from the
571+
* original query. For now, it doesn't seem worth the trouble.
572+
*/
573+
if (sub_action->hasModifyingCTE && rule_action != sub_action)
574+
ereport(ERROR,
575+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
576+
errmsg("INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH")));
554577
}
555578

556579
/*

src/test/regress/expected/with.out

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1656,7 +1656,7 @@ SELECT * FROM bug6051;
16561656
CREATE TEMP TABLE bug6051_2 (i int);
16571657
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
16581658
INSERT INTO bug6051_2
1659-
SELECT NEW.i;
1659+
VALUES(NEW.i);
16601660
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
16611661
INSERT INTO bug6051 SELECT * FROM t1;
16621662
SELECT * FROM bug6051;
@@ -1672,6 +1672,41 @@ SELECT * FROM bug6051_2;
16721672
3
16731673
(3 rows)
16741674

1675+
-- check INSERT...SELECT rule actions are disallowed on commands
1676+
-- that have modifyingCTEs
1677+
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
1678+
INSERT INTO bug6051_2
1679+
SELECT NEW.i;
1680+
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
1681+
INSERT INTO bug6051 SELECT * FROM t1;
1682+
ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH
1683+
-- silly example to verify that hasModifyingCTE flag is propagated
1684+
CREATE TEMP TABLE bug6051_3 AS
1685+
SELECT a FROM generate_series(11,13) AS a;
1686+
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
1687+
SELECT i FROM bug6051_2;
1688+
BEGIN; SET LOCAL force_parallel_mode = on;
1689+
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
1690+
INSERT INTO bug6051_3 SELECT * FROM t1;
1691+
i
1692+
---
1693+
1
1694+
2
1695+
3
1696+
1
1697+
2
1698+
3
1699+
1
1700+
2
1701+
3
1702+
(9 rows)
1703+
1704+
COMMIT;
1705+
SELECT * FROM bug6051_3;
1706+
a
1707+
---
1708+
(0 rows)
1709+
16751710
-- a truly recursive CTE in the same list
16761711
WITH RECURSIVE t(a) AS (
16771712
SELECT 0

src/test/regress/sql/with.sql

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,14 +765,39 @@ CREATE TEMP TABLE bug6051_2 (i int);
765765

766766
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
767767
INSERT INTO bug6051_2
768-
SELECT NEW.i;
768+
VALUES(NEW.i);
769769

770770
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
771771
INSERT INTO bug6051 SELECT * FROM t1;
772772

773773
SELECT * FROM bug6051;
774774
SELECT * FROM bug6051_2;
775775

776+
-- check INSERT...SELECT rule actions are disallowed on commands
777+
-- that have modifyingCTEs
778+
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
779+
INSERT INTO bug6051_2
780+
SELECT NEW.i;
781+
782+
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
783+
INSERT INTO bug6051 SELECT * FROM t1;
784+
785+
-- silly example to verify that hasModifyingCTE flag is propagated
786+
CREATE TEMP TABLE bug6051_3 AS
787+
SELECT a FROM generate_series(11,13) AS a;
788+
789+
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
790+
SELECT i FROM bug6051_2;
791+
792+
BEGIN; SET LOCAL force_parallel_mode = on;
793+
794+
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
795+
INSERT INTO bug6051_3 SELECT * FROM t1;
796+
797+
COMMIT;
798+
799+
SELECT * FROM bug6051_3;
800+
776801
-- a truly recursive CTE in the same list
777802
WITH RECURSIVE t(a) AS (
778803
SELECT 0

0 commit comments

Comments
 (0)