Skip to main content

hinge/infrastructure/duckdb/
executor.rs

1use duckdb::Connection;
2use std::sync::{Arc, Mutex};
3
4// ── Error ─────────────────────────────────────────────────────────────────────
5
6#[derive(Debug, thiserror::Error)]
7pub enum DuckDbConnectionError {
8    #[error("invalid DuckDB URL: {0}")]
9    InvalidUrl(String),
10    #[error("failed to open DuckDB database: {0}")]
11    Open(#[from] duckdb::Error),
12}
13
14// ── Executor ──────────────────────────────────────────────────────────────────
15
16/// Executes SQL assets against a DuckDB database.
17///
18/// DuckDB is an embedded, in-process analytical database — no server required.
19/// Ideal for local development, testing, and file-based analytics pipelines.
20///
21/// Asset execution is dispatched to a blocking thread via
22/// `tokio::task::spawn_blocking` since DuckDB's Rust binding is synchronous.
23///
24/// See [`DuckDbExecutor::from_url`] for URL format details.
25pub struct DuckDbExecutor {
26    conn: Arc<Mutex<Connection>>,
27}
28
29impl DuckDbExecutor {
30    /// Build an executor from a connection URL.
31    ///
32    /// Supported formats:
33    /// ```text
34    /// duckdb://:memory:           in-memory database (discarded on drop)
35    /// duckdb:///absolute/path.db  persistent database at an absolute path
36    /// duckdb://./relative.db      persistent database relative to the CWD
37    /// ```
38    pub fn from_url(url: &str) -> Result<Self, DuckDbConnectionError> {
39        let path = url
40            .strip_prefix("duckdb://")
41            .ok_or_else(|| DuckDbConnectionError::InvalidUrl(
42                format!("expected duckdb:// prefix, got '{url}'"),
43            ))?;
44
45        let conn = match path {
46            "" | ":memory:" => Connection::open_in_memory()?,
47            p => Connection::open(p)?,
48        };
49
50        Ok(Self { conn: Arc::new(Mutex::new(conn)) })
51    }
52
53    pub(super) fn conn(&self) -> Arc<Mutex<Connection>> {
54        Arc::clone(&self.conn)
55    }
56}