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
33 changes: 33 additions & 0 deletions test/check_ledger.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3902,6 +3902,26 @@ parallel_scan_cost parallel_scan_cost premise: the serial plan has no Gather 15;
parallel_scan_cost parallel_scan_cost premise: the serial plan is a columnar scan 15;16;17;18;19 never -
parallel_scan_cost parallel_scan_cost premise: the table holds every inserted row 15;16;17;18;19 never -
parallel_scan_cost parallel_scan_cost the leader-participation branch changes the divisor 15;16;17;18;19 never -
parquet_level_width parquet_level_width a non-positive maximum returns before shifting at all 15;16;17;18;19 2026-10-21 columnar_parquet_format.h: pq_bits_for's non-positive guard is commented out
parquet_level_width parquet_level_width and both callers reach the shared one 15;16;17;18;19 never -
parquet_level_width parquet_level_width and no signed 1 << remains in it 15;16;17;18;19 2026-10-21 columnar_parquet_format.h: pq_bits_for shifts a signed 1
parquet_level_width parquet_level_width no local copy of the helper survives in the writer or the reader 15;16;17;18;19 2026-10-21 columnar_parquet.c: a local bits_for copy reappears in the writer
parquet_level_width parquet_level_width the loop is bounded below 32 shifts 15;16;17;18;19 2026-10-21 columnar_parquet_format.h: pq_bits_for's loop loses its b < 31 bound
parquet_level_width parquet_level_width the shared level-width helper exists 15;16;17;18;19 never -
parquet_level_width parquet_level_width the shift is unsigned 15;16;17;18;19 2026-10-21 columnar_parquet_format.h: pq_bits_for shifts a signed 1
parquet_nested parquet_nested nested Parquet export rows written 15;16;17;18;19 never -
parquet_nested parquet_nested nested Parquet field types 15;16;17;18;19 never -
parquet_nested parquet_nested nested Parquet values match expectation 15;16;17;18;19 2026-10-21 columnar_parquet.c: an empty array is written with definition level 0, so it reads back NULL
parquet_nested parquet_nested np2 export rows 15;16;17;18;19 never -
parquet_nested parquet_nested np2 values match 15;16;17;18;19 never -
parquet_nested parquet_nested reject multi-dim array 15;16;17;18;19 2026-10-21 columnar_parquet.c: a multi-dimensional array is no longer refused
parquet_nested_import parquet_nested_import export rows 15;16;17;18;19 never -
parquet_nested_import parquet_nested_import import count matches 15;16;17;18;19 never -
parquet_nested_import parquet_nested_import import rows 15;16;17;18;19 never -
parquet_nested_import parquet_nested_import nested import round-trips (arrays + composite) 15;16;17;18;19 2026-10-21 columnar_parquet_reader.c: an empty Parquet list is read back as a NULL array
parquet_nested_import parquet_nested_import pyarrow import rows 15;16;17;18;19 never -
parquet_nested_import parquet_nested_import pyarrow nested file matches oracle 15;16;17;18;19 2026-10-21 columnar_parquet_reader.c: an empty Parquet list is read back as a NULL array
parquet_nested_import parquet_nested_import round-trips after delete 15;16;17;18;19 2026-10-21 columnar_parquet_reader.c: an empty Parquet list is read back as a NULL array
partition_lifecycle partition_lifecycle a NULL cutoff is refused, in both overloads 15;16;17;18;19 2026-10-13 partition_drop_plan without its NULL-cutoff check, in both overloads
partition_lifecycle partition_lifecycle a call that would create more than 1000 partitions is refused before any DDL 15;16;17;18;19 2026-10-12 partition_create_ahead without the 1000-partition bound
partition_lifecycle partition_lifecycle a cutoff before every partition plans nothing 15;16;17;18;19 2026-10-13 the cutoff filter removed: every partition planned
Expand Down Expand Up @@ -4497,6 +4517,19 @@ range_pruning range_pruning premise: the range type collates differently from it
range_pruning range_pruning so does a containment probe 15;16;17;18;19 never -
range_pruning range_pruning the overlap qual is pushed down, where it used to be dropped 15;16;17;18;19 never -
range_pruning range_pruning the same for a containment probe far to its right 15;16;17;18;19 2026-10-17 columnar_write_state.c: the writer ignores an unbounded range, so its unit records only the largest finite upper bound
read_stream read_stream [off] a fresh session reads enable_read_stream as off 15;16;17;18;19 2026-10-21 columnar_tableam.c: pgcolumnar.enable_read_stream registered as pgcolumnar.enable_read_streamz, so the product's setting is gone
read_stream read_stream [off] aggregate 15;16;17;18;19 2026-10-20 the synchronous block-read path copies each page's bytes one byte late
read_stream read_stream [off] filtered 15;16;17;18;19 2026-10-20 the synchronous block-read path copies each page's bytes one byte late
read_stream read_stream [off] full scan 15;16;17;18;19 2026-10-20 the synchronous block-read path copies each page's bytes one byte late
read_stream read_stream [off] range 15;16;17;18;19 2026-10-20 the synchronous block-read path copies each page's bytes one byte late
read_stream read_stream [off] wide sample 15;16;17;18;19 2026-10-20 the synchronous block-read path copies each page's bytes one byte late
read_stream read_stream [on] a fresh session reads enable_read_stream as on 15;16;17;18;19 2026-10-21 columnar_tableam.c: pgcolumnar.enable_read_stream registered as pgcolumnar.enable_read_streamz, so the product's setting is gone
read_stream read_stream [on] aggregate 15;16;17;18;19 2026-10-20 the read-stream path copies each page's bytes one byte late (PG17+)
read_stream read_stream [on] filtered 15;16;17;18;19 2026-10-20 the read-stream path copies each page's bytes one byte late (PG17+)
read_stream read_stream [on] full scan 15;16;17;18;19 2026-10-20 the read-stream path copies each page's bytes one byte late (PG17+)
read_stream read_stream [on] range 15;16;17;18;19 2026-10-20 the read-stream path copies each page's bytes one byte late (PG17+)
read_stream read_stream [on] wide sample 15;16;17;18;19 2026-10-20 the read-stream path copies each page's bytes one byte late (PG17+)
read_stream read_stream multiple stripes present 15;16;17;18;19 never -
reader_buffer_reuse reader_buffer_reuse a PROJECTED scan (one column of two) matches too 15;16;17;18;19 never -
reader_buffer_reuse reader_buffer_reuse a column ADDED after the groups exist reads back NULL, as on heap 15;16;17;18;19 2026-10-21 columnar_reader.c: when the group buffer is reused rather than grown, a projected read does not refresh it, so it decodes the previous group's bytes
reader_buffer_reuse reader_buffer_reuse a rescan (nested loop) returns the same rows 15;16;17;18;19 never -
Expand Down
70 changes: 68 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 113
suites_not_covered 109
#
# checks_never_observed_red -- A CENSUS. NOT a ceiling, and it must not become
# one.
Expand Down Expand Up @@ -5211,4 +5211,70 @@ suites_not_covered 113
# never 3790 + 3 = 3793
# dated 2063 + 10 = 2073
# 3793 + 2073 == 5866
checks_never_observed_red 3793
#
# #1341, read_stream seeded, with its twin (jdatcmd): 3503 -> 3504, and
# suites_not_covered 149 -> 148. The suite had no rows. It enters with 13 checks from
# five-major runs (13/13 on each of 15-19, shell and twin alike), two of them new.
# The setting arrives by ALTER DATABASE SET, whose error the shell's q drops, so a
# setting that never took ran both modes down one path: with it misspelled and the
# synchronous path broken, the shell passed 11 of 11. Each mode now reads the setting
# back from a fresh session first. Those two reads name a pgcolumnar setting, and the
# setting registered under another name reddens them, so they are not premises. The
# twin's fixture returns the failed setting as their value, so they red by name there
# too. The ten comparisons' names are literal, and the stripe premise carries its
# count and leaves lossy_arms.tsv. 12 are dated on PG17, each in both twins with the
# same names: each mode's five comparisons by its path copying a page one byte late,
# and the two read-backs by the renamed setting. 1 arrives never. Nothing observes
# which path a read took: with the stream path taken whatever the setting says and the
# synchronous path broken, all 13 pass, so the [off] rows hold only while the setting
# routes reads. On 15 and 16 the stream path is compiled out and [on] repeats [off].
#
# ledger rows 5866 + 13 = 5879
# never 3793 + 1 = 3794
# dated 2073 + 12 = 2085
# 3794 + 2085 == 5879
#
# #1341, parquet_level_width seeded, with its twin (jdatcmd): 3543 -> 3545, and
# suites_not_covered 144 -> 143. The suite had no rows. It enters with 7 checks from
# five-major runs (7/7 on each of 15-19; its twin needs no cluster). Three fixes: "no local
# copy" summed grep -c over two files, so with the writer's path wrong it read 0 and
# passed; it now needs both files. "no signed 1 << remains" counted over the extracted
# body, so with the header unreadable it read 0 and passed; it now needs a body. And the
# body checks read the comments inside the body, so a guard or bound commented out of the
# code passed; the body now has its comments blanked. 5 are dated by four source
# mutations, each reddening the same names in both twins: a signed shift, the b < 31
# bound removed, a local copy back in the writer, and the non-positive guard commented
# out. 2 arrive never.
#
# ledger rows 5879 + 7 = 5886
# never 3794 + 2 = 3796
# dated 2085 + 5 = 2090
# 3796 + 2090 == 5886
#
# #1341, parquet_nested seeded, with its twin (jdatcmd): 3543 -> 3547, and
# suites_not_covered 144 -> 143. The suite had no rows. It enters with 6 checks from
# five-major runs (6/6 on each of 15-19, shell and twin alike), after two fixes. The
# value comparison visited only the rows the file held, so a zero-row file of the same
# schema printed MATCH; it now requires every id 1..5000. And any error counted as the
# multi-dimensional refusal, a missing table's included; it now reads that refusal. 2 are
# dated by two mutations, each reddening the same name in both twins: an empty array
# written as NULL, and the multi-dimensional refusal removed. 4 arrive never.
#
# ledger rows 5886 + 6 = 5892
# never 3796 + 4 = 3800
# dated 2090 + 2 = 2092
# 3800 + 2092 == 5892
#
# #1341, parquet_nested_import seeded, with its twin (jdatcmd): 3600 -> 3604, and
# suites_not_covered 139 -> 138. The suite had no rows. It enters with 7 checks from
# five-major runs (7/7 on each of 15-19, shell and twin alike), after one fix: "import
# count matches" compared two counts with check, so with both tables missing two empty
# readings agreed; it is check_num. 3 are dated by one mutation, reddening the same
# names in both twins: the reader taking an empty Parquet list for a NULL array, which
# every round-trip and the pyarrow oracle catch. 4 arrive never.
#
# ledger rows 5892 + 7 = 5899
# never 3800 + 4 = 3804
# dated 2092 + 3 = 2095
# 3804 + 2095 == 5899
checks_never_observed_red 3804
1 change: 0 additions & 1 deletion test/lossy_arms.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ projection_scan_cost a tight covering projection is cheaper relative to the base
projection_scan_cost premise: the prunable range is priced above the one-stripe floor, so the arms differ
projection_scan_cost while a plain range on the sort key still earns its discount
projections fp2 spans multiple projection row groups
read_stream multiple stripes present
scan_decode_cost a wide projection costs materially more than a narrow one (decode is priced)
scan_decode_cost four wide text columns cost more than eight narrow int ones (#768)
truncate_cleanup PREMISE the projection has its own storage, distinct from the base
Expand Down
18 changes: 13 additions & 5 deletions test/parquet_level_width.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,23 @@ SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/src"
HDR="$SRC/columnar_parquet_format.h"

# The body, so every assertion below reads the shared definition and not a
# comment that happens to mention it.
body="$(awk '/^pq_bits_for\(int maxval\)/{f=1} f{print} f&&/^}/{exit}' "$HDR")"
# comment that happens to mention it. Comments inside the body are blanked too,
# multi-line ones included: a guard commented out of the code is not a guard.
body="$(awk '/^pq_bits_for\(int maxval\)/{f=1} f{print} f&&/^}/{exit}' "$HDR" |
perl -0777 -pe 's{/\*.*?\*/}{}gs; s{//[^\n]*}{}g')"

check "the shared level-width helper exists" \
"$([ -n "$body" ] && echo yes || echo no)" "yes"

# SINGLE-SOURCED. A local copy reappearing in either .c file is the drift this
# is here to catch, and it would not fail any other check in the tree.
# Both files must be READ: grep -c over a missing file prints nothing for it, and the
# sum then reads 0, the passing answer -- with the writer's path wrong this passed (#1341).
check "no local copy of the helper survives in the writer or the reader" \
"$(grep -cE '^[a-z_]*bits_for\(int' "$SRC/columnar_parquet.c" "$SRC/columnar_parquet_reader.c" \
| awk -F: '{n += $2} END {print n+0}')" "0"
"$(if [ -f "$SRC/columnar_parquet.c" ] && [ -f "$SRC/columnar_parquet_reader.c" ]; then
grep -cE '^[a-z_]*bits_for\(int' "$SRC/columnar_parquet.c" "$SRC/columnar_parquet_reader.c" \
| awk -F: '{n += $2} END {print n+0}'
else echo "no reading (a source file is missing)"; fi)" "0"
check "and both callers reach the shared one" \
"$([ "$(grep -c 'pq_bits_for(' "$SRC/columnar_parquet.c")" -ge 1 ] &&
[ "$(grep -c 'pq_bits_for(' "$SRC/columnar_parquet_reader.c")" -ge 1 ] &&
Expand All @@ -59,8 +65,10 @@ check "and both callers reach the shared one" \
# UNSIGNED. This is the #710 defect itself.
check "the shift is unsigned" \
"$(grep -c '1u <<' <<<"$body")" "1"
# Over the body only when there IS one: a count over an empty extraction is 0, the
# passing answer, so with the header unreadable this passed (#1341).
check "and no signed 1 << remains in it" \
"$(grep -cE '\(1 <<|[^u] 1 <<' <<<"$body")" "0"
"$(if [ -n "$body" ]; then grep -cE '\(1 <<|[^u] 1 <<' <<<"$body"; else echo "no reading (no body)"; fi)" "0"

# BOUNDED, which `1u <<` alone does NOT give. Making the shift unsigned also
# makes the comparison unsigned, so a negative maxval converts to a huge value
Expand Down
27 changes: 20 additions & 7 deletions test/parquet_nested.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ if ! python3 -c 'import pyarrow' 2>/dev/null; then
pgc_skip pyarrow "pyarrow not available; skipping Parquet nested export verification"
fi

expect_error() {
local label="$1" sql="$2"
if psql_run "$sql" >/dev/null 2>&1; then
check "$label (expected error)" "succeeded" "error"
else
check "$label" "error" "error"
# refused SQL -> "refused: multi-dimensional", "succeeded", or the other error. Any
# error used to pass, so with the export pointed at a missing table it read as the
# refusal (#1341), as arrow_nested found before it.
md_refusal() {
local out
if out="$(psql_run "$1" 2>&1)"; then
echo "succeeded"
return
fi
case "$out" in
*"does not support multi-dimensional arrays"*) echo "refused: multi-dimensional" ;;
*) echo "other error: $(printf '%s' "$out" | head -n 1)" ;;
esac
}

NF="$PGC_WORKDIR/nt.parquet"
Expand Down Expand Up @@ -62,6 +68,11 @@ check "nested Parquet field types" "$types" "True int32 True string True 2 x int
vals="$(python3 - "$NF" <<'PY'
import sys, pyarrow as pa, pyarrow.parquet as pq
d = pq.read_table(sys.argv[1]).to_pydict()
# Every id 1..5000, once: the loop below compares only rows the file holds, so a file
# with none printed MATCH (#1341).
if sorted(d['id']) != list(range(1, 5001)):
print("ROWS %d, not ids 1..5000" % len(d['id']))
sys.exit(0)
bad = None
for i in range(len(d['id'])):
g = d['id'][i]
Expand Down Expand Up @@ -93,6 +104,8 @@ PY
echo "-- multi-dimensional array rejected"
psql_run "CREATE TABLE npm (id int, m int[]) USING pgcolumnar;"
psql_run "INSERT INTO npm VALUES (1, ARRAY[[1,2],[3,4]]);"
expect_error "reject multi-dim array" "SELECT pgcolumnar.export_parquet('npm', '$PGC_WORKDIR/npm.parquet');"
check "reject multi-dim array" \
"$(md_refusal "SELECT pgcolumnar.export_parquet('npm', '$PGC_WORKDIR/npm.parquet');")" \
"refused: multi-dimensional"

pgc_summary
4 changes: 3 additions & 1 deletion test/parquet_nested_import.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ SEL="SELECT id, ia::text, ta::text, c::text FROM"
check "nested import round-trips (arrays + composite)" \
"$(pgc_set_hash "$SEL pna2")" \
"$(pgc_set_hash "$SEL pna")"
check "import count matches" "$(q 'SELECT count(*) FROM pna2;')" "$(q 'SELECT count(*) FROM pna;')"
# check_num: under check two failed counts were both empty and agreed, so with both
# tables missing this passed (#1341).
check_num "import count matches" "$(q 'SELECT count(*) FROM pna2;')" "$(q 'SELECT count(*) FROM pna;')"

# after a delete on the source, a fresh export+import still round-trips the live set
psql_run "DELETE FROM pna WHERE id BETWEEN 1000 AND 2000;"
Expand Down
Loading
Loading