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
4 changes: 2 additions & 2 deletions test/check_ledger.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2256,7 +2256,7 @@ ttl_expire ttl_expire a negative ttl_interval is refused with 22023 15;16;17;18;
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 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 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 its rows are gone 15;16;17;18;19 never -
Expand All @@ -2269,6 +2269,7 @@ ttl_expire ttl_expire and the table really is smaller than it was 15;16;17;18;19
ttl_expire ttl_expire and they are the groups gone from the row-group catalog 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 those NULL rows are still there 15;16;17;18;19 never -
ttl_expire ttl_expire but the zone map still records the deleted NULLs 15;16;17;18;19 never -
ttl_expire ttl_expire compact_rewrite retired a group 15;16;17;18;19 2026-10-14 compact_rewrite returning 0 before rewriting anything
ttl_expire ttl_expire control: a group whose NULLs are still live is NOT retired 15;16;17;18;19 never -
ttl_expire ttl_expire control: and a positive one is still accepted 15;16;17;18;19 never -
ttl_expire ttl_expire control: and the rows are all still there 15;16;17;18;19 never -
Expand All @@ -2292,7 +2293,6 @@ ttl_expire ttl_expire premise: an index-only scan is chosen on the all-visible t
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 -
ttl_expire ttl_expire premise: compact_rewrite retired a group 15;16;17;18;19 never -
ttl_expire ttl_expire premise: in UTC the fixture straddles that session's cutoff 15;16;17;18;19 never -
ttl_expire ttl_expire premise: in a zone on another date the fixture straddles that session's cutoff 15;16;17;18;19 never -
ttl_expire ttl_expire premise: no LIVE row holds a NULL retention value any more 15;16;17;18;19 never -
Expand Down
16 changes: 15 additions & 1 deletion test/check_ledger_budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1818,4 +1818,18 @@ suites_not_covered 246
# never 1801 + 2 - 2 - 3 = 1798
# dated 539 + 1 + 2 + 3 = 545
# 1798 + 545 == 2343
checks_never_observed_red 1798
#
# #1339 audit, ttl_expire's twins (OffgridwithJD): 1798 -> 1796. Two arms gain their
# first red date. The no-retention arm now requires SQLSTATE 55000, as its pytest twin
# does (it accepted any ERROR); it is red with the refusal raised as XX000. And
# "compact_rewrite retired a group" tests product code, so it loses the `premise:`
# prefix (#1236), its row renamed with its history, and is red with compact_rewrite
# returning 0. The pytest date arm now reads expire's own return, as the shell twin
# does; that is a pytest change and moves no row. Five-major runs on the tree merged
# with #1353: 70/70 on 15-19.
#
# ledger rows 2343 = 2343
# never 1798 - 2 = 1796
# dated 545 + 2 = 547
# 1796 + 547 == 2343
checks_never_observed_red 1796
17 changes: 11 additions & 6 deletions test/pytest/test_ttl_expire.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,22 +502,27 @@ def test_a_date_retention_column_truncates_toward_keeping(pgc_conn, expect):
on_cutoff, 1,
"premise: and rows dated exactly ON the cutoff, which decide the rounding")

state = _sqlstate(pgc_conn, "SELECT pgcolumnar.expire('ttl_dt')")
# THE CALL'S OWN ANSWER, AS THE SHELL TWIN READS IT (#1339). This used to discard
# it and call expire a second time, so "retires at least one group" was decided
# by the row count alone, and an expire that retired groups but reported 0 passed.
try:
first = _one(pgc_conn, "SELECT pgcolumnar.expire('ttl_dt')")
state = ""
except psycopg.Error as exc:
first, state = None, exc.sqlstate or "?"
expect.text(
"accepted" if state == "" else f"refused ({state})",
"accepted",
"a date retention column is accepted, not refused (#1135)")

# Re-read after the call above, which ran it once.
retired = _one(pgc_conn, "SELECT pgcolumnar.expire('ttl_dt')")
rows_after = _one(pgc_conn, "SELECT count(*) FROM ttl_dt")
live_after = _one(pgc_conn, f"SELECT count(*) FROM ttl_dt WHERE d >= {cutoff}")
print(f"-- date: {rows_before} rows in {groups_before} groups, "
f"{d_expired} past retention, {on_cutoff} on the cutoff")
print(f"-- date: rows left {rows_after}, second call retired {retired}")
print(f"-- date: expire returned {first}, rows left {rows_after}")

expect.text(
"retired" if rows_after < rows_before else f"RETIRED NOTHING ({rows_after})",
"retired" if (first or 0) > 0 else f"RETIRED NOTHING ({first})",
"retired",
"expire on a date column retires at least one group")
expect.num(
Expand Down Expand Up @@ -697,7 +702,7 @@ def rec(sid):
cur.execute("DELETE FROM ttl_rr WHERE v % 10 < 6")
cur.execute("SELECT pgcolumnar.compact_rewrite('ttl_rr', 0.1)")
expect.text("retired" if rgs(sid) != "4,5" else "nothing retired [4,5]", "retired",
"premise: compact_rewrite retired a group")
"compact_rewrite retired a group")
expect.text(rec(sid), "1,2,3", "a compaction's retired groups are not recorded as retention")

pgc_conn.execute("DROP TABLE ttl_rr")
Expand Down
24 changes: 14 additions & 10 deletions test/ttl_expire.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,23 @@ check "every remaining row reads back its own value" \
AGAIN="$(q "SELECT pgcolumnar.expire('ttl_t')")"
check "running it again retires nothing, because nothing new expired" "$AGAIN" "0"

# SQLSTATE, not message text: the refusals below each have their own state, while a
# missing function would be 42883 and a non-owner 42501. Empty when the call succeeds.
ttl_state() {
env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres \
-d "$PGC_DB" -v ON_ERROR_STOP=1 -Atq -v VERBOSITY=sqlstate -c "$1" 2>&1 \
| sed -n 's/^ERROR: \([0-9A-Z]\{5\}\).*/\1/p' | head -1
}

# ---- a table with no declared retention ------------------------------------
psql_run "CREATE TABLE ttl_none (id int, ts timestamptz) USING pgcolumnar;"
psql_run "INSERT INTO ttl_none SELECT g, now() FROM generate_series(1,10) g;"
ERR="$(q "SELECT pgcolumnar.expire('ttl_none')")"
# BY SQLSTATE, AS THE PYTEST TWIN ASSERTS (#1339). The message grep accepted any
# ERROR at all -- a missing function, a non-owner, an internal error -- so a refusal
# raised as the wrong kind of failure passed. 55000 is the prerequisite-state check.
_none_state="$(ttl_state "SELECT pgcolumnar.expire('ttl_none')")"
check "a table with no declared retention is an error, not a silent success" \
"$(grep -qiE 'ERROR|no retention|ttl' <<<"$ERR" && echo "refused" || echo "ACCEPTED ($ERR)")" "refused"
"${_none_state:-ACCEPTED}" "55000"

# ---- NULL in the retention column is a straddle, not an expiry ------------
psql_run "CREATE TABLE ttl_null (id int, ts timestamptz, v text) USING pgcolumnar;"
Expand Down Expand Up @@ -235,13 +246,6 @@ check "index-only scan does not return rows expire already retired" "$IOS_AFTER"
# A negative ttl_interval puts the cutoff in the FUTURE, so expire finds
# `maximum < cutoff` true for groups entirely inside their retention and retires
# them: live rows dropped, which is the failure this suite is named for.
# SQLSTATE, not message text -- 22023 comes from the range check, while a
# missing function would be 42883 and a non-owner 42501.
ttl_state() {
env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres \
-d "$PGC_DB" -v ON_ERROR_STOP=1 -Atq -v VERBOSITY=sqlstate -c "$1" 2>&1 \
| sed -n 's/^ERROR: \([0-9A-Z]\{5\}\).*/\1/p' | head -1
}
psql_run "CREATE TABLE ttl_neg (id int, ts timestamptz) USING pgcolumnar;"
psql_run "INSERT INTO ttl_neg SELECT g, now() FROM generate_series(1,100) g;"
check "a negative ttl_interval is refused with 22023" \
Expand Down Expand Up @@ -550,7 +554,7 @@ check "and they are the groups gone from the row-group catalog" "$(_rgs)" "4"
psql_run "INSERT INTO ttl_rec SELECT now() + g * interval '1 second', g FROM generate_series(1001, 3000) g;
DELETE FROM ttl_rec WHERE v % 10 < 6 AND ts > now();
SELECT pgcolumnar.compact_rewrite('ttl_rec', 0.1);"
check "premise: compact_rewrite retired a group" \
check "compact_rewrite retired a group" \
"$([ "$(_rgs)" != "4,5,6" ] && echo retired || echo "nothing retired [$(_rgs)]")" "retired"
check "a compaction's retired groups are not recorded as retention" "$(_rec)" "1,2,3"

Expand Down
Loading