From f2b8022f0e4e9fa2af759d6e7ab26301d6688726 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Mon, 26 Oct 2026 15:34:27 +0000
Subject: [PATCH v1 3/5] Refactor table scan instrumentation lookup

Extract the lookup of leader and per-worker table scan instrumentation from
show_scan_io_usage(). This will allow other scan instrumentation to reuse the
lookup without coupling it to I/O reporting.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion: https://postgr.es/m/
---
 src/backend/commands/explain.c | 158 +++++++++++++++++----------------
 1 file changed, 80 insertions(+), 78 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4b1ad4227ac..d0ce72d83ff 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4088,112 +4088,114 @@ print_io_usage(ExplainState *es, IOStats *stats)
 }
 
 /*
- * Show information about prefetch and I/O in a scan node.
+ * Return table scan instrumentation for the leader or a parallel worker.
+ *
+ * Pass a negative worker number for the leader. Return NULL if the process
+ * has no scan instrumentation or the node type does not collect it.
  */
-static void
-show_scan_io_usage(ScanState *planstate, ExplainState *es)
+static TableScanInstrumentation *
+get_table_scan_instrumentation(ScanState *planstate, int worker)
 {
-	Plan	   *plan = planstate->ps.plan;
-	IOStats		stats = {0};
-
-	if (!es->io)
-		return;
-
-	/*
-	 * Initialize counters with stats from the local process first.
-	 *
-	 * The scan descriptor may not exist, e.g. if the scan did not start, or
-	 * because of debug_parallel_query=regress. We still want to collect data
-	 * from workers.
-	 */
-	if (planstate->ss_currentScanDesc &&
-		planstate->ss_currentScanDesc->rs_instrument)
+	if (worker < 0)
 	{
-		stats = planstate->ss_currentScanDesc->rs_instrument->io;
+		if (planstate->ss_currentScanDesc)
+			return planstate->ss_currentScanDesc->rs_instrument;
+
+		return NULL;
 	}
 
-	/*
-	 * Accumulate data from parallel workers (if any).
-	 */
-	switch (nodeTag(plan))
+	switch (nodeTag(planstate->ps.plan))
 	{
 		case T_BitmapHeapScan:
 			{
-				SharedBitmapHeapInstrumentation *sinstrument
-				= ((BitmapHeapScanState *) planstate)->sinstrument;
-
-				if (sinstrument)
-				{
-					for (int i = 0; i < sinstrument->num_workers; ++i)
-					{
-						BitmapHeapScanInstrumentation *winstrument = &sinstrument->sinstrument[i];
-
-						AccumulateIOStats(&stats, &winstrument->stats.io);
-
-						if (!es->workers_state)
-							continue;
-
-						ExplainOpenWorker(i, es);
-						print_io_usage(es, &winstrument->stats.io);
-						ExplainCloseWorker(i, es);
-					}
-				}
+				SharedBitmapHeapInstrumentation *sinstrument =
+					((BitmapHeapScanState *) planstate)->sinstrument;
 
+				if (sinstrument && worker < sinstrument->num_workers)
+					return &sinstrument->sinstrument[worker].stats;
 				break;
 			}
 		case T_SeqScan:
 			{
-				SharedSeqScanInstrumentation *sinstrument
-				= ((SeqScanState *) planstate)->sinstrument;
-
-				if (sinstrument)
-				{
-					for (int i = 0; i < sinstrument->num_workers; ++i)
-					{
-						SeqScanInstrumentation *winstrument = &sinstrument->sinstrument[i];
-
-						AccumulateIOStats(&stats, &winstrument->stats.io);
-
-						if (!es->workers_state)
-							continue;
-
-						ExplainOpenWorker(i, es);
-						print_io_usage(es, &winstrument->stats.io);
-						ExplainCloseWorker(i, es);
-					}
-				}
+				SharedSeqScanInstrumentation *sinstrument =
+					((SeqScanState *) planstate)->sinstrument;
 
+				if (sinstrument && worker < sinstrument->num_workers)
+					return &sinstrument->sinstrument[worker].stats;
 				break;
 			}
 		case T_TidRangeScan:
 			{
-				SharedTidRangeScanInstrumentation *sinstrument
-				= ((TidRangeScanState *) planstate)->trss_sinstrument;
+				SharedTidRangeScanInstrumentation *sinstrument =
+					((TidRangeScanState *) planstate)->trss_sinstrument;
 
-				if (sinstrument)
-				{
-					for (int i = 0; i < sinstrument->num_workers; ++i)
-					{
-						TidRangeScanInstrumentation *winstrument = &sinstrument->sinstrument[i];
+				if (sinstrument && worker < sinstrument->num_workers)
+					return &sinstrument->sinstrument[worker].stats;
+				break;
+			}
+		default:
+			break;
+	}
 
-						AccumulateIOStats(&stats, &winstrument->stats.io);
+	return NULL;
+}
 
-						if (!es->workers_state)
-							continue;
+/*
+ * Show information about prefetch and I/O in a scan node.
+ */
+static void
+show_scan_io_usage(ScanState *planstate, ExplainState *es)
+{
+	TableScanInstrumentation *instrument;
+	IOStats		stats = {0};
 
-						ExplainOpenWorker(i, es);
-						print_io_usage(es, &winstrument->stats.io);
-						ExplainCloseWorker(i, es);
-					}
-				}
+	if (!es->io)
+		return;
 
-				break;
-			}
+	/* I/O usage is reported only for these scan types */
+	switch (nodeTag(planstate->ps.plan))
+	{
+		case T_BitmapHeapScan:
+		case T_SeqScan:
+		case T_TidRangeScan:
+			break;
 		default:
 			/* ignore other plans */
 			return;
 	}
 
+	/*
+	 * Initialize counters with stats from the local process first.
+	 *
+	 * The scan descriptor may not exist, e.g. if the scan did not start, or
+	 * because of debug_parallel_query=regress. We still want to collect data
+	 * from workers.
+	 */
+	instrument = get_table_scan_instrumentation(planstate, -1);
+	if (instrument)
+		stats = instrument->io;
+
+	/*
+	 * Accumulate data from parallel workers (if any).
+	 */
+	for (int i = 0;; ++i)
+	{
+		TableScanInstrumentation *winstrument =
+			get_table_scan_instrumentation(planstate, i);
+
+		if (!winstrument)
+			break;
+
+		AccumulateIOStats(&stats, &winstrument->io);
+
+		if (!es->workers_state)
+			continue;
+
+		ExplainOpenWorker(i, es);
+		print_io_usage(es, &winstrument->io);
+		ExplainCloseWorker(i, es);
+	}
+
 	print_io_usage(es, &stats);
 }
 
-- 
2.34.1

