use anyhow::{Context, Result};
pub type Connection = duckdb::Connection;
pub type Statement<'c> = duckdb::Statement<'c>;
pub type Rows<'s> = duckdb::Rows<'s>;
pub type Row<'s> = duckdb::Row<'s>;
pub use duckdb::types::{Decimal, FromSql, ListType, TimeUnit, ToSql, Value, ValueRef};
pub use duckdb::arrow;
use arrow::array::StructArray;
use arrow::datatypes::SchemaRef;
use arrow::record_batch::RecordBatch;
pub fn open(path: impl AsRef<std::path::Path>) -> Result<Connection> {
Ok(Connection::open(path)?)
}
pub fn open_readonly(path: impl AsRef<std::path::Path>) -> Result<Connection> {
let cfg = duckdb::Config::default().access_mode(duckdb::AccessMode::ReadOnly)?;
Ok(Connection::open_with_flags(path, cfg)?)
}
pub fn open_in_memory() -> Result<Connection> {
Ok(Connection::open_in_memory()?)
}
pub fn try_clone(con: &Connection) -> Result<Connection> {
Ok(con.try_clone()?)
}
pub fn exec(con: &Connection, sql: &str) -> Result<()> {
con.execute_batch(sql)?;
Ok(())
}
pub fn execute(con: &Connection, sql: &str) -> Result<usize> {
Ok(con.execute(sql, [])?)
}
pub fn set_setting(con: &Connection, name: &str, value: &str) -> Result<()> {
exec(con, &format!("SET {name}={value}"))
}
pub fn query_scalar<T: FromSql>(con: &Connection, sql: &str) -> Result<T> {
Ok(con.query_row(sql, [], |r| r.get::<_, T>(0))?)
}
pub fn query_one<T>(con: &Connection, sql: &str, f: impl FnOnce(&Row<'_>) -> Result<T>) -> Result<T> {
let mut st = con.prepare(sql)?;
let mut rows = st.query([])?;
match rows.next()? {
Some(r) => f(r),
None => Err(duckdb::Error::QueryReturnedNoRows.into()),
}
}
pub fn query_rows<T>(con: &Connection, sql: &str, mut f: impl FnMut(&Row<'_>) -> Result<T>) -> Result<Vec<T>> {
let mut st = con.prepare(sql)?;
let mut rows = st.query([])?;
let mut out = Vec::new();
while let Some(r) = rows.next()? { out.push(f(r)?); }
Ok(out)
}
pub fn query_rows_with<T>(con: &Connection, sql: &str, params: &[&dyn ToSql], mut f: impl FnMut(&Row<'_>) -> Result<T>) -> Result<Vec<T>> {
let mut st = con.prepare(sql)?;
let mut rows = st.query(params)?;
let mut out = Vec::new();
while let Some(r) = rows.next()? { out.push(f(r)?); }
Ok(out)
}
pub fn for_each_row(con: &Connection, sql: &str, mut f: impl FnMut(&Row<'_>) -> Result<()>) -> Result<()> {
let mut st = con.prepare(sql)?;
let mut rows = st.query([])?;
while let Some(r) = rows.next()? { f(r)?; }
Ok(())
}
pub fn list_of_strings(r: &Row<'_>, i: usize) -> Vec<String> {
match r.get_ref(i) {
Ok(ValueRef::List(list, idx)) => {
use arrow::array::Array;
let arr: std::sync::Arc<dyn Array> = match list { ListType::Regular(a) => a.value(idx), ListType::Large(a) => a.value(idx) };
let sa = arr.as_any().downcast_ref::<arrow::array::StringArray>();
match sa { Some(sa) => (0..sa.len()).filter(|&j| !sa.is_null(j)).map(|j| sa.value(j).to_string()).collect(), None => vec![] }
}
_ => vec![],
}
}
pub struct Appender<'c>(duckdb::Appender<'c>);
pub fn appender<'c>(con: &'c Connection, schema: &str, table: &str) -> Result<Appender<'c>> {
Ok(Appender(con.appender_to_db(table, schema)?))
}
impl Appender<'_> {
pub fn append_row(&mut self, row: Vec<Value>) -> Result<()> {
let outs: Vec<duckdb::types::ToSqlOutput<'static>> = row.into_iter().map(duckdb::types::ToSqlOutput::Owned).collect();
let refs: Vec<&dyn ToSql> = outs.iter().map(|o| o as &dyn ToSql).collect();
self.0.append_row(refs.as_slice())?;
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
self.0.flush()?;
Ok(())
}
}
pub struct ArrowStream<'c> {
stmt: Statement<'c>,
schema: SchemaRef,
}
pub fn stream<'c>(con: &'c Connection, sql: &str) -> Result<ArrowStream<'c>> {
let mut stmt = con.prepare(sql).with_context(|| format!("preparing {}", sql.chars().take(300).collect::<String>()))?;
let schema = stmt.stream_arrow([]).with_context(|| format!("running {}", sql.chars().take(300).collect::<String>()))?.get_schema();
Ok(ArrowStream { stmt, schema })
}
impl<'c> ArrowStream<'c> {
pub fn schema(&self) -> &SchemaRef { &self.schema }
pub fn next_chunk(&mut self) -> Result<Option<StructArray>> {
Ok(self.stmt.step()?)
}
pub fn rows(&mut self) -> Rows<'_> {
self.stmt.raw_query()
}
}
pub fn arrow_batches<'c>(con: &'c Connection, sql: &str) -> Result<impl Iterator<Item = Result<RecordBatch>> + 'c> {
let mut s = stream(con, sql)?;
Ok(std::iter::from_fn(move || match s.next_chunk() {
Ok(Some(chunk)) => {
let (fields, columns, _) = chunk.into_parts();
Some(RecordBatch::try_new(std::sync::Arc::new(arrow::datatypes::Schema::new(fields)), columns).map_err(anyhow::Error::from))
}
Ok(None) => None,
Err(e) => Some(Err(e)),
}))
}