This is an automated email from the ASF dual-hosted git repository.
CurtHagenlocher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 594aa18ab feat(csharp/src/Apache.Arrow.Adbc/C): bring driver exporter
to parity with importer (#4318)
594aa18ab is described below
commit 594aa18ab72277403de98778b742c187ba9567d7
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Tue May 19 11:02:46 2026 -0700
feat(csharp/src/Apache.Arrow.Adbc/C): bring driver exporter to parity with
importer (#4318)
Wires up exporter callbacks that were either missing or unreachable:
- `StatementSetOption` (1.0.0 gap): a managed driver behind the exporter
never received `SetOption()` calls. The importer always issued them via
`AdbcStatement.SetOption` / `BulkIngest`.
- `StatementExecuteSchema`, `ConnectionCancel`, `StatementCancel`
(1.1.0): the exporter previously returned `NotImplemented` for any
version >= 1.1.0; `AdbcDriverInit` now also populates the v1.1.0 fields
that map onto existing managed APIs and leaves the rest null so the
importer's defaults take effect. Adds matching `Cancel()` overrides on
`ImportedAdbcConnection` / `ImportedAdbcStatement` so the C-ABI is
actually exercised end-to-end; previously the base classes threw
`NotImplemented` before ever calling into the driver.
- Extends `ExportedDriverRoundTripTests` with cases for `SetOption`,
both `Cancel` paths, `ExecuteSchema` (verifies it doesn't run the
query), and a regression test that proves 1.1.0-only callbacks are gated
by the negotiated version.
Adds two projects under `csharp/test/AotInterop/`:
- Apache.Arrow.Adbc.TestFixture.Native: net10 library published with
`PublishAot=true`. Exports `AdbcDriverInit` (UnmanagedCallersOnly) that
delegates to `CAdbcDriverExporter` wrapped around a deterministic
managed `FixtureDriver`. The fixture echoes its inputs (SQL query,
statement options, database/connection options) through the result
schema so a consumer can assert that each ADBC API call survived the
C-ABI hop.
- Apache.Arrow.Adbc.TestFixture.Tests: net10 xUnit test project that
loads the AOT-published .dll via `CAdbcDriverImporter.Load(path)` and
exercises the round-trip. Tests use `SkippableFact` and read the path
from `ADBC_TEST_AOT_FIXTURE_PATH` so the suite stays green locally when
no AOT artifact is present.
Together these projects test BOTH halves of
csharp/src/Apache.Arrow.Adbc/C: the export path inside the AOT'd native
lib AND the import path in the managed test process. They also serve as
an AOT-compatibility canary — trim-unsafe reflection or missing exports
anywhere in core ADBC will fail this build.
Enables the csharp-aot job in .github/workflows/csharp.yml (previously
`if: false`, referencing a non-existent project). The job runs on
windows-2022 only for now because of the MSVC linker dependency for
NativeAOT on Windows.
Will look at enabling this on non-Windows platforms in a subsequent PR.
---
.github/workflows/csharp.yml | 47 ++---
csharp/Apache.Arrow.Adbc.sln | 14 ++
.../src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs | 84 +++++++-
.../src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs | 25 +++
.../Apache.Arrow.Adbc.TestFixture.Native.csproj | 29 +++
.../DriverInit.cs | 43 ++++
.../FixtureDriver.cs | 222 +++++++++++++++++++++
.../AotFixtureTests.cs | 167 ++++++++++++++++
.../Apache.Arrow.Adbc.TestFixture.Tests.csproj | 31 +++
.../ExportedDriverRoundTripTests.cs | 136 ++++++++++++-
10 files changed, 766 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml
index 619d728d7..97f5c403f 100644
--- a/.github/workflows/csharp.yml
+++ b/.github/workflows/csharp.yml
@@ -70,18 +70,15 @@ jobs:
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.
+ # Publishes the AOT test fixture driver as a NativeAOT shared library (net10)
+ # and exercises it through the managed CAdbcDriverImporter. This catches
+ # AOT regressions (trim-unsafe reflection, missing exports, broken C-ABI
+ # marshaling) on both the import and export halves of
csharp/src/Apache.Arrow.Adbc/C.
+ # Windows-only for now; the MSVC setup step below is Windows-specific.
csharp-aot:
- name: "C# NativeAOT smoke test (windows-2022)"
+ name: "C# NativeAOT fixture interop (windows-2022)"
runs-on: windows-2022
- # if: ${{ !contains(github.event.pull_request.title, 'WIP') }}
- if: false
+ if: ${{ !contains(github.event.pull_request.title, 'WIP') }}
timeout-minutes: 30
steps:
- name: Checkout ADBC
@@ -93,28 +90,26 @@ jobs:
- name: Install .NET 10
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 #
v5.2.0
with:
- # NativeAOT for our producer requires net10. Using 10.0.x selects
- # the latest available SDK; preview tags may be needed until GA.
+ # NativeAOT 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
- - name: Publish NativeAOT driver
+ - name: Publish AOT test fixture
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@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
- with:
- python-version: '3.11'
- - name: Install Python dependencies
+
csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/Apache.Arrow.Adbc.TestFixture.Native.csproj
\
+ -c Release -r win-x64 \
+ -o csharp/artifacts/aot-fixture
+ - name: Run AOT interop tests
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
+ env:
+ # Consumer test loads this DLL through CAdbcDriverImporter.
+ ADBC_TEST_AOT_FIXTURE_PATH: ${{ github.workspace
}}/csharp/artifacts/aot-fixture/Apache.Arrow.Adbc.TestFixture.Native.dll
+ run: |
+ dotnet test \
+
csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/Apache.Arrow.Adbc.TestFixture.Tests.csproj
\
+ -c Release
diff --git a/csharp/Apache.Arrow.Adbc.sln b/csharp/Apache.Arrow.Adbc.sln
index 61947d6a9..f51aaa1f5 100644
--- a/csharp/Apache.Arrow.Adbc.sln
+++ b/csharp/Apache.Arrow.Adbc.sln
@@ -46,6 +46,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Arrow.Adbc.Tests.Tel
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Arrow.Adbc.Telemetry.Traces.Listeners",
"src\Telemetry\Traces\Listeners\Apache.Arrow.Adbc.Telemetry.Traces.Listeners.csproj",
"{4D5ADA1A-2DEE-5860-2351-221090CF4442}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") =
"Apache.Arrow.Adbc.TestFixture.Native",
"test\AotInterop\Apache.Arrow.Adbc.TestFixture.Native\Apache.Arrow.Adbc.TestFixture.Native.csproj",
"{3354B6FE-4373-48E8-B52F-604F810E60E2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") =
"Apache.Arrow.Adbc.TestFixture.Tests",
"test\AotInterop\Apache.Arrow.Adbc.TestFixture.Tests\Apache.Arrow.Adbc.TestFixture.Tests.csproj",
"{CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -92,6 +96,14 @@ Global
{4D5ADA1A-2DEE-5860-2351-221090CF4442}.Debug|Any CPU.Build.0 =
Debug|Any CPU
{4D5ADA1A-2DEE-5860-2351-221090CF4442}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{4D5ADA1A-2DEE-5860-2351-221090CF4442}.Release|Any CPU.Build.0
= Release|Any CPU
+ {3354B6FE-4373-48E8-B52F-604F810E60E2}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {3354B6FE-4373-48E8-B52F-604F810E60E2}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {3354B6FE-4373-48E8-B52F-604F810E60E2}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {3354B6FE-4373-48E8-B52F-604F810E60E2}.Release|Any CPU.Build.0
= Release|Any CPU
+ {CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC}.Release|Any CPU.Build.0
= Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -114,6 +126,8 @@ Global
{1C530561-1008-4F39-B437-15B2FD59EAC9} =
{22EF23A3-1566-446F-B696-9323F3B6F56C}
{9CE9106B-ACBB-54C1-DE57-370E5CF09363} =
{53C45FD3-7277-49FA-AEEB-DF8F2386ECAA}
{4D5ADA1A-2DEE-5860-2351-221090CF4442} =
{1C530561-1008-4F39-B437-15B2FD59EAC9}
+ {3354B6FE-4373-48E8-B52F-604F810E60E2} =
{5BD04C26-CE52-4893-8C1A-479705195CEF}
+ {CE1764C5-0BDC-4BA4-B25E-D4A3E4997FCC} =
{5BD04C26-CE52-4893-8C1A-479705195CEF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4795CF16-0FDB-4BE0-9768-5CF31564DC03}
diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs
b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs
index 0aa1d46ce..7f47213f4 100644
--- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs
+++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs
@@ -57,9 +57,13 @@ namespace Apache.Arrow.Adbc.C
private static unsafe delegate* unmanaged<CAdbcConnection*,
CAdbcStatement*, CAdbcError*, AdbcStatusCode> StatementNewPtr => &NewStatement;
private static unsafe delegate* unmanaged<CAdbcStatement*,
CAdbcError*, AdbcStatusCode> StatementReleasePtr => &ReleaseStatement;
private static unsafe delegate* unmanaged<CAdbcStatement*,
CAdbcError*, AdbcStatusCode> StatementPreparePtr => &PrepareStatement;
+ private static unsafe delegate* unmanaged<CAdbcStatement*, byte*,
byte*, CAdbcError*, AdbcStatusCode> StatementSetOptionPtr =>
&SetStatementOption;
private static unsafe delegate* unmanaged<CAdbcStatement*, byte*,
CAdbcError*, AdbcStatusCode> StatementSetSqlQueryPtr => &SetStatementSqlQuery;
private static unsafe delegate* unmanaged<CAdbcStatement*, byte*, int,
CAdbcError*, AdbcStatusCode> StatementSetSubstraitPlanPtr =>
&SetStatementSubstraitPlan;
private static unsafe delegate* unmanaged<CAdbcStatement*,
CArrowSchema*, CAdbcError*, AdbcStatusCode> StatementGetParameterSchemaPtr =>
&GetStatementParameterSchema;
+
+ private static unsafe delegate* unmanaged<CAdbcConnection*,
CAdbcError*, AdbcStatusCode> ConnectionCancelPtr => &CancelConnection;
+ private static unsafe delegate* unmanaged<CAdbcStatement*,
CAdbcError*, AdbcStatusCode> StatementCancelPtr => &CancelStatement;
#else
internal static unsafe IntPtr ReleaseErrorPtr =>
s_releaseError.Pointer;
private static unsafe IntPtr ReleaseDriverPtr =
NativeDelegate<DriverRelease>.AsNativePointer(ReleaseDriver);
@@ -88,16 +92,19 @@ namespace Apache.Arrow.Adbc.C
private static unsafe IntPtr StatementNewPtr =
NativeDelegate<StatementNew>.AsNativePointer(NewStatement);
private static unsafe IntPtr StatementReleasePtr =
NativeDelegate<StatementFn>.AsNativePointer(ReleaseStatement);
private static unsafe IntPtr StatementPreparePtr =
NativeDelegate<StatementFn>.AsNativePointer(PrepareStatement);
+ private static unsafe IntPtr StatementSetOptionPtr =
NativeDelegate<StatementSetOption>.AsNativePointer(SetStatementOption);
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);
+
+ private static unsafe IntPtr ConnectionCancelPtr =
NativeDelegate<ConnectionFn>.AsNativePointer(CancelConnection);
+ private static unsafe IntPtr StatementCancelPtr =
NativeDelegate<StatementFn>.AsNativePointer(CancelStatement);
#endif
public unsafe static AdbcStatusCode AdbcDriverInit(int version,
CAdbcDriver* nativeDriver, CAdbcError* error, AdbcDriver driver)
{
- if (version != AdbcVersion.Version_1_0_0)
+ if (version != AdbcVersion.Version_1_0_0 && version !=
AdbcVersion.Version_1_1_0)
{
- // TODO: implement support for AdbcVersion.Version_1_1_0
return AdbcStatusCode.NotImplemented;
}
@@ -131,9 +138,18 @@ namespace Apache.Arrow.Adbc.C
nativeDriver->StatementNew = StatementNewPtr;
nativeDriver->StatementPrepare = StatementPreparePtr;
nativeDriver->StatementRelease = StatementReleasePtr;
+ nativeDriver->StatementSetOption = StatementSetOptionPtr;
nativeDriver->StatementSetSqlQuery = StatementSetSqlQueryPtr;
nativeDriver->StatementSetSubstraitPlan =
StatementSetSubstraitPlanPtr;
+ if (version >= AdbcVersion.Version_1_1_0)
+ {
+ nativeDriver->ConnectionCancel = ConnectionCancelPtr;
+
+ nativeDriver->StatementCancel = StatementCancelPtr;
+ nativeDriver->StatementExecuteSchema =
StatementExecuteSchemaPtr;
+ }
+
return 0;
}
@@ -175,6 +191,11 @@ namespace Apache.Arrow.Adbc.C
return adbcException.Status;
}
+ if (exception is ArgumentException)
+ {
+ return AdbcStatusCode.InvalidArgument;
+ }
+
return AdbcStatusCode.UnknownError;
}
@@ -502,6 +523,64 @@ namespace Apache.Arrow.Adbc.C
}
}
+#if NET5_0_OR_GREATER
+ [UnmanagedCallersOnly]
+#endif
+ private unsafe static AdbcStatusCode
SetStatementOption(CAdbcStatement* nativeStatement, byte* name, byte* value,
CAdbcError* error)
+ {
+ try
+ {
+ if (name == null) throw new
ArgumentNullException(nameof(name));
+ if (value == null) throw new
ArgumentNullException(nameof(value));
+
+ GCHandle gch =
GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target!;
+
+ stub.SetOption(MarshalExtensions.PtrToStringUTF8(name)!,
MarshalExtensions.PtrToStringUTF8(value)!);
+ return AdbcStatusCode.Success;
+ }
+ catch (Exception e)
+ {
+ return SetError(error, e);
+ }
+ }
+
+#if NET5_0_OR_GREATER
+ [UnmanagedCallersOnly]
+#endif
+ private unsafe static AdbcStatusCode CancelStatement(CAdbcStatement*
nativeStatement, CAdbcError* error)
+ {
+ try
+ {
+ GCHandle gch =
GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target!;
+ stub.Cancel();
+ return AdbcStatusCode.Success;
+ }
+ catch (Exception e)
+ {
+ return SetError(error, e);
+ }
+ }
+
+#if NET5_0_OR_GREATER
+ [UnmanagedCallersOnly]
+#endif
+ private unsafe static AdbcStatusCode CancelConnection(CAdbcConnection*
nativeConnection, CAdbcError* error)
+ {
+ try
+ {
+ GCHandle gch =
GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target!;
+ stub.Cancel();
+ return AdbcStatusCode.Success;
+ }
+ catch (Exception e)
+ {
+ return SetError(error, e);
+ }
+ }
+
#if NET5_0_OR_GREATER
[UnmanagedCallersOnly]
#endif
@@ -872,6 +951,7 @@ namespace Apache.Arrow.Adbc.C
public void Rollback() { Connection.Rollback(); }
public void Commit() { Connection.Commit(); }
+ public void Cancel() { Connection.Cancel(); }
public void Dispose()
{
diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs
b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs
index 040399f8d..713fbe4d3 100644
--- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs
+++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs
@@ -517,6 +517,15 @@ namespace Apache.Arrow.Adbc.C
}
}
+ private unsafe ref CAdbcDriver Driver11
+ {
+ get
+ {
+ if (_disposed) { throw new
ObjectDisposedException(nameof(ImportedAdbcConnection)); }
+ return ref _driver.Driver11;
+ }
+ }
+
public override bool AutoCommit
{
get => _autoCommit ?? throw AdbcException.NotImplemented("no
value has been set for AutoCommit");
@@ -759,6 +768,14 @@ namespace Apache.Arrow.Adbc.C
}
}
+ public unsafe override void Cancel()
+ {
+ using (CallHelper caller = new CallHelper())
+ {
+ caller.Call(Driver11.ConnectionCancel, ref
_nativeConnection);
+ }
+ }
+
public unsafe override void SetOption(string key, string value)
{
using (CallHelper caller = new CallHelper())
@@ -1067,6 +1084,14 @@ namespace Apache.Arrow.Adbc.C
}
}
+ public unsafe override void Cancel()
+ {
+ using (CallHelper caller = new CallHelper())
+ {
+ caller.Call(Driver11.StatementCancel, ref
_nativeStatement);
+ }
+ }
+
public override void Dispose()
{
Dispose(true);
diff --git
a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/Apache.Arrow.Adbc.TestFixture.Native.csproj
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/Apache.Arrow.Adbc.TestFixture.Native.csproj
new file mode 100644
index 000000000..e1236677f
--- /dev/null
+++
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/Apache.Arrow.Adbc.TestFixture.Native.csproj
@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <!--
+ Publishes a NativeAOT shared library that exports `AdbcDriverInit`.
+ The exported entry point delegates to CAdbcDriverExporter wrapped around
+ a deterministic in-process fixture driver. The companion test project
+ (Apache.Arrow.Adbc.TestFixture.Tests) loads the published .dll/.so/.dylib
+ via CAdbcDriverImporter and exercises both sides of the C ABI.
+
+ A regular `dotnet build` produces a managed library; only `dotnet publish`
+ with `-c Release -r <rid>` produces the native shared library that the
+ consumer test expects.
+ -->
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <OutputType>Library</OutputType>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <PublishAot>true</PublishAot>
+ <IsAotCompatible>true</IsAotCompatible>
+ <IsPackable>false</IsPackable>
+ <!-- Strong-name signing isn't meaningful for the produced native lib. -->
+ <SignAssembly>false</SignAssembly>
+ <!-- Suppress the SourceLink reference; this is internal test infra. -->
+ <PublishRepositoryUrl>false</PublishRepositoryUrl>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference
Include="..\..\..\src\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
+ </ItemGroup>
+</Project>
diff --git
a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/DriverInit.cs
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/DriverInit.cs
new file mode 100644
index 000000000..28997cc00
--- /dev/null
+++ b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/DriverInit.cs
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Runtime.InteropServices;
+using Apache.Arrow.Adbc.C;
+
+namespace Apache.Arrow.Adbc.TestFixture.Native
+{
+ /// <summary>
+ /// Native entry point exposed by this AOT-published shared library.
+ ///
+ /// <para>The ADBC driver manager looks up a symbol named
<c>AdbcDriverInit</c>
+ /// (the default; callers may override via <c>entrypoint=</c>). When
called,
+ /// this method delegates to <see
cref="CAdbcDriverExporter.AdbcDriverInit"/>
+ /// wrapped around a fresh <see cref="FixtureDriver"/>.</para>
+ ///
+ /// <para>One <see cref="FixtureDriver"/> instance is created per load; the
+ /// exporter takes ownership via a GCHandle stored on the CAdbcDriver and
+ /// disposes it when the driver is released.</para>
+ /// </summary>
+ public static unsafe class DriverInit
+ {
+ [UnmanagedCallersOnly(EntryPoint = "AdbcDriverInit")]
+ public static AdbcStatusCode AdbcDriverInit(int version, CAdbcDriver*
driver, CAdbcError* error)
+ {
+ return CAdbcDriverExporter.AdbcDriverInit(version, driver, error,
new FixtureDriver());
+ }
+ }
+}
diff --git
a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/FixtureDriver.cs
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/FixtureDriver.cs
new file mode 100644
index 000000000..9fd7da343
--- /dev/null
+++
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Native/FixtureDriver.cs
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+
+namespace Apache.Arrow.Adbc.TestFixture.Native
+{
+ /// <summary>
+ /// Managed driver exposed across the C ABI by the AOT-published shared
library.
+ ///
+ /// <para>The driver is deliberately deterministic and self-contained so
that the
+ /// consumer test process — which loads this library through
+ /// <c>CAdbcDriverImporter.Load(path)</c> — can assert on exact schemas
and values
+ /// without needing any external state. Every method returns or echoes
data that
+ /// makes a particular C-ABI call observable from the importer side.</para>
+ /// </summary>
+ internal sealed class FixtureDriver : AdbcDriver
+ {
+ public override AdbcDatabase Open(IReadOnlyDictionary<string, string>
parameters)
+ => new FixtureDatabase(parameters);
+ }
+
+ internal sealed class FixtureDatabase : AdbcDatabase
+ {
+ private readonly Dictionary<string, string> _options;
+
+ public FixtureDatabase(IReadOnlyDictionary<string, string> parameters)
+ {
+ _options = new Dictionary<string, string>(parameters.Count);
+ foreach (KeyValuePair<string, string> pair in parameters)
+ {
+ _options.Add(pair.Key, pair.Value);
+ }
+ }
+
+ public override void SetOption(string key, string value) =>
_options[key] = value;
+
+ public override AdbcConnection Connect(IReadOnlyDictionary<string,
string>? options)
+ {
+ // Connection-scoped options (set via ConnectionSetOption before
init)
+ // arrive here in `options`. Merge them into the option bag the
connection
+ // exposes so the consumer can see that ConnectionSetOption
propagated.
+ var merged = new Dictionary<string, string>(_options);
+ if (options != null)
+ {
+ foreach (KeyValuePair<string, string> pair in options)
+ {
+ merged[pair.Key] = pair.Value;
+ }
+ }
+ return new FixtureConnection(merged);
+ }
+ }
+
+ internal sealed class FixtureConnection : AdbcConnection
+ {
+ private readonly Dictionary<string, string> _options;
+ private bool _cancelled;
+
+ public FixtureConnection(Dictionary<string, string> options) {
_options = options; }
+
+ public override void SetOption(string key, string value) =>
_options[key] = value;
+
+ public override void Cancel() => _cancelled = true;
+
+ // Echoes the driver options under a fixed schema so the consumer can
assert
+ // that DatabaseSetOption and ConnectionSetOption propagated across
the ABI.
+ public override IArrowArrayStream GetObjects(
+ GetObjectsDepth depth, string? catalogPattern, string?
dbSchemaPattern,
+ string? tableNamePattern, IReadOnlyList<string>? tableTypes,
string? columnNamePattern)
+ => throw AdbcException.NotImplemented("fixture does not support
GetObjects");
+
+ public override Schema GetTableSchema(string? catalog, string?
dbSchema, string tableName)
+ => throw AdbcException.NotImplemented("fixture does not support
GetTableSchema");
+
+ public override IArrowArrayStream GetTableTypes()
+ {
+ var schema = new Schema.Builder()
+ .Field(f =>
f.Name("table_type").DataType(StringType.Default).Nullable(false))
+ .Build();
+ var values = new
StringArray.Builder().Append("TABLE").Append("VIEW").Build();
+ var batch = new RecordBatch(schema, new IArrowArray[] { values },
2);
+ return new SingleBatchStream(schema, batch);
+ }
+
+ public override AdbcStatement CreateStatement() => new
FixtureStatement(this, _options);
+
+ internal bool WasCancelled => _cancelled;
+ }
+
+ internal sealed class FixtureStatement : AdbcStatement
+ {
+ private readonly FixtureConnection _connection;
+ private readonly Dictionary<string, string> _connectionOptions;
+ private readonly Dictionary<string, string> _statementOptions = new
Dictionary<string, string>();
+ private string? _sqlQuery;
+ private bool _cancelled;
+ private bool _executed;
+
+ public FixtureStatement(FixtureConnection connection,
Dictionary<string, string> connectionOptions)
+ {
+ _connection = connection;
+ _connectionOptions = connectionOptions;
+ }
+
+ public override string? SqlQuery
+ {
+ get => _sqlQuery;
+ set => _sqlQuery = value;
+ }
+
+ public override void SetOption(string key, string value) =>
_statementOptions[key] = value;
+
+ public override void Cancel() => _cancelled = true;
+
+ // Returns a fixed schema independent of the query. ExecuteSchema must
NOT
+ // execute the query, so the schema is computable without side effects.
+ public override Schema ExecuteSchema() => ResultSchema();
+
+ public override QueryResult ExecuteQuery()
+ {
+ _executed = true;
+
+ // The result encodes the inputs the C ABI carried into this
method so the
+ // consumer can assert that SqlQuery, statement options, and
database/
+ // connection options all round-tripped.
+ Schema schema = ResultSchema();
+ var keys = new StringArray.Builder();
+ var values = new StringArray.Builder();
+
+ keys.Append("sql"); values.Append(_sqlQuery ?? string.Empty);
+ keys.Append("stmt_option_count");
values.Append(_statementOptions.Count.ToString());
+ keys.Append("db_option_count");
values.Append(_connectionOptions.Count.ToString());
+
+ // Surface user-supplied options verbatim. Sort for stability.
+ string[] stmtKeys = SortedKeys(_statementOptions);
+ for (int i = 0; i < stmtKeys.Length; i++)
+ {
+ keys.Append("stmt:" + stmtKeys[i]);
+ values.Append(_statementOptions[stmtKeys[i]]);
+ }
+ string[] dbKeys = SortedKeys(_connectionOptions);
+ for (int i = 0; i < dbKeys.Length; i++)
+ {
+ keys.Append("db:" + dbKeys[i]);
+ values.Append(_connectionOptions[dbKeys[i]]);
+ }
+
+ StringArray keyArray = keys.Build();
+ StringArray valueArray = values.Build();
+ var batch = new RecordBatch(schema, new IArrowArray[] { keyArray,
valueArray }, keyArray.Length);
+ return new QueryResult(keyArray.Length, new
SingleBatchStream(schema, batch));
+ }
+
+ public override UpdateResult ExecuteUpdate()
+ => throw AdbcException.NotImplemented("fixture does not support
ExecuteUpdate");
+
+ private static Schema ResultSchema() => new Schema.Builder()
+ .Field(f =>
f.Name("key").DataType(StringType.Default).Nullable(false))
+ .Field(f =>
f.Name("value").DataType(StringType.Default).Nullable(true))
+ .Build();
+
+ private static string[] SortedKeys(Dictionary<string, string> dict)
+ {
+ var keys = new List<string>(dict.Count);
+ foreach (KeyValuePair<string, string> pair in dict)
+ {
+ keys.Add(pair.Key);
+ }
+ keys.Sort(StringComparer.Ordinal);
+ return keys.ToArray();
+ }
+
+ internal bool WasCancelled => _cancelled;
+ internal bool WasExecuted => _executed;
+ }
+
+ /// <summary>
+ /// Single-batch IArrowArrayStream used to return deterministic data.
+ /// </summary>
+ internal sealed class SingleBatchStream : IArrowArrayStream
+ {
+ private readonly Schema _schema;
+ private RecordBatch? _batch;
+
+ public SingleBatchStream(Schema schema, RecordBatch batch)
+ {
+ _schema = schema;
+ _batch = batch;
+ }
+
+ public Schema Schema => _schema;
+
+ public ValueTask<RecordBatch?>
ReadNextRecordBatchAsync(CancellationToken cancellationToken = default)
+ {
+ RecordBatch? result = _batch;
+ _batch = null;
+ return new ValueTask<RecordBatch?>(result);
+ }
+
+ public void Dispose() { _batch?.Dispose(); _batch = null; }
+ }
+}
diff --git
a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs
new file mode 100644
index 000000000..f7042b6b6
--- /dev/null
+++
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Apache.Arrow.Adbc.C;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+using Xunit;
+
+namespace Apache.Arrow.Adbc.TestFixture.Tests
+{
+ /// <summary>
+ /// Loads the AOT-published <c>Apache.Arrow.Adbc.TestFixture.Native</c>
shared
+ /// library through <see cref="CAdbcDriverImporter"/> and exercises both
+ /// sides of the C ABI: import path (driver manager → native lib) and
export
+ /// path (native lib → managed fixture inside the AOT'd assembly).
+ ///
+ /// <para>The path to the published library is taken from the
+ /// <c>ADBC_TEST_AOT_FIXTURE_PATH</c> environment variable. When unset the
+ /// tests skip rather than fail, so the suite stays green in local builds
+ /// that haven't run <c>dotnet publish</c> against the native
project.</para>
+ /// </summary>
+ public class AotFixtureTests
+ {
+ private const string FixturePathEnvVar = "ADBC_TEST_AOT_FIXTURE_PATH";
+
+ private static string? ResolveFixturePath()
+ {
+ string? path =
Environment.GetEnvironmentVariable(FixturePathEnvVar);
+ return string.IsNullOrEmpty(path) || !File.Exists(path) ? null :
path;
+ }
+
+ private static AdbcDriver LoadFixture()
+ {
+ string? path = ResolveFixturePath();
+ Skip.IfNot(
+ path != null,
+ $"Set {FixturePathEnvVar} to the AOT-published
Apache.Arrow.Adbc.TestFixture.Native shared library to run this test.");
+ return CAdbcDriverImporter.Load(path!);
+ }
+
+ [SkippableFact]
+ public void DriverNegotiatesV1_1_0()
+ {
+ using AdbcDriver driver = LoadFixture();
+ Assert.Equal(AdbcVersion.Version_1_1_0, driver.DriverVersion);
+ }
+
+ [SkippableFact]
+ public async Task ExecuteQueryRoundTripsThroughAotBoundary()
+ {
+ using AdbcDriver driver = LoadFixture();
+ using AdbcDatabase db = driver.Open(new Dictionary<string, string>
+ {
+ { "db.host", "example.com" },
+ });
+ using AdbcConnection conn = db.Connect(new Dictionary<string,
string>
+ {
+ { "conn.user", "alice" },
+ });
+ using AdbcStatement stmt = conn.CreateStatement();
+ stmt.SetOption("stmt.timeout", "30");
+ stmt.SqlQuery = "SELECT * FROM fixture";
+
+ QueryResult result = stmt.ExecuteQuery();
+ using IArrowArrayStream stream = result.Stream!;
+ Assert.NotNull(stream);
+
+ // Fixture schema: key utf8 not null, value utf8 nullable
+ Schema schema = stream.Schema;
+ Assert.Equal(2, schema.FieldsList.Count);
+ Assert.Equal("key", schema.FieldsList[0].Name);
+ Assert.Equal("value", schema.FieldsList[1].Name);
+
+ using RecordBatch? batch = await stream.ReadNextRecordBatchAsync();
+ Assert.NotNull(batch);
+
+ StringArray keys = Assert.IsType<StringArray>(batch!.Column(0));
+ StringArray values = Assert.IsType<StringArray>(batch.Column(1));
+ var resultMap = new Dictionary<string, string?>();
+ for (int i = 0; i < batch.Length; i++)
+ {
+ resultMap[keys.GetString(i)] = values.GetString(i);
+ }
+
+ // Echo'd values prove the C ABI carried the inputs across.
+ Assert.Equal("SELECT * FROM fixture", resultMap["sql"]);
+ Assert.Equal("30", resultMap["stmt:stmt.timeout"]);
+ // db.host arrived via DatabaseSetOption; conn.user via
ConnectionSetOption.
+ // Both go into the same option bag exposed to the statement.
+ Assert.Equal("example.com", resultMap["db:db.host"]);
+ Assert.Equal("alice", resultMap["db:conn.user"]);
+
+ Assert.Null(await stream.ReadNextRecordBatchAsync());
+ }
+
+ [SkippableFact]
+ public void ExecuteSchemaReturnsSchemaOnly()
+ {
+ using AdbcDriver driver = LoadFixture();
+ using AdbcDatabase db = driver.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+ stmt.SqlQuery = "SELECT 1";
+
+ Schema schema = stmt.ExecuteSchema();
+ Assert.Equal(2, schema.FieldsList.Count);
+ Assert.Equal(ArrowTypeId.String,
schema.FieldsList[0].DataType.TypeId);
+ }
+
+ [SkippableFact]
+ public void GetTableTypesReturnsFixtureValues()
+ {
+ using AdbcDriver driver = LoadFixture();
+ using AdbcDatabase db = driver.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+
+ using IArrowArrayStream stream = conn.GetTableTypes();
+ Assert.Equal("table_type", stream.Schema.FieldsList[0].Name);
+ }
+
+ [SkippableFact]
+ public void CancelDoesNotThrow()
+ {
+ using AdbcDriver driver = LoadFixture();
+ using AdbcDatabase db = driver.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+
+ stmt.Cancel();
+ conn.Cancel();
+ // Success means the call dispatched across the C ABI and returned
+ // AdbcStatusCode.Success rather than NotImplemented.
+ }
+
+ [SkippableFact]
+ public void NotImplementedSurfaceIsReported()
+ {
+ using AdbcDriver driver = LoadFixture();
+ using AdbcDatabase db = driver.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+
+ // The fixture deliberately throws NotImplemented for GetObjects;
verify
+ // that maps back to an AdbcException with NotImplemented status.
+ AdbcException ex = Assert.ThrowsAny<AdbcException>(
+ () => conn.GetObjects(AdbcConnection.GetObjectsDepth.All,
null, null, null, null, null));
+ Assert.Equal(AdbcStatusCode.NotImplemented, ex.Status);
+ }
+ }
+}
diff --git
a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/Apache.Arrow.Adbc.TestFixture.Tests.csproj
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/Apache.Arrow.Adbc.TestFixture.Tests.csproj
new file mode 100644
index 000000000..594275dc0
--- /dev/null
+++
b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/Apache.Arrow.Adbc.TestFixture.Tests.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <!--
+ Loads the AOT-published Apache.Arrow.Adbc.TestFixture.Native shared
+ library through CAdbcDriverImporter and exercises both sides of the
+ C ABI end-to-end. The fixture library is built and AOT-published
+ out-of-band (see csharp.yml `csharp-aot` job); these tests locate it
+ via the ADBC_TEST_AOT_FIXTURE_PATH environment variable and skip
+ cleanly when it isn't available, so the suite remains green on
+ machines without an AOT-published artifact.
+ -->
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <IsTestProject>true</IsTestProject>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" />
+ <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.runner.visualstudio">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers;
buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Xunit.SkippableFact" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference
Include="..\..\..\src\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
+ </ItemGroup>
+</Project>
diff --git
a/csharp/test/Apache.Arrow.Adbc.Tests/ExportedDriverRoundTripTests.cs
b/csharp/test/Apache.Arrow.Adbc.Tests/ExportedDriverRoundTripTests.cs
index 071e8805e..c9d759670 100644
--- a/csharp/test/Apache.Arrow.Adbc.Tests/ExportedDriverRoundTripTests.cs
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/ExportedDriverRoundTripTests.cs
@@ -114,6 +114,112 @@ namespace Apache.Arrow.Adbc.Tests
Assert.Contains("boom", ex.Message);
}
+ [Fact]
+ public void StatementSetOptionIsMarshaledToProducer()
+ {
+ var fixture = new FixtureDriver();
+
+ using AdbcDriver imported =
CAdbcDriverImporter.Load(CreateAdapter(fixture));
+ using AdbcDatabase db = imported.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+
+ stmt.SetOption("adbc.ingest.target_table", "my_table");
+ stmt.SetOption("adbc.ingest.mode", "append");
+
+ Assert.Equal("my_table",
fixture.LastStatement!.Options["adbc.ingest.target_table"]);
+ Assert.Equal("append",
fixture.LastStatement!.Options["adbc.ingest.mode"]);
+ }
+
+ [Fact]
+ public void StatementCancelIsMarshaledToProducer()
+ {
+ var fixture = new FixtureDriver();
+
+ using AdbcDriver imported =
CAdbcDriverImporter.Load(CreateAdapter(fixture));
+ Assert.Equal(AdbcVersion.Version_1_1_0, imported.DriverVersion);
+
+ using AdbcDatabase db = imported.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+
+ stmt.Cancel();
+
+ Assert.True(fixture.LastStatement!.WasCancelled);
+ }
+
+ [Fact]
+ public void ConnectionCancelIsMarshaledToProducer()
+ {
+ var fixture = new FixtureDriver();
+
+ using AdbcDriver imported =
CAdbcDriverImporter.Load(CreateAdapter(fixture));
+ Assert.Equal(AdbcVersion.Version_1_1_0, imported.DriverVersion);
+
+ using AdbcDatabase db = imported.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+
+ conn.Cancel();
+
+ Assert.True(fixture.LastConnection!.WasCancelled);
+ }
+
+ [Fact]
+ public void StatementExecuteSchemaReturnsSchemaWithoutExecutingQuery()
+ {
+ var fixture = new FixtureDriver();
+
+ using AdbcDriver imported =
CAdbcDriverImporter.Load(CreateAdapter(fixture));
+ using AdbcDatabase db = imported.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+ stmt.SqlQuery = "SELECT 42";
+
+ Schema schema = stmt.ExecuteSchema();
+ Assert.Single(schema.FieldsList);
+ Assert.Equal("answer", schema.FieldsList[0].Name);
+ Assert.Equal(ArrowTypeId.Int32,
schema.FieldsList[0].DataType.TypeId);
+
+ Assert.False(fixture.LastStatement!.WasExecuted);
+ }
+
+ [Fact]
+ public void V1_0_0_DriverInitRejectsV1_1_0_OnlyFunctions()
+ {
+ // Force the importer to settle on v1.0.0 by having the producer's
init
+ // refuse v1.1.0. This proves the v1.1.0 functions are gated by
version.
+ var fixture = new FixtureDriver();
+ AdbcDriverInit adapter = (int version, ref CAdbcDriver
nativeDriver, ref CAdbcError error) =>
+ {
+ if (version != AdbcVersion.Version_1_0_0) { return
AdbcStatusCode.NotImplemented; }
+ return CallExporter(version, ref nativeDriver, ref error,
fixture);
+ };
+
+ using AdbcDriver imported = CAdbcDriverImporter.Load(adapter);
+ Assert.Equal(AdbcVersion.Version_1_0_0, imported.DriverVersion);
+
+ using AdbcDatabase db = imported.Open(new Dictionary<string,
string>());
+ using AdbcConnection conn = db.Connect(null);
+ using AdbcStatement stmt = conn.CreateStatement();
+
+ // ExecuteSchema, Cancel are 1.1.0-only: on a v1.0.0 driver the
importer's
+ // built-in defaults return NotImplemented.
+ AdbcException ex = Assert.ThrowsAny<AdbcException>(() =>
stmt.ExecuteSchema());
+ Assert.Equal(AdbcStatusCode.NotImplemented, ex.Status);
+ }
+
+ private static AdbcStatusCode CallExporter(int version, ref
CAdbcDriver nativeDriver, ref CAdbcError error, AdbcDriver driver)
+ {
+ unsafe
+ {
+ fixed (CAdbcDriver* dp = &nativeDriver)
+ fixed (CAdbcError* ep = &error)
+ {
+ return CAdbcDriverExporter.AdbcDriverInit(version, dp, ep,
driver);
+ }
+ }
+ }
+
private static AdbcDriverInit CreateAdapter(AdbcDriver driver)
{
return (int version, ref CAdbcDriver nativeDriver, ref CAdbcError
error) =>
@@ -132,6 +238,7 @@ namespace Apache.Arrow.Adbc.Tests
private sealed class FixtureDriver : AdbcDriver
{
public FixtureDatabase? LastDatabase { get; private set; }
+ public FixtureConnection? LastConnection { get; private set; }
public FixtureStatement? LastStatement { get; private set; }
public Exception? ThrowOnExecute { get; set; }
@@ -142,6 +249,7 @@ namespace Apache.Arrow.Adbc.Tests
return db;
}
+ internal void RecordConnection(FixtureConnection conn) =>
LastConnection = conn;
internal void RecordStatement(FixtureStatement stmt) =>
LastStatement = stmt;
}
@@ -167,7 +275,11 @@ namespace Apache.Arrow.Adbc.Tests
public override void SetOption(string key, string value) =>
Options[key] = value;
public override AdbcConnection Connect(IReadOnlyDictionary<string,
string>? options)
- => new FixtureConnection(_driver);
+ {
+ var conn = new FixtureConnection(_driver);
+ _driver.RecordConnection(conn);
+ return conn;
+ }
}
private sealed class FixtureConnection : AdbcConnection
@@ -176,6 +288,10 @@ namespace Apache.Arrow.Adbc.Tests
public FixtureConnection(FixtureDriver driver) { _driver = driver;
}
+ public bool WasCancelled { get; private set; }
+
+ public override void Cancel() => WasCancelled = true;
+
public override AdbcStatement CreateStatement()
{
var stmt = new FixtureStatement(_driver);
@@ -203,6 +319,9 @@ namespace Apache.Arrow.Adbc.Tests
public FixtureStatement(FixtureDriver driver) { _driver = driver; }
public string? ReceivedQuery => _sqlQuery;
+ public Dictionary<string, string> Options { get; } = new
Dictionary<string, string>();
+ public bool WasCancelled { get; private set; }
+ public bool WasExecuted { get; private set; }
public override string? SqlQuery
{
@@ -210,13 +329,18 @@ namespace Apache.Arrow.Adbc.Tests
set => _sqlQuery = value;
}
+ public override void SetOption(string key, string value) =>
Options[key] = value;
+
+ public override void Cancel() => WasCancelled = true;
+
+ public override Schema ExecuteSchema() => BuildResultSchema();
+
public override QueryResult ExecuteQuery()
{
if (_driver.ThrowOnExecute != null) { throw
_driver.ThrowOnExecute; }
- var schema = new Schema.Builder()
- .Field(f =>
f.Name("answer").DataType(Int32Type.Default).Nullable(false))
- .Build();
+ WasExecuted = true;
+ Schema schema = BuildResultSchema();
var column = new Int32Array.Builder().Append(42).Build();
var batch = new RecordBatch(schema, new IArrowArray[] { column
}, 1);
return new QueryResult(1, new SingleBatchStream(schema,
batch));
@@ -224,6 +348,10 @@ namespace Apache.Arrow.Adbc.Tests
public override UpdateResult ExecuteUpdate()
=> throw AdbcException.NotImplemented("fixture does not
support ExecuteUpdate");
+
+ private static Schema BuildResultSchema() => new Schema.Builder()
+ .Field(f =>
f.Name("answer").DataType(Int32Type.Default).Nullable(false))
+ .Build();
}
private sealed class SingleBatchStream : IArrowArrayStream