From f7489cb807556c23f8dc98d5d7f2046ee0b74b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Sun, 27 Sep 2026 09:19:49 +0300 Subject: [PATCH 1/3] fix(csharp): fall back to the standard driver entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- .../C/CAdbcDriverImporter.cs | 25 +++++++++++++++++-- .../DriverManager/AdbcDriverManager.cs | 11 ++++++-- .../AotFixtureTests.cs | 20 +++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs index 713fbe4d32..b250665e18 100644 --- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs +++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs @@ -50,7 +50,20 @@ public static partial class CAdbcDriverImporter /// The path to the driver to load /// Whether the driver can be safely unloaded /// The name of the entry point. If not provided, the name AdbcDriverInit will be used. - public static AdbcDriver Load(string file, bool canUnload, string? entryPoint = null) + public static AdbcDriver Load(string file, bool canUnload, string? entryPoint = null) => + Load(file, canUnload, entryPoint, fallbackEntryPoint: null); + + /// + /// Loads a native driver, trying the fallback entry point when the requested symbol is absent. + /// + internal static AdbcDriver LoadWithFallback(string file, string entryPoint, string fallbackEntryPoint) => + Load(file, canUnload: false, entryPoint, fallbackEntryPoint); + + private static AdbcDriver Load( + string file, + bool canUnload, + string? entryPoint, + string? fallbackEntryPoint) { if (file == null) { @@ -71,7 +84,15 @@ public static AdbcDriver Load(string file, bool canUnload, string? entryPoint = try { entryPoint = entryPoint ?? driverInit; - IntPtr export = NativeLibrary.GetExport(library, entryPoint); + IntPtr export; + try + { + export = NativeLibrary.GetExport(library, entryPoint); + } + catch (EntryPointNotFoundException) when (fallbackEntryPoint != null) + { + export = NativeLibrary.GetExport(library, fallbackEntryPoint); + } if (export == IntPtr.Zero) { if (canUnload) { NativeLibrary.Free(library); } diff --git a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs index 3badc3e457..371b20583c 100644 --- a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs +++ b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs @@ -36,6 +36,8 @@ namespace Apache.Arrow.Adbc.DriverManager /// public static class AdbcDriverManager { + private const string DefaultNativeEntrypoint = "AdbcDriverInit"; + /// /// The environment variable that specifies additional driver search paths. /// @@ -132,7 +134,12 @@ private static AdbcDriver LoadNativeDriver(string driverPath, string? entrypoint typeName: null, manifestPath: null, loadMethod: loadMethod, - () => CAdbcDriverImporter.Load(driverPath, resolvedEntrypoint)); + () => entrypoint == null + ? CAdbcDriverImporter.LoadWithFallback( + driverPath, + resolvedEntrypoint, + DefaultNativeEntrypoint) + : CAdbcDriverImporter.Load(driverPath, resolvedEntrypoint)); } // ----------------------------------------------------------------------- @@ -641,7 +648,7 @@ public static string DeriveEntrypoint(string driverPath) baseName = baseName.Substring(adbcPrefix.Length); if (string.IsNullOrEmpty(baseName)) - return "AdbcDriverInit"; + return DefaultNativeEntrypoint; // Convert snake_case to PascalCase. string pascal = ToPascalCase(baseName); diff --git a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs index f7042b6b64..f431bb5ad8 100644 --- a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs +++ b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs @@ -63,6 +63,26 @@ public void DriverNegotiatesV1_1_0() Assert.Equal(AdbcVersion.Version_1_1_0, driver.DriverVersion); } + [SkippableFact] + public void DriverManagerFallsBackToStandardEntrypoint() + { + string fixturePath = ResolveFixturePath()!; + string directory = Path.Combine(Path.GetDirectoryName(fixturePath)!, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + string renamedPath = Path.Combine(directory, "adbc_driver_fixture" + Path.GetExtension(fixturePath)); + File.Copy(fixturePath, renamedPath); + + try + { + using AdbcDriver driver = Apache.Arrow.Adbc.DriverManager.AdbcDriverManager.LoadDriver(renamedPath); + Assert.Equal(AdbcVersion.Version_1_1_0, driver.DriverVersion); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + [SkippableFact] public async Task ExecuteQueryRoundTripsThroughAotBoundary() { From 5242099f1839de4615bdd95c1ed3fa67f5679a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Thu, 1 Oct 2026 15:37:38 +0300 Subject: [PATCH 2/3] test(csharp): avoid deleting loaded Windows fixture --- .../AotFixtureTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs index f431bb5ad8..55ba263ceb 100644 --- a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs +++ b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs @@ -79,7 +79,13 @@ public void DriverManagerFallsBackToStandardEntrypoint() } finally { - Directory.Delete(directory, recursive: true); + // DriverManager keeps native drivers loaded for the process lifetime. + // Windows therefore prevents deleting the copied DLL while the test + // process is alive; the CI workspace is disposable. + if (!OperatingSystem.IsWindows()) + { + Directory.Delete(directory, recursive: true); + } } } From bf4091f81447bf8e181eaa96e4e2626c022f6585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Thu, 1 Oct 2026 21:29:48 +0300 Subject: [PATCH 3/3] Fix manifest driver entrypoint fallback --- .../DriverManager/AdbcDriverManager.cs | 22 +++++++-- .../AotFixtureTests.cs | 49 +++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs index 371b20583c..dfae3f468f 100644 --- a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs +++ b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs @@ -725,7 +725,10 @@ private static AdbcDriver LoadFromManifest(string manifestPath, string? entrypoi DriverManifest manifest = DriverManifest.LoadFromFile(manifestPath); // Caller-supplied entrypoint wins over the manifest's. Falls back to - // a derived native symbol name only when neither is provided. + // a derived native symbol name only when neither is provided. Keep + // that provenance so the standard native fallback is not applied to + // an explicit caller- or manifest-selected symbol. + bool allowNativeFallback = entrypoint == null && manifest.Entrypoint == null; string resolvedEntrypoint = entrypoint ?? manifest.Entrypoint ?? DeriveEntrypoint(manifest.LibraryPath); @@ -733,7 +736,12 @@ private static AdbcDriver LoadFromManifest(string manifestPath, string? entrypoi string? manifestDir = Path.GetDirectoryName(Path.GetFullPath(manifestPath)); string resolvedPath = ResolveManifestPath(manifest.LibraryPath, manifestDir); - return LoadByEntrypointScheme(resolvedPath, resolvedEntrypoint, manifestPath, nameof(LoadFromManifest)); + return LoadByEntrypointScheme( + resolvedPath, + resolvedEntrypoint, + manifestPath, + nameof(LoadFromManifest), + allowNativeFallback); } /// Returns true if uses a managed-runtime scheme prefix. @@ -770,7 +778,8 @@ private static AdbcDriver LoadByEntrypointScheme( string driverPath, string entrypoint, string? manifestPath, - string loadMethod) + string loadMethod, + bool allowNativeFallback = false) { if (entrypoint.StartsWith(DotnetEntrypointScheme, StringComparison.Ordinal)) { @@ -801,7 +810,12 @@ private static AdbcDriver LoadByEntrypointScheme( typeName: null, manifestPath: manifestPath, loadMethod: loadMethod, - () => CAdbcDriverImporter.Load(driverPath, entrypoint)); + () => allowNativeFallback + ? CAdbcDriverImporter.LoadWithFallback( + driverPath, + entrypoint, + DefaultNativeEntrypoint) + : CAdbcDriverImporter.Load(driverPath, entrypoint)); } /// diff --git a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs index 55ba263ceb..ccbc15a477 100644 --- a/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs +++ b/csharp/test/AotInterop/Apache.Arrow.Adbc.TestFixture.Tests/AotFixtureTests.cs @@ -50,10 +50,15 @@ public class AotFixtureTests private static AdbcDriver LoadFixture() { string? path = ResolveFixturePath(); + SkipIfFixtureUnavailable(path); + return CAdbcDriverImporter.Load(path!); + } + + private static void SkipIfFixtureUnavailable(string? path) + { 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] @@ -66,11 +71,14 @@ public void DriverNegotiatesV1_1_0() [SkippableFact] public void DriverManagerFallsBackToStandardEntrypoint() { - string fixturePath = ResolveFixturePath()!; - string directory = Path.Combine(Path.GetDirectoryName(fixturePath)!, Guid.NewGuid().ToString("N")); + string? fixturePath = ResolveFixturePath(); + SkipIfFixtureUnavailable(fixturePath); + string? fixtureParent = Path.GetDirectoryName(fixturePath); + Skip.IfNot(!string.IsNullOrEmpty(fixtureParent), "The AOT fixture path must have a parent directory."); + string directory = Path.Combine(fixtureParent!, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); string renamedPath = Path.Combine(directory, "adbc_driver_fixture" + Path.GetExtension(fixturePath)); - File.Copy(fixturePath, renamedPath); + File.Copy(fixturePath!, renamedPath); try { @@ -89,6 +97,39 @@ public void DriverManagerFallsBackToStandardEntrypoint() } } + [SkippableFact] + public void FindLoadDriverManifestWithoutEntrypointFallsBackToStandardEntrypoint() + { + string? fixturePath = ResolveFixturePath(); + SkipIfFixtureUnavailable(fixturePath); + string? fixtureParent = Path.GetDirectoryName(fixturePath); + Skip.IfNot(!string.IsNullOrEmpty(fixtureParent), "The AOT fixture path must have a parent directory."); + string directory = Path.Combine(fixtureParent!, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + string renamedName = "adbc_driver_fixture"; + string renamedPath = Path.Combine(directory, renamedName + Path.GetExtension(fixturePath)); + File.Copy(fixturePath!, renamedPath); + File.WriteAllText( + Path.Combine(directory, renamedName + ".toml"), + "manifest_version = 1\n[Driver]\nshared = \"" + Path.GetFileName(renamedPath) + "\"\n"); + + try + { + using AdbcDriver driver = Apache.Arrow.Adbc.DriverManager.AdbcDriverManager.FindLoadDriver( + renamedName, + loadOptions: Apache.Arrow.Adbc.DriverManager.AdbcLoadFlags.Default, + additionalSearchPathList: directory); + Assert.Equal(AdbcVersion.Version_1_1_0, driver.DriverVersion); + } + finally + { + if (!OperatingSystem.IsWindows()) + { + Directory.Delete(directory, recursive: true); + } + } + } + [SkippableFact] public async Task ExecuteQueryRoundTripsThroughAotBoundary() {