diff options
-rw-r--r-- | src/backend/executor/nodeSubplan.c | 15 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 4 | ||||
-rw-r--r-- | src/backend/optimizer/plan/subselect.c | 95 | ||||
-rw-r--r-- | src/include/optimizer/subselect.h | 1 | ||||
-rw-r--r-- | src/test/regress/expected/inherit.out | 49 | ||||
-rw-r--r-- | src/test/regress/sql/inherit.sql | 20 |
6 files changed, 184 insertions, 0 deletions
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 62ef1385710..ede4e4bbfae 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -246,6 +246,21 @@ ExecScanSubPlan(SubPlanState *node, * ones, so this should be safe.) Unlike ExecReScanSetParamPlan, we do * *not* set bits in the parent plan node's chgParam, because we don't * want to cause a rescan of the parent. + * + * Note: we are also relying on MULTIEXPR SubPlans not sharing any output + * parameters with other SubPlans, because if one does then it is unclear + * which SubPlanState node the parameter's execPlan field will be pointing + * to when we come to evaluate the parameter. We can allow plain initplan + * SubPlans to share output parameters, because it doesn't actually matter + * which initplan SubPlan we reference as long as they all point to the + * same underlying subplan. However, that fails to hold for MULTIEXPRs + * because they can have non-empty args lists, and the "same" args might + * have mutated into different forms in different parts of a plan tree. + * There is not a problem in ordinary queries because MULTIEXPR will + * appear only in an UPDATE's top-level target list, so it won't get + * duplicated anyplace. However, when inheritance_planner clones a + * partially-planned targetlist it must take care to assign non-duplicate + * param IDs to the cloned copy. */ if (subLinkType == MULTIEXPR_SUBLINK) { diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 10ecc2976e8..8585ecdcf44 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1597,6 +1597,10 @@ inheritance_planner(PlannerInfo *root) /* and we haven't created PlaceHolderInfos, either */ Assert(subroot->placeholder_list == NIL); + /* Fix MULTIEXPR_SUBLINK params if any */ + if (root->multiexpr_params) + SS_make_multiexprs_unique(root, subroot); + /* Generate Path(s) for accessing this result relation */ grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 528e2a36244..4c6f84a6755 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -851,6 +851,101 @@ hash_ok_operator(OpExpr *expr) } } +/* + * SS_make_multiexprs_unique + * + * After cloning an UPDATE targetlist that contains MULTIEXPR_SUBLINK + * SubPlans, inheritance_planner() must call this to assign new, unique Param + * IDs to the cloned MULTIEXPR_SUBLINKs' output parameters. See notes in + * ExecScanSubPlan. + */ +void +SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot) +{ + List *new_multiexpr_params = NIL; + int offset; + ListCell *lc; + + /* + * Find MULTIEXPR SubPlans in the cloned query. We need only look at the + * top level of the targetlist. + */ + foreach(lc, subroot->parse->targetList) + { + TargetEntry *tent = (TargetEntry *) lfirst(lc); + SubPlan *splan; + Plan *plan; + List *params; + + if (!IsA(tent->expr, SubPlan)) + continue; + splan = (SubPlan *) tent->expr; + if (splan->subLinkType != MULTIEXPR_SUBLINK) + continue; + + /* Found one, get the associated subplan */ + plan = (Plan *) list_nth(root->glob->subplans, splan->plan_id - 1); + + /* + * Generate new PARAM_EXEC Param nodes, and overwrite splan->setParam + * with their IDs. This is just like what build_subplan did when it + * made the SubPlan node we're cloning. But because the param IDs are + * assigned globally, we'll get new IDs. (We assume here that the + * subroot's tlist is a clone we can scribble on.) + */ + params = generate_subquery_params(root, + plan->targetlist, + &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. + */ + new_multiexpr_params = lappend(new_multiexpr_params, params); + } + + /* + * 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 at top level of the cloned targetlist. + */ + offset = list_length(root->multiexpr_params); + + foreach(lc, subroot->parse->targetList) + { + TargetEntry *tent = (TargetEntry *) lfirst(lc); + Param *p; + int subqueryid; + int colno; + + if (!IsA(tent->expr, Param)) + continue; + p = (Param *) tent->expr; + if (p->paramkind != PARAM_MULTIEXPR) + continue; + 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); +} + /* * SS_process_ctes: process a query's WITH list diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h index 71a22eeb647..c784b984f85 100644 --- a/src/include/optimizer/subselect.h +++ b/src/include/optimizer/subselect.h @@ -16,6 +16,7 @@ #include "nodes/pathnodes.h" #include "nodes/plannodes.h" +extern void SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot); extern void SS_process_ctes(PlannerInfo *root); extern JoinExpr *convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink, diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index ca1f5e29e31..cef006f5565 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1715,6 +1715,55 @@ reset enable_seqscan; reset enable_indexscan; reset enable_bitmapscan; -- +-- Check handling of MULTIEXPR SubPlans in inherited updates +-- +create table inhpar(f1 int, f2 name); +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) = (select p2.unique2, p2.stringu1 + from int4_tbl limit 1) +from onek p2 where inhpar.f1 = p2.unique1; + QUERY PLAN +--------------------------------------------------------------------------- + Update on public.inhpar + Update on public.inhpar + Update on public.inhcld + -> Merge Join + Output: $4, $5, (SubPlan 1 (returns $2,$3)), 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 + -> Sort + Output: inhpar.ctid, inhpar.f1 + Sort Key: inhpar.f1 + -> Seq Scan on public.inhpar + Output: inhpar.ctid, inhpar.f1 + SubPlan 1 (returns $2,$3) + -> Limit + Output: (p2.unique2), (p2.stringu1) + -> Seq Scan on public.int4_tbl + Output: p2.unique2, p2.stringu1 + -> Hash Join + Output: $6, $7, (SubPlan 1 (returns $2,$3)), inhcld.ctid, p2.ctid + Hash Cond: (inhcld.f1 = p2.unique1) + -> Seq Scan on public.inhcld + Output: inhcld.ctid, inhcld.f1 + -> Hash + 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) + +update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1 + from int4_tbl limit 1) +from onek p2 where inhpar.f1 = p2.unique1; +drop table inhpar cascade; +NOTICE: drop cascades to table inhcld +-- -- Check handling of a constant-null CHECK constraint -- create table cnullparent (f1 int); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 64173a8738c..fd4f252c291 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -630,6 +630,26 @@ reset enable_indexscan; reset enable_bitmapscan; -- +-- Check handling of MULTIEXPR SubPlans in inherited updates +-- +create table inhpar(f1 int, f2 name); +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) = (select p2.unique2, p2.stringu1 + from int4_tbl limit 1) +from onek p2 where inhpar.f1 = p2.unique1; +update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1 + from int4_tbl limit 1) +from onek p2 where inhpar.f1 = p2.unique1; + +drop table inhpar cascade; + +-- -- Check handling of a constant-null CHECK constraint -- create table cnullparent (f1 int); |