Pass down information on table modification to scan nodes master github/master
authorMelanie Plageman <melanieplageman@gmail.com>
Mon, 30 Mar 2026 17:27:34 +0000 (13:27 -0400)
committerMelanie Plageman <melanieplageman@gmail.com>
Mon, 30 Mar 2026 17:27:34 +0000 (13:27 -0400)
Pass down information to sequential scan, index [only] scan, bitmap
table scan, sample scan, and TID range scan nodes on whether or not the
query modifies the relation being scanned. A later commit will use this
information to update the VM during on-access pruning only if the
relation is not modified by the query.

Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Reviewed-by: Tomas Vondra <tomas@vondra.me>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/4379FDA3-9446-4E2C-9C15-32EFE8D4F31B%40yandex-team.ru

src/backend/executor/execUtils.c
src/backend/executor/nodeBitmapHeapscan.c
src/backend/executor/nodeIndexonlyscan.c
src/backend/executor/nodeIndexscan.c
src/backend/executor/nodeSamplescan.c
src/backend/executor/nodeSeqscan.c
src/backend/executor/nodeTidrangescan.c
src/include/access/tableam.h
src/include/executor/executor.h

index 36c5285d252a6f08c5935eed3c18d87a22d01121..1eb6b9f1f4068b2f755375dfa5e9a11333c6dc2a 100644 (file)
@@ -736,6 +736,27 @@ ExecRelationIsTargetRelation(EState *estate, Index scanrelid)
    return bms_is_member(scanrelid, estate->es_plannedstmt->resultRelationRelids);
 }
 
+/*
+ * Return true if the scan node's relation is not modified by the query.
+ *
+ * This is not perfectly accurate. INSERT ... SELECT from the same table does
+ * not add the scan relation to resultRelationRelids, so it will be reported
+ * as read-only even though the query modifies it.
+ *
+ * Conversely, when any relation in the query has a modifying row mark, all
+ * other relations get a ROW_MARK_REFERENCE, causing them to be reported as
+ * not read-only even though they may be.
+ */
+bool
+ScanRelIsReadOnly(ScanState *ss)
+{
+   Index       scanrelid = ((Scan *) ss->ps.plan)->scanrelid;
+   PlannedStmt *pstmt = ss->ps.state->es_plannedstmt;
+
+   return !bms_is_member(scanrelid, pstmt->resultRelationRelids) &&
+       !bms_is_member(scanrelid, pstmt->rowMarkRelids);
+}
+
 /* ----------------------------------------------------------------
  *     ExecOpenScanRelation
  *
index 69683d81527853bb58d23d99980ed8edc2547335..73831aed45119f0ba85d6f485973f4afb1d4dbd1 100644 (file)
@@ -149,7 +149,8 @@ BitmapTableScanSetup(BitmapHeapScanState *node)
                               node->ss.ps.state->es_snapshot,
                               0,
                               NULL,
-                              SO_NONE);
+                              ScanRelIsReadOnly(&node->ss) ?
+                              SO_HINT_REL_READ_ONLY : SO_NONE);
    }
 
    node->ss.ss_currentScanDesc->st.rs_tbmiterator = tbmiterator;
index 02df40f32c5fd5e432a9b972dbcbd361ef5c66f5..de6154fd5413930ca1f3e8ba34468f6f98e090c6 100644 (file)
@@ -96,7 +96,8 @@ IndexOnlyNext(IndexOnlyScanState *node)
                                   node->ioss_Instrument,
                                   node->ioss_NumScanKeys,
                                   node->ioss_NumOrderByKeys,
-                                  SO_NONE);
+                                  ScanRelIsReadOnly(&node->ss) ?
+                                  SO_HINT_REL_READ_ONLY : SO_NONE);
 
        node->ioss_ScanDesc = scandesc;
 
@@ -796,7 +797,8 @@ ExecIndexOnlyScanInitializeDSM(IndexOnlyScanState *node,
                                 node->ioss_NumScanKeys,
                                 node->ioss_NumOrderByKeys,
                                 piscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
    node->ioss_ScanDesc->xs_want_itup = true;
    node->ioss_VMBuffer = InvalidBuffer;
 
@@ -863,7 +865,8 @@ ExecIndexOnlyScanInitializeWorker(IndexOnlyScanState *node,
                                 node->ioss_NumScanKeys,
                                 node->ioss_NumOrderByKeys,
                                 piscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
    node->ioss_ScanDesc->xs_want_itup = true;
 
    /*
index 3c0b8daf664b01c772c1a3a25c3bea557097e1c5..1620d146071733262454575b01f026f1c0b5f8bb 100644 (file)
@@ -114,7 +114,8 @@ IndexNext(IndexScanState *node)
                                   node->iss_Instrument,
                                   node->iss_NumScanKeys,
                                   node->iss_NumOrderByKeys,
-                                  SO_NONE);
+                                  ScanRelIsReadOnly(&node->ss) ?
+                                  SO_HINT_REL_READ_ONLY : SO_NONE);
 
        node->iss_ScanDesc = scandesc;
 
@@ -211,7 +212,8 @@ IndexNextWithReorder(IndexScanState *node)
                                   node->iss_Instrument,
                                   node->iss_NumScanKeys,
                                   node->iss_NumOrderByKeys,
-                                  SO_NONE);
+                                  ScanRelIsReadOnly(&node->ss) ?
+                                  SO_HINT_REL_READ_ONLY : SO_NONE);
 
        node->iss_ScanDesc = scandesc;
 
@@ -1733,7 +1735,8 @@ ExecIndexScanInitializeDSM(IndexScanState *node,
                                 node->iss_NumScanKeys,
                                 node->iss_NumOrderByKeys,
                                 piscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
 
    /*
     * If no run-time keys to calculate or they are ready, go ahead and pass
@@ -1798,7 +1801,8 @@ ExecIndexScanInitializeWorker(IndexScanState *node,
                                 node->iss_NumScanKeys,
                                 node->iss_NumOrderByKeys,
                                 piscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
 
    /*
     * If no run-time keys to calculate or they are ready, go ahead and pass
index cf32df33d82fbf24ba9ef5760fc8462b6952b533..f3d273e1c5e8fa8d2026ff90c183b8ff70759616 100644 (file)
@@ -299,7 +299,8 @@ tablesample_init(SampleScanState *scanstate)
                                     scanstate->use_bulkread,
                                     allow_sync,
                                     scanstate->use_pagemode,
-                                    SO_NONE);
+                                    ScanRelIsReadOnly(&scanstate->ss) ?
+                                    SO_HINT_REL_READ_ONLY : SO_NONE);
    }
    else
    {
index 09ccc65de1ce6304d1c706aaf7d5bb3f431d131a..04803b0e37d43a702a8cc787e0fdf0180a6897c6 100644 (file)
@@ -72,7 +72,8 @@ SeqNext(SeqScanState *node)
        scandesc = table_beginscan(node->ss.ss_currentRelation,
                                   estate->es_snapshot,
                                   0, NULL,
-                                  SO_NONE);
+                                  ScanRelIsReadOnly(&node->ss) ?
+                                  SO_HINT_REL_READ_ONLY : SO_NONE);
        node->ss.ss_currentScanDesc = scandesc;
    }
 
@@ -375,9 +376,11 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
                                  pscan,
                                  estate->es_snapshot);
    shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
+
    node->ss.ss_currentScanDesc =
        table_beginscan_parallel(node->ss.ss_currentRelation, pscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
 }
 
 /* ----------------------------------------------------------------
@@ -411,5 +414,6 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
    pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
    node->ss.ss_currentScanDesc =
        table_beginscan_parallel(node->ss.ss_currentRelation, pscan,
-                                SO_NONE);
+                                ScanRelIsReadOnly(&node->ss) ?
+                                SO_HINT_REL_READ_ONLY : SO_NONE);
 }
index 084e4c6ec90c2161bf1596ec39aca1c061bd9356..4a8fe91b2b342b4533c6495f7bf580af24c35a97 100644 (file)
@@ -246,7 +246,8 @@ TidRangeNext(TidRangeScanState *node)
                                                estate->es_snapshot,
                                                &node->trss_mintid,
                                                &node->trss_maxtid,
-                                               SO_NONE);
+                                               ScanRelIsReadOnly(&node->ss) ?
+                                               SO_HINT_REL_READ_ONLY : SO_NONE);
            node->ss.ss_currentScanDesc = scandesc;
        }
        else
@@ -461,7 +462,9 @@ ExecTidRangeScanInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt)
    shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
    node->ss.ss_currentScanDesc =
        table_beginscan_parallel_tidrange(node->ss.ss_currentRelation,
-                                         pscan, SO_NONE);
+                                         pscan,
+                                         ScanRelIsReadOnly(&node->ss) ?
+                                         SO_HINT_REL_READ_ONLY : SO_NONE);
 }
 
 /* ----------------------------------------------------------------
@@ -495,5 +498,7 @@ ExecTidRangeScanInitializeWorker(TidRangeScanState *node,
    pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
    node->ss.ss_currentScanDesc =
        table_beginscan_parallel_tidrange(node->ss.ss_currentRelation,
-                                         pscan, SO_NONE);
+                                         pscan,
+                                         ScanRelIsReadOnly(&node->ss) ?
+                                         SO_HINT_REL_READ_ONLY : SO_NONE);
 }
index e0d27afa876eeeaeb2a104ee4cee15b87ef7b33d..ab2e7fc1dfe3f824261bfe32734f287cced72a29 100644 (file)
@@ -65,6 +65,9 @@ typedef enum ScanOptions
 
    /* unregister snapshot at scan end? */
    SO_TEMP_SNAPSHOT = 1 << 9,
+
+   /* set if the query doesn't modify the relation */
+   SO_HINT_REL_READ_ONLY = 1 << 10,
 }          ScanOptions;
 
 /*
index 07f4b1f7490bfdc8817c04e0fdce09f93a8e56ea..7979a17e4ec79a42de84630c9a0bdf7c56453483 100644 (file)
@@ -690,6 +690,8 @@ extern void ExecCreateScanSlotFromOuterPlan(EState *estate,
 
 extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 
+extern bool ScanRelIsReadOnly(ScanState *ss);
+
 extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags);
 
 extern void ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos,