From 67acc2e3ea3a3227e68a85c500ec8104a8c5b812 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 3 Dec 2025 15:07:24 -0500 Subject: [PATCH v45 1/5] Track which relations are modified by a query Save the range table indexes of modified relations in a Bitmapset in the PlannedStmt. A later commit will use this information during scans to control whether or not on-access pruning is allowed to set the visibility map. Setting the visibility map during a scan is counterproductive if the query is going to modify the page immediately after. Relations are considered modified if they are the target of INSERT, UPDATE, DELETE, or MERGE, or if they have any row mark (including SELECT FOR UPDATE/SHARE). All row mark types are included, even those which don't actually modify tuples, because this set is only used as a hint to avoid unnecessary work. Author: Melanie Plageman Reviewed-by: Andres Freund Reviewed-by: Chao Li Discussion: https://postgr.es/m/F5CDD1B5-628C-44A1-9F85-3958C626F6A9%40gmail.com --- src/backend/executor/execParallel.c | 1 + src/backend/executor/nodeLockRows.c | 3 +++ src/backend/executor/nodeModifyTable.c | 14 ++++++++++++++ src/backend/optimizer/plan/planner.c | 21 ++++++++++++++++++++- src/include/nodes/plannodes.h | 6 ++++++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index ac84af294c9..4f39767d033 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -188,6 +188,7 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt->partPruneInfos = estate->es_part_prune_infos; pstmt->rtable = estate->es_range_table; pstmt->unprunableRelids = estate->es_unpruned_relids; + pstmt->modifiedRelids = estate->es_plannedstmt->modifiedRelids; pstmt->permInfos = estate->es_rteperminfos; pstmt->resultRelations = NIL; pstmt->appendRelations = NIL; diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 8d865470780..38a43315f11 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -113,6 +113,9 @@ lnext: } erm->ermActive = true; + Assert(bms_is_member(erm->rti, + estate->es_plannedstmt->modifiedRelids)); + /* fetch the tuple's ctid */ datum = ExecGetJunkAttribute(slot, aerm->ctidAttNo, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 4cd5e262e0f..4c64589b421 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -896,6 +896,14 @@ ExecInsert(ModifyTableContext *context, resultRelationDesc = resultRelInfo->ri_RelationDesc; + /* + * If this is a leaf partition we just found, it won't have a valid range + * table index. + */ + Assert(resultRelInfo->ri_RangeTableIndex == 0 || + bms_is_member(resultRelInfo->ri_RangeTableIndex, + estate->es_plannedstmt->modifiedRelids)); + /* * Open the table's indexes, if we have not done so already, so that we * can add new index entries for the inserted tuple. @@ -1523,6 +1531,9 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, { EState *estate = context->estate; + Assert(bms_is_member(resultRelInfo->ri_RangeTableIndex, + estate->es_plannedstmt->modifiedRelids)); + return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid, estate->es_output_cid, estate->es_snapshot, @@ -2205,6 +2216,9 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, bool partition_constraint_failed; TM_Result result; + Assert(bms_is_member(resultRelInfo->ri_RangeTableIndex, + estate->es_plannedstmt->modifiedRelids)); + updateCxt->crossPartUpdate = false; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 42604a0f75c..86dea1c9cb8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -340,8 +340,10 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, RelOptInfo *final_rel; Path *best_path; Plan *top_plan; + Bitmapset *modifiedRelids = NULL; ListCell *lp, - *lr; + *lr, + *lc; /* * Set up global state for this planner invocation. This data is needed @@ -661,6 +663,23 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result->subplans = glob->subplans; result->rewindPlanIDs = glob->rewindPlanIDs; result->rowMarks = glob->finalrowmarks; + + /* + * Compute modifiedRelids from result relations and row marks. + * + * This isn't exactly what the executor will actually modify/lock at + * runtime. Runtime partition pruning may eliminate some result relations + * and parent row marks included here may be skipped by the executor. + * Conversely, leaf partitions whose result relations are created at the + * time of insert are not included here. + */ + foreach(lc, glob->resultRelations) + modifiedRelids = bms_add_member(modifiedRelids, lfirst_int(lc)); + foreach(lc, glob->finalrowmarks) + modifiedRelids = bms_add_member(modifiedRelids, + ((PlanRowMark *) lfirst(lc))->rti); + result->modifiedRelids = modifiedRelids; + result->relationOids = glob->relationOids; result->invalItems = glob->invalItems; result->paramExecTypes = glob->paramExecTypes; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index b6185825fcb..a9cf9dd0f29 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -112,6 +112,12 @@ typedef struct PlannedStmt */ Bitmapset *unprunableRelids; + /* + * RT indexes of relations modified by the query through + * UPDATE/DELETE/INSERT/MERGE or targeted by SELECT FOR UPDATE/SHARE. + */ + Bitmapset *modifiedRelids; + /* * list of RTEPermissionInfo nodes for rtable entries needing one */ -- 2.43.0