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
30 changes: 21 additions & 9 deletions test/check_ledger.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2477,6 +2477,18 @@ native_fetch_coalesce native_fetch_coalesce premise: fetching one projected colu
native_fetch_coalesce native_fetch_coalesce premise: the coalescing helper holds both the bound and the validity copy 15;16;17;18;19 never -
native_fetch_coalesce native_fetch_coalesce the validity copy is bounded by the chunk length before it runs 15;16;17;18;19 never -
native_fetch_coalesce native_fetch_coalesce the wide fetch returns the projected values 15;16;17;18;19 2026-10-14 every column of a coalesced fetch is copied from the start of the span (off = 0)
native_fetch_position native_fetch_position a varying-length column reaches its value through an offset table 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row reaches a varying-length value by decoding every earlier one (the #143 walk)
native_fetch_position native_fetch_position doubling the row group does not double the cost of the same fetches 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row counts the validity bits from the group's start for every fetch (the #143 walk);columnar_reader.c: pgcolumnar_fetch_row reaches a varying-length value by decoding every earlier one (the #143 walk)
native_fetch_position native_fetch_position every column matches the heap mirror 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row takes the row number as the rank, counting rows rather than present values;columnar_reader.c: pgcolumnar_fetch_row's rank is one past the row's
native_fetch_position native_fetch_position fetching from the far end of a group costs about what the near end does 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row counts the validity bits from the group's start for every fetch (the #143 walk);columnar_reader.c: pgcolumnar_fetch_row reaches a varying-length value by decoding every earlier one (the #143 walk)
native_fetch_position native_fetch_position premise: a single-row fetch goes through the index 15;16;17;18;19 never -
native_fetch_position native_fetch_position premise: pgcolumnar_fetch_row was extracted, not an empty range 15;16;17;18;19 never -
native_fetch_position native_fetch_position premise: the doubled group's reads fetch through its index 15;16;17;18;19 never -
native_fetch_position native_fetch_position premise: the mirror comparison fetches through the index 15;16;17;18;19 never -
native_fetch_position native_fetch_position premise: the timed reads fetch through the index 15;16;17;18;19 never -
native_fetch_position native_fetch_position single-row fetches are right at every position 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row takes the row number as the rank, counting rows rather than present values;columnar_reader.c: pgcolumnar_fetch_row's rank is one past the row's
native_fetch_position native_fetch_position the rank comes from a prefix rather than a loop over earlier rows 15;16;17;18;19 2026-10-16 columnar_reader.c: pgcolumnar_fetch_row counts the validity bits from the group's start for every fetch (the #143 walk);columnar_reader.c: pgcolumnar_fetch_row takes the row number as the rank, counting rows rather than present values
native_fetch_position native_fetch_position the rows are in one row group 15;16;17;18;19 never -
native_fetch_projection native_fetch_projection a deleted row stops being read through the projection 15;16;17;18;19 2026-10-16 columnar_projection.c: read_projection ignores the base row's liveness
native_fetch_projection native_fetch_projection an uncovered column still reads correctly after a delete 15;16;17;18;19 2026-10-16 columnar_projection.c: reconstruct's uncovered set misses the table's last column
native_fetch_projection native_fetch_projection and does not decode the row to find out 15;16;17;18;19 2026-10-16 columnar_tableam.c: index deletion decodes each bottom-up row to decide
Expand Down Expand Up @@ -2595,17 +2607,17 @@ native_groupagg_batch native_groupagg_batch <> filter: the fold declined 15;16;1
native_groupagg_batch native_groupagg_batch add column: ANALYZE reports the row-path run 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped node's ANALYZE prints the predicted fold, not what ran (the #602 shape)
native_groupagg_batch native_groupagg_batch add column: plain EXPLAIN reports the prediction 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch add column: the answer is still right 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch count(*): answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch count(*): answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch count(*): the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch count(*): the grouped node is planned 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch date key: answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch date key: the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch date key: the grouped node is planned 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch deletes: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask
native_groupagg_batch native_groupagg_batch deletes: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch deletes: the fold still ran 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch expression key: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: an expression key's -1 attidx is not refused by the grouped fold's eligibility
native_groupagg_batch native_groupagg_batch expression key: the fold declined 15;16;17;18;19 2026-10-16 columnar_vector.c: an expression key's -1 attidx is not refused by the grouped fold's eligibility
native_groupagg_batch native_groupagg_batch filtered: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold applies no scan key per row
native_groupagg_batch native_groupagg_batch filtered: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold applies no scan key per row;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch filtered: the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch filtered: the grouped node is planned 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch float8 key: answers match the heap mirror 15;16;17;18;19 never -
Expand All @@ -2619,13 +2631,13 @@ native_groupagg_batch native_groupagg_batch float8 zero: the grouped node is pla
native_groupagg_batch native_groupagg_batch float8 zero: the grouped sums match the heap 15;16;17;18;19 2026-10-16 columnar_vector.c: float8 admitted as a simple key, so its group keys are hashed and compared by bits
native_groupagg_batch native_groupagg_batch min/max: answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch min/max: the fold declined 15;16;17;18;19 2026-10-16 columnar_vector.c: min and max admitted as batch fold kinds
native_groupagg_batch native_groupagg_batch parallel: and they equal the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: a partial grouped node folds without the shared row-group counter;columnar_vector.c: the grouped fold ignores the delete mask
native_groupagg_batch native_groupagg_batch parallel: and they equal the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: a partial grouped node folds without the shared row-group counter;columnar_vector.c: the grouped fold ignores the delete mask;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch parallel: our parallel-aware grouped node is under the Gather 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch parallel: premise: the value arm's own plan launches workers 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch parallel: the answers equal the serial fold, byte for byte 15;16;17;18;19 2026-10-16 columnar_vector.c: a partial grouped node folds without the shared row-group counter
native_groupagg_batch native_groupagg_batch parallel: the fold ran in the partial node (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch parallel: workers actually launched for that node 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch plain: answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch plain: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch plain: the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch plain: the grouped node is planned 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch premise: fixture loaded every row (columnar) 15;16;17;18;19 never -
Expand All @@ -2646,16 +2658,16 @@ native_groupagg_batch native_groupagg_batch text key: the fold declined 15;16;17
native_groupagg_batch native_groupagg_batch timestamp key: answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch timestamp key: the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch timestamp key: the grouped node is planned 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: filtered 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold applies no scan key per row;columnar_vector.c: the grouped fold ignores the delete mask
native_groupagg_batch native_groupagg_batch toggle-differential: filtered 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold applies no scan key per row;columnar_vector.c: the grouped fold ignores the delete mask;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch toggle-differential: filtered [premise: node fires with the path on] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: filtered [premise: the off arm is not the grouped node] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: plain 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask
native_groupagg_batch native_groupagg_batch toggle-differential: plain 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch toggle-differential: plain [premise: node fires with the path on] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: plain [premise: the off arm is not the grouped node] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: two keys 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask
native_groupagg_batch native_groupagg_batch toggle-differential: two keys 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold ignores the delete mask;columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch toggle-differential: two keys [premise: node fires with the path on] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch toggle-differential: two keys [premise: the off arm is not the grouped node] 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch two keys: answers match the heap mirror 15;16;17;18;19 never -
native_groupagg_batch native_groupagg_batch two keys: answers match the heap mirror 15;16;17;18;19 2026-10-16 columnar_vector.c: the grouped fold's gather drops the NULL-key flag, so the NULL group folds into k = 0 (jdatcmd's #1485 review)
native_groupagg_batch native_groupagg_batch two keys: the fold ran (work done) 15;16;17;18;19 2026-10-16 columnar_vector.c: pgcolumnar_groupagg_build never calls the grouped batch fold (the removal proof)
native_groupagg_batch native_groupagg_batch two keys: the grouped node is planned 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 -
Expand Down
30 changes: 28 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 199
suites_not_covered 198
#
# checks_never_observed_red -- A CENSUS. NOT a ceiling, and it must not become
# one.
Expand Down Expand Up @@ -3343,4 +3343,30 @@ suites_not_covered 199
# never 2988 - 16 = 2972
# dated 1201 + 16 = 1217
# 2972 + 1217 == 4189
checks_never_observed_red 2972
#
# #1341, native_groupagg_batch's NULL-key mutation (OffgridwithJD, from jdatcmd's
# #1485 review): 2972 -> 2969. The grouped fold's gather dropping the NULL-key
# flag folds the NULL group into k = 0 on the batch path only. Nine checks redden,
# shell and twin alike; three of them (plain, two keys and count(*) against the
# heap mirror) entered never and are now dated, and the other six gain the
# mutation in their history. No row is added or removed.
#
# ledger rows 4189 + 0 = 4189
# never 2972 - 3 = 2969
# dated 1217 + 3 = 1220
# 2969 + 1220 == 4189
#
# #1341, native_fetch_position seeded, with its twin (OffgridwithJD): 2969 -> 2975,
# and suites_not_covered 199 -> 198. The suite had no rows. It enters with all 12
# checks from five-major runs (12/12 on each of 15-19). 6 are dated by four
# mutations of pgcolumnar_fetch_row: the rank counting rows, the rank one too far,
# the rank walking the validity bits, and a varying-length value reached by
# decoding every earlier one. The two timing arms are dated by the walks only now
# that the fetches go through the index and the timed pass is a read. Six arrive
# never: the one-row-group fixture fact and five premises.
#
# ledger rows 4189 + 12 = 4201
# never 2969 + 6 = 2975
# dated 1220 + 6 = 1226
# 2975 + 1226 == 4201
checks_never_observed_red 2975
44 changes: 41 additions & 3 deletions test/native_fetch_position.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,32 @@ check "the rows are in one row group" \

# Fetch by row number is what an index scan drives, so force one. Each leading
# SET prints its own line ahead of the result, so these queries take the last.
#
# enable_seqscan=off does NOT turn the columnar custom scan off, and it was all
# this suite set: measured, the single-row fetch, the near and far UPDATEs and
# the mirror join all planned as Custom Scan (PgColumnarScan), so none of them
# fetched by row number. A rank off by one left the single-row arm green, and a
# walk over every earlier row left both timing arms green. The custom scan is
# off now, and each measurement asserts first that its plan reads the index.
FORCE="SET enable_seqscan = off; SET enable_bitmapscan = off;
SET max_parallel_workers_per_gather = 0;"
SET max_parallel_workers_per_gather = 0;
SET pgcolumnar.enable_custom_scan = off;"

# reads_index QUERY INDEX -> yes, or the plan's first line: the premise that a
# query fetches by row number through INDEX rather than scanning
reads_index() {
local plan
plan="$(q "$FORCE EXPLAIN (COSTS OFF) $1")"
case "$plan" in
*"Index Scan using $2 "*) echo yes ;;
*) echo "no: $(grep -m1 -E 'Scan|Update' <<<"$plan" | sed 's/^ *//')" ;;
esac
}

check "premise: the mirror comparison fetches through the index" \
"$(reads_index "SELECT count(*) FROM fp_c c JOIN fp_h h USING (id) WHERE c.fixed IS DISTINCT FROM h.fixed" fp_c_id)" "yes"
check "premise: a single-row fetch goes through the index" \
"$(reads_index "SELECT fixed FROM fp_c WHERE id = 1001" fp_c_id)" "yes"

# rows spread across the whole group, including the far end where the old walk
# was longest
Expand Down Expand Up @@ -103,7 +127,7 @@ FAR_HI=$ROWS
upd_once() { # id-low, id-high -> milliseconds for one pass
local start end
start=$(date +%s%N)
psql_run "$FORCE UPDATE fp_c SET fixed = fixed + 1
psql_run "$FORCE SELECT sum(fixed), sum(length(txt)) FROM fp_c
WHERE id BETWEEN $1 AND $2;" >/dev/null 2>&1
end=$(date +%s%N)
echo $(( (end - start) / 1000000 ))
Expand All @@ -130,6 +154,17 @@ upd_ms() { # id-low, id-high -> median of three passes
printf '%s\n%s\n%s\n' "$a" "$b" "$c" | sort -n | sed -n 2p
}

# The timed pass is a SELECT, not the UPDATE it was. A columnar UPDATE writes
# each row again into a new row group, so only the first of three passes
# fetched from the far end of the big group; the second and third fetched from
# the start of the new one, and the median was always one of those. Measured
# with the rank walking every earlier row: the UPDATE form read near 122 ms,
# far 132 ms, green. A read leaves every row where it was, so all three passes
# fetch the same positions. sum(length(txt)) makes it decode a varying-length
# column, which the offset table serves.
check "premise: the timed reads fetch through the index" \
"$(reads_index "SELECT sum(fixed), sum(length(txt)) FROM fp_c WHERE id BETWEEN $FAR_LO AND $FAR_HI" fp_c_id)" "yes"

near="$(upd_ms $NEAR_LO $NEAR_HI)"
far="$(upd_ms $FAR_LO $FAR_HI)"
echo "-- $N fetches, median of three: first tenth ${near} ms, last tenth ${far} ms"
Expand Down Expand Up @@ -157,7 +192,7 @@ build() { # table, rows
fetch_once() { # table, table-rows, count -> milliseconds for one pass
local start end
start=$(date +%s%N)
psql_run "$FORCE UPDATE $1 SET v = v + 1 WHERE id > $(( $2 - $3 ));" \
psql_run "$FORCE SELECT sum(v), sum(length(t)) FROM $1 WHERE id > $(( $2 - $3 ));" \
>/dev/null 2>&1
end=$(date +%s%N)
echo $(( (end - start) / 1000000 ))
Expand Down Expand Up @@ -205,6 +240,9 @@ build fp_small $((ROWS / 2))
build fp_big "$ROWS"

K=$((ROWS / 20))
check "premise: the doubled group's reads fetch through its index" \
"$(reads_index "SELECT sum(v), sum(length(t)) FROM fp_big WHERE id > $(( ROWS - K ))" fp_big_id)" "yes"

small="$(fetch_ms fp_small $((ROWS / 2)) $K)"
big="$(fetch_ms fp_big "$ROWS" $K)"
echo "-- $K fetches at the far end, median of three: group of $((ROWS / 2)) took ${small} ms, group of $ROWS took ${big} ms"
Expand Down
Loading
Loading