parquet_reader
Stable type · Native arrays and readers
Tier: stable · Kind: type · Topic: Native arrays and readers
| Python | Julia | Rust |
|---|---|---|
ParquetReader | ParquetReader | ParquetReader |
Python
class ParquetReader
ParquetReader.formats() -> List[str]
ParquetReader.extensions() -> List[str]
ParquetReader.open(blob_path: Any) -> Any
ParquetReader.read_native(handle: Any, variables: Optional[Sequence[str]] = None, select: Optional[Any] = None, *, float_columns: Optional[Sequence[str]] = None, null_int: Optional[int] = None, null_string: Optional[str] = None, **_: Any) -> NativeDataset
The active parquet reader — an Apache Parquet file as a flat table.
Decode is delegated to pyarrow (pyarrow.parquet), imported lazily
so a base install stays lean. pyarrow is the reference Apache Arrow/Parquet
implementation and the peer of the Rust track’s parquet crate, so the
Arrow type surface below is literally the same enum on both sides — the
reason cross-language parity is cheap here. Nothing about the format is
re-implemented; what this reader owns is the mapping onto the NATIVE-ARRAY
contract (spec/conformance.md §3, “Parquet decode notes”).
A table, not a grid. Like the csv/ff10/shapefile readers,
every column becomes a rank-1 field over index, keyed by its on-disk
column name, and the dataset carries no coordinates. index has
length num_rows. A zero-row file is typed, not absent: the schema
lives in the footer, so every column comes back empty with its declared
dtype — which matters because most of a MOVES fixture’s ~770 tables are
empty and a document binding one must still see the array it named.
Type mapping (a total function of the Arrow type):
========================================== =========
Arrow type dtype
========================================== =========
Boolean bool
Int8/Int16/Int32/UInt8/UInt16 int32
Int64/UInt32/UInt64 int64
Float16/Float32/Float64 float64
Decimal128/Decimal256 float64 (unscaled ÷ 10^scale)
Utf8/LargeUtf8/Utf8View string
Date32/Time32 int32 (raw, undecoded)
Date64/Time64/Timestamp/Duration int64 (raw, undecoded)
Dictionary(_, V) as V, expanded
Null float64, all NaN
========================================== =========
The narrow/wide integer split is the NetCDFReader
’s, verbatim,
so a MOVES int32 ID column and a CF int32 time axis cannot drift
apart. A uint64 above int64 max is an error naming the column and
row, never a wraparound into a negative ID. Nested and binary columns have
no rank-1 reading: naming one in variables is an error, unrequested it is
simply not a field (as the NetCDF reader skips its non-numeric variables).
Null policy. Nearly every Parquet column is nullable in its schema
whether or not it holds a null — a table exported from a relational database
usually marks every column nullable — so nullability cannot pick the dtype.
A null in a float column is NaN; a null in an integer/string/boolean
column is an error, opened only by a declared null_int / null_string.
Column projection pushes down. variables becomes a pyarrow column
projection, so only those column chunks are read off disk — not
read-then-discarded. These tables are wide (a MOVES table runs to dozens of
columns) and a document typically wants three. Empty variables reads
every column; a requested name absent from the file is an error listing what
is present, never a silently missing array.
READER-ONLY (Risk R3): no row selection, no filtering, no code mapping, no
name remap, no unit conversion. esm-spec §8.9 puts codes,
record_filter, select and extent downstream of the decode, and
select never reaches a whole-file reader at all (the Provider hands one
whole-file selection unconditionally).
reader_kwargs: float_columns=[...] forces the named columns to
float64 whatever their on-disk type — the Parquet twin of the
ShapefileReader’s numeric_columns — and does two jobs, because they
are the same statement about the source: an integer column that is really a
measurement (missing cells → NaN rather than a sentinel), and a
column of fixed-decimal text. A corpus that needs byte-reproducible
floats stores them as decimal strings rather than IEEE doubles — the MOVES
snapshots write meanBaseRate as "261.000000000000" — and this is how
a document says so. null_int=n / null_string="…" declare the null
substitutes above. None of the three enters the cache key.
Members
read_native— Decode a Parquet blob into a flat table of rank-1indexfields.
Julia
struct ParquetReader <: Reader
ParquetReader()
ParquetReader()
The parquet format reader — an Apache Parquet file as a flat table.
Every column becomes a rank-1 field over index, keyed by its on-disk column
name, and a table produces no coordinates (like [CSVReader] and
[ZarrReader]). index has length num_rows; a zero-row file still yields
every column, empty and correctly typed (the schema is in the footer, so an
empty table is TYPED, not absent — most of a MOVES fixture’s ~770 tables are
empty, and a document binding one must still see the array it named).
Decode is delegated to Parquet2.jl, loaded LAZILY via a weakdep extension
(EarthSciIOParquet2Ext, active on using Parquet2) — mirroring the shapefile
reader’s Shapefile.jl and the Python track’s lazy pyarrow import. Calling
read_native without using Parquet2 throws a clear install hint.
Type mapping (spec/conformance.md §3, and [_parquet_kind]): Parquet carries
an explicit logical type per column, so the dtype is a total function of it —
Boolean→Bool; Int8/Int16/Int32/UInt8/UInt16→Int32 and
Int64/UInt32/UInt64→Int64 (the SAME narrow/wide split as [NetCDFReader]);
floats and Decimal→Float64 (unscaled ÷ 10^scale); Utf8→String; a
dictionary-encoded column reads as its VALUE type, expanded to one value per row;
an all-null column is Float64/all-NaN. Temporal columns ride as their RAW
stored integer, undecoded (Date32/millisecond-Time32→Int32,
Date64/Time64/Timestamp→Int64): the unit and any timezone are not applied,
because an epoch offset → instant is ESS’s job (Risk R3). A uint64 past
typemax(Int64) is an error naming the column and row, never a wraparound.
Nested and binary columns have no rank-1 reading: naming one in variables is an
error, and unrequested it is simply not a field.
Null policy. Nearly every Parquet column is nullable in its schema whether or
not it holds a null (a table exported from a relational database usually marks
every column nullable), so nullability cannot pick the dtype. A null in a FLOAT
column becomes NaN; a null in an INTEGER, STRING or BOOLEAN column is an
ERROR naming the column and the row, because those types have no NaN and any
default would be a real value silently standing in for a missing one.
reader_kwargs: variables=[...] is the projection and is PUSHED DOWN — only
those column chunks are read off disk (these tables are wide and a document
typically wants three columns); an absent name is an error listing what is
present. float_columns=[...] forces the named columns to Float64 whatever
their on-disk type — the Parquet twin of [ShapefileReader]’s numeric_columns
— and does double duty: an integer column whose missing cells must be NaN, AND
a column of fixed-decimal TEXT (a corpus needing byte-reproducible floats stores
them as decimal strings; the MOVES snapshots write meanBaseRate as
"261.000000000000"), trimmed and parsed, blank→NaN, anything else
unparseable an error naming column/row/text. null_int=n substitutes an integer
sentinel for a null AND reports it back in the field’s attrs["fill_value"]
(an integer sentinel cannot be NaN, so it survives exactly as a CF integer fill
does); null_string=s substitutes text, which is then indistinguishable from a
real cell holding it — which is why the document has to choose it. A boolean has
no such option.
READER-ONLY (Risk R3): row selection, record_filter, codes and extent are
esm-spec §8.9 work DOWNSTREAM of the decode (and a whole-file reader never even
sees a select), and there is no name remap and no unit conversion.
Parquet2.jl backend limits
A file containing a nested (
list/struct/map) column cannot be OPENED by Parquet2.jl at all, so such a file errors rather than decoding its readable columns — the one place this track cannot reach the §3 contract’s “unrequested, it is simply not a field”. Parquet2.jl decodes aTimestampinto a millisecond-resolutionDateTimebefore this reader sees it, so the raw integer of a MICROS/NANOS timestamp column is recovered only to millisecond granularity. AFloat16column is not decoded at all, so it is simply not a field here where the other two tracks return float64. And it decodes aDecimalinto aDec64(16 significant decimal digits) during page decode, so a widerDecimal128/Decimal256cannot be read here at the full precision the Rust and Python tracks get from the unscaled integer; within binary64’s exact range the three agree bit for bit (seeext/EarthSciIOParquet2Ext.jl).One further Parquet2.jl defect is REPAIRED rather than tolerated: it folds a
FIXED_LEN_BYTE_ARRAYdecimal’s big-endian two’s-complement bytes into anInt64with no sign extension, so every negative cell in a column narrower than 8 bytes decoded as its huge unsigned reinterpretation. The extension undoes that exactly. The one width it cannot reach is 7 bytes (pyarrow precision 15-16), where the unsigned fold overflowsDec64inside the backend; a negative there is an error naming the limit, never a wrong number.
Rust
pub struct ParquetReader {
/* private fields */
}
impl ParquetReader { pub fn new() -> Self }
impl ParquetReader { pub fn float_columns<I, S>(self, columns: I) -> Self where I: IntoIterator<Item = S>, S: Into<String> }
impl ParquetReader { pub fn null_int(self, sentinel: i64) -> Self }
impl ParquetReader { pub fn null_string(self, text: impl Into<String>) -> Self }
The active parquet reader: an Apache Parquet file as a flat table on
index. See the module docs for the type and null policies.