]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix mishandling of OLD/NEW references in subqueries in rule actions.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 25 Feb 2023 14:48:08 +0000 (14:48 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 25 Feb 2023 14:48:08 +0000 (14:48 +0000)
If a rule action contains a subquery that refers to columns from OLD
or NEW, then those are really lateral references, and the planner will
complain if it sees such things in a subquery that isn't marked as
lateral. However, at rule-definition time, the user isn't required to
mark the subquery with LATERAL, and so it can fail when the rule is
used.

Fix this by marking such subqueries as lateral in the rewriter, at the
point where they're used.

Dean Rasheed and Tom Lane, per report from Alexander Lakhin.
Back-patch to all supported branches.

Discussion: https://postgr.es/m/5e09da43-aaba-7ea7-0a51-a2eb981b058b%40gmail.com

src/backend/rewrite/rewriteHandler.c
src/test/regress/expected/rules.out
src/test/regress/sql/rules.sql

index 400ed36a7ac4710abe2ca8290eaf3dccc757900e..10e41af50051f760d408b77d3a5a1a1ef379ea4d 100644 (file)
@@ -28,6 +28,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
@@ -343,6 +344,7 @@ rewriteRuleAction(Query *parsetree,
        Query      *sub_action;
        Query     **sub_action_ptr;
        acquireLocksOnSubLinks_context context;
+       ListCell   *lc;
 
        context.for_execute = true;
 
@@ -381,6 +383,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
@@ -422,8 +441,6 @@ rewriteRuleAction(Query *parsetree,
         */
        if (parsetree->hasSubLinks && !sub_action->hasSubLinks)
        {
-               ListCell   *lc;
-
                foreach(lc, parsetree->rtable)
                {
                        RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
@@ -521,8 +538,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
index 481389aa4a566bc1abe1083f8d31f851782484a3..4c632005ee85ecfa37bb0770b3a35d6979ebf48e 100644 (file)
@@ -2915,6 +2915,31 @@ Rules:
   RETURNING trgt.f1,
     trgt.f2
 
+--
+-- 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
 --
index ac53399c52a252661c742a28baf0df9760293e4a..fd316875c4d47b86dd8c9a5aa3d60244963bfd69 100644 (file)
@@ -1053,6 +1053,23 @@ create rule r7 as on delete to rules_src do instead
 -- check display of all rules added above
 \d+ rules_src
 
+--
+-- 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
 --