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
20 changes: 14 additions & 6 deletions pgcolumnar--1.0-alpha4--1.0-alpha5.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1068,9 +1068,13 @@ BEGIN
LOOP
RETURN NEXT format('ALTER TABLE %s DETACH PARTITION %s %s', parent, p.part,
CASE WHEN p.pending THEN 'FINALIZE' ELSE 'CONCURRENTLY' END);
RETURN NEXT format('DO $pgcolumnar_drop$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION ''%% is still attached to its parent; its DETACH did not finish'', %L USING ERRCODE = ''55000''; END IF; DROP %s %s; END $pgcolumnar_drop$',
p.part::text, p.part::text,
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part);
-- the body as a literal, not dollar-quoted: no name can end it (#1331 review)
RETURN NEXT format('DO %L',
format('BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION %L, %L USING ERRCODE = %L; END IF; DROP %s %s; END',
p.part::text,
'% is still attached to its parent; its DETACH did not finish',
p.part::text, '55000',
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part));
END LOOP;
END
$drop_plan_tz$;
Expand All @@ -1093,9 +1097,13 @@ BEGIN
LOOP
RETURN NEXT format('ALTER TABLE %s DETACH PARTITION %s %s', parent, p.part,
CASE WHEN p.pending THEN 'FINALIZE' ELSE 'CONCURRENTLY' END);
RETURN NEXT format('DO $pgcolumnar_drop$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION ''%% is still attached to its parent; its DETACH did not finish'', %L USING ERRCODE = ''55000''; END IF; DROP %s %s; END $pgcolumnar_drop$',
p.part::text, p.part::text,
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part);
-- the body as a literal, not dollar-quoted: no name can end it (#1331 review)
RETURN NEXT format('DO %L',
format('BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION %L, %L USING ERRCODE = %L; END IF; DROP %s %s; END',
p.part::text,
'% is still attached to its parent; its DETACH did not finish',
p.part::text, '55000',
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part));
END LOOP;
END
$drop_plan_ts$;
Expand Down
20 changes: 14 additions & 6 deletions pgcolumnar--1.0-alpha5.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2312,9 +2312,13 @@ BEGIN
LOOP
RETURN NEXT format('ALTER TABLE %s DETACH PARTITION %s %s', parent, p.part,
CASE WHEN p.pending THEN 'FINALIZE' ELSE 'CONCURRENTLY' END);
RETURN NEXT format('DO $pgcolumnar_drop$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION ''%% is still attached to its parent; its DETACH did not finish'', %L USING ERRCODE = ''55000''; END IF; DROP %s %s; END $pgcolumnar_drop$',
p.part::text, p.part::text,
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part);
-- the body as a literal, not dollar-quoted: no name can end it (#1331 review)
RETURN NEXT format('DO %L',
format('BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION %L, %L USING ERRCODE = %L; END IF; DROP %s %s; END',
p.part::text,
'% is still attached to its parent; its DETACH did not finish',
p.part::text, '55000',
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part));
END LOOP;
END
$drop_plan_tz$;
Expand All @@ -2337,9 +2341,13 @@ BEGIN
LOOP
RETURN NEXT format('ALTER TABLE %s DETACH PARTITION %s %s', parent, p.part,
CASE WHEN p.pending THEN 'FINALIZE' ELSE 'CONCURRENTLY' END);
RETURN NEXT format('DO $pgcolumnar_drop$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION ''%% is still attached to its parent; its DETACH did not finish'', %L USING ERRCODE = ''55000''; END IF; DROP %s %s; END $pgcolumnar_drop$',
p.part::text, p.part::text,
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part);
-- the body as a literal, not dollar-quoted: no name can end it (#1331 review)
RETURN NEXT format('DO %L',
format('BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %L::regclass) THEN RAISE EXCEPTION %L, %L USING ERRCODE = %L; END IF; DROP %s %s; END',
p.part::text,
'% is still attached to its parent; its DETACH did not finish',
p.part::text, '55000',
CASE WHEN p.foreign_table THEN 'FOREIGN TABLE' ELSE 'TABLE' END, p.part));
END LOOP;
END
$drop_plan_ts$;
Expand Down
2 changes: 2 additions & 0 deletions test/check_ledger.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,7 @@ partition_lifecycle partition_lifecycle a parent with a DEFAULT partition is ref
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
partition_lifecycle partition_lifecycle a parent with a partition bounded by MAXVALUE is refused, and nothing is created 15;16;17;18;19 2026-10-12 partition_create_ahead without the MAXVALUE refusal
partition_lifecycle partition_lifecycle a partition from MINVALUE is listed once its upper bound is past, one to MAXVALUE never 15;16;17;18;19 2026-10-13 a partition planned only when it has a lower bound, so never one from MINVALUE;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 partition named with the drop block's dollar-quote tag is dropped by the plan as returned 15;16;17;18;19 2026-10-13 the drop block's body dollar-quoted, as on main at 614eb02
partition_lifecycle partition_lifecycle a partition that is itself partitioned is planned whole 15;16;17;18;19 2026-10-13 a partition that is itself partitioned left out of the plan;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;the plan detaches without CONCURRENTLY
partition_lifecycle partition_lifecycle a pending detach is finalised, not started again 15;16;17;18;19 2026-10-13 a partition whose upper bound equals the cutoff left out;a pending detach planned as a new concurrent detach, not FINALIZE;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 plan for names that need quoting runs as returned 15;16;17;18;19 2026-10-13 a partition whose upper bound equals the cutoff left out;names in the plan with their quotes removed;the cutoff filter removed: every partition planned;the lower bound read where the upper is meant;the plan detaches but drops nothing
Expand All @@ -1891,6 +1892,7 @@ partition_lifecycle partition_lifecycle asking further ahead creates only the di
partition_lifecycle partition_lifecycle every partition it creates is columnar 15;16;17;18;19 2026-10-12 partition_create_ahead creating heap partitions
partition_lifecycle partition_lifecycle from no partitions it creates enough to reach 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 it never takes AccessExclusiveLock on the parent 15;16;17;18;19 2026-10-12 partition_create_ahead using CREATE TABLE ... PARTITION OF
partition_lifecycle partition_lifecycle premise: a partition is named with the drop block's dollar-quote tag 15;16;17;18;19 never -
partition_lifecycle partition_lifecycle premise: a timestamp parent continued from a hand-made partition, in a session west of UTC 15;16;17;18;19 never -
partition_lifecycle partition_lifecycle premise: a timestamp parent was given partitions from a session east of UTC 15;16;17;18;19 never -
partition_lifecycle partition_lifecycle premise: a timestamptz parent continued from a hand-made partition, in a session west of UTC 15;16;17;18;19 never -
Expand Down
13 changes: 12 additions & 1 deletion test/check_ledger_budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1625,4 +1625,15 @@ suites_not_covered 246
# never 1801 + 3 = 1804
# dated 485 + 20 = 505
# 1804 + 505 == 2309
checks_never_observed_red 1804
#
# The drop block's body as a literal (#1331 review, OffgridwithJD): 1804 -> 1805.
# Two checks from a five-major run (partition_lifecycle.sh 52/52 on each of
# 15-19): the arm, a partition named with the block's dollar-quote tag dropped
# as planned, dated by the run on main's dollar-quoted drop; and its premise,
# that the fixture's name holds the tag, which arrives never.
#
# ledger rows 2309 + 2 = 2311
# never 1804 + 1 = 1805
# dated 505 + 1 = 506
# 1805 + 506 == 2311
checks_never_observed_red 1805
20 changes: 17 additions & 3 deletions test/partition_lifecycle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,12 @@ plan() { q "SELECT string_agg(s, E'\n') FROM pgcolumnar.partition_drop_plan('$1'
# The drop each partition gets: a DO block that refuses (55000) while the partition
# is still attached, so a DETACH that failed cannot be followed by a DROP that takes
# ACCESS EXCLUSIVE on the parent (jdatcmd, #1331 review).
sqlit() { local x="${1//\'/\'\'}"; printf "'%s'" "$x"; } # a SQL string literal
gdrop() { # gdrop 'TABLE'|'FOREIGN TABLE' QUALIFIED-NAME -> the planned statement
local lit="'${2//\'/\'\'}'"
printf "DO \$pgcolumnar_drop\$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = %s::regclass) THEN RAISE EXCEPTION '%% is still attached to its parent; its DETACH did not finish', %s USING ERRCODE = '55000'; END IF; DROP %s %s; END \$pgcolumnar_drop\$" \
"$lit" "$lit" "$1" "$2"
# The body is a string literal, not dollar-quoted, so no name can end it.
local body
body="BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits WHERE inhrelid = $(sqlit "$2")::regclass) THEN RAISE EXCEPTION '% is still attached to its parent; its DETACH did not finish', $(sqlit "$2") USING ERRCODE = '55000'; END IF; DROP $1 $2; END"
printf 'DO %s' "$(sqlit "$body")"
}
run_plan() { # each statement top-level, as \gexec would run it
local line
Expand Down Expand Up @@ -372,6 +374,18 @@ states="$states;$(stmt_state "${df_plan[1]}")"
check "after a DETACH that fails, the plan's DROP refuses and the partition stays attached" \
"$states|$(kids df)|$(q "SELECT count(*) FROM pg_inherits WHERE inhrelid = 'df_1'::regclass;")" "55P03;55000|2|1"

# A partition whose name holds the DO block's own dollar-quote tag (jdatcmd, #1331
# review): the guarded drop must still be one statement that runs as returned.
q 'CREATE TABLE dq (ts timestamptz NOT NULL) PARTITION BY RANGE (ts);
CREATE TABLE "dq$pgcolumnar_drop$1" PARTITION OF dq FOR VALUES FROM ('"'2020-01-01 00:00+00'"') TO ('"'2020-02-01 00:00+00'"') USING pgcolumnar;
CREATE TABLE dq_2 PARTITION OF dq FOR VALUES FROM ('"'2020-02-01 00:00+00'"') TO ('"'2020-03-01 00:00+00'"') USING pgcolumnar;' >/dev/null
check "premise: a partition is named with the drop block's dollar-quote tag" \
"$(q "SELECT count(*) FROM pg_class WHERE relname = 'dq\$pgcolumnar_drop\$1';")" "1"
dq_failures="$(run_plan dq '2020-02-01 00:00+00')"
check "a partition named with the drop block's dollar-quote tag is dropped by the plan as returned" \
"${dq_failures:-every statement ran}|$(q "SELECT count(*) FROM pg_class WHERE relname = 'dq\$pgcolumnar_drop\$1';")|$(kids dq)" \
"every statement ran|0|1"

# Refusals, as partition_create_ahead refuses.
check "a drop plan for a parent with a DEFAULT partition is refused" \
"$(sqlstate_or "SELECT count(*) FROM pgcolumnar.partition_drop_plan('td', now())")" "55000"
Expand Down
2 changes: 1 addition & 1 deletion test/pytest/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6549,7 +6549,7 @@ Removal proofs, each red in both twins: the prototype's zone handling; `CREATE T
| test | what it holds |
| --- | --- |
| `test_partition_create_ahead` | from nothing it reaches now plus the lead, columnar, contiguous and one width wide; a second call creates nothing, and asking further ahead creates only the difference; rows land in columnar partitions; the parent is never held ACCESS EXCLUSIVE; west of UTC, for both key types, partitions continue a hand-made one contiguously and a row at every boundary routes; east of UTC, a timestamp parent covers the local now and starts on the width's boundary; inputs that broke the first version (a generated column, a 63-character name, more than 1000 partitions, a MAXVALUE bound) are handled or refused before any DDL; a DEFAULT partition is refused and nothing is created |
| `test_partition_drop_plan` | the drop half: `partition_drop_plan` lists `DETACH ... CONCURRENTLY` and `DROP` for the partitions wholly before the cutoff, in range order, from partitions created newest first. A cutoff on an upper bound includes the partition, and one before them all plans nothing. Running the plan, each statement top-level, leaves the rest with their rows and drops the storage rows. A `timestamp` key with a `timestamp` cutoff plans the same in Tokyo and Denver, and a cutoff of the other type is refused (`42804`). MINVALUE is listed once past, and MAXVALUE never. Quoted schema and partition names run as returned. A sub-partitioned partition is planned whole and dropped with its children. A tiered partition is planned with `DROP FOREIGN TABLE`, and its Parquet file stays. A detach cancelled in its second phase is planned as `FINALIZE` and dropped. The DEFAULT and non-timestamp parents are refused (`55000`) |
| `test_partition_drop_plan` | the drop half: `partition_drop_plan` lists `DETACH ... CONCURRENTLY` and `DROP` for the partitions wholly before the cutoff, in range order, from partitions created newest first. A cutoff on an upper bound includes the partition, and one before them all plans nothing. Running the plan, each statement top-level, leaves the rest with their rows and drops the storage rows. A `timestamp` key with a `timestamp` cutoff plans the same in Tokyo and Denver, and a cutoff of the other type is refused (`42804`). MINVALUE is listed once past, and MAXVALUE never. Quoted schema and partition names run as returned. A sub-partitioned partition is planned whole and dropped with its children. A tiered partition is planned with `DROP FOREIGN TABLE`, and its Parquet file stays. A partition whose name holds the drop block's dollar-quote tag drops as planned. A detach cancelled in its second phase is planned as `FINALIZE` and dropped. The DEFAULT and non-timestamp parents are refused (`55000`) |

## 94. test_approx_aggregates.py: hll and tdigest over columnar tables, the compatibility statement

Expand Down
29 changes: 23 additions & 6 deletions test/pytest/test_partition_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,14 @@ def q(name):
def gdrop(kind, name):
"""The drop the plan gives a partition: a DO block that refuses (55000)
while it is still attached, so a failed DETACH is never followed by a DROP
that locks the parent (#1331 review)."""
lit = "'" + name.replace("'", "''") + "'"
return ("DO $pgcolumnar_drop$ BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits"
f" WHERE inhrelid = {lit}::regclass) THEN RAISE EXCEPTION '% is still attached to its parent;"
f" its DETACH did not finish', {lit} USING ERRCODE = '55000'; END IF; DROP {kind} {name};"
" END $pgcolumnar_drop$")
that locks the parent (#1331 review). Its body is a string literal, not
dollar-quoted, so no partition name can end it."""
def lit(x):
return "'" + x.replace("'", "''") + "'"
body = ("BEGIN IF EXISTS (SELECT 1 FROM pg_catalog.pg_inherits"
f" WHERE inhrelid = {lit(name)}::regclass) THEN RAISE EXCEPTION '% is still attached to its parent;"
f" its DETACH did not finish', {lit(name)} USING ERRCODE = '55000'; END IF; DROP {kind} {name}; END")
return "DO " + lit(body)

def sqlstate_with(sql, setup=None):
"""'ok', or the SQLSTATE the statement failed with, in its own session."""
Expand Down Expand Up @@ -470,6 +472,21 @@ def sqlstate(sql):
expect.text(f"{';'.join(states)}|{_kids(conn, 'xf')}|{attached}", "55P03;55000|2|1",
"after a DETACH that fails, the plan's DROP refuses and the partition stays attached")

# A partition named with the drop block's own dollar-quote tag (#1331 review).
with conn.cursor() as cur:
cur.execute("CREATE TABLE xq (ts timestamptz NOT NULL) PARTITION BY RANGE (ts)")
cur.execute('CREATE TABLE "xq$pgcolumnar_drop$a" PARTITION OF xq'
" FOR VALUES FROM ('2019-10-01 00:00+00') TO ('2019-10-08 00:00+00') USING pgcolumnar")
cur.execute("CREATE TABLE xq_b PARTITION OF xq"
" FOR VALUES FROM ('2019-10-08 00:00+00') TO ('2019-10-15 00:00+00') USING pgcolumnar")
named = ("SELECT count(*) FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace"
f" WHERE n.nspname = '{schema}' AND c.relname = 'xq$pgcolumnar_drop$a'")
expect.num(_one(conn, named), 1, "premise: a partition is named with the drop block's dollar-quote tag")
failed = [st for st in plan("xq", "2019-10-08 00:00+00") if sqlstate_with(st) != "ok"]
expect.text(f"{'; '.join(failed) or 'every statement ran'}|{_one(conn, named)}|{_kids(conn, 'xq')}",
"every statement ran|0|1",
"a partition named with the drop block's dollar-quote tag is dropped by the plan as returned")

with conn.cursor() as cur:
cur.execute("CREATE TABLE xd (ts timestamptz NOT NULL) PARTITION BY RANGE (ts)")
cur.execute("CREATE TABLE xd_default PARTITION OF xd DEFAULT USING pgcolumnar")
Expand Down
Loading