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
50 changes: 49 additions & 1 deletion .github/workflows/csharp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
strategy:
fail-fast: false
matrix:
dotnet: ['8.0.x']
dotnet: ['8.0.x', '10.0.x']
os: [ubuntu-latest, windows-2022, macos-15-intel, macos-latest]
steps:
- name: Install C#
Expand All @@ -68,3 +68,51 @@ jobs:
- name: Test
shell: bash
run: ci/scripts/csharp_test.sh $(pwd)

# TODO: Create a test fixture driver to use for interop testing as the
# real drivers have migrated to https://github.com/adbc-drivers

# Publishes the Apache driver as a NativeAOT shared library (net10) and
# loads it from Python via adbc_driver_manager to catch AOT regressions
# (trim-unsafe reflection, missing exports, broken C-ABI marshaling).
# Windows-only for now; the smoke test only exercises Windows paths.
csharp-aot:
name: "C# NativeAOT smoke test (windows-2022)"
runs-on: windows-2022
# if: ${{ !contains(github.event.pull_request.title, 'WIP') }}
if: false
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled for now

timeout-minutes: 30
steps:
- name: Checkout ADBC
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- name: Install .NET 10
uses: actions/setup-dotnet@v5
with:
# NativeAOT for our producer requires net10. Using 10.0.x selects
# the latest available SDK; preview tags may be needed until GA.
dotnet-version: '10.0.x'
- name: Setup MSVC (for NativeAOT linker)
# Third-party action; activates vcvars64 so ilc's downstream
# link.exe step can find link.exe/lib.exe and the Windows SDK.
# The existing workflow only uses first-party actions, so flag
# this for review before enabling.
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be fairly widely used and hasn't been changed in a while. (This was a Claude suggestion.)

- name: Publish NativeAOT driver
shell: bash
run: |
dotnet publish \
csharp/src/Drivers/Apache/Apache.Arrow.Adbc.Drivers.Apache.Native/Apache.Arrow.Adbc.Drivers.Apache.Native.csproj \
-c Release -r win-x64
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
shell: bash
run: python -m pip install adbc_driver_manager
- name: Run Python smoke test
shell: bash
run: python csharp/src/Drivers/Apache/Apache.Arrow.Adbc.Drivers.Apache.Native/smoke_test.py
6 changes: 5 additions & 1 deletion csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
Comment thread
CurtHagenlocher marked this conversation as resolved.
<ItemGroup>
<PackageReference Include="Apache.Arrow" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
Expand Down
6 changes: 3 additions & 3 deletions csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public class CAdbcDriverExporter
private static unsafe IntPtr StatementExecutePartitionsPtr = NativeDelegate<StatementExecutePartitions>.AsNativePointer(ExecuteStatementPartitions);
private static unsafe IntPtr StatementExecuteSchemaPtr = NativeDelegate<StatementExecuteSchema>.AsNativePointer(ExecuteStatementSchema);
private static unsafe IntPtr StatementNewPtr = NativeDelegate<StatementNew>.AsNativePointer(NewStatement);
private static unsafe IntPtr StatementReleasePtr = NativeDelegate<StatementRelease>.AsNativePointer(ReleaseStatement);
private static unsafe IntPtr StatementPreparePtr = NativeDelegate<StatementPrepare>.AsNativePointer(PrepareStatement);
private static unsafe IntPtr StatementReleasePtr = NativeDelegate<StatementFn>.AsNativePointer(ReleaseStatement);
private static unsafe IntPtr StatementPreparePtr = NativeDelegate<StatementFn>.AsNativePointer(PrepareStatement);
private static unsafe IntPtr StatementSetSqlQueryPtr = NativeDelegate<StatementSetSqlQuery>.AsNativePointer(SetStatementSqlQuery);
private static unsafe IntPtr StatementSetSubstraitPlanPtr = NativeDelegate<StatementSetSubstraitPlan>.AsNativePointer(SetStatementSubstraitPlan);
private static unsafe IntPtr StatementGetParameterSchemaPtr = NativeDelegate<StatementGetParameterSchema>.AsNativePointer(GetStatementParameterSchema);
Expand All @@ -98,7 +98,7 @@ public unsafe static AdbcStatusCode AdbcDriverInit(int version, CAdbcDriver* nat
if (version != AdbcVersion.Version_1_0_0)
{
// TODO: implement support for AdbcVersion.Version_1_1_0
return AdbcStatusCode.InternalError;
return AdbcStatusCode.NotImplemented;
}

DriverStub stub = new DriverStub(driver);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ private static unsafe AdbcStatusCode StatementGetParameterSchemaDefaultImpl(CAdb
}

#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementPrepareDefault = NativeDelegate<StatementPrepare>.AsNativePointer(StatementPrepareDefaultImpl);
private static unsafe IntPtr StatementPrepareDefault = NativeDelegate<StatementFn>.AsNativePointer(StatementPrepareDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CAdbcError*, AdbcStatusCode> StatementPrepareDefault => &StatementPrepareDefaultImpl;
[UnmanagedCallersOnly]
Expand Down
62 changes: 39 additions & 23 deletions csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,34 +79,50 @@ public static AdbcDriver Load(string file, bool canUnload, string? entryPoint =
}

AdbcDriverInit init = Marshal.GetDelegateForFunctionPointer<AdbcDriverInit>(export);
CAdbcDriver driver = new CAdbcDriver();
int version;
using (CallHelper caller = new CallHelper())
{
try
{
caller.Call(init, AdbcVersion.Version_1_1_0, ref driver);
version = AdbcVersion.Version_1_1_0;
}
catch (AdbcException e) when (e.Status == AdbcStatusCode.NotImplemented)
{
caller.Call(init, AdbcVersion.Version_1_0_0, ref driver);
version = AdbcVersion.Version_1_0_0;
}

ValidateDriver(ref driver, version);

ImportedAdbcDriver result = new ImportedAdbcDriver(library, driver, version, canUnload);
library = IntPtr.Zero;
return result;
}
ImportedAdbcDriver result = LoadFromInit(init, library, canUnload);
library = IntPtr.Zero;
return result;
}
finally
{
if (library != IntPtr.Zero && canUnload) { NativeLibrary.Free(library); }
}
}

/// <summary>
/// Loads an <see cref="AdbcDriver"/> from an in-process delegate that acts as the driver's
/// AdbcDriverInit entry point. Used for round-tripping <see cref="CAdbcDriverExporter"/>
/// through the importer in tests without producing a native library.
/// </summary>
internal static AdbcDriver Load(AdbcDriverInit init)
{
if (init == null) { throw new ArgumentNullException(nameof(init)); }
return LoadFromInit(init, IntPtr.Zero, canUnload: false);
}

private static ImportedAdbcDriver LoadFromInit(AdbcDriverInit init, IntPtr library, bool canUnload)
{
CAdbcDriver driver = new CAdbcDriver();
int version;
using (CallHelper caller = new CallHelper())
{
try
{
caller.Call(init, AdbcVersion.Version_1_1_0, ref driver);
version = AdbcVersion.Version_1_1_0;
}
catch (AdbcException e) when (e.Status == AdbcStatusCode.NotImplemented)
{
caller.Call(init, AdbcVersion.Version_1_0_0, ref driver);
version = AdbcVersion.Version_1_0_0;
}

ValidateDriver(ref driver, version);

return new ImportedAdbcDriver(library, driver, version, canUnload);
}
}

private static unsafe void ValidateDriver(ref CAdbcDriver driver, int version)
{
#if NET5_0_OR_GREATER
Expand Down Expand Up @@ -1036,7 +1052,7 @@ public unsafe override void Prepare()
#if NET5_0_OR_GREATER
Driver.StatementPrepare
#else
Marshal.GetDelegateForFunctionPointer<StatementPrepare>(Driver.StatementPrepare)
Marshal.GetDelegateForFunctionPointer<StatementFn>(Driver.StatementPrepare)
#endif
(statement, &caller._error));
}
Expand Down Expand Up @@ -1412,7 +1428,7 @@ public unsafe void Call(IntPtr fn, ref CAdbcStatement nativeStatement)
fixed (CAdbcStatement* stmt = &nativeStatement)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<StatementPrepare>(fn)(stmt, e));
TranslateCode(Marshal.GetDelegateForFunctionPointer<StatementFn>(fn)(stmt, e));
}
}
#endif
Expand Down
3 changes: 1 addition & 2 deletions csharp/src/Apache.Arrow.Adbc/C/Delegates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,18 @@ namespace Apache.Arrow.Adbc.C
internal unsafe delegate AdbcStatusCode StatementExecuteQuery(CAdbcStatement* statement, CArrowArrayStream* stream, long* rows, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementExecutePartitions(CAdbcStatement* statement, CArrowSchema* schema, CAdbcPartitions* partitions, long* rows, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementExecuteSchema(CAdbcStatement* statement, CArrowSchema* stream, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementFn(CAdbcStatement* statement, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementGetParameterSchema(CAdbcStatement* statement, CArrowSchema* schema, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementGetOption(CAdbcStatement* statement, byte* name, byte* value, nint* length, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementGetOptionBytes(CAdbcStatement* statement, byte* name, byte* value, nint* length, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementGetOptionDouble(CAdbcStatement* statement, byte* name, double* value, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementGetOptionInt(CAdbcStatement* statement, byte* name, long* value, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementNew(CAdbcConnection* connection, CAdbcStatement* statement, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementPrepare(CAdbcStatement* statement, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetOption(CAdbcStatement* statement, byte* name, byte* value, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetOptionBytes(CAdbcStatement* statement, byte* name, byte* value, nint length, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetOptionDouble(CAdbcStatement* statement, byte* name, double value, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetOptionInt(CAdbcStatement* statement, byte* name, long value, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetSqlQuery(CAdbcStatement* statement, byte* text, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementSetSubstraitPlan(CAdbcStatement* statement, byte* plan, int length, CAdbcError* error);
internal unsafe delegate AdbcStatusCode StatementRelease(CAdbcStatement* statement, CAdbcError* error);
#endif
}
116 changes: 115 additions & 1 deletion csharp/src/Apache.Arrow.Adbc/Extensions/IArrowArrayExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,121 @@ private static string SerializeToJson(StructArray structArray, int index)
{
Dictionary<string, object?>? obj = ParseStructArray(structArray, index);

return JsonSerializer.Serialize(obj);
using MemoryStream ms = new();
using (Utf8JsonWriter writer = new(ms))
{
WriteJsonValue(writer, obj);
}
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
}

private static void WriteJsonValue(Utf8JsonWriter writer, object? value)
{
switch (value)
{
case null:
writer.WriteNullValue();
return;
case string s:
writer.WriteStringValue(s);
return;
case bool b:
writer.WriteBooleanValue(b);
return;
case int i:
writer.WriteNumberValue(i);
return;
case long l:
writer.WriteNumberValue(l);
return;
case short sh:
writer.WriteNumberValue(sh);
return;
case byte by:
writer.WriteNumberValue(by);
return;
case sbyte sb:
writer.WriteNumberValue(sb);
return;
case uint ui:
writer.WriteNumberValue(ui);
return;
case ulong ul:
writer.WriteNumberValue(ul);
return;
case ushort us:
writer.WriteNumberValue(us);
return;
case float f:
writer.WriteNumberValue(f);
return;
case double d:
writer.WriteNumberValue(d);
return;
case decimal dec:
writer.WriteNumberValue(dec);
return;
case SqlDecimal sqlDec:
// Decimal32/Decimal64 come back as plain `decimal` and are handled above.
// Decimal128 values arrive here; SqlDecimal.Precision reflects the column's
// declared precision (every row from the same column shares it, so schema
// stays stable). Any decimal that fits in 15 significant digits round-trips
// cleanly through double, so it's safe to emit as a JSON number. Wider
// columns are emitted as strings — consumers must parse them deliberately.
if (sqlDec.Precision <= 15)
{
writer.WriteNumberValue(sqlDec.Value);
}
else
{
// SqlDecimal.ToString() formats from the Data array, so it handles
// precisions that would overflow SqlDecimal.Value (>28 digits).
writer.WriteStringValue(sqlDec.ToString());
}
return;
case DateTime dt:
writer.WriteStringValue(dt);
return;
case DateTimeOffset dto:
writer.WriteStringValue(dto);
return;
#if NET6_0_OR_GREATER
case TimeOnly time:
// Match System.Text.Json's ISO-ish "HH:mm:ss[.fraction]" format.
// FFFFFFF elides trailing zeros and the decimal point when zero.
writer.WriteStringValue(time.ToString("HH:mm:ss.FFFFFFF", System.Globalization.CultureInfo.InvariantCulture));
return;
case DateOnly date:
writer.WriteStringValue(date.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture));
return;
#endif
case Guid g:
writer.WriteStringValue(g);
return;
case byte[] bytes:
writer.WriteBase64StringValue(bytes);
return;
case IDictionary<string, object?> dict:
writer.WriteStartObject();
foreach (KeyValuePair<string, object?> kv in dict)
{
writer.WritePropertyName(kv.Key);
WriteJsonValue(writer, kv.Value);
}
writer.WriteEndObject();
return;
case IEnumerable enumerable:
writer.WriteStartArray();
foreach (object? item in enumerable)
{
WriteJsonValue(writer, item);
}
writer.WriteEndArray();
return;
default:
writer.WriteStringValue(value.ToString());
return;
}
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions csharp/src/Apache.Arrow.Adbc/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
[assembly: InternalsVisibleTo("Apache.Arrow.Adbc.Drivers.BigQuery, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
[assembly: InternalsVisibleTo("Apache.Arrow.Adbc.Tests.Drivers.Interop.FlightSql, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
[assembly: InternalsVisibleTo("Apache.Arrow.Adbc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
[assembly: InternalsVisibleTo("Apache.Arrow.Adbc.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
Loading
Loading