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
50 changes: 27 additions & 23 deletions test/check_ledger.tsv

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion test/check_ledger_budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1783,4 +1783,24 @@ suites_not_covered 246
# never 1803 + 0 = 1803
# dated 524 + 9 = 533
# 1803 + 533 == 2336
checks_never_observed_red 1803
#
# #1339 audit, my assigned files, batch one: collation_change, tier_partition,
# column_extent (OffgridwithJD): 1803 -> 1801. Four new arms, each dated:
# - before the change the column's extent is the C collation's bound
# (column_extent() returning no row);
# - before expire the extent covers the table, before DROP the table has extent
# rows to remove (no extent row ever written);
# - and with SELECT on the table the same role reads its extent (column_extent()
# refused to every non-superuser).
# Three column_extent checks named `premise:` test product code and lose the
# prefix, their rows renamed with their history (#1236). Two are now dated:
# "expire retires ..." (expire returning 0), "compact_rewrite rewrites ..."
# (compact_rewrite returning 0). "parallel_copy's loaders ... all commit" stays
# never. Five-major runs: collation_change 12/12, column_extent 39/39,
# tier_partition 71/71 on 18-19 and 70 + 1 named SKIP on 15-17.
#
# ledger rows 2336 + 4 = 2340
# never 1803 - 2 = 1801
# dated 533 + 4 + 2 = 539
# 1801 + 539 == 2340
checks_never_observed_red 1801
12 changes: 10 additions & 2 deletions test/collation_change.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ for t in cc ch; do
done
check "premise: the columnar table has several row groups" \
"$(q "SELECT count(*) FROM pgcolumnar.row_group WHERE storage_id = pgcolumnar.get_storage_id('cc');")" "3"
# THE EXTENT EXISTED, AND HELD C'S BOUND (#1339). The arm after the change accepts
# "none", because the fix forgets the extent; without this one, a column_extent()
# that never returns a row passed it too.
check "before the change the column's extent is the C collation's bound" \
"$(q "SELECT coalesce((SELECT minimum || '|' || maximum FROM pgcolumnar.column_extent('cc', 's')), 'none');")" \
"$(q "SELECT min(s) || '|' || max(s) FROM ch;")"
before="$(q "SELECT pg_relation_filenode('cc');")"
for t in cc ch; do
q "ALTER TABLE $t ALTER COLUMN s TYPE text COLLATE \"$COLL\";" >/dev/null
Expand All @@ -56,7 +62,8 @@ check "premise: the collation change rewrote nothing" "$(q "SELECT pg_relation_f
same() { # same SQL-with-%T -> "same", or both answers when they differ
local c h
c="$(q "${1//%T/cc}")"; h="$(q "${1//%T/ch}")"
[ "$c" = "$h" ] && echo same || echo "columnar [$c] heap [$h]"
# An answer is required (#1339): two empty answers are not the same answer.
[ -n "$c" ] && [ "$c" = "$h" ] && echo same || echo "columnar [$c] heap [$h]"
}
check "after the collation change min and max are heap's" "$(same "SELECT min(s) || '|' || max(s) FROM %T;")" "same"
check "and a range below an upper-case bound returns heap's rows" \
Expand Down Expand Up @@ -104,7 +111,8 @@ done
same2() { # same2 SQL-with-%T over vs and vh
local c h
c="$(q "${1//%T/vs}")"; h="$(q "${1//%T/vh}")"
[ "$c" = "$h" ] && echo same || echo "columnar [$c] heap [$h]"
# An answer is required (#1339): two empty answers are not the same answer.
[ -n "$c" ] && [ "$c" = "$h" ] && echo same || echo "columnar [$c] heap [$h]"
}
check "a table sorted under the old collation orders ORDER BY ... LIMIT as heap does" \
"$(same2 "SELECT string_agg(s, ',') FROM (SELECT s FROM %T ORDER BY s, k LIMIT 6) x;")" "same"
Expand Down
19 changes: 15 additions & 4 deletions test/column_extent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ q "CREATE TABLE cw (ts timestamptz, v int) USING pgcolumnar;
INSERT INTO cw SELECT '2026-06-01'::timestamptz + g * interval '1 s', g FROM generate_series(1, 2000) g;" >/dev/null
q "COPY (SELECT '2020-01-01'::timestamptz + g * interval '1 day', g FROM generate_series(1, 4000) g)
TO '$PGC_WORKDIR/cw.txt' WITH (FORMAT text);" >/dev/null
check "premise: parallel_copy's loaders, flushing into one table at once, all commit" \
check "parallel_copy's loaders, flushing into one table at once, all commit" \
"$(q "SELECT pgcolumnar.parallel_copy('cw', '$PGC_WORKDIR/cw.txt', 4);")" "4000"
check "and the extent covers what they loaded" "$(ext cw ts)" "$(truth cw ts)"

Expand Down Expand Up @@ -189,8 +189,11 @@ q "CREATE TABLE ex (ts timestamptz, v int) USING pgcolumnar;
INSERT INTO ex SELECT now() - interval '400 days' + g * interval '1 min', g FROM generate_series(1, 3000) g;
INSERT INTO ex SELECT now() - interval '1 day' + g * interval '1 s', g FROM generate_series(1, 3000) g;" >/dev/null
ex_before="$(ext ex ts)"
# The extent was there before expire (#1339): the arms below say it MOVED, which
# means nothing if it was never written.
check "before expire the extent covers the table" "$ex_before" "$(truth ex ts)"
retired="$(q "SELECT pgcolumnar.expire('ex');")"
check "premise: expire retires the old row groups with the extent in place" "$([ "${retired:-0}" -ge 1 ] && echo yes || echo no)" "yes"
check "expire retires the old row groups with the extent in place" "$([ "${retired:-0}" -ge 1 ] && echo yes || echo no)" "yes"
check "after expire the extent is recomputed to the data that is left" "$(ext ex ts)" "$(truth ex ts)"
check "and it did move: the retired groups held the old minimum" \
"$([ "$ex_before" != "$(ext ex ts)" ] && echo yes || echo no)" "yes"
Expand All @@ -200,7 +203,7 @@ q "CREATE TABLE cr (ts timestamptz, v int) USING pgcolumnar;
INSERT INTO cr SELECT '2026-01-01'::timestamptz + g * interval '1 min', g FROM generate_series(1, 4000) g;
DELETE FROM cr WHERE v > 3500 OR v <= 400;" >/dev/null
rewritten="$(q "SELECT pgcolumnar.compact_rewrite('cr', 0.1);")"
check "premise: compact_rewrite rewrites the groups the deletes thinned" "$([ "${rewritten:-0}" -ge 1 ] && echo yes || echo no)" "yes"
check "compact_rewrite rewrites the groups the deletes thinned" "$([ "${rewritten:-0}" -ge 1 ] && echo yes || echo no)" "yes"
check "after compact_rewrite the extent is the live rows'" "$(ext cr ts)" "$(truth cr ts)"

q "CREATE TABLE vr (ts timestamptz, v int) USING pgcolumnar;
Expand Down Expand Up @@ -234,13 +237,16 @@ q "CREATE TABLE hk (ts timestamptz, v int) USING pgcolumnar;" >/dev/null
for k in $(seq 1 50); do q "INSERT INTO hk VALUES ('2026-01-01'::timestamptz + interval '$k min', $k);" >/dev/null; done
hk_rows="$(q "SELECT count(*) FROM pgcolumnar.column_extent WHERE storage_id = $(sid hk) AND column_index = 0;")"
check "fifty committing writers leave a bounded number of extent rows" \
"$([ "${hk_rows:-99}" -le 9 ] && echo yes || echo "no (${hk_rows:-none})")" "yes"
"$([ "${hk_rows:-0}" -ge 1 ] && [ "${hk_rows:-99}" -le 9 ] && echo yes || echo "no (${hk_rows:-none})")" "yes"
check "and the extent still covers all fifty" "$(ext hk ts)" "$(truth hk ts)"
hk_old="$(sid hk)"
q "TRUNCATE hk;" >/dev/null
check "TRUNCATE removes the old storage's extent rows" \
"$(q "SELECT count(*) FROM pgcolumnar.column_extent WHERE storage_id = $hk_old;")" "0"
cw_old="$(sid cw)"
# Rows to remove (#1339): a count of zero after the DROP proves nothing without it.
check "before DROP the table has extent rows to remove" \
"$(q "SELECT (count(*) > 0)::text FROM pgcolumnar.column_extent WHERE storage_id = $cw_old;")" "true"
q "DROP TABLE cw;" >/dev/null
check "DROP TABLE removes them" "$(q "SELECT count(*) FROM pgcolumnar.column_extent WHERE storage_id = $cw_old;")" "0"

Expand All @@ -251,5 +257,10 @@ check "the extent catalog is not readable by an ordinary role (it holds column v
"$(as_role ce_r "SELECT count(*) FROM pgcolumnar.column_extent" | sed -n 's/^ERROR: *\([0-9A-Z]\{5\}\).*/\1/p')" "42501"
check "nor is a table's extent through the function, without SELECT on the table" \
"$(as_role ce_r "SELECT * FROM pgcolumnar.column_extent('public.ty', 'ts')" | sed -n 's/^ERROR: *\([0-9A-Z]\{5\}\).*/\1/p')" "42501"
# The control (#1339): the same role, given SELECT, reads it. Without this, a function
# refused to everyone -- by EXECUTE, or by the schema -- passed the arm above.
q "GRANT SELECT ON ty TO ce_r;" >/dev/null
check "and with SELECT on the table the same role reads its extent" \
"$(as_role ce_r "SELECT count(*) FROM pgcolumnar.column_extent('public.ty', 'ts')")" "1"

pgc_summary
14 changes: 12 additions & 2 deletions test/pytest/test_collation_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def _one(conn, sql):
def _same(conn, sql, columnar, heap):
c = _one(conn, sql.replace("%T", columnar))
h = _one(conn, sql.replace("%T", heap))
return "same" if c == h else f"columnar [{c}] heap [{h}]"
# An answer is required (#1339): None on both sides is not the same answer.
return "same" if c is not None and c == h else f"columnar [{c}] heap [{h}]"


def _value(g):
Expand All @@ -41,7 +42,10 @@ def test_a_collation_change_without_a_rewrite(pgc_conn, expect):
" AND collencoding IN (-1, pg_char_to_encoding(current_setting('server_encoding')))"
" ORDER BY collname LIMIT 1")
if coll is None:
expect.cannot_run("no en_US collation", name="the server has no en_US collation to change a C column to")
# A reason cannot_run knows (#1339): "no en_US collation" is not one, and it
# raised VacuityError instead of recording the test as unrunnable.
expect.cannot_run("MISSING_DEPENDENCY", "no en_US collation",
name="the server has no en_US collation to change a C column to")
return
expect.text(_one(conn, f"SELECT ('c' < 'Q' COLLATE \"{coll}\")::text || '|' || ('c' < 'Q' COLLATE \"C\")::text"),
"true|false", "premise: the new collation orders a lower-case letter before an upper-case one that C puts first")
Expand All @@ -54,6 +58,12 @@ def test_a_collation_change_without_a_rewrite(pgc_conn, expect):
cur.execute(f"INSERT INTO {t} SELECT g, {_value('g')} FROM generate_series(1, {4 * GROUP}) g")
expect.num(_one(conn, "SELECT count(*) FROM pgcolumnar.row_group WHERE storage_id = pgcolumnar.get_storage_id('xc')"),
4, "premise: the columnar table has several row groups")
# The extent existed, and held C's bound (#1339): the arm after the change accepts
# "none", so without this a column_extent() that never returns a row passed it.
expect.text(_one(conn, "SELECT coalesce((SELECT minimum || '|' || maximum"
" FROM pgcolumnar.column_extent('xc', 's')), 'none')"),
_one(conn, "SELECT min(s) || '|' || max(s) FROM xh"),
"before the change the column's extent is the C collation's bound")
before = _one(conn, "SELECT pg_relation_filenode('xc')")
for t in ("xc", "xh"):
conn.execute(f'ALTER TABLE {t} ALTER COLUMN s TYPE text COLLATE "{coll}"')
Expand Down
18 changes: 14 additions & 4 deletions test/pytest/test_column_extent.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def test_writers_concurrent_aborted_and_prepared(pgc_own_db, pgc_cluster, expect
_run(conn, "COPY (SELECT '2018-03-03'::timestamptz + g * interval '13 hours', g"
f" FROM generate_series(1, 5200) g) TO '{path}' WITH (FORMAT text)")
expect.num(_one(conn, f"SELECT pgcolumnar.parallel_copy('cw', '{path}', 3)"), 5200,
"premise: parallel_copy's loaders, flushing into one table at once, all commit")
"parallel_copy's loaders, flushing into one table at once, all commit")
expect.text(_ext(conn, "cw", "ts"), _truth(conn, "cw", "ts"), "and the extent covers what they loaded")

with psycopg.connect(dsn) as c:
Expand Down Expand Up @@ -221,9 +221,11 @@ def test_a_shrink_is_recomputed_never_guessed(pgc_own_db, expect):
_run(conn, "INSERT INTO ex SELECT now() - interval '700 days' + g * interval '90 s', g FROM generate_series(1, 2500) g",
"INSERT INTO ex SELECT now() - interval '2 days' + g * interval '7 s', g FROM generate_series(1, 2500) g")
before = _ext(conn, "ex", "ts")
# The extent was there before expire (#1339): "it did move" means nothing otherwise.
expect.text(before, _truth(conn, "ex", "ts"), "before expire the extent covers the table")
retired = _one(conn, "SELECT pgcolumnar.expire('ex')")
expect.text("yes" if (retired or 0) >= 1 else f"no ({retired})", "yes",
"premise: expire retires the old row groups with the extent in place")
"expire retires the old row groups with the extent in place")
after = _ext(conn, "ex", "ts")
expect.text(after, _truth(conn, "ex", "ts"), "after expire the extent is recomputed to the data that is left")
expect.text("yes" if before != after else "no", "yes",
Expand All @@ -235,7 +237,7 @@ def test_a_shrink_is_recomputed_never_guessed(pgc_own_db, expect):
"DELETE FROM cr WHERE v > 4200 OR v <= 650")
rewritten = _one(conn, "SELECT pgcolumnar.compact_rewrite('cr', 0.1)")
expect.text("yes" if (rewritten or 0) >= 1 else f"no ({rewritten})", "yes",
"premise: compact_rewrite rewrites the groups the deletes thinned")
"compact_rewrite rewrites the groups the deletes thinned")
expect.text(_ext(conn, "cr", "ts"), _truth(conn, "cr", "ts"), "after compact_rewrite the extent is the live rows'")

_run(conn, "CREATE TABLE vr (ts timestamptz, v int) USING pgcolumnar")
Expand Down Expand Up @@ -274,7 +276,7 @@ def test_housekeeping_and_privilege(pgc_own_db, pgc_cluster, expect):
_run(conn, f"INSERT INTO hk VALUES ('2025-01-01'::timestamptz + interval '{k} hours', {k})")
rows = _one(conn, f"SELECT count(*) FROM pgcolumnar.column_extent WHERE storage_id = {_sid(conn, 'hk')}"
" AND column_index = 0")
expect.text("yes" if rows <= 9 else f"no ({rows})", "yes",
expect.text("yes" if 1 <= rows <= 9 else f"no ({rows})", "yes",
"fifty committing writers leave a bounded number of extent rows")
expect.text(_ext(conn, "hk", "ts"), _truth(conn, "hk", "ts"), "and the extent still covers all fifty")
old = _sid(conn, "hk")
Expand All @@ -284,6 +286,9 @@ def test_housekeeping_and_privilege(pgc_own_db, pgc_cluster, expect):
_run(conn, "CREATE TABLE dr (ts timestamptz) USING pgcolumnar",
"INSERT INTO dr SELECT now() FROM generate_series(1, 10)")
old = _sid(conn, "dr")
# Rows to remove (#1339): a count of zero after the DROP proves nothing without it.
expect.text(str(_one(conn, f"SELECT count(*) > 0 FROM pgcolumnar.column_extent WHERE storage_id = {old}")),
"True", "before DROP the table has extent rows to remove")
_run(conn, "DROP TABLE dr")
expect.num(_one(conn, f"SELECT count(*) FROM pgcolumnar.column_extent WHERE storage_id = {old}"), 0,
"DROP TABLE removes them")
Expand All @@ -305,3 +310,8 @@ def as_role(sql):
"the extent catalog is not readable by an ordinary role (it holds column values)")
expect.text(as_role("SELECT * FROM pgcolumnar.column_extent('public.priv', 'ts')"), "42501",
"nor is a table's extent through the function, without SELECT on the table")
# The control (#1339): given SELECT, the same role reads it. Without this, a
# function refused to everyone passed the arm above.
_run(conn, "GRANT SELECT ON priv TO ce_py")
expect.text(as_role("SELECT * FROM pgcolumnar.column_extent('public.priv', 'ts')"), "ok",
"and with SELECT on the table the same role reads its extent")
7 changes: 5 additions & 2 deletions test/pytest/test_tier_partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,11 @@ def plan(sql):
"42809", "a partition that is not columnar is refused")
expect.text(_sqlstate(conn, f"SELECT pgcolumnar.tier_partition('s_rest', 'tq', '{server_dir}/rest.parquet')"),
"0A000", "a DEFAULT partition is refused")
expect.text(_sqlstate(conn, f"SELECT pgcolumnar.tier_partition('s_apr', 'ti', '{server_dir}/apr.parquet')"),
"42809", "a server of another wrapper is refused")
# Before anything moves (#1339): a refusal from a later step, after the export,
# would leave the file behind with the same SQLSTATE.
other = _sqlstate(conn, f"SELECT pgcolumnar.tier_partition('s_apr', 'ti', '{server_dir}/apr.parquet')")
expect.text(f"{other}|{'file' if (server_dir / 'apr.parquet').exists() else 'nofile'}",
"42809|nofile", "a server of another wrapper is refused")
expect.text(_sqlstate(conn, f"SELECT pgcolumnar.tier_partition('s_apr', 'tq', '{path}')"),
"58P02", "an existing path is refused, so no file a table reads is overwritten")
expect.text(_digest(conn, "s"), rb, "and the refusals moved nothing")
Expand Down
5 changes: 4 additions & 1 deletion test/tier_partition.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ psql_run "CREATE TABLE m_def PARTITION OF m DEFAULT USING pgcolumnar;"
check "a DEFAULT partition is refused" \
"$(sqlstate "SELECT pgcolumnar.tier_partition('m_def', 'tier_pq', '$W/m_def.parquet');")" "0A000"
psql_run "CREATE SERVER tier_ice FOREIGN DATA WRAPPER pgcolumnar_iceberg;"
# Before anything moves (#1339): a refusal that came only from a later step, after
# the export, would leave the file behind with the same SQLSTATE.
check "a server of another wrapper is refused" \
"$(sqlstate "SELECT pgcolumnar.tier_partition('m_feb', 'tier_ice', '$W/m_feb.parquet');")" "42809"
"$(sqlstate "SELECT pgcolumnar.tier_partition('m_feb', 'tier_ice', '$W/m_feb.parquet');")|$([ -e "$W/m_feb.parquet" ] && echo file || echo nofile)" \
"42809|nofile"
check "an existing path is refused, so no file a table reads is overwritten" \
"$(sqlstate "SELECT pgcolumnar.tier_partition('m_feb', 'tier_pq', '$W/m_jan.parquet');")" "58P02"
check "and the refusals moved nothing" "$(digest m)" "$rb"
Expand Down
Loading