Re: BUG #17606: There is still some glitch in 3f7323cbb fixing failure of MULTIEXPR_SUBLINK - Mailing list pgsql-bugs

From Tom Lane
Subject Re: BUG #17606: There is still some glitch in 3f7323cbb fixing failure of MULTIEXPR_SUBLINK
Date
Msg-id 3251631.1662483221@sss.pgh.pa.us
Whole thread Raw
In response to Re: BUG #17606: There is still some glitch in 3f7323cbb fixing failure of MULTIEXPR_SUBLINK  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-bugs
I wrote:
> After contemplating that for awhile, by far the least painful way
> seems to be to add the subLinkId to struct SubPlan.  We can get
> away with that in the back branches as long as we add it at the
> end, since SubPlans don't get recorded in the catalogs.

The attached seems to fix it.  While writing a test case I discovered
that the code I'd stolen from ruleutils.c is inadequate for the purpose.
That code was only designed to deal with pre-planning query trees,
so it only expects one Param per targetlist entry --- but cases like
multiple assignments to the same array column can get rolled up into
nests containing several Params (cf. rewriteTargetListIU).  So I abandoned
that tactic in favor of just writing a tree walker.  Andre's gut feeling
that we needed one was right.

            regards, tom lane

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8db09fda63..692b6c1559 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1719,6 +1719,7 @@ _copySubPlan(const SubPlan *from)
     COPY_NODE_FIELD(args);
     COPY_SCALAR_FIELD(startup_cost);
     COPY_SCALAR_FIELD(per_call_cost);
+    COPY_SCALAR_FIELD(subLinkId);

     return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 092fb37923..337be2f896 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -455,6 +455,7 @@ _equalSubPlan(const SubPlan *a, const SubPlan *b)
     COMPARE_NODE_FIELD(args);
     COMPARE_SCALAR_FIELD(startup_cost);
     COMPARE_SCALAR_FIELD(per_call_cost);
+    COMPARE_SCALAR_FIELD(subLinkId);

     return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4547eed43b..21ececf0c2 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1349,6 +1349,7 @@ _outSubPlan(StringInfo str, const SubPlan *node)
     WRITE_NODE_FIELD(args);
     WRITE_FLOAT_FIELD(startup_cost, "%.2f");
     WRITE_FLOAT_FIELD(per_call_cost, "%.2f");
+    WRITE_INT_FIELD(subLinkId);
 }

 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 62c945b6c5..7976b369ba 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2536,6 +2536,7 @@ _readSubPlan(void)
     READ_NODE_FIELD(args);
     READ_FLOAT_FIELD(startup_cost);
     READ_FLOAT_FIELD(per_call_cost);
+    READ_INT_FIELD(subLinkId);

     READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index a873d72b3a..7a4c85faf2 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -86,6 +86,7 @@ static bool subplan_is_hashable(Plan *plan);
 static bool testexpr_is_hashable(Node *testexpr, List *param_ids);
 static bool test_opexpr_is_hashable(OpExpr *testexpr, List *param_ids);
 static bool hash_ok_operator(OpExpr *expr);
+static bool SS_make_multiexprs_unique_walker(Node *node, void *context);
 static bool contain_dml(Node *node);
 static bool contain_dml_walker(Node *node, void *context);
 static bool contain_outer_selfref(Node *node);
@@ -335,6 +336,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
      */
     splan = makeNode(SubPlan);
     splan->subLinkType = subLinkType;
+    splan->subLinkId = subLinkId;
     splan->testexpr = NULL;
     splan->paramIds = NIL;
     get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod,
@@ -858,12 +860,17 @@ hash_ok_operator(OpExpr *expr)
  * SubPlans, inheritance_planner() must call this to assign new, unique Param
  * IDs to the cloned MULTIEXPR_SUBLINKs' output parameters.  See notes in
  * ExecScanSubPlan.
+ *
+ * We do not need to re-map Param IDs for MULTIEXPR_SUBLINK plans that are
+ * initplans, because those don't have input parameters that could cause
+ * confusion.  Such initplans will not appear in the targetlist anyway, but
+ * they still complicate matters because the surviving regular subplans might
+ * now not have consecutive subLinkIds.
  */
 void
 SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot)
 {
-    List       *new_multiexpr_params = NIL;
-    int            offset;
+    List       *param_mapping = NIL;
     ListCell   *lc;

     /*
@@ -876,6 +883,9 @@ SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot)
         SubPlan    *splan;
         Plan       *plan;
         List       *params;
+        int            oldId,
+                    newId;
+        ListCell   *lc2;

         if (!IsA(tent->expr, SubPlan))
             continue;
@@ -898,86 +908,77 @@ SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot)
                                           &splan->setParam);

         /*
-         * We will append the replacement-Params lists to
-         * root->multiexpr_params, but for the moment just make a local list.
-         * Since we lack easy access here to the original subLinkId, we have
-         * to fall back on the slightly shaky assumption that the MULTIEXPR
-         * SubPlans appear in the targetlist in subLinkId order.  This should
-         * be safe enough given the way that the parser builds the targetlist
-         * today.  I wouldn't want to rely on it going forward, but since this
-         * code has a limited lifespan it should be fine.  We can partially
-         * protect against problems with assertions below.
+         * Append the new replacement-Params list to root->multiexpr_params.
+         * Then its index in that list becomes the new subPlanId of the
+         * SubPlan.
          */
-        new_multiexpr_params = lappend(new_multiexpr_params, params);
+        root->multiexpr_params = lappend(root->multiexpr_params, params);
+        oldId = splan->subLinkId;
+        newId = list_length(root->multiexpr_params);
+        Assert(newId > oldId);
+        splan->subLinkId = newId;
+
+        /*
+         * Add a mapping entry to param_mapping so that we can update the
+         * associated Params below.  Leave zeroes in the list for any
+         * subLinkIds we don't encounter; those must have been converted to
+         * initplans.
+         */
+        while (list_length(param_mapping) <= oldId)
+            param_mapping = lappend_int(param_mapping, 0);
+        lc2 = list_nth_cell(param_mapping, oldId);
+        lfirst_int(lc2) = newId;
     }

     /*
-     * Now we must find the Param nodes that reference the MULTIEXPR outputs
-     * and update their sublink IDs so they'll reference the new outputs.
-     * Fortunately, those too must be in the cloned targetlist, but they could
-     * be buried under FieldStores and SubscriptingRefs and CoerceToDomains
-     * (cf processIndirection()), and underneath those there could be an
-     * implicit type coercion.
+     * Unless all the MULTIEXPRs were converted to initplans, we must now find
+     * the Param nodes that reference the MULTIEXPR outputs and update their
+     * sublink IDs so they'll reference the new outputs.  While such Params
+     * must be in the cloned targetlist, they could be buried under stuff such
+     * as FieldStores and SubscriptingRefs and type coercions.
      */
-    offset = list_length(root->multiexpr_params);
+    if (param_mapping != NIL)
+        SS_make_multiexprs_unique_walker((Node *) subroot->parse->targetList,
+                                         (void *) param_mapping);
+}

-    foreach(lc, subroot->parse->targetList)
+/*
+ * Locate PARAM_MULTIEXPR Params in an expression tree, and update as needed.
+ * (We can update-in-place because the tree was already copied.)
+ */
+static bool
+SS_make_multiexprs_unique_walker(Node *node, void *context)
+{
+    if (node == NULL)
+        return false;
+    if (IsA(node, Param))
     {
-        TargetEntry *tent = (TargetEntry *) lfirst(lc);
-        Node       *expr;
-        Param       *p;
+        Param       *p = (Param *) node;
+        List       *param_mapping = (List *) context;
         int            subqueryid;
         int            colno;
+        int            newId;

-        expr = (Node *) tent->expr;
-        while (expr)
-        {
-            if (IsA(expr, FieldStore))
-            {
-                FieldStore *fstore = (FieldStore *) expr;
-
-                expr = (Node *) linitial(fstore->newvals);
-            }
-            else if (IsA(expr, SubscriptingRef))
-            {
-                SubscriptingRef *sbsref = (SubscriptingRef *) expr;
-
-                if (sbsref->refassgnexpr == NULL)
-                    break;
-
-                expr = (Node *) sbsref->refassgnexpr;
-            }
-            else if (IsA(expr, CoerceToDomain))
-            {
-                CoerceToDomain *cdomain = (CoerceToDomain *) expr;
-
-                if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
-                    break;
-                expr = (Node *) cdomain->arg;
-            }
-            else
-                break;
-        }
-        expr = strip_implicit_coercions(expr);
-        if (expr == NULL || !IsA(expr, Param))
-            continue;
-        p = (Param *) expr;
         if (p->paramkind != PARAM_MULTIEXPR)
-            continue;
+            return false;
         subqueryid = p->paramid >> 16;
         colno = p->paramid & 0xFFFF;
-        Assert(subqueryid > 0 &&
-               subqueryid <= list_length(new_multiexpr_params));
-        Assert(colno > 0 &&
-               colno <= list_length((List *) list_nth(new_multiexpr_params,
-                                                      subqueryid - 1)));
-        subqueryid += offset;
-        p->paramid = (subqueryid << 16) + colno;
-    }

-    /* Finally, attach new replacement lists to the global list */
-    root->multiexpr_params = list_concat(root->multiexpr_params,
-                                         new_multiexpr_params);
+        /*
+         * If subqueryid doesn't have a mapping entry, it must refer to an
+         * initplan, so don't change the Param.
+         */
+        Assert(subqueryid > 0);
+        if (subqueryid >= list_length(param_mapping))
+            return false;
+        newId = list_nth_int(param_mapping, subqueryid);
+        if (newId == 0)
+            return false;
+        p->paramid = (newId << 16) + colno;
+        return false;
+    }
+    return expression_tree_walker(node, SS_make_multiexprs_unique_walker,
+                                  context);
 }


@@ -1110,6 +1111,7 @@ SS_process_ctes(PlannerInfo *root)
          */
         splan = makeNode(SubPlan);
         splan->subLinkType = CTE_SUBLINK;
+        splan->subLinkId = 0;
         splan->testexpr = NULL;
         splan->paramIds = NIL;
         get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod,
@@ -3075,6 +3077,7 @@ SS_make_initplan_from_plan(PlannerInfo *root,
      */
     node = makeNode(SubPlan);
     node->subLinkType = EXPR_SUBLINK;
+    node->subLinkId = 0;
     node->plan_id = list_length(root->glob->subplans);
     node->plan_name = psprintf("InitPlan %d (returns $%d)",
                                node->plan_id, prm->paramid);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d73be2ad46..cfc4f5a9a7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -731,6 +731,8 @@ typedef struct SubPlan
     /* Estimated execution costs: */
     Cost        startup_cost;    /* one-time setup cost */
     Cost        per_call_cost;    /* cost for each subplan evaluation */
+    /* Copied from original SubLink, but placed at end for ABI stability */
+    int            subLinkId;        /* ID (1..n); 0 if not MULTIEXPR */
 } SubPlan;

 /*
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index ed99bdde7f..658fed7922 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1717,23 +1717,36 @@ reset enable_bitmapscan;
 --
 -- Check handling of MULTIEXPR SubPlans in inherited updates
 --
-create table inhpar(f1 int, f2 text[]);
+create table inhpar(f1 int, f2 text[], f3 int);
 insert into inhpar select generate_series(1,10);
 create table inhcld() inherits(inhpar);
 insert into inhcld select generate_series(11,10000);
 vacuum analyze inhcld;
 vacuum analyze inhpar;
 explain (verbose, costs off)
-update inhpar set (f1, f2[1]) = (select p2.unique2, p2.stringu1
-                                 from int4_tbl limit 1)
+update inhpar set
+  (f1, f2[1])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[2], f2[3]) = (select 'x', 'y' from int4_tbl limit 1),
+  (f3, f2[4])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[5], f2[6]) = (select 'x', 'y' from int4_tbl limit 1)
 from onek p2 where inhpar.f1 = p2.unique1;
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
+                                                                                                       QUERY PLAN
                                                                                                  

+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.inhpar
    Update on public.inhpar
    Update on public.inhcld inhpar_1
+   InitPlan 2 (returns $4,$5)
+     ->  Limit
+           Output: 'x'::text, 'y'::text
+           ->  Seq Scan on public.int4_tbl int4_tbl_1
+                 Output: 'x'::text, 'y'::text
+   InitPlan 4 (returns $10,$11)
+     ->  Limit
+           Output: 'x'::text, 'y'::text
+           ->  Seq Scan on public.int4_tbl int4_tbl_3
+                 Output: 'x'::text, 'y'::text
    ->  Merge Join
-         Output: $4, inhpar.f2[1] := $5, (SubPlan 1 (returns $2,$3)), inhpar.ctid, p2.ctid
+         Output: $12, (((((inhpar.f2[1] := $13)[2] := $4)[3] := $5)[4] := $15)[5] := $10)[6] := $11, $14, (SubPlan 1
(returns$2,$3)), NULL::record, (SubPlan 3 (returns $8,$9)), NULL::record, inhpar.ctid, p2.ctid 
          Merge Cond: (p2.unique1 = inhpar.f1)
          ->  Index Scan using onek_unique1 on public.onek p2
                Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
@@ -1747,8 +1760,13 @@ from onek p2 where inhpar.f1 = p2.unique1;
                  Output: (p2.unique2), (p2.stringu1)
                  ->  Seq Scan on public.int4_tbl
                        Output: p2.unique2, p2.stringu1
+         SubPlan 3 (returns $8,$9)
+           ->  Limit
+                 Output: (p2.unique2), (p2.stringu1)
+                 ->  Seq Scan on public.int4_tbl int4_tbl_2
+                       Output: p2.unique2, p2.stringu1
    ->  Hash Join
-         Output: $6, inhpar_1.f2[1] := $7, (SubPlan 1 (returns $2,$3)), inhpar_1.ctid, p2.ctid
+         Output: $16, (((((inhpar_1.f2[1] := $17)[2] := $4)[3] := $5)[4] := $19)[5] := $10)[6] := $11, $18, (SubPlan 1
(returns$2,$3)), NULL::record, (SubPlan 3 (returns $8,$9)), NULL::record, inhpar_1.ctid, p2.ctid 
          Hash Cond: (inhpar_1.f1 = p2.unique1)
          ->  Seq Scan on public.inhcld inhpar_1
                Output: inhpar_1.f2, inhpar_1.ctid, inhpar_1.f1
@@ -1756,10 +1774,13 @@ from onek p2 where inhpar.f1 = p2.unique1;
                Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
                ->  Seq Scan on public.onek p2
                      Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
-(27 rows)
+(42 rows)

-update inhpar set (f1, f2[1]) = (select p2.unique2, p2.stringu1
-                                 from int4_tbl limit 1)
+update inhpar set
+  (f1, f2[1])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[2], f2[3]) = (select 'x', 'y' from int4_tbl limit 1),
+  (f3, f2[4])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[5], f2[6]) = (select 'x', 'y' from int4_tbl limit 1)
 from onek p2 where inhpar.f1 = p2.unique1;
 drop table inhpar cascade;
 NOTICE:  drop cascades to table inhcld
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 7d3813704e..a4a33a3da8 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -632,7 +632,7 @@ reset enable_bitmapscan;
 --
 -- Check handling of MULTIEXPR SubPlans in inherited updates
 --
-create table inhpar(f1 int, f2 text[]);
+create table inhpar(f1 int, f2 text[], f3 int);
 insert into inhpar select generate_series(1,10);
 create table inhcld() inherits(inhpar);
 insert into inhcld select generate_series(11,10000);
@@ -640,11 +640,17 @@ vacuum analyze inhcld;
 vacuum analyze inhpar;

 explain (verbose, costs off)
-update inhpar set (f1, f2[1]) = (select p2.unique2, p2.stringu1
-                                 from int4_tbl limit 1)
+update inhpar set
+  (f1, f2[1])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[2], f2[3]) = (select 'x', 'y' from int4_tbl limit 1),
+  (f3, f2[4])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[5], f2[6]) = (select 'x', 'y' from int4_tbl limit 1)
 from onek p2 where inhpar.f1 = p2.unique1;
-update inhpar set (f1, f2[1]) = (select p2.unique2, p2.stringu1
-                                 from int4_tbl limit 1)
+update inhpar set
+  (f1, f2[1])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[2], f2[3]) = (select 'x', 'y' from int4_tbl limit 1),
+  (f3, f2[4])    = (select p2.unique2, p2.stringu1 from int4_tbl limit 1),
+  (f2[5], f2[6]) = (select 'x', 'y' from int4_tbl limit 1)
 from onek p2 where inhpar.f1 = p2.unique1;

 drop table inhpar cascade;

pgsql-bugs by date:

Previous
From: Tom Lane
Date:
Subject: Re: NULL returned when using RETURNING in main query in combination with a CTE containing FOR UPDATE.
Next
From: Sandeep Thakkar
Date:
Subject: Re: BUG #17603: Problem running the post-install step. Installation may not complete correctly.