This is an automated email from the ASF dual-hosted git repository.

zeroshade 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 2519191fb feat: load driver from uri (#3694)
2519191fb is described below

commit 2519191fbba848fe776dcdac9ca70f09486e4d66
Author: Matt Topol <[email protected]>
AuthorDate: Wed Nov 19 13:15:19 2025 -0500

    feat: load driver from uri (#3694)
    
    Allow loading a driver via the driver manager using only a URI
    
    ---------
    
    Co-authored-by: David Li <[email protected]>
---
 c/driver_manager/adbc_driver_manager.cc            |  66 ++++++++++-
 c/driver_manager/adbc_driver_manager_test.cc       |  80 +++++++++++++
 go/adbc/drivermgr/adbc_driver_manager.cc           |  66 ++++++++++-
 python/adbc_driver_manager/tests/test_manifest.py  |  21 ++++
 rust/driver_manager/src/lib.rs                     | 124 ++++++++++++++++++++-
 rust/driver_manager/tests/driver_manager_sqlite.rs |  15 ++-
 6 files changed, 358 insertions(+), 14 deletions(-)

diff --git a/c/driver_manager/adbc_driver_manager.cc 
b/c/driver_manager/adbc_driver_manager.cc
index 29a4b6a0f..b2fdb173f 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -62,6 +62,14 @@ ADBC_EXPORT
 std::filesystem::path InternalAdbcSystemConfigDir();
 #endif  // !defined(_WIN32)
 
+struct ParseDriverUriResult {
+  std::string_view driver;
+  std::optional<std::string_view> uri;
+};
+
+ADBC_EXPORT
+std::optional<ParseDriverUriResult> 
InternalAdbcParseDriverUri(std::string_view& str);
+
 namespace {
 
 /// \brief Where a search path came from (for error reporting)
@@ -1540,6 +1548,33 @@ std::string 
InternalAdbcDriverManagerDefaultEntrypoint(const std::string& driver
   return entrypoint;
 }
 
+ADBC_EXPORT
+std::optional<ParseDriverUriResult> 
InternalAdbcParseDriverUri(std::string_view& str) {
+  std::string::size_type pos = str.find(":");
+  if (pos == std::string::npos) {
+    return std::nullopt;
+  }
+
+  std::string_view d = str.substr(0, pos);
+  if (str.size() <= pos + 1) {
+    return ParseDriverUriResult{d, std::nullopt};
+  }
+
+#ifdef _WIN32
+  if (std::filesystem::exists(std::filesystem::path(str))) {
+    // No scheme, just a path
+    return ParseDriverUriResult{str, std::nullopt};
+  }
+#endif
+
+  if (str[pos + 1] == '/') {  // scheme is also driver
+    return ParseDriverUriResult{d, str};
+  }
+
+  // driver:scheme:.....
+  return ParseDriverUriResult{d, str.substr(pos + 1)};
+}
+
 // Direct implementations of API methods
 
 int AdbcErrorGetDetailCount(const struct AdbcError* error) {
@@ -1691,16 +1726,35 @@ AdbcStatusCode AdbcDatabaseSetOption(struct 
AdbcDatabase* database, const char*
   TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
   if (std::strcmp(key, "driver") == 0) {
     std::string_view v{value};
-    std::string::size_type pos = v.find("://");
-    if (pos != std::string::npos) {
-      std::string_view d = v.substr(0, pos);
-      args->driver = std::string{d};
-      args->options["uri"] = std::string{v};
+    auto result = InternalAdbcParseDriverUri(v);
+    if (!result) {
+      args->driver = std::string{v};
     } else {
-      args->driver = value;
+      args->driver = std::string{result->driver};
+      if (result->uri) {
+        args->options["uri"] = std::string{*result->uri};
+      }
     }
   } else if (std::strcmp(key, "entrypoint") == 0) {
     args->entrypoint = value;
+  } else if (std::strcmp(key, "uri") == 0) {
+    if (!args->driver.empty()) {  // if driver is already set, just set uri
+      args->options[key] = value;
+    } else {
+      std::string_view v{value};
+      auto result = InternalAdbcParseDriverUri(v);
+      if (!result) {
+        SetError(error, "Invalid URI: missing scheme");
+        return ADBC_STATUS_INVALID_ARGUMENT;
+      }
+
+      args->driver = std::string{result->driver};
+      if (!result->uri) {
+        SetError(error, "Invalid URI: " + std::string{value});
+        return ADBC_STATUS_INVALID_ARGUMENT;
+      }
+      args->options["uri"] = std::string{*result->uri};
+    }
   } else {
     args->options[key] = value;
   }
diff --git a/c/driver_manager/adbc_driver_manager_test.cc 
b/c/driver_manager/adbc_driver_manager_test.cc
index 85a53c04a..e52e717f1 100644
--- a/c/driver_manager/adbc_driver_manager_test.cc
+++ b/c/driver_manager/adbc_driver_manager_test.cc
@@ -40,6 +40,13 @@ std::string InternalAdbcDriverManagerDefaultEntrypoint(const 
std::string& filena
 std::vector<std::filesystem::path> InternalAdbcParsePath(const 
std::string_view path);
 std::filesystem::path InternalAdbcUserConfigDir();
 
+struct ParseDriverUriResult {
+  std::string_view driver;
+  std::optional<std::string_view> uri;
+};
+
+std::optional<ParseDriverUriResult> 
InternalAdbcParseDriverUri(std::string_view& str);
+
 // Tests of the SQLite example driver, except using the driver manager
 
 namespace adbc {
@@ -413,6 +420,51 @@ TEST(AdbcDriverManagerInternal, InternalAdbcParsePath) {
   EXPECT_THAT(output, ::testing::ElementsAreArray(paths));
 }
 
+TEST(AdbcDriverManagerInternal, InternalAdbcParseDriverUri) {
+  std::vector<std::pair<std::string, std::optional<ParseDriverUriResult>>> 
uris = {
+      {"sqlite", std::nullopt},
+      {"sqlite:", {{"sqlite", std::nullopt}}},
+      {"sqlite:file::memory:", {{"sqlite", "file::memory:"}}},
+      {"sqlite:file::memory:?cache=shared", {{"sqlite", 
"file::memory:?cache=shared"}}},
+      {"postgresql://a:b@localhost:9999/nonexistent",
+       {{"postgresql", "postgresql://a:b@localhost:9999/nonexistent"}}}};
+
+#ifdef _WIN32
+  auto temp_dir = std::filesystem::temp_directory_path() / 
"adbc_driver_manager_tests";
+  std::filesystem::create_directories(temp_dir);
+  std::string temp_driver_path = temp_dir.string() + "\\driver.dll";
+  std::ofstream temp_driver_file(temp_driver_path);
+  temp_driver_file << "placeholder";
+  temp_driver_file.close();
+
+  uris.push_back({temp_driver_path, {{temp_driver_path, std::nullopt}}});
+#endif
+
+  auto cmp = [](std::optional<ParseDriverUriResult> a,
+                std::optional<ParseDriverUriResult> b) {
+    if (!a.has_value()) {
+      EXPECT_FALSE(b.has_value());
+      return;
+    }
+    EXPECT_EQ(a->driver, b->driver);
+    if (!a->uri) {
+      EXPECT_FALSE(b->uri);
+    } else {
+      EXPECT_EQ(*a->uri, *b->uri);
+    }
+  };
+
+  for (const auto& [uri, expected] : uris) {
+    std::string_view uri_view = uri;
+    auto result = InternalAdbcParseDriverUri(uri_view);
+    cmp(result, expected);
+  }
+
+#ifdef _WIN32
+  std::filesystem::remove_all(temp_dir);
+#endif
+}
+
 class DriverManifest : public ::testing::Test {
  public:
   void SetUp() override {
@@ -1110,4 +1162,32 @@ shared = "adbc_driver_postgresql")";
   ASSERT_TRUE(std::filesystem::remove(filepath));
 }
 
+TEST_F(DriverManifest, DriverFromUri) {
+  auto filepath = temp_dir / "sqlite.toml";
+  std::ofstream test_manifest_file(filepath);
+  ASSERT_TRUE(test_manifest_file.is_open());
+  test_manifest_file << R"([Driver]
+shared = "adbc_driver_sqlite")";
+  test_manifest_file.close();
+
+  const std::string uri = "sqlite:file::memory:";
+  for (const auto& driver_option : {"driver", "uri"}) {
+    SCOPED_TRACE(driver_option);
+    SCOPED_TRACE(uri);
+    adbc_validation::Handle<struct AdbcDatabase> database;
+    ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+    ASSERT_THAT(
+        AdbcDatabaseSetOption(&database.value, driver_option, uri.c_str(), 
&error),
+        IsOkStatus(&error));
+    std::string search_path = temp_dir.string();
+    ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+                    &database.value, search_path.data(), &error),
+                IsOkStatus(&error));
+    ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+                IsStatus(ADBC_STATUS_OK, &error));
+  }
+
+  ASSERT_TRUE(std::filesystem::remove(filepath));
+}
+
 }  // namespace adbc
diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc 
b/go/adbc/drivermgr/adbc_driver_manager.cc
index 29a4b6a0f..b2fdb173f 100644
--- a/go/adbc/drivermgr/adbc_driver_manager.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager.cc
@@ -62,6 +62,14 @@ ADBC_EXPORT
 std::filesystem::path InternalAdbcSystemConfigDir();
 #endif  // !defined(_WIN32)
 
+struct ParseDriverUriResult {
+  std::string_view driver;
+  std::optional<std::string_view> uri;
+};
+
+ADBC_EXPORT
+std::optional<ParseDriverUriResult> 
InternalAdbcParseDriverUri(std::string_view& str);
+
 namespace {
 
 /// \brief Where a search path came from (for error reporting)
@@ -1540,6 +1548,33 @@ std::string 
InternalAdbcDriverManagerDefaultEntrypoint(const std::string& driver
   return entrypoint;
 }
 
+ADBC_EXPORT
+std::optional<ParseDriverUriResult> 
InternalAdbcParseDriverUri(std::string_view& str) {
+  std::string::size_type pos = str.find(":");
+  if (pos == std::string::npos) {
+    return std::nullopt;
+  }
+
+  std::string_view d = str.substr(0, pos);
+  if (str.size() <= pos + 1) {
+    return ParseDriverUriResult{d, std::nullopt};
+  }
+
+#ifdef _WIN32
+  if (std::filesystem::exists(std::filesystem::path(str))) {
+    // No scheme, just a path
+    return ParseDriverUriResult{str, std::nullopt};
+  }
+#endif
+
+  if (str[pos + 1] == '/') {  // scheme is also driver
+    return ParseDriverUriResult{d, str};
+  }
+
+  // driver:scheme:.....
+  return ParseDriverUriResult{d, str.substr(pos + 1)};
+}
+
 // Direct implementations of API methods
 
 int AdbcErrorGetDetailCount(const struct AdbcError* error) {
@@ -1691,16 +1726,35 @@ AdbcStatusCode AdbcDatabaseSetOption(struct 
AdbcDatabase* database, const char*
   TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
   if (std::strcmp(key, "driver") == 0) {
     std::string_view v{value};
-    std::string::size_type pos = v.find("://");
-    if (pos != std::string::npos) {
-      std::string_view d = v.substr(0, pos);
-      args->driver = std::string{d};
-      args->options["uri"] = std::string{v};
+    auto result = InternalAdbcParseDriverUri(v);
+    if (!result) {
+      args->driver = std::string{v};
     } else {
-      args->driver = value;
+      args->driver = std::string{result->driver};
+      if (result->uri) {
+        args->options["uri"] = std::string{*result->uri};
+      }
     }
   } else if (std::strcmp(key, "entrypoint") == 0) {
     args->entrypoint = value;
+  } else if (std::strcmp(key, "uri") == 0) {
+    if (!args->driver.empty()) {  // if driver is already set, just set uri
+      args->options[key] = value;
+    } else {
+      std::string_view v{value};
+      auto result = InternalAdbcParseDriverUri(v);
+      if (!result) {
+        SetError(error, "Invalid URI: missing scheme");
+        return ADBC_STATUS_INVALID_ARGUMENT;
+      }
+
+      args->driver = std::string{result->driver};
+      if (!result->uri) {
+        SetError(error, "Invalid URI: " + std::string{value});
+        return ADBC_STATUS_INVALID_ARGUMENT;
+      }
+      args->options["uri"] = std::string{*result->uri};
+    }
   } else {
     args->options[key] = value;
   }
diff --git a/python/adbc_driver_manager/tests/test_manifest.py 
b/python/adbc_driver_manager/tests/test_manifest.py
index 0b4e62bfa..70e374efc 100644
--- a/python/adbc_driver_manager/tests/test_manifest.py
+++ b/python/adbc_driver_manager/tests/test_manifest.py
@@ -41,6 +41,27 @@ shared = "adbc_driver_sqlite"
             assert cursor.fetchone() is not None
 
 
[email protected]
+def test_manifest_implicit_uri(tmp_path, monkeypatch) -> None:
+    with (tmp_path / "testdriver.toml").open("w") as sink:
+        sink.write(
+            """
+name = "my driver"
+version = "0.1.0"
+
+[Driver]
+shared = "adbc_driver_sqlite"
+            """
+        )
+
+    monkeypatch.setenv("ADBC_DRIVER_PATH", str(tmp_path))
+
+    with adbc_driver_manager.dbapi.connect("testdriver:file::memory:") as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("SELECT sqlite_version()")
+            assert cursor.fetchone() is not None
+
+
 def test_manifest_indirect_unknown_driver(tmp_path, monkeypatch) -> None:
     with (tmp_path / "testdriver2.toml").open("w") as sink:
         sink.write(
diff --git a/rust/driver_manager/src/lib.rs b/rust/driver_manager/src/lib.rs
index 6fed38d04..173b60d06 100644
--- a/rust/driver_manager/src/lib.rs
+++ b/rust/driver_manager/src/lib.rs
@@ -121,7 +121,7 @@ use toml::de::DeTable;
 use adbc_core::{
     constants,
     error::{AdbcStatusCode, Error, Result, Status},
-    options::{self, AdbcVersion, InfoCode, OptionValue},
+    options::{self, AdbcVersion, InfoCode, OptionDatabase, OptionValue},
     Connection, Database, Driver, LoadFlags, Optionable, PartitionedResult, 
Statement,
     LOAD_FLAG_ALLOW_RELATIVE_PATHS, LOAD_FLAG_SEARCH_ENV, 
LOAD_FLAG_SEARCH_SYSTEM,
     LOAD_FLAG_SEARCH_USER,
@@ -890,6 +890,47 @@ pub struct ManagedDatabase {
 }
 
 impl ManagedDatabase {
+    pub fn from_uri(
+        uri: &str,
+        entrypoint: Option<&[u8]>,
+        version: AdbcVersion,
+        load_flags: LoadFlags,
+        additional_search_paths: Option<Vec<PathBuf>>,
+    ) -> Result<Self> {
+        Self::from_uri_with_opts(
+            uri,
+            entrypoint,
+            version,
+            load_flags,
+            additional_search_paths,
+            std::iter::empty(),
+        )
+    }
+
+    pub fn from_uri_with_opts(
+        uri: &str,
+        entrypoint: Option<&[u8]>,
+        version: AdbcVersion,
+        load_flags: LoadFlags,
+        additional_search_paths: Option<Vec<PathBuf>>,
+        opts: impl IntoIterator<Item = (<Self as Optionable>::Option, 
OptionValue)>,
+    ) -> Result<Self> {
+        let (driver, final_uri) = parse_driver_uri(uri)?;
+
+        let mut drv = ManagedDriver::load_from_name(
+            driver,
+            entrypoint,
+            version,
+            load_flags,
+            additional_search_paths,
+        )?;
+
+        drv.new_database_with_opts(opts.into_iter().chain(std::iter::once((
+            OptionDatabase::Uri,
+            OptionValue::String(final_uri.to_string()),
+        ))))
+    }
+
     fn ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
         &self.inner.driver.driver
     }
@@ -1876,6 +1917,30 @@ fn get_search_paths(lvls: LoadFlags) -> Vec<PathBuf> {
     result
 }
 
+fn parse_driver_uri(uri: &str) -> Result<(&str, &str)> {
+    let idx = uri.find(":").ok_or(Error::with_message_and_status(
+        format!("Invalid URI: {uri}"),
+        Status::InvalidArguments,
+    ))?;
+
+    let driver = &uri[..idx];
+    if uri.len() <= idx + 2 {
+        return Ok((driver, uri));
+    }
+
+    #[cfg(target_os = "windows")]
+    if let Ok(true) = std::fs::exists(uri) {
+        return Ok((uri, ""));
+    }
+
+    if &uri[idx..idx + 2] == ":/" {
+        // scheme is also driver
+        return Ok((driver, uri));
+    }
+
+    Ok((driver, &uri[idx + 1..]))
+}
+
 const fn arch_triplet() -> (&'static str, &'static str, &'static str) {
     #[cfg(target_arch = "x86_64")]
     const ARCH: &str = "amd64";
@@ -2399,4 +2464,61 @@ mod tests {
             assert_eq!(search_paths, Vec::<PathBuf>::new());
         }
     }
+
+    #[test]
+    fn test_parse_driver_uri() {
+        let cases = vec![
+            ("sqlite", Err(Status::InvalidArguments)),
+            ("sqlite:", Ok(("sqlite", "sqlite:"))),
+            ("sqlite:file::memory:", Ok(("sqlite", "file::memory:"))),
+            (
+                "sqlite:file::memory:?cache=shared",
+                Ok(("sqlite", "file::memory:?cache=shared")),
+            ),
+            (
+                "postgresql://a:b@localhost:9999/nonexistent",
+                Ok(("postgresql", 
"postgresql://a:b@localhost:9999/nonexistent")),
+            ),
+        ];
+
+        for (input, expected) in cases {
+            let result = parse_driver_uri(input);
+            match expected {
+                Ok((exp_driver, exp_conn)) => {
+                    let (driver, conn) = result.expect("Expected Ok result");
+                    assert_eq!(driver, exp_driver);
+                    assert_eq!(conn, exp_conn);
+                }
+                Err(exp_status) => {
+                    let err = result.expect_err("Expected Err result");
+                    assert_eq!(err.status, exp_status);
+                }
+            }
+        }
+    }
+
+    #[cfg(target_os = "windows")]
+    #[test]
+    fn test_parse_driver_uri_windows_file() {
+        let tmp_dir = Builder::new()
+            .prefix("adbc_tests")
+            .tempdir()
+            .expect("Failed to create temporary directory for driver manager 
manifest test");
+
+        let temp_db_path = tmp_dir.path().join("test.dll");
+        if let Some(parent) = temp_db_path.parent() {
+            std::fs::create_dir_all(parent)
+                .expect("Failed to create parent directory for manifest");
+        }
+        std::fs::write(&temp_db_path, b"").expect("Failed to create temporary 
database file");
+
+        let (driver, conn) = 
parse_driver_uri(temp_db_path.to_str().unwrap()).unwrap();
+
+        assert_eq!(driver, temp_db_path.to_str().unwrap());
+        assert_eq!(conn, "");
+
+        tmp_dir
+            .close()
+            .expect("Failed to close/remove temporary directory");
+    }
 }
diff --git a/rust/driver_manager/tests/driver_manager_sqlite.rs 
b/rust/driver_manager/tests/driver_manager_sqlite.rs
index 8f9a654ab..b00f9a555 100644
--- a/rust/driver_manager/tests/driver_manager_sqlite.rs
+++ b/rust/driver_manager/tests/driver_manager_sqlite.rs
@@ -19,7 +19,7 @@ use arrow_schema::{Field, Schema};
 
 use adbc_core::options::{AdbcVersion, OptionConnection, OptionDatabase};
 use adbc_core::{error::Status, Driver, Optionable};
-use adbc_core::{Connection, Database, Statement};
+use adbc_core::{Connection, Database, Statement, LOAD_FLAG_DEFAULT};
 use adbc_driver_manager::{ManagedDatabase, ManagedDriver};
 
 mod common;
@@ -50,6 +50,19 @@ fn test_database() {
     common::test_database(&database);
 }
 
+#[test]
+fn test_database_implicit_uri() {
+    let database = ManagedDatabase::from_uri(
+        "adbc_driver_sqlite:file::memory:",
+        None,
+        AdbcVersion::V100,
+        LOAD_FLAG_DEFAULT,
+        None,
+    )
+    .unwrap();
+    common::test_database(&database);
+}
+
 #[test]
 fn test_database_get_option() {
     let mut driver = get_driver();

Reply via email to