hinge 0.1.0

SQL-native ELT engine — dependency graph resolved automatically from FROM/JOIN clauses, parallel execution, single binary
Documentation
use duckdb::Connection;
use std::sync::{Arc, Mutex};

// ── Error ─────────────────────────────────────────────────────────────────────

#[derive(Debug, thiserror::Error)]
pub enum DuckDbConnectionError {
    #[error("invalid DuckDB URL: {0}")]
    InvalidUrl(String),
    #[error("failed to open DuckDB database: {0}")]
    Open(#[from] duckdb::Error),
}

// ── Executor ──────────────────────────────────────────────────────────────────

/// Executes SQL assets against a DuckDB database.
///
/// DuckDB is an embedded, in-process analytical database — no server required.
/// Ideal for local development, testing, and file-based analytics pipelines.
///
/// Asset execution is dispatched to a blocking thread via
/// `tokio::task::spawn_blocking` since DuckDB's Rust binding is synchronous.
///
/// See [`DuckDbExecutor::from_url`] for URL format details.
pub struct DuckDbExecutor {
    conn: Arc<Mutex<Connection>>,
}

impl DuckDbExecutor {
    /// Build an executor from a connection URL.
    ///
    /// Supported formats:
    /// ```text
    /// duckdb://:memory:           in-memory database (discarded on drop)
    /// duckdb:///absolute/path.db  persistent database at an absolute path
    /// duckdb://./relative.db      persistent database relative to the CWD
    /// ```
    pub fn from_url(url: &str) -> Result<Self, DuckDbConnectionError> {
        let path = url
            .strip_prefix("duckdb://")
            .ok_or_else(|| DuckDbConnectionError::InvalidUrl(
                format!("expected duckdb:// prefix, got '{url}'"),
            ))?;

        let conn = match path {
            "" | ":memory:" => Connection::open_in_memory()?,
            p => Connection::open(p)?,
        };

        Ok(Self { conn: Arc::new(Mutex::new(conn)) })
    }

    pub(super) fn conn(&self) -> Arc<Mutex<Connection>> {
        Arc::clone(&self.conn)
    }
}