Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@
</PropertyGroup>

<PropertyGroup>
<BuildType>ManagedOnly</BuildType>
<BuildType Condition="'$(BuildType)' == ''">ManagedOnly</BuildType>
<DuckDbVersion Condition="'$(DuckDbVersion)' == ''">1.5.5</DuckDbVersion>
<!-- Outside obj, because the SDK puts obj\** in DefaultItemExcludes and an item glob under it
silently matches nothing. The version in the path makes the directory the cache key, so a
bump lands in a fresh folder and cannot pick up the previous engine. -->
<DuckDbNativeRoot Condition="'$(DuckDbNativeRoot)' == ''">$(MSBuildThisFileDirectory)artifacts\natives\$(DuckDbVersion)\</DuckDbNativeRoot>
<NoNativeText>This package does not include a copy of the native DuckDB library.</NoNativeText>
</PropertyGroup>
<PropertyGroup Condition="'$(BuildType)' == 'Full' ">
Expand Down
10 changes: 0 additions & 10 deletions DuckDB.NET.Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,4 @@
<ProjectReference Include="..\DuckDB.NET.Bindings\Bindings.csproj" />
</ItemGroup>

<!-- Copy the already-downloaded native DuckDB library into the benchmark output so it can be
loaded via runtimes/{rid}/native/. -->
<ItemGroup>
<None Include="..\DuckDB.NET.Bindings\obj\runtimes\**\*.dll;..\DuckDB.NET.Bindings\obj\runtimes\**\*.so;..\DuckDB.NET.Bindings\obj\runtimes\**\*.dylib;">
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>runtimes\%(RecursiveDir)\%(FileName)%(Extension)</Link>
</None>
</ItemGroup>

</Project>
48 changes: 0 additions & 48 deletions DuckDB.NET.Benchmarks/NativeLibraryLoader.cs

This file was deleted.

86 changes: 86 additions & 0 deletions DuckDB.NET.Benchmarks/ParameterBindingBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using DuckDB.NET.Data;

namespace DuckDB.NET.Benchmarks;

[MemoryDiagnoser]
public class ParameterBindingBenchmark
{
private DuckDBConnection connection = null!;
private DuckDBCommand exact = null!;
private DuckDBCommand prefixed = null!;
private DuckDBCommand positional = null!;

[Params(1, 8, 32)]
public int ParameterCount { get; set; }

[GlobalSetup]
public void Setup()
{
connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();

exact = BuildNamed("");
prefixed = BuildNamed("$");
positional = BuildPositional();
}

[GlobalCleanup]
public void Cleanup()
{
exact.Dispose();
prefixed.Dispose();
positional.Dispose();
connection.Dispose();
}

[Benchmark(Baseline = true)]
public object? NamedExact() => exact.ExecuteScalar();

[Benchmark]
public object? NamedPrefixed() => prefixed.ExecuteScalar();

[Benchmark]
public object? Positional() => positional.ExecuteScalar();

private DuckDBCommand BuildNamed(string parameterNamePrefix)
{
var command = connection.CreateCommand();
var sql = new StringBuilder("SELECT ");

for (var i = 0; i < ParameterCount; i++)
{
if (i > 0)
{
sql.Append(" + ");
}

sql.Append("$p").Append(i).Append("::INT");
command.Parameters.Add(new DuckDBParameter($"{parameterNamePrefix}p{i}", i));
}

command.CommandText = sql.ToString();
return command;
}

private DuckDBCommand BuildPositional()
{
var command = connection.CreateCommand();
var sql = new StringBuilder("SELECT ");

for (var i = 0; i < ParameterCount; i++)
{
if (i > 0)
{
sql.Append(" + ");
}

sql.Append("?::INT");
command.Parameters.Add(new DuckDBParameter(i));
}

command.CommandText = sql.ToString();
return command;
}
}
2 changes: 1 addition & 1 deletion DuckDB.NET.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
.AddJob(Job.Default.WithToolchain(InProcessEmitToolchain.Instance));

BenchmarkSwitcher
.FromTypes([typeof(AppenderBenchmark), typeof(MappedAppenderBenchmark)])
.FromTypes([typeof(AppenderBenchmark), typeof(MappedAppenderBenchmark), typeof(ParameterBindingBenchmark)])
.Run(args, config);
38 changes: 28 additions & 10 deletions DuckDB.NET.Bindings/Bindings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<PropertyGroup>
<Description>DuckDB Bindings for C#.</Description>
<PackageReleaseNotes>
- Updated to DuckDB v1.5.5
- Updated to DuckDB v$(DuckDbVersion)

New features:
- Added bindings for the Arrow C Data Interface (duckdb_to_arrow_schema, duckdb_data_chunk_to_arrow, duckdb_result_get_arrow_options)
- Added bindings for error data (duckdb_appender_error_data, duckdb_error_data_error_type)
</PackageReleaseNotes>
<RootNamespace>DuckDB.NET.Native</RootNamespace>
<RuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx</RuntimeIdentifiers>
<DuckDbArtifactRoot Condition=" '$(DuckDbArtifactRoot)' == '' ">https://github.com/duckdb/duckdb/releases/download/v1.5.5</DuckDbArtifactRoot>
<DuckDbArtifactRoot Condition=" '$(DuckDbArtifactRoot)' == '' ">https://github.com/duckdb/duckdb/releases/download/v$(DuckDbVersion)</DuckDbArtifactRoot>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\keyPair.snk</AssemblyOriginatorKeyFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand All @@ -23,7 +23,16 @@ New features:
</PropertyGroup>

<!-- Download and include the native libraries into the NuGet package-->
<Target Name="DownloadNativeLibs" BeforeTargets="GenerateAdditionalSources" Condition="'$(BuildType)' == 'Full' AND '$(TargetFramework)' == '$([System.String]::Copy($(TargetFrameworks)).Split(%27;%27)[0])'">
<!-- Outer build only. The inner per-framework builds would otherwise race each other for the same
zip, and DispatchToInnerBuilds below makes the outer finish downloading before they start. -->
<PropertyGroup>
<!-- Only the Full package ships the engine, but every project that runs needs it beside the
assembly, so the copy is unconditional and only packing is gated. -->
<_PackDuckDbNatives Condition="'$(BuildType)' == 'Full' ">true</_PackDuckDbNatives>
<_PackDuckDbNatives Condition="'$(_PackDuckDbNatives)' == ''">false</_PackDuckDbNatives>
</PropertyGroup>

<Target Name="DownloadNativeLibs" Condition="'$(TargetFramework)' == '' ">
<ItemGroup Condition="'$(NightlyBuild)' != 'true'">
<_NativeLib Include="win-x64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-amd64.zip" />
<_NativeLib Include="win-arm64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-arm64.zip" />
Expand All @@ -36,20 +45,29 @@ New features:
<_NativeLib Include="linux-x64" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-linux-amd64.zip" InnerZipName="libduckdb-linux-amd64.zip" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<_NativeLib Include="osx" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-osx.zip" InnerZipName="libduckdb-osx-universal.zip" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
</ItemGroup>
<MSBuild Projects="DownloadNativeLibs.targets" Properties="Rid=%(_NativeLib.Identity);LibUrl=%(_NativeLib.LibUrl);InnerZipName=%(_NativeLib.InnerZipName)" />
<MSBuild Projects="DownloadNativeLibs.targets" Properties="Rid=%(_NativeLib.Identity);LibUrl=%(_NativeLib.LibUrl);InnerZipName=%(_NativeLib.InnerZipName);DuckDbVersion=$(DuckDbVersion);DuckDbNativeRoot=$(DuckDbNativeRoot)" />
</Target>
<Target Name="CleanNativeLibs" BeforeTargets="Clean" Condition="'$(BuildType)' == 'Full' ">
<RemoveDir Directories="obj\runtimes" />
<RemoveDir Directories="$(DuckDbNativeRoot)" />
</Target>
<ItemGroup Condition="'$(BuildType)' == 'Full' ">
<None Include="obj\runtimes\**\*.dll;obj\runtimes\**\*.so;obj\runtimes\**\*.dylib;">
<!-- Content, not None, and in the project body rather than a target: Content items flow across a
ProjectReference, so every consumer gets the engine from its reference alone. The glob works
here only because DuckDbNativeRoot sits outside obj, which DefaultItemExcludes would hide. -->
<ItemGroup>
<Content Include="$(DuckDbNativeRoot)**\*.dll;$(DuckDbNativeRoot)**\*.so;$(DuckDbNativeRoot)**\*.dylib">
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Pack>true</Pack>
<Pack>$(_PackDuckDbNatives)</Pack>
<PackagePath>\runtimes</PackagePath>
<Link>runtimes\%(RecursiveDir)\%(FileName)%(Extension)</Link>
</None>
<Link>runtimes\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
</ItemGroup>

<!-- Restore is the only phase before evaluation, and the glob above expands during evaluation.
NuGet runs this target once per project for a solution restore and a single-project restore
alike; the public Restore target only runs on the project the command names. -->
<Target Name="EnsureDuckDbNativeLibs" AfterTargets="_GenerateRestoreGraphProjectEntry"
DependsOnTargets="DownloadNativeLibs" />
<ItemGroup Condition="'$(CI)' == 'true'">
<PackageReference Include="GitVersion.MsBuild" Version="6.4.0" PrivateAssets="all" />
</ItemGroup>
Expand Down
114 changes: 72 additions & 42 deletions DuckDB.NET.Bindings/DownloadNativeLibs.targets
Original file line number Diff line number Diff line change
@@ -1,47 +1,77 @@
<Project DefaultTargets="DownloadReleaseNatives;DownloadNightlyNatives">

<!-- Release: download lib zip directly -->
<Target Name="DownloadReleaseNatives"
Condition="!Exists('$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native') AND '$(InnerZipName)' == ''">
<PropertyGroup>
<NativeZipPath>$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip</NativeZipPath>
</PropertyGroup>
<MakeDir Directories="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native" />
<Delete Files="$(NativeZipPath)"
Condition="Exists('$(NativeZipPath)')"
ContinueOnError="true"/>
<DownloadFile
DestinationFolder="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)"
SourceUrl="$(LibUrl)"
Retries="5"
RetryDelayMilliseconds="1000"
DestinationFileName="native.zip"/>
<Unzip DestinationFolder="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native"
SourceFiles="$(NativeZipPath)"/>
<Delete Files="$(NativeZipPath)"/>
<Project DefaultTargets="DownloadNatives">

<PropertyGroup>
<RidDir>$(DuckDbNativeRoot)$(Rid)</RidDir>
<NativeDir>$(RidDir)\native</NativeDir>
<NativeZipPath>$(RidDir)\native.zip</NativeZipPath>
<TempExtractPath>$(RidDir)\temp</TempExtractPath>
<NativeInputsCacheFile>$(RidDir)\duckdb.inputs.cache</NativeInputsCacheFile>
<NativeDownloadCacheFile>$(RidDir)\duckdb.download.cache</NativeDownloadCacheFile>
</PropertyGroup>

<!-- This file is invoked as a project, not imported, and it carries no Sdk attribute, so it never
auto-imports Directory.Build.props. Every property below arrives from the caller, and an unset
DuckDbVersion would key the cache stamp on an empty string. -->
<Target Name="_ValidateNativeDownloadInputs">
<Error Text="Rid and LibUrl must be supplied by the caller."
Condition="'$(Rid)' == '' OR '$(LibUrl)' == ''" />
<Error Text="DuckDbVersion must be supplied by the caller so the cache stamp is keyed on a version."
Condition="('$(DuckDbVersion)' == '' OR '$(DuckDbNativeRoot)' == '') AND '$(InnerZipName)' == ''" />
</Target>

<!-- The URL carries the version, so writing it to a cache file makes a version change move that
file's timestamp and nothing else does. WriteOnlyWhenDifferent is what holds the timestamp
still when the version has not moved. This is how the SDK lets a value, rather than a source
file, drive incrementality: see CreateGeneratedAssemblyInfoInputsCacheFile in
Microsoft.NET.GenerateAssemblyInfo.targets. -->
<Target Name="_CreateNativeInputsCacheFile" DependsOnTargets="_ValidateNativeDownloadInputs">
<MakeDir Directories="$(RidDir)" />
<WriteLinesToFile Lines="$(LibUrl)"
File="$(NativeInputsCacheFile)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
</Target>

<!-- Nightly: download outer zip, extract inner lib zip -->
<Target Name="DownloadNightlyNatives"
Condition="!Exists('$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native') AND '$(InnerZipName)' != ''">
<PropertyGroup>
<NativeZipPath>$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip</NativeZipPath>
<TempExtractPath>$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\temp</TempExtractPath>
</PropertyGroup>
<MakeDir Directories="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native" />
<Delete Files="$(NativeZipPath)" Condition="Exists('$(NativeZipPath)')" ContinueOnError="true"/>
<DownloadFile
DestinationFolder="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)"
SourceUrl="$(LibUrl)"
Retries="5"
RetryDelayMilliseconds="1000"
DestinationFileName="native.zip"/>
<Unzip DestinationFolder="$(TempExtractPath)"
SourceFiles="$(NativeZipPath)"/>
<Unzip DestinationFolder="$(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native"
SourceFiles="$(TempExtractPath)\$(InnerZipName)"/>
<RemoveDir Directories="$(TempExtractPath)" />
<Delete Files="$(NativeZipPath)"/>
<!-- A release artifact is a zip holding the library. A nightly artifact is a zip holding one
per-platform zip that holds the library, which is the only difference between the two. -->
<Target Name="DownloadNatives"
DependsOnTargets="_CreateNativeInputsCacheFile"
Inputs="$(NativeInputsCacheFile)"
Outputs="$(NativeDownloadCacheFile)">
<Delete Files="$(NativeZipPath)" Condition="Exists('$(NativeZipPath)')" ContinueOnError="true" />

<!-- Fetch before touching the installed payload. Clearing first would leave the build with no
native library at all whenever the download fails. -->
<DownloadFile DestinationFolder="$(RidDir)"
SourceUrl="$(LibUrl)"
Retries="5"
RetryDelayMilliseconds="1000"
DestinationFileName="native.zip" />

<!-- The archive is in hand, so the superseded payload can go. Unzipping over the old files
would keep whatever the new release no longer ships. -->
<RemoveDir Directories="$(NativeDir)" />
<MakeDir Directories="$(NativeDir)" />

<Unzip DestinationFolder="$(NativeDir)" SourceFiles="$(NativeZipPath)"
Condition="'$(InnerZipName)' == ''" />

<Unzip DestinationFolder="$(TempExtractPath)" SourceFiles="$(NativeZipPath)"
Condition="'$(InnerZipName)' != ''" />
<Unzip DestinationFolder="$(NativeDir)" SourceFiles="$(TempExtractPath)\$(InnerZipName)"
Condition="'$(InnerZipName)' != ''" />
<RemoveDir Directories="$(TempExtractPath)" Condition="'$(InnerZipName)' != ''" />

<Delete Files="$(NativeZipPath)" />

<!-- Written last. A failure anywhere above leaves this file absent or older than the inputs
cache, so the next build retries instead of treating a half-populated directory as a
complete one. -->
<WriteLinesToFile File="$(NativeDownloadCacheFile)" Lines="$(LibUrl)" Overwrite="true" />

<ItemGroup>
<FileWrites Include="$(NativeInputsCacheFile);$(NativeDownloadCacheFile)" />
</ItemGroup>
</Target>

</Project>
Loading