Skip to content

Read Parquet files via Parquet2.jl - #43

Merged
davidanthoff merged 2 commits into
mainfrom
parquet2-backend
Aug 31, 2026
Merged

davidanthoff merged 2 commits into
mainfrom
parquet2-backend

Conversation

@davidanthoff

@davidanthoff davidanthoff commented Aug 31, 2026 •

Copy link
Copy Markdown
Member

ParquetFiles was pinned to Parquet = "0.2, 0.3, 0.4". Parquet.jl in that range depends on Thrift.jl ≤0.6, which depends on BinaryProvider, so the package could not be installed at all on a current Julia — Pkg.resolve() failed before any code ran.

This moves the backend to Parquet2.jl, a pure Julia implementation (Thrift2 plus the ChunkCodec stack — no BinaryProvider), and the package works again.

Why Parquet2 rather than a newer Parquet.jl

The old API this was written against (ParFile, RecCursor, JuliaBuilder, JuliaConverter, the codegen form of schema) was removed from Parquet.jl around 0.5/0.6, so a rewrite was required regardless of the backend. Given that, Parquet2 costs nothing extra and buys:

  • lighter dependencies — Thrift2 (MacroTools, OrderedCollections, PrecompileTools) instead of Thrift 0.8, which pulls a C++ compiler JLL plus Distributed/Sockets;
  • more recent releases — 0.2.34 in Aug 2026 on a steady cadence, against Parquet.jl's roughly annual patch;
  • Parquet.jl's own README, which now recommends Parquet2.jl (and DuckDB.jl) as alternatives.

Parquet.jl keeps the better bus factor (JuliaIO org) and a lower Julia floor, and neither project is adding features — both authors point at DuckDB.jl. DuckDB was considered and set aside as a large binary dependency for what is a thin FileIO loader.

This supersedes #25, which attempted the same rewrite against Parquet.jl's RecordCursor and has been blocked on an upstream release since 2020.

What changed

Parquet2 exposes the file as a Tables.jl column source, so the row-cursor machinery is gone: the @generated iterator and the row struct that was gensym'd and eval'd into the module on every read (a world-age hazard) are replaced by reading columns and handing them to TableTraitsUtils.create_tableiterator, which already maps Union{Missing,T} to DataValue. Net 78 lines changed in src, and the file is shorter than before despite doing more.

Because Parquet2 is columnar, the package now also implements the TableTraits column interface, so sinks such as DataFrame no longer go row by row. Base.collect is forwarded too, matching CSVFiles.

Two behaviours are deliberately preserved:

  • Byte array columns with no UTF8 annotation still arrive as Vector{UInt8} and are decoded to String, as this package has always presented them. The vendored fixture is written by Impala 1.1.1 and predates the annotation, so it covers this path.
  • Parquet2 memory maps by default and its string columns are views onto that buffer, so columns are materialized before the dataset is closed. Otherwise the mapping outlives the read and keeps the file locked on Windows — there is a test item covering it.

Tests

The suite previously reached into Parquet.jl's own package directory and included its test/get_parcompat.jl, which git-clones the parquet-compatibility repo for a fixture. That depended on another package's internal test layout, on network access, and on the depot being writable. The 3 KB file is now vendored under test/data/ with its Apache-2.0 provenance recorded.

Three test items pass locally on Julia 1.11. Row values are unchanged; they are now DataValue-wrapped because Impala writes these columns as optional. The show prefix assertions moved from s[1:100] to first(s, 100) — the byte slice now splits a multibyte box-drawing character.

julia compat moves to 1.10, which Parquet2 0.2.28+ requires.

Queryverse needs a matching floor bump: queryverse/Queryverse.jl#65.

32-bit is excluded

CI does not run the 32-bit legs, because Parquet2 does not precompile there. Its own precompilation workload reads a bundled sample file and throws while loading an INT64-backed DateTime column:

BoundsError: attempt to access 80-element view(::Vector{UInt8}, 1:80) with eltype UInt8 at index [1:160]
@ Parquet2 src/read.jl:368

That is 20 rows at sizeof(Int64) against a buffer holding 20 rows at sizeof(Int) — INT64-backed columns get half the space they need when Int is 32 bits, and every index in the stack is an Int32.

Since the failure is in the dependency's precompilation, nothing in ParquetFiles can work around it: using ParquetFiles cannot succeed on 32-bit regardless of how this package is written. (The vendored fixture has only Int32 and byte-array columns and would not trip the bug, but it never gets the chance to load.)

No support is lost in practice — before this PR the package could not be installed on any architecture. The bug is not yet reported upstream; once it is fixed and released, the two include-*-x86 lines can be dropped and a Parquet2 compat floor raised in a follow-up.

ParquetFiles was pinned to Parquet.jl 0.2-0.4, which depends on Thrift.jl
<=0.6 and therefore BinaryProvider. That chain is no longer installable, so
the package could not be resolved at all on a current Julia.

Parquet2.jl is a pure Julia implementation (Thrift2 plus the ChunkCodec
stack) and exposes the file as a Tables.jl column source. That replaces the
whole row-cursor machinery: the generated iterator, and the row struct that
was gensym'd and eval'd into the module on every read, are gone in favour of
reading the columns and handing them to TableTraitsUtils.create_tableiterator,
which already maps Union{Missing,T} columns to DataValue.

The old API this was written against (ParFile, RecCursor, JuliaBuilder,
JuliaConverter) was removed from Parquet.jl years ago, so a rewrite was
needed regardless of the backend. Parquet.jl's own README now points at
Parquet2.jl, which also has much lighter dependencies and more recent
releases. This supersedes the stale #25.

Because Parquet2 is columnar, the package can now also implement the
TableTraits column interface, so sinks such as DataFrame no longer have to
go row by row.

Byte array columns with no UTF8 annotation still come back as Vector{UInt8}
and are decoded to String, preserving how this package has always presented
them.

Parquet2 memory maps by default and its string columns are views onto that
buffer, so the columns are materialized before the dataset is closed. Without
that the mapping outlives the read and keeps the file locked on Windows;
there is a test item covering it.

The tests no longer reach into Parquet.jl's own test directory to git clone
parquet-compatibility for a fixture. That file is vendored instead, so the
suite no longer depends on another package's test layout or on network access.

julia compat moves to 1.10, which is what Parquet2 0.2.28+ requires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Parquet2 does not precompile on 32 bit. Its own precompilation workload
reads a bundled sample file and throws while loading an INT64 backed
DateTime column:

  BoundsError: attempt to access 80-element view(::Vector{UInt8}, 1:80)
  with eltype UInt8 at index [1:160]
  @ Parquet2 src/read.jl:368

That is 20 rows at sizeof(Int64), against a buffer holding 20 rows at
sizeof(Int), so INT64 backed columns get half the space they need when Int
is 32 bits. Every index in the stack is an Int32.

Because this happens during precompilation of the dependency, no amount of
care in ParquetFiles can work around it: `using ParquetFiles` cannot succeed
on 32 bit. Our own fixture only has Int32 and byte array columns and would
not trip the bug, but it never gets the chance to load.

No support is lost in practice. Before the move to Parquet2 the package
could not be installed on any architecture at all.

All eight failing CI legs were the 32 bit ones; every x64 and aarch64 leg
passes. IterableTables already disables these same two legs with the same
surrounding settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@davidanthoff
davidanthoff merged commit 423865e into main Aug 31, 2026
22 checks passed
@davidanthoff
davidanthoff deleted the parquet2-backend branch August 31, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant