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
87 changes: 87 additions & 0 deletions test/check_ledger.tsv

Large diffs are not rendered by default.

39 changes: 37 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 201
suites_not_covered 199
#
# checks_never_observed_red -- A CENSUS. NOT a ceiling, and it must not become
# one.
Expand Down Expand Up @@ -3285,4 +3285,39 @@ suites_not_covered 201
# never 2941 + 1 = 2942
# dated 1160 + 0 = 1160
# 2942 + 1160 == 4102
checks_never_observed_red 2942
#
# #1341, native_groupagg_batch seeded, with its twin (OffgridwithJD): 2942 -> 2980,
# and suites_not_covered 201 -> 200. The suite had no rows. It enters with all 69
# checks from five-major runs (69/69 on each of 15-19). 31 are dated by twelve
# mutations of the grouped fold: the fold never called, float8 keys compared by
# bits, the delete mask ignored, a skipped vector's slots not consumed, no scan key
# applied, the by-value, exactness and expression-key gates dropped, min and max
# admitted, the partial node without its shared counter, ANALYZE printing the
# prediction, and the grouped path refusing a text key. Thirty-eight arrive never:
# sixteen premises and the two float8 fixture facts; the eight "grouped node is
# planned" arms and the two parallel plan arms, which no fold mutation reaches;
# seven heap-mirror oracles that none of these mutations moves; the <> count, read
# by an ungrouped count(*); and the add-column prediction and answer.
#
# ledger rows 4102 + 69 = 4171
# never 2942 + 38 = 2980
# dated 1160 + 31 = 1191
# 2980 + 1191 == 4171
#
# #1341, native_fetch_projection seeded, with its twin (OffgridwithJD): 2980 -> 2990,
# and suites_not_covered 200 -> 199. The suite had no rows. It enters with all 18
# checks from five-major runs (18/18 on each of 15-19). 8 are dated by seven
# mutations: reconstruct's uncovered set missing the last column, read_projection
# and reconstruct ignoring liveness, a full decode planted beside the liveness
# check, index deletion asking each row whether it is live and decoding each row,
# and an unguarded needed-set test. Ten arrive never: seven premises; the fixture's
# column count and the opening row count; and the two all-covering-projection
# arms, which an empty set read as "every column" cannot redden, because decoding
# everything still answers right (measured: the old convention restored leaves
# both green).
#
# ledger rows 4171 + 18 = 4189
# never 2980 + 10 = 2990
# dated 1191 + 8 = 1199
# 2990 + 1199 == 4189
checks_never_observed_red 2990
47 changes: 37 additions & 10 deletions test/native_fetch_projection.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ psql_run "DROP TABLE IF EXISTS fp_w;
CREATE TABLE fp_w (id int$cols) USING pgcolumnar;
INSERT INTO fp_w SELECT g$sel FROM generate_series(1, $ROWS) g;" >/dev/null

check "the wide fixture has $((NCOLS + 1)) columns" \
# A literal name, so the ledger and the twin's grader can read it. The
# all-covering projection below names c1..c40, so NCOLS is 40 in practice.
check "the wide fixture has 41 columns" \
"$(q "SELECT count(*) FROM information_schema.columns
WHERE table_name = 'fp_w';")" "$((NCOLS + 1))"

Expand Down Expand Up @@ -139,6 +141,20 @@ check "and its values are right with nothing decoded from the base" \
# every check above while giving back what the change was for.
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/src"

# Every source arm below reads CODE, not prose. The index-deletion arm used to
# pass on a comment: pgcolumnar_index_delete_tuples stopped asking any row
# whether it is live (it vouches for nothing, #1341), and the only
# PgColumnarRowIsLive left in it was the comment explaining why. String literals
# are kept, so a "/*" inside one is not read as a comment.
code_of() { # code_of FILE -> FILE with its C comments blanked, line count kept
python3 -c '
import re, sys
s = open(sys.argv[1]).read()
pat = re.compile(r"\"(?:\\.|[^\"\\])*\"|\x27(?:\\.|[^\x27\\])*\x27|/\*.*?\*/|//[^\n]*", re.S)
sys.stdout.write(pat.sub(lambda m: m.group(0) if m.group(0)[0] in "\"\x27" else "\n" * m.group(0).count("\n"), s))
' "$1"
}

# THE ARM NAMED "DECODES NOTHING" COULD NOT SEE A DECODE. Both of these counted a
# CALL SITE, with its argument text, and pinned it at a literal 1 -- so they
# asserted that a particular call is still written the way it was written, which is
Expand All @@ -162,9 +178,10 @@ SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/src"
#
# The two premises keep the zero from being vacuous: a file that called NOTHING
# would also report zero full decodes.
_np_live="$(grep -c 'PgColumnarRowIsLive(' "$SRC/columnar_projection.c")"
_np_cols="$(grep -c 'PgColumnarReadRowByNumberCols(' "$SRC/columnar_projection.c")"
_np_full="$(grep -c 'PgColumnarReadRowByNumber(' "$SRC/columnar_projection.c")"
_np_code="$(code_of "$SRC/columnar_projection.c")"
_np_live="$(grep -c 'PgColumnarRowIsLive(' <<<"$_np_code")"
_np_cols="$(grep -c 'PgColumnarReadRowByNumberCols(' <<<"$_np_code")"
_np_full="$(grep -c 'PgColumnarReadRowByNumber(' <<<"$_np_code")"

check "premise: the visibility-only entry point is called at all" \
"$([ "${_np_live:-0}" -ge 1 ] && echo yes || echo no)" "yes"
Expand Down Expand Up @@ -198,10 +215,19 @@ check "neither caller decodes every column: no full decode in the projection pat
# string appears legitimately elsewhere now that the index fetch also asks only
# for liveness, and a whole-file count turned that correct second use into a
# failure.
deltuples="$(awk '/^pgcolumnar_index_delete_tuples\(/,/^}/' "$SRC/columnar_tableam.c")"
#
# The first arm was "index deletion asks whether the row is live". It stopped
# being true when the function stopped asking (an answer from this backend's
# snapshot erased index entries of rows live to everyone else), and it stayed
# green on the comment that says so. The property now is the absence: a
# per-row liveness test here is the regression, so it is what reddens.
deltuples="$(code_of "$SRC/columnar_tableam.c" | awk '/^pgcolumnar_index_delete_tuples\(/,/^}/')"

check "premise: the index deletion function was extracted, not blank" \
"$(case "$deltuples" in *ndeltids*) echo yes ;; *) echo "extracted=<${deltuples:-empty}>" ;; esac)" "yes"

check "index deletion asks whether the row is live" \
"$(case "$deltuples" in *PgColumnarRowIsLive*) echo yes ;; *) echo no ;; esac)" "yes"
check "index deletion asks no row whether it is live: it vouches for nothing" \
"$(case "$deltuples" in *PgColumnarRowIsLive*) echo "no (asks per row)" ;; *) echo yes ;; esac)" "yes"

check "and does not decode the row to find out" \
"$(case "$deltuples" in *PgColumnarReadRowByNumber*) echo "no (still decodes)" ;;
Expand All @@ -222,9 +248,10 @@ check "and does not decode the row to find out" \
# first. Honest callers move both counts together; an unguarded test moves only
# the total. Both numbers are printed in the arm so the comparison is the
# reconciliation rather than a bare verdict.
_afc_tests="$(grep -c 'bms_is_member(c, needed)' "$SRC/columnar_reader.c")"
_afc_guarded="$(grep -c '!allColumns && !bms_is_member(c, needed)' "$SRC/columnar_reader.c")"
_afc_flags="$(grep -c 'bool allColumns' "$SRC/columnar_reader.c")"
_afc_code="$(code_of "$SRC/columnar_reader.c")"
_afc_tests="$(grep -c 'bms_is_member(c, needed)' <<<"$_afc_code")"
_afc_guarded="$(grep -c '!allColumns && !bms_is_member(c, needed)' <<<"$_afc_code")"
_afc_flags="$(grep -c 'bool allColumns' <<<"$_afc_code")"

check "premise: there is a needed-set membership test to guard" \
"$([ "${_afc_tests:-0}" -ge 1 ] && echo yes || echo no)" "yes"
Expand Down
11 changes: 7 additions & 4 deletions test/native_groupagg_batch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,16 @@ for pair in "text key:$Q_TEXTKEY" "expression key:$Q_EXPRKEY" \
"min/max:$Q_MINMAX" "<> filter:$Q_NEQ"; do
label="${pair%%:*}"; tmpl="${pair#*:}"
qcol="${tmpl//%T/gbb}"
# The decline must be the FOLD's, read off the grouped node. This branch was
# a check_skip, and it never fired on 15-19: with the node gone the gate
# under test is unreachable, so a skip there was a silent pass. A shape the
# node stops taking is now a red under the arm's own name.
if [ "$(is_groupvec "$qcol")" = yes ]; then
check_text "$label: the fold declined" \
"$(fold_of "$ANALYZE_PFX" "$qcol")" no
else
# The node itself declined the shape; there is no fold line to read and
# nothing for this suite to gate. Say so rather than assert a missing line.
check_skip "$label" "SKIP $label: the grouped node is not planned for this shape" "the grouped node is not planned for this shape"
check_text "$label: the fold declined" \
"the grouped node is not planned" no
fi
agree "$label: answers match the heap mirror" "$tmpl"
done
Expand Down Expand Up @@ -315,7 +318,7 @@ agree_in gbs "skipped vectors: answers match the heap mirror" "$Q_SKIP"
Q_SKIP4="SELECT k, count(*), sum(p1), sum(p2), sum(p3), sum(p4) FROM %T WHERE sel = 1 GROUP BY k"
check_text "skipped vectors: premise: the wide arm is still the grouped node" \
"$(is_groupvec "${Q_SKIP4//%T/gbs}")" yes
check_text "skipped vectors: four payload columns all stay in step" \
check_text "skipped vectors: the fold ran on the four-payload arm" \
"$(fold_of "$ANALYZE_PFX" "${Q_SKIP4//%T/gbs}")" yes
agree_in gbs "skipped vectors: and their sums match the heap mirror" "$Q_SKIP4"

Expand Down
32 changes: 32 additions & 0 deletions test/pytest/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ behaviour, the source of that number is named.
- [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)
- [164. test_native_saop_pushdown.py: IN-lists and = ANY(array) prune row groups](#164-test_native_saop_pushdownpy-in-lists-and--anyarray-prune-row-groups)
- [165. test_native_groupagg_batch.py: the grouped aggregate folds column-at-a-time](#165-test_native_groupagg_batchpy-the-grouped-aggregate-folds-column-at-a-time)
- [166. test_native_fetch_projection.py: a fetch by row number decodes only what its caller needs](#166-test_native_fetch_projectionpy-a-fetch-by-row-number-decodes-only-what-its-caller-needs)

## 1. How to read a test in here

Expand Down Expand Up @@ -7659,3 +7661,33 @@ The pytest twin of `test/native_saop_pushdown.sh` (ported for #1341), every chec
| `test_the_batch_fold_never_takes_the_set_keys_as_exact` | the batch fold refuses the IN-list but accepts the equivalent range, and the vectorized count stays exact |
| `test_a_correlated_array_is_not_frozen_and_the_backend_lives` | a correlated LATERAL array is not frozen, and the session's backend is still alive; a crash is a named red |
| `test_the_reader_refuses_every_special_flag_but_the_array_one` | the reader's reject expression names every special scan-key flag, and SK_SEARCHARRAY reaches its own arm |

## 165. test_native_groupagg_batch.py: the grouped aggregate folds column-at-a-time

The pytest twin of `test/native_groupagg_batch.sh` (ported for #1341), every check by the shell's name. The grouped vectorized aggregate folds a whole row group at a time (#708), and two things are proved separately. The answers must be unchanged: each shape agrees with a heap mirror, and with the same rows when the path is off. The work must happen: "Columnar Batch Fold: yes" under EXPLAIN ANALYZE is the only check that goes red when the fold stops running. Each shape the fold must decline is read off the grouped node, so a shape the node stops taking is a red, not a skip.

### Every test

| test | what it holds |
| --- | --- |
| `test_the_fold_takes_every_eligible_shape_and_answers_as_the_heap_does` | seven shapes (plain, filtered, two keys, float8, count(*), timestamp and date keys) plan the grouped node, fold, and match the heap |
| `test_the_fold_declines_every_ineligible_shape_and_still_answers` | a text key, an expression key, min/max and a `<>` filter make the fold decline on the grouped node, and each still matches the heap |
| `test_the_fold_honours_the_delete_mask` | after deletes the fold still runs and matches the heap |
| `test_the_fold_equals_the_row_path_over_the_same_rows` | three shapes answer the same with the path off and on, and the off arm really is not the grouped node |
| `test_float8_keys_group_by_the_types_equality_not_by_bits` | a stored -0.0 and 0.0 are one group on the fold, as on the heap |
| `test_the_fold_steps_over_skipped_vectors` | a scan that really skips vectors folds, and its one-column and four-column sums match the heap |
| `test_the_parallel_partial_fold_equals_the_serial_one` | our parallel-aware node launches workers and folds, and equals the serial fold and the heap |
| `test_a_column_added_after_some_row_groups_runs_the_row_path` | plain EXPLAIN predicts the fold, ANALYZE reports the row path that ran, and the answer is right |

## 166. test_native_fetch_projection.py: a fetch by row number decodes only what its caller needs

The pytest twin of `test/native_fetch_projection.sh` (ported for #1341), every check by the shell's name. A row fetched by number decoded every column whatever the caller wanted (#157). Two narrower entry points exist beside it: one decodes a given set of columns, and one answers visibility and decodes nothing. Neither has a SQL surface, so they are exercised through `pgcolumnar.read_projection` and `pgcolumnar.reconstruct_via_projection` on a 41-column table, against the base table as the oracle. The source arms read code with its comments blanked, as the shell's `code_of` does.

### Every test

| test | what it holds |
| --- | --- |
| `test_the_projected_fetch_returns_the_base_tables_rows_and_sees_deletes` | every reconstructed row matches the base table, a deleted row stops being read or reconstructed, and an uncovered column still reads right |
| `test_a_projection_covering_every_column_decodes_nothing_and_is_right` | with the computed base-column set empty, every row is reconstructed with the right values |
| `test_no_caller_decodes_every_column_and_index_deletion_reads_no_row` | the projection path calls no full decode, and index deletion asks no row whether it is live |
| `test_every_needed_set_test_consults_the_all_columns_flag` | every needed-set membership test in the reader consults the explicit all-columns flag |
6 changes: 5 additions & 1 deletion test/pytest/expected_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -791,4 +791,8 @@ guard_tests 438
# four tests, by collection over the cluster files on this tree.
# cluster_tests 702 -> 710 for #1341's test_native_saop_pushdown.py, the twin of native_saop_pushdown.sh:
# eight tests, by collection over the cluster files on this tree.
cluster_tests 710
# cluster_tests 710 -> 718 for #1341's test_native_groupagg_batch.py, the twin of native_groupagg_batch.sh:
# eight tests, by collection over the cluster files on this tree.
# cluster_tests 718 -> 722 for #1341's test_native_fetch_projection.py, the twin of native_fetch_projection.sh:
# four tests, by collection over the cluster files on this tree.
cluster_tests 722
4 changes: 3 additions & 1 deletion test/pytest/test_compare_to_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@
"native_dml",
"native_encdesc_golden",
"native_fetch_coalesce",
"native_fetch_projection",
"native_groupagg",
"native_groupagg_batch",
"native_index_fetch_stripe_cost",
"native_index_projection",
"native_join_vector_agg",
Expand Down Expand Up @@ -410,7 +412,7 @@ def test_the_loop_reader_invents_nothing_in_this_corpus(expect):
"every name the loop reader returns appears verbatim in the file it "
"came from")
expect.text(", ".join(files),
"build_refusal, differential, join_runtime_filter, "
"build_refusal, differential, join_runtime_filter, native_groupagg_batch, "
"native_index_projection, native_reclaim, native_repack, "
"native_saop_pushdown, native_vacuum_race, native_vecskip, sorted_pathkeys",
"and it is these files, so another appearing is a diff a reviewer "
Expand Down
Loading
Loading