feat(csharp): Add driver manager#4075
Conversation
|
I debated whether to put this in its own assembly or as a subfolder under the shared Adbc project. I decided on a subfolder approach to reduce the number of dependencies required (especially since the AdbcDrivers are still using submodules and any tests there would require an additional submodule to be managed). |
|
To what extent have you tried this with .NET 8.0? I'm discovering that dynamic loading of assemblies is fairly different in that environment. |
It has .NET 8 tests that all pass but I added some more items in the recent push. |
I added some items to address #4082. For #4085 , based on your table, this implementation currently does: Feature -> How It Works
❌ Not Yet Implemented
|
|
Anything else needed for this one @lidavidm @CurtHagenlocher ? |
|
We have more users asking for the ability to edit configuration files for their drivers. Is there anything else needed for this @lidavidm or @CurtHagenlocher ? |
|
My main concern is being explicit about how dependency resolution is expected to work for .NET 8+, as I think there is trickiness and subtlety there based on my experiences in our own code base. I'll try to find time this week to take a closer look and run a few experiments. |
CurtHagenlocher
left a comment
There was a problem hiding this comment.
I'm still working on reviewing this; there's more to absorb than I'd expected. Here's some initial feedback.
| /// </item> | ||
| /// <item> | ||
| /// <description> | ||
| /// If a driver does not ship a <c>.deps.json</c>, the loader still works but |
There was a problem hiding this comment.
In my experience, loading a driver without a .deps.json file did not generally work. Has it worked for you?
| // .deps.json is consulted when the default AssemblyLoadContext fails to | ||
| // resolve a dependency. AssemblyDependencyResolver throws if no .deps.json | ||
| // is present next to the assembly; in that case we silently fall back to | ||
| // the host's normal probing behavior. |
There was a problem hiding this comment.
I found that it was necessary to always reference the host's version of the following three assemblies regardless of the driver's dependencies: Apache.Arrow, Apache.Arrow.Adbc and System.Diagnostics.DiagnosticSource. That's because these three assemblies contain types that are in the public ADBC contracts. If the driver resolves to a different version of these assemblies than the host, an attempt to use an API with one of these types will produce a MethodMissingException.
A test which covers this scenario would need to have the driver built against a different version of one or more of these than the test code is built against.
| continue; | ||
| } | ||
|
|
||
| if (line.StartsWith("[", StringComparison.Ordinal) && line.EndsWith("]", StringComparison.Ordinal)) |
There was a problem hiding this comment.
Should this throw an exception if the section name contains anything other than A-Za-z0-9_-? I know we don't want or need a full-featured parser, but perhaps we should try to be overly strict in what we accept rather than reading something incorrectly.
| // Tracks which driver path "won" the very first contested resolution for a | ||
| // given simple assembly name (e.g. "Newtonsoft.Json"). Used purely to emit a | ||
| // diagnostic when a later resolver could also satisfy that name -- the runtime | ||
| // always returns the assembly already loaded by the first winner, so any later | ||
| // candidate is silently shadowed. |
There was a problem hiding this comment.
I'm not sure to what extent this is true under .NET Core. Two assemblies with different load contexts can definitely resolve the same assembly to two different files/versions. Is this code path always used or only if there's no .deps file?
|
On the whole this looks good to me in its current state. The tests need to pass, of course, and I'm not super certain about the managed driver loading code for .NET 8+. But I think the latter can be worked out as people run into problems. I'll take one last pass over the changes once the test issues are worked out. |
Everything passes now except a strange Databricks test. Maybe it needs to be re-run? |
That test has always been flaky and I was never able to figure out why. |
|
@davidhcoe. the CI tests seem to be flaky after this checkin :(. See https://github.com/apache/arrow-adbc/actions/runs/26067113103/job/76641108349 for example. |
Implements C# driver manager matching adbc_driver_manager.h spec:
Core APIs:
• LoadDriver() / FindLoadDriver() - Load native/managed drivers
• OpenDatabaseFromProfile() - TOML profile support
• AdbcLoadFlags - Search control (env/user/system paths)
Features:
• Native + managed (.NET) driver loading
• TOML manifests with env_var() expansion
• Uses a custom TOML parser vs Tomlyn because a strong name is required for dependencies
• Cross-platform driver discovery
• Option merging (profile + explicit)
• Co-located manifest detection
Tests: 40+ unit tests + sample BigQuery/Snowflake implementations (attached, not checked in)
BigQueryDriverManagerTests.txt
SnowflakeDriverManagerTests.txt