Skip to content

Add DeltaLakeService to resolve a Delta table's parquet files - #911

Closed
SeanDuHare wants to merge 1 commit into
prep/delta-lake-groundworkfrom
feature/delta-lake-service
Closed

Add DeltaLakeService to resolve a Delta table's parquet files#911
SeanDuHare wants to merge 1 commit into
prep/delta-lake-groundworkfrom
feature/delta-lake-service

Conversation

@SeanDuHare

Copy link
Copy Markdown
Contributor

Second PR in the Delta Lake stack. Base branch is prep/delta-lake-groundwork,
not main — review only this branch's own diff (two new files, nothing else touched).

Adds the service that works out which parquet files make up a Delta Lake table.
Nothing calls it yet; wiring it into DatabaseService is the next PR.

Why replay the log instead of scanning the directory

Files that a later UPDATE, DELETE, MERGE or OPTIMIZE superseded are still on
disk. Only the log knows they are no longer part of the table.

Measured on a real delta-rs table with one delete in its history: scanning every
parquet returned 3 rows (2 of them deleted); log replay returned 1, matching the
deltalake reference reader.

Why this also unblocks non-S3 hosting

Every request is a GET at a path the Delta protocol makes deterministic, and the
log is never listed. Object listing is an S3 API, not an HTTP one — so a table on
nginx with autoindex off, Azure blob, or a CDN works exactly like one on S3.

Two error-handling decisions worth a look

403 counts as "absent" alongside 404. An S3 bucket granting s3:GetObject but
not s3:ListBucket answers 403 for a key that does not exist — the usual setup for
a public read-only bucket, including this repo's own datasets bucket (verified).
Without this the commit walk cannot find its end. The trade: a genuinely
unreadable table reports "no data files found" rather than "access denied".

Unsupported table features are rejected, not approximated. Deletion vectors,
column mapping, reader v3+, and multi-part or v2 checkpoints each raise a specific
error rather than silently returning wrong rows.

Design note

Reading a checkpoint needs a parquet reader, which this class has no business
owning — so it takes a CheckpointReader callback, supplied by DatabaseService
in the next PR. That keeps this service purely about transport, and free of DOM
APIs so it can run inside the DuckDB web worker.

Verification

  • 22 unit tests: commit replay, checkpoint seeding, s3: → https conversion,
    aged-out logs, 403-as-absent, each rejected feature. One asserts no request ever
    contains list-type.
  • End-to-end against three delta-rs tables served with directory listing disabled
    — file lists match the reference reader exactly.
  • typeCheck, lint and the full suite pass on this commit alone.

Delta Lake does not modify the parquet format: a table is a directory of
ordinary parquet data files plus a "_delta_log" transaction log. DuckDB
has a delta extension but it is not built for wasm, so to read one we
have to work out the file list ourselves.

Scanning every parquet under the table root is not enough. Files a later
UPDATE, DELETE or OPTIMIZE superseded are still on disk, and only the log
knows they are no longer part of the table -- scanning them resurrects
deleted rows and duplicates updated ones. So this replays the log:
seed from the checkpoint, then apply each commit's add/remove actions.

Every request is a GET at a path the protocol makes deterministic, and
the log is never listed. That is what lets a table on a plain web server
(nginx with autoindex off, Azure blob, a CDN) work the same as one on S3
-- object listing is an S3 API, not an HTTP one.

Reading the checkpoint needs a parquet reader, which this class has no
business owning, so it takes a CheckpointReader callback instead.

Two things worth knowing about the error handling. 403 is treated as
"absent" alongside 404, because an S3 bucket granting s3:GetObject but
not s3:ListBucket answers 403 for a key that does not exist -- the usual
setup for a public read-only bucket, and without this the walk cannot
find its end. And table features that change how data files are read
(deletion vectors, column mapping, reader v3+, multi-part and v2
checkpoints) are rejected outright rather than silently misread.

The tests serve a log from memory over a stubbed HTTP client. Most cases
hand-write it, so one replays a verbatim delta-rs log kept as a fixture
-- otherwise the suite would only prove we parse our own idea of the
format, not what a real writer emits.
@SeanDuHare SeanDuHare closed this Aug 20, 2026
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