From 28317de723b60b61c40e7de4341a3029f698ddaf Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 19 Mar 2025 12:14:24 +0900 Subject: Ensure first ModifyTable rel initialized if all are pruned Commit cbc127917e introduced tracking of unpruned relids to avoid processing pruned relations, and changed ExecInitModifyTable() to initialize only unpruned result relations. As a result, MERGE statements that prune all target partitions can now lead to crashes or incorrect behavior during execution. The crash occurs because some executor code paths rely on ModifyTableState.resultRelInfo[0] being present and initialized, even when no result relations remain after pruning. For example, ExecMerge() and ExecMergeNotMatched() use the first resultRelInfo to determine the appropriate action. Similarly, ExecInitPartitionInfo() assumes that at least one result relation exists. To preserve these assumptions, ExecInitModifyTable() now includes the first result relation in the initialized result relation list if all result relations for that ModifyTable were pruned. To enable that, ExecDoInitialPruning() ensures the first relation is locked if it was pruned and locking is necessary. To support this exception to the pruning logic, PlannedStmt now includes a list of RT indexes identifying the first result relation of each ModifyTable node in the plan. This allows ExecDoInitialPruning() to check whether each such relation was pruned and, if so, lock it if necessary. Bug: #18830 Reported-by: Robins Tharakan Diagnozed-by: Tender Wang Diagnozed-by: Dean Rasheed Co-authored-by: Dean Rasheed Reviewed-by: Tender Wang Reviewed-by: Dean Rasheed Discussion: https://postgr.es/m/18830-1f31ea1dc930d444%40postgresql.org --- src/backend/executor/execPartition.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'src/backend/executor/execPartition.c') diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 5cd5e2eeb80..84ccd7d457d 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -1819,6 +1819,7 @@ adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap) void ExecDoInitialPruning(EState *estate) { + PlannedStmt *stmt = estate->es_plannedstmt; ListCell *lc; List *locked_relids = NIL; @@ -1867,6 +1868,34 @@ ExecDoInitialPruning(EState *estate) validsubplans); } + /* + * Lock the first result relation of each ModifyTable node, even if it was + * pruned. This is required for ExecInitModifyTable(), which keeps its + * first result relation if all other result relations have been pruned, + * because some executor paths (e.g., in nodeModifyTable.c and + * execPartition.c) rely on there being at least one result relation. + * + * There's room for improvement here --- we actually only need to do this + * if all other result relations of the ModifyTable node were pruned, but + * we don't have an easy way to tell that here. + */ + if (stmt->resultRelations && ExecShouldLockRelations(estate)) + { + foreach(lc, stmt->firstResultRels) + { + Index firstResultRel = lfirst_int(lc); + + if (!bms_is_member(firstResultRel, estate->es_unpruned_relids)) + { + RangeTblEntry *rte = exec_rt_fetch(firstResultRel, estate); + + Assert(rte->rtekind == RTE_RELATION && rte->rellockmode != NoLock); + LockRelationOid(rte->relid, rte->rellockmode); + locked_relids = lappend_int(locked_relids, firstResultRel); + } + } + } + /* * Release the useless locks if the plan won't be executed. This is the * same as what CheckCachedPlan() in plancache.c does. @@ -2076,7 +2105,7 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo, * because that entry will be held open and locked for the * duration of this executor run. */ - partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex); + partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex, false); /* Remember for InitExecPartitionPruneContext(). */ pprune->partrel = partrel; -- cgit v1.2.3