Skip to content

Optimize mapped appends and prepare stable 1.5.5#8

Merged
skuirrels merged 39 commits into
mainfrom
feature/mapped-appender-typed-parameters
Jul 23, 2026
Merged

Optimize mapped appends and prepare stable 1.5.5#8
skuirrels merged 39 commits into
mainfrom
feature/mapped-appender-typed-parameters

Conversation

@skuirrels

@skuirrels skuirrels commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • compile mapped-appender projections into one cached row writer
  • add typed prepared-parameter conversion and binding fast paths
  • specialize LIST/ARRAY writes for arrays, List<T>, and typed enumerable collections
  • use weak collection-plan caching without permanently rooting runtime types
  • retain safe fallback handling for non-SZ arrays and ambiguous collection shapes
  • add focused benchmarks and regression coverage
  • promote the fork package-release baseline and prepare stable v1.5.5

Impact

The change reduces per-row reflection, interface dispatch, boxing, and temporary allocations across mapped appends, prepared commands, and nested collection writes while preserving the existing public API.

Representative LIST/ARRAY results for one million rows with 32 integers:

Workload Before After
Array to LIST 221.6 ms 125.9 ms
List<int> to LIST 339.3 ms 139.7 ms
Array to fixed ARRAY 208.0 ms 123.4 ms

For ReadOnlyCollection<int> at 100,000 rows, runtime improved from 35.59 ms to 16.10 ms and allocations fell from 78,139.55 KB to 3,138.35 KB.

Validation

  • release-mode build completed for net8.0 and net10.0
  • 7,019 tests passed on net8.0
  • 7,019 tests passed on net10.0
  • Skuirrels.DuckDB.NET.Bindings.Full.1.5.5.nupkg created and validated
  • Skuirrels.DuckDB.NET.Data.Full.1.5.5.nupkg created and validated
  • package smoke tests passed on net8.0 and net10.0
  • diff whitespace validation passed

Release

After merge, this change is intended to be published as the stable v1.5.5 release.

Giorgi and others added 30 commits May 24, 2026 23:59
* Add Apache Arrow result streaming to DuckDBCommand

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>

* Honor UseStreamingMode in Arrow APIs and fix result stream cleanup

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.

* Refactor Arrow error handling to typed DuckDBErrorData handle

- 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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the duckdb_error_data_error_type binding and pass the resolved
error type to DuckDBException in ThrowOnError. Also drop unused usings
in DuckDBArrowArrayStream, DuckDBCommand, and DuckDBDataReader.
Switch the appender from duckdb_appender_error (plain string) to
duckdb_appender_error_data, which exposes both the message and the
DuckDB error type. Route all appender error sites through the existing
DuckDBErrorData.ThrowOnError extension (message prefix now optional),
so appender exceptions carry ErrorType, consistent with the Arrow paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWKN1SKSTFDyeqSrA4dfYT
NumericVectorDataReader.GetValue(offset, targetType) backs the non-generic read
surface (GetValue(ordinal), this[ordinal], GetValues). It always ran the boxed
value through Convert.ChangeType, even when targetType already matched the
column's natural type — the common case, since those APIs read using the
column's ClrType.

Convert.ChangeType does not early-out on same-type for IConvertible values: it
dispatches through ToXxx() and re-boxes the result, so every value was boxed
twice. Add a value.GetType() == targetType fast path that returns the already
boxed value directly.

Benchmark (read 1,000,000 rows x 3 numeric columns via GetValue, .NET 10, M4 Pro):

  Before: 54.50 ms, 137.3 MB allocated
  After:  22.73 ms,  68.7 MB allocated

-58% time and -50% allocations on the object-returning path (the remaining
allocation is the unavoidable single box of the object API). The typed
GetFieldValue<T> path is unaffected. Behavior is unchanged: ChangeType returned
the same value for same-type, and BigInteger (not IConvertible) already used
this same early-out inside ChangeType.
PreparedStatement.BindParameters ran a LINQ OfType<>().Any(...) and iterated
the parameters with foreach over the non-generic collection (which boxed the
List<T> struct enumerator) on every command execution. Replace both with
index-based loops over the typed indexer.

This removes ~128 bytes and 2-3 allocations per command execution, which
matters in tight parameterized-query loops. Execution time is unchanged, as the
native prepare/execute dominates.
Prepare consolidated fork preview release
Point fork README to preview packages
Use compact preview package badges
Removed project icon from README.
Updated description to reflect cutting-edge performance work.
@skuirrels skuirrels changed the title Optimize mapped appends and prepare 1.5.5 preview Optimize mapped appends and prepare stable 1.5.5 Jul 23, 2026
@skuirrels
skuirrels merged commit 975bcd1 into main Jul 23, 2026
3 of 6 checks passed
@skuirrels
skuirrels deleted the feature/mapped-appender-typed-parameters branch July 23, 2026 19:55
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.

3 participants