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
37 changes: 32 additions & 5 deletions test/pytest/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6573,9 +6573,26 @@ The rest of the shell suite was ported for #1341, every check by its name: the `

| test | what it holds |
| --- | --- |
| `test_min_max_over_timestamptz_read_no_data` | the table holds every row; `max(ts)` plans the metadata node; a decode reads at least 10 blocks; `max` and `min` each read under a tenth of that; parity with heap before and after deleting rows at both ends |
| `test_a_where_with_no_column_in_it_is_applied` | a WHERE that references no column, such as `(SELECT false)`, `now() < ...` or `current_user = ...`, gives heap's answer, with a premise that the same aggregate with no WHERE is the vectorized node (#1435) |
| `test_the_metadata_aggregates_answer_as_heap_and_win_their_plans` | count, sum, avg, min and max over int4 and text equal heap with the metadata node asserted first; the node beats a parallel plan with no Gather; `count(*)` reads no zone maps while `count(*), sum(id)` does; filtered, `sum(bigint)` and empty-table answers are right |
| `test_the_timestamptz_table_holds_every_row` | **premise**: the table holds every row inserted |
| `test_max_ts_plans_the_metadata_agg_node` | `max(ts)` over timestamptz plans the metadata node |
| `test_decoding_ts_reads_the_tables_blocks` | **control**: a decode of `ts` reads at least 10 blocks |
| `test_min_max_ts_read_under_a_tenth_of_a_decode` | `max(ts)` and `min(ts)` each read under a tenth of the decode's blocks (parametrized) |
| `test_timestamptz_parity` | `max` and `min` equal heap's, before and after deleting rows at both ends (parametrized) |
| `test_the_deletes_removed_the_same_rows_at_both_ends` | the deletes removed rows, the same rows from each table |
| `test_count_min_max_with_no_where_are_the_vectorized_aggregate` | **premise**: with no WHERE, the aggregate is the vectorized node |
| `test_a_where_with_no_column_in_it_gives_heaps_answer` | `(SELECT false)`, `now() < ...` and `current_user = ...` each give heap's answer (#1435) (parametrized) |
| `test_heaps_answer_to_a_false_gate_is_no_rows` | **premise**: heap's answer to a false gate is no rows |
| `test_the_aggregate_uses_the_metadata_node` | `count(*)` and sum/min/max plan the metadata node (parametrized) |
| `test_the_aggregate_answers_as_heap` | count, sum, avg, min and max over int4 and text, the multi-aggregate, the filtered aggregate and the `sum(bigint)` fallback each equal heap (parametrized, 13 names) |
| `test_the_metadata_node_beats_a_parallel_plan` | the node wins against a parallel plan, for `count(*)` and the multi-aggregate (#133) (parametrized) |
| `test_the_metadata_plan_has_no_gather` | neither plan has a Gather (parametrized) |
| `test_count_reads_no_zone_maps` | `count(*)` reads no zone maps |
| `test_an_aggregate_that_needs_zone_maps_reads_them` | `count(*), sum(id)` reads at least one: the measured delta, so the zero above cannot pass on a counter that never moves |
| `test_a_filtered_aggregate_is_not_the_metadata_node` | a filtered aggregate falls back |
| `test_an_empty_table_counts_zero` | an empty table counts 0 |
| `test_an_empty_table_aggregates_to_null` | an empty table's `sum` and `min` are NULL (parametrized) |

ONE CHECK PER TEST (#1339). Three module fixtures take every reading in order, and each test asserts one, so a red cannot stop the next check from running. Measured with #133 restored (the metadata path priced at the scan's cost) on PG17: the shell reddens 4 arms, the parallel-plan and no-Gather pairs. The earlier twin, three tests holding 40 checks, reported 1. This one reports the same 4.

## 86. test_format_downgrade.py: an alpha4 binary refuses what this build wrote

Expand Down Expand Up @@ -6606,15 +6623,25 @@ A ZERO IS NOT A MEASUREMENT. On the shell twin, the red run before the section e

THE FIXTURE NEEDS MANY ROWS PER DEVICE. The `LATERAL` form costs about a block per device whatever the table's size, so at 5,000 rows per device it read 45 blocks of a 177-page table and "a tenth" failed. Here it is 36,000 rows per device.

On a private database, because the document names its tables, `readings` and `devices`.
On a private database, built once by a module fixture, because the document names its tables, `readings` and `devices`.

Removal proof: the document's `LATERAL` edited to `ORDER BY ts` (ascending) returns each device's first reading, and `the LATERAL form returns what DISTINCT ON returns` fails. On the shell twin that is the only red, on every major.

### Every test

| test | what it holds |
| --- | --- |
| `test_the_latest_reading_recipe_does_what_the_document_says` | the document's three blocks are found; the table holds its rows; without the btree the `LATERAL` form reads the table about once per device; the `GROUP BY` form and, with the document's index, the `LATERAL` form return the rows `DISTINCT ON` returns; the `GROUP BY` form reads at least one block and no more than the table, as the document says (#1339); the indexed `LATERAL` reads at least one block and under a tenth of the table |
| `test_the_documents_three_recipe_blocks_were_found` | **premise**: the document's three recipe blocks are found |
| `test_the_table_holds_every_row` | **premise**: the table holds every row |
| `test_without_the_btree_the_lateral_form_reads_the_table_once_per_device` | the trap the document warns about: without the btree, the `LATERAL` form reads the table about once per device |
| `test_the_group_by_form_returns_what_distinct_on_returns` | the `GROUP BY` form returns the rows `DISTINCT ON` returns |
| `test_the_group_by_form_reads_the_table_once` | the `GROUP BY` form reads at least one block and no more than the table, as the document says (#1339) |
| `test_the_documents_index_exists` | **premise**: the document's index exists |
| `test_the_lateral_form_returns_what_distinct_on_returns` | with the index, the `LATERAL` form returns the rows `DISTINCT ON` returns |
| `test_the_indexed_lateral_form_read_the_table_at_all` | **premise**: the indexed `LATERAL` form read at least one block |
| `test_with_the_btree_the_lateral_form_reads_under_a_tenth_of_the_table` | with the index, the `LATERAL` form reads under a tenth of the table |

ONE CHECK PER TEST (#1339). A module fixture runs every step in the shell's order, the trap before the document's index exists, and records each reading; each test asserts one. Measured with the document's index statement replaced by `SELECT 1` on PG17: the shell reddens "the document's index exists" and "reads under a tenth of the table". The earlier one-test twin reported only the first. This one reports both.

## 88. test_storage_row_repair.py: the alpha5 release notes' repair procedure works as written

Expand Down
4 changes: 3 additions & 1 deletion test/pytest/expected_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -833,4 +833,6 @@ guard_tests 440
# eight tests, one per check, by collection over the cluster files on this tree.
# cluster_tests 787 -> 792 for #1341's completion of test_native_skip.py, the twin of native_skip.sh:
# five tests, by collection over the cluster files on this tree.
cluster_tests 792
# cluster_tests 792 -> 837 for #1339, one check per test in test_native_agg.py (3 -> 40
# tests) and test_latest_reading.py (1 -> 9 tests), by collection over the cluster files.
cluster_tests 837
2 changes: 1 addition & 1 deletion test/pytest/test_compare_to_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def test_the_loop_reader_invents_nothing_in_this_corpus(expect):
"every name the loop reader returns appears verbatim in the file it "
"came from")
expect.text(", ".join(files),
"build_refusal, differential, native_agg, native_groupagg_batch, "
"build_refusal, differential, native_groupagg_batch, "
"native_index_projection, native_parquet_pushdown, "
"native_reclaim, native_repack, "
"native_saop_pushdown, native_vacuum_race, native_vecskip, sorted_pathkeys, "
Expand Down
150 changes: 107 additions & 43 deletions test/pytest/test_latest_reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
under a tenth of the table, and at least one block (a query that reads nothing
would otherwise pass); without it, it reads the table about once per device.

ON A PRIVATE DATABASE (`pgc_own_db`): the document names its tables, `readings`
and `devices`, so they cannot carry a per-test prefix. Independent of
ON A PRIVATE DATABASE, built once by a module fixture: the document names its
tables, `readings` and `devices`, so they cannot carry a per-test prefix. The fixture
runs every step in the shell's order and records each reading; each test asserts one,
so one red cannot stop the next from running (#1339, as #1517-#1519 do). Independent of
`test/latest_reading.sh`: its own extraction of the document (Python, not awk),
its own data, device count and row count.
"""

import pathlib
import re

import psycopg
import pytest

DOC = pathlib.Path(__file__).resolve().parent.parent.parent / "docs" / "how-to.md"
SECTION = "## Find the latest reading per device"
DEVICES = 25
Expand Down Expand Up @@ -63,55 +68,114 @@ def _blocks(conn, sql):
return int(row[0]) if row else None


def test_the_latest_reading_recipe_does_what_the_document_says(pgc_own_db, expect):
conn = pgc_own_db
blocks = _recipe_blocks()
expect.num(len(blocks), 3, "premise: the document's three recipe blocks were found")
if len(blocks) != 3:
return
index_sql, group_sql, lateral_sql = blocks

with conn.cursor() as cur:
cur.execute("CREATE TABLE readings (device int, ts timestamptz, v float8) USING pgcolumnar")
cur.execute("CREATE TABLE devices (device int PRIMARY KEY)")
cur.execute(f"INSERT INTO devices SELECT g FROM generate_series(1, {DEVICES}) g")
cur.execute(
f"INSERT INTO readings SELECT 1 + (h % {DEVICES}), '2025-12-01'::timestamptz"
f" + (h % {ROWS}) * interval '30 s', (h % 613)::float8"
f" FROM (SELECT (hashint8(g) & 9223372036854775807) AS h"
f" FROM generate_series(1::bigint, {ROWS}) g) x"
)
expect.num(_one(conn, "SELECT count(*) FROM readings"), ROWS,
"premise: the table holds every row")

oracle = _rows(conn, "SELECT DISTINCT ON (device) device, ts FROM readings"
" ORDER BY device, ts DESC")
pages = _one(conn, "SELECT pg_relation_size('readings') / 8192")

trap = _blocks(conn, lateral_sql)
print(f"-- table {pages} pages; LATERAL without the btree read {trap} blocks")
expect.at_least(trap, pages * DEVICES // 2,
def _try(fn, *args):
"""A reading, or the error that replaced it: a failed step reddens its own test
rather than erroring the fixture and every test with it."""
try:
return fn(*args)
except psycopg.Error as exc:
return f"error: {exc.diag.message_primary}"


@pytest.fixture(scope="module")
def recipe(pgc_cluster):
"""-> the readings, taken in the shell's order: the trap before the document's
index exists, the indexed form after."""
db = "pgc_latest_reading"
with psycopg.connect(pgc_cluster.dsn(), autocommit=True) as a:
a.execute(f"DROP DATABASE IF EXISTS {db} WITH (FORCE)")
a.execute(f"CREATE DATABASE {db}")
got = {"blocks": _recipe_blocks()}
try:
with psycopg.connect(pgc_cluster.dsn(dbname=db), autocommit=True) as conn:
conn.execute("CREATE EXTENSION pgcolumnar")
conn.execute("CREATE TABLE readings (device int, ts timestamptz, v float8) USING pgcolumnar")
conn.execute("CREATE TABLE devices (device int PRIMARY KEY)")
conn.execute(f"INSERT INTO devices SELECT g FROM generate_series(1, {DEVICES}) g")
_try(conn.execute,
f"INSERT INTO readings SELECT 1 + (h % {DEVICES}), '2025-12-01'::timestamptz"
f" + (h % {ROWS}) * interval '30 s', (h % 613)::float8"
f" FROM (SELECT (hashint8(g) & 9223372036854775807) AS h"
f" FROM generate_series(1::bigint, {ROWS}) g) x")
got["count"] = _try(_one, conn, "SELECT count(*) FROM readings")
got["oracle"] = str(_try(_rows, conn, "SELECT DISTINCT ON (device) device, ts FROM readings"
" ORDER BY device, ts DESC"))
got["pages"] = _try(_one, conn, "SELECT pg_relation_size('readings') / 8192")
if len(got["blocks"]) == 3:
index_sql, group_sql, lateral_sql = got["blocks"]
got["trap"] = _try(_blocks, conn, lateral_sql)
got["group rows"] = str(_try(_rows, conn, group_sql))
got["group blocks"] = _try(_blocks, conn, group_sql)
_try(conn.execute, index_sql)
got["indexes"] = _try(_one, conn, "SELECT count(*) FROM pg_indexes WHERE tablename = 'readings'")
got["lateral rows"] = str(_try(_rows, conn, lateral_sql))
got["lateral blocks"] = _try(_blocks, conn, lateral_sql)
print(f"-- table {got['pages']} pages; readings: "
+ ", ".join(f"{k}={got.get(k)}" for k in ("trap", "group blocks", "lateral blocks")))
yield got
finally:
with psycopg.connect(pgc_cluster.dsn(), autocommit=True) as a:
a.execute(f"DROP DATABASE IF EXISTS {db} WITH (FORCE)")


def _int(v, what=""):
"""A block count, or 0 with the error printed, so an error or an absent step
fails the bound it was meant to meet by name, with its reason in the output."""
if isinstance(v, int):
return v
print(f"-- {what or 'reading'}: not a count: {v}")
return 0


def _num(v, what=""):
"""A count for expect.num, or -1 with the error printed: expect.num refuses a
non-number with a VacuityError rather than a named red (#1522 review)."""
return v if isinstance(v, int) else _int(v, what) - 1


def test_the_documents_three_recipe_blocks_were_found(recipe, expect):
expect.num(len(recipe["blocks"]), 3, "premise: the document's three recipe blocks were found")


def test_the_table_holds_every_row(recipe, expect):
expect.num(_num(recipe["count"], "count"), ROWS, "premise: the table holds every row")


def test_without_the_btree_the_lateral_form_reads_the_table_once_per_device(recipe, expect):
expect.at_least(_int(recipe.get("trap")), _int(recipe["pages"], "pages") * DEVICES // 2,
"without the btree the LATERAL form reads the table once per device")

expect.text(str(_rows(conn, group_sql)), str(oracle),

def test_the_group_by_form_returns_what_distinct_on_returns(recipe, expect):
expect.text(recipe.get("group rows", "absent"), recipe["oracle"],
"the GROUP BY form returns what DISTINCT ON returns")


def test_the_group_by_form_reads_the_table_once(recipe, expect):
# The document says this form reads the table once (#1339). Rows alone cannot tell
# it from a per-device subquery, which returns the same rows and reads the table
# once per device. Reading nothing also reads under the table, so a zero is refused.
grp = _blocks(conn, group_sql)
print(f"-- table {pages} pages; the GROUP BY form read {grp} blocks")
expect.at_least(pages + 1 - grp if grp else 0, 1,
grp = _int(recipe.get("group blocks"))
expect.at_least(_int(recipe["pages"], "pages") + 1 - grp if grp else 0, 1,
"the GROUP BY form reads the table once")

with conn.cursor() as cur:
cur.execute(index_sql)
expect.num(_one(conn, "SELECT count(*) FROM pg_indexes WHERE tablename = 'readings'"), 1,
"premise: the document's index exists")
expect.text(str(_rows(conn, lateral_sql)), str(oracle),

def test_the_documents_index_exists(recipe, expect):
expect.num(_num(recipe.get("indexes", 0), "indexes"), 1, "premise: the document's index exists")


def test_the_lateral_form_returns_what_distinct_on_returns(recipe, expect):
expect.text(recipe.get("lateral rows", "absent"), recipe["oracle"],
"the LATERAL form returns what DISTINCT ON returns")
lat = _blocks(conn, lateral_sql)
print(f"-- with the btree the LATERAL form read {lat} blocks")


def test_the_indexed_lateral_form_read_the_table_at_all(recipe, expect):
# Reading nothing also reads under a tenth of the table, so a zero is refused.
expect.at_least(lat, 1, "premise: the indexed LATERAL form read the table at all")
expect.at_least(pages - 10 * lat, 1,
expect.at_least(_int(recipe.get("lateral blocks")), 1,
"premise: the indexed LATERAL form read the table at all")


def test_with_the_btree_the_lateral_form_reads_under_a_tenth_of_the_table(recipe, expect):
lat = _int(recipe.get("lateral blocks"))
expect.at_least(_int(recipe["pages"], "pages") - 10 * lat if lat else 0, 1,
"with the btree the LATERAL form reads under a tenth of the table")
Loading
Loading