From 8f83509b3237dacdd7b9d7ba8d51ad750603a038 Mon Sep 17 00:00:00 2001
From: Yuhang Qiu <iamqyh@gmail.com>
Date: Wed, 26 Aug 2026 11:58:21 +0000
Subject: [PATCH 3/3] explain: Report streaming I/O statistics for sample scans

The preceding commit gives sample scans a read stream, but their
statistics are not reported by EXPLAIN. Unlike other heap scan streams,
a sample scan's stream does not exist when heap_beginscan() returns.

Allocate scan instrumentation independently of stream creation, attach
it when the sample stream is initialized, and include SampleScan in
show_scan_io_usage().
---
 src/backend/access/heap/heapam.c         |  7 ++++---
 src/backend/access/heap/heapam_handler.c |  4 ++++
 src/backend/commands/explain.c           |  2 ++
 src/backend/executor/nodeSamplescan.c    | 11 +++++++++--
 4 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index a43d9fb87b1..2e0f09bff78 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1322,11 +1322,12 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	}
 
 	/* enable read stream instrumentation */
-	if ((flags & SO_SCAN_INSTRUMENT) && (scan->rs_read_stream != NULL))
+	if (flags & SO_SCAN_INSTRUMENT)
 	{
 		scan->rs_base.rs_instrument = palloc0_object(TableScanInstrumentation);
-		read_stream_enable_stats(scan->rs_read_stream,
-								 &scan->rs_base.rs_instrument->io);
+		if (scan->rs_read_stream != NULL)
+			read_stream_enable_stats(scan->rs_read_stream,
+									 &scan->rs_base.rs_instrument->io);
 	}
 
 	scan->rs_vmbuffer = InvalidBuffer;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index e804bb0db11..edff0774084 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2222,6 +2222,10 @@ heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
 								   heapam_scan_sample_stream_read_next,
 								   scanstate,
 								   0);
+
+		if (hscan->rs_base.rs_instrument)
+			read_stream_enable_stats(hscan->rs_read_stream,
+									 &hscan->rs_base.rs_instrument->io);
 	}
 
 	hscan->rs_cbuf = read_stream_next_buffer(hscan->rs_read_stream, NULL);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e35e0a649b3..cea6d6f4687 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4159,6 +4159,8 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
 
 				break;
 			}
+		case T_SampleScan:
+			break;
 		default:
 			/* ignore other plans */
 			return;
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index f3d273e1c5e..9aef0ffb9ed 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -19,6 +19,7 @@
 #include "access/tsmapi.h"
 #include "common/pg_prng.h"
 #include "executor/executor.h"
+#include "executor/instrument.h"
 #include "executor/nodeSamplescan.h"
 #include "utils/fmgrprotos.h"
 #include "utils/rel.h"
@@ -224,6 +225,7 @@ tablesample_init(SampleScanState *scanstate)
 	Datum		datum;
 	bool		isnull;
 	uint32		seed;
+	uint32		flags = SO_NONE;
 	bool		allow_sync;
 	int			i;
 	ListCell   *arg;
@@ -292,6 +294,12 @@ tablesample_init(SampleScanState *scanstate)
 	/* Now we can create or reset the HeapScanDesc */
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
+		if (ScanRelIsReadOnly(&scanstate->ss))
+			flags |= SO_HINT_REL_READ_ONLY;
+
+		if (scanstate->ss.ps.state->es_instrument & INSTRUMENT_IO)
+			flags |= SO_SCAN_INSTRUMENT;
+
 		scanstate->ss.ss_currentScanDesc =
 			table_beginscan_sampling(scanstate->ss.ss_currentRelation,
 									 scanstate->ss.ps.state->es_snapshot,
@@ -299,8 +307,7 @@ tablesample_init(SampleScanState *scanstate)
 									 scanstate->use_bulkread,
 									 allow_sync,
 									 scanstate->use_pagemode,
-									 ScanRelIsReadOnly(&scanstate->ss) ?
-									 SO_HINT_REL_READ_ONLY : SO_NONE);
+									 flags);
 	}
 	else
 	{
-- 
2.43.7

