From bc2a186f291272f0454b6a9821038c6beac1fc49 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Fri, 16 Oct 2026 07:05:46 +0000 Subject: [PATCH 1/2] test: native_index_projection gets its pytest twin, and every oracle reads the index it names (#1341) The oracles comparing an index scan with a sequential scan set enable_seqscan=off, which does not disable the columnar custom scan. Measured: the late-column, expression and text-expression oracles, and the expression point lookup, ran as the custom scan on both sides, so a sequential scan was compared with itself; dropping w_expr before them left the shell green. The index side now turns the custom scan off, and each oracle asserts, as a premise, the index its own query reads. The premise reads the oracle query itself, fenced with OFFSET 0: without the fence the aggregate's ORDER BY sent the late-column oracle down a full scan of w_k12 with its predicate as a filter, two minutes at 60,000 rows. The md5-shape failure now records under the oracle's own name. test_native_index_projection.py carries all 41 checks by the shell's name in four tests, building the parallel indexes after the serial oracles as the shell does. Its oracle table joins the loop-reader guard's list. COMPLETE gains native_index_projection, TESTS.md section 163, cluster_tests 698 -> 702. Co-Authored-By: Claude Opus 5.5 --- test/native_index_projection.sh | 51 +++-- test/pytest/TESTS.md | 14 ++ test/pytest/expected_tests.txt | 4 +- test/pytest/test_compare_to_bash.py | 5 +- test/pytest/test_native_index_projection.py | 209 ++++++++++++++++++++ 5 files changed, 261 insertions(+), 22 deletions(-) create mode 100644 test/pytest/test_native_index_projection.py diff --git a/test/native_index_projection.sh b/test/native_index_projection.sh index 4fd1a79e2..b35242d03 100755 --- a/test/native_index_projection.sh +++ b/test/native_index_projection.sh @@ -78,12 +78,13 @@ check "an expression over a text column projects that column" \ # Correctness. An index built from under-projected data indexes unset slot values, so # each of these must find what a sequential scan finds. +# The custom scan off as well: measured, the expression lookup ran as a Custom Scan. check "plain index finds the row" \ - "$(qset 'SET enable_seqscan=off' 'SELECT k2 FROM w WHERE k = 12345')" "24690" + "$(qset 'SET enable_seqscan=off; SET pgcolumnar.enable_custom_scan=off' 'SELECT k2 FROM w WHERE k = 12345')" "24690" check "expression index finds the row" \ - "$(qset 'SET enable_seqscan=off' 'SELECT count(*) FROM w WHERE (k + k2) = 30000')" "1" + "$(qset 'SET enable_seqscan=off; SET pgcolumnar.enable_custom_scan=off' 'SELECT count(*) FROM w WHERE (k + k2) = 30000')" "1" check "partial index finds the row inside its predicate" \ - "$(qset 'SET enable_seqscan=off' 'SELECT count(*) FROM w WHERE k = 50000 AND k2 > 100')" "1" + "$(qset 'SET enable_seqscan=off; SET pgcolumnar.enable_custom_scan=off' 'SELECT count(*) FROM w WHERE k = 50000 AND k2 > 100')" "1" # The oracle, depending on nothing but PostgreSQL. Compare the FULL ordered result of a # forced index scan against a forced seq scan. @@ -96,26 +97,38 @@ check "partial index finds the row inside its predicate" \ # The emptiness half is `check_text` in `test/lib.sh` now (#418, #422), so this file no # longer carries its own version of it. The shape half stays here: an md5 is 32 hex # characters, and this file knows that where the shared helper only knows "not empty". -agree() { # $1 label, $2 predicate, $3 selected expression - local viaix viaseq - viaix=$(qset "SET enable_seqscan=off; SET enable_bitmapscan=off" \ - "SELECT md5(string_agg(t::text, ',' ORDER BY t)) - FROM (SELECT $3 AS t FROM w WHERE $2) s") - viaseq=$(qset "SET enable_indexscan=off; SET enable_bitmapscan=off; SET enable_indexonlyscan=off" \ - "SELECT md5(string_agg(t::text, ',' ORDER BY t)) - FROM (SELECT $3 AS t FROM w WHERE $2) s") +# THE INDEX SIDE MUST READ THE INDEX (#1341). enable_seqscan=off does not disable the +# columnar custom scan, and measured, the late-column, expression and text-expression +# oracles ran as Custom Scan on BOTH sides: a sequential scan compared with itself, +# whatever the index held. So the custom scan is off too, and each oracle names the +# index it must read and asserts the plan does. +IXOFF="SET enable_seqscan=off; SET enable_bitmapscan=off; SET pgcolumnar.enable_custom_scan=off" +# +# The premise reads the plan of the oracle query ITSELF, fenced with OFFSET 0. Without +# the fence the aggregate's ORDER BY asked for rows in key order, and the planner took +# a full scan of w_k12 with the late-column predicate as a filter: every row fetched +# through the index, two minutes at 60,000 rows and hours at this size. +agree() { # $1 label, $2 predicate, $3 selected expression, $4 the index it must read + local viaix viaseq ixplan oq + oq="SELECT md5(string_agg(t::text, ',' ORDER BY t)) FROM (SELECT $3 AS t FROM w WHERE $2 OFFSET 0) s" + ixplan="$(env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres -d "$PGC_DB" \ + -At -q -c "$IXOFF" -c "EXPLAIN (COSTS OFF) $oq" 2>/dev/null)" + check "premise: $1 -- the index side reads $4" \ + "$(grep -cE "Index (Only )?Scan using $4 on w" <<<"$ixplan")" "1" + viaix=$(qset "$IXOFF" "$oq") + viaseq=$(qset "SET enable_indexscan=off; SET enable_bitmapscan=off; SET enable_indexonlyscan=off" "$oq") if ! grep -qE '^[0-9a-f]{32}$' <<<"$viaix" || ! grep -qE '^[0-9a-f]{32}$' <<<"$viaseq"; then - check "$1 (both sides must be a real md5, not empty or an error)" \ - "index=[$viaix] seq=[$viaseq]" "two md5 hashes" + # under the oracle's own name, so a red here is a red of that check + check "$1" "index=[$viaix] seq=[$viaseq] (both sides must be a real md5)" "two md5 hashes" return fi check_text "$1" "$viaix" "$viaseq" } -agree "plain index agrees with a sequential scan" "k BETWEEN 1000 AND 9999" "k" -agree "late-column index agrees" "c18 > repeat('x',80)||'99000'" "k" -agree "expression index agrees" "(k + k2) BETWEEN 300 AND 30000" "k" -agree "partial index agrees on its subset" "k2 > 100 AND k < 5000" "k" -agree "text expression index agrees" "length(c1) = 83" "k" +agree "plain index agrees with a sequential scan" "k BETWEEN 1000 AND 9999" "k" "w_k(12)?" +agree "late-column index agrees" "c18 > repeat('x',80)||'99000'" "k" "w_c18" +agree "expression index agrees" "(k + k2) BETWEEN 300 AND 30000" "k" "w_expr" +agree "partial index agrees on its subset" "k2 > 100 AND k < 5000" "k" "w_part" +agree "text expression index agrees" "length(c1) = 83" "k" "w_len" # The PARALLEL build, which is the default path for any table of consequential size and # was the one left unprojected. @@ -178,7 +191,7 @@ usesix() { # $1 = query -> the index name the plan scans } check "the oracle below really reads the parallel-built index" \ "$(usesix 'SELECT k2 FROM w WHERE k2 BETWEEN 2000 AND 19998')" "w_par2" -agree "parallel-built index agrees with a sequential scan" "k2 BETWEEN 2000 AND 19998" "k2" +agree "parallel-built index agrees with a sequential scan" "k2 BETWEEN 2000 AND 19998" "k2" "w_par2" check "parallel-built index returns each row once" \ "$(qset 'SET enable_seqscan=off; SET enable_bitmapscan=off' \ 'SELECT count(*) FROM w WHERE k2 BETWEEN 2 AND 2000')" "1000" diff --git a/test/pytest/TESTS.md b/test/pytest/TESTS.md index 7642c77f4..baa90b2bf 100644 --- a/test/pytest/TESTS.md +++ b/test/pytest/TESTS.md @@ -208,6 +208,7 @@ behaviour, the source of that number is named. - [160. test_native_encdesc_golden.py: the encoding descriptor's wire layout](#160-test_native_encdesc_goldenpy-the-encoding-descriptors-wire-layout) - [161. test_native_parquet_schema.py: parquet_schema round trip](#161-test_native_parquet_schemapy-parquet_schema-round-trip) - [162. test_native_vecskip.py: per-vector skipping by the zone maps](#162-test_native_vecskippy-per-vector-skipping-by-the-zone-maps) +- [163. test_native_index_projection.py: CREATE INDEX projects only what it needs](#163-test_native_index_projectionpy-create-index-projects-only-what-it-needs) ## 1. How to read a test in here @@ -7627,3 +7628,16 @@ The pytest twin of `test/native_vecskip.sh` (ported for #1341), every check by t | test | what it holds | | --- | --- | | `test_per_vector_skipping` | the zone maps skip exactly the seven vectors outside the range without decoding them, on the scalar scan and the vectorized aggregate, and every range returns the heap's rows | + +## 163. test_native_index_projection.py: CREATE INDEX projects only what it needs + +The pytest twin of `test/native_index_projection.sh` (ported for #1341), every check by the shell's name. CREATE INDEX projects only the columns the index needs (#413), serial and parallel: each build's own DEBUG1 line says which branch ran and how many columns its reader decodes. The built indexes must then answer as a sequential scan does: point lookups through the index, and the full ordered result of an index scan against a sequential scan. The index side runs with the columnar scan off, and each oracle asserts the index its own query reads. Before that, three of the oracles ran as the columnar scan on both sides. + +### Every test + +| test | what it holds | +| --- | --- | +| `test_the_built_indexes_answer_as_a_sequential_scan_does` | each serial index finds its rows, and its full ordered result equals a sequential scan's, read through that index | +| `test_each_build_projects_only_what_it_needs` | every serial and parallel build projects only its key, expression and predicate columns | +| `test_the_parallel_built_index_agrees_and_holds_each_row_once` | the parallel-built index agrees with a sequential scan and holds each row once | +| `test_amcheck_finds_every_index_sound` | `bt_index_check` finds all eleven indexes sound | diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index dfeee9cfe..59387c959 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -787,4 +787,6 @@ guard_tests 438 # two tests, by collection over the cluster files on this tree. # cluster_tests 697 -> 698 for #1341's test_native_vecskip.py, the twin of native_vecskip.sh: # one test, by collection over the cluster files on this tree. -cluster_tests 698 +# cluster_tests 698 -> 702 for #1341's test_native_index_projection.py, the twin of native_index_projection.sh: +# four tests, by collection over the cluster files on this tree. +cluster_tests 702 diff --git a/test/pytest/test_compare_to_bash.py b/test/pytest/test_compare_to_bash.py index 1539e1c8a..78a88434e 100644 --- a/test/pytest/test_compare_to_bash.py +++ b/test/pytest/test_compare_to_bash.py @@ -132,6 +132,7 @@ "native_fetch_coalesce", "native_groupagg", "native_index_fetch_stripe_cost", + "native_index_projection", "native_join_vector_agg", "native_ownership", "native_parquet_dict_oob", @@ -409,8 +410,8 @@ def test_the_loop_reader_invents_nothing_in_this_corpus(expect): "came from") expect.text(", ".join(files), "build_refusal, differential, join_runtime_filter, " - "native_reclaim, native_repack, native_vacuum_race, native_vecskip, " - "sorted_pathkeys", + "native_index_projection, native_reclaim, native_repack, " + "native_vacuum_race, native_vecskip, sorted_pathkeys", "and it is these files, so another appearing is a diff a reviewer " "sees rather than a number that moved") diff --git a/test/pytest/test_native_index_projection.py b/test/pytest/test_native_index_projection.py new file mode 100644 index 000000000..d25cccce8 --- /dev/null +++ b/test/pytest/test_native_index_projection.py @@ -0,0 +1,209 @@ +"""CREATE INDEX projects only the columns the index needs, serial and parallel, and the +index it builds agrees with a sequential scan (#413, #1341). The pytest twin of +`test/native_index_projection.sh`, every check by the shell's name. + +A 20-column table and indexes on a key, two keys, a late column, an expression, a +partial predicate and a text expression: each build's own DEBUG1 line says which +branch ran and how many columns its reader decodes, which is the check a do-nothing fix +would fail. The parallel build, the default for any table of size, must project too. +Then correctness: point lookups through the index, and the FULL ordered result of an +index scan against a sequential scan. The index side runs with the columnar scan off +and each oracle asserts the index it reads: with only seqscan and bitmapscan off, the +late-column, expression and text-expression oracles ran as the columnar scan on both +sides, comparing a sequential scan with itself. amcheck closes it. +""" +import re + +import psycopg +import pytest + +ROWS = 300000 +IXOFF = ("SET enable_seqscan=off", "SET enable_bitmapscan=off", "SET pgcolumnar.enable_custom_scan=off") +SEQ = ("SET enable_indexscan=off", "SET enable_bitmapscan=off", "SET enable_indexonlyscan=off") +PAR = ("SET max_parallel_maintenance_workers=4", "SET min_parallel_table_scan_size='0'", + "SET maintenance_work_mem='256MB'") +INDEXES = ("w_k", "w_k12", "w_c18", "w_expr", "w_part", "w_len", "w_par", "w_par2", "w_pare", "w_parp", "w_ser") +LINE = re.compile(r'(parallel|serial) index build on "([a-z0-9_]+)" projecting (\d+) of (\d+)') + + +def _conn(cluster, db): + return psycopg.connect(cluster.dsn(dbname=db), autocommit=True) + + +def _build(conn, sets, ddl): + """-> 'branch n of m' from the build's DEBUG1 line, or '' when it printed none.""" + seen = [] + + def handler(diag): + seen.append(diag.message_primary or "") + + conn.add_notice_handler(handler) + try: + conn.execute("SET client_min_messages = debug1") + for s in sets: + conn.execute(s) + conn.execute(ddl) + finally: + conn.execute("RESET client_min_messages") + for g in ("max_parallel_maintenance_workers", "min_parallel_table_scan_size", "maintenance_work_mem"): + conn.execute(f"RESET {g}") + conn.remove_notice_handler(handler) + for msg in seen: + m = LINE.search(msg) + if m: + return f"{m.group(1)} {m.group(3)} of {m.group(4)}" + return "" + + +def _under(conn, sets, sql): + for s in sets: + conn.execute(s) + try: + return conn.execute(sql).fetchone()[0] + finally: + for g in ("enable_seqscan", "enable_bitmapscan", "enable_indexscan", "enable_indexonlyscan", + "pgcolumnar.enable_custom_scan"): + conn.execute(f"RESET {g}") + + +@pytest.fixture(scope="module") +def wide(pgc_cluster): + """-> (cluster, db, {index: 'branch n of m'}): the table and its SERIAL indexes. The + parallel ones come later, as in the shell, so the serial oracles cannot pick them.""" + db = "pgc_idxproj" + with psycopg.connect(pgc_cluster.dsn(), autocommit=True) as a: + a.execute(f"DROP DATABASE IF EXISTS {db} WITH (FORCE)") + a.execute(f"CREATE DATABASE {db}") + try: + with _conn(pgc_cluster, db) as c: + c.execute("CREATE EXTENSION pgcolumnar") + cols = ", ".join(["k int", "k2 int"] + [f"c{i} text" for i in range(1, 19)]) + vals = ", ".join(["g", "g*2"] + ["repeat('x',80)||g"] * 18) + c.execute(f"CREATE TABLE w ({cols}) USING pgcolumnar") + c.execute(f"INSERT INTO w SELECT {vals} FROM generate_series(1,{ROWS}) g") + built = {} + for ix, ddl in (("w_k", "CREATE INDEX w_k ON w (k)"), ("w_k12", "CREATE INDEX w_k12 ON w (k, k2)"), + ("w_c18", "CREATE INDEX w_c18 ON w (c18)"), + ("w_expr", "CREATE INDEX w_expr ON w ((k + k2))"), + ("w_part", "CREATE INDEX w_part ON w (k) WHERE k2 > 100"), + ("w_len", "CREATE INDEX w_len ON w ((length(c1)))")): + built[ix] = _build(c, (), ddl) + yield pgc_cluster, db, built + finally: + with psycopg.connect(pgc_cluster.dsn(), autocommit=True) as a: + a.execute(f"DROP DATABASE IF EXISTS {db} WITH (FORCE)") + + +@pytest.fixture(scope="module") +def par(wide): + """-> wide, after the parallel builds and the serial c17 build have been added.""" + cluster, db, built = wide + with _conn(cluster, db) as c: + for ix, ddl in (("w_par", "CREATE INDEX w_par ON w (k)"), ("w_par2", "CREATE INDEX w_par2 ON w (k2)"), + ("w_pare", "CREATE INDEX w_pare ON w ((k + k2))"), + ("w_parp", "CREATE INDEX w_parp ON w (k) WHERE k2 > 100")): + built[ix] = _build(c, PAR, ddl) + built["w_ser"] = _build(c, ("SET max_parallel_maintenance_workers=0",), "CREATE INDEX w_ser ON w (c17)") + return wide + + +def _proj(built, ix): + return built[ix].split(" ", 1)[1] if " " in built[ix] else built[ix] + + +def test_the_built_indexes_answer_as_a_sequential_scan_does(wide, expect): + cluster, db, _ = wide + lookup = ("SET enable_seqscan=off", "SET pgcolumnar.enable_custom_scan=off") + with _conn(cluster, db) as c: + expect.num(_under(c, lookup, "SELECT k2 FROM w WHERE k = 12345"), 24690, "plain index finds the row") + expect.num(_under(c, lookup, "SELECT count(*) FROM w WHERE (k + k2) = 30000"), 1, "expression index finds the row") + expect.num(_under(c, lookup, "SELECT count(*) FROM w WHERE k = 50000 AND k2 > 100"), 1, + "partial index finds the row inside its predicate") + for label, pred, sel, ix in ( + ("plain index agrees with a sequential scan", "k BETWEEN 1000 AND 9999", "k", "w_k(12)?"), + ("late-column index agrees", "c18 > repeat('x',80)||'99000'", "k", "w_c18"), + ("expression index agrees", "(k + k2) BETWEEN 300 AND 30000", "k", "w_expr"), + ("partial index agrees on its subset", "k2 > 100 AND k < 5000", "k", "w_part"), + ("text expression index agrees", "length(c1) = 83", "k", "w_len"), + ): + # the plan of the oracle query itself, fenced so the aggregate's ORDER BY + # cannot send it down a full scan of another index + agg = (f"SELECT md5(string_agg(t::text, ',' ORDER BY t)) " + f"FROM (SELECT {sel} AS t FROM w WHERE {pred} OFFSET 0) s") + for s in IXOFF: + c.execute(s) + plan = "\n".join(r[0] for r in c.execute(f"EXPLAIN (COSTS OFF) {agg}")) + for g in ("enable_seqscan", "enable_bitmapscan", "pgcolumnar.enable_custom_scan"): + c.execute(f"RESET {g}") + expect.num(len(re.findall(rf"Index (Only )?Scan using {ix} on w", plan)), 1, + f"premise: {label} -- the index side reads {ix}") + viaix, viaseq = _under(c, IXOFF, agg), _under(c, SEQ, agg) + # both sides a real md5: two empty results compare equal and prove nothing (#418) + if not all(isinstance(h, str) and re.fullmatch(r"[0-9a-f]{32}", h) for h in (viaix, viaseq)): + expect.text(f"index=[{viaix}] seq=[{viaseq}] (both sides must be a real md5)", "two md5 hashes", label) + continue + expect.text(viaix, viaseq, label) + + +def test_each_build_projects_only_what_it_needs(par, expect): + cluster, db, built = par + with _conn(cluster, db) as c: + expect.num(c.execute("SELECT count(*) FROM w").fetchone()[0], ROWS, "fixture rows") + expect.num(c.execute("SELECT count(*) FROM pg_attribute WHERE attrelid='w'::regclass AND attnum>0") + .fetchone()[0], 20, "fixture is wide") + expect.text(_proj(built, "w_k"), "1 of 20", "plain index projects one column of twenty") + expect.text(_proj(built, "w_k12"), "2 of 20", "two-column index projects two") + expect.text(_proj(built, "w_c18"), "1 of 20", "an index on a late column projects one, not everything before it") + expect.text(_proj(built, "w_expr"), "2 of 20", "an expression index projects its expression's columns") + expect.text(_proj(built, "w_part"), "2 of 20", "a partial index projects the predicate's columns too") + expect.text(_proj(built, "w_len"), "1 of 20", "an expression over a text column projects that column") + # the premise first: if core declines to go parallel, the next checks read the serial branch + expect.text(built["w_par"].split(" ")[0], "parallel", + "forcing parallel maintenance workers does reach the parallel branch") + expect.text(built["w_par2"], "parallel 1 of 20", "a parallel build projects one column of twenty, not all twenty") + expect.text(built["w_pare"], "parallel 2 of 20", "a parallel expression build projects its expression's columns") + expect.text(built["w_parp"], "parallel 2 of 20", "a parallel partial build projects the predicate's columns too") + expect.text(built["w_ser"].split(" ")[0], "serial", "the serial branch is still reached when workers are refused") + + +def test_the_parallel_built_index_agrees_and_holds_each_row_once(par, expect): + cluster, db, _ = par + with _conn(cluster, db) as c: + # premise first: the query names a column, not an index + for s in ("SET enable_seqscan=off", "SET enable_bitmapscan=off"): + c.execute(s) + plan = "\n".join(r[0] for r in c.execute("EXPLAIN (COSTS OFF) SELECT k2 FROM w WHERE k2 BETWEEN 2000 AND 19998")) + for g in ("enable_seqscan", "enable_bitmapscan"): + c.execute(f"RESET {g}") + expect.text((re.findall(r"using ([a-z0-9_]+)", plan) or [""])[0], "w_par2", + "the oracle below really reads the parallel-built index") + agg = ("SELECT md5(string_agg(t::text, ',' ORDER BY t)) " + "FROM (SELECT k2 AS t FROM w WHERE k2 BETWEEN 2000 AND 19998 OFFSET 0) s") + for st in IXOFF: + c.execute(st) + plan = "\n".join(r[0] for r in c.execute(f"EXPLAIN (COSTS OFF) {agg}")) + for g in ("enable_seqscan", "enable_bitmapscan", "pgcolumnar.enable_custom_scan"): + c.execute(f"RESET {g}") + expect.num(len(re.findall(r"Index (Only )?Scan using w_par2 on w", plan)), 1, + "premise: parallel-built index agrees with a sequential scan -- the index side reads w_par2") + expect.text(_under(c, IXOFF, agg), _under(c, SEQ, agg), "parallel-built index agrees with a sequential scan") + expect.num(_under(c, ("SET enable_seqscan=off", "SET enable_bitmapscan=off"), + "SELECT count(*) FROM w WHERE k2 BETWEEN 2 AND 2000"), 1000, + "parallel-built index returns each row once") + + +def test_amcheck_finds_every_index_sound(par, expect): + cluster, db, _ = par + with _conn(cluster, db) as c: + try: + c.execute("CREATE EXTENSION IF NOT EXISTS amcheck") + except psycopg.Error as exc: + expect.cannot_run("MISSING_DEPENDENCY", f"amcheck is not installed on this build ({exc.diag.message_primary})") + return + for ix in INDEXES: + try: + c.execute(f"SELECT bt_index_check('{ix}'::regclass)") + verdict = "ok" + except psycopg.Error: + verdict = "bad" + expect.text(verdict, "ok", f"bt_index_check({ix})") From a08464a2f1ddc05320bc367dfc94f048c4c47435 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Fri, 16 Oct 2026 14:05:37 +0000 Subject: [PATCH 2/2] test: native_index_projection seeds the ledger, 7 of 41 arms dated by two projection mutations (#1341) The suite had no rows. suites_not_covered 203 -> 202; census 4048 rows / 2923 never / 1125 dated. Co-Authored-By: Claude Opus 5.5 --- test/check_ledger.tsv | 41 ++++++++++++++++++++++++++++++++++++ test/check_ledger_budget.txt | 19 +++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index c1446c643..10a8cd6b2 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2570,6 +2570,47 @@ native_groupagg native_groupagg toggle sum signed-zero (-0 preserved) [node fire native_groupagg native_groupagg while a matching-type predicate still gets one 15;16;17;18;19 never - native_groupagg native_groupagg with no time bound at all, a truncating key is bounded from the zone maps 15;16;17;18;19 never - native_groupagg native_groupagg with only a lower time bound, a truncating key is still bounded 15;16;17;18;19 never - +native_index_projection native_index_projection a parallel build projects one column of twenty, not all twenty 15;16;17;18;19 never - +native_index_projection native_index_projection a parallel expression build projects its expression's columns 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Expressions' columns +native_index_projection native_index_projection a parallel partial build projects the predicate's columns too 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Predicate's columns +native_index_projection native_index_projection a partial index projects the predicate's columns too 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Predicate's columns +native_index_projection native_index_projection an expression index projects its expression's columns 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Expressions' columns +native_index_projection native_index_projection an expression over a text column projects that column 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Expressions' columns +native_index_projection native_index_projection an index on a late column projects one, not everything before it 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_c18) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_expr) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_k) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_k12) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_len) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_par) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_par2) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_pare) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_parp) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_part) 15;16;17;18;19 never - +native_index_projection native_index_projection bt_index_check(w_ser) 15;16;17;18;19 never - +native_index_projection native_index_projection expression index agrees 15;16;17;18;19 never - +native_index_projection native_index_projection expression index finds the row 15;16;17;18;19 never - +native_index_projection native_index_projection fixture is wide 15;16;17;18;19 never - +native_index_projection native_index_projection fixture rows 15;16;17;18;19 never - +native_index_projection native_index_projection forcing parallel maintenance workers does reach the parallel branch 15;16;17;18;19 never - +native_index_projection native_index_projection late-column index agrees 15;16;17;18;19 never - +native_index_projection native_index_projection parallel-built index agrees with a sequential scan 15;16;17;18;19 never - +native_index_projection native_index_projection parallel-built index returns each row once 15;16;17;18;19 never - +native_index_projection native_index_projection partial index agrees on its subset 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Predicate's columns +native_index_projection native_index_projection partial index finds the row inside its predicate 15;16;17;18;19 2026-10-16 columnar_tableam.c: the index build's projection omits ii_Predicate's columns +native_index_projection native_index_projection plain index agrees with a sequential scan 15;16;17;18;19 never - +native_index_projection native_index_projection plain index finds the row 15;16;17;18;19 never - +native_index_projection native_index_projection plain index projects one column of twenty 15;16;17;18;19 never - +native_index_projection native_index_projection premise: expression index agrees -- the index side reads w_expr 15;16;17;18;19 never - +native_index_projection native_index_projection premise: late-column index agrees -- the index side reads w_c18 15;16;17;18;19 never - +native_index_projection native_index_projection premise: parallel-built index agrees with a sequential scan -- the index side reads w_par2 15;16;17;18;19 never - +native_index_projection native_index_projection premise: partial index agrees on its subset -- the index side reads w_part 15;16;17;18;19 never - +native_index_projection native_index_projection premise: plain index agrees with a sequential scan -- the index side reads w_k(12)? 15;16;17;18;19 never - +native_index_projection native_index_projection premise: text expression index agrees -- the index side reads w_len 15;16;17;18;19 never - +native_index_projection native_index_projection text expression index agrees 15;16;17;18;19 never - +native_index_projection native_index_projection the oracle below really reads the parallel-built index 15;16;17;18;19 never - +native_index_projection native_index_projection the serial branch is still reached when workers are refused 15;16;17;18;19 never - +native_index_projection native_index_projection two-column index projects two 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter 3-table answer equals filter-off 15;16;17;18;19 2026-09-23 the runtime bloom probe's verdict inverted, so the filter rejects what matches native_join_runtime_filter native_join_runtime_filter 3-table join order matches filter-off 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter 3-table plan has coordinator 15;16;17;18;19 never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index b9e23fe4d..37c628816 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -15,7 +15,7 @@ # # Adding a check to a suite that is already covered does not move it, which is # what makes it safe to bound. -suites_not_covered 203 +suites_not_covered 202 # # checks_never_observed_red -- A CENSUS. NOT a ceiling, and it must not become # one. @@ -3241,4 +3241,19 @@ suites_not_covered 203 # never 2894 - 5 = 2889 # dated 1113 + 5 = 1118 # 2889 + 1118 == 4007 -checks_never_observed_red 2889 +# +# #1341, native_index_projection seeded, with its twin (OffgridwithJD): 2889 -> 2923, +# and suites_not_covered 203 -> 202. The suite had no rows. It enters with all 41 +# checks from five-major runs (41/41 on each of 15-19). 7 are dated by two mutations of +# the index build's projection: ii_Expressions' columns omitted (the three expression +# projection arms) and ii_Predicate's omitted (the partial projections, its lookup and +# its oracle). Thirty-four arrive never: the six new oracle premises, which a harness +# change reddens (an index dropped before the oracles), the fixture and the serial and +# parallel branch arms, the plain and late-column projections, the oracles a projection +# mutation cannot reach, and the eleven amcheck arms. +# +# ledger rows 4007 + 41 = 4048 +# never 2889 + 34 = 2923 +# dated 1118 + 7 = 1125 +# 2923 + 1125 == 4048 +checks_never_observed_red 2923