blob: 8118868281efe3f68d1a22f3dbecf36590ed94da [file]
// 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.
//! Load and use ADBC drivers.
//!
//! The driver manager provides an implementation of the ADBC interface which
//! uses FFI to wrap an object file implementation of
//! [`adbc.h`](https://github.com/apache/arrow-adbc/blob/main/c/include/arrow-adbc/adbc.h).
//!
//! There are two ways that drivers can be used:
//! 1. By linking (either statically or dynamically) the driver implementation
//! at link-time and then using [ManagedDriver::load_static].
//! 2. By loading the driver implementation at run-time (with `dlopen/LoadLibrary`)
//! using [ManagedDriver::load_dynamic_from_name] or
//! [ManagedDriver::load_dynamic_from_filename].
//!
//! Drivers are initialized using a function provided by the driver as a main
//! entrypoint, canonically called `AdbcDriverInit`. Although many will use a
//! different name to support statically linking multiple drivers within the
//! same program.
//!
//! ## Using across threads
//!
//! [ManagedDriver], [ManagedDatabase], [ManagedConnection] and [ManagedStatement]
//! can be used across threads though all of their operations are serialized
//! under the hood. They hold their inner implementations within [std::sync::Arc],
//! so they are cheaply clonable.
//!
//! ## Example
//!
//! ```rust
//! # use std::sync::Arc;
//! # use arrow_array::{Array, StringArray, Int64Array, Float64Array};
//! # use arrow_array::{RecordBatch, RecordBatchReader};
//! # use arrow_schema::{Field, Schema, DataType};
//! # use arrow_select::concat::concat_batches;
//! # use adbc_core::{
//! # options::{AdbcVersion, OptionDatabase, OptionStatement},
//! # Connection, Database, Driver, Statement, Optionable
//! # };
//! # use adbc_driver_manager::ManagedDriver;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let opts = [(OptionDatabase::Uri, ":memory:".into())];
//! let mut driver = ManagedDriver::load_dynamic_from_name("adbc_driver_sqlite", None, AdbcVersion::V100)?;
//! let database = driver.new_database_with_opts(opts)?;
//! let mut connection = database.new_connection()?;
//! let mut statement = connection.new_statement()?;
//!
//! // Define some data.
//! let columns: Vec<Arc<dyn Array>> = vec![
//! Arc::new(Int64Array::from(vec![1, 2, 3, 4])),
//! Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
//! Arc::new(StringArray::from(vec!["a", "b", "c", "d"])),
//! ];
//! let schema = Schema::new(vec![
//! Field::new("a", DataType::Int64, true),
//! Field::new("b", DataType::Float64, true),
//! Field::new("c", DataType::Utf8, true),
//! ]);
//! let input: RecordBatch = RecordBatch::try_new(Arc::new(schema), columns)?;
//!
//! // Ingest data.
//! statement.set_option(OptionStatement::TargetTable, "my_table".into())?;
//! statement.bind(input.clone())?;
//! statement.execute_update()?;
//!
//! // Extract data.
//! statement.set_sql_query("select * from my_table")?;
//! let output = statement.execute()?;
//! let schema = output.schema();
//! let output: Result<Vec<RecordBatch>, _> = output.collect();
//! let output = concat_batches(&schema, &output?)?;
//! assert_eq!(input, output);
//!
//! # Ok(())
//! # }
//! ```
// According to the ADBC specification, objects allow serialized access from
// multiple threads: one thread may make a call, and once finished, another
// thread may make a call. They do not allow concurrent access from multiple
// threads.
//
// In order to implement these semantics, all mutable FFI objects are wrapped
// in `Mutex`es. `FFI_Driver` is not wrapped in a `Mutex` because it is
// an immutable struct of function pointers. Wrapping the driver in a `Mutex`
// would prevent any parallelism between driver calls, which is not desirable.
pub mod error;
pub mod profile;
pub mod search;
use std::collections::HashSet;
use std::ffi::{CString, OsStr};
use std::ops::DerefMut;
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};
use adbc_ffi::options::{
check_status, get_option_bytes, get_option_string, set_option_connection, set_option_database,
set_option_statement,
};
use arrow_array::ffi::{FFI_ArrowSchema, to_ffi};
use arrow_array::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream};
use arrow_array::{Array, RecordBatch, RecordBatchReader, StructArray};
use adbc_core::{
Connection, Database, Driver, LoadFlags, Optionable, PartitionedResult, Statement,
error::{Error, Result, Status},
options::{self, AdbcVersion, InfoCode, OptionDatabase, OptionValue},
};
use adbc_ffi::driver_method;
use self::search::{DriverLibrary, DriverLocator, parse_driver_uri};
use crate::profile::{
ConnectionProfile, ConnectionProfileProvider, FilesystemProfileProvider, process_profile_value,
};
const ERR_CANCEL_UNSUPPORTED: &str =
"Canceling connection or statement is not supported with ADBC 1.0.0";
const ERR_STATISTICS_UNSUPPORTED: &str = "Statistics are not supported with ADBC 1.0.0";
#[derive(Debug)]
struct ManagedDriverInner {
driver: adbc_ffi::FFI_AdbcDriver,
version: AdbcVersion, // Driver version
// The dynamic library must be kept loaded for the entire lifetime of the driver.
// To avoid complex lifetimes we prefer to store it as part of this struct.
// Besides, the `library` field must always appear after `driver` because of drop order:
// `driver` has an implicit dependency on `library` and so it must be dropped
// before `library` because otherwise `driver` would be full of dangling
// function pointers.
// See: https://doc.rust-lang.org/std/ops/trait.Drop.html#drop-order
_library: Option<libloading::Library>,
}
/// Implementation of [Driver].
#[derive(Clone, Debug)]
pub struct ManagedDriver {
inner: Pin<Arc<ManagedDriverInner>>,
}
impl ManagedDriver {
/// Returns the [`AdbcVersion`] of this driver.
pub fn version(&self) -> AdbcVersion {
self.inner.version
}
/// Load a driver from an initialization function.
pub fn load_static(
init: &adbc_ffi::FFI_AdbcDriverInitFunc,
version: AdbcVersion,
) -> Result<Self> {
let driver = DriverLibrary::from_static_init(init).init_driver(version)?;
let inner = Arc::pin(ManagedDriverInner {
driver,
version,
_library: None,
});
Ok(ManagedDriver { inner })
}
/// Load a driver either by name, filename, path, or via locating a toml manifest file.
/// The `load_flags` control what directories are searched to locate a manifest.
/// The `entrypoint` allows customizing the name of the driver initialization function
/// if it is not the default `AdbcDriverInit` and isn't described in the loaded manifest.
/// If not provided, an entrypoint will be searched for based on the driver's name.
/// The `version` defines the ADBC revision to attempt to initialize.
///
/// The full logic used here is as follows:
/// - 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
/// - 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.
pub fn load_from_name(
name: impl AsRef<OsStr>,
entrypoint: Option<&[u8]>,
version: AdbcVersion,
load_flags: LoadFlags,
additional_search_paths: Option<Vec<PathBuf>>,
) -> Result<Self> {
let search_hit = DriverLibrary::search(name, 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.
///
/// Will attempt to load the dynamic library located at `filename`, find the
/// symbol with name `entrypoint` (defaults to `AdbcDriverInit` if `None`),
/// and then create the driver using the resolved function.
///
/// The `filename` argument may be either:
/// - A library filename;
/// - The absolute path to the library;
/// - A relative (to the current working directory) path to the library.
pub fn load_dynamic_from_filename(
filename: impl AsRef<OsStr>,
entrypoint: Option<&[u8]>,
version: AdbcVersion,
) -> Result<Self> {
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,
_library: Some(library),
});
Ok(ManagedDriver { inner })
}
/// Load a driver from a dynamic library name.
///
/// Will attempt to load the dynamic library with the given `name`, find the
/// symbol with name `entrypoint` (defaults to `AdbcDriverInit` if `None`),
/// and then create the driver using the resolved function.
///
/// The `name` should not include any platform-specific prefixes or suffixes.
/// For example, use `adbc_driver_sqlite` rather than `libadbc_driver_sqlite.so`.
pub fn load_dynamic_from_name(
name: impl AsRef<str>,
entrypoint: Option<&[u8]>,
version: AdbcVersion,
) -> Result<Self> {
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 {
&self.inner.driver
}
/// Returns a new database using the loaded driver.
fn database_new(&self) -> Result<adbc_ffi::FFI_AdbcDatabase> {
let driver = self.inner_ffi_driver();
let mut database = adbc_ffi::FFI_AdbcDatabase {
private_driver: driver,
..Default::default()
};
// DatabaseNew
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(*driver, DatabaseNew);
let status = unsafe { method(&mut database, &mut error) };
check_status(status, error)?;
Ok(database)
}
/// Initialize the given database using the loaded driver.
fn database_init(
&self,
mut database: adbc_ffi::FFI_AdbcDatabase,
) -> Result<adbc_ffi::FFI_AdbcDatabase> {
let driver = self.inner_ffi_driver();
// DatabaseInit
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, DatabaseInit);
let status = unsafe { method(&mut database, &mut error) };
check_status(status, error)?;
Ok(database)
}
}
impl Driver for ManagedDriver {
type DatabaseType = ManagedDatabase;
fn new_database(&mut self) -> Result<Self::DatabaseType> {
// Construct a new database.
let database = self.database_new()?;
// Initialize the database.
let database = self.database_init(database)?;
let inner = Arc::new(ManagedDatabaseInner {
database: Mutex::new(database),
driver: self.inner.clone(),
});
Ok(Self::DatabaseType { inner })
}
fn new_database_with_opts(
&mut self,
opts: impl IntoIterator<Item = (<Self::DatabaseType as Optionable>::Option, OptionValue)>,
) -> Result<Self::DatabaseType> {
// Construct a new database.
let mut database = self.database_new()?;
// Set the options.
{
let driver = self.inner_ffi_driver();
for (key, value) in opts {
set_option_database(driver, &mut database, self.inner.version, key, value)?;
}
}
// Initialize the database.
let database = self.database_init(database)?;
let inner = Arc::new(ManagedDatabaseInner {
database: Mutex::new(database),
driver: self.inner.clone(),
});
Ok(Self::DatabaseType { inner })
}
}
struct ManagedDatabaseInner {
database: Mutex<adbc_ffi::FFI_AdbcDatabase>,
driver: Pin<Arc<ManagedDriverInner>>,
}
impl Drop for ManagedDatabaseInner {
fn drop(&mut self) {
let driver = &self.driver.driver;
if let Ok(mut database) = self.database.lock() {
let method = driver_method!(driver, DatabaseRelease);
// TODO(alexandreyc): how should we handle `DatabaseRelease` failing?
// See: https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
unsafe { method(database.deref_mut(), null_mut()) };
}
// We could still drop here but if the lock is poisoned, we have no
// clue what the status is. Since a panic comes from Rust code,
// _probably_ the FFI handle is unharmed. But I think it's safer to
// leak than to try to release.
}
}
/// Implementation of [Database].
#[derive(Clone)]
pub struct ManagedDatabase {
inner: Arc<ManagedDatabaseInner>,
}
impl ManagedDatabase {
/// Creates a new database connection from a URI string.
///
/// This method supports both direct driver URIs and profile references.
///
/// # URI Formats
///
/// ## Direct Driver Connection
/// - `"driver_name:connection_string"` - Loads the specified driver and connects
/// - `"driver_name://host:port/database"` - Standard database URI format
///
/// ## Profile Reference
/// - `"profile://name"` - Loads connection configuration from a profile file
/// - `"profile:///absolute/path/to/profile.toml"` - Absolute path to profile
/// - `"profile://relative/path/to/profile.toml"` - Relative path to profile
///
/// # Arguments
///
/// * `uri` - The connection URI or profile reference
/// * `entrypoint` - Optional driver entrypoint name (uses default if `None`)
/// * `version` - ADBC version to use
/// * `load_flags` - Flags controlling driver loading behavior
/// * `additional_search_paths` - Optional paths to search for drivers or profiles
///
/// # Returns
///
/// A configured `ManagedDatabase` ready for creating connections.
///
/// # Errors
///
/// Returns an error if:
/// - The URI format is invalid
/// - The driver cannot be loaded
/// - The profile cannot be found or parsed
/// - Database initialization fails
///
/// # Examples
///
/// ```no_run
/// use adbc_core::options::AdbcVersion;
/// use adbc_driver_manager::ManagedDatabase;
/// use adbc_core::LOAD_FLAG_DEFAULT;
///
/// // Direct connection
/// let db = ManagedDatabase::from_uri(
/// "sqlite::memory:",
/// None,
/// AdbcVersion::V100,
/// LOAD_FLAG_DEFAULT,
/// None
/// )?;
///
/// // Profile connection
/// let db = ManagedDatabase::from_uri(
/// "profile://my_database",
/// None,
/// AdbcVersion::V100,
/// LOAD_FLAG_DEFAULT,
/// None
/// )?;
/// # Ok::<(), adbc_core::error::Error>(())
/// ```
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(),
)
}
/// Creates a new database connection from a URI with additional options.
///
/// This is similar to [`from_uri`](Self::from_uri), but allows passing additional
/// database options that override any options from profiles.
///
/// # Arguments
///
/// * `uri` - The connection URI or profile reference
/// * `entrypoint` - Optional driver entrypoint name
/// * `version` - ADBC version to use
/// * `load_flags` - Flags controlling driver loading behavior
/// * `additional_search_paths` - Optional paths to search for drivers or profiles
/// * `opts` - Database options to apply (override profile options)
///
/// # Returns
///
/// A configured `ManagedDatabase` with the specified options applied.
///
/// # Option Priority
///
/// Options are applied in this order (later values override earlier ones):
/// 1. Profile options (if using a profile URI)
/// 2. Options provided via `opts` parameter
/// 3. URI connection string (for direct driver URIs)
///
/// # Examples
///
/// ```no_run
/// use adbc_core::options::{AdbcVersion, OptionDatabase, OptionValue};
/// use adbc_driver_manager::ManagedDatabase;
/// use adbc_core::LOAD_FLAG_DEFAULT;
///
/// let opts = vec![
/// (OptionDatabase::Username, OptionValue::String("user".to_string())),
/// (OptionDatabase::Password, OptionValue::String("pass".to_string())),
/// ];
///
/// let db = ManagedDatabase::from_uri_with_opts(
/// "profile://my_database",
/// None,
/// AdbcVersion::V100,
/// LOAD_FLAG_DEFAULT,
/// None,
/// opts,
/// )?;
/// # Ok::<(), adbc_core::error::Error>(())
/// ```
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> {
Self::from_uri_with_profile_provider(
uri,
entrypoint,
version,
load_flags,
additional_search_paths,
FilesystemProfileProvider::default(),
opts,
)
}
/// Creates a new database connection from a URI with a custom profile provider.
///
/// This advanced method allows using a custom implementation of
/// [`ConnectionProfileProvider`] to load profiles from alternative sources
/// (e.g., remote configuration services, encrypted storage, etc.).
///
/// # Arguments
///
/// * `uri` - The connection URI or profile reference
/// * `entrypoint` - Optional driver entrypoint name
/// * `version` - ADBC version to use
/// * `load_flags` - Flags controlling driver loading behavior
/// * `additional_search_paths` - Optional paths to search for drivers or profiles
/// * `profile_provider` - Custom profile provider implementation
/// * `opts` - Database options to apply (override profile options)
///
/// # Returns
///
/// A configured `ManagedDatabase` using the custom profile provider.
///
/// # Examples
///
/// ```no_run
/// use adbc_core::options::{AdbcVersion, OptionDatabase, OptionValue};
/// use adbc_driver_manager::ManagedDatabase;
/// use adbc_driver_manager::profile::FilesystemProfileProvider;
/// use adbc_core::LOAD_FLAG_DEFAULT;
///
/// let provider = FilesystemProfileProvider::default();
/// let opts = vec![(OptionDatabase::Username, OptionValue::String("admin".to_string()))];
///
/// let db = ManagedDatabase::from_uri_with_profile_provider(
/// "profile://my_database",
/// None,
/// AdbcVersion::V100,
/// LOAD_FLAG_DEFAULT,
/// None,
/// provider,
/// opts,
/// )?;
/// # Ok::<(), adbc_core::error::Error>(())
/// ```
pub fn from_uri_with_profile_provider(
uri: &str,
entrypoint: Option<&[u8]>,
version: AdbcVersion,
load_flags: LoadFlags,
additional_search_paths: Option<Vec<PathBuf>>,
profile_provider: impl ConnectionProfileProvider,
opts: impl IntoIterator<Item = (<Self as Optionable>::Option, OptionValue)>,
) -> Result<Self> {
let result = parse_driver_uri(uri)?;
let (mut drv, default_opts) = match result {
DriverLocator::Uri(driver, final_uri) => {
let drv = ManagedDriver::load_from_name(
driver,
entrypoint,
version,
load_flags,
additional_search_paths,
)?;
let final_opts = vec![(
OptionDatabase::Uri,
OptionValue::String(final_uri.to_string()),
)];
(drv, final_opts)
}
DriverLocator::Profile(profile) => {
let profile = profile_provider.get_profile(profile)?;
let (driver_name, init_func) = profile.get_driver_name()?;
let drv: ManagedDriver;
if let Some(init_fn) = init_func {
drv = ManagedDriver::load_static(init_fn, version)?;
} else {
drv = ManagedDriver::load_from_name(
driver_name,
entrypoint,
version,
load_flags,
additional_search_paths,
)?;
}
let profile_opts: Vec<(OptionDatabase, OptionValue)> = profile
.get_options()?
.into_iter()
.map(|(k, v)| -> Result<(OptionDatabase, OptionValue)> {
if let OptionValue::String(s) = v {
let result = process_profile_value(&s)?;
Ok((k, result))
} else {
Ok((k, v))
}
})
.collect::<Result<Vec<_>>>()?;
(drv, profile_opts)
}
};
drv.new_database_with_opts(default_opts.into_iter().chain(opts))
}
fn ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
&self.inner.driver.driver
}
fn driver_version(&self) -> AdbcVersion {
self.inner.driver.version
}
/// Returns a new connection using the loaded driver.
fn connection_new(&self) -> Result<adbc_ffi::FFI_AdbcConnection> {
let driver = self.ffi_driver();
let mut connection = adbc_ffi::FFI_AdbcConnection::default();
// ConnectionNew
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(*driver, ConnectionNew);
let status = unsafe { method(&mut connection, &mut error) };
check_status(status, error)?;
Ok(connection)
}
/// Initialize the given connection using the loaded driver.
fn connection_init(
&self,
mut connection: adbc_ffi::FFI_AdbcConnection,
) -> Result<adbc_ffi::FFI_AdbcConnection> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
// ConnectionInit
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionInit);
let status = unsafe { method(&mut connection, &mut *database, &mut error) };
check_status(status, error)?;
Ok(connection)
}
}
impl Optionable for ManagedDatabase {
type Option = options::OptionDatabase;
fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, DatabaseGetOptionBytes);
let populate = |key: *const c_char,
value: *mut u8,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(database.deref_mut(), key, value, length, error)
};
get_option_bytes(key, populate, driver)
}
fn get_option_double(&self, key: Self::Option) -> Result<f64> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
let key = CString::new(key.as_ref())?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let mut value: f64 = f64::default();
let method = driver_method!(driver, DatabaseGetOptionDouble);
let status = unsafe { method(database.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_int(&self, key: Self::Option) -> Result<i64> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
let key = CString::new(key.as_ref())?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let mut value: i64 = 0;
let method = driver_method!(driver, DatabaseGetOptionInt);
let status = unsafe { method(database.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_string(&self, key: Self::Option) -> Result<String> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, DatabaseGetOption);
let populate = |key: *const c_char,
value: *mut c_char,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(database.deref_mut(), key, value, length, error)
};
get_option_string(key, populate, driver)
}
fn set_option(&mut self, key: Self::Option, value: OptionValue) -> Result<()> {
let driver = self.ffi_driver();
let mut database = self.inner.database.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] database is poisoned: {e:?}"),
Status::Internal,
)
})?;
set_option_database(
driver,
database.deref_mut(),
self.driver_version(),
key,
value,
)
}
}
impl Database for ManagedDatabase {
type ConnectionType = ManagedConnection;
fn new_connection(&self) -> Result<Self::ConnectionType> {
// Construct a new connection.
let connection = self.connection_new()?;
// Initialize the connection.
let connection = self.connection_init(connection)?;
let inner = ManagedConnectionInner {
connection: Mutex::new(connection),
database: self.inner.clone(),
};
Ok(Self::ConnectionType {
inner: Arc::new(inner),
})
}
fn new_connection_with_opts(
&self,
opts: impl IntoIterator<Item = (<Self::ConnectionType as Optionable>::Option, OptionValue)>,
) -> Result<Self::ConnectionType> {
// Construct a new connection.
let mut connection = self.connection_new()?;
// Set the options.
{
let driver = self.ffi_driver();
for (key, value) in opts {
set_option_connection(driver, &mut connection, self.driver_version(), key, value)?;
}
}
// Initialize the connection.
let connection = self.connection_init(connection)?;
let inner = ManagedConnectionInner {
connection: Mutex::new(connection),
database: self.inner.clone(),
};
Ok(Self::ConnectionType {
inner: Arc::new(inner),
})
}
}
struct ManagedConnectionInner {
connection: Mutex<adbc_ffi::FFI_AdbcConnection>,
database: Arc<ManagedDatabaseInner>,
}
impl Drop for ManagedConnectionInner {
fn drop(&mut self) {
let driver = &self.database.driver.driver;
if let Ok(mut connection) = self.connection.lock() {
let method = driver_method!(driver, ConnectionRelease);
// TODO(alexandreyc): how should we handle `ConnectionRelease` failing?
// See: https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
unsafe { method(connection.deref_mut(), null_mut()) };
}
// We could still drop here but if the lock is poisoned, we have no
// clue what the status is. Since a panic comes from Rust code,
// _probably_ the FFI handle is unharmed. But I think it's safer to
// leak than to try to release.
}
}
/// Implementation of [Connection].
#[derive(Clone)]
pub struct ManagedConnection {
inner: Arc<ManagedConnectionInner>,
}
struct ConnectionCancelHandle {
inner: std::sync::Weak<ManagedConnectionInner>,
}
impl adbc_core::CancelHandle for ConnectionCancelHandle {
fn try_cancel(&self) -> Result<()> {
if let Some(inner) = self.inner.upgrade() {
if let AdbcVersion::V100 = inner.database.driver.version {
return Err(Error::with_message_and_status(
ERR_CANCEL_UNSUPPORTED,
Status::NotImplemented,
));
}
let driver = &inner.database.driver.driver;
let mut connection = inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionCancel);
let status = unsafe { method(connection.deref_mut(), &mut error) };
check_status(status, error)
} else {
Ok(())
}
}
}
impl ManagedConnection {
fn ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
&self.inner.database.driver.driver
}
fn driver_version(&self) -> AdbcVersion {
self.inner.database.driver.version
}
}
impl Optionable for ManagedConnection {
type Option = options::OptionConnection;
fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, ConnectionGetOptionBytes);
let populate = |key: *const c_char,
value: *mut u8,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(connection.deref_mut(), key, value, length, error)
};
get_option_bytes(key, populate, driver)
}
fn get_option_double(&self, key: Self::Option) -> Result<f64> {
let key = CString::new(key.as_ref())?;
let mut value: f64 = f64::default();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetOptionDouble);
let status =
unsafe { method(connection.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_int(&self, key: Self::Option) -> Result<i64> {
let key = CString::new(key.as_ref())?;
let mut value: i64 = 0;
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetOptionInt);
let status =
unsafe { method(connection.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_string(&self, key: Self::Option) -> Result<String> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, ConnectionGetOption);
let populate = |key: *const c_char,
value: *mut c_char,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(connection.deref_mut(), key, value, length, error)
};
get_option_string(key, populate, driver)
}
fn set_option(&mut self, key: Self::Option, value: OptionValue) -> Result<()> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
set_option_connection(
driver,
connection.deref_mut(),
self.driver_version(),
key,
value,
)
}
}
impl Connection for ManagedConnection {
type StatementType = ManagedStatement;
fn new_statement(&mut self) -> Result<Self::StatementType> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut statement = adbc_ffi::FFI_AdbcStatement::default();
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementNew);
let status = unsafe { method(connection.deref_mut(), &mut statement, &mut error) };
check_status(status, error)?;
let inner = Arc::new(ManagedStatementInner {
statement: Mutex::new(statement),
connection: self.inner.clone(),
});
Ok(Self::StatementType { inner })
}
fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
Box::new(ConnectionCancelHandle {
inner: Arc::downgrade(&self.inner),
})
}
fn commit(&mut self) -> Result<()> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionCommit);
let status = unsafe { method(connection.deref_mut(), &mut error) };
check_status(status, error)
}
fn rollback(&mut self) -> Result<()> {
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionRollback);
let status = unsafe { method(connection.deref_mut(), &mut error) };
check_status(status, error)
}
fn get_info(
&self,
codes: Option<HashSet<InfoCode>>,
) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let mut stream = FFI_ArrowArrayStream::empty();
let codes: Option<Vec<u32>> =
codes.map(|codes| codes.iter().map(|code| code.into()).collect());
let (codes_ptr, codes_len) = codes
.as_ref()
.map(|c| (c.as_ptr(), c.len()))
.unwrap_or((null(), 0));
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetInfo);
let status = unsafe {
method(
connection.deref_mut(),
codes_ptr,
codes_len,
&mut stream,
&mut error,
)
};
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn get_objects(
&self,
depth: crate::options::ObjectDepth,
catalog: Option<&str>,
db_schema: Option<&str>,
table_name: Option<&str>,
table_type: Option<Vec<&str>>,
column_name: Option<&str>,
) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let catalog = catalog.map(CString::new).transpose()?;
let db_schema = db_schema.map(CString::new).transpose()?;
let table_name = table_name.map(CString::new).transpose()?;
let column_name = column_name.map(CString::new).transpose()?;
let table_type = table_type
.map(|t| {
t.iter()
.map(|x| CString::new(*x))
.collect::<std::result::Result<Vec<CString>, _>>()
})
.transpose()?;
let catalog_ptr = catalog.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let db_schema_ptr = db_schema.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let table_name_ptr = table_name.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let column_name_ptr = column_name.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let mut table_type_ptrs = table_type
.as_ref()
.map(|v| v.iter().map(|c| c.as_ptr()))
.map(|c| c.collect::<Vec<_>>());
let table_type_ptr = match table_type_ptrs.as_mut() {
None => null(),
Some(t) => {
t.push(null());
t.as_ptr()
}
};
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetObjects);
let mut stream = FFI_ArrowArrayStream::empty();
let status = unsafe {
method(
connection.deref_mut(),
depth.into(),
catalog_ptr,
db_schema_ptr,
table_name_ptr,
table_type_ptr,
column_name_ptr,
&mut stream,
&mut error,
)
};
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn get_statistics(
&self,
catalog: Option<&str>,
db_schema: Option<&str>,
table_name: Option<&str>,
approximate: bool,
) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
if let AdbcVersion::V100 = self.driver_version() {
return Err(Error::with_message_and_status(
ERR_STATISTICS_UNSUPPORTED,
Status::NotImplemented,
));
}
let catalog = catalog.map(CString::new).transpose()?;
let db_schema = db_schema.map(CString::new).transpose()?;
let table_name = table_name.map(CString::new).transpose()?;
let catalog_ptr = catalog.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let db_schema_ptr = db_schema.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let table_name_ptr = table_name.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let mut stream = FFI_ArrowArrayStream::empty();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetStatistics);
let status = unsafe {
method(
connection.deref_mut(),
catalog_ptr,
db_schema_ptr,
table_name_ptr,
approximate as std::os::raw::c_char,
&mut stream,
&mut error,
)
};
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn get_statistic_names(&self) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
if let AdbcVersion::V100 = self.driver_version() {
return Err(Error::with_message_and_status(
ERR_STATISTICS_UNSUPPORTED,
Status::NotImplemented,
));
}
let mut stream = FFI_ArrowArrayStream::empty();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetStatisticNames);
let status = unsafe { method(connection.deref_mut(), &mut stream, &mut error) };
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn get_table_schema(
&self,
catalog: Option<&str>,
db_schema: Option<&str>,
table_name: &str,
) -> Result<arrow_schema::Schema> {
let catalog = catalog.map(CString::new).transpose()?;
let db_schema = db_schema.map(CString::new).transpose()?;
let table_name = CString::new(table_name)?;
let catalog_ptr = catalog.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let db_schema_ptr = db_schema.as_ref().map(|c| c.as_ptr()).unwrap_or(null());
let table_name_ptr = table_name.as_ptr();
let mut schema = FFI_ArrowSchema::empty();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetTableSchema);
let status = unsafe {
method(
connection.deref_mut(),
catalog_ptr,
db_schema_ptr,
table_name_ptr,
&mut schema,
&mut error,
)
};
check_status(status, error)?;
Ok((&schema).try_into()?)
}
fn get_table_types(&self) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let mut stream = FFI_ArrowArrayStream::empty();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionGetTableTypes);
let status = unsafe { method(connection.deref_mut(), &mut stream, &mut error) };
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn read_partition(
&self,
partition: impl AsRef<[u8]>,
) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let mut stream = FFI_ArrowArrayStream::empty();
let driver = self.ffi_driver();
let mut connection = self.inner.connection.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] connection is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, ConnectionReadPartition);
let partition = partition.as_ref();
let status = unsafe {
method(
connection.deref_mut(),
partition.as_ptr(),
partition.len(),
&mut stream,
&mut error,
)
};
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
}
struct ManagedStatementInner {
statement: Mutex<adbc_ffi::FFI_AdbcStatement>,
connection: Arc<ManagedConnectionInner>,
}
/// Implementation of [Statement].
#[derive(Clone)]
pub struct ManagedStatement {
inner: Arc<ManagedStatementInner>,
}
impl ManagedStatement {
fn driver_version(&self) -> AdbcVersion {
self.inner.connection.database.driver.version
}
fn ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
&self.inner.connection.database.driver.driver
}
}
struct StatementCancelHandle {
inner: std::sync::Weak<ManagedStatementInner>,
}
impl adbc_core::CancelHandle for StatementCancelHandle {
fn try_cancel(&self) -> Result<()> {
if let Some(inner) = self.inner.upgrade() {
if let AdbcVersion::V100 = inner.connection.database.driver.version {
return Err(Error::with_message_and_status(
ERR_CANCEL_UNSUPPORTED,
Status::NotImplemented,
));
}
let driver = &inner.connection.database.driver.driver;
let mut statement = inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementCancel);
let status = unsafe { method(statement.deref_mut(), &mut error) };
check_status(status, error)
} else {
Ok(())
}
}
}
impl Statement for ManagedStatement {
fn bind(&mut self, batch: RecordBatch) -> Result<()> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementBind);
let batch: StructArray = batch.into();
let (mut array, mut schema) = to_ffi(&batch.to_data())?;
let status = unsafe { method(statement.deref_mut(), &mut array, &mut schema, &mut error) };
check_status(status, error)?;
Ok(())
}
fn bind_stream(&mut self, reader: Box<dyn RecordBatchReader + Send>) -> Result<()> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementBindStream);
let mut stream = FFI_ArrowArrayStream::new(reader);
let status = unsafe { method(statement.deref_mut(), &mut stream, &mut error) };
check_status(status, error)?;
Ok(())
}
fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
Box::new(StatementCancelHandle {
inner: Arc::downgrade(&self.inner),
})
}
fn execute(&mut self) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementExecuteQuery);
let mut stream = FFI_ArrowArrayStream::empty();
let status = unsafe { method(statement.deref_mut(), &mut stream, null_mut(), &mut error) };
check_status(status, error)?;
let reader = ArrowArrayStreamReader::try_new(stream)?;
Ok(Box::new(reader))
}
fn execute_schema(&mut self) -> Result<arrow_schema::Schema> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementExecuteSchema);
let mut schema = FFI_ArrowSchema::empty();
let status = unsafe { method(statement.deref_mut(), &mut schema, &mut error) };
check_status(status, error)?;
Ok((&schema).try_into()?)
}
fn execute_update(&mut self) -> Result<Option<i64>> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementExecuteQuery);
let mut rows_affected: i64 = -1;
let status = unsafe {
method(
statement.deref_mut(),
null_mut(),
&mut rows_affected,
&mut error,
)
};
check_status(status, error)?;
Ok((rows_affected != -1).then_some(rows_affected))
}
fn execute_partitions(&mut self) -> Result<PartitionedResult> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementExecutePartitions);
let mut schema = FFI_ArrowSchema::empty();
let mut partitions = adbc_ffi::FFI_AdbcPartitions::default();
let mut rows_affected: i64 = -1;
let status = unsafe {
method(
statement.deref_mut(),
&mut schema,
&mut partitions,
&mut rows_affected,
&mut error,
)
};
check_status(status, error)?;
let result = PartitionedResult {
partitions: partitions.into(),
schema: (&schema).try_into()?,
rows_affected,
};
Ok(result)
}
fn get_parameter_schema(&self) -> Result<arrow_schema::Schema> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementGetParameterSchema);
let mut schema = FFI_ArrowSchema::empty();
let status = unsafe { method(statement.deref_mut(), &mut schema, &mut error) };
check_status(status, error)?;
Ok((&schema).try_into()?)
}
fn prepare(&mut self) -> Result<()> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementPrepare);
let status = unsafe { method(statement.deref_mut(), &mut error) };
check_status(status, error)?;
Ok(())
}
fn set_sql_query(&mut self, query: impl AsRef<str>) -> Result<()> {
let query = CString::new(query.as_ref())?;
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementSetSqlQuery);
let status = unsafe { method(statement.deref_mut(), query.as_ptr(), &mut error) };
check_status(status, error)?;
Ok(())
}
fn set_substrait_plan(&mut self, plan: impl AsRef<[u8]>) -> Result<()> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementSetSubstraitPlan);
let plan = plan.as_ref();
let status =
unsafe { method(statement.deref_mut(), plan.as_ptr(), plan.len(), &mut error) };
check_status(status, error)?;
Ok(())
}
}
impl Optionable for ManagedStatement {
type Option = options::OptionStatement;
fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, StatementGetOptionBytes);
let populate = |key: *const c_char,
value: *mut u8,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(statement.deref_mut(), key, value, length, error)
};
get_option_bytes(key, populate, driver)
}
fn get_option_double(&self, key: Self::Option) -> Result<f64> {
let key = CString::new(key.as_ref())?;
let mut value: f64 = f64::default();
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementGetOptionDouble);
let status = unsafe { method(statement.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_int(&self, key: Self::Option) -> Result<i64> {
let key = CString::new(key.as_ref())?;
let mut value: i64 = 0;
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
let method = driver_method!(driver, StatementGetOptionInt);
let status = unsafe { method(statement.deref_mut(), key.as_ptr(), &mut value, &mut error) };
check_status(status, error)?;
Ok(value)
}
fn get_option_string(&self, key: Self::Option) -> Result<String> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
let method = driver_method!(driver, StatementGetOption);
let populate = |key: *const c_char,
value: *mut c_char,
length: *mut usize,
error: *mut adbc_ffi::FFI_AdbcError| unsafe {
method(statement.deref_mut(), key, value, length, error)
};
get_option_string(key, populate, driver)
}
fn set_option(&mut self, key: Self::Option, value: OptionValue) -> Result<()> {
let driver = self.ffi_driver();
let mut statement = self.inner.statement.lock().map_err(|e| {
Error::with_message_and_status(
format!("[Driver Manager] statement is poisoned: {e:?}"),
Status::Internal,
)
})?;
set_option_statement(
driver,
statement.deref_mut(),
self.driver_version(),
key,
value,
)
}
}
impl Drop for ManagedStatement {
fn drop(&mut self) {
let driver = self.ffi_driver();
if let Ok(mut statement) = self.inner.statement.lock() {
let method = driver_method!(driver, StatementRelease);
// TODO(alexandreyc): how should we handle `StatementRelease` failing?
// See: https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
unsafe { method(statement.deref_mut(), null_mut()) };
}
// We could still drop here but if the lock is poisoned, we have no
// clue what the status is. Since a panic comes from Rust code,
// _probably_ the FFI handle is unharmed. But I think it's safer to
// leak than to try to release.
}
}