blob: 032a5e34750da87a3b817b3cbd93e237ec184bf6 [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.
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::mpsc;
use adbc_core::{
Connection, Database, Driver, LOAD_FLAG_DEFAULT, Optionable, Statement,
error::{Error as AdbcError, Status},
options::{
AdbcVersion, InfoCode, ObjectDepth, OptionConnection, OptionDatabase, OptionStatement,
OptionValue,
},
};
use adbc_driver_manager::profile::{
ConnectionProfile, ConnectionProfileProvider, FilesystemProfileProvider,
};
use adbc_driver_manager::{ManagedConnection, ManagedDatabase, ManagedDriver, ManagedStatement};
use arrow_array::RecordBatchReader;
use arrow_ipc::reader::StreamReader;
use arrow_ipc::writer::StreamWriter;
use arrow_schema::SchemaRef;
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("ADBC Error: {0}")]
Adbc(#[from] adbc_core::error::Error),
#[error("Arrow Error: {0}")]
Arrow(#[from] arrow_schema::ArrowError),
#[error("Other Error: {0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, ClientError>;
pub struct ConnectOptions {
pub driver: Option<String>,
pub entrypoint: Option<String>,
pub manifest_search_paths: Option<Vec<String>>,
pub profile_search_paths: Option<Vec<String>>,
pub load_flags: Option<u32>,
pub database_options: Option<HashMap<String, String>>,
}
pub struct GetObjectsOptions {
pub depth: i32,
pub catalog: Option<String>,
pub db_schema: Option<String>,
pub table_name: Option<String>,
pub table_type: Option<Vec<String>>,
pub column_name: Option<String>,
}
pub struct GetTableSchemaOptions {
pub catalog: Option<String>,
pub db_schema: Option<String>,
pub table_name: String,
}
pub struct AdbcDatabaseCore {
inner: ManagedDatabase,
}
impl AdbcDatabaseCore {
pub fn new(opts: ConnectOptions) -> Result<Self> {
let version = AdbcVersion::V110;
let load_flags = opts.load_flags.unwrap_or(LOAD_FLAG_DEFAULT);
let entrypoint = opts.entrypoint.as_ref().map(|s| s.as_bytes().to_vec());
let manifest_search_paths: Option<Vec<PathBuf>> = opts
.manifest_search_paths
.map(|paths| paths.into_iter().map(PathBuf::from).collect());
let profile_search_paths: Option<Vec<PathBuf>> = opts
.profile_search_paths
.map(|paths| paths.into_iter().map(PathBuf::from).collect());
let mut raw_opts = opts.database_options.unwrap_or_default();
let profile_uri: Option<String> = if let Some(profile) = raw_opts.remove("profile") {
if raw_opts
.get("uri")
.is_some_and(|u| u.starts_with("profile://"))
{
return Err(ClientError::Other(
"multiple profile sources: databaseOptions.profile and a profile:// URI in databaseOptions.uri are mutually exclusive"
.to_string(),
));
}
Some(format!("profile://{profile}"))
} else if raw_opts
.get("uri")
.is_some_and(|u| u.starts_with("profile://"))
{
Some(raw_opts.remove("uri").unwrap())
} else {
None
};
let database = if let Some(ref uri) = profile_uri {
let provider = FilesystemProfileProvider::new_with_search_paths(profile_search_paths);
// If driver is also specified, validate it agrees with the profile's driver.
// The C driver manager errors on disagreement; we replicate that here until
// the Rust driver manager gains native support for this validation.
if let Some(ref driver) = opts.driver {
let profile_name = uri.trim_start_matches("profile://");
let profile = provider.clone().get_profile(profile_name)?;
let (profile_driver, _) = profile.get_driver_name()?;
if !driver.is_empty() && driver != profile_driver {
return Err(ClientError::Adbc(AdbcError::with_message_and_status(
format!(
"profile specifies driver `{profile_driver}` which does not match requested driver `{driver}`"
),
Status::InvalidArguments,
)));
}
}
ManagedDatabase::from_uri_with_profile_provider(
uri,
entrypoint.as_deref(),
version,
load_flags,
manifest_search_paths,
provider,
map_database_options(raw_opts),
)?
} else {
match opts.driver {
Some(ref driver) if driver.contains(':') => {
let provider =
adbc_driver_manager::profile::FilesystemProfileProvider::new_with_search_paths(
profile_search_paths,
);
ManagedDatabase::from_uri_with_profile_provider(
driver,
entrypoint.as_deref(),
version,
load_flags,
manifest_search_paths,
provider,
map_database_options(raw_opts),
)?
}
Some(ref driver) => {
let mut drv = ManagedDriver::load_from_name(
driver,
entrypoint.as_deref(),
version,
load_flags,
manifest_search_paths,
)?;
drv.new_database_with_opts(map_database_options(raw_opts))?
}
None => {
let Some(uri) = raw_opts.remove("uri") else {
return Err(ClientError::Other(
"driver is required unless databaseOptions.uri or databaseOptions.profile is provided"
.to_string(),
));
};
let provider =
adbc_driver_manager::profile::FilesystemProfileProvider::new_with_search_paths(
profile_search_paths,
);
ManagedDatabase::from_uri_with_profile_provider(
&uri,
entrypoint.as_deref(),
version,
load_flags,
manifest_search_paths,
provider,
map_database_options(raw_opts),
)?
}
}
};
Ok(Self { inner: database })
}
pub fn connect(&self, options: Option<HashMap<String, String>>) -> Result<AdbcConnectionCore> {
let conn = if let Some(opts) = options {
self
.inner
.new_connection_with_opts(map_connection_options(opts))?
} else {
self.inner.new_connection()?
};
Ok(AdbcConnectionCore {
inner: Mutex::new(conn),
})
}
}
pub struct AdbcConnectionCore {
inner: Mutex<ManagedConnection>,
}
impl AdbcConnectionCore {
pub fn new_statement(&self) -> Result<AdbcStatementCore> {
let mut conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
let stmt = conn.new_statement()?;
Ok(AdbcStatementCore { inner: stmt })
}
pub fn set_option(&self, key: &str, value: &str) -> Result<()> {
let mut conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
conn.set_option(
OptionConnection::Other(key.to_string()),
OptionValue::String(value.to_string()),
)?;
Ok(())
}
pub fn get_objects(&self, opts: GetObjectsOptions) -> Result<AdbcResultIteratorCore> {
let conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
let depth = match opts.depth {
1 => ObjectDepth::Catalogs,
2 => ObjectDepth::Schemas,
3 => ObjectDepth::Tables,
_ => ObjectDepth::All,
};
let table_types_str: Option<Vec<&str>> = opts
.table_type
.as_ref()
.map(|v| v.iter().map(|s| s.as_str()).collect());
let reader = conn.get_objects(
depth,
opts.catalog.as_deref(),
opts.db_schema.as_deref(),
opts.table_name.as_deref(),
table_types_str,
opts.column_name.as_deref(),
)?;
Ok(AdbcResultIteratorCore {
reader,
exhausted: false,
})
}
pub fn get_table_schema(&self, opts: GetTableSchemaOptions) -> Result<Vec<u8>> {
let conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
let schema = conn.get_table_schema(
opts.catalog.as_deref(),
opts.db_schema.as_deref(),
&opts.table_name,
)?;
let mut output = Vec::new();
let mut writer = StreamWriter::try_new(&mut output, &schema)?;
writer.finish()?;
Ok(output)
}
pub fn get_table_types(&self) -> Result<AdbcResultIteratorCore> {
let conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
let reader = conn.get_table_types()?;
Ok(AdbcResultIteratorCore {
reader,
exhausted: false,
})
}
pub fn get_info(&self, info_codes: Option<Vec<u32>>) -> Result<AdbcResultIteratorCore> {
let conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
let codes: Option<HashSet<InfoCode>> = info_codes.map(|v| {
v.into_iter()
.map(InfoCode::from)
.collect::<HashSet<InfoCode>>()
});
let reader = conn.get_info(codes)?;
Ok(AdbcResultIteratorCore {
reader,
exhausted: false,
})
}
pub fn commit(&self) -> Result<()> {
let mut conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
conn.commit()?;
Ok(())
}
pub fn rollback(&self) -> Result<()> {
let mut conn = self
.inner
.lock()
.map_err(|e| ClientError::Other(e.to_string()))?;
conn.rollback()?;
Ok(())
}
}
pub struct AdbcStatementCore {
inner: ManagedStatement,
}
impl AdbcStatementCore {
pub fn set_sql_query(&mut self, query: &str) -> Result<()> {
self.inner.set_sql_query(query)?;
Ok(())
}
pub fn set_option(&mut self, key: &str, value: &str) -> Result<()> {
self.inner.set_option(
OptionStatement::Other(key.to_string()),
OptionValue::String(value.to_string()),
)?;
Ok(())
}
pub fn execute_query(&mut self) -> Result<AdbcResultIteratorCore> {
let reader = self.inner.execute()?;
Ok(AdbcResultIteratorCore {
reader,
exhausted: false,
})
}
pub fn execute_update(&mut self) -> Result<i64> {
let rows = self.inner.execute_update()?;
Ok(rows.unwrap_or(-1))
}
pub fn bind(&mut self, c_data: Vec<u8>) -> Result<()> {
let reader =
StreamReader::try_new(std::io::Cursor::new(c_data), None).map_err(ClientError::Arrow)?;
self.inner.bind_stream(Box::new(reader))?;
Ok(())
}
pub fn bind_channel_stream(
&mut self,
schema_bytes: Vec<u8>,
receiver: mpsc::Receiver<Vec<u8>>,
) -> Result<()> {
let reader = ChannelBatchReader::new(schema_bytes, receiver)?;
self.inner.bind_stream(Box::new(reader))?;
Ok(())
}
pub fn bind_stream_and_execute(
&mut self,
schema_bytes: Vec<u8>,
receiver: mpsc::Receiver<Vec<u8>>,
) -> Result<i64> {
self.bind_channel_stream(schema_bytes, receiver)?;
let rows = self.inner.execute_update()?;
Ok(rows.unwrap_or(-1))
}
}
pub struct AdbcResultIteratorCore {
reader: Box<dyn RecordBatchReader + Send>,
exhausted: bool,
}
impl AdbcResultIteratorCore {
pub fn next(&mut self) -> Result<Option<Vec<u8>>> {
if self.exhausted {
return Ok(None);
}
self.exhausted = true;
let schema = self.reader.schema();
let mut output = Vec::new();
let mut writer = StreamWriter::try_new(&mut output, &schema)?;
for batch in self.reader.by_ref() {
writer.write(&batch?)?;
}
writer.finish()?;
Ok(Some(output))
}
}
/// A `RecordBatchReader` backed by a channel. Batches arrive as IPC bytes
/// from the JS main thread and are deserialized on demand by the thread pool.
pub struct ChannelBatchReader {
schema: SchemaRef,
receiver: mpsc::Receiver<Vec<u8>>,
}
impl ChannelBatchReader {
pub fn new(schema_bytes: Vec<u8>, receiver: mpsc::Receiver<Vec<u8>>) -> Result<Self> {
let ipc_reader = StreamReader::try_new(std::io::Cursor::new(schema_bytes), None)
.map_err(ClientError::Arrow)?;
let schema = ipc_reader.schema();
Ok(Self { schema, receiver })
}
}
impl Iterator for ChannelBatchReader {
type Item = std::result::Result<arrow_array::RecordBatch, arrow_schema::ArrowError>;
fn next(&mut self) -> Option<Self::Item> {
let bytes = self.receiver.recv().ok()?;
let mut ipc_reader = match StreamReader::try_new(std::io::Cursor::new(bytes), None) {
Ok(r) => r,
Err(e) => return Some(Err(e)),
};
match ipc_reader.next() {
Some(result) => Some(result),
None => Some(Err(arrow_schema::ArrowError::IpcError(
"Received IPC stream with no record batches".to_string(),
))),
}
}
}
impl RecordBatchReader for ChannelBatchReader {
fn schema(&self) -> SchemaRef {
self.schema.clone()
}
}
fn map_database_options(
opts: HashMap<String, String>,
) -> impl Iterator<Item = (OptionDatabase, OptionValue)> {
opts.into_iter().map(|(k, v)| {
let key = match k.as_str() {
"uri" => OptionDatabase::Uri,
"user" => OptionDatabase::Username,
"password" => OptionDatabase::Password,
other => OptionDatabase::Other(other.to_string()),
};
(key, OptionValue::String(v))
})
}
fn map_connection_options(
opts: HashMap<String, String>,
) -> impl Iterator<Item = (OptionConnection, OptionValue)> {
opts
.into_iter()
.map(|(k, v)| (OptionConnection::Other(k), OptionValue::String(v)))
}