Skip to content

Commit f98ae60

Browse files
committed
Initial implementation for table-valued functions
1 parent 9c5b06f commit f98ae60

File tree

8 files changed

+170
-25
lines changed

8 files changed

+170
-25
lines changed

DuckDB.NET.Bindings/DuckDBWrapperObjects.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System;
2-
using Microsoft.Win32.SafeHandles;
1+
using Microsoft.Win32.SafeHandles;
2+
using System;
33

44
namespace DuckDB.NET.Native;
55

@@ -89,7 +89,7 @@ protected override bool ReleaseHandle()
8989
}
9090
}
9191

92-
public class DuckDBValue() : SafeHandleZeroOrMinusOneIsInvalid(true)
92+
public class DuckDBValue() : SafeHandleZeroOrMinusOneIsInvalid(true), IDuckDBValueReader
9393
{
9494
private DuckDBValue[] childValues = [];
9595

@@ -108,4 +108,11 @@ internal void SetChildValues(DuckDBValue[] values)
108108
{
109109
childValues = values;
110110
}
111+
112+
public T GetValue<T>()
113+
{
114+
var value = NativeMethods.Value.DuckDBGetInt32(this);
115+
return (T)(object)value;
116+
//return Unsafe.As<int, T>(ref value);
117+
}
111118
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DuckDB.NET.Native;
2+
3+
public interface IDuckDBValueReader
4+
{
5+
T GetValue<T>();
6+
}

DuckDB.NET.Bindings/NativeMethods/NativeMethods.TableFunction.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ public partial class NativeMethods
77
{
88
public static class TableFunction
99
{
10-
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_tableFunction")]
10+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_table_function")]
1111
public static extern IntPtr DuckDBCreateTableFunction();
1212

13-
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_destroy_tableFunction")]
13+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_destroy_table_function")]
1414
public static extern void DuckDBDestroyTableFunction(out IntPtr tableFunction);
1515

1616
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_table_function_set_name")]
@@ -25,17 +25,20 @@ public static class TableFunction
2525
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_table_function_set_bind")]
2626
public static extern unsafe void DuckDBTableFunctionSetBind(IntPtr tableFunction, delegate* unmanaged[Cdecl]<IntPtr, void> bind);
2727

28-
//[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_table_function_set_init")]
29-
//public static extern unsafe void DuckDBTableFunctionSetInit(IntPtr tableFunction, duckdb_tableFunction_init_t init);
28+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_table_function_set_init")]
29+
public static extern unsafe void DuckDBTableFunctionSetInit(IntPtr tableFunction, delegate* unmanaged[Cdecl]<IntPtr, void> init);
3030

3131
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_table_function_set_function")]
32-
public static extern unsafe void DuckDBTableFunctionSetFunction(IntPtr tableFunction, delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr, void> callback);
32+
public static extern unsafe void DuckDBTableFunctionSetFunction(IntPtr tableFunction, delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void> callback);
3333

3434
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_register_table_function")]
3535
public static extern DuckDBState DuckDBRegisterTableFunction(DuckDBNativeConnection con, IntPtr tableFunction);
3636

3737
#region TableFunctionBind
3838

39+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_bind_get_extra_info")]
40+
public static extern unsafe IntPtr DuckDBBindGetExtraInfo(IntPtr info);
41+
3942
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_bind_add_result_column")]
4043
public static extern unsafe void DuckDBBindAddResultColumn(IntPtr info, SafeUnmanagedMemoryHandle name, DuckDBLogicalType type);
4144

@@ -52,8 +55,11 @@ public static class TableFunction
5255

5356
#region TableFunction
5457

58+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_function_get_extra_info")]
59+
public static extern unsafe IntPtr DuckDBFunctionGetExtraInfo(IntPtr info);
60+
5561
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_function_get_bind_data")]
56-
public static extern unsafe void* duckdb_function_get_bind_data(IntPtr info);
62+
public static extern unsafe IntPtr DuckDBFunctionGetBindData(IntPtr info);
5763

5864
#endregion
5965
}

DuckDB.NET.Data/DuckDBConnection.ScalarFunction.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private unsafe void RegisterScalarMethod(string name, Action<IDuckDBDataReader[]
9797

9898
if (!state.IsSuccess())
9999
{
100-
throw new InvalidOperationException("Error registering user defined scalar function");
100+
throw new InvalidOperationException($"Error registering user defined scalar function: {name}");
101101
}
102102
}
103103

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1-
using System;
2-
using System.Runtime.CompilerServices;
3-
using System.Runtime.InteropServices;
4-
using DuckDB.NET.Data.Extensions;
1+
using DuckDB.NET.Data.Extensions;
52
using DuckDB.NET.Data.Internal;
63
using DuckDB.NET.Native;
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Linq;
9+
using System.Runtime.CompilerServices;
10+
using System.Runtime.InteropServices;
11+
using DuckDB.NET.Data.Internal.Writer;
12+
using DuckDB.NET.Data.Writer;
713

814
namespace DuckDB.NET.Data;
915

16+
public record ColumnInfo(string Name, Type Type)
17+
{
18+
}
19+
20+
public record TableFunction(IReadOnlyList<ColumnInfo> Columns, IEnumerable Data)
21+
{
22+
}
23+
1024
partial class DuckDBConnection
1125
{
12-
public unsafe void RegisterTableFunction<T, TResult>(string name)
26+
#if NET8_0_OR_GREATER
27+
[Experimental("DuckDBNET001")]
28+
public unsafe void RegisterTableFunction<T>(string name, Func<IEnumerable<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[]> mapperCallback)
1329
{
1430
var function = NativeMethods.TableFunction.DuckDBCreateTableFunction();
1531
NativeMethods.TableFunction.DuckDBTableFunctionSetName(function, name.ToUnmanagedString());
@@ -19,20 +35,105 @@ public unsafe void RegisterTableFunction<T, TResult>(string name)
1935
NativeMethods.TableFunction.DuckDBTableFunctionAddParameter(function, logicalType);
2036
}
2137

38+
var tableFunctionInfo = new TableFunctionInfo(resultCallback, mapperCallback);
39+
2240
NativeMethods.TableFunction.DuckDBTableFunctionSetBind(function, &Bind);
41+
NativeMethods.TableFunction.DuckDBTableFunctionSetInit(function, &Init);
42+
NativeMethods.TableFunction.DuckDBTableFunctionSetFunction(function, &TableFunction);
43+
NativeMethods.TableFunction.DuckDBTableFunctionSetExtraInfo(function, tableFunctionInfo.ToHandle(), &DestroyExtraInfo);
44+
45+
var state = NativeMethods.TableFunction.DuckDBRegisterTableFunction(NativeConnection, function);
46+
47+
if (!state.IsSuccess())
48+
{
49+
throw new InvalidOperationException($"Error registering user defined table function: {name}");
50+
}
51+
52+
NativeMethods.TableFunction.DuckDBDestroyTableFunction(out function);
2353
}
2454

2555
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
2656
public static unsafe void Bind(IntPtr info)
2757
{
28-
var parameters = new object[NativeMethods.TableFunction.DuckDBBindGetParameterCount(info)];
58+
var handle = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBBindGetExtraInfo(info));
59+
60+
if (handle.Target is not TableFunctionInfo functionInfo)
61+
{
62+
throw new InvalidOperationException("User defined table function bind failed. Bind extra info is null");
63+
}
64+
65+
var parameters = new IDuckDBValueReader[NativeMethods.TableFunction.DuckDBBindGetParameterCount(info)];
2966

3067
for (var i = 0; i < parameters.Length; i++)
3168
{
32-
using var value = NativeMethods.TableFunction.DuckDBBindGetParameter(info, (ulong)i);
33-
parameters[i] = NativeMethods.Value.DuckDBGetInt32(value);
69+
var value = NativeMethods.TableFunction.DuckDBBindGetParameter(info, (ulong)i);
70+
parameters[i] = value;
71+
}
72+
73+
var tableFunctionData = functionInfo.Bind(parameters);
74+
75+
foreach (var parameter in parameters)
76+
{
77+
(parameter as IDisposable)?.Dispose();
78+
}
79+
80+
foreach (var columnInfo in tableFunctionData.Columns)
81+
{
82+
using var logicalType = DuckDBTypeMap.GetLogicalType(columnInfo.Type);
83+
NativeMethods.TableFunction.DuckDBBindAddResultColumn(info, columnInfo.Name.ToUnmanagedString(), logicalType);
84+
}
85+
86+
var bindData = new TableFunctionBindData(tableFunctionData.Columns, tableFunctionData.Data.GetEnumerator());
87+
88+
NativeMethods.TableFunction.DuckDBBindSetBindData(info, bindData.ToHandle(), &DestroyExtraInfo);
89+
}
90+
91+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
92+
public static unsafe void Init(IntPtr info) { }
93+
94+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
95+
public static unsafe void TableFunction(IntPtr info, IntPtr chunk)
96+
{
97+
var bindData = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetBindData(info));
98+
var extraInfo = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetExtraInfo(info));
99+
100+
if (bindData.Target is not TableFunctionBindData tableFunctionBindData)
101+
{
102+
throw new InvalidOperationException("User defined table function failed. Function bind data is null");
103+
}
104+
105+
if (extraInfo.Target is not TableFunctionInfo tableFunctionInfo)
106+
{
107+
throw new InvalidOperationException("User defined table function failed. Function extra info is null");
108+
}
109+
110+
var dataChunk = new DuckDBDataChunk(chunk);
111+
112+
var writers = new VectorDataWriterBase[tableFunctionBindData.Columns.Count];
113+
for (var columnIndex = 0; columnIndex < tableFunctionBindData.Columns.Count; columnIndex++)
114+
{
115+
var column = tableFunctionBindData.Columns[columnIndex];
116+
var vector = NativeMethods.DataChunks.DuckDBDataChunkGetVector(dataChunk, columnIndex);
117+
118+
using var logicalType = DuckDBTypeMap.GetLogicalType(column.Type);
119+
writers[columnIndex] = VectorDataWriterFactory.CreateWriter(vector, logicalType);
120+
}
121+
122+
ulong size = 0;
123+
124+
for (; size < DuckDBGlobalData.VectorSize; size++)
125+
{
126+
if (tableFunctionBindData.DataEnumerator.MoveNext())
127+
{
128+
tableFunctionInfo.Mapper(tableFunctionBindData.DataEnumerator.Current, writers);
129+
}
130+
else
131+
{
132+
break;
133+
}
34134
}
35135

36-
NativeMethods.TableFunction.DuckDBBindSetBindData(info, parameters.ToHandle(), &DestroyExtraInfo);
136+
NativeMethods.DataChunks.DuckDBDataChunkSetSize(dataChunk, size);
37137
}
138+
#endif
38139
}

DuckDB.NET.Data/Internal/DuckDBTypeMap.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ public static DbType GetDbTypeForValue(object? value)
7979
return DbType.Object;
8080
}
8181

82-
public static DuckDBLogicalType GetLogicalType<T>()
82+
public static DuckDBLogicalType GetLogicalType<T>() => GetLogicalType(typeof(T));
83+
84+
public static DuckDBLogicalType GetLogicalType(Type type)
8385
{
84-
if (ClrToDuckDBTypeMap.TryGetValue(typeof(T), out var duckDBType))
86+
if (ClrToDuckDBTypeMap.TryGetValue(type, out var duckDBType))
8587
{
8688
return NativeMethods.LogicalType.DuckDBCreateLogicalType(duckDBType);
8789
}
8890

89-
throw new InvalidOperationException($"Cannot map type {typeof(T).FullName} to DuckDBType.");
91+
throw new InvalidOperationException($"Cannot map type {type.FullName} to DuckDBType.");
9092
}
9193
}

DuckDB.NET.Data/Internal/ScalarFunctionInfo.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using DuckDB.NET.Data.Internal.Reader;
1+
using DuckDB.NET.Data.Internal.Reader;
42
using DuckDB.NET.Data.Internal.Writer;
53
using DuckDB.NET.Native;
4+
using System;
65

76
namespace DuckDB.NET.Data.Internal;
87

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using DuckDB.NET.Data.Internal.Writer;
2+
using DuckDB.NET.Native;
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
7+
namespace DuckDB.NET.Data.Internal;
8+
9+
class TableFunctionInfo(Func<IEnumerable<IDuckDBValueReader>, TableFunction> bind, Action<object?, VectorDataWriterBase[]> mapper)
10+
{
11+
public Func<IEnumerable<IDuckDBValueReader>, TableFunction> Bind { get; private set; } = bind;
12+
public Action<object?, VectorDataWriterBase[]> Mapper { get; private set; } = mapper;
13+
}
14+
15+
class TableFunctionBindData(IReadOnlyList<ColumnInfo> columns, IEnumerator dataEnumerator) : IDisposable
16+
{
17+
public IReadOnlyList<ColumnInfo> Columns { get; } = columns;
18+
public IEnumerator DataEnumerator { get; private set; } = dataEnumerator;
19+
20+
public void Dispose()
21+
{
22+
(DataEnumerator as IDisposable)?.Dispose();
23+
}
24+
}

0 commit comments

Comments
 (0)