Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ true until the next version shipped.

### Added

- Exact zone-map boundary coverage now lives in matching shell and pytest tests
(#831).

The `<=` and `>=` arms put the constant exactly at a row-group minimum or
maximum and compare returned rows with a heap twin. The `<`, `>`, and `=`
mirrors assert groups removed with bloom disabled, so a conservative pruning
regression cannot hide behind a correct answer. Each of the five one-token
strategy mutations was proved to fail its corresponding assertion.

- The test harness refuses to measure a binary that was not built from the source
under test.

Expand Down
32 changes: 25 additions & 7 deletions test/pytest/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Reference for anyone reading, running, or adding to `test/pytest/`. The design a
the decisions behind the harness are in `design/ISSUE_432_PYTEST_HARNESS.md`. This
file covers the tests themselves.

**120 tests in 8 files.** One hundred and five of them test the harness rather than the
**121 tests in 9 files.** One hundred and six of them test the harness rather than the
product, and they come first, because a harness that can report a false green makes
every other result in this directory worthless.

Expand Down Expand Up @@ -34,9 +34,10 @@ behaviour, the source of that number is named.
- [8. test_native_projection.py: the ported suite](#8-test_native_projectionpy-the-ported-suite)
- [9. test_ordered.py: the ordered oracle](#9-test_orderedpy-the-ordered-oracle)
- [10. test_runshape.py: the shape of the run itself](#10-test_runshapepy-the-shape-of-the-run-itself)
- [11. Adding a test](#11-adding-a-test)
- [12. What this corpus does NOT yet refuse](#12-what-this-corpus-does-not-yet-refuse)
- [13. Traps this corpus records](#13-traps-this-corpus-records)
- [11. test_zonemap_boundaries.py: exact boundaries](#11-test_zonemap_boundariespy-exact-boundaries)
- [12. Adding a test](#12-adding-a-test)
- [13. What this corpus does NOT yet refuse](#13-what-this-corpus-does-not-yet-refuse)
- [14. Traps this corpus records](#14-traps-this-corpus-records)

## 1. How to read a test in here

Expand Down Expand Up @@ -861,7 +862,24 @@ The empty-parametrize refusal carries its own message rather than folding into t
bare-skip refusal. When a corpus glob matches nothing, the cause the reader needs to
see is the corpus, not the marker.

## 11. Adding a test
## 11. test_zonemap_boundaries.py: exact boundaries

### `test_exact_zonemap_boundaries`

Pairs with `test/zonemap_boundaries.sh`. Two monotonic 1,000-row groups put
`1001` exactly at the second group's minimum and `1000` exactly at the first
group's maximum. Heap-row comparisons pin that `<= 1001` and `>= 1000` keep
their boundary rows. Work-done counters, with bloom disabled, pin the
correctness-preserving cases: `< 1001`, `> 1000`, and `= 1001` each remove one
group.

The five one-token strategy mutations make the corresponding assertion fail:
`<=` and `>=` lose one row, while `<`, `>`, and `=` remain row-correct but
remove no group. This distinguishes correctness coverage from
pruning-effectiveness coverage rather than relying on incidental fixtures
elsewhere in the matrix.

## 12. Adding a test

0. **Write it twice.** Every test in this tree ships as a `.sh` suite and a pytest
test **in the same change** (jd, 2026-09-09). Not ported later, not one or the
Expand All @@ -888,7 +906,7 @@ see is the corpus, not the marker.
failed the selftest on both majors of the matrix, which is how it was found. A
new directory under `test/` inherits every rule the old ones follow.

## 12. What this corpus does NOT yet refuse
## 13. What this corpus does NOT yet refuse

`VACUITY_MODES.md` is the inventory: 79 ways a pytest harness can report a pass while
asserting nothing, 73 of them demonstrated by an actual run. **This layer refuses 25
Expand All @@ -900,7 +918,7 @@ Read it before adding a test. The gaps most likely to affect a new test are that
same family satisfies it, and that a write is not required to have written anything.
Both are named there with the refusal each needs.

## 13. Traps this corpus records
## 14. Traps this corpus records

Recorded because each one produced a confident wrong result before it was caught,
and all are the same family as the defect the layer exists to prevent.
Expand Down
82 changes: 82 additions & 0 deletions test/pytest/test_zonemap_boundaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Exact zone-map comparison boundaries and pruning effectiveness (#831)."""


def _nodes(plan):
for root in plan:
stack = [root["Plan"]]
while stack:
node = stack.pop()
yield node
stack.extend(node.get("Plans", ()))


def _plan(conn, qual):
with conn.cursor() as cur:
cur.execute("SET pgcolumnar.enable_bloom_filter=off")
cur.execute("SET pgcolumnar.enable_vectorization=off")
cur.execute(
"EXPLAIN (ANALYZE, FORMAT JSON, COSTS OFF, TIMING OFF, SUMMARY OFF) "
f"SELECT id FROM zb_c WHERE {qual}"
)
return cur.fetchone()[0]


def _removed(plan):
return next(
node.get("Columnar Chunk Groups Removed by Filter", 0)
for node in _nodes(plan)
if "Columnar Chunk Groups Total" in node
)


def _ids(conn, table, qual):
with conn.cursor() as cur:
cur.execute(f"SELECT id FROM {table} WHERE {qual} ORDER BY id")
return [row[0] for row in cur]


def test_exact_zonemap_boundaries(pgc_conn, expect):
with pgc_conn.cursor() as cur:
cur.execute("CREATE TABLE zb_h(id int, v int)")
cur.execute("CREATE TABLE zb_c(id int, v int) USING pgcolumnar")
cur.execute(
"SELECT pgcolumnar.set_options('zb_c', stripe_row_limit => 1000)"
)
cur.execute("INSERT INTO zb_h SELECT g,g FROM generate_series(1,2000) g")
cur.execute("INSERT INTO zb_c SELECT * FROM zb_h")
cur.execute(
"SELECT count(*) FROM pgcolumnar.row_group "
"WHERE storage_id=pgcolumnar.get_storage_id('zb_c')"
)
expect.num(cur.fetchone()[0], 2, "premise: fixture has two row groups")

expect.num(
_removed(_plan(pgc_conn, "v < 1001")), 1,
"< excludes the group whose minimum equals the constant",
)
expect.rows(
_ids(pgc_conn, "zb_c", "v <= 1001"),
_ids(pgc_conn, "zb_h", "v <= 1001"),
"<= keeps the row at a row-group minimum",
)
expect.num(
_removed(_plan(pgc_conn, "v <= 1001")), 0,
"premise: <= at the second-group minimum removes no group",
)
expect.rows(
_ids(pgc_conn, "zb_c", "v >= 1000"),
_ids(pgc_conn, "zb_h", "v >= 1000"),
">= keeps the row at a row-group maximum",
)
expect.num(
_removed(_plan(pgc_conn, "v >= 1000")), 0,
"premise: >= at the first-group maximum removes no group",
)
expect.num(
_removed(_plan(pgc_conn, "v > 1000")), 1,
"> excludes the group whose maximum equals the constant",
)
expect.num(
_removed(_plan(pgc_conn, "v = 1001")), 1,
"= excludes the group lying wholly below the constant",
)
1 change: 1 addition & 0 deletions test/run_all_versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ SUITES=(
wal_envelope
write_fsst_compressed
write_minmax_fastpath
zonemap_boundaries
zonemap_cost
zonemap_estimate_sample)

Expand Down
54 changes: 54 additions & 0 deletions test/zonemap_boundaries.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
#
# Exact zone-map comparison boundaries are intentional coverage (#831).
#
# Usage: test/zonemap_boundaries.sh [PG_CONFIG]

set -uo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
pgc_setup "${1:-/usr/local/pg17/bin/pg_config}"

psql_run "CREATE TABLE zb_h(id int, v int);
CREATE TABLE zb_c(id int, v int) USING pgcolumnar;
SELECT pgcolumnar.set_options('zb_c', stripe_row_limit => 1000);
INSERT INTO zb_h SELECT g,g FROM generate_series(1,2000) g;
INSERT INTO zb_c SELECT * FROM zb_h;
ANALYZE zb_h; ANALYZE zb_c;" >/dev/null

check "premise: the boundary fixture has two row groups" \
"$(q "SELECT count(*) FROM pgcolumnar.row_group
WHERE storage_id=pgcolumnar.get_storage_id('zb_c');")" "2"

groups_removed() {
env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" \
-U postgres -d "$PGC_DB" -Atq \
-c "SET pgcolumnar.enable_bloom_filter=off;
SET pgcolumnar.enable_vectorization=off;
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT id FROM zb_c WHERE $1;" 2>&1 |
sed -n 's/.*Columnar Chunk Groups Removed by Filter: \([0-9]*\).*/\1/p' |
head -1
}

check "< excludes the group whose minimum equals the constant" \
"$(groups_removed 'v < 1001')" "1"
check_text "<= keeps the row at a row-group minimum" \
"$(pgc_set_hash "SELECT id FROM zb_c WHERE v <= 1001")" \
"$(pgc_set_hash "SELECT id FROM zb_h WHERE v <= 1001")"
check "premise: <= at the second-group minimum removes no group" \
"$(groups_removed 'v <= 1001')" "0"

check_text ">= keeps the row at a row-group maximum" \
"$(pgc_set_hash "SELECT id FROM zb_c WHERE v >= 1000")" \
"$(pgc_set_hash "SELECT id FROM zb_h WHERE v >= 1000")"
check "premise: >= at the first-group maximum removes no group" \
"$(groups_removed 'v >= 1000')" "0"

# These mutations remain row-correct, so only work-done counters can see them.
check "> excludes the group whose maximum equals the constant" \
"$(groups_removed 'v > 1000')" "1"
check "= excludes the group lying wholly below the constant" \
"$(groups_removed 'v = 1001')" "1"

check "backend alive" "$(q 'SELECT 1')" "1"
pgc_summary
Loading