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

Reply via email to