wireform-parquet-0.1.0.0: scripts/parquet_file_preflight.py
#!/usr/bin/env python3
"""Check whole-file benchmark values/layouts and prove rejection controls.
Call before measurements. Positive fixtures come independently from wireform,
parquet-rs and Arrow C++. Negative fixtures retain100000rows but change values,
schema, compression, page version, dictionary mode or page/group boundaries.
"""
from __future__ import annotations
import argparse
from pathlib import Path
import shutil
import subprocess
import tempfile
import pyarrow as pa
import pyarrow.parquet as pq
from parquet_bench_compare import CODECS, N_ROWS, make_dataset, write_table
def verify(binary, directory, success=True, message=None):
result = subprocess.run([str(binary), "--verify-fixtures", str(directory)],
capture_output=True, text=True)
if success:
assert result.returncode == 0, (directory.name, result.stdout, result.stderr)
else:
assert result.returncode != 0, (directory.name, "unexpected verification success")
assert "benchmark fixture:" in result.stderr, (directory.name, result.stderr)
if message:
assert message in result.stderr, (directory.name, result.stderr)
def reference_write(table, path, **overrides):
options = dict(compression="NONE", use_dictionary=False, column_encoding="PLAIN", data_page_version="2.0",
write_page_index=False, store_schema=False, row_group_size=N_ROWS,
data_page_size=8 * 1024 * 1024, max_rows_per_page=N_ROWS)
options.update(overrides)
if options["use_dictionary"]:
options.pop("column_encoding", None)
pq.write_table(table, path, **options)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--wireform-bench", type=Path, required=True)
parser.add_argument("--rust-bench", type=Path, required=True)
parser.add_argument("--output-dir", type=Path)
args = parser.parse_args()
native, rust = args.wireform_bench.resolve(), args.rust_bench.resolve()
temporary = tempfile.TemporaryDirectory(prefix="wireform-parquet-file-preflight-")
root = args.output_dir or Path(temporary.name)
root.mkdir(parents=True, exist_ok=True)
expected = make_dataset()
for label, binary in (("native", native), ("rust", rust)):
subprocess.run([str(binary), "--write-fixtures", str(root / label)], check=True)
cpp = root / "cpp"
cpp.mkdir(exist_ok=True)
for codec in CODECS:
(cpp / f"{codec}.parquet").write_bytes(write_table(expected, codec).to_pybytes())
for label in ("native", "rust", "cpp"):
directory = root / label
verify(native, directory)
for codec in CODECS:
actual = pq.read_table(directory / f"{codec}.parquet", use_threads=False, pre_buffer=False)
actual.validate(full=True)
assert actual.equals(expected), (label, codec, "full values/schema differ")
print("PASS full values/schema/layout:", label)
changed_id = pa.concat_arrays([expected.column(0).combine_chunks().slice(0, N_ROWS - 1), pa.array([-999999], type=pa.int64())])
changed_bool = pa.concat_arrays([expected.column(3).combine_chunks().slice(0, N_ROWS - 1), pa.array([True], type=pa.bool_())])
changed_schema = pa.schema([pa.field("id", pa.int64(), nullable=True), *list(expected.schema)[1:]])
cases = [
("last-id", expected.set_column(0, expected.schema.field(0), changed_id), {}, "decoded values differ"),
("last-bool", expected.set_column(3, expected.schema.field(3), changed_bool), {}, "decoded values differ"),
("nullable-schema", pa.Table.from_arrays(expected.columns, schema=changed_schema), {}, "schema differs"),
("codec", expected, {"compression": "snappy"}, "codec differs"),
("dictionary", expected, {"use_dictionary": True}, "disable dictionary"),
("v1-pages", expected, {"data_page_version": "1.0"}, "V2 data pages"),
("row-groups", expected, {"row_group_size": 20000}, None),
("multiple-pages", expected, {"max_rows_per_page": 8192}, None),
("page-index", expected, {"write_page_index": True}, "disable page indexes"),
]
for label, table, options, message in cases:
destination = root / ("wrong-" + label)
shutil.copytree(root / "native", destination, dirs_exist_ok=True)
reference_write(table, destination / "uncompressed.parquet", **options)
verify(native, destination, success=False, message=message)
print("PASS rejection:", label)
print("PASS:9 independently-written valid fixtures and9 rejection controls;400000values checked per file")
if __name__ == "__main__":
main()