diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 68d5b1587..cb4c291d7 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -210,6 +210,7 @@ collation_change collation_change a table sorted under the old collation orders collation_change collation_change after the collation change min and max are heap's 15;16;17;18;19 2026-10-13 nothing forgotten after a collation change (main's behaviour);the min/max fold skips a group with values and no bounds as all-NULL collation_change collation_change and a range below an upper-case bound returns heap's rows 15;16;17;18;19 2026-10-13 nothing forgotten after a collation change (main's behaviour) collation_change collation_change and an equality on a value C sorts first returns heap's rows 15;16;17;18;19 2026-10-13 nothing forgotten after a collation change (main's behaviour) +collation_change collation_change before the change the column's extent is the C collation's bound 15;16;17;18;19 2026-10-14 column_extent() returning no row collation_change collation_change premise: the collation change rewrote nothing 15;16;17;18;19 never - collation_change collation_change premise: the columnar table has several row groups 15;16;17;18;19 never - collation_change collation_change premise: the new collation orders a lower-case letter before an upper-case one that C puts first 15;16;17;18;19 never - @@ -218,38 +219,41 @@ collation_change collation_change the column's extent is not the old collation's column_extent column_extent DROP TABLE removes them 15;16;17;18;19 2026-10-13 PgColumnarDeleteMetadata leaves the extent rows column_extent column_extent TRUNCATE removes the old storage's extent rows 15;16;17;18;19 2026-10-13 PgColumnarDeleteMetadata leaves the extent rows column_extent column_extent a column added after older groups has no complete extent (those groups have no zone map for it) 15;16;17;18;19 2026-10-13 a recompute that ignores a group with no zone map for the column;pgcolumnar.column_extent returns a row with no complete extent;the extent trusted without a complete row -column_extent column_extent a prepared transaction's groups are inside the extent after COMMIT PREPARED 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;no extent row written at pre-commit;no extent rows written at PRE_PREPARE -column_extent column_extent a savepoint rolled back after flushing may widen the extent, never narrow it 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the extent folded by comparing Datums as words, not with the type's comparison;the writer's fold keeps the largest minimum +column_extent column_extent a prepared transaction's groups are inside the extent after COMMIT PREPARED 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no extent row written at pre-commit;no extent rows written at PRE_PREPARE +column_extent column_extent a savepoint rolled back after flushing may widen the extent, never narrow it 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the extent folded by comparing Datums as words, not with the type's comparison;the writer's fold keeps the largest minimum column_extent column_extent a table created in a rolled-back savepoint leaves no extent rows 15;16;17;18;19 2026-10-13 pre-commit writes extent rows for a storage that does not exist -column_extent column_extent a transaction's own flushed groups are inside the extent it reads before commit 15;16;17;18;19 2026-10-13 the extent a transaction reads leaves out its own pending fold (the tree before the fix) +column_extent column_extent a transaction's own flushed groups are inside the extent it reads before commit 15;16;17;18;19 2026-10-14 no extent row is ever written (PgColumnarExtentPreCommit returns at once);the extent a transaction reads leaves out its own pending fold (the tree before the fix) column_extent column_extent a type with no ordering gets no extent 15;16;17;18;19 2026-10-13 a recompute that skips values without an ordering;pgcolumnar.column_extent returns a row with no complete extent -column_extent column_extent a writer that finds no complete extent recomputes one over every group 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one -column_extent column_extent a writer that rolls back leaves the extent as the data has it 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the writer's fold keeps the largest minimum -column_extent column_extent after VACUUM retires a fully deleted group the extent is the live rows' 15;16;17;18;19 2026-10-13 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded -column_extent column_extent after compact_rewrite the extent is the live rows' 15;16;17;18;19 2026-10-13 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded -column_extent column_extent after expire the extent is recomputed to the data that is left 15;16;17;18;19 2026-10-13 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded +column_extent column_extent a writer that finds no complete extent recomputes one over every group 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent a writer that rolls back leaves the extent as the data has it 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the writer's fold keeps the largest minimum +column_extent column_extent after VACUUM retires a fully deleted group the extent is the live rows' 15;16;17;18;19 2026-10-14 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent after compact_rewrite the extent is the live rows' 15;16;17;18;19 2026-10-14 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded;compact_rewrite returning 0 before rewriting anything;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent after expire the extent is recomputed to the data that is left 15;16;17;18;19 2026-10-14 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded;no extent row is ever written (PgColumnarExtentPreCommit returns at once) column_extent column_extent and a plan made in that transaction estimates as the zone maps do 15;16;17;18;19 2026-10-13 the extent a transaction reads leaves out its own pending fold (the tree before the fix) -column_extent column_extent and it did move: the retired groups held the old minimum 15;16;17;18;19 2026-10-13 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded +column_extent column_extent and it did move: the retired groups held the old minimum 15;16;17;18;19 2026-10-14 PgColumnarRetireGroup without marking the storage for a recompute;a recompute that leaves the rows it folded;no extent row is ever written (PgColumnarExtentPreCommit returns at once) column_extent column_extent and the estimate is the one the zone maps give 15;16;17;18;19 2026-10-13 the planner takes the extent's minimum as its upper bound too;the planner's zone-map fallback removed: with no complete extent it reads nothing and bounds nothing -column_extent column_extent and the extent covers what they loaded 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the writer's fold keeps the largest minimum -column_extent column_extent and the extent still covers all fifty 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;no extent row written at pre-commit +column_extent column_extent and the extent covers what they loaded 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no extent row written at pre-commit;no extent rows written at PRE_PREPARE;the writer's fold keeps the largest minimum +column_extent column_extent and the extent still covers all fifty 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no extent row written at pre-commit +column_extent column_extent and with SELECT on the table the same role reads its extent 15;16;17;18;19 2026-10-14 column_extent() refused to every non-superuser;no extent row is ever written (PgColumnarExtentPreCommit returns at once) column_extent column_extent and with no extent rows the function reports no complete extent 15;16;17;18;19 2026-10-13 pgcolumnar.column_extent returns a row with no complete extent -column_extent column_extent fifty committing writers leave a bounded number of extent rows 15;16;17;18;19 2026-10-13 a recompute that leaves the rows it folded;a writer that finds no complete extent does not recompute one;no merge however many extent rows a column has +column_extent column_extent before DROP the table has extent rows to remove 15;16;17;18;19 2026-10-14 no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent before expire the extent covers the table 15;16;17;18;19 2026-10-14 no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent compact_rewrite rewrites the groups the deletes thinned 15;16;17;18;19 2026-10-14 compact_rewrite returning 0 before rewriting anything +column_extent column_extent expire retires the old row groups with the extent in place 15;16;17;18;19 2026-10-14 expire returning 0 whatever it retired +column_extent column_extent fifty committing writers leave a bounded number of extent rows 15;16;17;18;19 2026-10-14 a recompute that leaves the rows it folded;a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);no merge however many extent rows a column has column_extent column_extent nor is a table's extent through the function, without SELECT on the table 15;16;17;18;19 2026-10-13 pgcolumnar.column_extent without its SELECT check -column_extent column_extent planning a bucketed GROUP BY scans zone_map not at all when the extent is complete 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;the planner's time range ignores the extent -column_extent column_extent premise: compact_rewrite rewrites the groups the deletes thinned 15;16;17;18;19 never - -column_extent column_extent premise: expire retires the old row groups with the extent in place 15;16;17;18;19 never - -column_extent column_extent premise: parallel_copy's loaders, flushing into one table at once, all commit 15;16;17;18;19 never - +column_extent column_extent parallel_copy's loaders, flushing into one table at once, all commit 15;16;17;18;19 never - +column_extent column_extent planning a bucketed GROUP BY scans zone_map not at all when the extent is complete 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);the planner's time range ignores the extent column_extent column_extent premise: the extent function exists 15;16;17;18;19 never - column_extent column_extent premise: the transaction's plans gave two estimates 15;16;17;18;19 never - column_extent column_extent premise: the typed fixture spans several row groups 15;16;17;18;19 never - column_extent column_extent the extent catalog is not readable by an ordinary role (it holds column values) 15;16;17;18;19 2026-10-13 the column_extent catalog granted to PUBLIC -column_extent column_extent the extent of ty.d is the data's min and max, in the type's order 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one -column_extent column_extent the extent of ty.i is the data's min and max, in the type's order 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one -column_extent column_extent the extent of ty.n is the data's min and max, in the type's order 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one;the extent folded by comparing Datums as words, not with the type's comparison -column_extent column_extent the extent of ty.s is the data's min and max, in the type's order 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one -column_extent column_extent the extent of ty.ts is the data's min and max, in the type's order 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one -column_extent column_extent while the column that was there from the start still has one 15;16;17;18;19 2026-10-13 a writer that finds no complete extent does not recompute one +column_extent column_extent the extent of ty.d is the data's min and max, in the type's order 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent the extent of ty.i is the data's min and max, in the type's order 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent the extent of ty.n is the data's min and max, in the type's order 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once);the extent folded by comparing Datums as words, not with the type's comparison +column_extent column_extent the extent of ty.s is the data's min and max, in the type's order 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent the extent of ty.ts is the data's min and max, in the type's order 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) +column_extent column_extent while the column that was there from the start still has one 15;16;17;18;19 2026-10-14 a writer that finds no complete extent does not recompute one;no extent row is ever written (PgColumnarExtentPreCommit returns at once) column_extent column_extent without the extent rows the planner falls back to one zone_map scan 15;16;17;18;19 2026-10-13 the planner's zone-map fallback removed: with no complete extent it reads nothing and bounds nothing differential differential agg avg 15;16;17;18;19 never - differential differential agg count 15;16;17;18;19 never - @@ -2181,7 +2185,7 @@ tier_partition tier_partition a query on the other range scans only the local pa tier_partition tier_partition a query on the tiered range scans only the foreign partition 15;16;17;18;19 never - 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 of another wrapper is refused 15;16;17;18;19 2026-10-14 tier_partition without its wrong-wrapper branch, so the iceberg wrapper refuses only after the export;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 diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index b21ca9c6f..01290e6c6 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -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 diff --git a/test/collation_change.sh b/test/collation_change.sh index c81fdac3d..233950b3e 100755 --- a/test/collation_change.sh +++ b/test/collation_change.sh @@ -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 @@ -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" \ @@ -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" diff --git a/test/column_extent.sh b/test/column_extent.sh index b81c750f3..bba291034 100755 --- a/test/column_extent.sh +++ b/test/column_extent.sh @@ -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)" @@ -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" @@ -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; @@ -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" @@ -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 diff --git a/test/pytest/test_collation_change.py b/test/pytest/test_collation_change.py index 7cfe87759..44745ba43 100644 --- a/test/pytest/test_collation_change.py +++ b/test/pytest/test_collation_change.py @@ -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): @@ -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") @@ -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}"') diff --git a/test/pytest/test_column_extent.py b/test/pytest/test_column_extent.py index 302b22498..add40cc65 100644 --- a/test/pytest/test_column_extent.py +++ b/test/pytest/test_column_extent.py @@ -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: @@ -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", @@ -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") @@ -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") @@ -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") @@ -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") diff --git a/test/pytest/test_tier_partition.py b/test/pytest/test_tier_partition.py index 339742395..ad306382a 100644 --- a/test/pytest/test_tier_partition.py +++ b/test/pytest/test_tier_partition.py @@ -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") diff --git a/test/tier_partition.sh b/test/tier_partition.sh index 976bb4a45..03b3540d8 100755 --- a/test/tier_partition.sh +++ b/test/tier_partition.sh @@ -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"