mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Fix PlanRowMark/ExecRowMark structures to handle inheritance correctly.
In an inherited UPDATE/DELETE, each target table has its own subplan, because it might have a column set different from other targets. This means that the resjunk columns we add to support EvalPlanQual might be at different physical column numbers in each subplan. The EvalPlanQual rewrite I did for 9.0 failed to account for this, resulting in possible misbehavior or even crashes during concurrent updates to the same row, as seen in a recent report from Gordon Shannon. Revise the data structure so that we track resjunk column numbers separately for each subplan. I also chose to move responsibility for identifying the physical column numbers back to executor startup, instead of assuming that numbers derived during preprocess_targetlist would stay valid throughout subsequent massaging of the plan. That's a bit slower, so we might want to consider undoing it someday; but it would complicate the patch considerably and didn't seem justifiable in a bug fix that has to be back-patched to 9.0.
This commit is contained in:
@ -207,10 +207,22 @@ ExecInitJunkFilterConversion(List *targetList,
|
||||
*/
|
||||
AttrNumber
|
||||
ExecFindJunkAttribute(JunkFilter *junkfilter, const char *attrName)
|
||||
{
|
||||
return ExecFindJunkAttributeInTlist(junkfilter->jf_targetList, attrName);
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecFindJunkAttributeInTlist
|
||||
*
|
||||
* Find a junk attribute given a subplan's targetlist (not necessarily
|
||||
* part of a JunkFilter).
|
||||
*/
|
||||
AttrNumber
|
||||
ExecFindJunkAttributeInTlist(List *targetlist, const char *attrName)
|
||||
{
|
||||
ListCell *t;
|
||||
|
||||
foreach(t, junkfilter->jf_targetList)
|
||||
foreach(t, targetlist)
|
||||
{
|
||||
TargetEntry *tle = lfirst(t);
|
||||
|
||||
|
@ -744,9 +744,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
|
||||
erm->prti = rc->prti;
|
||||
erm->markType = rc->markType;
|
||||
erm->noWait = rc->noWait;
|
||||
erm->ctidAttNo = rc->ctidAttNo;
|
||||
erm->toidAttNo = rc->toidAttNo;
|
||||
erm->wholeAttNo = rc->wholeAttNo;
|
||||
ItemPointerSetInvalid(&(erm->curCtid));
|
||||
estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
|
||||
}
|
||||
@ -1386,6 +1383,71 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecFindRowMark -- find the ExecRowMark struct for given rangetable index
|
||||
*/
|
||||
ExecRowMark *
|
||||
ExecFindRowMark(EState *estate, Index rti)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, estate->es_rowMarks)
|
||||
{
|
||||
ExecRowMark *erm = (ExecRowMark *) lfirst(lc);
|
||||
|
||||
if (erm->rti == rti)
|
||||
return erm;
|
||||
}
|
||||
elog(ERROR, "failed to find ExecRowMark for rangetable index %u", rti);
|
||||
return NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecBuildAuxRowMark -- create an ExecAuxRowMark struct
|
||||
*
|
||||
* Inputs are the underlying ExecRowMark struct and the targetlist of the
|
||||
* input plan node (not planstate node!). We need the latter to find out
|
||||
* the column numbers of the resjunk columns.
|
||||
*/
|
||||
ExecAuxRowMark *
|
||||
ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
|
||||
{
|
||||
ExecAuxRowMark *aerm = (ExecAuxRowMark *) palloc0(sizeof(ExecAuxRowMark));
|
||||
char resname[32];
|
||||
|
||||
aerm->rowmark = erm;
|
||||
|
||||
/* Look up the resjunk columns associated with this rowmark */
|
||||
if (erm->relation)
|
||||
{
|
||||
Assert(erm->markType != ROW_MARK_COPY);
|
||||
|
||||
/* if child rel, need tableoid */
|
||||
if (erm->rti != erm->prti)
|
||||
{
|
||||
snprintf(resname, sizeof(resname), "tableoid%u", erm->prti);
|
||||
aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
|
||||
resname);
|
||||
}
|
||||
|
||||
/* always need ctid for real relations */
|
||||
snprintf(resname, sizeof(resname), "ctid%u", erm->prti);
|
||||
aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
|
||||
resname);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(erm->markType == ROW_MARK_COPY);
|
||||
|
||||
snprintf(resname, sizeof(resname), "wholerow%u", erm->prti);
|
||||
aerm->wholeAttNo = ExecFindJunkAttributeInTlist(targetlist,
|
||||
resname);
|
||||
}
|
||||
|
||||
return aerm;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* EvalPlanQual logic --- recheck modified tuple(s) to see if we want to
|
||||
* process the updated version under READ COMMITTED rules.
|
||||
@ -1676,11 +1738,13 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
|
||||
/*
|
||||
* EvalPlanQualInit -- initialize during creation of a plan state node
|
||||
* that might need to invoke EPQ processing.
|
||||
* Note: subplan can be NULL if it will be set later with EvalPlanQualSetPlan.
|
||||
*
|
||||
* Note: subplan/auxrowmarks can be NULL/NIL if they will be set later
|
||||
* with EvalPlanQualSetPlan.
|
||||
*/
|
||||
void
|
||||
EvalPlanQualInit(EPQState *epqstate, EState *estate,
|
||||
Plan *subplan, int epqParam)
|
||||
Plan *subplan, List *auxrowmarks, int epqParam)
|
||||
{
|
||||
/* Mark the EPQ state inactive */
|
||||
epqstate->estate = NULL;
|
||||
@ -1688,7 +1752,7 @@ EvalPlanQualInit(EPQState *epqstate, EState *estate,
|
||||
epqstate->origslot = NULL;
|
||||
/* ... and remember data that EvalPlanQualBegin will need */
|
||||
epqstate->plan = subplan;
|
||||
epqstate->rowMarks = NIL;
|
||||
epqstate->arowMarks = auxrowmarks;
|
||||
epqstate->epqParam = epqParam;
|
||||
}
|
||||
|
||||
@ -1698,25 +1762,14 @@ EvalPlanQualInit(EPQState *epqstate, EState *estate,
|
||||
* We need this so that ModifyTuple can deal with multiple subplans.
|
||||
*/
|
||||
void
|
||||
EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan)
|
||||
EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
|
||||
{
|
||||
/* If we have a live EPQ query, shut it down */
|
||||
EvalPlanQualEnd(epqstate);
|
||||
/* And set/change the plan pointer */
|
||||
epqstate->plan = subplan;
|
||||
}
|
||||
|
||||
/*
|
||||
* EvalPlanQualAddRowMark -- add an ExecRowMark that EPQ needs to handle.
|
||||
*
|
||||
* Currently, only non-locking RowMarks are supported.
|
||||
*/
|
||||
void
|
||||
EvalPlanQualAddRowMark(EPQState *epqstate, ExecRowMark *erm)
|
||||
{
|
||||
if (RowMarkRequiresRowShareLock(erm->markType))
|
||||
elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
|
||||
epqstate->rowMarks = lappend(epqstate->rowMarks, erm);
|
||||
/* The rowmarks depend on the plan, too */
|
||||
epqstate->arowMarks = auxrowmarks;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1766,13 +1819,17 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
|
||||
|
||||
Assert(epqstate->origslot != NULL);
|
||||
|
||||
foreach(l, epqstate->rowMarks)
|
||||
foreach(l, epqstate->arowMarks)
|
||||
{
|
||||
ExecRowMark *erm = (ExecRowMark *) lfirst(l);
|
||||
ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(l);
|
||||
ExecRowMark *erm = aerm->rowmark;
|
||||
Datum datum;
|
||||
bool isNull;
|
||||
HeapTupleData tuple;
|
||||
|
||||
if (RowMarkRequiresRowShareLock(erm->markType))
|
||||
elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
|
||||
|
||||
/* clear any leftover test tuple for this rel */
|
||||
EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
|
||||
|
||||
@ -1788,7 +1845,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
|
||||
Oid tableoid;
|
||||
|
||||
datum = ExecGetJunkAttribute(epqstate->origslot,
|
||||
erm->toidAttNo,
|
||||
aerm->toidAttNo,
|
||||
&isNull);
|
||||
/* non-locked rels could be on the inside of outer joins */
|
||||
if (isNull)
|
||||
@ -1804,7 +1861,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
|
||||
|
||||
/* fetch the tuple's ctid */
|
||||
datum = ExecGetJunkAttribute(epqstate->origslot,
|
||||
erm->ctidAttNo,
|
||||
aerm->ctidAttNo,
|
||||
&isNull);
|
||||
/* non-locked rels could be on the inside of outer joins */
|
||||
if (isNull)
|
||||
@ -1829,7 +1886,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
|
||||
|
||||
/* fetch the whole-row Var for the relation */
|
||||
datum = ExecGetJunkAttribute(epqstate->origslot,
|
||||
erm->wholeAttNo,
|
||||
aerm->wholeAttNo,
|
||||
&isNull);
|
||||
/* non-locked rels could be on the inside of outer joins */
|
||||
if (isNull)
|
||||
|
@ -58,12 +58,13 @@ lnext:
|
||||
|
||||
/*
|
||||
* Attempt to lock the source tuple(s). (Note we only have locking
|
||||
* rowmarks in lr_rowMarks.)
|
||||
* rowmarks in lr_arowMarks.)
|
||||
*/
|
||||
epq_started = false;
|
||||
foreach(lc, node->lr_rowMarks)
|
||||
foreach(lc, node->lr_arowMarks)
|
||||
{
|
||||
ExecRowMark *erm = (ExecRowMark *) lfirst(lc);
|
||||
ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
|
||||
ExecRowMark *erm = aerm->rowmark;
|
||||
Datum datum;
|
||||
bool isNull;
|
||||
HeapTupleData tuple;
|
||||
@ -84,7 +85,7 @@ lnext:
|
||||
Oid tableoid;
|
||||
|
||||
datum = ExecGetJunkAttribute(slot,
|
||||
erm->toidAttNo,
|
||||
aerm->toidAttNo,
|
||||
&isNull);
|
||||
/* shouldn't ever get a null result... */
|
||||
if (isNull)
|
||||
@ -101,7 +102,7 @@ lnext:
|
||||
|
||||
/* fetch the tuple's ctid */
|
||||
datum = ExecGetJunkAttribute(slot,
|
||||
erm->ctidAttNo,
|
||||
aerm->ctidAttNo,
|
||||
&isNull);
|
||||
/* shouldn't ever get a null result... */
|
||||
if (isNull)
|
||||
@ -189,9 +190,10 @@ lnext:
|
||||
* so as to avoid overhead in the common case where there are no
|
||||
* concurrent updates.)
|
||||
*/
|
||||
foreach(lc, node->lr_rowMarks)
|
||||
foreach(lc, node->lr_arowMarks)
|
||||
{
|
||||
ExecRowMark *erm = (ExecRowMark *) lfirst(lc);
|
||||
ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
|
||||
ExecRowMark *erm = aerm->rowmark;
|
||||
HeapTupleData tuple;
|
||||
Buffer buffer;
|
||||
|
||||
@ -251,6 +253,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
|
||||
{
|
||||
LockRowsState *lrstate;
|
||||
Plan *outerPlan = outerPlan(node);
|
||||
List *epq_arowmarks;
|
||||
ListCell *lc;
|
||||
|
||||
/* check for unsupported flags */
|
||||
@ -262,7 +265,6 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
|
||||
lrstate = makeNode(LockRowsState);
|
||||
lrstate->ps.plan = (Plan *) node;
|
||||
lrstate->ps.state = estate;
|
||||
EvalPlanQualInit(&lrstate->lr_epqstate, estate, outerPlan, node->epqParam);
|
||||
|
||||
/*
|
||||
* Miscellaneous initialization
|
||||
@ -288,15 +290,17 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
|
||||
lrstate->ps.ps_ProjInfo = NULL;
|
||||
|
||||
/*
|
||||
* Locate the ExecRowMark(s) that this node is responsible for. (InitPlan
|
||||
* should already have built the global list of ExecRowMarks.)
|
||||
* Locate the ExecRowMark(s) that this node is responsible for, and
|
||||
* construct ExecAuxRowMarks for them. (InitPlan should already have
|
||||
* built the global list of ExecRowMarks.)
|
||||
*/
|
||||
lrstate->lr_rowMarks = NIL;
|
||||
lrstate->lr_arowMarks = NIL;
|
||||
epq_arowmarks = NIL;
|
||||
foreach(lc, node->rowMarks)
|
||||
{
|
||||
PlanRowMark *rc = (PlanRowMark *) lfirst(lc);
|
||||
ExecRowMark *erm = NULL;
|
||||
ListCell *lce;
|
||||
ExecRowMark *erm;
|
||||
ExecAuxRowMark *aerm;
|
||||
|
||||
Assert(IsA(rc, PlanRowMark));
|
||||
|
||||
@ -304,16 +308,9 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
|
||||
if (rc->isParent)
|
||||
continue;
|
||||
|
||||
foreach(lce, estate->es_rowMarks)
|
||||
{
|
||||
erm = (ExecRowMark *) lfirst(lce);
|
||||
if (erm->rti == rc->rti)
|
||||
break;
|
||||
erm = NULL;
|
||||
}
|
||||
if (erm == NULL)
|
||||
elog(ERROR, "failed to find ExecRowMark for PlanRowMark %u",
|
||||
rc->rti);
|
||||
/* find ExecRowMark and build ExecAuxRowMark */
|
||||
erm = ExecFindRowMark(estate, rc->rti);
|
||||
aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
|
||||
|
||||
/*
|
||||
* Only locking rowmarks go into our own list. Non-locking marks are
|
||||
@ -322,11 +319,15 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
|
||||
* do an EPQ recheck.
|
||||
*/
|
||||
if (RowMarkRequiresRowShareLock(erm->markType))
|
||||
lrstate->lr_rowMarks = lappend(lrstate->lr_rowMarks, erm);
|
||||
lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
|
||||
else
|
||||
EvalPlanQualAddRowMark(&lrstate->lr_epqstate, erm);
|
||||
epq_arowmarks = lappend(epq_arowmarks, aerm);
|
||||
}
|
||||
|
||||
/* Now we have the info needed to set up EPQ state */
|
||||
EvalPlanQualInit(&lrstate->lr_epqstate, estate,
|
||||
outerPlan, epq_arowmarks, node->epqParam);
|
||||
|
||||
return lrstate;
|
||||
}
|
||||
|
||||
|
@ -824,7 +824,8 @@ ExecModifyTable(ModifyTableState *node)
|
||||
estate->es_result_relation_info++;
|
||||
subplanstate = node->mt_plans[node->mt_whichplan];
|
||||
junkfilter = estate->es_result_relation_info->ri_junkFilter;
|
||||
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
|
||||
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
|
||||
node->mt_arowmarks[node->mt_whichplan]);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@ -953,10 +954,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
mtstate->ps.targetlist = NIL; /* not actually used */
|
||||
|
||||
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
|
||||
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
|
||||
mtstate->mt_nplans = nplans;
|
||||
mtstate->operation = operation;
|
||||
/* set up epqstate with dummy subplan pointer for the moment */
|
||||
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
|
||||
/* set up epqstate with dummy subplan data for the moment */
|
||||
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
|
||||
mtstate->fireBSTriggers = true;
|
||||
|
||||
/* For the moment, assume our targets are exactly the global result rels */
|
||||
@ -978,11 +980,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
}
|
||||
estate->es_result_relation_info = NULL;
|
||||
|
||||
/* select first subplan */
|
||||
mtstate->mt_whichplan = 0;
|
||||
subplan = (Plan *) linitial(node->plans);
|
||||
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan);
|
||||
|
||||
/*
|
||||
* Initialize RETURNING projections if needed.
|
||||
*/
|
||||
@ -1046,8 +1043,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
foreach(l, node->rowMarks)
|
||||
{
|
||||
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
|
||||
ExecRowMark *erm = NULL;
|
||||
ListCell *lce;
|
||||
ExecRowMark *erm;
|
||||
|
||||
Assert(IsA(rc, PlanRowMark));
|
||||
|
||||
@ -1055,20 +1051,26 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
if (rc->isParent)
|
||||
continue;
|
||||
|
||||
foreach(lce, estate->es_rowMarks)
|
||||
{
|
||||
erm = (ExecRowMark *) lfirst(lce);
|
||||
if (erm->rti == rc->rti)
|
||||
break;
|
||||
erm = NULL;
|
||||
}
|
||||
if (erm == NULL)
|
||||
elog(ERROR, "failed to find ExecRowMark for PlanRowMark %u",
|
||||
rc->rti);
|
||||
/* find ExecRowMark (same for all subplans) */
|
||||
erm = ExecFindRowMark(estate, rc->rti);
|
||||
|
||||
EvalPlanQualAddRowMark(&mtstate->mt_epqstate, erm);
|
||||
/* build ExecAuxRowMark for each subplan */
|
||||
for (i = 0; i < nplans; i++)
|
||||
{
|
||||
ExecAuxRowMark *aerm;
|
||||
|
||||
subplan = mtstate->mt_plans[i]->plan;
|
||||
aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
|
||||
mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
|
||||
}
|
||||
}
|
||||
|
||||
/* select first subplan */
|
||||
mtstate->mt_whichplan = 0;
|
||||
subplan = (Plan *) linitial(node->plans);
|
||||
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
|
||||
mtstate->mt_arowmarks[0]);
|
||||
|
||||
/*
|
||||
* Initialize the junk filter(s) if needed. INSERT queries need a filter
|
||||
* if there are any junk attrs in the tlist. UPDATE and DELETE always
|
||||
|
Reference in New Issue
Block a user