Skip to content

feat(vortex): read DuckLake data files in the vortex format (per-file dispatch) - #1

Merged
moshap-firebolt merged 3 commits into
mainfrom
firebolt/vortex-support
Aug 4, 2026
Merged

moshap-firebolt merged 3 commits into
mainfrom
firebolt/vortex-support

Conversation

@moshap-firebolt

@moshap-firebolt moshap-firebolt commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Adds vortex (and general non-parquet) read support to DuckLake, keyed off the per-file file_format column, and vendors the vortex extension so DuckLake + vortex build together.

What this does

  • DuckLake scans data files tagged file_format='vortex' through a generic DuckLakeFormatReader that drives read_vortex (parquet files unchanged). Formats can mix within one table (per-file dispatch), reusing the existing delete-filter / row-id / column-mapping machinery.
  • Threads file_format from ducklake_data_file metadata into the scan; adds a data_file_format catalog option + write-side recording.
  • Vendors vortex-data/duckdb-vortex as the ./vortex submodule and wires it via extension_config.cmake.

Why the base is April-1 DuckLake + DuckDB d8cdaa33
Vortex's C++ glue needs DuckDB's pre-March-24 Vector layout (data/validity members, StringVector::AddBuffer) and its Rust needs the GEOMETRY/VARIANT C-API types. d8cdaa33 (vortex's DuckDB fork) is the one commit that has both, and DuckLake @ Apr-1 is the newest DuckLake that predates the DuckDB Vector refactor / table_index.hpp dependency. This is the only combination where DuckLake + vortex compile together. Two small covariance fixes (unique_ptr<Derived>→Base) let DuckLake build against that DuckDB.

Verified

  • test/sql/vortex/vortex_read_dispatch.test (24 assertions): writes a real .vortex file with vortex's own COPY, tags it in metadata, reads it back through DuckLake with projection + filter pushdown + aggregation.
  • test/sql/format/json_read_dispatch.test (24) and data_file_format_option.test (9): the same mechanism via read_json + option validation.

Known gap (follow-up)
INSERT ... (data_file_format='vortex') is not wired yet: vortex's C-API COPY cannot report per-file statistics (the DuckDB C API has no written-stats setter) and DuckLake's writer requests WRITTEN_FILE_STATISTICS. Next step: route vortex writes through CHANGED_ROWS_AND_FILE_LIST + fstat for size.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core scan/insert and metadata I/O for all tables; behavior change is gated on per-file format, but bugs could mis-route reads or persist wrong format metadata.

Overview
Adds pluggable data-file formats to DuckLake: each data file’s physical format is stored in metadata and used at scan time, while inserts can target parquet or vortex via a new catalog option.

Read path: Metadata now loads ducklake_data_file.file_format into scan file options. Non-parquet files use a new DuckLakeFormatReader that binds and runs the matching read_<format> table function (e.g. read_vortex, read_json), maps columns by name (with rename maps via mapping_id), synthesizes row ordinals, and applies the same deletion and filter pushdown as the parquet path. Parquet still uses the existing reader.

Write / config: ducklake.set_option('data_file_format', …) validates parquet or vortex; GetCopyOptions picks the corresponding COPY function and records file_format on written files instead of hard-coding parquet. Parquet-only options (field IDs, encryption, compression) are skipped for other formats.

Build: Optional vortex submodule and ENABLE_VORTEX=1 in extension_config.cmake; .gitignore adds .cache/.

SQL tests cover option validation, JSON read dispatch, vortex read dispatch (including deletes), and name-map reads after column rename.

Reviewed by Cursor Bugbot for commit 4e9776f. Bugbot is set up for automated code reviews on this repo. Configure here.

moshap-firebolt and others added 2 commits August 4, 2026 12:48
…rtex)

Make the DuckLake scan path honor the per-file file_format column of
ducklake_data_file instead of assuming parquet for every file. Files
recorded as "vortex" are now read via a dedicated reader that drives the
vortex extension's read_vortex table function; parquet files are
unchanged. Formats can be mixed within a single table (per-file
dispatch), reusing the existing delete-filter, row-id and column-mapping
machinery.

- thread file_format from ducklake_data_file into DuckLakeFileData and on
  into OpenFileInfo.extended_info (GetFileSelectList/ReadDataFile gain an
  opt-in flag, enabled only on the data-file scan paths)
- DuckLakeMultiFileReader::CreateReader dispatches "vortex" files to the
  new DuckLakeVortexReader (BaseFileReader), mirroring the existing
  inlined-data reader dispatch
- vendor vortex-data/duckdb-vortex as the ./vortex submodule and load it
  alongside parquet via extension_config.cmake (opt-out with DISABLE_VORTEX)

Write support (recording file_format="vortex" on insert / COPY FORMAT
VORTEX) is intentionally deferred to a follow-up, so end-to-end vortex
round-trip tests land with the write path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tex dispatch

Follow-up to the file_format read dispatch: generalize the reader and add the
write side so a table's data files can be produced and read in a non-parquet
format, verified end-to-end.

- rename DuckLakeVortexReader -> DuckLakeFormatReader: drives read_<format> for
  any non-parquet format (vortex, json, ...), mapping columns by name and
  applying deletes/row-ids/filters via the existing MultiFileReader contract
- write path: data_file_format catalog option (parquet default, or vortex);
  GetCopyOptions routes to the format's COPY, records file_format in metadata,
  and PlanInsert/AddWrittenFiles stamp it. A guard rejects copy functions
  without copy_to_bind (e.g. json/csv plan-based COPY) instead of crashing.
- tests: data_file_format_option (recording + validation), json_read_dispatch
  (proves per-file dispatch end-to-end via read_json, incl. projection/filter
  pushdown), and vortex_format (round-trip; runs where vortex's DuckDB pin is
  aligned with DuckLake's).

Verified on an icecc-farm release build: option 9/9, json_read_dispatch 24/24,
full existing DuckLake suite 503/503 (no regressions). Vortex round-trip is
currently unbuildable here because the latest vortex extension pins DuckDB
d8cdaa33 (Jul-21) while DuckLake main requires c97bd8b9 (Jul-22, adds
table_index.hpp, removes Vector::GetBuffer) - an upstream version-alignment
gap, not a code defect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/storage/ducklake_metadata_manager.cpp
Comment thread src/storage/ducklake_multi_file_list.cpp
Comment thread src/functions/ducklake_flush_inlined_data.cpp
@moshap-firebolt
moshap-firebolt force-pushed the firebolt/vortex-support branch from 5cda6b9 to e7d05b9 Compare August 4, 2026 21:20
Comment thread src/include/common/ducklake_data_file.hpp
@moshap-firebolt
moshap-firebolt force-pushed the firebolt/vortex-support branch 2 times, most recently from d896754 to 9b7110e Compare August 4, 2026 21:46
Comment thread src/storage/ducklake_format_reader.cpp
… dispatch)

Adds vortex (and general non-parquet) read support to DuckLake keyed off the
per-file file_format column, on an aligned DuckDB base where DuckLake + vortex
build together.

- generic DuckLakeFormatReader drives read_<format> and adapts chunks to the
  MultiFileReader contract (column mapping, deletes, filters, file_row_number)
- thread file_format through all scan paths (forward, insertions, delete-scan,
  compaction) and into transaction-local files + DuckLakeDataFile copy/assign
- resolve the written format once on DuckLakeInsertGlobalState so INSERT, CTAS,
  flush and compaction all record it consistently
- data_file_format catalog option; vendor vortex-data/duckdb-vortex submodule
- pin DuckDB to d8cdaa33 (vortex's fork: old Vector layout + GEOMETRY/VARIANT
  C-API); two unique_ptr covariance fixes to build DuckLake against it
- tests: vortex_read_dispatch (read + projection/filter pushdown + delete),
  json_read_dispatch, data_file_format_option

Write path (INSERT -> vortex) is a follow-up: vortex's C-API COPY cannot report
per-file statistics, so DuckLake's WRITTEN_FILE_STATISTICS writer would need to
route through CHANGED_ROWS_AND_FILE_LIST + fstat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@moshap-firebolt
moshap-firebolt force-pushed the firebolt/vortex-support branch from 9b7110e to 4e9776f Compare August 4, 2026 22:20

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4e9776f. Configure here.

for (idx_t r = 0; r < scan_count; r++) {
ordinal_data[r] = file_row_number + NumericCast<int64_t>(r);
}
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordinal column left non-flat

High Severity

DuckLakeFormatReader::Scan writes file_row_number ordinals with FlatVector::GetData but never forces that column back to a flat vector. After a batch is Sliced by deletes or filters, the ordinal column can remain a dictionary vector, so later batches overwrite the child data while the old selection stays active. That corrupts row-ids used by deletes, updates, and any rowid projection on multi-batch vortex/json scans.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4e9776f. Configure here.

@moshap-firebolt
moshap-firebolt merged commit 4fda9e5 into main Aug 4, 2026
28 of 33 checks passed
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