Skip to main content

acorn/io/database/
backend.rs

1//! Database backend abstraction layer.
2//!
3//! Conditionally re-exports types from either `rusqlite` (default) or `duckdb`
4//! based on enabled feature flags.
5use crate::io::ApiResult;
6use crate::prelude::{env, String};
7use crate::util::constants::env::DATABASE_BACKEND;
8use color_eyre::eyre::eyre;
9#[cfg(feature = "duckdb")]
10pub use duckdb::{params, params_from_iter, Connection, Error, Params, ParamsFromIter, Row as BackendRow, ToSql};
11use jiff::Timestamp;
12#[cfg(not(feature = "duckdb"))]
13pub use rusqlite::{params, params_from_iter, Connection, Error, Params, ParamsFromIter, Row as BackendRow, ToSql};
14
15/// SQLite backend value.
16pub const BACKEND_SQLITE: &str = "sqlite";
17/// DuckDB backend value.
18pub const BACKEND_DUCKDB: &str = "duckdb";
19/// Additional parsing operations for database backend rows.
20pub trait BackendRowExt {
21    /// Parse an RFC 3339 timestamp from the column at `index`.
22    fn parse_rfc3339(&self, index: usize) -> Option<Timestamp>;
23}
24impl BackendRowExt for BackendRow<'_> {
25    fn parse_rfc3339(&self, index: usize) -> Option<Timestamp> {
26        self.get::<_, String>(index).ok().and_then(|value| value.parse().ok())
27    }
28}
29/// Returns the backend compiled into this binary.
30pub fn backend() -> &'static str {
31    #[cfg(feature = "duckdb")]
32    {
33        BACKEND_DUCKDB
34    }
35    #[cfg(not(feature = "duckdb"))]
36    {
37        BACKEND_SQLITE
38    }
39}
40/// Returns the backend selected from environment, defaulting to compiled backend.
41pub fn selected_backend() -> String {
42    env::var(DATABASE_BACKEND)
43        .map(|value| value.trim().to_ascii_lowercase())
44        .ok()
45        .filter(|value| !value.is_empty())
46        .unwrap_or_else(|| backend().to_string())
47}
48/// Validates runtime backend selection for the current build.
49pub fn validate_backend_selection() -> ApiResult<String> {
50    let selected = selected_backend();
51    if selected != BACKEND_SQLITE && selected != BACKEND_DUCKDB {
52        return Err(eyre!(
53            "Invalid database backend '{}'. Supported values are '{}' or '{}'",
54            selected,
55            BACKEND_SQLITE,
56            BACKEND_DUCKDB
57        ));
58    }
59    let bundled = backend();
60    if selected != bundled {
61        return Err(eyre!(
62            "Database backend '{}' is not available in this build (bundled backend: '{}'). Rebuild ACORN with matching feature flags.",
63            selected,
64            bundled,
65        ));
66    }
67    Ok(selected)
68}