Skip to content

fix: bound Parquet storage streaming - #671

Merged
cofin merged 5 commits into
mainfrom
fix/storage-parquet-streaming
Aug 2, 2026
Merged

fix: bound Parquet storage streaming#671
cofin merged 5 commits into
mainfrom
fix/storage-parquet-streaming

Conversation

@cofin

@cofin cofin commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Arrow storage streaming now reads one Parquet row group at a time instead of pulling whole objects into memory, and the storage payload codecs reject or mishandle fewer cases. This also cuts the v0.58.0 release.

What changed

  • Streaming is bounded by a row group, not by the object size. Local, fsspec, and obstore backends each read one Parquet row group at a time. The obstore backend reads through its seekable reader rather than buffering the entire object into memory first.
  • Streaming is explicitly Parquet-only. stream_arrow_sync() and stream_arrow_async() take a keyword-only file_format="parquet" and batch_size=65_536, validated before any storage call. Other formats raise StorageCapabilityError.
  • Readers close deterministically. Closing a sync generator, or calling aclose() on the async iterator, closes the active storage reader.
  • Large JSONL rows decode again. A JSONL payload containing a row bigger than 1 MiB previously failed with ArrowInvalid: straddling object straddles two block boundaries. The reader block is now sized to the payload.
  • Unsupported storage formats raise StorageCapabilityError rather than ValueError, so callers can catch one SQLSpec error type. A non-positive batch_size stays a ValueError, since that is an argument error rather than a missing capability.

How you use it

backend = registry.get("s3://bucket/exports")

for batch in backend.stream_arrow_sync("year=2026/*.parquet", batch_size=10_000):
    process(batch)

Memory is bounded by one Parquet row group, so choose the row-group size when writing the files. Async is the same call with stream_arrow_async() and async for.

Closes #667.

@codecov-commenter

codecov-commenter commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.87395% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.30%. Comparing base (6de8411) to head (91589ac).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
sqlspec/storage/backends/obstore.py 81.25% 7 Missing and 2 partials ⚠️
sqlspec/storage/backends/base.py 58.82% 4 Missing and 3 partials ⚠️
sqlspec/adapters/adbc/core.py 83.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #671   +/-   ##
=======================================
  Coverage   77.30%   77.30%           
=======================================
  Files         475      476    +1     
  Lines       67968    68052   +84     
  Branches     9347     9357   +10     
=======================================
+ Hits        52541    52608   +67     
- Misses      12025    12036   +11     
- Partials     3402     3408    +6     
Flag Coverage Δ
integration 60.89% <43.69%> (-0.03%) ⬇️
py3.10 75.62% <83.19%> (+<0.01%) ⬆️
py3.11 75.63% <83.19%> (+<0.01%) ⬆️
py3.12 75.64% <83.19%> (+0.01%) ⬆️
py3.13 75.63% <83.19%> (-0.01%) ⬇️
py3.14 76.52% <84.03%> (+0.01%) ⬆️
unit 64.85% <82.35%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sqlspec/protocols.py 100.00% <100.00%> (ø)
sqlspec/storage/_arrow_payload.py 93.61% <100.00%> (+0.28%) ⬆️
sqlspec/storage/_arrow_stream.py 100.00% <100.00%> (ø)
sqlspec/storage/backends/fsspec.py 90.64% <100.00%> (+0.09%) ⬆️
sqlspec/storage/backends/local.py 81.50% <100.00%> (+0.18%) ⬆️
sqlspec/storage/pipeline.py 86.19% <100.00%> (ø)
sqlspec/adapters/adbc/core.py 82.74% <83.33%> (+0.09%) ⬆️
sqlspec/storage/backends/base.py 88.65% <58.82%> (-6.47%) ⬇️
sqlspec/storage/backends/obstore.py 83.12% <81.25%> (-0.89%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cofin
cofin force-pushed the fix/storage-parquet-streaming branch from 369bcfe to e129289 Compare August 1, 2026 22:39
@cofin cofin changed the title fix(#667): bound Parquet storage streaming fix: bound Parquet storage streaming Aug 2, 2026
@cofin
cofin marked this pull request as ready for review August 2, 2026 16:10
cofin added 2 commits August 2, 2026 16:24
Decoding a JSONL payload failed when any single row exceeded PyArrow's
default 1 MiB JSON block size, raising "ArrowInvalid: straddling object
straddles two block boundaries". The reader block is now sized to the
payload, so row size is bounded by the payload rather than by a fixed
default.

Unsupported storage formats now raise StorageCapabilityError instead of
ValueError, matching the rest of the storage layer and letting callers
catch a single SQLSpec error type. Requests for a batch_size that is not
greater than zero remain a ValueError, since that is an argument error
rather than a missing capability.

Compile the Parquet streaming helpers with the rest of the storage
package.
Two changes in this release alter behavior that previously succeeded, so
this is a minor rather than a patch release: migration configuration now
rejects keys SQLSpec does not read, and storage writes reject formats
that cannot carry the payload being written.

Refresh dependency locks and pre-commit hook versions.
@cofin
cofin force-pushed the fix/storage-parquet-streaming branch from e129289 to 96a6b49 Compare August 2, 2026 16:25
@cofin cofin changed the title fix: bound Parquet storage streaming fix: bound Parquet storage streaming and release v0.58.0 Aug 2, 2026
@cofin cofin changed the title fix: bound Parquet storage streaming and release v0.58.0 fix: bound Parquet storage streaming Aug 2, 2026
adbc-driver-postgresql refuses to start a transaction on a connection
that is already in an error state. The rollback issued after a failed
statement went through a cursor, so it needed exactly that transaction
and failed, and the failure was swallowed. The connection stayed
unusable and every later statement raised
"INVALID_STATE: [libpq] cannot start transaction".

Roll back through the connection instead, falling back to the cursor
statement when a driver exposes no connection handle.

Uncommitted work in the aborted transaction is discarded, which is what
PostgreSQL requires, so the resilience test now commits the rows it
expects to outlive the failure.
@cofin
cofin merged commit 5b222ed into main Aug 2, 2026
38 of 41 checks passed
@cofin
cofin deleted the fix/storage-parquet-streaming branch August 2, 2026 17:53
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.

Make Arrow batch streaming row-group-bounded and explicitly Parquet-only

2 participants