use std::ffi::{c_char, CString};
#[cfg(all(feature = "arrow", direct_arrow_insert))]
use crate::arrow_options::InsertOptions;
#[cfg(feature = "arrow")]
use crate::arrow_stream::{ArrowArray, ArrowSchema, ArrowStream};
use crate::error::{Error, Result};
use crate::format::OutputFormat;
use crate::query_result::QueryResult;
use crate::{bindings, CHDB_PROGRAM_NAME};
#[derive(Debug)]
pub struct Connection {
inner: *mut bindings::chdb_connection,
}
unsafe impl Send for Connection {}
impl Connection {
pub fn open(args: &[&str]) -> Result<Self> {
let c_args: Vec<CString> = std::iter::once(CHDB_PROGRAM_NAME)
.chain(args.iter().copied())
.map(CString::new)
.collect::<std::result::Result<_, _>>()?;
let argv: Vec<*const c_char> = c_args.iter().map(|s| s.as_ptr()).collect();
let conn_ptr =
unsafe { bindings::chdb_connect(argv.len() as i32, argv.as_ptr() as *mut *mut c_char) };
if conn_ptr.is_null() {
return Err(Error::ConnectionFailed);
}
let conn = unsafe { *conn_ptr };
if conn.is_null() {
return Err(Error::ConnectionFailed);
}
Ok(Self { inner: conn_ptr })
}
pub fn open_in_memory() -> Result<Self> {
Self::open(&[])
}
#[deprecated(note = "Use `SessionBuilder` instead")]
pub fn open_with_path(path: &str) -> Result<Self> {
let path_arg = format!("--path={path}");
Self::open(&[&path_arg])
}
pub fn query(&self, sql: &str, format: OutputFormat) -> Result<QueryResult> {
let query_cstr = CString::new(sql)?;
let format_cstr = CString::new(format.as_str())?;
let conn = unsafe { *self.inner };
let result_ptr =
unsafe { bindings::chdb_query(conn, query_cstr.as_ptr(), format_cstr.as_ptr()) };
if result_ptr.is_null() {
return Err(Error::NoResult);
}
let result = QueryResult::new(result_ptr);
result.check_error()
}
#[cfg(feature = "arrow")]
pub fn register_arrow_stream(
&self,
table_name: &str,
arrow_stream: &ArrowStream,
) -> Result<()> {
let table_name_cstr = CString::new(table_name)?;
let conn = unsafe { *self.inner };
let state = unsafe {
bindings::chdb_arrow_scan(conn, table_name_cstr.as_ptr(), arrow_stream.as_raw())
};
if state == bindings::chdb_state_CHDBSuccess {
Ok(())
} else {
Err(Error::QueryError(format!(
"Failed to register Arrow stream as table '{}'",
table_name
)))
}
}
#[cfg(feature = "arrow")]
pub fn register_arrow_array(
&self,
table_name: &str,
arrow_schema: &ArrowSchema,
arrow_array: &ArrowArray,
) -> Result<()> {
let table_name_cstr = CString::new(table_name)?;
let conn = unsafe { *self.inner };
let state = unsafe {
bindings::chdb_arrow_array_scan(
conn,
table_name_cstr.as_ptr(),
arrow_schema.as_raw(),
arrow_array.as_raw(),
)
};
if state == bindings::chdb_state_CHDBSuccess {
Ok(())
} else {
Err(Error::QueryError(format!(
"Failed to register Arrow array as table '{}'",
table_name
)))
}
}
#[cfg(feature = "arrow")]
pub fn unregister_arrow_table(&self, table_name: &str) -> Result<()> {
let table_name_cstr = CString::new(table_name)?;
let conn = unsafe { *self.inner };
let state =
unsafe { bindings::chdb_arrow_unregister_table(conn, table_name_cstr.as_ptr()) };
if state == bindings::chdb_state_CHDBSuccess {
Ok(())
} else {
Err(Error::QueryError(format!(
"Failed to unregister Arrow table '{}'",
table_name
)))
}
}
#[cfg(all(feature = "arrow", direct_arrow_insert))]
pub fn insert_arrow_array(
&self,
dest_table: &str,
arrow_schema: &ArrowSchema,
arrow_array: &ArrowArray,
options: &InsertOptions,
) -> Result<()> {
let dest_cstr = CString::new(dest_table)?;
let (options_ptr, _settings) = build_insert_options_c(options)?;
let conn = unsafe { *self.inner };
let result_ptr = unsafe {
bindings::chdb_insert_arrow_array(
conn,
dest_cstr.as_ptr(),
arrow_schema.as_raw(),
arrow_array.as_raw(),
options_ptr,
)
};
check_insert_result(result_ptr)
}
#[cfg(all(feature = "arrow", direct_arrow_insert))]
pub fn insert_arrow_stream(
&self,
dest_table: &str,
arrow_stream: &ArrowStream,
options: &InsertOptions,
) -> Result<()> {
let dest_cstr = CString::new(dest_table)?;
let (options_ptr, _settings) = build_insert_options_c(options)?;
let conn = unsafe { *self.inner };
let result_ptr = unsafe {
bindings::chdb_insert_arrow_stream(
conn,
dest_cstr.as_ptr(),
arrow_stream.as_raw(),
options_ptr,
)
};
check_insert_result(result_ptr)
}
}
#[cfg(all(feature = "arrow", direct_arrow_insert))]
fn build_insert_options_c(
options: &InsertOptions,
) -> Result<(*const bindings::chdb_arrow_insert_options, Option<CString>)> {
let settings_cstr = options.settings_clause().map(CString::new).transpose()?;
let c_options = settings_cstr
.as_ref()
.map(|settings| bindings::chdb_arrow_insert_options {
settings: settings.as_ptr(),
});
let options_ptr = c_options
.as_ref()
.map(|opts| opts as *const bindings::chdb_arrow_insert_options)
.unwrap_or(std::ptr::null());
Ok((options_ptr, settings_cstr))
}
#[cfg(all(feature = "arrow", direct_arrow_insert))]
fn check_insert_result(result_ptr: *mut bindings::chdb_result) -> Result<()> {
if result_ptr.is_null() {
return Err(Error::NoResult);
}
let result = QueryResult::new(result_ptr);
result.check_error().map(|_| ())
}
impl Drop for Connection {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { bindings::chdb_close_conn(self.inner) };
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{error::Result, test_utils::tempdir};
#[test]
fn test_connection_open_with_explicit_data_path() -> Result<()> {
let tmp = tempdir();
let path_arg = format!(
"--path={}",
tmp.path().to_str().expect("temp path is not valid UTF-8")
);
Connection::open(&[&path_arg])?;
assert!(
tmp.path().read_dir()?.next().is_some(),
"expected chDB to create files in the data dir"
);
Ok(())
}
}