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
9 changes: 9 additions & 0 deletions test/check_ledger.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions test/check_ledger_budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 54 additions & 0 deletions test/partition_lifecycle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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;"; }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading