Skip to content

Commit cbba6ba

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 ddfc729 commit cbba6ba

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
@@ -1715,7 +1715,7 @@ SELECT * FROM bug6051;
17151715
CREATE TEMP TABLE bug6051_2 (i int);
17161716
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
17171717
INSERT INTO bug6051_2
1718-
SELECT NEW.i;
1718+
VALUES(NEW.i);
17191719
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
17201720
INSERT INTO bug6051 SELECT * FROM t1;
17211721
SELECT * FROM bug6051;
@@ -1731,6 +1731,41 @@ SELECT * FROM bug6051_2;
17311731
3
17321732
(3 rows)
17331733

1734+
-- check INSERT...SELECT rule actions are disallowed on commands
1735+
-- that have modifyingCTEs
1736+
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
1737+
INSERT INTO bug6051_2
1738+
SELECT NEW.i;
1739+
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
1740+
INSERT INTO bug6051 SELECT * FROM t1;
1741+
ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH
1742+
-- silly example to verify that hasModifyingCTE flag is propagated
1743+
CREATE TEMP TABLE bug6051_3 AS
1744+
SELECT a FROM generate_series(11,13) AS a;
1745+
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
1746+
SELECT i FROM bug6051_2;
1747+
BEGIN; SET LOCAL force_parallel_mode = on;
1748+
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
1749+
INSERT INTO bug6051_3 SELECT * FROM t1;
1750+
i
1751+
---
1752+
1
1753+
2
1754+
3
1755+
1
1756+
2
1757+
3
1758+
1
1759+
2
1760+
3
1761+
(9 rows)
1762+
1763+
COMMIT;
1764+
SELECT * FROM bug6051_3;
1765+
a
1766+
---
1767+
(0 rows)
1768+
17341769
-- a truly recursive CTE in the same list
17351770
WITH RECURSIVE t(a) AS (
17361771
SELECT 0

src/test/regress/sql/with.sql

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

810810
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
811811
INSERT INTO bug6051_2
812-
SELECT NEW.i;
812+
VALUES(NEW.i);
813813

814814
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
815815
INSERT INTO bug6051 SELECT * FROM t1;
816816

817817
SELECT * FROM bug6051;
818818
SELECT * FROM bug6051_2;
819819

820+
-- check INSERT...SELECT rule actions are disallowed on commands
821+
-- that have modifyingCTEs
822+
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
823+
INSERT INTO bug6051_2
824+
SELECT NEW.i;
825+
826+
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
827+
INSERT INTO bug6051 SELECT * FROM t1;
828+
829+
-- silly example to verify that hasModifyingCTE flag is propagated
830+
CREATE TEMP TABLE bug6051_3 AS
831+
SELECT a FROM generate_series(11,13) AS a;
832+
833+
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
834+
SELECT i FROM bug6051_2;
835+
836+
BEGIN; SET LOCAL force_parallel_mode = on;
837+
838+
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
839+
INSERT INTO bug6051_3 SELECT * FROM t1;
840+
841+
COMMIT;
842+
843+
SELECT * FROM bug6051_3;
844+
820845
-- a truly recursive CTE in the same list
821846
WITH RECURSIVE t(a) AS (
822847
SELECT 0

0 commit comments

Comments
 (0)