From 9f7098111d8520a955b0b4d6d4d62c4a79a5497c Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 3 Dec 2025 15:07:24 -0500 Subject: [PATCH v36 11/16] Track which relations are modified by a query Save the relids in a bitmap in the estate. A later commit will pass this information down to scan nodes to control whether or not the scan allows setting the visibility map while on-access pruning. We don't want to set the visibility map if the query is just going to modify the page immediately after. 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 d46ba59895d..05f032baeaa 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -703,6 +703,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