PG18: Add _plan_json() test helper and switch columnar index tests to JSON plan checks (#8299)

fixes #8263

* Introduces `columnar_test_helpers._plan_json(q text) -> jsonb`, which
runs `EXPLAIN (FORMAT JSON, COSTS OFF, ANALYZE OFF)` and returns the
plan as `jsonb`. This lets us assert on plan structure instead of text
matching.

* Updates `columnar_indexes.sql` tests to detect whether any index-based
scan is used by searching the JSON plan’s `"Node Type"` (e.g., `Index
Scan`, `Index Only Scan`, `Bitmap Index Scan`).

## Notable changes

* New helper in `src/test/regress/sql/columnar_test_helpers.sql`:

* `EXECUTE format('EXPLAIN (FORMAT JSON, COSTS OFF, ANALYZE OFF) %s', q)
INTO j;`
  * Returns the `jsonb` plan for downstream assertions.
  
  ```sql
  SELECT NOT jsonb_path_exists(
columnar_test_helpers._plan_json('SELECT b FROM columnar_table WHERE b =
30000'),
'$[*].Plan.** ? (@."Node Type" like_regex "^(Index|Bitmap
Index).*Scan$")'
  ) AS uses_no_index_scan;
  ```

to verify no index scan occurs at the partial index boundary (`b =
30000`) and that an index scan is used where expected (`b = 30001`).

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
pull/8292/head
Mehmet YILMAZ 2025-11-03 15:34:28 +03:00 committed by GitHub
parent 6251eab9b7
commit 61b491f0f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 6 deletions

View File

@ -176,12 +176,15 @@ SELECT pg_total_relation_size('columnar_table_b_idx') * 5 <
(1 row)
-- can't use index scan due to partial index boundaries
EXPLAIN (COSTS OFF) SELECT b FROM columnar_table WHERE b = 30000;
QUERY PLAN
SELECT NOT jsonb_path_exists(
columnar_test_helpers._plan_json('SELECT b FROM columnar_table WHERE b = 30000'),
-- Regex matches any index-based scan: "Index Scan", "Index Only Scan", "Bitmap Index Scan".
'$[*].Plan.** ? (@."Node Type" like_regex "^(Index|Bitmap Index).*Scan$")'
) AS uses_no_index_scan; -- expect: t
uses_no_index_scan
---------------------------------------------------------------------
Seq Scan on columnar_table
Filter: (b = 30000)
(2 rows)
t
(1 row)
-- can use index scan
EXPLAIN (COSTS OFF) SELECT b FROM columnar_table WHERE b = 30001;

View File

@ -146,3 +146,11 @@ BEGIN
RETURN NEXT;
END LOOP;
END; $$ language plpgsql;
CREATE OR REPLACE FUNCTION _plan_json(q text)
RETURNS jsonb
LANGUAGE plpgsql AS $$
DECLARE j jsonb;
BEGIN
EXECUTE format('EXPLAIN (FORMAT JSON, COSTS OFF, ANALYZE OFF) %s', q) INTO j;
RETURN j;
END $$;

View File

@ -114,7 +114,11 @@ SELECT pg_total_relation_size('columnar_table_b_idx') * 5 <
pg_total_relation_size('columnar_table_a_idx');
-- can't use index scan due to partial index boundaries
EXPLAIN (COSTS OFF) SELECT b FROM columnar_table WHERE b = 30000;
SELECT NOT jsonb_path_exists(
columnar_test_helpers._plan_json('SELECT b FROM columnar_table WHERE b = 30000'),
-- Regex matches any index-based scan: "Index Scan", "Index Only Scan", "Bitmap Index Scan".
'$[*].Plan.** ? (@."Node Type" like_regex "^(Index|Bitmap Index).*Scan$")'
) AS uses_no_index_scan; -- expect: t
-- can use index scan
EXPLAIN (COSTS OFF) SELECT b FROM columnar_table WHERE b = 30001;

View File

@ -158,3 +158,12 @@ BEGIN
RETURN NEXT;
END LOOP;
END; $$ language plpgsql;
CREATE OR REPLACE FUNCTION _plan_json(q text)
RETURNS jsonb
LANGUAGE plpgsql AS $$
DECLARE j jsonb;
BEGIN
EXECUTE format('EXPLAIN (FORMAT JSON, COSTS OFF, ANALYZE OFF) %s', q) INTO j;
RETURN j;
END $$;