Skip to content

Commit 7794d61

Browse files
authored
Add estimated cardinality support for table functions (#324)
1 parent 96e4851 commit 7794d61

3 files changed

Lines changed: 62 additions & 1 deletion

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ public static partial class TableFunction
8989
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
9090
public static unsafe partial void DuckDBBindSetBindData(IntPtr info, IntPtr bindData, delegate* unmanaged[Cdecl]<IntPtr, void> destroy);
9191

92+
[SuppressGCTransition]
93+
[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_set_cardinality")]
94+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
95+
public static unsafe partial void DuckDBBindSetCardinality(IntPtr info, ulong cardinality, [MarshalAs(UnmanagedType.I1)] bool isExact);
96+
9297
// Maybe [SuppressGCTransition]: strdup error string — one small allocation
9398
[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_set_error", StringMarshalling = StringMarshalling.Utf8)]
9499
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]

DuckDB.NET.Data/DuckDBConnection.TableFunction.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ namespace DuckDB.NET.Data;
88

99
public record ColumnInfo(string Name, Type Type);
1010

11-
public record TableFunction(IReadOnlyList<ColumnInfo> Columns, IEnumerable Data);
11+
public record CardinalityHint(ulong Value, bool IsExact = false);
12+
13+
public record TableFunction(IReadOnlyList<ColumnInfo> Columns, IEnumerable Data, CardinalityHint? Cardinality = null);
1214

1315
partial class DuckDBConnection
1416
{
@@ -142,6 +144,11 @@ private static unsafe void Bind(IntPtr info)
142144
NativeMethods.TableFunction.DuckDBBindAddResultColumn(info, columnInfo.Name, logicalType);
143145
}
144146

147+
if (tableFunctionData.Cardinality is { } cardinality)
148+
{
149+
NativeMethods.TableFunction.DuckDBBindSetCardinality(info, cardinality.Value, isExact: cardinality.IsExact);
150+
}
151+
145152
var connectionId = UdfExceptionStore.GetTableFunctionBindConnectionId(info);
146153
var bindData = new TableFunctionBindData(tableFunctionData.Columns, tableFunctionData.Data.GetEnumerator(), connectionId);
147154

DuckDB.NET.Test/TableFunctionTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Globalization;
2+
using System.Text.Json;
23
using System.Threading;
34

45
namespace DuckDB.NET.Test;
@@ -422,4 +423,52 @@ public void TableFunctionMapError_PreservesInnerException()
422423
ex.InnerException!.Message.Should().Be("custom map error");
423424
ex.InnerException.Should().BeSameAs(originalException);
424425
}
426+
427+
[Theory]
428+
[InlineData(100, false, "card_estimated")]
429+
[InlineData(50, true, "card_exact")]
430+
public void RegisterTableFunctionWithCardinality(int count, bool isExact, string funcName)
431+
{
432+
var expectedData = Enumerable.Range(0, count).ToList();
433+
434+
Connection.RegisterTableFunction(funcName, parameters =>
435+
{
436+
return new TableFunction(new List<ColumnInfo>
437+
{
438+
new("value", typeof(int)),
439+
}, expectedData, Cardinality: new CardinalityHint((ulong)count, IsExact: isExact));
440+
}, (item, writers, rowIndex) =>
441+
{
442+
writers[0].WriteValue((int)item, rowIndex);
443+
});
444+
445+
var data = Connection.Query<int>($"SELECT * FROM {funcName}();").ToList();
446+
data.Should().BeEquivalentTo(expectedData);
447+
}
448+
449+
[Theory]
450+
[InlineData(42, false, "plan_estimated")]
451+
[InlineData(50, true, "plan_exact")]
452+
public void RegisterTableFunctionWithCardinality_AppearsInQueryPlan(int cardinality, bool isExact, string funcName)
453+
{
454+
Connection.RegisterTableFunction(funcName, _ =>
455+
{
456+
return new TableFunction(new List<ColumnInfo>
457+
{
458+
new("value", typeof(int)),
459+
}, Enumerable.Range(0, 50), Cardinality: new CardinalityHint((ulong)cardinality, IsExact: isExact));
460+
}, (item, writers, rowIndex) =>
461+
{
462+
writers[0].WriteValue((int)item, rowIndex);
463+
});
464+
465+
var plan = Connection.Query<(string, string)>($"EXPLAIN (FORMAT JSON) SELECT * FROM {funcName}();").First();
466+
var json = JsonDocument.Parse(plan.Item2);
467+
var reported = json.RootElement[0]
468+
.GetProperty("extra_info")
469+
.GetProperty("Estimated Cardinality")
470+
.GetString();
471+
472+
reported.Should().Be(cardinality.ToString());
473+
}
425474
}

0 commit comments

Comments
 (0)