Skip to content

Prepare consolidated fork preview release#2

Merged
skuirrels merged 12 commits into
developfrom
release/fork-1.5.4
Jul 21, 2026
Merged

Prepare consolidated fork preview release#2
skuirrels merged 12 commits into
developfrom
release/fork-1.5.4

Conversation

@skuirrels

Copy link
Copy Markdown
Owner

What changed

  • consolidates the fork performance work on top of upstream develop
  • adds opt-in Skuirrels.DuckDB.NET.* preview package identities
  • packages only the bundled Data.Full and Bindings.Full variants
  • adds a GitHub prerelease workflow using NuGet OIDC trusted publishing
  • validates package metadata, all native runtime assets, and a clean scoped-writer smoke test

Why

The upstream pull requests will take time to merge and release. This provides an explicitly unofficial 1.5.4-preview.1 build for testing in another open-source project without colliding with the official NuGet package IDs.

Validation

  • Release build succeeded for net8.0 and net10.0
  • 7,005 tests passed, 0 failed
  • generated package dependency verified as Skuirrels.DuckDB.NET.Data.Full -> Skuirrels.DuckDB.NET.Bindings.Full
  • all Windows, Linux, and macOS native assets verified
  • clean local-feed restore and scoped appender writer smoke test passed against DuckDB v1.5.4

The existing CA2265 span/null analyzer warning remains non-blocking.

skuirrels added 12 commits July 12, 2026 00:58
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.
@skuirrels
skuirrels merged commit 2e16e69 into develop Jul 21, 2026
5 of 8 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