Fix segfault during EvalPlanQual with mix of local and foreign partitions.

It's not sensible to re-evaluate a direct-modify Foreign Update or Delete
during EvalPlanQual. However, ExecInitForeignScan() can still get called
if a table mixes local and foreign partitions. EvalPlanQualStart() left
the es_result_relations array uninitialized in the child EPQ EState, but
ExecInitForeignScan() still expected to find it. That caused a segfault.

Fix by skipping the es_result_relations lookup during EvalPlanQual
processing. To make things a bit more robust, also skip the
BeginDirectModify calls, and add a runtime check that ExecForeignScan()
is not called on direct-modify foreign scans during EvalPlanQual
processing.

This is new in v14, commit 1375422c78. Before that, EvalPlanQualStart()
copied the whole ResultRelInfo array to the EPQ EState. Backpatch to v14.

Report and diagnosis by Andrey Lepikhov.

Discussion: https://www.postgresql.org/message-id/cb2b808d-cbaa-4772-76ee-c8809bafcf3d%40postgrespro.ru
This commit is contained in:
Heikki Linnakangas 2021-08-12 11:02:29 +03:00
parent a4957b5a72
commit 6458ed18fe
1 changed files with 41 additions and 5 deletions

View File

@ -44,12 +44,22 @@ ForeignNext(ForeignScanState *node)
TupleTableSlot *slot;
ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
ExprContext *econtext = node->ss.ps.ps_ExprContext;
EState *estate = node->ss.ps.state;
MemoryContext oldcontext;
/* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (plan->operation != CMD_SELECT)
{
/*
* direct modifications cannot be re-evaluated, so shouldn't get here
* during EvalPlanQual processing
*/
if (estate->es_epq_active != NULL)
elog(ERROR, "cannot re-evaluate a Foreign Update or Delete during EvalPlanQual");
slot = node->fdwroutine->IterateDirectModify(node);
}
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext);
@ -223,11 +233,25 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->fdw_state = NULL;
/*
* For the FDW's convenience, look up the modification target relation's.
* ResultRelInfo.
* For the FDW's convenience, look up the modification target relation's
* ResultRelInfo. The ModifyTable node should have initialized it for us,
* see ExecInitModifyTable.
*
* Don't try to look up the ResultRelInfo when EvalPlanQual is active,
* though. Direct modififications cannot be re-evaluated as part of
* EvalPlanQual. The lookup wouldn't work anyway because during
* EvalPlanQual processing, EvalPlanQual only initializes the subtree
* under the ModifyTable, and doesn't run ExecInitModifyTable.
*/
if (node->resultRelation > 0)
if (node->resultRelation > 0 && estate->es_epq_active == NULL)
{
if (estate->es_result_relations == NULL ||
estate->es_result_relations[node->resultRelation - 1] == NULL)
{
elog(ERROR, "result relation not initialized");
}
scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1];
}
/* Initialize any outer plan. */
if (outerPlan(node))
@ -238,7 +262,15 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
* Tell the FDW to initialize the scan.
*/
if (node->operation != CMD_SELECT)
fdwroutine->BeginDirectModify(scanstate, eflags);
{
/*
* Direct modifications cannot be re-evaluated by EvalPlanQual, so
* don't bother preparing the FDW. There can ForeignScan nodes in the
* EvalPlanQual subtree, but ExecForeignScan should never be called.
*/
if (estate->es_epq_active == NULL)
fdwroutine->BeginDirectModify(scanstate, eflags);
}
else
fdwroutine->BeginForeignScan(scanstate, eflags);
@ -255,10 +287,14 @@ void
ExecEndForeignScan(ForeignScanState *node)
{
ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
/* Let the FDW shut down */
if (plan->operation != CMD_SELECT)
node->fdwroutine->EndDirectModify(node);
{
if (estate->es_epq_active == NULL)
node->fdwroutine->EndDirectModify(node);
}
else
node->fdwroutine->EndForeignScan(node);