From: Xiaoxuan Li Date: Fri, 13 Nov 2026 15:34:12 -0800 Subject: [PATCH v1] Fix auto_explain crash for EXPLAIN without ANALYZE on index scans Commit f026fbf059f turned the IndexScanInstrumentation of index scan, index-only scan and bitmap index scan nodes into a pointer that is allocated only when the node is going to be run. For a plan started with EXEC_FLAG_EXPLAIN_ONLY it stays NULL, but show_indexscan_info() dereferenced it whenever es->analyze was set. EXPLAIN never sets es->analyze for such a plan, but auto_explain does. EXPLAIN (BUFFERS) without ANALYZE puts INSTRUMENT_BUFFERS into the query's instrument options, and with auto_explain.log_analyze enabled auto_explain then prints the plan with es->analyze set, which crashed the backend. Fix by treating a missing IndexScanInstrumentation as zero counts. That is what was shown before f026fbf059f, and what is shown for any other node that was never executed. Author: Xiaoxuan Li Backpatch-through: 19 --- contrib/auto_explain/t/001_auto_explain.pl | 51 ++++++++++++++++++++++++++++ src/backend/commands/explain.c | 23 ++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl index f32a5e84f65..0df5202a484 100644 --- a/contrib/auto_explain/t/001_auto_explain.pl +++ b/contrib/auto_explain/t/001_auto_explain.pl @@ -135,6 +135,57 @@ unlike( qr/Query Identifier:/, "query identifier not logged with compute_query_id=regress, text mode"); +# EXPLAIN (BUFFERS) without ANALYZE. The plan is started but never run, yet +# with log_analyze enabled it is logged as if it had been analyzed (BUFFERS +# is what requests instrumentation; a plain EXPLAIN is logged bare). Check +# that each kind of index scan copes with that. +$log_contents = query_log($node, + q{EXPLAIN (BUFFERS) SELECT * FROM pg_proc WHERE proname = 'int4pl';}); + +like( + $log_contents, + qr/Index Scan using pg_proc_proname_args_nsp_index on pg_proc .*\(never executed\)/, + "index scan logged as never executed, text mode"); + +like( + $log_contents, + qr/Index Searches: 0/, + "index searches logged for index scan, text mode"); + +$log_contents = query_log($node, + q{EXPLAIN (BUFFERS) SELECT proname FROM pg_proc WHERE proname = 'int4pl';} +); + +like( + $log_contents, + qr/Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc .*\(never executed\)/, + "index-only scan logged as never executed, text mode"); + +like( + $log_contents, + qr/Heap Fetches: 0/, + "heap fetches logged for index-only scan, text mode"); + +like( + $log_contents, + qr/Index Searches: 0/, + "index searches logged for index-only scan, text mode"); + +$log_contents = query_log( + $node, + q{EXPLAIN (BUFFERS) SELECT * FROM pg_proc WHERE proname = 'int4pl';}, + { "enable_indexscan" => "off" }); + +like( + $log_contents, + qr/Bitmap Index Scan on pg_proc_proname_args_nsp_index .*\(never executed\)/, + "bitmap index scan logged as never executed, text mode"); + +like( + $log_contents, + qr/Index Searches: 0/, + "index searches logged for bitmap index scan, text mode"); + # JSON format. $log_contents = query_log( $node, diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b8d1d0ec751..52e631ca96a 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3982,14 +3982,23 @@ show_indexscan_info(PlanState *planstate, ExplainState *es) if (!es->analyze) return; - /* Initialize counters with stats from the local process first */ + /* + * Initialize counters with stats from the local process first. + * + * The node's instrumentation is not allocated when the plan was started + * with EXEC_FLAG_EXPLAIN_ONLY. We can still get here in that case: for + * EXPLAIN (BUFFERS) without ANALYZE, auto_explain prints the plan with + * es->analyze set. Report zero counts then, like for any other node that + * was never executed. + */ switch (nodeTag(plan)) { case T_IndexScan: { IndexScanState *indexstate = ((IndexScanState *) planstate); - nsearches = indexstate->iss_Instrument->nsearches; + if (indexstate->iss_Instrument) + nsearches = indexstate->iss_Instrument->nsearches; SharedInfo = indexstate->iss_SharedInfo; break; } @@ -3997,8 +4006,11 @@ show_indexscan_info(PlanState *planstate, ExplainState *es) { IndexOnlyScanState *indexstate = ((IndexOnlyScanState *) planstate); - nsearches = indexstate->ioss_Instrument->nsearches; - ntabletuplefetches = indexstate->ioss_Instrument->ntabletuplefetches; + if (indexstate->ioss_Instrument) + { + nsearches = indexstate->ioss_Instrument->nsearches; + ntabletuplefetches = indexstate->ioss_Instrument->ntabletuplefetches; + } SharedInfo = indexstate->ioss_SharedInfo; break; } @@ -4006,7 +4018,8 @@ show_indexscan_info(PlanState *planstate, ExplainState *es) { BitmapIndexScanState *indexstate = ((BitmapIndexScanState *) planstate); - nsearches = indexstate->biss_Instrument->nsearches; + if (indexstate->biss_Instrument) + nsearches = indexstate->biss_Instrument->nsearches; SharedInfo = indexstate->biss_SharedInfo; break; } -- 2.54.0 (Apple Git-157)