Skip to content

Commit 0f26025

Browse files
committed
Stabilize regression tests across PostgreSQL 17–19
Extend portable_explain_analyze() to normalise additional sources of version-specific output variation: - Strip "Index Searches: N" lines introduced in PG 18 so that a single expected file covers both PG 17 and PG 18+. This also lets us drop expected/join_filtering_1.out entirely, leaving one canonical file. - Normalise "actual rows=N.00" to "actual rows=N". PG 18 started printing row counts with two decimal places inside EXPLAIN ANALYZE output; the normalisation hides this from all callers of the helper. - Normalise "Heap Fetches: N" to mask the non-deterministic value that depends on visibility-map state at test time. Switch subplan.sql to route both EXPLAIN calls through portable_explain_analyze() so that the normalisation rules apply there too. Add "AND query LIKE 'EXPLAIN%'" to the tracking-result query so that only the inner EXPLAIN query appears in pg_track_optimizer(), not the portable_explain_analyze() wrapper itself. Update expected output files accordingly: - expected/subplan.out – PG 19+ (SubPlan expr_1 naming) - expected/subplan_1.out – PG 17–18 (SubPlan 1 naming) - expected/subplan_2.out – deleted; PG 16 is no longer supported - expected/join_filtering_1.out – deleted; consolidated into one file - expected/pg_track_optimizer.out / _1.out – updated for the portable_explain_analyze() change in the verify_test block Add a "Regression Test Expected Output Files" section to README.md documenting which file covers which PostgreSQL version and why.
1 parent 4422777 commit 0f26025

10 files changed

Lines changed: 67 additions & 410 deletions

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,22 @@ SELECT pg_track_optimizer_reset();
313313
-- Re-check error metrics
314314
```
315315

316+
## Regression Test Expected Output Files
317+
318+
PostgreSQL's `pg_regress` supports multiple alternative expected output files
319+
(`test.out`, `test_1.out`, `test_2.out`, …). It tries each in order and
320+
passes if any matches. This extension uses that mechanism to handle
321+
differences in plan output across PostgreSQL versions.
322+
323+
| File | PostgreSQL version | Key differences |
324+
|------|--------------------|-----------------|
325+
| `expected/pg_track_optimizer.out` | PG 18+ | `actual rows=N.00` notation; `Heap Fetches` normalised via `portable_explain_analyze`; `Index Searches` filtered out |
326+
| `expected/pg_track_optimizer_1.out` | PG 17 | `actual rows=N` (no decimals); `Heap Fetches` normalised; `Index Searches` filtered out |
327+
| `expected/join_filtering.out` | all versions | `Index Searches` and `rows=N.00` normalised by `portable_explain_analyze` |
328+
| `expected/subplan.out` | PG 19+ | `SubPlan expr_1` naming (new in PG 19); output via `portable_explain_analyze` |
329+
| `expected/subplan_1.out` | PG 17–18 | `SubPlan 1` naming; output via `portable_explain_analyze` |
330+
| `expected/interface.out` | all versions | No version-specific differences |
331+
316332
## Implementation Notes
317333

318334
- **Filtering metrics**: The extension tracks filtering overhead separately from error calculations via `f_scan_filter` and `f_join_filter` metrics. For leaf nodes (scans), `f_scan_filter` captures `nfiltered1` weighted by relative time and output rows. For JOIN nodes, `f_join_filter` captures `nfiltered1 + nfiltered2` similarly weighted. These metrics help identify inefficient scans or joins where many rows are fetched but filtered out. Filtered tuples are **not** included in error calculations (`avg_error`, `rms_error`, etc.) to keep error metrics focused on cardinality estimation accuracy rather than filtering efficiency.

expected/join_filtering.out

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ begin
1111
out_line := regexp_replace(line, '\d+kB', 'NNkB', 'g');
1212
out_line := regexp_replace(out_line, 'rows=(\d+)\.00', 'rows=\1', 'g');
1313
out_line := regexp_replace(out_line, '(Heap Fetches:) \d+', '\1 N', 'g');
14+
-- Skip Index Searches line: added in PG 18, not present in PG 17
15+
if out_line ~ '^\s*Index Searches:' then
16+
continue;
17+
end if;
1418
return next;
1519
end loop;
1620
end;
@@ -157,12 +161,11 @@ SELECT portable_explain_analyze('SELECT * FROM join_inner JOIN join_outer USING
157161
Merge Join (actual rows=100 loops=1)
158162
Merge Cond: (join_outer.id = join_inner.id)
159163
-> Index Scan using join_outer_id_idx on join_outer (actual rows=101 loops=1)
160-
Index Searches: 1
161164
-> Sort (actual rows=100 loops=1)
162165
Sort Key: join_inner.id
163166
Sort Method: quicksort Memory: NNkB
164167
-> Seq Scan on join_inner (actual rows=100 loops=1)
165-
(8 rows)
168+
(7 rows)
166169

167170
SELECT
168171
ROUND((avg_avg::numeric), 2) AS err,

expected/join_filtering_1.out

Lines changed: 0 additions & 221 deletions
This file was deleted.

expected/pg_track_optimizer.out

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ SELECT portable_explain_analyze('SELECT val FROM verify_test;');
146146
Workers Launched: 4
147147
-> Parallel Index Only Scan using idx_verify on verify_test (actual rows=20000 loops=5)
148148
Heap Fetches: N
149-
Index Searches: 1
150-
(6 rows)
149+
(5 rows)
151150

152151
-- XXX: if we ever implement per-node error printing it would allow to
153152
-- demonstrate how the error grows and show reasoning for the final value in

expected/pg_track_optimizer_1.out

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,14 @@ UPDATE verify_test SET val = val + 1 WHERE id % 10 = 0;
138138
SET min_parallel_index_scan_size = 0;
139139
SET enable_seqscan = off;
140140
-- Check if we get parallel index-only scan WITH heap fetches
141-
EXPLAIN (COSTS OFF, ANALYZE, BUFFERS OFF, TIMING OFF, SUMMARY OFF)
142-
SELECT val FROM verify_test;
143-
QUERY PLAN
141+
SELECT portable_explain_analyze('SELECT val FROM verify_test;');
142+
portable_explain_analyze
144143
--------------------------------------------------------------------------------------------
145144
Gather (actual rows=100000 loops=1)
146145
Workers Planned: 4
147146
Workers Launched: 4
148147
-> Parallel Index Only Scan using idx_verify on verify_test (actual rows=20000 loops=5)
149-
Heap Fetches: 110000
148+
Heap Fetches: N
150149
(5 rows)
151150

152151
-- XXX: if we ever implement per-node error printing it would allow to
@@ -155,11 +154,11 @@ SELECT val FROM verify_test;
155154
-- So, make this test helpful in the future ...
156155
SELECT query,evaluated_nodes,plan_nodes,nexecs
157156
FROM pg_track_optimizer() WHERE query LIKE '%FROM verify_test%';
158-
query | evaluated_nodes | plan_nodes | nexecs
159-
--------------------------------------------------------------------+-----------------+------------+--------
160-
EXPLAIN (COSTS OFF, ANALYZE, BUFFERS OFF, TIMING OFF, SUMMARY OFF)+| 2 | 2 | 1
161-
SELECT val FROM verify_test; | | |
162-
(1 row)
157+
query | evaluated_nodes | plan_nodes | nexecs
158+
------------------------------------------------------------------------------------------------+-----------------+------------+--------
159+
SELECT portable_explain_analyze('SELECT val FROM verify_test;'); | 2 | 2 | 1
160+
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)SELECT val FROM verify_test; | 2 | 2 | 1
161+
(2 rows)
163162

164163
-- Test the case when we prepare all the stuff for execution but never actually
165164
-- executed query plan (total time == 0)
@@ -176,7 +175,7 @@ COMMIT;
176175
SELECT * FROM pg_track_optimizer_reset();
177176
pg_track_optimizer_reset
178177
--------------------------
179-
13
178+
14
180179
(1 row)
181180

182181
DROP EXTENSION pg_track_optimizer;

0 commit comments

Comments
 (0)