Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Fix EXPLAIN output for subplans in MERGE.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Sun, 17 Mar 2024 10:20:20 +0000 (10:20 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Sun, 17 Mar 2024 10:20:20 +0000 (10:20 +0000)
Given a subplan in a MERGE query, EXPLAIN would sometimes fail to
properly display expressions involving Params referencing variables in
other parts of the plan tree.

This would affect subplans outside the topmost join plan node, for
which expansion of Params would go via the top-level ModifyTable plan
node.  The problem was that "inner_tlist" for the ModifyTable node's
deparse_namespace was set to the join node's targetlist, but
"inner_plan" was set to the ModifyTable node itself, rather than the
join node, leading to incorrect results when descending to the
referenced variable.

Fix and backpatch to v15, where MERGE was introduced.

Discussion: https://postgr.es/m/CAEZATCWAv-sZuH%2BwG5xJ-%2BGt7qGNGX8wUQd3XYydMFDKgRB9nw%40mail.gmail.com

src/backend/utils/adt/ruleutils.c
src/test/regress/expected/merge.out
src/test/regress/sql/merge.sql

index b7d5ddabe14da308c98aa01bfc77193b779d6582..a4277719aadc1732d026ccc5ee46813194a41dc5 100644 (file)
@@ -5013,8 +5013,11 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
     * For a WorkTableScan, locate the parent RecursiveUnion plan node and use
     * that as INNER referent.
     *
-    * For MERGE, make the inner tlist point to the merge source tlist, which
-    * is same as the targetlist that the ModifyTable's source plan provides.
+    * For MERGE, pretend the ModifyTable's source plan (its outer plan) is
+    * INNER referent.  This is the join from the target relation to the data
+    * source, and all INNER_VAR Vars in other parts of the query refer to its
+    * targetlist.
+    *
     * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the
     * excluded expression's tlist. (Similar to the SubqueryScan we don't want
     * to reuse OUTER, it's used for RETURNING in some modify table cases,
@@ -5029,17 +5032,17 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
        dpns->inner_plan = find_recursive_union(dpns,
                                                (WorkTableScan *) plan);
    else if (IsA(plan, ModifyTable))
-       dpns->inner_plan = plan;
-   else
-       dpns->inner_plan = innerPlan(plan);
-
-   if (IsA(plan, ModifyTable))
    {
        if (((ModifyTable *) plan)->operation == CMD_MERGE)
-           dpns->inner_tlist = dpns->outer_tlist;
+           dpns->inner_plan = outerPlan(plan);
        else
-           dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
+           dpns->inner_plan = plan;
    }
+   else
+       dpns->inner_plan = innerPlan(plan);
+
+   if (IsA(plan, ModifyTable) && ((ModifyTable *) plan)->operation == CMD_INSERT)
+       dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
    else if (dpns->inner_plan)
        dpns->inner_tlist = dpns->inner_plan->targetlist;
    else
index 1fb53a3a546b1b84b093ca48466a7b528eb7d050..63a829ea4e84b336a1d91a947e4368b07e356db6 100644 (file)
@@ -1483,6 +1483,56 @@ WHEN MATCHED AND t.a < 10 THEN
 
 DROP TABLE ex_msource, ex_mtarget;
 DROP FUNCTION explain_merge(text);
+-- EXPLAIN SubPlans and InitPlans
+CREATE TABLE src (a int, b int, c int, d int);
+CREATE TABLE tgt (a int, b int, c int, d int);
+CREATE TABLE ref (ab int, cd int);
+EXPLAIN (verbose, costs off)
+MERGE INTO tgt t
+USING (SELECT *, (SELECT count(*) FROM ref r
+                   WHERE r.ab = s.a + s.b
+                     AND r.cd = s.c - s.d) cnt
+         FROM src s) s
+ON t.a = s.a AND t.b < s.cnt
+WHEN MATCHED AND t.c > s.cnt THEN
+  UPDATE SET (b, c) = (SELECT s.b, s.cnt);
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Merge on public.tgt t
+   ->  Hash Join
+         Output: t.ctid, s.a, s.b, s.c, s.d, s.ctid
+         Hash Cond: (t.a = s.a)
+         Join Filter: (t.b < (SubPlan 1))
+         ->  Seq Scan on public.tgt t
+               Output: t.ctid, t.a, t.b
+         ->  Hash
+               Output: s.a, s.b, s.c, s.d, s.ctid
+               ->  Seq Scan on public.src s
+                     Output: s.a, s.b, s.c, s.d, s.ctid
+         SubPlan 1
+           ->  Aggregate
+                 Output: count(*)
+                 ->  Seq Scan on public.ref r
+                       Output: r.ab, r.cd
+                       Filter: ((r.ab = (s.a + s.b)) AND (r.cd = (s.c - s.d)))
+   SubPlan 4
+     ->  Aggregate
+           Output: count(*)
+           ->  Seq Scan on public.ref r_2
+                 Output: r_2.ab, r_2.cd
+                 Filter: ((r_2.ab = (s.a + s.b)) AND (r_2.cd = (s.c - s.d)))
+   SubPlan 3 (returns $9,$10)
+     ->  Result
+           Output: s.b, $8
+           InitPlan 2 (returns $8)
+             ->  Aggregate
+                   Output: count(*)
+                   ->  Seq Scan on public.ref r_1
+                         Output: r_1.ab, r_1.cd
+                         Filter: ((r_1.ab = (s.a + s.b)) AND (r_1.cd = (s.c - s.d)))
+(32 rows)
+
+DROP TABLE src, tgt, ref;
 -- Subqueries
 BEGIN;
 MERGE INTO sq_target t
index 28b696f3ae00b39250cb644992407c333975c395..f6b8b9f5d7e3669adb418c1fdcea49409a55c592 100644 (file)
@@ -948,6 +948,23 @@ WHEN MATCHED AND t.a < 10 THEN
 DROP TABLE ex_msource, ex_mtarget;
 DROP FUNCTION explain_merge(text);
 
+-- EXPLAIN SubPlans and InitPlans
+CREATE TABLE src (a int, b int, c int, d int);
+CREATE TABLE tgt (a int, b int, c int, d int);
+CREATE TABLE ref (ab int, cd int);
+
+EXPLAIN (verbose, costs off)
+MERGE INTO tgt t
+USING (SELECT *, (SELECT count(*) FROM ref r
+                   WHERE r.ab = s.a + s.b
+                     AND r.cd = s.c - s.d) cnt
+         FROM src s) s
+ON t.a = s.a AND t.b < s.cnt
+WHEN MATCHED AND t.c > s.cnt THEN
+  UPDATE SET (b, c) = (SELECT s.b, s.cnt);
+
+DROP TABLE src, tgt, ref;
+
 -- Subqueries
 BEGIN;
 MERGE INTO sq_target t