From 6458ed18fe40ac91f29496128dd1a49c0ef2db5b Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 12 Aug 2021 11:02:29 +0300 Subject: [PATCH] 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 1375422c782. 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 --- src/backend/executor/nodeForeignscan.c | 46 +++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 9dc38d47ea..ad9edd26f1 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -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);