Skip to main content

siderite_orm/
backend.rs

1//! Executor, backend and transaction traits implemented by database adapters.
2//!
3//! Application code does not use these traits directly; it goes through the
4//! [`Db`](crate::Db) handle, which wraps either a backend (pool) or an open
5//! transaction behind the same API.
6
7use crate::capabilities::{BackendCapabilities, IsolationLevel};
8use crate::error::{OrmError, QueryError};
9use crate::plan::QueryPlan;
10use crate::value::Value;
11use crate::write::WritePlan;
12use async_trait::async_trait;
13
14/// One decoded result row: column names paired with backend-neutral values.
15///
16/// Dynamic projections and annotations decode into `Row`; models decode from
17/// it with [`Model::from_row`](crate::Model::from_row).
18#[derive(Debug, Clone, PartialEq, Default)]
19pub struct Row {
20    columns: Vec<(String, Value)>,
21}
22
23impl Row {
24    /// Build a row from `(column, value)` pairs.
25    pub fn new(columns: Vec<(String, Value)>) -> Self {
26        Self { columns }
27    }
28
29    /// Look up a value by column name.
30    pub fn get(&self, column: &str) -> Option<&Value> {
31        self.columns
32            .iter()
33            .find(|(c, _)| c == column)
34            .map(|(_, v)| v)
35    }
36
37    /// Iterate over `(column, value)` pairs in projection order.
38    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
39        self.columns.iter().map(|(c, v)| (c.as_str(), v))
40    }
41
42    /// Consume into `(column, value)` pairs.
43    pub fn into_columns(self) -> Vec<(String, Value)> {
44        self.columns
45    }
46
47    /// Number of columns.
48    pub fn len(&self) -> usize {
49        self.columns.len()
50    }
51
52    /// Whether the row has no columns.
53    pub fn is_empty(&self) -> bool {
54        self.columns.is_empty()
55    }
56}
57
58/// Result of executing a read plan.
59#[derive(Debug, Clone, PartialEq, Default)]
60pub struct QueryResult {
61    /// Rows in backend order.
62    pub rows: Vec<Row>,
63}
64
65/// Result of executing a write plan.
66#[derive(Debug, Clone, PartialEq, Default)]
67pub struct ExecResult {
68    /// Rows inserted, updated or deleted.
69    pub rows_affected: u64,
70    /// Rows produced by `RETURNING`, in statement order.
71    pub returning: Vec<Row>,
72}
73
74/// Runs plans and raw statements on a pool or inside a transaction.
75///
76/// Implementations **must** check plans against [`capabilities`](Self::capabilities)
77/// (`QueryPlan::check` / `WritePlan::check`) before any I/O and return a
78/// capability error instead of ignoring unsupported features.
79#[async_trait]
80pub trait Executor: Send + Sync + 'static {
81    /// Declared capabilities.
82    fn capabilities(&self) -> BackendCapabilities;
83
84    /// Execute a read plan.
85    async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError>;
86
87    /// Execute a write plan.
88    async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError>;
89
90    /// Run raw SQL returning rows. `params` are bound, never interpolated.
91    async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError>;
92
93    /// Run raw SQL returning the affected-row count. `params` are bound.
94    async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError>;
95
96    /// Run a multi-statement script without parameters (DDL, migrations).
97    async fn execute_script(&self, sql: &str) -> Result<(), OrmError>;
98}
99
100/// A connection pool that can open transactions.
101#[async_trait]
102pub trait Backend: Executor {
103    /// Count compiled read parameters without database I/O.
104    ///
105    /// Args:
106    ///     _plan: Complete read plan whose existing binds consume capacity.
107    ///
108    /// Returns:
109    ///     Exact compiled bind count, or None when the backend cannot report
110    ///     it. SQL prefetch requires this contract for bounded batching.
111    ///
112    /// # Errors
113    /// Capability or plan compilation errors.
114    fn read_parameter_count(&self, _plan: &QueryPlan) -> Result<Option<usize>, OrmError> {
115        Ok(None)
116    }
117
118    /// Begin a transaction on a dedicated connection.
119    ///
120    /// `isolation` is validated against
121    /// [`BackendCapabilities::isolation_levels`] before any I/O.
122    async fn begin(
123        &self,
124        isolation: Option<IsolationLevel>,
125    ) -> Result<Box<dyn Transaction>, OrmError>;
126
127    /// Connection for a schema migration.
128    ///
129    /// When `transactional` is true this is a transaction, same as
130    /// [`begin`](Self::begin) with no isolation override. SQLite turns
131    /// `PRAGMA foreign_keys` off on that connection *before* `BEGIN` (the
132    /// pragma is a no-op inside a transaction), runs `PRAGMA foreign_key_check`
133    /// before commit, and restores the previous pragma value afterwards.
134    /// Other backends ignore the SQLite-specific work.
135    ///
136    /// # Errors
137    /// The default implementation has no way to hold a connection outside a
138    /// transaction, so `transactional == false` fails with a capability error
139    /// instead of silently handing back a transaction.
140    async fn begin_schema(&self, transactional: bool) -> Result<Box<dyn Transaction>, OrmError> {
141        if !transactional {
142            return Err(QueryError::InvalidPlan(
143                "this backend does not support a non-transactional schema connection".into(),
144            )
145            .into());
146        }
147        self.begin(None).await
148    }
149}
150
151/// An open transaction.
152///
153/// Dropping a transaction without calling [`commit`](Self::commit) rolls it
154/// back (the connection is returned to the pool in a clean state).
155#[async_trait]
156pub trait Transaction: Executor {
157    /// Commit. Later calls on this transaction fail with
158    /// [`QueryError::TransactionClosed`].
159    async fn commit(&self) -> Result<(), OrmError>;
160
161    /// Roll back. Later calls fail like after [`commit`](Self::commit).
162    async fn rollback(&self) -> Result<(), OrmError>;
163}