From 6a570e087696479f1be020c13891c8c6242d91e7 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Fri, 3 Apr 2026 14:15:28 +0200
Subject: [PATCH v8 2/5] Show Bitmap Heap Scan stats for non-parallel-aware
 workers

Commit 5a1e6df3b84c added information about exact/lossy pages for each
parallel worker. It however works only for parallel-aware scans, not for
other parallel scans. For example scans on the outer side of a parallel
join are not parallel-aware, and so won't report the stats. Similarly,
queries run with debug_parallel_query=regress are not parallel-aware,
which may cause regression failures due to changes in explain output.

Fixed in a way similar to 0fbceae841cb, by invoking functions always,
and only then considering parallel_aware.

Probably needs to be backpatched to 18.
---
 src/backend/commands/explain.c            |  2 +-
 src/backend/executor/execParallel.c       | 18 ++---
 src/backend/executor/nodeBitmapHeapscan.c | 86 ++++++++++++++++-------
 3 files changed, 71 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e4b70166b0e..8275bb2af61 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3949,7 +3949,7 @@ show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
 	}
 
 	/* Display stats for each parallel worker */
-	if (planstate->pstate != NULL)
+	if (planstate->sinstrument != NULL)
 	{
 		for (int n = 0; n < planstate->sinstrument->num_workers; n++)
 		{
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 755191b51ef..ce377a774f8 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -290,9 +290,9 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
 									   e->pcxt);
 			break;
 		case T_BitmapHeapScanState:
-			if (planstate->plan->parallel_aware)
-				ExecBitmapHeapEstimate((BitmapHeapScanState *) planstate,
-									   e->pcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecBitmapHeapEstimate((BitmapHeapScanState *) planstate,
+								   e->pcxt);
 			break;
 		case T_HashJoinState:
 			if (planstate->plan->parallel_aware)
@@ -522,9 +522,9 @@ ExecParallelInitializeDSM(PlanState *planstate,
 											d->pcxt);
 			break;
 		case T_BitmapHeapScanState:
-			if (planstate->plan->parallel_aware)
-				ExecBitmapHeapInitializeDSM((BitmapHeapScanState *) planstate,
-											d->pcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecBitmapHeapInitializeDSM((BitmapHeapScanState *) planstate,
+										d->pcxt);
 			break;
 		case T_HashJoinState:
 			if (planstate->plan->parallel_aware)
@@ -1400,9 +1400,9 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
 											   pwcxt);
 			break;
 		case T_BitmapHeapScanState:
-			if (planstate->plan->parallel_aware)
-				ExecBitmapHeapInitializeWorker((BitmapHeapScanState *) planstate,
-											   pwcxt);
+			/* even when not parallel-aware, for EXPLAIN ANALYZE */
+			ExecBitmapHeapInitializeWorker((BitmapHeapScanState *) planstate,
+										   pwcxt);
 			break;
 		case T_HashJoinState:
 			if (planstate->plan->parallel_aware)
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 73831aed451..0c580365e9c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -495,9 +495,21 @@ void
 ExecBitmapHeapEstimate(BitmapHeapScanState *node,
 					   ParallelContext *pcxt)
 {
-	Size		size;
+	Size		size = 0;
+	bool		instrument = (node->ss.ps.instrument != NULL);
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 
-	size = MAXALIGN(sizeof(ParallelBitmapHeapState));
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
+		return;
+	}
+
+	/* parallel state is needed only in parallel-aware scans */
+	if (parallel_aware)
+	{
+		size = MAXALIGN(sizeof(ParallelBitmapHeapState));
+	}
 
 	/* account for instrumentation, if required */
 	if (node->ss.ps.instrument && pcxt->nworkers > 0)
@@ -521,48 +533,61 @@ ExecBitmapHeapInitializeDSM(BitmapHeapScanState *node,
 							ParallelContext *pcxt)
 {
 	ParallelBitmapHeapState *pstate;
+	bool		instrument = node->ss.ps.instrument != NULL;
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 	SharedBitmapHeapInstrumentation *sinstrument = NULL;
-	dsa_area   *dsa = node->ss.ps.state->es_query_dsa;
 	char	   *ptr;
-	Size		size;
+	Size		size = 0;
 
-	/* If there's no DSA, there are no workers; initialize nothing. */
-	if (dsa == NULL)
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
 		return;
+	}
 
-	size = MAXALIGN(sizeof(ParallelBitmapHeapState));
-	if (node->ss.ps.instrument && pcxt->nworkers > 0)
+	/* parallel state is needed only in parallel-aware scans */
+	if (parallel_aware)
+	{
+		size = MAXALIGN(sizeof(ParallelBitmapHeapState));
+	}
+
+	if (instrument && pcxt->nworkers > 0)
 	{
 		size = add_size(size, offsetof(SharedBitmapHeapInstrumentation, sinstrument));
 		size = add_size(size, mul_size(pcxt->nworkers, sizeof(BitmapHeapScanInstrumentation)));
 	}
 
 	ptr = shm_toc_allocate(pcxt->toc, size);
-	pstate = (ParallelBitmapHeapState *) ptr;
-	ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
-	if (node->ss.ps.instrument && pcxt->nworkers > 0)
-		sinstrument = (SharedBitmapHeapInstrumentation *) ptr;
+	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, ptr);
+
+	if (parallel_aware)
+	{
+		pstate = (ParallelBitmapHeapState *) ptr;
+		ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
 
-	pstate->tbmiterator = 0;
+		pstate->tbmiterator = 0;
 
-	/* Initialize the mutex */
-	SpinLockInit(&pstate->mutex);
-	pstate->state = BM_INITIAL;
+		/* Initialize the mutex */
+		SpinLockInit(&pstate->mutex);
+		pstate->state = BM_INITIAL;
 
-	ConditionVariableInit(&pstate->cv);
+		ConditionVariableInit(&pstate->cv);
 
-	if (sinstrument)
+		node->pstate = pstate;
+	}
+
+	if (instrument && pcxt->nworkers > 0)
 	{
+		sinstrument = (SharedBitmapHeapInstrumentation *) ptr;
+
 		sinstrument->num_workers = pcxt->nworkers;
 
 		/* ensure any unfilled slots will contain zeroes */
 		memset(sinstrument->sinstrument, 0,
 			   pcxt->nworkers * sizeof(BitmapHeapScanInstrumentation));
-	}
 
-	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pstate);
-	node->pstate = pstate;
-	node->sinstrument = sinstrument;
+		node->sinstrument = sinstrument;
+	}
 }
 
 /* ----------------------------------------------------------------
@@ -600,16 +625,27 @@ void
 ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
 							   ParallelWorkerContext *pwcxt)
 {
+	bool		instrument = node->ss.ps.instrument != NULL;
+	bool		parallel_aware = node->ss.ps.plan->parallel_aware;
 	char	   *ptr;
 
+	if (!instrument && !parallel_aware)
+	{
+		/* No DSM required by the scan */
+		return;
+	}
+
 	Assert(node->ss.ps.state->es_query_dsa != NULL);
 
 	ptr = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 
-	node->pstate = (ParallelBitmapHeapState *) ptr;
-	ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
+	if (parallel_aware)
+	{
+		node->pstate = (ParallelBitmapHeapState *) ptr;
+		ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
+	}
 
-	if (node->ss.ps.instrument)
+	if (instrument)
 		node->sinstrument = (SharedBitmapHeapInstrumentation *) ptr;
 }
 
-- 
2.53.0

