Add Apache Arrow result streaming to DuckDBCommand#334
Conversation
Adds a managed Apache Arrow read surface built on DuckDB's current Arrow C Data Interface (duckdb_to_arrow_schema / duckdb_data_chunk_to_arrow), addressing Giorgi#26. - Bindings: duckdb_result_get_arrow_options, duckdb_to_arrow_schema, duckdb_data_chunk_to_arrow, error-data helpers, and a DuckDBArrowOptions safe handle. - Data: DuckDBArrowArrayStream (IArrowArrayStream) that builds the schema once and converts each data chunk into an Arrow RecordBatch via Apache.Arrow's C importers. - DuckDBCommand.ExecuteArrowStream() and ExecuteArrowBatchesAsync() public APIs. - Apache.Arrow dependency added to DuckDB.NET.Data only. - Tests covering schema, scalar values, nulls, multi-chunk streaming, and the stream API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #334 +/- ##
===========================================
- Coverage 87.59% 87.46% -0.14%
===========================================
Files 75 77 +2
Lines 3038 3134 +96
Branches 445 463 +18
===========================================
+ Hits 2661 2741 +80
- Misses 268 278 +10
- Partials 109 115 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Coverage Report for CI Build 28102732334Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage decreased (-0.1%) to 89.358%Details
Uncovered Changes
Coverage Regressions6 previously-covered lines in 2 files lost coverage.
Coverage Stats💛 - Coveralls |
There was a problem hiding this comment.
Pull request overview
Adds an Apache Arrow read/streaming surface to DuckDB.NET.Data by exposing DuckDB’s Arrow C Data Interface through new DuckDBCommand APIs, plus tests to validate batch/schema/value behavior.
Changes:
- Added
DuckDBCommand.ExecuteArrowStream()andExecuteArrowBatchesAsync()to stream the first result set as Arrow schema/record batches. - Implemented
DuckDBArrowArrayStream : IArrowArrayStreamto convert DuckDB data chunks to Arrow record batches via DuckDB’s Arrow C API. - Added new bindings/safe handles for DuckDB Arrow functions/options and introduced Arrow-focused test coverage.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| DuckDB.NET.Test/Arrow/ArrowResultTests.cs | New tests validating Arrow schema, scalar values, null handling, multi-chunk streaming, and stream semantics. |
| DuckDB.NET.Data/DuckDBCommand.cs | Adds public Arrow streaming APIs on DuckDBCommand. |
| DuckDB.NET.Data/Data.csproj | Adds Apache.Arrow NuGet dependency to DuckDB.NET.Data. |
| DuckDB.NET.Data/Arrow/DuckDBArrowArrayStream.cs | New Arrow array stream implementation bridging DuckDB chunks to Arrow RecordBatch. |
| DuckDB.NET.Bindings/NativeMethods/NativeMethods.Arrow.cs | Adds P/Invoke declarations for DuckDB Arrow C Data Interface functions and error-data accessors. |
| DuckDB.NET.Bindings/DuckDBWrapperObjects.cs | Adds DuckDBArrowOptions safe handle wrapper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Pass UseStreamingMode through ExecuteArrowStream and fetch chunks via the streaming or materialized path accordingly. Dispose Arrow options and close the result if schema building fails in the stream constructor. Add tests for streaming mode, cancellation, and dispose semantics.
|
@Giorgi, I've addressed the feedback. Let me know if there's any other changes or suggestions you have here. Thanks! |
|
Thanks for the PR @luisquintanilla Added some comments. One question: If |
- Add DuckDBErrorData SafeHandle wrapping duckdb_error_data - Move error-data P/Invokes to NativeMethods.ErrorData - Retype duckdb_to_arrow_schema/data_chunk_to_arrow returns to DuckDBErrorData - Add reusable ThrowOnError extension throwing DuckDBException - Make DuckDBArrowArrayStream internal
|
@luisquintanilla Thanks for implementing such a long-standing feature request! It would be great if you could add a page about it to https://github.com/Giorgi/DuckDB.NET-Docs, as I'm not very familiar with Arrow. |
|
Thanks for merging this @Giorgi I was doing some research into your question
I dug into how the rest of the Arrow ecosystem handles this before answering. Short version: I'd keep the Arrow methods on Why single-stream is the norm. The Arrow C Stream Interface defines Why not What I'd propose:
One behavior detail worth deciding now, separate from the bigger question. Because the current method Related: Re: documentation I'll add to it and as mentioned above, I'll document the first result set behavior. Let me know if you need anything else. |
|
We could make |
What's Changed
Adds a managed Apache Arrow read surface to
DuckDB.NET.Data, addressing #26. It is built on DuckDB's current Arrow C Data Interface (duckdb_to_arrow_schema/duckdb_data_chunk_to_arrow), not the deprecatedduckdb_query_arrowfamily, so it should keep working as the legacy API is removed.Two new APIs on
DuckDBCommand:How it works
DuckDB.NET.Bindings): addsduckdb_result_get_arrow_options,duckdb_to_arrow_schema,duckdb_data_chunk_to_arrow, theduckdb_error_dataaccessors, and aDuckDBArrowOptionssafe handle.DuckDB.NET.Data): aDuckDBArrowArrayStream : IArrowArrayStreambuilds theArrowSchemaonce from the result column types/names, then loopsduckdb_fetch_chunkand converts each chunk into an ArrowRecordBatch. The nativeArrowSchema/ArrowArraystructs are handed straight toApache.Arrow.C.CArrowSchemaImporter.ImportSchemaandCArrowArrayImporter.ImportRecordBatch, so all type mapping is delegated to DuckDB's own converter (no per-type C# code).Apache.Arrowpackage dependency lives inDuckDB.NET.Dataonly;DuckDB.NET.Bindingsstays free of it.Open question
The main thing to confirm is where the
Apache.Arrowdependency should sit. This PR puts it inDuckDB.NET.Data, withBindingsonly exposing the native structs/functions. Happy to move it if you would prefer a separate package (e.g.DuckDB.NET.Data.Arrow) so that callers who do not need Arrow do not take the dependency.Scope
In scope (this PR):
Deferred (follow-ups if the direction is welcome):
Tests
Adds
ArrowResultTestscovering schema shape, scalar values, nulls, multi-chunk streaming (range(5000)yields multiple batches), and theExecuteArrowStream()path. The full existing suite (6886 tests) still passes locally.Opening as a draft pending your call on the dependency-location question above.