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
23 changes: 21 additions & 2 deletions test/audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,26 @@ check "add-column aggregate" \
"$(q "SELECT sum(c) || '/' || count(d) || '/' || count(b) FROM ac;")" "218/6/1"

# index scan over an added column
#
# FORCED THROUGH THE INDEX, AND ASSERTED (#1339). enable_seqscan alone leaves the
# columnar custom scan on, so this and "create index rows indexed once" below could
# be answered without reading the index they name. Both lookups now turn the
# custom scan and bitmap scans off (a bitmap scan would also collapse a duplicate
# index entry), and assert an Index Scan or Index Only Scan through their index.
IDXONLY="SET enable_seqscan=off; SET enable_bitmapscan=off; SET pgcolumnar.enable_custom_scan=off;"
reads_index() { # reads_index INDEX QUERY -> yes, or the plan's scan line
local plan
plan="$(q "$IDXONLY EXPLAIN (COSTS OFF) $2")"
case "$plan" in
*"Index Scan using $1 "*|*"Index Only Scan using $1 "*) echo yes ;;
*) echo "no: $(grep -m1 'Scan' <<<"$plan" | sed 's/^ *//')" ;;
esac
}
q "CREATE INDEX ac_c_idx ON ac(c);" >/dev/null
check "premise: the add-column lookup reads ac_c_idx" \
"$(reads_index ac_c_idx "SELECT count(*) FROM ac WHERE c=42")" "yes"
check "add-column index scan" \
"$(q "SET enable_seqscan=off; SELECT count(*) FROM ac WHERE c=42;")" "5"
"$(q "$IDXONLY SELECT count(*) FROM ac WHERE c=42;" | tail -1)" "5"
q "DROP TABLE ac;" >/dev/null

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -369,8 +386,10 @@ LEAKS="$(run_pg "grep -c 'resource was not closed' '$LOGFILE'" || true)"
check "create index no relation leak" "$LEAKS" "0"

# and every row is indexed exactly once (no duplicate TIDs from a per-worker rescan)
check "premise: the indexed-once count reads idxleak_a_idx" \
"$(reads_index idxleak_a_idx "SELECT count(*) FROM idxleak WHERE a>0")" "yes"
check "create index rows indexed once" \
"$(q "SET enable_seqscan=off; SELECT count(*) FROM idxleak WHERE a>0;")" "50000"
"$(q "$IDXONLY SELECT count(*) FROM idxleak WHERE a>0;" | tail -1)" "50000"
q "DROP TABLE idxleak;" >/dev/null

# ---------------------------------------------------------------------------
Expand Down
66 changes: 61 additions & 5 deletions test/check_ledger.tsv

Large diffs are not rendered by default.

55 changes: 53 additions & 2 deletions test/check_ledger_budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 193
suites_not_covered 190
#
# checks_never_observed_red -- A CENSUS. NOT a ceiling, and it must not become
# one.
Expand Down Expand Up @@ -3467,4 +3467,55 @@ suites_not_covered 193
# never 3019 + 2 - 4 = 3017
# dated 1311 + 4 = 1315
# 3017 + 1315 == 4332
checks_never_observed_red 3017
#
# #1341, native_fetch_bigcap seeded, with its twin (OffgridwithJD): 3017 -> 3031,
# and suites_not_covered 193 -> 192. The suite had no rows. It enters with all 15
# checks from five-major runs (15/15 on each of 15-19). One is dated: the right-rows
# arm, by the deferred decode reading tag as NULL on every 7th row number. Fourteen
# arrive never: eleven premises; the payload length; and the two #433 ratios, which
# the cache missing every lookup and the cap dropping every column cannot move,
# because each re-reads only the projected columns.
#
# ledger rows 4332 + 15 = 4347
# never 3017 + 14 = 3031
# dated 1315 + 1 = 1316
# 3031 + 1316 == 4347
#
# #1339, partition_rows' primary-key arm reads the key it names (OffgridwithJD):
# 3031 -> 3029. One premise is new, never. With pgcolumnar_index_fetch_tuple
# finding no row, five arms are red: the three duplicate-key refusals entered never
# and are now dated, and the primary-key and races arms gain it in their history.
#
# ledger rows 4347 + 1 = 4348
# never 3031 + 1 - 3 = 3029
# dated 1316 + 3 = 1319
# 3029 + 1319 == 4348
#
# #1341, sorted_projection seeded, with its twin (OffgridwithJD): 3029 -> 3053, and
# suites_not_covered 192 -> 191. The suite had no rows. It enters with all 29 checks
# from five-major runs (29/29 on each of 15-19). 5 are dated by three mutations:
# the post-sort reindex removed (both index lookups), the compaction not sorting
# (the pruning arm and the json refusal, which the sort raises), and vacuum_sorted's
# columnar check removed. Twenty-four arrive never: the ten heap-oracle comparisons
# and the multi-key and post-delete counts, which no rewrite mutation here moves;
# the oracle and index premises; the pre-sort control; idempotence; and the
# unknown-column and no-sort-key refusals.
#
# ledger rows 4348 + 29 = 4377
# never 3029 + 24 = 3053
# dated 1319 + 5 = 1324
# 3053 + 1324 == 4377
#
# #1341, native_ios seeded, with its twin (OffgridwithJD): 3053 -> 3058, and
# suites_not_covered 191 -> 190. The suite had no rows. It enters with all 11 checks
# from five-major runs (11/11 on each of 15-19). 6 are dated by four mutations: a
# delete not clearing the VM bit, VACUUM setting no VM bits, VACUUM not recording
# relallvisible, and relallvisible overshooting the relation by the synthetic block
# count. Five arrive never: the row count, the plan choice, the all-visible oracle
# match, and two premises.
#
# ledger rows 4377 + 11 = 4388
# never 3053 + 5 = 3058
# dated 1324 + 6 = 1330
# 3058 + 1330 == 4388
checks_never_observed_red 3058
14 changes: 13 additions & 1 deletion test/native_fetch_bigcap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ CAP=$((32 * 1024 * 1024)) # COLUMNAR_FETCH_CACHE_MAX_BYTES, src/columnar.h
# by its own model, and then nothing measures the fetch path at all. So the index
# path is forced, and every measured query asserts it got one. Whether the
# planner should choose it is #434, and that is a separate suite.
#
# pgcolumnar.enable_custom_scan=off as well: enable_seqscan alone does not turn
# the columnar custom scan off (#1339). Every measured query asserts its plan, so
# a planner change cannot quietly take the arms off the fetch path.
FORCE_INDEX="SET max_parallel_workers_per_gather=0; SET enable_seqscan=off;
SET enable_bitmapscan=off; SET pgcolumnar.enable_index_fetch_penalty=off;"
SET enable_bitmapscan=off; SET pgcolumnar.enable_index_fetch_penalty=off;
SET pgcolumnar.enable_custom_scan=off;"

# ---------------------------------------------------------------------------
# The fixture, whose premise is its ON-DISK SIZE.
Expand Down Expand Up @@ -144,6 +149,11 @@ nbufs() { # $1 = number of rows, projecting one narrow column
} } END { print t + 0 }'
}

# The narrow and medium projections had no plan premise of their own; only the
# payload query's plan was asserted (#1339). They plan as Index Scans today, and
# these keep a planner change from turning their ratios into scan-against-scan.
check_num "premise: the narrow projection is fetched through an index scan" \
"$(pgc_uses_row_fetch "$FORCE_INDEX" "SELECT sum(tag) FROM bigcap WHERE id BETWEEN 1 AND 10" | grep -c yes)" "1"
N1=$(nbufs 1)
N10=$(nbufs 10)
echo "-- narrow projection buffers: 1 row = ${N1}, 10 rows = ${N10}"
Expand Down Expand Up @@ -172,6 +182,8 @@ mbufs() { # $1 = number of rows, projecting three medium columns
} } END { print t + 0 }'
}

check_num "premise: the medium projection is fetched through an index scan" \
"$(pgc_uses_row_fetch "$FORCE_INDEX" "SELECT count(m1 || m2 || m3) FROM bigcap WHERE id BETWEEN 1 AND 10" | grep -c yes)" "1"
M1=$(mbufs 1)
M10=$(mbufs 10)
echo "-- three medium columns from an oversized group: 1 row = ${M1}, 10 rows = ${M10}"
Expand Down
14 changes: 12 additions & 2 deletions test/partition_rows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@ check "an UPDATE moving 49 rows to the other partition succeeds, as on heap" \
"$(both "UPDATE %p SET id = id + 1000 WHERE id < 50")" "ok|ok"
check "and each partition holds what heap's does" "$(answers "$SPLIT")" "950/49|950/49"
check "and the parent holds the same rows as heap's" "$(same "$CONTENT")" "same"
IDX="SET enable_seqscan = off; SET enable_bitmapscan = off;"
# The custom scan off too, and a premise that the columnar lookup reads the key:
# enable_seqscan alone leaves the columnar custom scan on. Measured (#1339): with
# the index fetch finding no row, this arm stayed green, because the columnar
# parent was answered by its partitions' custom scans, not by the primary key.
IDX="SET enable_seqscan = off; SET enable_bitmapscan = off; SET pgcolumnar.enable_custom_scan = off;"
# It plans as an Index Only Scan (it reads only id), which on a columnar table still
# asks each row whether it is live; the premise accepts either index node.
KEY_SQL="SELECT count(*) FILTER (WHERE id = 1001) || '/' || count(*) FILTER (WHERE id = 1) FROM %p WHERE id IN (1, 1001)"
check "premise: the columnar key lookup reads the primary key" \
"$(case "$(q "$IDX EXPLAIN (COSTS OFF) ${KEY_SQL//%p/pr_pgcolumnar}")" in
*"Index Scan using"*|*"Index Only Scan using"*) echo yes ;; *) echo no ;; esac)" yes
check "the primary key finds a moved row at its new key, and not at its old one, as on heap" \
"$(answers "$IDX SELECT count(*) FILTER (WHERE id = 1001) || '/' || count(*) FILTER (WHERE id = 1) FROM %p WHERE id IN (1, 1001)")" \
"$(answers "$IDX $KEY_SQL")" \
"1/0|1/0"

# ---- the key on the parent ----------------------------------------------------------
Expand Down
37 changes: 37 additions & 0 deletions test/pytest/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ behaviour, the source of that number is named.
- [170. test_native_parquet_flba.py: uuid and DECIMAL read from fixed-length byte arrays](#170-test_native_parquet_flbapy-uuid-and-decimal-read-from-fixed-length-byte-arrays)
- [171. test_native_parquet_multifile.py: directories and globs read as one relation](#171-test_native_parquet_multifilepy-directories-and-globs-read-as-one-relation)
- [172. test_native_index.py: index lookups and unique enforcement fetch by row number](#172-test_native_indexpy-index-lookups-and-unique-enforcement-fetch-by-row-number)
- [173. test_native_fetch_bigcap.py: fetching from a row group above the cache cap](#173-test_native_fetch_bigcappy-fetching-from-a-row-group-above-the-cache-cap)
- [174. test_sorted_projection.py: vacuum_sorted reorders storage and changes no answer](#174-test_sorted_projectionpy-vacuum_sorted-reorders-storage-and-changes-no-answer)
- [175. test_native_ios.py: an index-only scan skips the fetch on all-visible row groups](#175-test_native_iospy-an-index-only-scan-skips-the-fetch-on-all-visible-row-groups)

## 1. How to read a test in here

Expand Down Expand Up @@ -7773,3 +7776,37 @@ The pytest twin of `test/native_index.sh` (ported for #1341), every check by the
| --- | --- |
| `test_index_lookups_match_the_heap` | four lookups plan as Index Scans and match the heap, and a duplicate primary key is refused with the row count unchanged |
| `test_a_standalone_unique_index_enforces` | a standalone unique index refuses a duplicate and admits a new key |

## 173. test_native_fetch_bigcap.py: fetching from a row group above the cache cap

The pytest twin of `test/native_fetch_bigcap.sh` (ported for #1341), every check by the shell's name. Above the 32 MB fetch-cache cap, the entry used to be dropped whole after every fetch, so each fetched row re-read the whole group (#433). The measure is buffers, a count rather than a time. The fixture's premise is its on-disk size, read back from `pgcolumnar.row_group`. Every measured query is forced onto the index path with the custom scan off, and asserts its plan. A payload wider than the cap re-decodes per fetch by design (#359). A narrow projection, and columns that fit, must still be cached.

### Every test

| test | what it holds |
| --- | --- |
| `test_a_group_above_the_cap_is_read_once_for_what_fits` | the fixture has a group above the cap, every measured query is an index fetch, and ten rows of a narrow or a fitting projection cost under three times one row |
| `test_caching_changes_no_row` | the first ten rows equal the same rows read in key order, and every payload is full length |

## 174. test_sorted_projection.py: vacuum_sorted reorders storage and changes no answer

The pytest twin of `test/sorted_projection.sh` (ported for #1341), every check by the shell's name. `pgcolumnar.vacuum_sorted()` rewrites a columnar table physically sorted on a key. It must never change an answer: every query shape agrees with a heap oracle before and after, order-independently, and in order where the query orders. It must also reorder storage for real, so a narrow range on the sort key skips vectors it could not skip before. The rest covers a multi-column key, the reclaim of deleted rows with the index rebuilt, idempotence, and argument validation. Each index lookup is forced with the custom scan off and asserts that it reads the index. Each refusal must be the function's own, identified by its SQLSTATE.

### Every test

| test | what it holds |
| --- | --- |
| `test_the_sorted_rewrite_changes_no_answer_and_really_reorders` | five query shapes match the heap before and after the sort, the ordered oracle is order-sensitive, and the sort makes the range skip vectors |
| `test_a_multi_key_sort_reclaims_and_rebuilds_the_index` | a two-column sort matches, deletes are reclaimed, the rebuilt index finds and hides the right rows, and a second sort changes nothing |
| `test_vacuum_sorted_refuses_what_it_cannot_sort` | a heap table, an unknown column, no sort key and a json key are each refused with their own SQLSTATE |

## 175. test_native_ios.py: an index-only scan skips the fetch on all-visible row groups

The pytest twin of `test/native_ios.sh` (ported for #1341), every check by the shell's name. Once VACUUM has marked a native table's all-visible row groups in the visibility map, an index-only scan over an all-visible range skips the fetch (Heap Fetches: 0) and returns a heap mirror's rows. VACUUM also records the all-visible pages in `pg_class` (#507). They must be in pages, not the VM's synthetic blocks, because core prices an index-only scan by `relallvisible / relpages`. A delete clears the VM bit, so the scan falls back to the fetch there and never returns a deleted row.

### Every test

| test | what it holds |
| --- | --- |
| `test_an_all_visible_range_needs_no_fetch_and_vacuum_records_it` | the index-only scan is chosen, fetches nothing, matches the heap, and VACUUM records a page count no larger than the relation and most of it, as the heap's is |
| `test_a_delete_clears_the_bit_and_is_never_returned` | after a delete the scan matches the heap and fetches where the bit was cleared |
8 changes: 7 additions & 1 deletion test/pytest/expected_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -807,4 +807,10 @@ guard_tests 438
# three tests, by collection over the cluster files on this tree.
# cluster_tests 740 -> 742 for #1341's test_native_index.py, the twin of native_index.sh:
# two tests, by collection over the cluster files on this tree.
cluster_tests 742
# cluster_tests 742 -> 744 for #1341's test_native_fetch_bigcap.py, the twin of native_fetch_bigcap.sh:
# two tests, by collection over the cluster files on this tree.
# cluster_tests 744 -> 747 for #1341's test_sorted_projection.py, the twin of sorted_projection.sh:
# three tests, by collection over the cluster files on this tree.
# cluster_tests 747 -> 749 for #1341's test_native_ios.py, the twin of native_ios.sh:
# two tests, by collection over the cluster files on this tree.
cluster_tests 749
6 changes: 5 additions & 1 deletion test/pytest/test_compare_to_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"native_dict_underfill",
"native_dml",
"native_encdesc_golden",
"native_fetch_bigcap",
"native_fetch_cache",
"native_fetch_coalesce",
"native_fetch_position",
Expand All @@ -138,6 +139,7 @@
"native_index",
"native_index_fetch_stripe_cost",
"native_index_projection",
"native_ios",
"native_join_vector_agg",
"native_ownership",
"native_parquet_dict_oob",
Expand Down Expand Up @@ -187,6 +189,7 @@
"serializable",
"settings_reference",
"sorted_pathkeys",
"sorted_projection",
"statement_self_visibility",
"stats_privilege",
"storage_row_repair",
Expand Down Expand Up @@ -420,7 +423,8 @@ def test_the_loop_reader_invents_nothing_in_this_corpus(expect):
expect.text(", ".join(files),
"build_refusal, differential, join_runtime_filter, native_groupagg_batch, "
"native_index_projection, native_parquet_pushdown, native_reclaim, native_repack, "
"native_saop_pushdown, native_vacuum_race, native_vecskip, sorted_pathkeys",
"native_saop_pushdown, native_vacuum_race, native_vecskip, sorted_pathkeys, "
"sorted_projection",
"and it is these files, so another appearing is a diff a reviewer "
"sees rather than a number that moved")

Expand Down
Loading
Loading