From f707d345f3ee43a9b5e914e4d496c83485ea380b Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 3 Dec 2025 15:07:24 -0500 Subject: [PATCH v38 08/12] Track which relations are modified by a query Save the relids of modified relations in a bitmap in the executor state. A later commit will pass this information down to scan nodes 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 bitmap 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/execMain.c | 18 ++++++++++++++++++ src/backend/executor/execUtils.c | 31 +++++++++++++++++++++++++++++++ src/include/executor/executor.h | 3 +++ src/include/nodes/execnodes.h | 6 ++++++ 4 files changed, 58 insertions(+) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index bfd3ebc601e..57dcdeda056 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -920,6 +920,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) break; } + /* If it has a rowmark, the relation may be modified */ + estate->es_modified_relids = bms_add_member(estate->es_modified_relids, + rc->rti); + /* Check that relation is a legal target for marking */ if (relation) CheckValidRowMarkRel(relation, rc->markType); @@ -990,6 +994,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) */ planstate = ExecInitNode(plan, estate, eflags); +#ifdef USE_ASSERT_CHECKING + CrossCheckModifiedRelids(estate); +#endif + /* * Get the tuple descriptor describing the type of tuples to return. */ @@ -3033,6 +3041,12 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) rcestate->es_output_cid = parentestate->es_output_cid; rcestate->es_queryEnv = parentestate->es_queryEnv; + /* + * Use a deep copy to avoid stale pointers since bms_add_member() may + * reallocate the bitmap. + */ + rcestate->es_modified_relids = bms_copy(parentestate->es_modified_relids); + /* * ResultRelInfos needed by subplans are initialized from scratch when the * subplans themselves are initialized. @@ -3165,6 +3179,10 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) */ epqstate->recheckplanstate = ExecInitNode(planTree, rcestate, 0); +#ifdef USE_ASSERT_CHECKING + CrossCheckModifiedRelids(rcestate); +#endif + MemoryContextSwitchTo(oldcontext); } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index cd4d5452cfb..0f8364b8720 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -123,6 +123,8 @@ CreateExecutorState(void) estate->es_part_prune_results = NIL; estate->es_unpruned_relids = NULL; + estate->es_modified_relids = NULL; + estate->es_junkFilter = NULL; estate->es_output_cid = (CommandId) 0; @@ -871,6 +873,33 @@ ExecGetRangeTableRelation(EState *estate, Index rti, bool isResultRel) return rel; } +#ifdef USE_ASSERT_CHECKING +/* + * Assert that es_modified_relids includes all potentially modified RT + * indexes. + */ +void +CrossCheckModifiedRelids(EState *estate) +{ + Bitmapset *expected = NULL; + ListCell *lc; + + foreach(lc, estate->es_opened_result_relations) + { + ResultRelInfo *rri = lfirst_node(ResultRelInfo, lc); + + expected = bms_add_member(expected, rri->ri_RangeTableIndex); + } + if (estate->es_rowmarks) + { + for (Index rti = 1; rti <= estate->es_range_table_size; rti++) + if (estate->es_rowmarks[rti - 1] != NULL) + expected = bms_add_member(expected, rti); + } + Assert(bms_is_subset(expected, estate->es_modified_relids)); +} +#endif + /* * ExecInitResultRelation * Open relation given by the passed-in RT index and fill its @@ -896,6 +925,8 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, estate->es_result_relations = (ResultRelInfo **) palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *)); estate->es_result_relations[rti - 1] = resultRelInfo; + estate->es_modified_relids = bms_add_member(estate->es_modified_relids, + rti); /* * Saving in the list allows to avoid needlessly traversing the whole diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 82c442d23f8..1411d5276ca 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -705,6 +705,9 @@ extern Relation ExecGetRangeTableRelation(EState *estate, Index rti, bool isResultRel); extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, Index rti); +#ifdef USE_ASSERT_CHECKING +extern void CrossCheckModifiedRelids(EState *estate); +#endif extern int executor_errposition(EState *estate, int location); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 63c067d5aae..610385df12b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -679,6 +679,12 @@ typedef struct EState * ExecDoInitialPruning() */ const char *es_sourceText; /* Source text from QueryDesc */ + /* + * RT indexes of relations modified by the query through a + * UPDATE/DELETE/INSERT/MERGE or targeted by a SELECT FOR UPDATE. + */ + Bitmapset *es_modified_relids; + JunkFilter *es_junkFilter; /* top-level junk filter, if any */ /* If query can insert/delete tuples, the command ID to mark them with */ -- 2.43.0