From e6f6d4c005a048be6587b09aa277e95a89d954c3 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 15:57:37 +0000 Subject: [PATCH 1/2] test: row_tid refuses a row number below 1 (22003) and accepts row 1, in both twins (#1341) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho --- test/pytest/TESTS.md | 3 ++- test/pytest/expected_tests.txt | 4 +++- test/pytest/test_rollup.py | 15 +++++++++++++++ test/rollup.sh | 7 +++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/test/pytest/TESTS.md b/test/pytest/TESTS.md index 450ae1ad4..4c132e6b2 100644 --- a/test/pytest/TESTS.md +++ b/test/pytest/TESTS.md @@ -6696,9 +6696,10 @@ Phase 1 removal proofs, each red on its arm: no `OFFSET 0` (the cost arm), no is | test | what it holds | | --- | --- | -| `test_rollup_phase_one` | the rollup equals a full recompute after the first refresh, appends with late rows, a bulk append, and a rebase; a refresh with nothing new folds nothing; rows committed during a refresh are folded by the next, once; two concurrent refreshes fold each row once; zones agree; a refresh after a small append reads under a fifth of the source; a rewritten source is refused until `rebase_rollup`; after a delete, an update, a straddling append, `compact_rewrite` or a deleted NULL-ts row the rollup equals a full recompute, rewriting no partial outside the changed groups' range; a recompute that would reach retained history is refused with `0A000` and changes nothing; a day bucket is recomputed whole across a DST switch; a heap rollup behaves the same; REPEATABLE READ is refused; an owner who is not a superuser creates and refreshes a rollup, and another role sees none of it; bad definitions are refused with `22023` | +| `test_rollup_phase_one` | the rollup equals a full recompute after the first refresh, appends with late rows, a bulk append, and a rebase; a refresh with nothing new folds nothing; rows committed during a refresh are folded by the next, once; two concurrent refreshes fold each row once; zones agree; a refresh after a small append reads under a fifth of the source; a rewritten source is refused until `rebase_rollup`; after a delete, an update, a straddling append, `compact_rewrite` or a deleted NULL-ts row the rollup equals a full recompute, rewriting no partial outside the changed groups' range; a recompute that would reach retained history is refused with `0A000` and changes nothing; a day bucket is recomputed whole across a DST switch; a heap rollup behaves the same; REPEATABLE READ is refused; an owner who is not a superuser creates and refreshes a rollup, and another role sees none of it; bad definitions are refused with `22023`; each group's TID range holds exactly its rows | | `test_a_rollup_and_its_source_are_dropped_together` | the view depends on its source, as a view over it would (#1376): `DROP TABLE` of the source is refused (`2BP01`), `CASCADE` takes the view, its partials table and its catalog rows, and `DROP VIEW` takes the partials and the rows. A role that cannot read a table cannot tie a rollup row to it (`42501`), so it cannot block that table's drop. Nor can it name another role's table as its partials (`42501`, and that table survives its view's drop), or a non-view as its rollup (`42809`); and a written row's view, partials and source cannot be changed (`0A000`) | | `test_rollup_dump_and_restore` | a dump carries a rollup's definition; after a restore into another database, and into the same one after DROP EXTENSION, the restored source has the recorded storage id, and the owner's refresh is refused with `55000` naming `rebase_rollup`, the rollup untouched; after the rebase a refresh folds nothing, then exactly the rows appended since, and the rollup equals a full recompute (#1377) | +| `test_row_tid_refuses_a_row_number_below_one` | `row_tid(0)` and `row_tid(-1)` are refused with `22003` and `row_tid(1)` is accepted. A test of its own, last in the file: without the floor `row_tid(0)` crashed the backend | ## 98. test_timeseries_recipes.py: the gap-fill, LOCF and interpolation recipes answer what they claim diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index 4dcd03a42..214ac9c92 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -685,4 +685,6 @@ guard_tests 435 # over the cluster files on this tree. # cluster_tests 575 -> 576 for #1341's test_recovery_minimal_wal.py, one test, by collection # over the cluster files on this tree. -cluster_tests 576 +# cluster_tests 576 -> 577 for #1341's test_row_tid_refuses_a_row_number_below_one in +# test_rollup.py, by collection over the cluster files on this tree. +cluster_tests 577 diff --git a/test/pytest/test_rollup.py b/test/pytest/test_rollup.py index e4fa9f407..d97783416 100644 --- a/test/pytest/test_rollup.py +++ b/test/pytest/test_rollup.py @@ -779,3 +779,18 @@ def restore(db): finally: for db in (src_db, dst_db): admin("postgres", f"DROP DATABASE IF EXISTS {db} WITH (FORCE)") + + +def test_row_tid_refuses_a_row_number_below_one(pgc_conn, expect): + """Row numbers start at 1 (#1341). LAST IN THE FILE, AND A TEST OF ITS OWN: without + the floor, row_tid(0) crashed the backend (measured on PG17), which would take the + checks after it in the same test down with it.""" + def state(n): + try: + _one(pgc_conn, f"SELECT pgcolumnar.row_tid({n})") + return "ok" + except psycopg.Error as e: + return e.sqlstate or "?" + + expect.text(f"{state(0)}|{state(-1)}|{state(1)}", "22003|22003|ok", + "row_tid refuses a row number below 1 (22003), and row 1 is accepted") diff --git a/test/rollup.sh b/test/rollup.sh index 6cd22b6fe..da9db2510 100755 --- a/test/rollup.sh +++ b/test/rollup.sh @@ -622,4 +622,11 @@ psql_run "CREATE TABLE lr_p5 (bucket timestamptz) USING pgcolumnar; CREATE VIEW check "a rollup row's view, partials and source cannot be changed once written (0A000, #1376)" \ "$(sqlstate "UPDATE pgcolumnar.rollup SET partials = 'lr_p6' WHERE rollup = 'lr_v5'::regclass")" "0A000" +# Row numbers start at 1 (#1341). LAST IN THE SUITE ON PURPOSE: without the floor, +# row_tid(0) crashed the backend (measured on PG17), and a crash mid-suite takes the +# checks after it down with it, premises included. +check "row_tid refuses a row number below 1 (22003), and row 1 is accepted" \ + "$(sqlstate "SELECT pgcolumnar.row_tid(0)")|$(sqlstate "SELECT pgcolumnar.row_tid(-1)")|$(sqlstate "SELECT pgcolumnar.row_tid(1)")" \ + "22003|22003|ok" + pgc_summary From 95d5c700ccf8f8db6fd7199b8217bf02749d3796 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 16:07:50 +0000 Subject: [PATCH 2/2] test: row_tid's floor arm enters the ledger, dated by the floor removed (#1341) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho --- test/check_ledger.tsv | 1 + test/check_ledger_budget.txt | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 62c24398a..39590eb2a 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2633,6 +2633,7 @@ rollup rollup premise: the source holds every row 15;16;17;18;19 never - rollup rollup rebase_rollup of a table that is not a rollup is refused (42704) 15;16;17;18;19 2026-10-14 rebase_rollup without its not-a-rollup check rollup rollup rebase_rollup records the source's current row groups 15;16;17;18;19 2026-10-13 rebase_rollup returning 0 rollup rollup refreshes from sessions in different time zones build the same rollup 15;16;17;18;19 2026-10-13 refresh_rollup bucketing with date_trunc, which follows the session's zone +rollup rollup row_tid refuses a row number below 1 (22003), and row 1 is accepted 15;16;17;18;19 2026-10-14 row_tid without its floor at row 1 (row_tid(0) then crashed the backend) rollup rollup the database holding a rollup dumps 15;16;17;18;19 never - rollup rollup the dump restores into another database 15;16;17;18;19 never - rollup rollup the dump restores into its own database after DROP EXTENSION, again under the recorded storage id 15;16;17;18;19 never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index bbaa4b490..ad7dd017f 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -2389,4 +2389,15 @@ suites_not_covered 231 # never 2281 + 1 = 2282 # dated 717 + 8 = 725 # 2282 + 725 == 3007 +# +# #1341, row_tid's floor (OffgridwithJD): 2282 -> 2282. One new rollup arm, dated by the +# floor removed: row_tid(0) then crashed the backend (PG17), so the arm runs last in +# the suite and as a pytest test of its own, where a crash takes nothing else down. +# Five-major runs: rollup 83/83 on 15-19, and 105/105 on PG17 and PG18 after the +# rebase onto f2bd5f1, which brought in 22 checks. +# +# ledger rows 3007 + 1 = 3008 +# never 2282 = 2282 +# dated 725 + 1 = 726 +# 2282 + 726 == 3008 checks_never_observed_red 2282