From 7f132a357e0417bdb03d3e24ef944ed6415a0c08 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 02:12:14 +0000 Subject: [PATCH 1/5] test: half a retention declaration is no retention, in both twins (#1341) Co-Authored-By: Claude Opus 5.5 --- test/pytest/TESTS.md | 1 + test/pytest/expected_tests.txt | 4 +++- test/pytest/test_ttl_expire.py | 29 +++++++++++++++++++++++++++++ test/ttl_expire.sh | 20 ++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/test/pytest/TESTS.md b/test/pytest/TESTS.md index 7816fec76..642129d7a 100644 --- a/test/pytest/TESTS.md +++ b/test/pytest/TESTS.md @@ -6235,6 +6235,7 @@ restored byte-identical. | --- | --- | | `test_expire_retires_expired_groups_and_keeps_the_straddling_one` | the feature, and the straddling group that constrains it | | `test_expire_refuses_what_it_cannot_honour` | #1341: a non-owner holding DELETE (`42501`), a retention column that is not a time type (`42804`) and a dropped one (`42703`) are refused with every row still there, on tables whose rows are all past retention; the owner's `expire` is the control that retires them | +| `test_a_half_declaration_is_no_retention` | #1341: a retention column without an interval, or an interval without a column, is accepted by `set_options` and refused by `expire` with `55000`, every aged row kept | | `test_a_null_retention_value_keeps_its_group` | a live NULL the maximum cannot speak for | | `test_an_index_only_scan_does_not_return_retired_rows` | the visibility-map bits over a group `expire` removed | | `test_a_non_positive_retention_is_refused` | a cutoff in the future would retire live groups: `22023`, with a positive control | diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index 0376bc038..4dcf90a27 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -649,4 +649,6 @@ guard_tests 433 # test/fdw_plan_privilege.sh, by collection over the cluster files on this branch. # cluster_tests 543 -> 549 for #1341's test_arrow_import.py, the twin of arrow_import.sh: # six tests, by collection over the cluster files on this tree. -cluster_tests 549 +# cluster_tests 549 -> 550 for #1341's test_a_half_declaration_is_no_retention in +# test_ttl_expire.py, by collection over the cluster files on this tree. +cluster_tests 550 diff --git a/test/pytest/test_ttl_expire.py b/test/pytest/test_ttl_expire.py index 7c7d252af..259e98537 100644 --- a/test/pytest/test_ttl_expire.py +++ b/test/pytest/test_ttl_expire.py @@ -464,6 +464,35 @@ def groups(t): "expire refuses a retention column that no longer exists (42703)") +def test_a_half_declaration_is_no_retention(pgc_conn, expect): + """A column with no interval names nothing to compare against, and an interval + with no column has nothing to compare: either alone reads as "no retention", so + a half declaration cannot drop rows (#1341). Every row is ten days old, so a + reader that filled in the missing half with a default would retire them all.""" + with pgc_conn.cursor() as cur: + for t in ("ttl_hcol", "ttl_hiv"): + cur.execute(f"CREATE TABLE {t} (id int, ts timestamptz) USING pgcolumnar") + cur.execute(f"SELECT pgcolumnar.set_options('{t}', stripe_row_limit => 1000)") + cur.execute(f"INSERT INTO {t} SELECT g, now() - interval '10 days' FROM generate_series(1, 3000) g") + + def declared(t): + return _one(pgc_conn, "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-')" + " FROM pgcolumnar.options WHERE regclass = %s::regclass", (t,)) + + col_state = _sqlstate(pgc_conn, "SELECT pgcolumnar.set_options('ttl_hcol', ttl_column => 'ts')") + expect.text(f"{col_state}|{declared('ttl_hcol')}", "|ts|-", + "premise: set_options takes a retention column without an interval") + iv_state = _sqlstate(pgc_conn, "SELECT pgcolumnar.set_options('ttl_hiv', ttl_interval => '1 day')") + expect.text(f"{iv_state}|{declared('ttl_hiv')}", "|-|1 day", + "premise: and an interval without a column") + hcol = _sqlstate(pgc_conn, "SELECT pgcolumnar.expire('ttl_hcol')") + expect.text(f"{hcol}|{_one(pgc_conn, 'SELECT count(*) FROM ttl_hcol')}", "55000|3000", + "a retention column without an interval is no retention: refused (55000), every row kept") + hiv = _sqlstate(pgc_conn, "SELECT pgcolumnar.expire('ttl_hiv')") + expect.text(f"{hiv}|{_one(pgc_conn, 'SELECT count(*) FROM ttl_hiv')}", "55000|3000", + "and an interval without a column is no retention either") + + def test_a_deleted_null_stops_pinning_its_group(pgc_conn, expect): """`null_count` is recorded at WRITE time and never revised. diff --git a/test/ttl_expire.sh b/test/ttl_expire.sh index 65a6db7e1..45075bba2 100755 --- a/test/ttl_expire.sh +++ b/test/ttl_expire.sh @@ -311,6 +311,26 @@ check "premise: the declaration still names the dropped column" \ check "expire refuses a retention column that no longer exists (42703)" \ "$(ttl_state "SELECT pgcolumnar.expire('ttl_gone');")|$(q 'SELECT count(*) FROM ttl_gone')" "42703|3000" +# ---- half a declaration is no retention (#1341) ----------------------------- +# +# A column with no interval names nothing to compare against, and an interval +# with no column has nothing to compare: either alone reads as "no retention", so +# a half declaration cannot drop rows. Every row here is ten days old, so a reader +# that filled in the missing half with a default would retire them all. +for _t in ttl_hcol ttl_hiv; do + psql_run "CREATE TABLE $_t (id int, ts timestamptz) USING pgcolumnar; + SELECT pgcolumnar.set_options('$_t', stripe_row_limit => 1000); + INSERT INTO $_t SELECT g, now() - interval '10 days' FROM generate_series(1, 3000) g;" +done +check "premise: set_options takes a retention column without an interval" \ + "$(ttl_state "SELECT pgcolumnar.set_options('ttl_hcol', ttl_column => 'ts');")|$(q "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-') FROM pgcolumnar.options WHERE regclass = 'ttl_hcol'::regclass;")" "|ts|-" +check "premise: and an interval without a column" \ + "$(ttl_state "SELECT pgcolumnar.set_options('ttl_hiv', ttl_interval => '1 day');")|$(q "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-') FROM pgcolumnar.options WHERE regclass = 'ttl_hiv'::regclass;")" "|-|1 day" +check "a retention column without an interval is no retention: refused (55000), every row kept" \ + "$(ttl_state "SELECT pgcolumnar.expire('ttl_hcol');")|$(q 'SELECT count(*) FROM ttl_hcol')" "55000|3000" +check "and an interval without a column is no retention either" \ + "$(ttl_state "SELECT pgcolumnar.expire('ttl_hiv');")|$(q 'SELECT count(*) FROM ttl_hiv')" "55000|3000" + # ---- a NULL that has been DELETED must stop pinning its group --------------- # # z->nullCount is recorded at WRITE time and never revised, so it still counts From 8b7a93672a6122b2392c31d1ca27487ac8fa3090 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 02:15:27 +0000 Subject: [PATCH 2/5] test: ledger rows for the half-declaration arms (#1341) Co-Authored-By: Claude Opus 5.5 --- test/check_ledger.tsv | 4 ++++ test/check_ledger_budget.txt | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 9ebbadaac..e23b28de5 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2453,12 +2453,14 @@ ttl_expire ttl_expire a group whose only NULLs have been deleted is retired 15;1 ttl_expire ttl_expire a later expire on a column of only NULLs reads no zone map 15;16;17;18;19 2026-10-13 expire's early exit removed (the tree before #1310);the early exit not taken for a column with no value ttl_expire ttl_expire a negative ttl_interval is refused with 22023 15;16;17;18;19 never - ttl_expire ttl_expire a no-op expire reads no zone map when the extent's minimum is past the cutoff 15;16;17;18;19 2026-10-13 expire's early exit removed (the tree before #1310) +ttl_expire ttl_expire a retention column without an interval is no retention: refused (55000), every row kept 15;16;17;18;19 2026-10-14 a retention column with no interval read as retention with a zero interval ttl_expire ttl_expire a role that does not own the table cannot expire it, even holding DELETE (42501) 15;16;17;18;19 2026-10-14 expire without its owner check ttl_expire ttl_expire a rolled-back expire records nothing 15;16;17;18;19 never - ttl_expire ttl_expire a row dated exactly on the cutoff is kept, so truncation errs toward keeping 15;16;17;18;19 never - ttl_expire ttl_expire a table with no declared retention is an error, not a silent success 15;16;17;18;19 2026-10-14 the no-retention refusal raised as XX000 rather than 55000 ttl_expire ttl_expire and VACUUM really did set visibility-map bits to clear 15;16;17;18;19 never - ttl_expire ttl_expire and a zero ttl_interval is refused too 15;16;17;18;19 never - +ttl_expire ttl_expire and an interval without a column is no retention either 15;16;17;18;19 never - ttl_expire ttl_expire and its rows are gone 15;16;17;18;19 never - ttl_expire ttl_expire and no row older than UTC's own cutoff survives 15;16;17;18;19 2026-10-13 the early exit compares the extent's maximum to the cutoff instead of its minimum ttl_expire ttl_expire and no row older than a zone on another date's own cutoff survives 15;16;17;18;19 2026-10-14 expire's date cutoff taken from the UTC wall clock, not the session's date;the early exit compares the extent's maximum to the cutoff instead of its minimum @@ -2493,6 +2495,7 @@ ttl_expire ttl_expire expire retires the aged groups and keeps those of NULLs 15 ttl_expire ttl_expire expire retires the all-visible expired group 15;16;17;18;19 2026-10-14 expire returning 0 whatever it retired ttl_expire ttl_expire index-only scan does not return rows expire already retired 15;16;17;18;19 never - ttl_expire ttl_expire premise: an index-only scan is chosen on the all-visible table 15;16;17;18;19 never - +ttl_expire ttl_expire premise: and an interval without a column 15;16;17;18;19 never - ttl_expire ttl_expire premise: and every live row is past the retention 15;16;17;18;19 never - ttl_expire ttl_expire premise: and rows dated exactly ON the cutoff, which decide the rounding 15;16;17;18;19 never - ttl_expire ttl_expire premise: and the SAME row group holds expired rows and NULL ones 15;16;17;18;19 never - @@ -2503,6 +2506,7 @@ ttl_expire ttl_expire premise: no LIVE row holds a NULL retention value any more ttl_expire ttl_expire premise: one date per row group in UTC, so no group straddles the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: one date per row group in a zone on another date, so no group straddles the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: rows exist on both sides of the cutoff within one day of it 15;16;17;18;19 never - +ttl_expire ttl_expire premise: set_options takes a retention column without an interval 15;16;17;18;19 never - ttl_expire ttl_expire premise: some rows are past the retention and some are not 15;16;17;18;19 never - ttl_expire ttl_expire premise: the date fixture has rows on both sides of the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: the date fixture is laid out in several row groups 15;16;17;18;19 never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index 79f5e5e6a..26cca778f 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -2037,4 +2037,16 @@ suites_not_covered 241 # never 1963 - 1 = 1962 # dated 586 + 1 = 587 # 1962 + 587 == 2549 -checks_never_observed_red 1962 +# +# #1341, half a retention declaration is no retention (OffgridwithJD): 1962 -> 1965. +# Four new ttl_expire checks. The column-only arm is dated by a reader that fills a +# missing interval with zero: expire then retired all 3000 aged rows instead of +# refusing. Three arrive never: two premises on what set_options accepts, and the +# interval-only arm, which no non-crashing mutation reaches (a column filled in +# would name a column that does not exist). Five-major runs: 74/74 on 15-19. +# +# ledger rows 2549 + 4 = 2553 +# never 1962 + 3 = 1965 +# dated 587 + 1 = 588 +# 1965 + 588 == 2553 +checks_never_observed_red 1965 From c02fba7acc46eeee12a64cc25ff8c89c8a91c26a Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 19:38:46 +0000 Subject: [PATCH 3/5] test: the two half-declaration set_options checks lose the premise prefix, in both twins (#1370 review) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho --- test/check_ledger.tsv | 4 ++-- test/pytest/test_ttl_expire.py | 4 ++-- test/ttl_expire.sh | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index e23b28de5..bbb2cc3d2 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2460,6 +2460,7 @@ ttl_expire ttl_expire a row dated exactly on the cutoff is kept, so truncation e ttl_expire ttl_expire a table with no declared retention is an error, not a silent success 15;16;17;18;19 2026-10-14 the no-retention refusal raised as XX000 rather than 55000 ttl_expire ttl_expire and VACUUM really did set visibility-map bits to clear 15;16;17;18;19 never - ttl_expire ttl_expire and a zero ttl_interval is refused too 15;16;17;18;19 never - +ttl_expire ttl_expire and an interval without a column 15;16;17;18;19 never - ttl_expire ttl_expire and an interval without a column is no retention either 15;16;17;18;19 never - ttl_expire ttl_expire and its rows are gone 15;16;17;18;19 never - ttl_expire ttl_expire and no row older than UTC's own cutoff survives 15;16;17;18;19 2026-10-13 the early exit compares the extent's maximum to the cutoff instead of its minimum @@ -2495,7 +2496,6 @@ ttl_expire ttl_expire expire retires the aged groups and keeps those of NULLs 15 ttl_expire ttl_expire expire retires the all-visible expired group 15;16;17;18;19 2026-10-14 expire returning 0 whatever it retired ttl_expire ttl_expire index-only scan does not return rows expire already retired 15;16;17;18;19 never - ttl_expire ttl_expire premise: an index-only scan is chosen on the all-visible table 15;16;17;18;19 never - -ttl_expire ttl_expire premise: and an interval without a column 15;16;17;18;19 never - ttl_expire ttl_expire premise: and every live row is past the retention 15;16;17;18;19 never - ttl_expire ttl_expire premise: and rows dated exactly ON the cutoff, which decide the rounding 15;16;17;18;19 never - ttl_expire ttl_expire premise: and the SAME row group holds expired rows and NULL ones 15;16;17;18;19 never - @@ -2506,7 +2506,6 @@ ttl_expire ttl_expire premise: no LIVE row holds a NULL retention value any more ttl_expire ttl_expire premise: one date per row group in UTC, so no group straddles the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: one date per row group in a zone on another date, so no group straddles the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: rows exist on both sides of the cutoff within one day of it 15;16;17;18;19 never - -ttl_expire ttl_expire premise: set_options takes a retention column without an interval 15;16;17;18;19 never - ttl_expire ttl_expire premise: some rows are past the retention and some are not 15;16;17;18;19 never - ttl_expire ttl_expire premise: the date fixture has rows on both sides of the cutoff 15;16;17;18;19 never - ttl_expire ttl_expire premise: the date fixture is laid out in several row groups 15;16;17;18;19 never - @@ -2521,6 +2520,7 @@ ttl_expire ttl_expire premise: the timestamp fixture is six groups of 1,000 rows ttl_expire ttl_expire running it again retires nothing, because nothing new expired 15;16;17;18;19 never - ttl_expire ttl_expire seqscan agrees the expired rows are gone 15;16;17;18;19 never - ttl_expire ttl_expire set_options accepts an int retention column, so the refusal is expire's 15;16;17;18;19 never - +ttl_expire ttl_expire set_options takes a retention column without an interval 15;16;17;18;19 never - ttl_expire ttl_expire the index-only scan is still the plan after expire 15;16;17;18;19 never - ttl_expire ttl_expire the retention is recorded where the other options live 15;16;17;18;19 never - ttl_expire ttl_expire with the extent, expire retires the same groups 15;16;17;18;19 2026-10-13 the early exit compares the extent's maximum to the cutoff instead of its minimum diff --git a/test/pytest/test_ttl_expire.py b/test/pytest/test_ttl_expire.py index 259e98537..0cb6823e9 100644 --- a/test/pytest/test_ttl_expire.py +++ b/test/pytest/test_ttl_expire.py @@ -481,10 +481,10 @@ def declared(t): col_state = _sqlstate(pgc_conn, "SELECT pgcolumnar.set_options('ttl_hcol', ttl_column => 'ts')") expect.text(f"{col_state}|{declared('ttl_hcol')}", "|ts|-", - "premise: set_options takes a retention column without an interval") + "set_options takes a retention column without an interval") iv_state = _sqlstate(pgc_conn, "SELECT pgcolumnar.set_options('ttl_hiv', ttl_interval => '1 day')") expect.text(f"{iv_state}|{declared('ttl_hiv')}", "|-|1 day", - "premise: and an interval without a column") + "and an interval without a column") hcol = _sqlstate(pgc_conn, "SELECT pgcolumnar.expire('ttl_hcol')") expect.text(f"{hcol}|{_one(pgc_conn, 'SELECT count(*) FROM ttl_hcol')}", "55000|3000", "a retention column without an interval is no retention: refused (55000), every row kept") diff --git a/test/ttl_expire.sh b/test/ttl_expire.sh index 45075bba2..b9c4f6c14 100755 --- a/test/ttl_expire.sh +++ b/test/ttl_expire.sh @@ -322,9 +322,12 @@ for _t in ttl_hcol ttl_hiv; do SELECT pgcolumnar.set_options('$_t', stripe_row_limit => 1000); INSERT INTO $_t SELECT g, now() - interval '10 days' FROM generate_series(1, 3000) g;" done -check "premise: set_options takes a retention column without an interval" \ +# Checks, not premises (#1236; #1370 review): set_options refusing a half declaration +# is a change to this repository's SQL, and it turns both red while the expire arms +# below stay green for an unrelated reason, so they are what notices it. +check "set_options takes a retention column without an interval" \ "$(ttl_state "SELECT pgcolumnar.set_options('ttl_hcol', ttl_column => 'ts');")|$(q "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-') FROM pgcolumnar.options WHERE regclass = 'ttl_hcol'::regclass;")" "|ts|-" -check "premise: and an interval without a column" \ +check "and an interval without a column" \ "$(ttl_state "SELECT pgcolumnar.set_options('ttl_hiv', ttl_interval => '1 day');")|$(q "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-') FROM pgcolumnar.options WHERE regclass = 'ttl_hiv'::regclass;")" "|-|1 day" check "a retention column without an interval is no retention: refused (55000), every row kept" \ "$(ttl_state "SELECT pgcolumnar.expire('ttl_hcol');")|$(q 'SELECT count(*) FROM ttl_hcol')" "55000|3000" From 46990090112819dd1145b680d3abba8794369d5a Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 19:40:57 +0000 Subject: [PATCH 4/5] test: the interval-only expire arm runs last, where a crash takes nothing else down (#1370 review) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho --- test/ttl_expire.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/ttl_expire.sh b/test/ttl_expire.sh index b9c4f6c14..438a8e9f9 100755 --- a/test/ttl_expire.sh +++ b/test/ttl_expire.sh @@ -331,8 +331,6 @@ check "and an interval without a column" \ "$(ttl_state "SELECT pgcolumnar.set_options('ttl_hiv', ttl_interval => '1 day');")|$(q "SELECT coalesce(ttl_column::text, '-') || '|' || coalesce(ttl_interval::text, '-') FROM pgcolumnar.options WHERE regclass = 'ttl_hiv'::regclass;")" "|-|1 day" check "a retention column without an interval is no retention: refused (55000), every row kept" \ "$(ttl_state "SELECT pgcolumnar.expire('ttl_hcol');")|$(q 'SELECT count(*) FROM ttl_hcol')" "55000|3000" -check "and an interval without a column is no retention either" \ - "$(ttl_state "SELECT pgcolumnar.expire('ttl_hiv');")|$(q 'SELECT count(*) FROM ttl_hiv')" "55000|3000" # ---- a NULL that has been DELETED must stop pinning its group --------------- # @@ -690,4 +688,10 @@ check_num "expire retires the aged groups and keeps those of NULLs" \ "$(q "SELECT pgcolumnar.expire('ttl_n');")" "2" check "a later expire on a column of only NULLs reads no zone map" "$(_expire_scans ttl_n)" "n=0 scans=0" +# The interval-only half (#1341), LAST IN THE SUITE ON PURPOSE: with the reader's +# column test removed, expire on ttl_hiv crashed the backend (PG17), and a crash +# mid-suite takes the checks after it down with it, premises included. +check "and an interval without a column is no retention either" \ + "$(ttl_state "SELECT pgcolumnar.expire('ttl_hiv');")|$(q 'SELECT count(*) FROM ttl_hiv')" "55000|3000" + pgc_summary From 941082a7fb1d08420a57be0789ae17db3e834464 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Wed, 14 Oct 2026 19:43:23 +0000 Subject: [PATCH 5/5] test: the half-declaration checks are dated, by set_options refusing them and by the reader's column test removed (#1370 review) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VjTxfQJj5pnii9wzmPP9Ho --- test/check_ledger.tsv | 6 +++--- test/check_ledger_budget.txt | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index bbb2cc3d2..369311873 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2460,8 +2460,8 @@ ttl_expire ttl_expire a row dated exactly on the cutoff is kept, so truncation e ttl_expire ttl_expire a table with no declared retention is an error, not a silent success 15;16;17;18;19 2026-10-14 the no-retention refusal raised as XX000 rather than 55000 ttl_expire ttl_expire and VACUUM really did set visibility-map bits to clear 15;16;17;18;19 never - ttl_expire ttl_expire and a zero ttl_interval is refused too 15;16;17;18;19 never - -ttl_expire ttl_expire and an interval without a column 15;16;17;18;19 never - -ttl_expire ttl_expire and an interval without a column is no retention either 15;16;17;18;19 never - +ttl_expire ttl_expire and an interval without a column 15;16;17;18;19 2026-10-14 set_options refuses a half retention declaration +ttl_expire ttl_expire and an interval without a column is no retention either 15;16;17;18;19 2026-10-14 the retention reader drops its column test, so an interval alone is read (the backend then crashed) ttl_expire ttl_expire and its rows are gone 15;16;17;18;19 never - ttl_expire ttl_expire and no row older than UTC's own cutoff survives 15;16;17;18;19 2026-10-13 the early exit compares the extent's maximum to the cutoff instead of its minimum ttl_expire ttl_expire and no row older than a zone on another date's own cutoff survives 15;16;17;18;19 2026-10-14 expire's date cutoff taken from the UTC wall clock, not the session's date;the early exit compares the extent's maximum to the cutoff instead of its minimum @@ -2520,7 +2520,7 @@ ttl_expire ttl_expire premise: the timestamp fixture is six groups of 1,000 rows ttl_expire ttl_expire running it again retires nothing, because nothing new expired 15;16;17;18;19 never - ttl_expire ttl_expire seqscan agrees the expired rows are gone 15;16;17;18;19 never - ttl_expire ttl_expire set_options accepts an int retention column, so the refusal is expire's 15;16;17;18;19 never - -ttl_expire ttl_expire set_options takes a retention column without an interval 15;16;17;18;19 never - +ttl_expire ttl_expire set_options takes a retention column without an interval 15;16;17;18;19 2026-10-14 set_options refuses a half retention declaration ttl_expire ttl_expire the index-only scan is still the plan after expire 15;16;17;18;19 never - ttl_expire ttl_expire the retention is recorded where the other options live 15;16;17;18;19 never - ttl_expire ttl_expire with the extent, expire retires the same groups 15;16;17;18;19 2026-10-13 the early exit compares the extent's maximum to the cutoff instead of its minimum diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index 26cca778f..9fcfd70e7 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -2049,4 +2049,16 @@ suites_not_covered 241 # never 1962 + 3 = 1965 # dated 587 + 1 = 588 # 1965 + 588 == 2553 -checks_never_observed_red 1965 +# +# #1370 review (OffgridwithJD): 1965 -> 1962. The two set_options checks lose the +# premise prefix and are dated, with the interval-only expire arm: +# - set_options refusing a half declaration reddens both set_options checks, while +# the expire arms stay green for an unrelated reason; +# - the retention reader without its column test crashed the backend on the +# interval-only table, so that arm now runs last in the suite. +# +# ledger rows 2553 = 2553 +# never 1965 - 3 = 1962 +# dated 588 + 3 = 591 +# 1962 + 591 == 2553 +checks_never_observed_red 1962