Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3f7323c

Browse files
committed
Repair rare failure of MULTIEXPR_SUBLINK subplans in inherited updates.
Prior to v14, if we have a MULTIEXPR SubPlan (that is, use of the syntax UPDATE ... SET (c1, ...) = (SELECT ...)) in an UPDATE with an inherited or partitioned target table, inheritance_planner() will clone the targetlist and therefore also the MULTIEXPR SubPlan and the Param nodes referencing it for each child target table. Up to now, we've allowed all the clones to share the underlying subplan as well as the output parameter IDs -- that is, the runtime ParamExecData slots. That technique is borrowed from the far older code that supports initplans, and it works okay in that case because the cloned SubPlan nodes are essentially identical. So it doesn't matter which one of the clones the shared ParamExecData.execPlan field might point to. However, this fails to hold for MULTIEXPR SubPlans, because they can have nonempty "args" lists (values to be passed into the subplan), and those lists could get mutated to different states in the various clones. In the submitted reproducer, as well as the test case added here, one clone contains Vars with varno OUTER_VAR where another has INNER_VAR, because the child tables are respectively on the outer or inner side of the join. Sharing the execPlan pointer can result in trying to evaluate an args list that doesn't match the local execution state, with mayhem ensuing. The result often is to trigger consistency checks in the executor, but I believe this could end in a crash or incorrect updates. To fix, assign new Param IDs to each of the cloned SubPlans, so that they don't share ParamExecData slots at runtime. It still seems fine for the clones to share the underlying subplan, and extra ParamExecData slots are cheap enough that this fix shouldn't cost much. This has been busted since we invented MULTIEXPR SubPlans in 9.5. Probably the lack of previous reports is because query plans in which the different clones of a MULTIEXPR mutate to effectively-different states are pretty rare. There's no issue in v14 and later, because without inheritance_planner() there's never a reason to clone MULTIEXPR SubPlans. Per bug #17596 from Andre Lin. Patch v10-v13 only. Discussion: https://postgr.es/m/[email protected]
1 parent 7d50165 commit 3f7323c

File tree

6 files changed

+184
-0
lines changed

6 files changed

+184
-0
lines changed

src/backend/executor/nodeSubplan.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ ExecScanSubPlan(SubPlanState *node,
245245
* ones, so this should be safe.) Unlike ExecReScanSetParamPlan, we do
246246
* *not* set bits in the parent plan node's chgParam, because we don't
247247
* want to cause a rescan of the parent.
248+
*
249+
* Note: we are also relying on MULTIEXPR SubPlans not sharing any output
250+
* parameters with other SubPlans, because if one does then it is unclear
251+
* which SubPlanState node the parameter's execPlan field will be pointing
252+
* to when we come to evaluate the parameter. We can allow plain initplan
253+
* SubPlans to share output parameters, because it doesn't actually matter
254+
* which initplan SubPlan we reference as long as they all point to the
255+
* same underlying subplan. However, that fails to hold for MULTIEXPRs
256+
* because they can have non-empty args lists, and the "same" args might
257+
* have mutated into different forms in different parts of a plan tree.
258+
* There is not a problem in ordinary queries because MULTIEXPR will
259+
* appear only in an UPDATE's top-level target list, so it won't get
260+
* duplicated anyplace. However, when inheritance_planner clones a
261+
* partially-planned targetlist it must take care to assign non-duplicate
262+
* param IDs to the cloned copy.
248263
*/
249264
if (subLinkType == MULTIEXPR_SUBLINK)
250265
{

src/backend/optimizer/plan/planner.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,10 @@ inheritance_planner(PlannerInfo *root)
16061606
/* and we haven't created PlaceHolderInfos, either */
16071607
Assert(subroot->placeholder_list == NIL);
16081608

1609+
/* Fix MULTIEXPR_SUBLINK params if any */
1610+
if (root->multiexpr_params)
1611+
SS_make_multiexprs_unique(root, subroot);
1612+
16091613
/* Generate Path(s) for accessing this result relation */
16101614
grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
16111615

src/backend/optimizer/plan/subselect.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,101 @@ hash_ok_operator(OpExpr *expr)
851851
}
852852
}
853853

854+
/*
855+
* SS_make_multiexprs_unique
856+
*
857+
* After cloning an UPDATE targetlist that contains MULTIEXPR_SUBLINK
858+
* SubPlans, inheritance_planner() must call this to assign new, unique Param
859+
* IDs to the cloned MULTIEXPR_SUBLINKs' output parameters. See notes in
860+
* ExecScanSubPlan.
861+
*/
862+
void
863+
SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot)
864+
{
865+
List *new_multiexpr_params = NIL;
866+
int offset;
867+
ListCell *lc;
868+
869+
/*
870+
* Find MULTIEXPR SubPlans in the cloned query. We need only look at the
871+
* top level of the targetlist.
872+
*/
873+
foreach(lc, subroot->parse->targetList)
874+
{
875+
TargetEntry *tent = (TargetEntry *) lfirst(lc);
876+
SubPlan *splan;
877+
Plan *plan;
878+
List *params;
879+
880+
if (!IsA(tent->expr, SubPlan))
881+
continue;
882+
splan = (SubPlan *) tent->expr;
883+
if (splan->subLinkType != MULTIEXPR_SUBLINK)
884+
continue;
885+
886+
/* Found one, get the associated subplan */
887+
plan = (Plan *) list_nth(root->glob->subplans, splan->plan_id - 1);
888+
889+
/*
890+
* Generate new PARAM_EXEC Param nodes, and overwrite splan->setParam
891+
* with their IDs. This is just like what build_subplan did when it
892+
* made the SubPlan node we're cloning. But because the param IDs are
893+
* assigned globally, we'll get new IDs. (We assume here that the
894+
* subroot's tlist is a clone we can scribble on.)
895+
*/
896+
params = generate_subquery_params(root,
897+
plan->targetlist,
898+
&splan->setParam);
899+
900+
/*
901+
* We will append the replacement-Params lists to
902+
* root->multiexpr_params, but for the moment just make a local list.
903+
* Since we lack easy access here to the original subLinkId, we have
904+
* to fall back on the slightly shaky assumption that the MULTIEXPR
905+
* SubPlans appear in the targetlist in subLinkId order. This should
906+
* be safe enough given the way that the parser builds the targetlist
907+
* today. I wouldn't want to rely on it going forward, but since this
908+
* code has a limited lifespan it should be fine. We can partially
909+
* protect against problems with assertions below.
910+
*/
911+
new_multiexpr_params = lappend(new_multiexpr_params, params);
912+
}
913+
914+
/*
915+
* Now we must find the Param nodes that reference the MULTIEXPR outputs
916+
* and update their sublink IDs so they'll reference the new outputs.
917+
* Fortunately, those too must be at top level of the cloned targetlist.
918+
*/
919+
offset = list_length(root->multiexpr_params);
920+
921+
foreach(lc, subroot->parse->targetList)
922+
{
923+
TargetEntry *tent = (TargetEntry *) lfirst(lc);
924+
Param *p;
925+
int subqueryid;
926+
int colno;
927+
928+
if (!IsA(tent->expr, Param))
929+
continue;
930+
p = (Param *) tent->expr;
931+
if (p->paramkind != PARAM_MULTIEXPR)
932+
continue;
933+
subqueryid = p->paramid >> 16;
934+
colno = p->paramid & 0xFFFF;
935+
Assert(subqueryid > 0 &&
936+
subqueryid <= list_length(new_multiexpr_params));
937+
Assert(colno > 0 &&
938+
colno <= list_length((List *) list_nth(new_multiexpr_params,
939+
subqueryid - 1)));
940+
subqueryid += offset;
941+
p->paramid = (subqueryid << 16) + colno;
942+
}
943+
944+
/* Finally, attach new replacement lists to the global list */
945+
root->multiexpr_params = list_concat(root->multiexpr_params,
946+
new_multiexpr_params);
947+
}
948+
854949

855950
/*
856951
* SS_process_ctes: process a query's WITH list

src/include/optimizer/subselect.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "nodes/pathnodes.h"
1717
#include "nodes/plannodes.h"
1818

19+
extern void SS_make_multiexprs_unique(PlannerInfo *root, PlannerInfo *subroot);
1920
extern void SS_process_ctes(PlannerInfo *root);
2021
extern JoinExpr *convert_ANY_sublink_to_join(PlannerInfo *root,
2122
SubLink *sublink,

src/test/regress/expected/inherit.out

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,55 @@ reset enable_seqscan;
17151715
reset enable_indexscan;
17161716
reset enable_bitmapscan;
17171717
--
1718+
-- Check handling of MULTIEXPR SubPlans in inherited updates
1719+
--
1720+
create table inhpar(f1 int, f2 name);
1721+
insert into inhpar select generate_series(1,10);
1722+
create table inhcld() inherits(inhpar);
1723+
insert into inhcld select generate_series(11,10000);
1724+
vacuum analyze inhcld;
1725+
vacuum analyze inhpar;
1726+
explain (verbose, costs off)
1727+
update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1
1728+
from int4_tbl limit 1)
1729+
from onek p2 where inhpar.f1 = p2.unique1;
1730+
QUERY PLAN
1731+
-----------------------------------------------------------------------------
1732+
Update on public.inhpar
1733+
Update on public.inhpar
1734+
Update on public.inhcld inhpar_1
1735+
-> Merge Join
1736+
Output: $4, $5, (SubPlan 1 (returns $2,$3)), inhpar.ctid, p2.ctid
1737+
Merge Cond: (p2.unique1 = inhpar.f1)
1738+
-> Index Scan using onek_unique1 on public.onek p2
1739+
Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
1740+
-> Sort
1741+
Output: inhpar.ctid, inhpar.f1
1742+
Sort Key: inhpar.f1
1743+
-> Seq Scan on public.inhpar
1744+
Output: inhpar.ctid, inhpar.f1
1745+
SubPlan 1 (returns $2,$3)
1746+
-> Limit
1747+
Output: (p2.unique2), (p2.stringu1)
1748+
-> Seq Scan on public.int4_tbl
1749+
Output: p2.unique2, p2.stringu1
1750+
-> Hash Join
1751+
Output: $6, $7, (SubPlan 1 (returns $2,$3)), inhpar_1.ctid, p2.ctid
1752+
Hash Cond: (inhpar_1.f1 = p2.unique1)
1753+
-> Seq Scan on public.inhcld inhpar_1
1754+
Output: inhpar_1.ctid, inhpar_1.f1
1755+
-> Hash
1756+
Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
1757+
-> Seq Scan on public.onek p2
1758+
Output: p2.unique2, p2.stringu1, p2.ctid, p2.unique1
1759+
(27 rows)
1760+
1761+
update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1
1762+
from int4_tbl limit 1)
1763+
from onek p2 where inhpar.f1 = p2.unique1;
1764+
drop table inhpar cascade;
1765+
NOTICE: drop cascades to table inhcld
1766+
--
17181767
-- Check handling of a constant-null CHECK constraint
17191768
--
17201769
create table cnullparent (f1 int);

src/test/regress/sql/inherit.sql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,26 @@ reset enable_seqscan;
629629
reset enable_indexscan;
630630
reset enable_bitmapscan;
631631

632+
--
633+
-- Check handling of MULTIEXPR SubPlans in inherited updates
634+
--
635+
create table inhpar(f1 int, f2 name);
636+
insert into inhpar select generate_series(1,10);
637+
create table inhcld() inherits(inhpar);
638+
insert into inhcld select generate_series(11,10000);
639+
vacuum analyze inhcld;
640+
vacuum analyze inhpar;
641+
642+
explain (verbose, costs off)
643+
update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1
644+
from int4_tbl limit 1)
645+
from onek p2 where inhpar.f1 = p2.unique1;
646+
update inhpar set (f1, f2) = (select p2.unique2, p2.stringu1
647+
from int4_tbl limit 1)
648+
from onek p2 where inhpar.f1 = p2.unique1;
649+
650+
drop table inhpar cascade;
651+
632652
--
633653
-- Check handling of a constant-null CHECK constraint
634654
--

0 commit comments

Comments
 (0)