From 6fea3e405c5bcbc16c8d2335b41267d232abc776 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Tue, 13 Oct 2026 21:35:59 +0000 Subject: [PATCH 1/2] test: refusals and the month/year path in the partition lifecycle and tiering, in both twins (#1341) Nine behaviours no arm exercised, each now an arm in both twins: partition_create_ahead, before any DDL: - a zero or negative width, or a negative lead (22023); - a parent not RANGE-partitioned on one column: LIST, two columns, not partitioned at all (55000); - a key that is not a timestamp: date, an integer type (55000); - a month or year width starts at this month's first and steps by the calendar, for a timestamptz and a timestamp key. partition_drop_plan refuses a NULL cutoff, in both overloads (22023). tier_partition refuses a server that does not exist before anything moves. pgcolumnar.tier_fault_after refuses a value that is not a step name. The width arms want the message as well as the code: without the width check, a zero width never moves the counting loop on and the 1000-partition cap answers with the same 22023. The relation in a message is named the way the function names it, schema-qualified under its own search_path. Red first on PG17, each mutation removing the branch its arm tests, in both twins. Each reddens only its own arm: - no width check: `22008 stride must be greater than zero` and `ok: 0`; - no RANGE check: 42P16 from ATTACH, 42P17 for an unpartitioned table; - no key-type check: a date key silently gets partitions (`ok: 2`); - no month branch: 0A000 from date_bin; - no NULL-cutoff check: `0|0`, an empty plan; - no missing-server branch: 42704 from core, but the file is written; - an open check hook: the bogus SET succeeds. Clean: partition_lifecycle 59/59 on each of 15-19; tier_partition 73/73 on 18-19, 72 + 1 named SKIP on 15-17; both pytest files green on each. Co-Authored-By: Claude Opus 5.5 --- test/partition_lifecycle.sh | 54 +++++++++++++++++++ test/pytest/test_partition_lifecycle.py | 71 +++++++++++++++++++++++++ test/pytest/test_tier_partition.py | 13 +++++ test/tier_partition.sh | 14 +++++ 4 files changed, 152 insertions(+) diff --git a/test/partition_lifecycle.sh b/test/partition_lifecycle.sh index 98dd1e007..9eeac5af6 100755 --- a/test/partition_lifecycle.sh +++ b/test/partition_lifecycle.sh @@ -42,6 +42,15 @@ sqlstate_or() { # sqlstate_or SQL -> the single value it returns, or the SQLSTAT case "$out" in ERROR:*) sed -n 's/^ERROR: *\([0-9A-Z]\{5\}\).*/\1/p' <<<"$out" | head -1;; *) echo "$out";; esac } kids() { q "SELECT count(*) FROM pg_inherits WHERE inhparent = '$1'::regclass;"; } +# rc REL -> the relation as the function's messages name it: they format regclass +# under its own search_path (pg_catalog, pg_temp), so always schema-qualified. +rc() { q "SELECT format('%I.%I', n.nspname, c.relname) FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = '$1'::regclass;"; } +refusal() { # refusal SQL -> "SQLSTATE message" of the error it raises, or "ok: VALUE" + local out + out=$(env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres -d "$PGC_DB" \ + -X -At -v ON_ERROR_STOP=1 -v VERBOSITY=verbose -c "$1" 2>&1) + case "$out" in ERROR:*) sed -n 's/^ERROR: *\([0-9A-Z]\{5\}\): \(.*\)$/\1 \2/p' <<<"$out" | head -1;; *) echo "ok: $out";; esac +} ams() { q "SELECT coalesce(string_agg(DISTINCT a.amname, ','), 'none') FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid JOIN pg_am a ON a.oid = c.relam WHERE i.inhparent = '$1'::regclass;"; } @@ -177,6 +186,48 @@ q "CREATE TABLE tail (ts timestamptz NOT NULL, v int) PARTITION BY RANGE (ts); check "a parent with a partition bounded by MAXVALUE is refused, and nothing is created" \ "$(sqlstate_or "SELECT pgcolumnar.partition_create_ahead('tail', '1 day', 2);")|$(kids tail)" "55000|1" +# ---- refused before any DDL, each by its own check (#1341) ------------------------ +# The MESSAGE is in the want, not just the code: with the width check gone, a zero +# or negative width never moves the counting loop on, and the 1000-partition cap +# answers with the same 22023. +q "CREATE TABLE wa (ts timestamptz NOT NULL, v int) PARTITION BY RANGE (ts);" >/dev/null +check "a width that is not positive, or a negative lead, is refused before any DDL" \ + "$(refusal "SELECT pgcolumnar.partition_create_ahead('wa', '0 seconds', 1);")|$(refusal "SELECT pgcolumnar.partition_create_ahead('wa', '-1 day', 1);")|$(refusal "SELECT pgcolumnar.partition_create_ahead('wa', '1 day', -1);")|$(kids wa)" \ + "22023 width must be positive and ahead not negative|22023 width must be positive and ahead not negative|22023 width must be positive and ahead not negative|0" +q "CREATE TABLE lp (ts timestamptz NOT NULL, v int) PARTITION BY LIST (ts); + CREATE TABLE mc (ts timestamptz NOT NULL, v int) PARTITION BY RANGE (ts, v); + CREATE TABLE np (ts timestamptz NOT NULL, v int) USING pgcolumnar;" >/dev/null +check "a parent that is not RANGE-partitioned on one column is refused before any DDL" \ + "$(refusal "SELECT pgcolumnar.partition_create_ahead('lp', '1 day', 1);")|$(refusal "SELECT pgcolumnar.partition_create_ahead('mc', '1 day', 1);")|$(refusal "SELECT pgcolumnar.partition_create_ahead('np', '1 day', 1);")|$(kids lp)|$(kids mc)" \ + "55000 $(rc lp) is not RANGE-partitioned on one column|55000 $(rc mc) is not RANGE-partitioned on one column|55000 $(rc np) is not RANGE-partitioned on one column|0|0" +q "CREATE TABLE kd (d date NOT NULL, v int) PARTITION BY RANGE (d); + CREATE TABLE ki (k bigint NOT NULL, v int) PARTITION BY RANGE (k);" >/dev/null +check "a parent partitioned on a key that is not a timestamp is refused before any DDL" \ + "$(refusal "SELECT pgcolumnar.partition_create_ahead('kd', '1 day', 1);")|$(refusal "SELECT pgcolumnar.partition_create_ahead('ki', '1 day', 1);")|$(kids kd)|$(kids ki)" \ + "55000 $(rc kd) is partitioned on date, not on a timestamp|55000 $(rc ki) is partitioned on bigint, not on a timestamp|0|0" + +# ---- month and year widths (#1341) ------------------------------------------------ +# date_bin cannot bin by months or years, so a width holding either starts at the +# first of the current month, and each bound is the last one plus the width: the +# calendar's month, not a fixed 30 days. +for key in timestamptz timestamp; do + q "CREATE TABLE mo_$key (ts $key NOT NULL, v int) PARTITION BY RANGE (ts);" >/dev/null + nowexpr="now()"; [ "$key" = timestamp ] && nowexpr="localtimestamp" + check "a $key parent with a month width starts at this month's first and steps by calendar months" \ + "$(sqlstate_or "SELECT pgcolumnar.partition_create_ahead('mo_$key', '1 month', 2);")|$(q "SELECT count(*) FILTER (WHERE f = date_trunc('month', f) AND t = f + interval '1 month') || '|' || (min(f) = date_trunc('month', $nowexpr)) + FROM (SELECT (regexp_match(pg_get_expr(c.relpartbound, c.oid), 'FROM \(''([^'']+)''\) TO \(''([^'']+)''\)'))[1]::$key AS f, + (regexp_match(pg_get_expr(c.relpartbound, c.oid), 'FROM \(''([^'']+)''\) TO \(''([^'']+)''\)'))[2]::$key AS t + FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'mo_$key'::regclass) b;")" \ + "3|3|true" +done +q "CREATE TABLE yr (ts timestamptz NOT NULL, v int) PARTITION BY RANGE (ts);" >/dev/null +check "a year width starts at this month's first and steps by calendar years" \ + "$(sqlstate_or "SELECT pgcolumnar.partition_create_ahead('yr', '1 year', 1);")|$(q "SELECT count(*) FILTER (WHERE f = date_trunc('month', f) AND t = f + interval '1 year') || '|' || (min(f) = date_trunc('month', now())) + FROM (SELECT (regexp_match(pg_get_expr(c.relpartbound, c.oid), 'FROM \(''([^'']+)''\) TO \(''([^'']+)''\)'))[1]::timestamptz AS f, + (regexp_match(pg_get_expr(c.relpartbound, c.oid), 'FROM \(''([^'']+)''\) TO \(''([^'']+)''\)'))[2]::timestamptz AS t + FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'yr'::regclass) b;")" \ + "2|2|true" + # ---- refused: a DEFAULT partition ------------------------------------------------- q "CREATE TABLE td (ts timestamptz NOT NULL, v int) PARTITION BY RANGE (ts); CREATE TABLE td_default PARTITION OF td DEFAULT USING pgcolumnar;" >/dev/null @@ -265,6 +316,9 @@ check "a timestamp key's plan for a timestamp cutoff is the same from sessions w check "a cutoff of the other timestamp type is refused" \ "$(sqlstate_or "SELECT count(*) FROM pgcolumnar.partition_drop_plan('dw', '2021-01-02 00:00+00'::timestamptz)")|$(sqlstate_or "SELECT count(*) FROM pgcolumnar.partition_drop_plan('dl', '2020-03-01 00:00'::timestamp)")" \ "42804|42804" +check "a NULL cutoff is refused, in both overloads" \ + "$(sqlstate_or "SELECT count(*) FROM pgcolumnar.partition_drop_plan('dl', NULL::timestamptz)")|$(sqlstate_or "SELECT count(*) FROM pgcolumnar.partition_drop_plan('dw', NULL::timestamp)")" \ + "22023|22023" # Bounds at the ends of the range: MINVALUE below is listed once its upper bound # is past, MAXVALUE above never is. diff --git a/test/pytest/test_partition_lifecycle.py b/test/pytest/test_partition_lifecycle.py index 21a0c51bb..3ad1e84cb 100644 --- a/test/pytest/test_partition_lifecycle.py +++ b/test/pytest/test_partition_lifecycle.py @@ -223,6 +223,74 @@ def value_or_state(sql): expect.text(f"{capped}|{_kids(conn, 'capped')}", "55000|1", "a parent with a partition bounded by MAXVALUE is refused, and nothing is created") + # Refused before any DDL, each by its own check (#1341). The message is in the + # want: with the width check gone, a zero or negative width never moves the + # counting loop on, and the 1000-partition cap answers with the same 22023. + def refusal(sql): + try: + return f"ok: {_one(conn, sql)}" + except psycopg.Error as e: + return f"{e.sqlstate} {e.diag.message_primary}" + + def name(t): + # As the function's messages name it: regclass formatted under its own + # search_path (pg_catalog, pg_temp), so always schema-qualified. + return _one(conn, "SELECT format('%I.%I', n.nspname, c.relname) FROM pg_class c" + f" JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = '{t}'::regclass") + + with conn.cursor() as cur: + cur.execute("CREATE TABLE wid (at timestamptz NOT NULL, reading float8) PARTITION BY RANGE (at)") + bad = "22023 width must be positive and ahead not negative" + expect.text("|".join([refusal("SELECT pgcolumnar.partition_create_ahead('wid', '0 seconds', 1)"), + refusal("SELECT pgcolumnar.partition_create_ahead('wid', '-6 hours', 1)"), + refusal("SELECT pgcolumnar.partition_create_ahead('wid', '6 hours', -1)"), + str(_kids(conn, "wid"))]), + f"{bad}|{bad}|{bad}|0", + "a width that is not positive, or a negative lead, is refused before any DDL") + with conn.cursor() as cur: + cur.execute("CREATE TABLE lst (at timestamptz NOT NULL, reading float8) PARTITION BY LIST (at)") + cur.execute("CREATE TABLE two (at timestamptz NOT NULL, reading float8) PARTITION BY RANGE (at, reading)") + cur.execute("CREATE TABLE flat (at timestamptz NOT NULL, reading float8) USING pgcolumnar") + expect.text("|".join([refusal(f"SELECT pgcolumnar.partition_create_ahead('{t}', '6 hours', 1)") + for t in ("lst", "two", "flat")] + [str(_kids(conn, "lst")), str(_kids(conn, "two"))]), + "|".join(f"55000 {name(t)} is not RANGE-partitioned on one column" + for t in ("lst", "two", "flat")) + "|0|0", + "a parent that is not RANGE-partitioned on one column is refused before any DDL") + with conn.cursor() as cur: + cur.execute("CREATE TABLE bydate (day date NOT NULL, reading float8) PARTITION BY RANGE (day)") + cur.execute("CREATE TABLE byint (n int NOT NULL, reading float8) PARTITION BY RANGE (n)") + expect.text("|".join([refusal("SELECT pgcolumnar.partition_create_ahead('bydate', '6 hours', 1)"), + refusal("SELECT pgcolumnar.partition_create_ahead('byint', '6 hours', 1)"), + str(_kids(conn, "bydate")), str(_kids(conn, "byint"))]), + f"55000 {name('bydate')} is partitioned on date, not on a timestamp" + f"|55000 {name('byint')} is partitioned on integer, not on a timestamp|0|0", + "a parent partitioned on a key that is not a timestamp is refused before any DDL") + + # Month and year widths (#1341): date_bin cannot bin by either, so the first + # partition starts on the first of this month and each bound adds the width. + def calendar(t, key, step, now_expr): + return _one(conn, f"SELECT count(*) FILTER (WHERE f = date_trunc('month', f) AND t = f + interval '{step}')" + f" || '|' || (min(f) = date_trunc('month', {now_expr}))" + f" FROM (SELECT (regexp_match(pg_get_expr(c.relpartbound, c.oid)," + f" 'FROM \\(''([^'']+)''\\) TO \\(''([^'']+)''\\)'))[1]::{key} AS f," + f" (regexp_match(pg_get_expr(c.relpartbound, c.oid)," + f" 'FROM \\(''([^'']+)''\\) TO \\(''([^'']+)''\\)'))[2]::{key} AS t" + f" FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid" + f" WHERE i.inhparent = '{t}'::regclass) b") + + for key, now_expr in (("timestamptz", "now()"), ("timestamp", "localtimestamp")): + t = f"monthly_{key}" + with conn.cursor() as cur: + cur.execute(f"CREATE TABLE {t} (at {key} NOT NULL, reading float8) PARTITION BY RANGE (at)") + made = value_or_state(f"SELECT pgcolumnar.partition_create_ahead('{t}', '1 month', 2)") + expect.text(f"{made}|{calendar(t, key, '1 month', now_expr)}", "3|3|true", + f"a {key} parent with a month width starts at this month's first and steps by calendar months") + with conn.cursor() as cur: + cur.execute("CREATE TABLE yearly (at timestamptz NOT NULL, reading float8) PARTITION BY RANGE (at)") + made = value_or_state("SELECT pgcolumnar.partition_create_ahead('yearly', '1 year', 1)") + expect.text(f"{made}|{calendar('yearly', 'timestamptz', '1 year', 'now()')}", "2|2|true", + "a year width starts at this month's first and steps by calendar years") + # Refused: a DEFAULT partition. with conn.cursor() as cur: cur.execute("CREATE TABLE fallback (at timestamptz NOT NULL, reading float8) PARTITION BY RANGE (at)") @@ -355,6 +423,9 @@ def sqlstate(sql): expect.text(sqlstate("SELECT * FROM pgcolumnar.partition_drop_plan('xw', '2019-05-01 06:00+00'::timestamptz)") + "|" + sqlstate("SELECT * FROM pgcolumnar.partition_drop_plan('xl', '2019-04-01 00:00'::timestamp)"), "42804|42804", "a cutoff of the other timestamp type is refused") + expect.text(sqlstate("SELECT * FROM pgcolumnar.partition_drop_plan('xl', NULL::timestamptz)") + "|" + + sqlstate("SELECT * FROM pgcolumnar.partition_drop_plan('xw', NULL::timestamp)"), + "22023|22023", "a NULL cutoff is refused, in both overloads") # MINVALUE below is listed once its upper bound is past; MAXVALUE above never. with conn.cursor() as cur: diff --git a/test/pytest/test_tier_partition.py b/test/pytest/test_tier_partition.py index d2987b91b..339742395 100644 --- a/test/pytest/test_tier_partition.py +++ b/test/pytest/test_tier_partition.py @@ -292,6 +292,19 @@ def refused(t, path): expect.text(f"{_sqlstate(conn, _tier('uq', p))}|{refused('uq', p)}", f"0A000|{ub}|r|nofile", "a parent with a UNIQUE index is refused before anything moves") + # A server that does not exist (#1341): refused by its own branch, before the + # export. Core's own error would come later, with the file already written. + _build(conn, "ns") + nb = _digest(conn, "ns") + p = server_dir / "ns.parquet" + ns_state = _sqlstate(conn, f"SELECT pgcolumnar.tier_partition('ns_mar', 'tq_no_such', '{p}')") + expect.text(f"{ns_state}|{refused('ns', p)}", f"42704|{nb}|r|nofile", + "a server that does not exist is refused before anything moves") + + # The fault hook's own guard (#1341). + expect.text(_sqlstate(conn, "SET pgcolumnar.tier_fault_after = 'exprot'"), "22023", + "a fault step that is not one of tier_partition's is refused") + _build(conn, "dep") db = _digest(conn, "dep") # Each dependent made and removed one statement at a time, with a premise that diff --git a/test/tier_partition.sh b/test/tier_partition.sh index 50c6353b0..976bb4a45 100755 --- a/test/tier_partition.sh +++ b/test/tier_partition.sh @@ -211,6 +211,20 @@ check "a parent with a UNIQUE index is refused before anything moves" \ "$(sqlstate "SELECT pgcolumnar.tier_partition('u_jan', 'tier_pq', '$W/u.parquet');")|$(refused_cleanly u "$W/u.parquet")" \ "0A000|$ub|r|nofile" +# A server that does not exist (#1341). Without its own branch the NULL wrapper name +# compares as NULL, nothing is refused, and core's own "server does not exist" +# arrives only at CREATE FOREIGN TABLE, after the export has written the file. +build ns +nsb="$(digest ns)" +check "a server that does not exist is refused before anything moves" \ + "$(sqlstate "SELECT pgcolumnar.tier_partition('ns_jan', 'tier_no_such', '$W/ns.parquet');")|$(refused_cleanly ns "$W/ns.parquet")" \ + "42704|$nsb|r|nofile" + +# The fault hook's own guard (#1341): a step that is not one of tier_partition's +# must be refused, so a typo cannot leave a test believing it injected a fault. +check "a fault step that is not one of tier_partition's is refused" \ + "$(sqlstate "SET pgcolumnar.tier_fault_after = 'exprot';")" "22023" + build d db="$(digest d)" # Each dependent made and removed in statements of its own, with a premise that it From 7d7d634a2d498e5577df382f6a6743bb5cac62ae Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Tue, 13 Oct 2026 21:55:55 +0000 Subject: [PATCH 2/2] test: ledger rows for the nine partition and tiering arms (#1341) Five-major clean runs of both suites, and each mutation merged with --target naming its arm. Nine rows, all dated. Restated from main d0ae0b1: rows 2316 -> 2325, and checks_never_observed_red stays 1796. Co-Authored-By: Claude Opus 5.5 --- test/check_ledger.tsv | 9 +++++++++ test/check_ledger_budget.txt | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 98d28860b..68d5b1587 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -1866,12 +1866,15 @@ parallel_scan_cost parallel_scan_cost premise: the serial plan has no Gather 15; parallel_scan_cost parallel_scan_cost premise: the serial plan is a columnar scan 15;16;17;18;19 never - parallel_scan_cost parallel_scan_cost premise: the table holds every inserted row 15;16;17;18;19 never - parallel_scan_cost parallel_scan_cost the leader-participation branch changes the divisor 15;16;17;18;19 never - +partition_lifecycle partition_lifecycle a NULL cutoff is refused, in both overloads 15;16;17;18;19 2026-10-13 partition_drop_plan without its NULL-cutoff check, in both overloads partition_lifecycle partition_lifecycle a call that would create more than 1000 partitions is refused before any DDL 15;16;17;18;19 2026-10-12 partition_create_ahead without the 1000-partition bound partition_lifecycle partition_lifecycle a cutoff before every partition plans nothing 15;16;17;18;19 2026-10-13 the cutoff filter removed: every partition planned partition_lifecycle partition_lifecycle a cutoff of the other timestamp type is refused 15;16;17;18;19 2026-10-13 a cutoff of the other timestamp type planned instead of refused partition_lifecycle partition_lifecycle a cutoff on a partition's upper bound includes it 15;16;17;18;19 2026-10-13 a partition whose upper bound equals the cutoff left out;a plain DROP, not refusing while the partition is still attached;the cutoff filter removed: every partition planned;the lower bound read where the upper is meant;the plan detaches but drops nothing partition_lifecycle partition_lifecycle a drop plan for a parent not partitioned on a timestamp is refused 15;16;17;18;19 2026-10-13 a parent partitioned on a non-timestamp key planned instead of refused partition_lifecycle partition_lifecycle a drop plan for a parent with a DEFAULT partition is refused 15;16;17;18;19 2026-10-13 a parent with a DEFAULT partition planned instead of refused +partition_lifecycle partition_lifecycle a parent partitioned on a key that is not a timestamp is refused before any DDL 15;16;17;18;19 2026-10-13 partition_create_ahead without its timestamp key check +partition_lifecycle partition_lifecycle a parent that is not RANGE-partitioned on one column is refused before any DDL 15;16;17;18;19 2026-10-13 partition_create_ahead without its RANGE-on-one-column check partition_lifecycle partition_lifecycle a parent with a 63-character name gets partitions whose names keep their dates 15;16;17;18;19 2026-10-12 partition_create_ahead stopping one width short of now plus the lead;partition_create_ahead without shortening a long parent name partition_lifecycle partition_lifecycle a parent with a DEFAULT partition is refused 15;16;17;18;19 2026-10-12 partition_create_ahead without the DEFAULT-partition refusal partition_lifecycle partition_lifecycle a parent with a generated column gets its partitions, and the column stays generated 15;16;17;18;19 2026-10-12 partition_create_ahead stopping one width short of now plus the lead;partition_create_ahead without INCLUDING GENERATED @@ -1886,8 +1889,12 @@ partition_lifecycle partition_lifecycle a second plan for the same cutoff is emp partition_lifecycle partition_lifecycle a tiered partition is planned with DROP FOREIGN TABLE 15;16;17;18;19 2026-10-13 a partition whose upper bound equals the cutoff left out;a plain DROP, not refusing while the partition is still attached;a tiered partition planned with DROP TABLE;the cutoff filter removed: every partition planned;the lower bound read where the upper is meant;the plan detaches but drops nothing;the plan detaches without CONCURRENTLY partition_lifecycle partition_lifecycle a timestamp key's plan for a timestamp cutoff is the same from sessions west and east of UTC 15;16;17;18;19 2026-10-13 a plain DROP, not refusing while the partition is still attached;the lower bound read where the upper is meant;the plan detaches but drops nothing;the plan detaches without CONCURRENTLY;the timestamp overload compares the bound read as UTC with the cutoff converted in the session's zone partition_lifecycle partition_lifecycle a timestamp parent made east of UTC covers the local now plus the lead 15;16;17;18;19 2026-10-12 partition_create_ahead stopping one width short of now plus the lead +partition_lifecycle partition_lifecycle a timestamp parent with a month width starts at this month's first and steps by calendar months 15;16;17;18;19 2026-10-13 partition_create_ahead without its month and year branch, so date_bin is handed the width partition_lifecycle partition_lifecycle a timestamp parent's partitions are contiguous across a non-UTC session 15;16;17;18;19 2026-10-12 partition_create_ahead leaving a minute between partitions;partition_create_ahead reading a timestamp key's bounds in the session's zone and writing them from UTC (the prototype);partition_create_ahead starting from now, not the highest upper bound +partition_lifecycle partition_lifecycle a timestamptz parent with a month width starts at this month's first and steps by calendar months 15;16;17;18;19 2026-10-13 partition_create_ahead without its month and year branch, so date_bin is handed the width partition_lifecycle partition_lifecycle a timestamptz parent's partitions are contiguous across a non-UTC session 15;16;17;18;19 2026-10-12 partition_create_ahead leaving a minute between partitions;partition_create_ahead starting from now, not the highest upper bound +partition_lifecycle partition_lifecycle a width that is not positive, or a negative lead, is refused before any DDL 15;16;17;18;19 2026-10-13 partition_create_ahead without its width and ahead check +partition_lifecycle partition_lifecycle a year width starts at this month's first and steps by calendar years 15;16;17;18;19 2026-10-13 partition_create_ahead without its month and year branch, so date_bin is handed the width partition_lifecycle partition_lifecycle after a DETACH that fails, the plan's DROP refuses and the partition stays attached 15;16;17;18;19 2026-10-13 a partition whose upper bound equals the cutoff left out;a plain DROP, not refusing while the partition is still attached;the plan detaches but drops nothing partition_lifecycle partition_lifecycle and a row at every boundary has exactly one partition to go to (timestamp) 15;16;17;18;19 2026-10-12 partition_create_ahead leaving a minute between partitions;partition_create_ahead reading a timestamp key's bounds in the session's zone and writing them from UTC (the prototype);partition_create_ahead starting from now, not the highest upper bound partition_lifecycle partition_lifecycle and a row at every boundary has exactly one partition to go to (timestamptz) 15;16;17;18;19 2026-10-12 partition_create_ahead leaving a minute between partitions;partition_create_ahead starting from now, not the highest upper bound @@ -2159,6 +2166,7 @@ tier_partition tier_partition a failure after create leaves the rows exactly onc tier_partition tier_partition a failure after detach leaves the rows exactly once, still local 15;16;17;18;19 never - tier_partition tier_partition a failure after drop leaves the rows exactly once, still local 15;16;17;18;19 never - tier_partition tier_partition a failure after export leaves the rows exactly once, still local 15;16;17;18;19 never - +tier_partition tier_partition a fault step that is not one of tier_partition's is refused 15;16;17;18;19 2026-10-13 the tier_fault_after check hook accepting any value tier_partition tier_partition a grant to a role whose name needs quoting is carried to the foreign partition 15;16;17;18;19 2026-10-13 tier_partition naming the foreign table with a suffix;tier_partition not carrying the grants;tier_partition returning 0 rows moved tier_partition tier_partition a parent held by a long reader times the move out, as lock_wait says 15;16;17;18;19 2026-10-13 tier_partition without the lock_wait bound tier_partition tier_partition a parent with a UNIQUE index is refused before anything moves 15;16;17;18;19 2026-10-13 tier_partition without the UNIQUE-parent refusal @@ -2174,6 +2182,7 @@ tier_partition tier_partition a query on the tiered range scans only the foreign tier_partition tier_partition a retry over the failed attempt's file moves the partition, exactly once 15;16;17;18;19 2026-10-13 tier_partition naming the foreign table with a suffix;tier_partition refusing every existing path (the first version's rule);tier_partition returning 0 rows moved tier_partition tier_partition a rollback after the whole call leaves the rows local, exactly once 15;16;17;18;19 never - tier_partition tier_partition a server of another wrapper is refused 15;16;17;18;19 2026-10-13 tier_partition without the wrapper check +tier_partition tier_partition a server that does not exist is refused before anything moves 15;16;17;18;19 2026-10-13 tier_partition without its missing-server branch, so core refuses only after the export tier_partition tier_partition a stored generated column moves and reads back the same 15;16;17;18;19 2026-10-13 tier_partition naming the foreign table with a suffix;tier_partition returning 0 rows moved;tier_partition's foreign table without generation expressions;tier_partition's foreign table without generation expressions (PG18, where the virtual arm runs) tier_partition tier_partition a table that is not a partition is refused 15;16;17;18;19 2026-10-13 tier_partition without the is-a-partition check tier_partition tier_partition a virtual generated column moves and reads back the same 15;16;17;18;19 2026-10-13 tier_partition's foreign table without generation expressions (PG18, where the virtual arm runs) diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index 8ccc64fbd..b21ca9c6f 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -1770,4 +1770,17 @@ suites_not_covered 246 # never 1802 + 1 = 1803 # dated 523 + 1 = 524 # 1803 + 524 == 2327 +# +# Missing tests in the partition lifecycle and tiering (#1341, OffgridwithJD): +# 1803 -> 1803. Nine new arms, each dated by the mutation that removes the branch +# it tests: partition_create_ahead's width/ahead, RANGE-on-one-column and +# timestamp-key checks and its month/year branch (three arms); partition_drop_plan's +# NULL-cutoff check; tier_partition's missing-server branch; the tier_fault_after +# check hook. From five-major runs: partition_lifecycle 59/59, and tier_partition +# 73/73 on 18-19 and 72 + 1 named SKIP on 15-17. +# +# ledger rows 2327 + 9 = 2336 +# never 1803 + 0 = 1803 +# dated 524 + 9 = 533 +# 1803 + 533 == 2336 checks_never_observed_red 1803