dbkit/read/mod.rs
1//! Engine-agnostic analytical read layer.
2//!
3//! The contract is Apache Arrow: every [`ReadEngine`] returns its results as
4//! `Vec<RecordBatch>`. This lets the analytical engine be swapped (DuckDB or
5//! DataFusion) without changing the public surface, and both may be enabled in
6//! the same build.
7//!
8//! [`RecordBatch`] comes from [`crate::analytical`], which depends on `arrow`
9//! directly. Because the engines are on the same arrow major, cargo unifies
10//! everything onto that one crate, so an engine's batches are exactly this type.
11
12use crate::DbkitError;
13use crate::analytical::RecordBatch;
14use crate::value::DbValue;
15use async_trait::async_trait;
16
17#[cfg(feature = "duckdb")]
18pub mod duckdb;
19#[cfg(feature = "datafusion")]
20pub mod datafusion;
21
22mod convert;
23pub(crate) use convert::rows_to_record_batch;
24
25/// An analytical query engine that returns columnar Arrow data.
26#[async_trait]
27pub trait ReadEngine: Send + Sync {
28 /// Run an analytical query and collect the result as Arrow batches.
29 ///
30 /// Parameter support is engine-dependent: DuckDB binds `params`; DataFusion
31 /// does not yet and returns an error if any are supplied.
32 async fn query_arrow(
33 &self,
34 sql: &str,
35 params: &[DbValue],
36 ) -> Result<Vec<RecordBatch>, DbkitError>;
37
38 /// Materialize a named in-memory table from Arrow batches, replacing any
39 /// existing table of the same name. An empty `batches` is a no-op.
40 async fn load_table(
41 &self,
42 name: &str,
43 batches: Vec<RecordBatch>,
44 ) -> Result<(), DbkitError>;
45
46 /// Drop the named in-memory table if it exists. Dropping a table that was
47 /// never loaded is a no-op, not an error.
48 async fn drop_table(&self, name: &str) -> Result<(), DbkitError>;
49}
50