This is an automated email from the ASF dual-hosted git repository.
felipecrv 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 a6757b769 feat(rust/driver_manager): Decouple driver search logic from
the driver objects themselves (#3930)
a6757b769 is described below
commit a6757b7694c1bef899b0e95ec13d0253dc03b9d4
Author: Felipe Oliveira Carvalho <[email protected]>
AuthorDate: Thu Feb 12 19:28:46 2026 -0300
feat(rust/driver_manager): Decouple driver search logic from the driver
objects themselves (#3930)
This is achieved through a new private sub-module called `search.rs`. In
later PRs we might consider publishing some of these APIs.
---
rust/driver_manager/src/lib.rs | 1248 +------------------------------
rust/driver_manager/src/search.rs | 1459 +++++++++++++++++++++++++++++++++++++
2 files changed, 1486 insertions(+), 1221 deletions(-)
diff --git a/rust/driver_manager/src/lib.rs b/rust/driver_manager/src/lib.rs
index b2cc03f3a..6464ab399 100644
--- a/rust/driver_manager/src/lib.rs
+++ b/rust/driver_manager/src/lib.rs
@@ -101,14 +101,13 @@
// would prevent any parallelism between driver calls, which is not desirable.
pub mod error;
+pub(crate) mod search;
use std::collections::HashSet;
-use std::env;
use std::ffi::{CString, OsStr};
-use std::fs;
use std::ops::DerefMut;
-use std::os::raw::{c_char, c_void};
-use std::path::{Path, PathBuf};
+use std::os::raw::c_char;
+use std::path::PathBuf;
use std::pin::Pin;
use std::ptr::{null, null_mut};
use std::sync::{Arc, Mutex};
@@ -120,18 +119,15 @@ use adbc_ffi::options::{
use arrow_array::ffi::{to_ffi, FFI_ArrowSchema};
use arrow_array::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream};
use arrow_array::{Array, RecordBatch, RecordBatchReader, StructArray};
-use toml::de::DeTable;
use adbc_core::{
error::{Error, Result, Status},
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,
};
use adbc_ffi::driver_method;
-use crate::error::libloading_error_to_adbc_error;
+use self::search::{parse_driver_uri, DriverLibrary};
const ERR_CANCEL_UNSUPPORTED: &str =
"Canceling connection or statement is not supported with ADBC 1.0.0";
@@ -151,124 +147,6 @@ struct ManagedDriverInner {
_library: Option<libloading::Library>,
}
-#[derive(Debug, Default)]
-struct DriverInfo {
- lib_path: std::path::PathBuf,
- entrypoint: Option<Vec<u8>>,
- // TODO: until we add more logging these are unused so we'll leave
- // them out until such time that we do so.
- // driver_name: String,
- // version: String,
- // source: String,
-}
-
-impl DriverInfo {
- fn load_driver_manifest(manifest_file: &Path, entrypoint: Option<&[u8]>)
-> Result<Self> {
- let contents = fs::read_to_string(manifest_file).map_err(|e| {
- Error::with_message_and_status(
- format!("Could not read manifest '{}': {e}",
manifest_file.display()),
- Status::InvalidArguments,
- )
- })?;
-
- let manifest = DeTable::parse(&contents)
- .map_err(|e| Error::with_message_and_status(e.to_string(),
Status::InvalidArguments))?;
-
- let manifest_version = manifest
- .get_ref()
- .get("manifest_version")
- .and_then(|v| v.get_ref().as_integer())
- .map(|v| v.as_str())
- .unwrap_or("1");
-
- if manifest_version != "1" {
- return Err(Error::with_message_and_status(
- format!("Unsupported manifest version: {manifest_version}"),
- Status::InvalidArguments,
- ));
- }
-
- // leave these out until we add logging that would actually utilize
them
- // let driver_name = get_optional_key(&manifest, "name");
- // let version = get_optional_key(&manifest, "version");
- // let source = get_optional_key(&manifest, "source");
- let (os, arch, extra) = arch_triplet();
-
- let lib_path = match manifest
- .get_ref()
- .get("Driver")
- .and_then(|v| v.get_ref().get("shared"))
- .map(|v| v.get_ref())
- {
- Some(driver) => {
- if driver.is_str() {
- // If the value is a string, we assume it is the path to
the shared library.
- Ok(PathBuf::from(driver.as_str().unwrap_or_default()))
- } else if driver.is_table() {
- // If the value is a table, we look for the specific key
for this OS and architecture.
- Ok(PathBuf::from(
- driver
- .get(format!("{os}_{arch}{extra}"))
- .and_then(|v| v.get_ref().as_str())
- .unwrap_or_default(),
- ))
- } else {
- Err(Error::with_message_and_status(
- format!(
- "Driver.shared must be a string or a table, found
'{}'",
- driver.type_str()
- ),
- Status::InvalidArguments,
- ))
- }
- }
- None => Err(Error::with_message_and_status(
- format!(
- "Manifest '{}' missing Driver.shared key",
- manifest_file.display()
- ),
- Status::InvalidArguments,
- )),
- }?;
-
- if lib_path.as_os_str().is_empty() {
- return Err(Error::with_message_and_status(
- format!(
- "Manifest '{}' found empty string for library path of
Driver.shared.{os}_{arch}{extra}",
- lib_path.display()
- ),
- Status::InvalidArguments,
- ));
- }
-
- let entrypoint_val = manifest
- .get_ref()
- .get("Driver")
- .and_then(|v| v.get_ref().as_table())
- .and_then(|t| t.get("entrypoint"))
- .map(|v| v.get_ref());
-
- if let Some(entry) = entrypoint_val {
- if !entry.is_str() {
- return Err(Error::with_message_and_status(
- "Driver entrypoint must be a string".to_string(),
- Status::InvalidArguments,
- ));
- }
- }
-
- let entrypoint = match entrypoint_val.and_then(|v| v.as_str()) {
- Some(s) => Some(s.as_bytes().to_vec()),
- None => entrypoint.map(|s| s.to_vec()),
- };
-
- Ok(DriverInfo {
- lib_path,
- entrypoint,
- })
- }
-}
-
/// Implementation of [Driver].
#[derive(Clone, Debug)]
pub struct ManagedDriver {
@@ -286,7 +164,7 @@ impl ManagedDriver {
init: &adbc_ffi::FFI_AdbcDriverInitFunc,
version: AdbcVersion,
) -> Result<Self> {
- let driver = Self::load_impl(init, version)?;
+ let driver =
DriverLibrary::from_static_init(init).init_driver(version)?;
let inner = Arc::pin(ManagedDriverInner {
driver,
version,
@@ -306,14 +184,14 @@ impl ManagedDriver {
/// - if `name` has an extension: it is treated as a filename. If the
load_flags does not
/// contain `LOAD_FLAG_ALLOW_RELATIVE_PATHS`, then relative paths will
be rejected.
/// - if the extension is `toml` then we attempt to load the Driver
Manifest, otherwise
- /// we defer to the previous logic in
[`Self::load_dynamic_from_filename`] which will attempt to
- /// load the library
+ /// we defer to the previous logic in
[`Self::load_dynamic_from_filename`] which will
+ /// attempt to load the library
/// - if `name` does not have an extension but is an absolute path: we
first check to see
/// if there is an existing file with the same name that *does* have a
"toml" extension,
/// attempting to load that if it exists. Otherwise we just pass it to
load_dynamic_from_filename.
/// - Finally, if there's no extension and it is not an absolute path, we
will search through
- /// the relevant directories (based on the set load flags) for a
manifest file with this name, and
- /// if one is not found we see if the name refers to a library on the
LD_LIBRARY_PATH etc.
+ /// the relevant directories (based on the set load flags) for a
manifest file with this name,
+ /// and if one is not found we see if the name refers to a library on
the LD_LIBRARY_PATH etc.
pub fn load_from_name(
name: impl AsRef<OsStr>,
entrypoint: Option<&[u8]>,
@@ -321,37 +199,10 @@ impl ManagedDriver {
load_flags: LoadFlags,
additional_search_paths: Option<Vec<PathBuf>>,
) -> Result<Self> {
- let driver_path = Path::new(name.as_ref());
- let allow_relative = load_flags & LOAD_FLAG_ALLOW_RELATIVE_PATHS != 0;
- if let Some(ext) = driver_path.extension() {
- if !allow_relative && driver_path.is_relative() {
- return Err(Error::with_message_and_status(
- "Relative paths are not allowed",
- Status::InvalidArguments,
- ));
- }
-
- if ext == "toml" {
- Self::load_from_manifest(driver_path, entrypoint, version)
- } else {
- Self::load_dynamic_from_filename(driver_path, entrypoint,
version)
- }
- } else if driver_path.is_absolute() {
- let toml_path = driver_path.with_extension("toml");
- if toml_path.is_file() {
- return Self::load_from_manifest(&toml_path, entrypoint,
version);
- }
+ let search_hit = DriverLibrary::search(name, load_flags,
additional_search_paths)?;
- Self::load_dynamic_from_filename(driver_path, entrypoint, version)
- } else {
- Self::find_driver(
- driver_path,
- entrypoint,
- version,
- load_flags,
- additional_search_paths,
- )
- }
+ let entrypoint = search_hit.resolve_entrypoint(entrypoint).to_vec();
+ Self::load_from_library(search_hit.library, entrypoint.as_ref(),
version)
}
/// Load a driver from a dynamic library filename.
@@ -369,70 +220,18 @@ impl ManagedDriver {
entrypoint: Option<&[u8]>,
version: AdbcVersion,
) -> Result<Self> {
- let default_entrypoint = get_default_entrypoint(filename.as_ref());
-
- let entrypoint = entrypoint.unwrap_or(default_entrypoint.as_bytes());
- // By default, go builds the libraries with '-Wl -z nodelete' which
does not
- // unload the go runtime. This isn't respected on mac (
https://github.com/golang/go/issues/11100#issuecomment-932638093 )
- // so we need to explicitly load the library with RTLD_NODELETE( which
prevents unloading )
- #[cfg(unix)]
- let library: libloading::Library = unsafe {
- use std::os::raw::c_int;
-
- const RTLD_NODELETE: c_int = if cfg!(any(
- target_os = "macos",
- target_os = "ios",
- target_os = "tvos",
- target_os = "visionos",
- target_os = "watchos",
- )) {
- 0x80
- } else if cfg!(any(
- target_os = "linux",
- target_os = "android",
- target_os = "emscripten",
- target_os = "freebsd",
- target_os = "dragonfly",
- target_os = "openbsd",
- target_os = "haiku",
- target_os = "solaris",
- target_os = "illumos",
- target_env = "uclibc",
- target_env = "newlib",
- target_os = "fuchsia",
- target_os = "redox",
- target_os = "hurd",
- target_os = "cygwin",
- )) {
- 0x1000
- } else {
- 0x0
- };
-
- libloading::os::unix::Library::open(
- Some(filename.as_ref()),
- libloading::os::unix::RTLD_LAZY |
libloading::os::unix::RTLD_LOCAL | RTLD_NODELETE,
- )
- .map(Into::into)
- .map_err(libloading_error_to_adbc_error)?
- };
- // on windows, we emulate the same behaviour by using the
GET_MODULE_HANDLE_EX_FLAG_PIN. The `.pin()`
- // function implements this.
- #[cfg(windows)]
- let library: libloading::Library = unsafe {
- let library: libloading::os::windows::Library =
- libloading::os::windows::Library::new(filename.as_ref())
- .map_err(libloading_error_to_adbc_error)?;
- library.pin().map_err(libloading_error_to_adbc_error)?;
- library.into()
- };
- let init: libloading::Symbol<adbc_ffi::FFI_AdbcDriverInitFunc> =
unsafe {
- library
- .get(entrypoint)
- .or_else(|_| library.get(b"AdbcDriverInit"))
- .map_err(libloading_error_to_adbc_error)?
- };
- let driver = Self::load_impl(&init, version)?;
+ let entrypoint = DriverLibrary::derive_entrypoint(entrypoint,
filename.as_ref());
+ let library = DriverLibrary::load_library(filename)?;
+ Self::load_from_library(library, entrypoint.as_ref(), version)
+ }
+
+ fn load_from_library(
+ library: libloading::Library,
+ entrypoint: &[u8],
+ version: AdbcVersion,
+ ) -> Result<ManagedDriver> {
+ let driver =
+ DriverLibrary::try_from_dynamic_library(&library,
entrypoint)?.init_driver(version)?;
let inner = Arc::pin(ManagedDriverInner {
driver,
version,
@@ -454,25 +253,9 @@ impl ManagedDriver {
entrypoint: Option<&[u8]>,
version: AdbcVersion,
) -> Result<Self> {
- let filename = libloading::library_filename(name.as_ref());
- Self::load_dynamic_from_filename(filename, entrypoint, version)
- }
-
- fn load_impl(
- init: &adbc_ffi::FFI_AdbcDriverInitFunc,
- version: AdbcVersion,
- ) -> Result<adbc_ffi::FFI_AdbcDriver> {
- let mut error = adbc_ffi::FFI_AdbcError::default();
- let mut driver = adbc_ffi::FFI_AdbcDriver::default();
- let status = unsafe {
- init(
- version.into(),
- &mut driver as *mut adbc_ffi::FFI_AdbcDriver as *mut c_void,
- &mut error,
- )
- };
- check_status(status, error)?;
- Ok(driver)
+ let entrypoint =
DriverLibrary::derive_entrypoint_from_name(entrypoint, name.as_ref());
+ let library = DriverLibrary::load_library_from_name(name.as_ref())?;
+ Self::load_from_library(library, entrypoint.as_ref(), version)
}
fn inner_ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
@@ -508,172 +291,6 @@ impl ManagedDriver {
Ok(database)
}
-
- fn load_from_manifest(
- driver_path: &Path,
- entrypoint: Option<&[u8]>,
- version: AdbcVersion,
- ) -> Result<Self> {
- let info = DriverInfo::load_driver_manifest(driver_path, entrypoint)?;
- Self::load_dynamic_from_filename(info.lib_path,
info.entrypoint.as_deref(), version)
- }
-
- fn search_path_list(
- driver_path: &Path,
- path_list: Vec<PathBuf>,
- entrypoint: Option<&[u8]>,
- version: AdbcVersion,
- ) -> Result<Self> {
- for path in path_list {
- let mut full_path = path.join(driver_path);
- full_path.set_extension("toml");
- if full_path.is_file() {
- if let Ok(result) = Self::load_from_manifest(&full_path,
entrypoint, version) {
- return Ok(result);
- }
- }
-
- full_path.set_extension(""); // Remove the extension to try
loading as a dynamic library.
- if let Ok(result) = Self::load_dynamic_from_filename(full_path,
entrypoint, version) {
- return Ok(result);
- }
- }
-
- Err(Error::with_message_and_status(
- format!("Driver not found: {}", driver_path.display()),
- Status::NotFound,
- ))
- }
-
- #[cfg(target_os = "windows")]
- fn find_driver(
- driver_path: &Path,
- entrypoint: Option<&[u8]>,
- version: AdbcVersion,
- load_flags: LoadFlags,
- additional_search_paths: Option<Vec<PathBuf>>,
- ) -> Result<Self> {
- if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
- if let Ok(result) = Self::search_path_list(
- driver_path,
- get_search_paths(LOAD_FLAG_SEARCH_ENV),
- entrypoint,
- version,
- ) {
- return Ok(result);
- }
- }
-
- // the logic we want is that we first search ADBC_DRIVER_PATH if set,
- // then we search the additional search paths if they exist. Finally,
- // we will search CONDA_PREFIX if built with conda_build before moving
on.
- if let Some(additional_search_paths) = additional_search_paths {
- if let Ok(result) =
- Self::search_path_list(driver_path, additional_search_paths,
entrypoint, version)
- {
- return Ok(result);
- }
- }
-
- #[cfg(conda_build)]
- if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
- if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") {
- let conda_path = PathBuf::from(conda_prefix)
- .join("etc")
- .join("adbc")
- .join("drivers");
- if let Ok(result) =
- Self::search_path_list(driver_path, vec![conda_path],
entrypoint, version)
- {
- return Ok(result);
- }
- }
- }
-
- if load_flags & LOAD_FLAG_SEARCH_USER != 0 {
- // first check registry for the driver, then check the user config
path
- if let Ok(result) = load_driver_from_registry(
- windows_registry::CURRENT_USER,
- driver_path.as_os_str(),
- entrypoint,
- ) {
- return Self::load_dynamic_from_filename(result.lib_path,
entrypoint, version);
- }
-
- if let Ok(result) = Self::search_path_list(
- driver_path,
- get_search_paths(LOAD_FLAG_SEARCH_USER),
- entrypoint,
- version,
- ) {
- return Ok(result);
- }
- }
-
- if load_flags & LOAD_FLAG_SEARCH_SYSTEM != 0 {
- if let Ok(result) = load_driver_from_registry(
- windows_registry::LOCAL_MACHINE,
- driver_path.as_os_str(),
- entrypoint,
- ) {
- return Self::load_dynamic_from_filename(result.lib_path,
entrypoint, version);
- }
-
- if let Ok(result) = Self::search_path_list(
- driver_path,
- get_search_paths(LOAD_FLAG_SEARCH_SYSTEM),
- entrypoint,
- version,
- ) {
- return Ok(result);
- }
- }
-
- let driver_name =
driver_path.as_os_str().to_string_lossy().into_owned();
- Self::load_dynamic_from_name(driver_name, entrypoint, version)
- }
-
- #[cfg(not(windows))]
- fn find_driver(
- driver_path: &Path,
- entrypoint: Option<&[u8]>,
- version: AdbcVersion,
- load_flags: LoadFlags,
- additional_search_paths: Option<Vec<PathBuf>>,
- ) -> Result<Self> {
- let mut path_list = get_search_paths(load_flags &
LOAD_FLAG_SEARCH_ENV);
-
- if let Some(additional_search_paths) = additional_search_paths {
- path_list.extend(additional_search_paths);
- }
-
- #[cfg(conda_build)]
- if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
- if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") {
- let conda_path = PathBuf::from(conda_prefix)
- .join("etc")
- .join("adbc")
- .join("drivers");
- path_list.push(conda_path);
- }
- }
-
- path_list.extend(get_search_paths(load_flags & !LOAD_FLAG_SEARCH_ENV));
- if let Ok(result) = Self::search_path_list(driver_path, path_list,
entrypoint, version) {
- return Ok(result);
- }
-
- // Convert OsStr to String before passing to load_dynamic_from_name
- let driver_name =
driver_path.as_os_str().to_string_lossy().into_owned();
- if let Ok(driver) = Self::load_dynamic_from_name(driver_name,
entrypoint, version) {
- return Ok(driver);
- }
-
- Err(Error::with_message_and_status(
- format!("Driver not found: {}", driver_path.display()),
- Status::NotFound,
- ))
- }
}
impl Driver for ManagedDriver {
@@ -714,85 +331,6 @@ impl Driver for ManagedDriver {
}
}
-#[cfg(target_os = "windows")]
-fn load_driver_from_registry(
- root: &windows_registry::Key,
- driver_name: &OsStr,
- entrypoint: Option<&[u8]>,
-) -> Result<DriverInfo> {
- const ADBC_DRIVER_REGISTRY: &str = "SOFTWARE\\ADBC\\Drivers";
- let drivers_key = root
- .open(ADBC_DRIVER_REGISTRY)
- .and_then(|k| k.open(driver_name.to_str().unwrap_or_default()))
- .map_err(|e| {
- Error::with_message_and_status(
- format!("Failed to open registry key: {e}"),
- Status::NotFound,
- )
- })?;
-
- let manifest_version =
drivers_key.get_u32("manifest_version").unwrap_or(1);
-
- if manifest_version != 1 {
- return Err(Error::with_message_and_status(
- format!("Unsupported manifest version: {manifest_version}"),
- Status::InvalidArguments,
- ));
- }
-
- let entrypoint_val = drivers_key
- .get_string("entrypoint")
- .ok()
- .map(|s| s.into_bytes());
-
- Ok(DriverInfo {
- lib_path:
PathBuf::from(drivers_key.get_string("driver").unwrap_or_default()),
- entrypoint: entrypoint_val.or_else(|| entrypoint.map(|s| s.to_vec())),
- })
-}
-
-// construct default entrypoint from the library name
-fn get_default_entrypoint(driver_path: impl AsRef<OsStr>) -> String {
- // - libadbc_driver_sqlite.so.2.0.0 -> AdbcDriverSqliteInit
- // - adbc_driver_sqlite.dll -> AdbcDriverSqliteInit
- // - proprietary_driver.dll -> AdbcProprietaryDriverInit
-
- // potential path -> filename
- let mut filename = driver_path.as_ref().to_str().unwrap_or_default();
- if let Some(pos) = filename.rfind(['/', '\\']) {
- filename = &filename[pos + 1..];
- }
-
- // remove all extensions
- filename = filename
- .find('.')
- .map_or_else(|| filename, |pos| &filename[..pos]);
-
- let mut entrypoint = filename
- .to_string()
- .strip_prefix(env::consts::DLL_PREFIX)
- .unwrap_or(filename)
- .split(&['-', '_'][..])
- .map(|s| {
- // uppercase first character of a string
- //
https://stackoverflow.com/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust
- let mut c = s.chars();
- match c.next() {
- None => String::new(),
- Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
- }
- })
- .collect::<Vec<_>>()
- .join("");
-
- if !entrypoint.starts_with("Adbc") {
- entrypoint = format!("Adbc{entrypoint}");
- }
-
- entrypoint.push_str("Init");
- entrypoint
-}
-
struct ManagedDatabaseInner {
database: Mutex<adbc_ffi::FFI_AdbcDatabase>,
driver: Pin<Arc<ManagedDriverInner>>,
@@ -1630,735 +1168,3 @@ impl Drop for ManagedStatement {
unsafe { method(statement.deref_mut(), null_mut()) };
}
}
-
-#[cfg(target_os = "windows")]
-mod target_windows {
- use windows_sys as windows;
-
- use std::ffi::c_void;
- use std::ffi::OsString;
- use std::os::windows::ffi::OsStringExt;
- use std::path::PathBuf;
- use std::slice;
-
- use windows::Win32::UI::Shell;
-
- // adapted from
https://github.com/dirs-dev/dirs-sys-rs/blob/main/src/lib.rs#L150
- pub fn user_config_dir() -> Option<PathBuf> {
- unsafe {
- let mut path_ptr: windows::core::PWSTR = std::ptr::null_mut();
- let result = Shell::SHGetKnownFolderPath(
- &Shell::FOLDERID_LocalAppData,
- 0,
- std::ptr::null_mut(),
- &mut path_ptr,
- );
-
- if result == 0 {
- let len = windows::Win32::Globalization::lstrlenW(path_ptr) as
usize;
- let path = slice::from_raw_parts(path_ptr, len);
- let ostr: OsString = OsStringExt::from_wide(path);
- windows::Win32::System::Com::CoTaskMemFree(path_ptr as *const
c_void);
- Some(PathBuf::from(ostr))
- } else {
- windows::Win32::System::Com::CoTaskMemFree(path_ptr as *const
c_void);
- None
- }
- }
- }
-}
-
-fn user_config_dir() -> Option<PathBuf> {
- #[cfg(target_os = "windows")]
- {
- use target_windows::user_config_dir;
- user_config_dir().map(|mut path| {
- path.push("ADBC");
- path.push("Drivers");
- path
- })
- }
-
- #[cfg(target_os = "macos")]
- {
- env::var_os("HOME").map(PathBuf::from).map(|mut path| {
- path.push("Library");
- path.push("Application Support");
- path.push("ADBC");
- path.push("Drivers");
- path
- })
- }
-
- #[cfg(all(unix, not(target_os = "macos")))]
- {
- env::var_os("XDG_CONFIG_HOME")
- .map(PathBuf::from)
- .or_else(|| {
- env::var_os("HOME").map(|home| {
- let mut path = PathBuf::from(home);
- path.push(".config");
- path
- })
- })
- .map(|mut path| {
- path.push("adbc");
- path.push("drivers");
- path
- })
- }
-}
-
-fn system_config_dir() -> Option<PathBuf> {
- #[cfg(target_os = "macos")]
- {
- Some(PathBuf::from("/Library/Application Support/ADBC/Drivers"))
- }
-
- #[cfg(all(unix, not(target_os = "macos")))]
- {
- Some(PathBuf::from("/etc/adbc/drivers"))
- }
-
- #[cfg(not(unix))]
- {
- None
- }
-}
-
-fn get_search_paths(lvls: LoadFlags) -> Vec<PathBuf> {
- let mut result = Vec::new();
- if lvls & LOAD_FLAG_SEARCH_ENV != 0 {
- if let Some(paths) = env::var_os("ADBC_DRIVER_PATH") {
- for p in env::split_paths(&paths) {
- result.push(p);
- }
- }
- }
-
- if lvls & LOAD_FLAG_SEARCH_USER != 0 {
- if let Some(path) = user_config_dir() {
- if path.exists() {
- result.push(path);
- }
- }
- }
-
- // system level for windows is to search the registry keys
- #[cfg(not(windows))]
- if lvls & LOAD_FLAG_SEARCH_SYSTEM != 0 {
- if let Some(path) = system_config_dir() {
- if path.exists() {
- result.push(path);
- }
- }
- }
-
- 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";
- #[cfg(all(target_arch = "aarch64", target_endian = "big"))]
- const ARCH: &str = "arm64be";
- #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
- const ARCH: &str = "arm64";
- #[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
- const ARCH: &str = "powerpc64le";
- #[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
- const ARCH: &str = "powerpc64";
- #[cfg(target_arch = "riscv32")]
- const ARCH: &str = "riscv";
- #[cfg(not(any(
- target_arch = "x86_64",
- target_arch = "aarch64",
- target_arch = "powerpc64",
- target_arch = "riscv32",
- )))]
- const ARCH: &str = std::env::consts::ARCH;
-
- const OS: &str = std::env::consts::OS;
-
- #[cfg(target_env = "musl")]
- const EXTRA: &str = "_musl";
- #[cfg(all(target_os = "windows", target_env = "gnu"))]
- const EXTRA: &str = "_mingw";
- #[cfg(not(any(target_env = "musl", all(target_os = "windows", target_env =
"gnu"))))]
- const EXTRA: &str = "";
-
- (OS, ARCH, EXTRA)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use adbc_core::LOAD_FLAG_DEFAULT;
- use temp_env::{with_var, with_var_unset};
- use tempfile::Builder;
-
- fn manifest_without_driver() -> &'static str {
- r#"
- manifest_version = 1
-
- name = 'SQLite3'
- publisher = 'arrow-adbc'
- version = '1.0.0'
-
- [ADBC]
- version = '1.1.0'
- "#
- }
-
- fn simple_manifest() -> String {
- // if this test is enabled, we expect the env var
ADBC_DRIVER_MANAGER_TEST_LIB
- // to be defined.
- let driver_path =
- PathBuf::from(env::var_os("ADBC_DRIVER_MANAGER_TEST_LIB").expect(
- "ADBC_DRIVER_MANAGER_TEST_LIB must be set for driver manager
manifest tests",
- ));
-
- assert!(
- driver_path.exists(),
- "ADBC_DRIVER_MANAGER_TEST_LIB path does not exist: {}",
- driver_path.display()
- );
-
- let (os, arch, extra) = arch_triplet();
- format!(
- r#"
- {}
-
- [Driver]
- [Driver.shared]
- {os}_{arch}{extra} = {driver_path:?}
- "#,
- manifest_without_driver()
- )
- }
-
- fn write_manifest_to_tempfile(p: PathBuf, tbl: String) ->
(tempfile::TempDir, PathBuf) {
- let tmp_dir = Builder::new()
- .prefix("adbc_tests")
- .tempdir()
- .expect("Failed to create temporary directory for driver manager
manifest test");
-
- let manifest_path = tmp_dir.path().join(p);
- if let Some(parent) = manifest_path.parent() {
- std::fs::create_dir_all(parent)
- .expect("Failed to create parent directory for manifest");
- }
-
- std::fs::write(&manifest_path, tbl.as_str())
- .expect("Failed to write driver manager manifest to temporary
file");
-
- (tmp_dir, manifest_path)
- }
-
- #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
- #[test]
- fn test_default_entrypoint() {
- for driver in [
- "adbc_driver_sqlite",
- "adbc_driver_sqlite.dll",
- "driver_sqlite",
- "libadbc_driver_sqlite",
- "libadbc_driver_sqlite.so",
- "libadbc_driver_sqlite.so.6.0.0",
- "/usr/lib/libadbc_driver_sqlite.so",
- "/usr/lib/libadbc_driver_sqlite.so.6.0.0",
- "C:\\System32\\adbc_driver_sqlite.dll",
- ] {
- assert_eq!(get_default_entrypoint(driver), "AdbcDriverSqliteInit");
- }
-
- for driver in [
- "adbc_sqlite",
- "sqlite",
- "/usr/lib/sqlite.so",
- "C:\\System32\\sqlite.dll",
- ] {
- assert_eq!(get_default_entrypoint(driver), "AdbcSqliteInit");
- }
-
- for driver in [
- "proprietary_engine",
- "libproprietary_engine.so.6.0.0",
- "/usr/lib/proprietary_engine.so",
- "C:\\System32\\proprietary_engine.dll",
- ] {
- assert_eq!(get_default_entrypoint(driver),
"AdbcProprietaryEngineInit");
- }
-
- for driver in ["driver_example", "libdriver_example.so"] {
- assert_eq!(get_default_entrypoint(driver),
"AdbcDriverExampleInit");
- }
- }
-
- /// Regression test for https://github.com/apache/arrow-adbc/pull/3693
- /// Ensures driver manager tests for Windows pull in Windows crates. This
- /// can be removed/replace when more complete tests are added.
- #[test]
- fn test_user_config_dir() {
- let _ = user_config_dir().unwrap();
- }
-
- /// Regression test for https://github.com/apache/arrow-adbc/pull/3693
- /// Ensures driver manager tests for Windows pull in Windows crates. This
- /// can be removed/replace when more complete tests are added.
- #[test]
- #[cfg(target_os = "windows")]
- fn test_load_driver_from_registry() {
- use std::ffi::OsStr;
- let result = load_driver_from_registry(
- windows_registry::CURRENT_USER,
- OsStr::new("nonexistent_test_driver"),
- None,
- );
- assert!(result.is_err());
- assert_eq!(result.unwrap_err().status, Status::NotFound);
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
- fn test_load_driver_env() {
- // ensure that we fail without the env var set
- with_var_unset("ADBC_DRIVER_PATH", || {
- let err = ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_SEARCH_ENV,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::NotFound);
- });
-
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
-
- with_var(
- "ADBC_DRIVER_PATH",
- Some(manifest_path.parent().unwrap().as_os_str()),
- || {
- ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_SEARCH_ENV,
- None,
- )
- .unwrap();
- },
- );
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_driver_env_multiple_paths() {
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
-
- let path_os_string = env::join_paths([
- Path::new("/home"),
- Path::new(""),
- manifest_path.parent().unwrap(),
- ])
- .unwrap();
-
- with_var("ADBC_DRIVER_PATH", Some(&path_os_string), || {
- ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_SEARCH_ENV,
- None,
- )
- .unwrap();
- });
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_non_ascii_path() {
- let p = PathBuf::from("majestik møøse/sqlite.toml");
- let (tmp_dir, manifest_path) = write_manifest_to_tempfile(p,
simple_manifest());
-
- with_var(
- "ADBC_DRIVER_PATH",
- Some(manifest_path.parent().unwrap().as_os_str()),
- || {
- ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_SEARCH_ENV,
- None,
- )
- .unwrap();
- },
- );
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
- fn test_disallow_env_config() {
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
-
- with_var(
- "ADBC_DRIVER_PATH",
- Some(manifest_path.parent().unwrap().as_os_str()),
- || {
- let load_flags = LOAD_FLAG_DEFAULT & !LOAD_FLAG_SEARCH_ENV;
- let err = ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- load_flags,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::NotFound);
- },
- );
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_additional_path() {
- let p = PathBuf::from("majestik møøse/sqlite.toml");
- let (tmp_dir, manifest_path) = write_manifest_to_tempfile(p,
simple_manifest());
-
- ManagedDriver::load_from_name(
- "sqlite",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_SEARCH_ENV,
- Some(vec![manifest_path.parent().unwrap().to_path_buf()]),
- )
- .unwrap();
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_absolute_path() {
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
-
- ManagedDriver::load_from_name(
- manifest_path,
- None,
- AdbcVersion::V100,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap();
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_absolute_path_no_ext() {
- let (tmp_dir, mut manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
-
- manifest_path.set_extension("");
- ManagedDriver::load_from_name(
- manifest_path,
- None,
- AdbcVersion::V100,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap();
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_load_relative_path() {
- std::fs::write(PathBuf::from("sqlite.toml"), simple_manifest())
- .expect("Failed to write driver manager manifest to file");
-
- let err = ManagedDriver::load_from_name("sqlite.toml", None,
AdbcVersion::V100, 0, None)
- .unwrap_err();
- assert_eq!(err.status, Status::InvalidArguments);
-
- ManagedDriver::load_from_name(
- "sqlite.toml",
- None,
- AdbcVersion::V100,
- LOAD_FLAG_ALLOW_RELATIVE_PATHS,
- None,
- )
- .unwrap();
-
- std::fs::remove_file("sqlite.toml")
- .expect("Failed to remove temporary driver manifest file");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_manifest_missing_driver() {
- let (tmp_dir, manifest_path) = write_manifest_to_tempfile(
- PathBuf::from("sqlite.toml"),
- manifest_without_driver().to_string(),
- );
-
- let err = ManagedDriver::load_from_name(
- manifest_path,
- None,
- AdbcVersion::V100,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::InvalidArguments);
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_manifest_bad_version() {
- let manifest = "manifest_version = 2\n".to_owned() +
&simple_manifest().to_owned();
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"), manifest);
-
- let err = ManagedDriver::load_from_name(
- manifest_path,
- None,
- AdbcVersion::V100,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::InvalidArguments);
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
- fn test_manifest_wrong_arch() {
- let manifest_wrong_arch = format!(
- r#"
- {}
-
- [Driver]
- [Driver.shared]
- non-existing = 'path/to/bad/driver.so'
- "#,
- manifest_without_driver()
- );
-
- let (tmp_dir, manifest_path) =
- write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
manifest_wrong_arch);
-
- let err = ManagedDriver::load_from_name(
- manifest_path,
- None,
- AdbcVersion::V100,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::InvalidArguments);
-
- tmp_dir
- .close()
- .expect("Failed to close/remove temporary directory");
- }
-
- #[test]
- #[cfg_attr(
- not(all(
- feature = "driver_manager_test_lib",
- feature = "driver_manager_test_manifest_user"
- )),
- ignore
- )]
- #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
- fn test_manifest_user_config() {
- let err = ManagedDriver::load_from_name(
- "adbc-test-sqlite",
- None,
- AdbcVersion::V110,
- LOAD_FLAG_DEFAULT,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::NotFound);
-
- let usercfg_dir = user_config_dir().unwrap();
- let mut created = false;
- if !usercfg_dir.exists() {
- std::fs::create_dir_all(&usercfg_dir)
- .expect("Failed to create user config directory for driver
manager test");
- created = true;
- }
-
- let manifest_path = usercfg_dir.join("adbc-test-sqlite.toml");
- std::fs::write(&manifest_path, simple_manifest())
- .expect("Failed to write driver manager manifest to user config
directory");
-
- // fail to load if the load flag doesn't have LOAD_FLAG_SEARCH_USER
- let err = ManagedDriver::load_from_name(
- "adbc-test-sqlite",
- None,
- AdbcVersion::V110,
- LOAD_FLAG_DEFAULT & !LOAD_FLAG_SEARCH_USER,
- None,
- )
- .unwrap_err();
- assert_eq!(err.status, Status::NotFound);
-
- // succeed loading if LOAD_FLAG_SEARCH_USER flag is set
- ManagedDriver::load_from_name(
- "adbc-test-sqlite",
- None,
- AdbcVersion::V110,
- LOAD_FLAG_SEARCH_USER,
- None,
- )
- .unwrap();
-
- std::fs::remove_file(&manifest_path)
- .expect("Failed to remove driver manager manifest from user config
directory");
- if created {
- std::fs::remove_dir(usercfg_dir).unwrap();
- }
- }
-
- #[test]
- #[cfg_attr(not(windows), ignore)]
- fn test_get_search_paths() {
- #[cfg(target_os = "macos")]
- let system_path = PathBuf::from("/Library/Application
Support/ADBC/Drivers");
- #[cfg(not(target_os = "macos"))]
- let system_path = PathBuf::from("/etc/adbc/drivers");
-
- let search_paths = get_search_paths(LOAD_FLAG_SEARCH_SYSTEM);
- if system_path.exists() {
- assert_eq!(search_paths, vec![system_path]);
- } else {
- 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/src/search.rs
b/rust/driver_manager/src/search.rs
new file mode 100644
index 000000000..f9d7a59f7
--- /dev/null
+++ b/rust/driver_manager/src/search.rs
@@ -0,0 +1,1459 @@
+// 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.
+
+use std::borrow::Cow;
+use std::ffi::{c_void, OsStr};
+use std::fmt::Write;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::{env, ops};
+
+use libloading::Symbol;
+use toml::de::{DeTable, DeValue};
+
+use adbc_core::{
+ error::{Error, Result, Status},
+ options::AdbcVersion,
+ LoadFlags, LOAD_FLAG_ALLOW_RELATIVE_PATHS, LOAD_FLAG_SEARCH_ENV,
LOAD_FLAG_SEARCH_SYSTEM,
+ LOAD_FLAG_SEARCH_USER,
+};
+use adbc_ffi::{
+ options::check_status,
+ {FFI_AdbcDriver, FFI_AdbcDriverInitFunc, FFI_AdbcError},
+};
+
+use crate::error::libloading_error_to_adbc_error;
+
+const ERR_DETAIL_DRIVER_LOAD_TRACE: &str =
"adbc.drivermanager.driver_load_trace";
+
+#[derive(Debug, Default)]
+struct DriverInfo {
+ lib_path: PathBuf,
+ entrypoint: Option<Vec<u8>>,
+ // TODO: until we add more logging these are unused so we'll leave
+ // them out until such time that we do so.
+ // driver_name: String,
+ // version: String,
+ // source: String,
+}
+
+impl DriverInfo {
+ fn load_driver_manifest(manifest_file: &Path) -> Result<DriverInfo> {
+ let contents = fs::read_to_string(manifest_file).map_err(|e| {
+ Error::with_message_and_status(
+ format!("Could not read manifest '{}': {e}",
manifest_file.display()),
+ Status::InvalidArguments,
+ )
+ })?;
+
+ let manifest = DeTable::parse(&contents)
+ .map_err(|e| Error::with_message_and_status(e.to_string(),
Status::InvalidArguments))?;
+
+ let manifest_version = manifest
+ .get_ref()
+ .get("manifest_version")
+ .and_then(|v| v.get_ref().as_integer())
+ .map(|v| v.as_str())
+ .unwrap_or("1");
+
+ if manifest_version != "1" {
+ return Err(Error::with_message_and_status(
+ format!("Unsupported manifest version: {manifest_version}"),
+ Status::InvalidArguments,
+ ));
+ }
+
+ // leave these out until we add logging that would actually utilize
them
+ // let driver_name = get_optional_key(&manifest, "name");
+ // let version = get_optional_key(&manifest, "version");
+ // let source = get_optional_key(&manifest, "source");
+ let (os, arch, extra) = arch_triplet();
+
+ // TODO(felipecrv): point to toml file line/column in error messages
+ let driver_spanned = manifest.get_ref().get("Driver");
+ let lib_path = match driver_spanned
+ .and_then(|v| v.get_ref().get("shared"))
+ .map(|v| v.get_ref())
+ {
+ // If the value is a string, we assume it is the path to the
shared library.
+ Some(DeValue::String(path)) => Ok(PathBuf::from(path.as_ref())),
+ // If the value is a table, we look for the specific key for this
OS and architecture.
+ Some(DeValue::Table(driver)) => {
+ let key = format!("{os}_{arch}{extra}");
+ let path = driver
+ .get(key.as_str())
+ .and_then(|v| v.get_ref().as_str())
+ .unwrap_or_default();
+ Ok(PathBuf::from(path))
+ }
+ Some(driver) => Err(Error::with_message_and_status(
+ format!(
+ "Driver.shared must be a string or a table, found '{}'",
+ driver.type_str()
+ ),
+ Status::InvalidArguments,
+ )),
+ None => Err(Error::with_message_and_status(
+ format!(
+ "Manifest '{}' missing Driver.shared key",
+ manifest_file.display()
+ ),
+ Status::InvalidArguments,
+ )),
+ }?;
+
+ if lib_path.as_os_str().is_empty() {
+ return Err(Error::with_message_and_status(
+ format!(
+ "Manifest '{}' found empty string for library path of
Driver.shared.{os}_{arch}{extra}",
+ lib_path.display()
+ ),
+ Status::InvalidArguments,
+ ));
+ }
+
+ let entrypoint = driver_spanned
+ .and_then(|driver| driver.get_ref().as_table())
+ .and_then(|table| table.get("entrypoint"))
+ .map(|spanned| {
+ let val = spanned.get_ref();
+ match val.as_str() {
+ Some(s) => Ok(s.as_bytes().to_vec()),
+ None => Err(Error::with_message_and_status(
+ "Driver entrypoint must be a string".to_string(),
+ Status::InvalidArguments,
+ )),
+ }
+ })
+ .transpose()?;
+
+ Ok(DriverInfo {
+ lib_path,
+ entrypoint,
+ })
+ }
+
+ #[cfg(target_os = "windows")]
+ fn load_from_registry(root: &windows_registry::Key, driver_name: &OsStr)
-> Result<DriverInfo> {
+ const ADBC_DRIVER_REGISTRY: &str = "SOFTWARE\\ADBC\\Drivers";
+ let drivers_key = root
+ .open(ADBC_DRIVER_REGISTRY)
+ .and_then(|k| k.open(driver_name.to_str().unwrap_or_default()))
+ .map_err(|e| {
+ Error::with_message_and_status(
+ format!("Failed to open registry key: {e}"),
+ Status::NotFound,
+ )
+ })?;
+
+ let manifest_version =
drivers_key.get_u32("manifest_version").unwrap_or(1);
+
+ if manifest_version != 1 {
+ return Err(Error::with_message_and_status(
+ format!("Unsupported manifest version: {manifest_version}"),
+ Status::InvalidArguments,
+ ));
+ }
+
+ let entrypoint = drivers_key
+ .get_string("entrypoint")
+ .ok()
+ .map(|s| s.into_bytes());
+
+ Ok(DriverInfo {
+ lib_path:
PathBuf::from(drivers_key.get_string("driver").unwrap_or_default()),
+ entrypoint,
+ })
+ }
+}
+
+pub(crate) struct SearchHit {
+ /// The path where `library` was loaded from.
+ pub lib_path: PathBuf,
+ /// The loaded library.
+ pub library: libloading::Library,
+ /// The entrypoint from the manifest file or registry if specified.
+ ///
+ /// Must have priority over a specified entrypoint or the one derived
+ /// from the library name.
+ pub entrypoint: Option<Vec<u8>>,
+}
+
+impl SearchHit {
+ pub fn new(
+ lib_path: PathBuf,
+ library: libloading::Library,
+ entrypoint: Option<Vec<u8>>,
+ ) -> Self {
+ Self {
+ lib_path,
+ library,
+ entrypoint,
+ }
+ }
+
+ pub fn resolve_entrypoint<'a>(&'a self, entrypoint: Option<&'a [u8]>) ->
Cow<'a, [u8]> {
+ if let Some(entrypoint) = self.entrypoint.as_deref() {
+ // prioritize manifest entrypoint..
+ Cow::Borrowed(entrypoint)
+ } else {
+ // ...over the provided one
+ DriverLibrary::derive_entrypoint(entrypoint, &self.lib_path)
+ }
+ }
+}
+
+enum DriverInitFunc<'a> {
+ /// The driver initialization function as a static function pointer.
+ Static(&'a FFI_AdbcDriverInitFunc),
+ /// The driver initialization function as a [Symbol] from a dynamically
loaded library.
+ Dynamic(Symbol<'a, FFI_AdbcDriverInitFunc>),
+}
+
+/// Allow using [DriverInitFunc] as a function pointer.
+impl<'a> ops::Deref for DriverInitFunc<'a> {
+ type Target = FFI_AdbcDriverInitFunc;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ DriverInitFunc::Static(init) => init,
+ DriverInitFunc::Dynamic(init) => init,
+ }
+ }
+}
+
+pub(crate) struct DriverLibrary<'a> {
+ init: DriverInitFunc<'a>,
+}
+
+impl<'a> DriverLibrary<'a> {
+ pub(crate) fn from_static_init(init: &'a FFI_AdbcDriverInitFunc) -> Self {
+ Self {
+ init: DriverInitFunc::Static(init),
+ }
+ }
+
+ pub(crate) fn try_from_dynamic_library(
+ library: &'a libloading::Library,
+ entrypoint: &[u8],
+ ) -> Result<Self> {
+ let init: libloading::Symbol<adbc_ffi::FFI_AdbcDriverInitFunc> =
unsafe {
+ library
+ .get(entrypoint)
+ .or_else(|_| library.get(b"AdbcDriverInit"))
+ .map_err(libloading_error_to_adbc_error)?
+ };
+ let init = DriverInitFunc::Dynamic(init);
+ Ok(Self { init })
+ }
+
+ /// Initialize the driver via the library's entrypoint.
+ pub(crate) fn init_driver(&self, version: AdbcVersion) ->
Result<FFI_AdbcDriver> {
+ let mut error = FFI_AdbcError::default();
+ let mut driver = FFI_AdbcDriver::default();
+ let status = unsafe {
+ (self.init)(
+ version.into(),
+ &mut driver as *mut FFI_AdbcDriver as *mut c_void,
+ &mut error,
+ )
+ };
+ check_status(status, error)?;
+ Ok(driver)
+ }
+
+ pub(crate) fn load_library(filename: impl AsRef<OsStr>) ->
Result<libloading::Library> {
+ // By default, go builds the libraries with '-Wl -z nodelete' which
does not
+ // unload the go runtime. This isn't respected on mac (
https://github.com/golang/go/issues/11100#issuecomment-932638093 )
+ // so we need to explicitly load the library with RTLD_NODELETE( which
prevents unloading )
+ #[cfg(unix)]
+ let library: libloading::Library = unsafe {
+ use std::os::raw::c_int;
+
+ const RTLD_NODELETE: c_int = if cfg!(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "tvos",
+ target_os = "visionos",
+ target_os = "watchos",
+ )) {
+ 0x80
+ } else if cfg!(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "openbsd",
+ target_os = "haiku",
+ target_os = "solaris",
+ target_os = "illumos",
+ target_env = "uclibc",
+ target_env = "newlib",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "hurd",
+ target_os = "cygwin",
+ )) {
+ 0x1000
+ } else {
+ 0x0
+ };
+
+ libloading::os::unix::Library::open(
+ Some(filename.as_ref()),
+ libloading::os::unix::RTLD_LAZY |
libloading::os::unix::RTLD_LOCAL | RTLD_NODELETE,
+ )
+ .map(Into::into)
+ .map_err(libloading_error_to_adbc_error)?
+ };
+ // on windows, we emulate the same behaviour by using the
GET_MODULE_HANDLE_EX_FLAG_PIN. The `.pin()`
+ // function implements this.
+ #[cfg(windows)]
+ let library: libloading::Library = unsafe {
+ let library: libloading::os::windows::Library =
+ libloading::os::windows::Library::new(filename.as_ref())
+ .map_err(libloading_error_to_adbc_error)?;
+ library.pin().map_err(libloading_error_to_adbc_error)?;
+ library.into()
+ };
+ Ok(library)
+ }
+
+ pub(crate) fn load_library_from_name(name: impl AsRef<str>) ->
Result<libloading::Library> {
+ let filename = libloading::library_filename(name.as_ref());
+ Self::load_library(&filename)
+ }
+
+ fn load_library_from_manifest(manifest_file: &Path) -> Result<SearchHit> {
+ let info = DriverInfo::load_driver_manifest(manifest_file)?;
+ let library = Self::load_library(&info.lib_path)?;
+ Ok(SearchHit::new(info.lib_path, library, info.entrypoint))
+ }
+
+ pub(crate) fn derive_entrypoint<'b>(
+ entrypoint: Option<&'b [u8]>,
+ driver_path: impl AsRef<OsStr>,
+ ) -> Cow<'b, [u8]> {
+ if let Some(entrypoint) = entrypoint {
+ Cow::Borrowed(entrypoint)
+ } else {
+ Cow::Owned(Self::get_default_entrypoint(driver_path).into_bytes())
+ }
+ }
+
+ pub(crate) fn derive_entrypoint_from_name<'b>(
+ entrypoint: Option<&'b [u8]>,
+ name: &str,
+ ) -> Cow<'b, [u8]> {
+ if let Some(entrypoint) = entrypoint {
+ Cow::Borrowed(entrypoint)
+ } else {
+
Cow::Owned(Self::get_default_entrypoint_from_name(name).into_bytes())
+ }
+ }
+
+ pub(crate) fn search(
+ name: impl AsRef<OsStr>,
+ load_flags: LoadFlags,
+ additional_search_paths: Option<Vec<PathBuf>>,
+ ) -> Result<SearchHit> {
+ // Trace ownership rules:
+ // 1. A failed attempt is recorded exactly once by the function that
chooses to continue.
+ // 2. The returned error doesn't have to be recorded.
+ let mut trace = Vec::new();
+
+ let driver_path = Path::new(name.as_ref());
+ let allow_relative = load_flags & LOAD_FLAG_ALLOW_RELATIVE_PATHS != 0;
+
+ let result = if let Some(ext) = driver_path.extension() {
+ if !allow_relative && driver_path.is_relative() {
+ Err(Error::with_message_and_status(
+ "Relative paths are not allowed",
+ Status::InvalidArguments,
+ ))
+ } else if ext == "toml" {
+ DriverLibrary::load_library_from_manifest(driver_path)
+ } else {
+ let library = DriverLibrary::load_library(driver_path)?;
+ Ok(SearchHit::new(driver_path.to_path_buf(), library, None))
+ }
+ } else if driver_path.is_absolute() {
+ let toml_path = driver_path.with_extension("toml");
+ if toml_path.is_file() {
+ DriverLibrary::load_library_from_manifest(&toml_path)
+ } else {
+ let library = DriverLibrary::load_library(driver_path)?;
+ Ok(SearchHit::new(driver_path.to_path_buf(), library, None))
+ }
+ } else {
+ DriverLibrary::find_driver(driver_path, load_flags,
additional_search_paths, &mut trace)
+ };
+
+ result.map_err(|mut err| {
+ if !trace.is_empty() {
+ let mut trace_text = String::new();
+ for (i, trace_entry) in trace.iter().enumerate() {
+ if i > 0 {
+ trace_text.push('\n');
+ }
+ // SAFETY: writing to String via the Write trait doesn't
fail
+ unsafe { write!(&mut trace_text,
"{trace_entry}").unwrap_unchecked() };
+ }
+ let mut details = err.details.take().unwrap_or_default();
+ details.push((
+ ERR_DETAIL_DRIVER_LOAD_TRACE.to_string(),
+ trace_text.into_bytes(),
+ ));
+ err.details = Some(details);
+ }
+ err
+ })
+ }
+
+ #[cfg(target_os = "windows")]
+ fn load_library_from_registry(
+ root: &windows_registry::Key,
+ driver_name: &OsStr,
+ ) -> Result<SearchHit> {
+ let info = DriverInfo::load_from_registry(root, driver_name)?;
+ let library = Self::load_library(&info.lib_path)?;
+ Ok(SearchHit::new(info.lib_path, library, info.entrypoint))
+ }
+
+ #[cfg(target_os = "windows")]
+ fn find_driver(
+ driver_path: &Path,
+ load_flags: LoadFlags,
+ additional_search_paths: Option<Vec<PathBuf>>,
+ trace: &mut Vec<Error>,
+ ) -> Result<SearchHit> {
+ if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
+ match DriverLibrary::search_path_list(
+ driver_path,
+ &get_search_paths(LOAD_FLAG_SEARCH_ENV),
+ trace,
+ ) {
+ Ok(result) => return Ok(result),
+ Err(err) => trace.push(err),
+ }
+ }
+
+ // the logic we want is that we first search ADBC_DRIVER_PATH if set,
+ // then we search the additional search paths if they exist. Finally,
+ // we will search CONDA_PREFIX if built with conda_build before moving
on.
+ if let Some(additional_search_paths) = additional_search_paths {
+ match DriverLibrary::search_path_list(driver_path,
&additional_search_paths, trace) {
+ Ok(result) => return Ok(result),
+ Err(err) => trace.push(err),
+ }
+ }
+
+ #[cfg(conda_build)]
+ if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
+ if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") {
+ let conda_path = PathBuf::from(conda_prefix)
+ .join("etc")
+ .join("adbc")
+ .join("drivers");
+ match DriverLibrary::search_path_list(driver_path,
&[conda_path], trace) {
+ Ok(result) => return Ok(result),
+ Err(err) => trace.push(err),
+ }
+ }
+ }
+
+ if load_flags & LOAD_FLAG_SEARCH_USER != 0 {
+ // first check registry for the driver, then check the user config
path
+ let result = DriverLibrary::load_library_from_registry(
+ windows_registry::CURRENT_USER,
+ driver_path.as_os_str(),
+ );
+ if let Err(err) = result {
+ trace.push(err);
+ } else {
+ return result;
+ }
+
+ match DriverLibrary::search_path_list(
+ driver_path,
+ &get_search_paths(LOAD_FLAG_SEARCH_USER),
+ trace,
+ ) {
+ Ok(result) => return Ok(result),
+ Err(err) => trace.push(err),
+ }
+ }
+
+ if load_flags & LOAD_FLAG_SEARCH_SYSTEM != 0 {
+ let result = DriverLibrary::load_library_from_registry(
+ windows_registry::LOCAL_MACHINE,
+ driver_path.as_os_str(),
+ );
+ if let Err(err) = result {
+ trace.push(err);
+ } else {
+ return result;
+ }
+
+ match DriverLibrary::search_path_list(
+ driver_path,
+ &get_search_paths(LOAD_FLAG_SEARCH_SYSTEM),
+ trace,
+ ) {
+ Ok(result) => return Ok(result),
+ Err(err) => trace.push(err),
+ }
+ }
+
+ let driver_name =
driver_path.as_os_str().to_string_lossy().into_owned();
+ let library = DriverLibrary::load_library_from_name(driver_name)?;
+ Ok(SearchHit::new(driver_path.to_path_buf(), library, None))
+ // XXX: should we return NotFound like the non-Windows version?
+ }
+
+ #[cfg(not(windows))]
+ fn find_driver(
+ driver_path: &Path,
+ load_flags: LoadFlags,
+ additional_search_paths: Option<Vec<PathBuf>>,
+ trace: &mut Vec<Error>,
+ ) -> Result<SearchHit> {
+ let mut path_list = get_search_paths(load_flags &
LOAD_FLAG_SEARCH_ENV);
+
+ if let Some(additional_search_paths) = additional_search_paths {
+ path_list.extend(additional_search_paths);
+ }
+
+ #[cfg(conda_build)]
+ if load_flags & LOAD_FLAG_SEARCH_ENV != 0 {
+ if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") {
+ let conda_path = PathBuf::from(conda_prefix)
+ .join("etc")
+ .join("adbc")
+ .join("drivers");
+ path_list.push(conda_path);
+ }
+ }
+
+ path_list.extend(get_search_paths(load_flags & !LOAD_FLAG_SEARCH_ENV));
+ match DriverLibrary::search_path_list(driver_path, &path_list, trace) {
+ Ok(hit) => return Ok(hit),
+ Err(err) => trace.push(err),
+ };
+
+ // Convert OsStr to String before passing to load_dynamic_from_name
+ let driver_name =
driver_path.as_os_str().to_string_lossy().into_owned();
+ match DriverLibrary::load_library_from_name(driver_name) {
+ Ok(library) => return Ok(SearchHit::new(driver_path.to_path_buf(),
library, None)),
+ Err(err) => trace.push(err),
+ }
+
+ Err(Error::with_message_and_status(
+ format!("Driver not found: {}", driver_path.display()),
+ Status::NotFound,
+ ))
+ }
+
+ /// Search for the driver library in the given list of paths.
+ ///
+ /// `driver_path` can be a library name or a manifest file name. The
search loop will also try
+ /// changing the extension to `.toml` and try to load it as a manifest
before trying to load it
+ /// directly as a dynamic library.
+ ///
+ /// The first successful load will return the library path, the loaded
library, and an optional
+ /// entrypoint defined in the manifest in case it was loaded from one that
specified it.
+ fn search_path_list(
+ driver_path: &Path,
+ path_list: &[PathBuf],
+ trace: &mut Vec<Error>,
+ ) -> Result<SearchHit> {
+ path_list
+ .iter()
+ .find_map(|path| {
+ let mut full_path = path.join(driver_path);
+ full_path.set_extension("toml");
+ if full_path.is_file() {
+ match
DriverLibrary::load_library_from_manifest(&full_path) {
+ Ok(hit) => return Some(Ok(hit)),
+ Err(err) => trace.push(err),
+ }
+ }
+
+ full_path.set_extension(""); // Remove the extension to try
loading as a dynamic library.
+ let result = DriverLibrary::load_library(&full_path)
+ .map(|library| SearchHit::new(full_path, library, None));
+ match result {
+ Ok(res) => return Some(Ok(res)),
+ Err(err) => trace.push(err),
+ }
+ None
+ })
+ .unwrap_or_else(|| {
+ Err(Error::with_message_and_status(
+ format!("Driver not found: {}", driver_path.display()),
+ Status::NotFound,
+ ))
+ })
+ }
+
+ /// Construct default entrypoint from the library path.
+ fn get_default_entrypoint(driver_path: impl AsRef<OsStr>) -> String {
+ // - libadbc_driver_sqlite.so.2.0.0 -> AdbcDriverSqliteInit
+ // - adbc_driver_sqlite.dll -> AdbcDriverSqliteInit
+ // - proprietary_driver.dll -> AdbcProprietaryDriverInit
+
+ // potential path -> filename
+ let filename = driver_path.as_ref().to_str().unwrap_or_default();
+ let filename = filename
+ .rfind(['/', '\\'])
+ .map_or(filename, |pos| &filename[pos + 1..]);
+ // remove all extensions
+ let basename = filename
+ .find('.')
+ .map_or_else(|| filename, |pos| &filename[..pos]);
+ // strip the lib prefix on unix-like systems
+ let name = basename
+ .strip_prefix(env::consts::DLL_PREFIX)
+ .unwrap_or(basename);
+ Self::get_default_entrypoint_from_name(name)
+ }
+
+ /// Construct default entrypoint from the library name.
+ fn get_default_entrypoint_from_name(name: &str) -> String {
+ let entrypoint = name
+ .split(&['-', '_'][..])
+ .map(|s| {
+ // uppercase first character of a string
+ //
https://stackoverflow.com/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust
+ let mut c = s.chars();
+ match c.next() {
+ None => String::new(),
+ Some(f) => f.to_uppercase().collect::<String>() +
c.as_str(),
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("");
+
+ if entrypoint.starts_with("Adbc") {
+ format!("{entrypoint}Init")
+ } else {
+ format!("Adbc{entrypoint}Init")
+ }
+ }
+}
+
+const fn arch_triplet() -> (&'static str, &'static str, &'static str) {
+ #[cfg(target_arch = "x86_64")]
+ const ARCH: &str = "amd64";
+ #[cfg(all(target_arch = "aarch64", target_endian = "big"))]
+ const ARCH: &str = "arm64be";
+ #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
+ const ARCH: &str = "arm64";
+ #[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
+ const ARCH: &str = "powerpc64le";
+ #[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
+ const ARCH: &str = "powerpc64";
+ #[cfg(target_arch = "riscv32")]
+ const ARCH: &str = "riscv";
+ #[cfg(not(any(
+ target_arch = "x86_64",
+ target_arch = "aarch64",
+ target_arch = "powerpc64",
+ target_arch = "riscv32",
+ )))]
+ const ARCH: &str = std::env::consts::ARCH;
+
+ const OS: &str = std::env::consts::OS;
+
+ #[cfg(target_env = "musl")]
+ const EXTRA: &str = "_musl";
+ #[cfg(all(target_os = "windows", target_env = "gnu"))]
+ const EXTRA: &str = "_mingw";
+ #[cfg(not(any(target_env = "musl", all(target_os = "windows", target_env =
"gnu"))))]
+ const EXTRA: &str = "";
+
+ (OS, ARCH, EXTRA)
+}
+
+#[cfg(target_os = "windows")]
+mod target_windows {
+ use windows_sys as windows;
+
+ use std::ffi::c_void;
+ use std::ffi::OsString;
+ use std::os::windows::ffi::OsStringExt;
+ use std::path::PathBuf;
+ use std::slice;
+
+ use windows::Win32::UI::Shell;
+
+ // adapted from
https://github.com/dirs-dev/dirs-sys-rs/blob/main/src/lib.rs#L150
+ pub fn user_config_dir() -> Option<PathBuf> {
+ unsafe {
+ let mut path_ptr: windows::core::PWSTR = std::ptr::null_mut();
+ let result = Shell::SHGetKnownFolderPath(
+ &Shell::FOLDERID_LocalAppData,
+ 0,
+ std::ptr::null_mut(),
+ &mut path_ptr,
+ );
+
+ if result == 0 {
+ let len = windows::Win32::Globalization::lstrlenW(path_ptr) as
usize;
+ let path = slice::from_raw_parts(path_ptr, len);
+ let ostr: OsString = OsStringExt::from_wide(path);
+ windows::Win32::System::Com::CoTaskMemFree(path_ptr as *const
c_void);
+ Some(PathBuf::from(ostr))
+ } else {
+ windows::Win32::System::Com::CoTaskMemFree(path_ptr as *const
c_void);
+ None
+ }
+ }
+ }
+}
+
+fn user_config_dir() -> Option<PathBuf> {
+ #[cfg(target_os = "windows")]
+ {
+ use target_windows::user_config_dir;
+ user_config_dir().map(|mut path| {
+ path.push("ADBC");
+ path.push("Drivers");
+ path
+ })
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ env::var_os("HOME").map(PathBuf::from).map(|mut path| {
+ path.push("Library");
+ path.push("Application Support");
+ path.push("ADBC");
+ path.push("Drivers");
+ path
+ })
+ }
+
+ #[cfg(all(unix, not(target_os = "macos")))]
+ {
+ env::var_os("XDG_CONFIG_HOME")
+ .map(PathBuf::from)
+ .or_else(|| {
+ env::var_os("HOME").map(|home| {
+ let mut path = PathBuf::from(home);
+ path.push(".config");
+ path
+ })
+ })
+ .map(|mut path| {
+ path.push("adbc");
+ path.push("drivers");
+ path
+ })
+ }
+}
+
+fn system_config_dir() -> Option<PathBuf> {
+ #[cfg(target_os = "macos")]
+ {
+ Some(PathBuf::from("/Library/Application Support/ADBC/Drivers"))
+ }
+
+ #[cfg(all(unix, not(target_os = "macos")))]
+ {
+ Some(PathBuf::from("/etc/adbc/drivers"))
+ }
+
+ #[cfg(not(unix))]
+ {
+ None
+ }
+}
+
+fn get_search_paths(lvls: LoadFlags) -> Vec<PathBuf> {
+ let mut result = Vec::new();
+ if lvls & LOAD_FLAG_SEARCH_ENV != 0 {
+ if let Some(paths) = env::var_os("ADBC_DRIVER_PATH") {
+ for p in env::split_paths(&paths) {
+ result.push(p);
+ }
+ }
+ }
+
+ if lvls & LOAD_FLAG_SEARCH_USER != 0 {
+ if let Some(path) = user_config_dir() {
+ if path.exists() {
+ result.push(path);
+ }
+ }
+ }
+
+ // system level for windows is to search the registry keys
+ #[cfg(not(windows))]
+ if lvls & LOAD_FLAG_SEARCH_SYSTEM != 0 {
+ if let Some(path) = system_config_dir() {
+ if path.exists() {
+ result.push(path);
+ }
+ }
+ }
+
+ result
+}
+
+pub(crate) 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..]))
+}
+
+#[cfg(test)]
+mod tests {
+ use std::env;
+
+ use adbc_core::{options::AdbcVersion, LOAD_FLAG_ALLOW_RELATIVE_PATHS};
+ use temp_env::{with_var, with_var_unset};
+
+ use crate::ManagedDriver;
+
+ use super::*;
+
+ fn manifest_without_driver() -> &'static str {
+ r#"
+ manifest_version = 1
+
+ name = 'SQLite3'
+ publisher = 'arrow-adbc'
+ version = '1.0.0'
+
+ [ADBC]
+ version = '1.1.0'
+ "#
+ }
+
+ fn simple_manifest() -> String {
+ // if this test is enabled, we expect the env var
ADBC_DRIVER_MANAGER_TEST_LIB
+ // to be defined.
+ let driver_path =
+ PathBuf::from(env::var_os("ADBC_DRIVER_MANAGER_TEST_LIB").expect(
+ "ADBC_DRIVER_MANAGER_TEST_LIB must be set for driver manager
manifest tests",
+ ));
+
+ assert!(
+ driver_path.exists(),
+ "ADBC_DRIVER_MANAGER_TEST_LIB path does not exist: {}",
+ driver_path.display()
+ );
+
+ let (os, arch, extra) = arch_triplet();
+ format!(
+ r#"
+ {}
+
+ [Driver]
+ [Driver.shared]
+ {os}_{arch}{extra} = {driver_path:?}
+ "#,
+ manifest_without_driver()
+ )
+ }
+
+ fn write_manifest_to_tempfile(p: PathBuf, tbl: String) ->
(tempfile::TempDir, PathBuf) {
+ let tmp_dir = tempfile::Builder::new()
+ .prefix("adbc_tests")
+ .tempdir()
+ .expect("Failed to create temporary directory for driver manager
manifest test");
+
+ let manifest_path = tmp_dir.path().join(p);
+ if let Some(parent) = manifest_path.parent() {
+ std::fs::create_dir_all(parent)
+ .expect("Failed to create parent directory for manifest");
+ }
+
+ std::fs::write(&manifest_path, tbl.as_str())
+ .expect("Failed to write driver manager manifest to temporary
file");
+
+ (tmp_dir, manifest_path)
+ }
+
+ #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
+ #[test]
+ fn test_default_entrypoint() {
+ for driver in [
+ "adbc_driver_sqlite",
+ "adbc_driver_sqlite.dll",
+ "driver_sqlite",
+ "libadbc_driver_sqlite",
+ "libadbc_driver_sqlite.so",
+ "libadbc_driver_sqlite.so.6.0.0",
+ "/usr/lib/libadbc_driver_sqlite.so",
+ "/usr/lib/libadbc_driver_sqlite.so.6.0.0",
+ "C:\\System32\\adbc_driver_sqlite.dll",
+ ] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(driver),
+ "AdbcDriverSqliteInit"
+ );
+ }
+
+ for driver in [
+ "adbc_sqlite",
+ "sqlite",
+ "/usr/lib/sqlite.so",
+ "C:\\System32\\sqlite.dll",
+ ] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(driver),
+ "AdbcSqliteInit"
+ );
+ }
+
+ for driver in [
+ "proprietary_engine",
+ "libproprietary_engine.so.6.0.0",
+ "/usr/lib/proprietary_engine.so",
+ "C:\\System32\\proprietary_engine.dll",
+ ] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(driver),
+ "AdbcProprietaryEngineInit"
+ );
+ }
+
+ for driver in ["driver_example", "libdriver_example.so"] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(driver),
+ "AdbcDriverExampleInit"
+ );
+ }
+ }
+
+ #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
+ #[test]
+ fn test_default_entrypoint_from_name() {
+ for name in ["adbc_driver_sqlite", "driver_sqlite"] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint_from_name(name),
+ "AdbcDriverSqliteInit"
+ );
+
+ let filename = libloading::library_filename(name);
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(filename),
+ "AdbcDriverSqliteInit"
+ );
+ }
+
+ for name in ["adbc_sqlite", "sqlite"] {
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint_from_name(name),
+ "AdbcSqliteInit"
+ );
+
+ let filename = libloading::library_filename(name);
+ assert_eq!(
+ DriverLibrary::get_default_entrypoint(filename),
+ "AdbcSqliteInit"
+ );
+ }
+ }
+
+ /// Regression test for https://github.com/apache/arrow-adbc/pull/3693
+ /// Ensures driver manager tests for Windows pull in Windows crates. This
+ /// can be removed/replace when more complete tests are added.
+ #[test]
+ fn test_user_config_dir() {
+ let _ = user_config_dir().unwrap();
+ }
+
+ #[test]
+ #[cfg_attr(not(windows), ignore)]
+ fn test_get_search_paths() {
+ #[cfg(target_os = "macos")]
+ let system_path = PathBuf::from("/Library/Application
Support/ADBC/Drivers");
+ #[cfg(not(target_os = "macos"))]
+ let system_path = PathBuf::from("/etc/adbc/drivers");
+
+ let search_paths = get_search_paths(LOAD_FLAG_SEARCH_SYSTEM);
+ if system_path.exists() {
+ assert_eq!(search_paths, vec![system_path]);
+ } else {
+ assert_eq!(search_paths, Vec::<PathBuf>::new());
+ }
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_relative_path() {
+ std::fs::write(PathBuf::from("sqlite.toml"), simple_manifest())
+ .expect("Failed to write driver manager manifest to file");
+
+ let err = ManagedDriver::load_from_name("sqlite.toml", None,
AdbcVersion::V100, 0, None)
+ .unwrap_err();
+ assert_eq!(err.status, Status::InvalidArguments);
+
+ ManagedDriver::load_from_name(
+ "sqlite.toml",
+ None,
+ AdbcVersion::V100,
+ LOAD_FLAG_ALLOW_RELATIVE_PATHS,
+ None,
+ )
+ .unwrap();
+
+ std::fs::remove_file("sqlite.toml")
+ .expect("Failed to remove temporary driver manifest file");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_manifest_missing_driver() {
+ let (tmp_dir, manifest_path) = write_manifest_to_tempfile(
+ PathBuf::from("sqlite.toml"),
+ manifest_without_driver().to_string(),
+ );
+
+ let err = ManagedDriver::load_from_name(
+ manifest_path,
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::InvalidArguments);
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_manifest_bad_version() {
+ let manifest = "manifest_version = 2\n".to_owned() +
&simple_manifest().to_owned();
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"), manifest);
+
+ let err = ManagedDriver::load_from_name(
+ manifest_path,
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::InvalidArguments);
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_manifest_wrong_arch() {
+ let manifest_wrong_arch = format!(
+ r#"
+ {}
+
+ [Driver]
+ [Driver.shared]
+ non-existing = 'path/to/bad/driver.so'
+ "#,
+ manifest_without_driver()
+ );
+
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
manifest_wrong_arch);
+
+ let err = ManagedDriver::load_from_name(
+ manifest_path,
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::InvalidArguments);
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_absolute_path() {
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
+
+ ManagedDriver::load_from_name(
+ manifest_path,
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap();
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_absolute_path_no_ext() {
+ let (tmp_dir, mut manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
+
+ manifest_path.set_extension("");
+ ManagedDriver::load_from_name(
+ manifest_path,
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap();
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
+ fn test_load_driver_env() {
+ // ensure that we fail without the env var set
+ with_var_unset("ADBC_DRIVER_PATH", || {
+ let err = ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_SEARCH_ENV,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::NotFound);
+ });
+
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
+
+ with_var(
+ "ADBC_DRIVER_PATH",
+ Some(manifest_path.parent().unwrap().as_os_str()),
+ || {
+ ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_SEARCH_ENV,
+ None,
+ )
+ .unwrap();
+ },
+ );
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_driver_env_multiple_paths() {
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
+
+ let path_os_string = env::join_paths([
+ Path::new("/home"),
+ Path::new(""),
+ manifest_path.parent().unwrap(),
+ ])
+ .unwrap();
+
+ with_var("ADBC_DRIVER_PATH", Some(&path_os_string), || {
+ ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_SEARCH_ENV,
+ None,
+ )
+ .unwrap();
+ });
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_additional_path() {
+ let p = PathBuf::from("majestik møøse/sqlite.toml");
+ let (tmp_dir, manifest_path) = write_manifest_to_tempfile(p,
simple_manifest());
+
+ ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_SEARCH_ENV,
+ Some(vec![manifest_path.parent().unwrap().to_path_buf()]),
+ )
+ .unwrap();
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ fn test_load_non_ascii_path() {
+ let p = PathBuf::from("majestik møøse/sqlite.toml");
+ let (tmp_dir, manifest_path) = write_manifest_to_tempfile(p,
simple_manifest());
+
+ with_var(
+ "ADBC_DRIVER_PATH",
+ Some(manifest_path.parent().unwrap().as_os_str()),
+ || {
+ ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_SEARCH_ENV,
+ None,
+ )
+ .unwrap();
+ },
+ );
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)]
+ #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
+ fn test_disallow_env_config() {
+ let (tmp_dir, manifest_path) =
+ write_manifest_to_tempfile(PathBuf::from("sqlite.toml"),
simple_manifest());
+
+ with_var(
+ "ADBC_DRIVER_PATH",
+ Some(manifest_path.parent().unwrap().as_os_str()),
+ || {
+ let load_flags = adbc_core::LOAD_FLAG_DEFAULT &
!adbc_core::LOAD_FLAG_SEARCH_ENV;
+ let err = ManagedDriver::load_from_name(
+ "sqlite",
+ None,
+ AdbcVersion::V100,
+ load_flags,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::NotFound);
+ },
+ );
+
+ tmp_dir
+ .close()
+ .expect("Failed to close/remove temporary directory");
+ }
+
+ #[test]
+ #[cfg_attr(
+ not(all(
+ feature = "driver_manager_test_lib",
+ feature = "driver_manager_test_manifest_user"
+ )),
+ ignore
+ )]
+ #[cfg_attr(target_os = "windows", ignore)] // TODO: remove this line after
fixing
+ fn test_manifest_user_config() {
+ let err = ManagedDriver::load_from_name(
+ "adbc-test-sqlite",
+ None,
+ AdbcVersion::V110,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::NotFound);
+
+ let usercfg_dir = user_config_dir().unwrap();
+ let mut created = false;
+ if !usercfg_dir.exists() {
+ std::fs::create_dir_all(&usercfg_dir)
+ .expect("Failed to create user config directory for driver
manager test");
+ created = true;
+ }
+
+ let manifest_path = usercfg_dir.join("adbc-test-sqlite.toml");
+ std::fs::write(&manifest_path, simple_manifest())
+ .expect("Failed to write driver manager manifest to user config
directory");
+
+ // fail to load if the load flag doesn't have LOAD_FLAG_SEARCH_USER
+ let err = ManagedDriver::load_from_name(
+ "adbc-test-sqlite",
+ None,
+ AdbcVersion::V110,
+ adbc_core::LOAD_FLAG_DEFAULT & !adbc_core::LOAD_FLAG_SEARCH_USER,
+ None,
+ )
+ .unwrap_err();
+ assert_eq!(err.status, Status::NotFound);
+
+ // succeed loading if LOAD_FLAG_SEARCH_USER flag is set
+ ManagedDriver::load_from_name(
+ "adbc-test-sqlite",
+ None,
+ AdbcVersion::V110,
+ adbc_core::LOAD_FLAG_SEARCH_USER,
+ None,
+ )
+ .unwrap();
+
+ std::fs::remove_file(&manifest_path)
+ .expect("Failed to remove driver manager manifest from user config
directory");
+ if created {
+ std::fs::remove_dir(usercfg_dir).unwrap();
+ }
+ }
+
+ /// Regression test for https://github.com/apache/arrow-adbc/pull/3693
+ /// Ensures driver manager tests for Windows pull in Windows crates. This
+ /// can be removed/replace when more complete tests are added.
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_load_driver_from_registry() {
+ use std::ffi::OsStr;
+ let driver_name = OsStr::new("nonexistent_test_driver");
+ let result =
+
DriverLibrary::load_library_from_registry(windows_registry::CURRENT_USER,
driver_name);
+ match result {
+ Ok(_) => panic!("Expected registry lookup to fail"),
+ Err(err) => assert_eq!(err.status, Status::NotFound),
+ }
+ }
+
+ #[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);
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_load_from_name_includes_search_trace_in_error_details() {
+ let err = ManagedDriver::load_from_name(
+ "adbc_test_driver_manager_missing_driver_7c8f5482",
+ None,
+ AdbcVersion::V100,
+ adbc_core::LOAD_FLAG_DEFAULT,
+ None,
+ )
+ .unwrap_err();
+
+ let details = err
+ .details
+ .expect("Expected load-from-name errors to include details");
+ let trace = details
+ .iter()
+ .find(|(key, _)| key == "adbc.drivermanager.driver_load_trace")
+ .expect("Expected driver load trace in error details");
+ assert!(
+ !trace.1.is_empty(),
+ "Expected driver load trace detail to be non-empty"
+ );
+ }
+
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_parse_driver_uri_windows_file() {
+ let tmp_dir = tempfile::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");
+ }
+}