diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 37222b28a..2a9ea5c9b 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2261,6 +2261,7 @@ maintenance_crash maintenance_crash compact_rewrite changed the row groups befor maintenance_crash maintenance_crash compact_rewrite changed the row groups in its open transaction 15;16;17;18;19 2026-10-15 compact_rewrite rewrites nothing (rewritten = 0) maintenance_crash maintenance_crash compact_rewrite committed, and the cluster came back after a crash before any checkpoint 15;16;17;18;19 never - maintenance_crash maintenance_crash compact_rewrite ran in its open transaction, and the cluster came back after a crash 15;16;17;18;19 never - +maintenance_crash maintenance_crash premise: the by-index reading is an Index Scan through mc_id 15;16;17;18;19 never - maintenance_crash maintenance_crash recluster changed the row groups before the crash 15;16;17;18;19 never - maintenance_crash maintenance_crash recluster changed the row groups in its open transaction 15;16;17;18;19 never - maintenance_crash maintenance_crash recluster committed, and the cluster came back after a crash before any checkpoint 15;16;17;18;19 never - @@ -2269,15 +2270,16 @@ maintenance_crash maintenance_crash vacuum changed the row groups before the cra maintenance_crash maintenance_crash vacuum changed the row groups in its open transaction 15;16;17;18;19 never - maintenance_crash maintenance_crash vacuum committed, and the cluster came back after a crash before any checkpoint 15;16;17;18;19 never - maintenance_crash maintenance_crash vacuum ran in its open transaction, and the cluster came back after a crash 15;16;17;18;19 never - -maintenance_rollback maintenance_rollback after a savepoint rolls back two rewrites, the rows written later in the transaction commit beside the old ones 15;16;17;18;19 2026-10-15 PgColumnarAllocateFreeSpace reuses space whatever its freeing xid, so a rolled-back maintenance function's freed bytes are overwritten -maintenance_rollback maintenance_rollback after compact rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 never - -maintenance_rollback maintenance_rollback after compact_rewrite rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-15 PgColumnarAllocateFreeSpace reuses space whatever its freeing xid, so a rolled-back maintenance function's freed bytes are overwritten -maintenance_rollback maintenance_rollback after recluster rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 never - -maintenance_rollback maintenance_rollback after vacuum rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 never - -maintenance_rollback maintenance_rollback after vacuum_sorted rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 never - +maintenance_rollback maintenance_rollback after a savepoint rolls back two rewrites, the rows written later in the transaction commit beside the old ones 15;16;17;18;19 2026-10-16 PgColumnarAllocateFreeSpace reuses space whatever its freeing xid, so a rolled-back maintenance function's freed bytes are overwritten;columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row +maintenance_rollback maintenance_rollback after compact rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-16 columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row +maintenance_rollback maintenance_rollback after compact_rewrite rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-16 PgColumnarAllocateFreeSpace reuses space whatever its freeing xid, so a rolled-back maintenance function's freed bytes are overwritten;columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row +maintenance_rollback maintenance_rollback after recluster rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-16 columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row +maintenance_rollback maintenance_rollback after vacuum rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-16 columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row +maintenance_rollback maintenance_rollback after vacuum_sorted rolls back, the table holds the same rows and row groups, by scan and by index 15;16;17;18;19 2026-10-16 columnar_tableam.c: pgcolumnar_index_fetch_tuple finds no row maintenance_rollback maintenance_rollback and every function then runs for real and keeps the same rows 15;16;17;18;19 never - maintenance_rollback maintenance_rollback compact changed the row groups inside its transaction 15;16;17;18;19 never - maintenance_rollback maintenance_rollback compact_rewrite changed the row groups inside its transaction 15;16;17;18;19 never - +maintenance_rollback maintenance_rollback premise: the by-index reading is an Index Scan through mr_id 15;16;17;18;19 never - maintenance_rollback maintenance_rollback premise: the table holds the rows it was given 15;16;17;18;19 never - maintenance_rollback maintenance_rollback recluster changed the row groups inside its transaction 15;16;17;18;19 never - maintenance_rollback maintenance_rollback vacuum changed the row groups inside its transaction 15;16;17;18;19 never - @@ -2426,14 +2428,21 @@ native_decode_gating native_decode_gating the needle removes exactly the non-mat native_decode_gating native_decode_gating the needle skips exactly the vectors holding no match 15;16;17;18;19 2026-10-16 columnar_reader.c: the per-vector qual evaluation between the decode passes never runs;columnar_reader.c: the per-vector qual evaluator marks every vector skipped native_decode_gating native_decode_gating the nomatch query returns nothing 15;16;17;18;19 never - native_decode_gating native_decode_gating the zero-match query removes every row by filter, counted once 15;16;17;18;19 never - -native_delete_visibility_paths native_delete_visibility_paths a DELETE whose qual deletes another row of the table succeeds and leaves heap's rows (#1394) 15;16;17;18;19 2026-10-14 PgColumnarUpsertDeleteVector without the command advance (main before #1394's fix) -native_delete_visibility_paths native_delete_visibility_paths a deleted row is invisible via the index 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths a DELETE whose qual deletes another row of the table succeeds and leaves heap's rows (#1394) 15;16;17;18;19 2026-10-16 PgColumnarUpsertDeleteVector without the command advance (main before #1394's fix);columnar_reader.c: the row-at-a-time scan reads a deleted row as live +native_delete_visibility_paths native_delete_visibility_paths a deleted row is invisible via the index 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row ignores the flushed delete vector native_delete_visibility_paths native_delete_visibility_paths a live row is visible via the index 15;16;17;18;19 never - -native_delete_visibility_paths native_delete_visibility_paths a row deleted in-transaction is invisible in the same transaction 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths a row deleted in-transaction is invisible in the same transaction 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row ignores the flushed delete vector native_delete_visibility_paths native_delete_visibility_paths aggregate over the whole table sees the live set 15;16;17;18;19 never - -native_delete_visibility_paths native_delete_visibility_paths index / bitmap scan sees the live set 15;16;17;18;19 never - -native_delete_visibility_paths native_delete_visibility_paths index-only scan sees the live set 15;16;17;18;19 never - -native_delete_visibility_paths native_delete_visibility_paths sequential scan sees the live set (row-emitting path) 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths index / bitmap scan sees the live set 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row ignores the flushed delete vector +native_delete_visibility_paths native_delete_visibility_paths index-only scan sees the live set 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row ignores the flushed delete vector +native_delete_visibility_paths native_delete_visibility_paths premise: the deletes span more than one row group 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths premise: the index arm really plans an Index Scan 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths premise: the index-only arm really plans an Index Only Scan 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths premise: the sequential arm really plans a Seq Scan 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths premise: the table holds all 5000 rows 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths premise: the whole-table aggregate stays on the columnar path 15;16;17;18;19 never - +native_delete_visibility_paths native_delete_visibility_paths sequential scan sees the live set (row-emitting path) 15;16;17;18;19 2026-10-16 columnar_reader.c: the row-at-a-time scan reads a deleted row as live +native_delete_visibility_paths native_delete_visibility_paths the rolled-back delete left the row in place 15;16;17;18;19 never - native_dict_underfill native_dict_underfill backend survives the under-filled DICT chunk 15;16;17;18;19 2026-10-16 columnar_encoding.c: the DICT fill refusal replaced by a backend crash native_dict_underfill native_dict_underfill premise: the inflation rewrote the DICT chunk's descriptor 15;16;17;18;19 never - native_dict_underfill native_dict_underfill the text column is DICT-encoded (precondition) 15;16;17;18;19 never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index b2388c156..affa347e8 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -3443,4 +3443,28 @@ suites_not_covered 193 # never 3010 + 7 = 3017 # dated 1300 + 6 = 1306 # 3017 + 1306 == 4323 +# +# #1339, native_delete_visibility_paths' shell arms take the paths they name +# (OffgridwithJD): 3017 -> 3019. Seven checks are new, all never: the six path and +# fixture premises the twin already had, and the rolled-back row. Five arms that +# entered never are now dated: the index / bitmap, index-only, deleted-row-via-index +# and in-transaction arms by pgcolumnar_fetch_row ignoring the delete vector, and +# the sequential arm by the row scan reading a deleted row as live. The #1394 arm +# gains the second in its history. +# +# ledger rows 4323 + 7 = 4330 +# never 3017 + 7 - 5 = 3019 +# dated 1306 + 5 = 1311 +# 3019 + 1311 == 4330 +# +# #1339, maintenance_rollback's and maintenance_crash's by-index readings read the +# index (OffgridwithJD): 3019 -> 3017. Two premises are new, one per suite, both +# never. With pgcolumnar_index_fetch_tuple finding no row, maintenance_rollback's +# six "by index" arms are red; four of them entered never and are now dated, and +# the other two gain the mutation in their history. No row is removed. +# +# ledger rows 4330 + 2 = 4332 +# never 3019 + 2 - 4 = 3017 +# dated 1311 + 4 = 1315 +# 3017 + 1315 == 4332 checks_never_observed_red 3017 diff --git a/test/maintenance_crash.sh b/test/maintenance_crash.sh index 72d613be0..2026e931a 100755 --- a/test/maintenance_crash.sh +++ b/test/maintenance_crash.sh @@ -57,10 +57,16 @@ rows() { } RG_SQL="SELECT count(*) || '/' || coalesce(sum(row_count), 0) FROM pgcolumnar.row_group WHERE storage_id = pgcolumnar.get_storage_id('mc')" +BY_INDEX_SETS="SET enable_seqscan = off; SET enable_bitmapscan = off; SET pgcolumnar.enable_custom_scan = off;" +BY_INDEX_SQL="SELECT count(*) || '/' || md5(string_agg(id || ':' || v || ':' || w, ',' ORDER BY id)) + FROM mc WHERE id BETWEEN 1 AND 10000" by_index() { - q "SET enable_seqscan = off; SET enable_bitmapscan = off; - SELECT count(*) || '/' || md5(string_agg(id || ':' || v || ':' || w, ',' ORDER BY id)) - FROM mc WHERE id BETWEEN 1 AND 10000" | grep -E '^[0-9]+/' | tail -1 + # pgcolumnar.enable_custom_scan = off as well, and a premise that this plan is an + # Index Scan: enable_seqscan alone leaves the columnar custom scan on, and this + # whole-table range planned as Custom Scan (PgColumnarScan). Measured (#1339): + # with the index fetch reading the neighbouring row, every "by index" arm stayed + # green, because none of them read the index. + q "$BY_INDEX_SETS $BY_INDEX_SQL" | grep -E '^[0-9]+/' | tail -1 } state() { # the table, as "||" echo "$(rows)|$(q "$RG_SQL" | grep -E '^[0-9]+/' | tail -1)|$(by_index)" @@ -75,6 +81,10 @@ fresh_table() { CHECKPOINT;" >/dev/null } +fresh_table +check "premise: the by-index reading is an Index Scan through mc_id" \ + "$(pgc_uses_row_fetch "$BY_INDEX_SETS" "$BY_INDEX_SQL")" yes + FUNCS=("pgcolumnar.compact_rewrite('mc', 0.0)" "pgcolumnar.recluster('mc', 'v')" "pgcolumnar.compact('mc')" "pgcolumnar.vacuum('mc')") # ---- uncommitted at the crash --------------------------------------------------------- diff --git a/test/maintenance_rollback.sh b/test/maintenance_rollback.sh index ccc030146..0fc129182 100755 --- a/test/maintenance_rollback.sh +++ b/test/maintenance_rollback.sh @@ -40,14 +40,22 @@ rows() { # the rows, by content } RG_SQL="SELECT count(*) || '/' || coalesce(sum(row_count), 0) FROM pgcolumnar.row_group WHERE storage_id = pgcolumnar.get_storage_id('mr')" +BY_INDEX_SETS="SET enable_seqscan = off; SET enable_bitmapscan = off; SET pgcolumnar.enable_custom_scan = off;" +BY_INDEX_SQL="SELECT count(*) || '/' || md5(string_agg(id || ':' || v || ':' || w, ',' ORDER BY id)) + FROM mr WHERE id BETWEEN 1 AND 10000" by_index() { # the rows an index scan returns, by content - q "SET enable_seqscan = off; SET enable_bitmapscan = off; - SELECT count(*) || '/' || md5(string_agg(id || ':' || v || ':' || w, ',' ORDER BY id)) - FROM mr WHERE id BETWEEN 1 AND 10000" | grep -E '^[0-9]+/' | tail -1 + # pgcolumnar.enable_custom_scan = off as well, and a premise that this plan is an + # Index Scan: enable_seqscan alone leaves the columnar custom scan on, and this + # whole-table range planned as Custom Scan (PgColumnarScan). Measured (#1339): + # with the index fetch reading the neighbouring row, every "by index" arm stayed + # green, because none of them read the index. + q "$BY_INDEX_SETS $BY_INDEX_SQL" | grep -E '^[0-9]+/' | tail -1 } ROWS0="$(rows)" RG0="$(q "$RG_SQL")" check "premise: the table holds the rows it was given" "${ROWS0%%/*}|$RG0" "5400|10/10000" +check "premise: the by-index reading is an Index Scan through mr_id" \ + "$(pgc_uses_row_fetch "$BY_INDEX_SETS" "$BY_INDEX_SQL")" yes # rolled_back FUNCTION -> "|||" rolled_back() { diff --git a/test/native_delete_visibility_paths.sh b/test/native_delete_visibility_paths.sh index 6cddc1c45..38776eb6d 100755 --- a/test/native_delete_visibility_paths.sh +++ b/test/native_delete_visibility_paths.sh @@ -26,8 +26,14 @@ q "CREATE EXTENSION IF NOT EXISTS pgcolumnar;" >/dev/null q "SET pgcolumnar.stripe_row_limit=1000; CREATE TABLE t (id int, v int) USING pgcolumnar; INSERT INTO t SELECT g, g FROM generate_series(1,5000) g; - CREATE UNIQUE INDEX t_id ON t (id); - DELETE FROM t WHERE id % 7 = 0;" >/dev/null + CREATE UNIQUE INDEX t_id ON t (id);" >/dev/null +check "premise: the table holds all 5000 rows" "$(q 'SELECT count(*) FROM t;')" "5000" +q "DELETE FROM t WHERE id % 7 = 0;" >/dev/null +# Each group builds its own mask, so one group would test the fold of one bitmap. +check "premise: the deletes span more than one row group" \ + "$(q "SELECT (count(*) >= 2)::int FROM pgcolumnar.storage s + JOIN pgcolumnar.row_group rg USING (storage_id) + WHERE s.relation_oid = 't'::regclass;")" "1" # Live count = 5000 - floor(5000/7) = 5000 - 714 = 4286. Five row groups. LIVE=4286 @@ -36,27 +42,58 @@ LIVE=4286 # multi-statement send. last() { q "$1" | tail -1; } -# A qual on the non-indexed column forces a real row-emitting sequential scan -# (which applies the delete mask per row), not the unqualified count(*) metadata -# fast-path (which subtracts delete counts and never reaches the bit test). +# EACH ARM FORCES THE PATH IT IS NAMED FOR, AND ASSERTS THE NODE FIRST (#1339). +# The arms set enable_seqscan/enable_indexonlyscan and trusted the names, but none +# of the enable_* scan GUCs governs Custom Scan (PgColumnarScan): measured, the +# index / bitmap and index-only arms planned as the columnar custom scan, the same +# path as the sequential arm, so three of four named paths were one path counted +# three times. pgcolumnar.enable_custom_scan=off is what moves the plan; the +# pytest twin has forced each path this way since it was written. +NO_COLUMNAR="SET pgcolumnar.enable_custom_scan = off;" +path_plan() { # path_plan SETS QUERY NODE -> yes, or the plan's node lines + local plan + plan="$(q "$1 EXPLAIN (COSTS OFF) $2")" + case "$plan" in + *"$3"*) echo yes ;; + *) echo "no: $(grep -E 'Scan|Aggregate' <<<"$plan" | sed 's/^ *//' | tr '\n' '/')" ;; + esac +} + +SEQ="$NO_COLUMNAR SET enable_indexscan=off; SET enable_bitmapscan=off; SET enable_indexonlyscan=off;" +check "premise: the sequential arm really plans a Seq Scan" \ + "$(path_plan "$SEQ" "SELECT count(*) FROM t WHERE v >= 0" "Seq Scan")" yes check "sequential scan sees the live set (row-emitting path)" \ - "$(last 'SET enable_indexscan=off; SET enable_bitmapscan=off; SET enable_indexonlyscan=off; SELECT count(*) FROM t WHERE v >= 0;')" "$LIVE" + "$(last "$SEQ SELECT count(*) FROM t WHERE v >= 0;")" "$LIVE" + +IDX="$NO_COLUMNAR SET enable_seqscan=off; SET enable_indexonlyscan=off;" +check "premise: the index arm really plans an Index Scan" \ + "$(path_plan "$IDX" "SELECT count(*) FROM t WHERE id >= 0" "Index Scan")" yes check "index / bitmap scan sees the live set" \ - "$(last 'SET enable_seqscan=off; SET enable_indexonlyscan=off; SELECT count(*) FROM t WHERE id >= 0;')" "$LIVE" + "$(last "$IDX SELECT count(*) FROM t WHERE id >= 0;")" "$LIVE" + +IOS="$NO_COLUMNAR SET enable_seqscan=off; SET enable_indexonlyscan=on;" +check "premise: the index-only arm really plans an Index Only Scan" \ + "$(path_plan "$IOS" "SELECT count(id) FROM t WHERE id >= 0" "Index Only Scan")" yes check "index-only scan sees the live set" \ - "$(last 'SET enable_seqscan=off; SET enable_indexonlyscan=on; SELECT count(id) FROM t WHERE id >= 0;')" "$LIVE" + "$(last "$IOS SELECT count(id) FROM t WHERE id >= 0;")" "$LIVE" + +# The columnar path, left to the planner: a different consumer of the same fold. +check "premise: the whole-table aggregate stays on the columnar path" \ + "$(path_plan "" "SELECT count(*) FROM t" "Custom Scan")" yes check "aggregate over the whole table sees the live set" \ "$(q 'SELECT count(*) FROM t;')" "$LIVE" # A specific deleted row is invisible, and its live neighbour visible, via the -# index fetch (which reaches the same bit test through a different path). +# index (which reaches the same bit test through a different path). check "a deleted row is invisible via the index" \ - "$(last 'SET enable_seqscan=off; SELECT count(*) FROM t WHERE id = 700;')" "0" + "$(last "$NO_COLUMNAR SET enable_seqscan=off; SELECT count(*) FROM t WHERE id = 700;")" "0" check "a live row is visible via the index" \ - "$(last 'SET enable_seqscan=off; SELECT count(*) FROM t WHERE id = 701;')" "1" + "$(last "$NO_COLUMNAR SET enable_seqscan=off; SELECT count(*) FROM t WHERE id = 701;")" "1" # The buffered (same-transaction) path: delete more inside a txn, then read it back. check "a row deleted in-transaction is invisible in the same transaction" \ "$(q 'BEGIN; DELETE FROM t WHERE id = 8; SELECT count(*) FROM t WHERE id = 8; ROLLBACK;' | grep -E '^[0-9]+$' | head -1)" "0" +# and the rollback really rolled back, so the arm above is not a delete that persisted +check "the rolled-back delete left the row in place" "$(q 'SELECT count(*) FROM t WHERE id = 8;')" "1" # A DELETE whose qual deletes another row of the same table (#1394). The inner DELETE's # scan start writes the outer statement's buffered marks, and its statement end writes diff --git a/test/pytest/test_maintenance_crash.py b/test/pytest/test_maintenance_crash.py index 97cdb0426..8fb17ee68 100644 --- a/test/pytest/test_maintenance_crash.py +++ b/test/pytest/test_maintenance_crash.py @@ -34,9 +34,13 @@ def _one(dsn, sql, setup=()): return f"ERROR: {str(exc).splitlines()[0]}" +BY_INDEX = ("SET enable_seqscan = off", "SET enable_bitmapscan = off", "SET pgcolumnar.enable_custom_scan = off") + + def _state(dsn): - by_index = _one(dsn, ROWS + " WHERE id BETWEEN 1 AND 10000", - ("SET enable_seqscan = off", "SET enable_bitmapscan = off")) + # the custom scan off too, and asserted by a premise: with only seqscan off this + # whole-table range planned as Custom Scan (PgColumnarScan) and read no index + by_index = _one(dsn, ROWS + " WHERE id BETWEEN 1 AND 10000", BY_INDEX) return f"{_one(dsn, ROWS)}|{_one(dsn, GROUPS)}|{by_index}" @@ -74,6 +78,14 @@ def test_a_crash_part_way_through_maintenance_loses_nothing(pgc_cluster, expect) with psycopg.connect(dsn, autocommit=True) as c: c.execute("CREATE EXTENSION IF NOT EXISTS pgcolumnar") + _fresh_table(dsn) + with psycopg.connect(dsn, autocommit=True) as c: + for st in BY_INDEX: + c.execute(st) + plan = [r[0] for r in c.execute("EXPLAIN (COSTS OFF) " + ROWS + " WHERE id BETWEEN 1 AND 10000")] + expect.text("yes" if any("Index Scan using" in line for line in plan) else "no", "yes", + "premise: the by-index reading is an Index Scan through mc_id") + for f in ("compact_rewrite('mc', 0.0)", "recluster('mc', 'v')", "compact('mc')", "vacuum('mc')"): name = f.split("(")[0] _fresh_table(dsn) diff --git a/test/pytest/test_maintenance_rollback.py b/test/pytest/test_maintenance_rollback.py index 9d4925209..0e4b97234 100644 --- a/test/pytest/test_maintenance_rollback.py +++ b/test/pytest/test_maintenance_rollback.py @@ -32,15 +32,25 @@ def _one(conn, sql): return f"ERROR: {str(exc).splitlines()[0]}" -def _by_index(conn): - with conn.cursor() as cur: - cur.execute("SET enable_seqscan = off") - cur.execute("SET enable_bitmapscan = off") - got = _one(conn, ROWS + " WHERE id BETWEEN 1 AND 10000") +BY_INDEX_SETS = ("enable_seqscan", "enable_bitmapscan", "pgcolumnar.enable_custom_scan") + + +def _by_index(conn, explain=False): + # the custom scan off too, and asserted by a premise: with only seqscan off this + # whole-table range planned as Custom Scan (PgColumnarScan) and read no index with conn.cursor() as cur: - cur.execute("RESET enable_seqscan") - cur.execute("RESET enable_bitmapscan") - return got + for g in BY_INDEX_SETS: + cur.execute(f"SET {g} = off") + try: + if explain: + with conn.cursor() as cur: + cur.execute("EXPLAIN (COSTS OFF) " + ROWS + " WHERE id BETWEEN 1 AND 10000") + return "yes" if any("Index Scan using" in r[0] for r in cur.fetchall()) else "no" + return _one(conn, ROWS + " WHERE id BETWEEN 1 AND 10000") + finally: + with conn.cursor() as cur: + for g in BY_INDEX_SETS: + cur.execute(f"RESET {g}") def _build_mr(conn): @@ -63,6 +73,7 @@ def test_a_maintenance_function_that_rolls_back_leaves_the_table_as_it_was(pgc_c rows0, groups0 = _one(pgc_conn, ROWS), _one(pgc_conn, GROUPS) expect.text(f"{rows0.split('/')[0]}|{groups0}", "5400|10/10000", "premise: the table holds the rows it was given") + expect.text(_by_index(pgc_conn, explain=True), "yes", "premise: the by-index reading is an Index Scan through mr_id") for f in funcs: name = f.split("(")[0] diff --git a/test/pytest/test_native_delete_visibility_paths.py b/test/pytest/test_native_delete_visibility_paths.py index 28d39f414..3e443f4ae 100644 --- a/test/pytest/test_native_delete_visibility_paths.py +++ b/test/pytest/test_native_delete_visibility_paths.py @@ -33,8 +33,7 @@ index-only Index Only Scan 4286 So this port forces the path each arm is named for and ASSERTS THE NODE before reading -the count. The shell suite is unchanged -- reported rather than edited, since this pull -request adds no bash check. +the count. The shell suite now does the same, with the same premise names (#1339). """ import psycopg @@ -85,7 +84,7 @@ def test_native_delete_visibility_paths(pgc_conn, expect): cur.execute(f"INSERT INTO t SELECT g, g FROM generate_series(1,{ROWS}) g") cur.execute("CREATE UNIQUE INDEX t_id ON t (id)") cur.execute("SELECT count(*) FROM t") - expect.num(cur.fetchone()[0], ROWS, f"premise: the table holds all {ROWS} rows") + expect.num(cur.fetchone()[0], ROWS, "premise: the table holds all 5000 rows") cur.execute("DELETE FROM t WHERE id % 7 = 0") cur.execute( "SELECT count(*) FROM pgcolumnar.storage s "