Hyperduck aims to be the fastest C# DuckDB interfacing library available. It does so by ignoring higher level constructs such as ADO adapters and automatic type conversion and instead focus on Mechanical Sympathy. In other words: DuckDB uses vectors and chunks, C# loves Spans, this is a match made in heaven, and libraries should operate on that level.
The DuckDB C-api follows a fairly clear structure:
- A function to get or create an opaque class of some type
- Methods for manipulating that class
- A method to destroy the class when done
These are mapped (by hand) in this library to the following C# constructs:
- A static method to create or get a reference to a construct
- A C#
unsafe structthat wraps thevoid*for this type - Methods on the struct that adapt the
Capi functions to C# friendly types - If required, implement
IDisposableand[MustDisposeResource](from JetBrains Annotations) to guide users about proper usage
A fairly basic example of how this looks is the implementation of an user defined Scalar functions. In other DuckDB
interop libraries the interface is a very C# native Func<int, int, int> interface for an "add"-like function. While
this interface is easy to use from C# perspective it has many drawbacks. One of the primary problems is that it doesn't
expose the DuckDB optimizations around vectorized chunks. Instead HyperDuck provides users with a input readon-only chunk
and an output vector. This means that implementers of this function can use SIMD and C# vectors
to mass-process inputs to the function.
public override void Execute(ReadOnlyChunk chunk, WritableVector vector)
{
var arg1 = chunk[0].GetData<int>();
var arg2 = chunk[1].GetData<int>();
var outData = vector.GetData<int>();
for (var i = 0; i < outData.Length; i++)
{
outData[i] = arg1[i] * arg2[i];
}
}