diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index b6452e9b36..a614e3f5bd 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree, Query *sub_action; Query **sub_action_ptr; acquireLocksOnSubLinks_context context; + ListCell *lc; context.for_execute = true; @@ -391,6 +392,23 @@ rewriteRuleAction(Query *parsetree, ChangeVarNodes(rule_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0); + /* + * Mark any subquery RTEs in the rule action as LATERAL if they contain + * Vars referring to the current query level (references to NEW/OLD). + * Those really are lateral references, but we've historically not + * required users to mark such subqueries with LATERAL explicitly. But + * the planner will complain if such Vars exist in a non-LATERAL subquery, + * so we have to fix things up here. + */ + foreach(lc, sub_action->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + + if (rte->rtekind == RTE_SUBQUERY && !rte->lateral && + contain_vars_of_level((Node *) rte->subquery, 1)) + rte->lateral = true; + } + /* * Generate expanded rtable consisting of main parsetree's rtable plus * rule action's rtable; this becomes the complete rtable for the rule @@ -439,8 +457,6 @@ rewriteRuleAction(Query *parsetree, */ if (parsetree->hasSubLinks && !sub_action->hasSubLinks) { - ListCell *lc; - foreach(lc, parsetree->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); @@ -538,8 +554,6 @@ rewriteRuleAction(Query *parsetree, */ if (parsetree->cteList != NIL && sub_action->commandType != CMD_UTILITY) { - ListCell *lc; - /* * Annoying implementation restriction: because CTEs are identified by * name within a cteList, we can't merge a CTE from the original query diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a3a5a62329..e953d1f515 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -3083,6 +3083,31 @@ Rules: drop table rule_t1, rule_dest; -- +-- Test implicit LATERAL references to old/new in rules +-- +CREATE TABLE rule_t1(a int, b text DEFAULT 'xxx', c int); +CREATE VIEW rule_v1 AS SELECT * FROM rule_t1; +CREATE RULE v1_ins AS ON INSERT TO rule_v1 + DO ALSO INSERT INTO rule_t1 + SELECT * FROM (SELECT a + 10 FROM rule_t1 WHERE a = NEW.a) tt; +CREATE RULE v1_upd AS ON UPDATE TO rule_v1 + DO ALSO UPDATE rule_t1 t + SET c = tt.a * 10 + FROM (SELECT a FROM rule_t1 WHERE a = OLD.a) tt WHERE t.a = tt.a; +INSERT INTO rule_v1 VALUES (1, 'a'), (2, 'b'); +UPDATE rule_v1 SET b = upper(b); +SELECT * FROM rule_t1; + a | b | c +----+-----+----- + 1 | A | 10 + 2 | B | 20 + 11 | XXX | 110 + 12 | XXX | 120 +(4 rows) + +DROP TABLE rule_t1 CASCADE; +NOTICE: drop cascades to view rule_v1 +-- -- check alter rename rule -- CREATE TABLE rule_t1 (a INT); diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index ac1a4ce554..4caab3434b 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -1046,6 +1046,23 @@ create rule rr as on update to rule_t1 do instead UPDATE rule_dest trgt \d+ rule_t1 drop table rule_t1, rule_dest; +-- +-- Test implicit LATERAL references to old/new in rules +-- +CREATE TABLE rule_t1(a int, b text DEFAULT 'xxx', c int); +CREATE VIEW rule_v1 AS SELECT * FROM rule_t1; +CREATE RULE v1_ins AS ON INSERT TO rule_v1 + DO ALSO INSERT INTO rule_t1 + SELECT * FROM (SELECT a + 10 FROM rule_t1 WHERE a = NEW.a) tt; +CREATE RULE v1_upd AS ON UPDATE TO rule_v1 + DO ALSO UPDATE rule_t1 t + SET c = tt.a * 10 + FROM (SELECT a FROM rule_t1 WHERE a = OLD.a) tt WHERE t.a = tt.a; +INSERT INTO rule_v1 VALUES (1, 'a'), (2, 'b'); +UPDATE rule_v1 SET b = upper(b); +SELECT * FROM rule_t1; +DROP TABLE rule_t1 CASCADE; + -- -- check alter rename rule --