From 0b19891ca39f986492d5079dc6406de454bc3511 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Wed, 1 Apr 2026 20:20:57 +0200
Subject: [PATCH v7 4/6] fixup: show prefetch stats for SeqScan

---
 src/backend/commands/explain.c      | 29 +++++----
 src/backend/executor/execParallel.c | 16 ++---
 src/backend/executor/nodeSeqscan.c  | 94 ++++++++++++++++++++---------
 3 files changed, 88 insertions(+), 51 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f91e9d0048e..20c5d720516 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4053,21 +4053,26 @@ static void
 show_scan_io_usage(ScanState *planstate, ExplainState *es)
 {
 	Plan	   *plan = planstate->ps.plan;
-	IOStats		stats;
+	IOStats		stats = {0};
 
 	if (!es->io)
 		return;
 
-	/* scan not started or no prefetch stats */
-	if (!(planstate &&
-		  planstate->ss_currentScanDesc &&
-		  planstate->ss_currentScanDesc->rs_instrument))
-		return;
-
 	/*
-	 * Initialize counters with stats from the local process first, then
-	 * accumulate data from parallel workers.
+	 * 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 &&
+		planstate->ss_currentScanDesc &&
+		planstate->ss_currentScanDesc->rs_instrument)
+	{
+		stats = planstate->ss_currentScanDesc->rs_instrument->io;
+	}
+
+	/* accumulate data from parallel workers */
 	switch (nodeTag(plan))
 	{
 		case T_SeqScan:
@@ -4075,9 +4080,6 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
 				SharedSeqScanInstrumentation *sinstrument
 					= ((SeqScanState *) planstate)->sinstrument;
 
-				/* collect prefetch statistics from the read stream */
-				stats = planstate->ss_currentScanDesc->rs_instrument->io;
-
 				/* get the sum of the counters set within each and every process */
 				if (sinstrument)
 				{
@@ -4096,9 +4098,6 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
 				SharedBitmapHeapInstrumentation *sinstrument
 				= ((BitmapHeapScanState *) planstate)->sinstrument;
 
-				/* collect prefetch statistics from the read stream */
-				stats = planstate->ss_currentScanDesc->rs_instrument->io;
-
 				/*
 				 * get the sum of the counters set within each and every
 				 * process
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index d1747993df0..c6f7ee292dd 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -250,9 +250,9 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
 	switch (nodeTag(planstate))
 	{
 		case T_SeqScanState:
-			if (planstate->plan->parallel_aware)
-				ExecSeqScanEstimate((SeqScanState *) planstate,
-									e->pcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecSeqScanEstimate((SeqScanState *) planstate,
+								e->pcxt);
 			break;
 		case T_IndexScanState:
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
@@ -484,9 +484,9 @@ ExecParallelInitializeDSM(PlanState *planstate,
 	switch (nodeTag(planstate))
 	{
 		case T_SeqScanState:
-			if (planstate->plan->parallel_aware)
-				ExecSeqScanInitializeDSM((SeqScanState *) planstate,
-										 d->pcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecSeqScanInitializeDSM((SeqScanState *) planstate,
+									 d->pcxt);
 			break;
 		case T_IndexScanState:
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
@@ -1366,8 +1366,8 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
 	switch (nodeTag(planstate))
 	{
 		case T_SeqScanState:
-			if (planstate->plan->parallel_aware)
-				ExecSeqScanInitializeWorker((SeqScanState *) planstate, pwcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecSeqScanInitializeWorker((SeqScanState *) planstate, pwcxt);
 			break;
 		case T_IndexScanState:
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 032f3dc5efc..2e4a6138eae 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -387,17 +387,25 @@ ExecSeqScanEstimate(SeqScanState *node,
 					ParallelContext *pcxt)
 {
 	EState	   *estate = node->ss.ps.state;
+	bool		instrument = node->ss.ps.instrument != NULL;
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 	Size		size;
 
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
+		return;
+	}
+
 	size = table_parallelscan_estimate(node->ss.ss_currentRelation,
-												  estate->es_snapshot);
+									   estate->es_snapshot);
 	node->pscan_len = size;
 
 	/* make sure the instrumentation is properly aligned */
 	size = MAXALIGN(size);
 
 	/* account for instrumentation, if required */
-	if (node->ss.ps.instrument && pcxt->nworkers > 0)
+	if (instrument && pcxt->nworkers > 0)
 	{
 		size = add_size(size, offsetof(SharedSeqScanInstrumentation, sinstrument));
 		size = add_size(size, mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
@@ -420,13 +428,20 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 	EState	   *estate = node->ss.ps.state;
 	ParallelTableScanDesc pscan;
 	SharedSeqScanInstrumentation *sinstrument = NULL;
+	bool		instrument = node->ss.ps.instrument != NULL;
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 	Size		size;
-	char	   *ptr;
 	uint32		flags = SO_NONE;
 
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
+		return;
+	}
+
 	/* Recalculate the size. This needs to match ExecSeqScanEstimate. */
 	size = MAXALIGN(node->pscan_len);
-	if (node->ss.ps.instrument && pcxt->nworkers > 0)
+	if (instrument && pcxt->nworkers > 0)
 	{
 		size = add_size(size, offsetof(SharedSeqScanInstrumentation, sinstrument));
 		size = add_size(size, mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
@@ -440,31 +455,38 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 								  estate->es_snapshot);
 	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
 
-	if (ScanRelIsReadOnly(&node->ss))
-		flags |= SO_HINT_REL_READ_ONLY;
-
-	if (estate->es_instrument)
-		flags |= SO_SCAN_INSTRUMENT;
+	/* initialize the shared instrumentation (with correct alignment) */
+	if (instrument && pcxt->nworkers > 0)
+	{
+		char *ptr = (char *) pscan;
 
-	node->ss.ss_currentScanDesc =
-		table_beginscan_parallel(node->ss.ss_currentRelation, pscan, flags);
+		ptr += MAXALIGN(node->pscan_len);
 
-	/* initialize the shared instrumentation (with correct alignment) */
-	ptr = (char *) pscan;
-	ptr += MAXALIGN(node->pscan_len);
-	if (node->ss.ps.instrument && pcxt->nworkers > 0)
 		sinstrument = (SharedSeqScanInstrumentation *) ptr;
 
-	if (sinstrument)
-	{
 		sinstrument->num_workers = pcxt->nworkers;
 
 		/* ensure any unfilled slots will contain zeroes */
 		memset(sinstrument->sinstrument, 0,
 			   pcxt->nworkers * sizeof(SeqScanInstrumentation));
+
+		node->sinstrument = sinstrument;
+	}
+
+	if (!parallel_aware)
+	{
+		/* Only here to set up worker node's shared instrumentation */
+		return;
 	}
 
-	node->sinstrument = sinstrument;
+	if (ScanRelIsReadOnly(&node->ss))
+		flags |= SO_HINT_REL_READ_ONLY;
+
+	if (estate->es_instrument)
+		flags |= SO_SCAN_INSTRUMENT;
+
+	node->ss.ss_currentScanDesc =
+		table_beginscan_parallel(node->ss.ss_currentRelation, pscan, flags);
 }
 
 /* ----------------------------------------------------------------
@@ -493,27 +515,43 @@ void
 ExecSeqScanInitializeWorker(SeqScanState *node,
 							ParallelWorkerContext *pwcxt)
 {
-	EState	   *estate = node->ss.ps.state;
 	ParallelTableScanDesc pscan;
-	char	   *ptr;
+	EState	   *estate = node->ss.ps.state;
+	bool		instrument = node->ss.ps.instrument != NULL;
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 	uint32		flags = SO_NONE;
 
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
+		return;
+	}
+
+	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
+
+	/* set pointer to the shared instrumentation */
+	if (instrument)
+	{
+		char *ptr = (char *) pscan;
+		ptr += MAXALIGN(pscan->phs_len);
+
+		node->sinstrument = (SharedSeqScanInstrumentation *) ptr;
+	}
+
+	if (!parallel_aware)
+	{
+		/* Only here to set up worker node's SharedInfo */
+		return;
+	}
+
 	if (ScanRelIsReadOnly(&node->ss))
 		flags |= SO_HINT_REL_READ_ONLY;
 
 	if (estate->es_instrument)
 		flags |= SO_SCAN_INSTRUMENT;
 
-	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, flags);
-
-	/* set pointer to the shared instrumentation */
-	ptr = (char *) pscan;
-	ptr += MAXALIGN(pscan->phs_len);
-
-	if (node->ss.ps.instrument)
-		node->sinstrument = (SharedSeqScanInstrumentation *) ptr;
 }
 
 /* ----------------------------------------------------------------
-- 
2.53.0

