diff --git a/Directory.Build.props b/Directory.Build.props index 1a34c2b4..b85d3fa8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ - netstandard2.0;net6.0 + netstandard2.0;net6.0;net8.0 False default @@ -37,6 +37,10 @@ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + net8.0 + ManagedOnly diff --git a/DuckDB.NET.AOT/AOT.csproj b/DuckDB.NET.AOT/AOT.csproj new file mode 100644 index 00000000..eef2dc25 --- /dev/null +++ b/DuckDB.NET.AOT/AOT.csproj @@ -0,0 +1,41 @@ + + + + Exe + net8.0 + enable + enable + true + true + + + + + + + + + + + + + false + PreserveNewest + \ + %(FileName)%(Extension) + + + false + PreserveNewest + \ + %(FileName)%(Extension) + + + false + PreserveNewest + \ + %(FileName)%(Extension) + + + + diff --git a/DuckDB.NET.AOT/Program.cs b/DuckDB.NET.AOT/Program.cs new file mode 100644 index 00000000..739eded5 --- /dev/null +++ b/DuckDB.NET.AOT/Program.cs @@ -0,0 +1,54 @@ +using DuckDB.NET.Data; + +namespace DuckDB.NET.AOT +{ + internal class Program + { + static void Main(string[] args) + { + using var conn = new DuckDBConnection(DuckDBConnectionStringBuilder.InMemoryConnectionString); + conn.Open(); + using (var comm = conn.CreateCommand()) + { + comm.CommandText = "SELECT 1"; + comm.ExecuteNonQuery(); + + comm.CommandText = "SELECT 1"; + using (var reader = comm.ExecuteReader()) + { + while (reader.Read()) + { + _ = reader.GetInt32(0); + } + } + + comm.CommandText = "SELECT [1,2,3]"; + using (var reader = comm.ExecuteReader()) + { + while (reader.Read()) + { + Console.Write("["); + foreach (var item in (List)reader[0]) + { + Console.Write(item+","); + } + Console.WriteLine("]"); + } + } + comm.CommandText = "select {\"a\":1,\"b\":2};"; + using (var reader = comm.ExecuteReader()) + { + while (reader.Read()) + { + Console.Write("{"); + foreach (var item in (Dictionary)reader[0]) + { + Console.Write($"{item.Key}={item.Value}, "); + } + Console.WriteLine("}"); + } + } + } + } + } +} diff --git a/DuckDB.NET.Bindings/Bindings.csproj b/DuckDB.NET.Bindings/Bindings.csproj index ee4b06f7..c69d44b4 100644 --- a/DuckDB.NET.Bindings/Bindings.csproj +++ b/DuckDB.NET.Bindings/Bindings.csproj @@ -4,7 +4,7 @@ DuckDB Bindings for C#. Update to DuckDB 0.10.3. DuckDB.NET.Native - win-x64;linux-x64;linux-arm64;osx + win-x64;linux-x64;linux-arm64;osx-x64 https://github.com/duckdb/duckdb/releases/download/v0.10.3 True ..\keyPair.snk @@ -15,13 +15,13 @@ $(Description) $(NoNativeText) - + - + diff --git a/DuckDB.NET.Data/DuckDBException.cs b/DuckDB.NET.Data/DuckDBException.cs index 4e1877b7..c9748a2b 100644 --- a/DuckDB.NET.Data/DuckDBException.cs +++ b/DuckDB.NET.Data/DuckDBException.cs @@ -1,4 +1,5 @@ -using System.Data.Common; +using System; +using System.Data.Common; using System.Runtime.Serialization; using DuckDB.NET.Native; @@ -10,6 +11,7 @@ internal DuckDBException() { } + [Obsolete] internal DuckDBException(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/DuckDB.NET.Data/Internal/Reader/CreatorCache.cs b/DuckDB.NET.Data/Internal/Reader/CreatorCache.cs new file mode 100644 index 00000000..bacd1d09 --- /dev/null +++ b/DuckDB.NET.Data/Internal/Reader/CreatorCache.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; + +namespace DuckDB.NET.Data.Internal.Reader; + +internal static class CreatorCache +{ + private static readonly ConcurrentDictionary> creators=new ConcurrentDictionary>(); + + public static Func GetCreator(Type type) + { + return creators.GetOrAdd(type, static t => + { + return Expression.Lambda>(Expression.Convert(Expression.New(t), typeof(object))).Compile(); + }); + } +} diff --git a/DuckDB.NET.Data/Internal/Reader/ListVectorDataReader.cs b/DuckDB.NET.Data/Internal/Reader/ListVectorDataReader.cs index 7ab4a8b4..4748696e 100644 --- a/DuckDB.NET.Data/Internal/Reader/ListVectorDataReader.cs +++ b/DuckDB.NET.Data/Internal/Reader/ListVectorDataReader.cs @@ -1,8 +1,11 @@ -using System; +using DuckDB.NET.Data.Extensions; +using DuckDB.NET.Native; +using System; using System.Collections; using System.Collections.Generic; -using DuckDB.NET.Data.Extensions; -using DuckDB.NET.Native; +#if NET8_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif namespace DuckDB.NET.Data.Internal.Reader; @@ -23,12 +26,17 @@ internal unsafe ListVectorDataReader(IntPtr vector, void* dataPointer, ulong* va arraySize = IsList ? 0 : (ulong)NativeMethods.LogicalType.DuckDBArrayVectorGetSize(logicalType); listDataReader = VectorDataReaderFactory.CreateReader(childVector, childType, columnName); } - +#if NET8_0_OR_GREATER + [return:DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif protected override Type GetColumnType() { return typeof(List<>).MakeGenericType(listDataReader.ClrType); } +#if NET8_0_OR_GREATER + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +#endif protected override Type GetColumnProviderSpecificType() { return typeof(List<>).MakeGenericType(listDataReader.ProviderSpecificClrType); @@ -57,7 +65,7 @@ private unsafe object GetList(Type returnType, ulong listOffset, ulong length) var allowNulls = listType.AllowsNullValue(out var _, out var nullableType); - var list = Activator.CreateInstance(returnType) as IList + var list = CreatorCache.GetCreator(returnType)() as IList ?? throw new ArgumentException($"The type '{returnType.Name}' specified in parameter {nameof(returnType)} cannot be instantiated as an IList."); //Special case for specific types to avoid boxing diff --git a/DuckDB.NET.Data/Internal/Reader/StructVectorDataReader.cs b/DuckDB.NET.Data/Internal/Reader/StructVectorDataReader.cs index 18a24858..5bef3f26 100644 --- a/DuckDB.NET.Data/Internal/Reader/StructVectorDataReader.cs +++ b/DuckDB.NET.Data/Internal/Reader/StructVectorDataReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using DuckDB.NET.Data.Extensions; using DuckDB.NET.Native; @@ -37,23 +38,13 @@ internal override object GetValue(ulong offset, Type targetType) return base.GetValue(offset, targetType); } - - private object GetStruct(ulong offset, Type returnType) + internal static TypeDetails GetTypeDetails( +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif + Type returnType) { - var result = Activator.CreateInstance(returnType); - - if (result is Dictionary dictionary) - { - foreach (var reader in structDataReaders) - { - var value = reader.Value.IsValid(offset) ? reader.Value.GetValue(offset) : null; - dictionary.Add(reader.Key, value); - } - - return result; - } - - var typeDetails = TypeCache.GetOrAdd(returnType, type => + return TypeCache.GetOrAdd(returnType, type => { var propertyInfos = returnType.GetProperties(); var details = new TypeDetails(); @@ -80,6 +71,29 @@ private object GetStruct(ulong offset, Type returnType) return details; }); + } + private object GetStruct(ulong offset, +#if NET8_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif + Type returnType) + { + if (returnType.IsEquivalentTo(typeof(Dictionary))) + { + var dictionary = new Dictionary(); + + foreach (var reader in structDataReaders) + { + var value = reader.Value.IsValid(offset) ? reader.Value.GetValue(offset) : null; + dictionary.Add(reader.Key, value); + } + + return dictionary; + } + + var typeDetails = GetTypeDetails(returnType); + + var result = CreatorCache.GetCreator(returnType)(); foreach (var property in typeDetails.Properties) { diff --git a/DuckDB.NET.sln b/DuckDB.NET.sln index 6c38102d..a9b51973 100644 --- a/DuckDB.NET.sln +++ b/DuckDB.NET.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data", "DuckDB.NET.Data\Dat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "DuckDB.NET.Test\Test.csproj", "{56C63520-26F9-4230-9AD1-04457E5EBF57}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT", "DuckDB.NET.AOT\AOT.csproj", "{29DB9656-5E17-4F49-944C-EDC5FF8F9312}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {56C63520-26F9-4230-9AD1-04457E5EBF57}.Debug|Any CPU.Build.0 = Debug|Any CPU {56C63520-26F9-4230-9AD1-04457E5EBF57}.Release|Any CPU.ActiveCfg = Release|Any CPU {56C63520-26F9-4230-9AD1-04457E5EBF57}.Release|Any CPU.Build.0 = Release|Any CPU + {29DB9656-5E17-4F49-944C-EDC5FF8F9312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29DB9656-5E17-4F49-944C-EDC5FF8F9312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29DB9656-5E17-4F49-944C-EDC5FF8F9312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29DB9656-5E17-4F49-944C-EDC5FF8F9312}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE