reinhardt_db/backends/backend.rs
1//! Database backend abstraction
2
3use async_trait::async_trait;
4
5use super::{
6 error::Result,
7 types::{DatabaseType, IsolationLevel, QueryResult, QueryValue, Row, TransactionExecutor},
8};
9
10/// Core database backend trait
11#[async_trait]
12pub trait DatabaseBackend: Send + Sync {
13 /// Returns the database type
14 fn database_type(&self) -> DatabaseType;
15
16 /// Generates a placeholder for the given parameter index (1-based)
17 fn placeholder(&self, index: usize) -> String;
18
19 /// Returns whether the database supports RETURNING clause
20 fn supports_returning(&self) -> bool;
21
22 /// Returns whether the database supports ON CONFLICT clause
23 fn supports_on_conflict(&self) -> bool;
24
25 /// Returns whether DDL statements can be rolled back in transactions
26 ///
27 /// PostgreSQL and SQLite support transactional DDL - CREATE TABLE, etc.
28 /// can be rolled back if the transaction fails.
29 ///
30 /// MySQL/MariaDB do NOT support transactional DDL - DDL statements cause
31 /// an implicit commit, so they cannot be rolled back.
32 fn supports_transactional_ddl(&self) -> bool {
33 // Default implementation: check database type
34 self.database_type().supports_transactional_ddl()
35 }
36
37 /// Executes a query that modifies the database
38 async fn execute(&self, sql: &str, params: Vec<QueryValue>) -> Result<QueryResult>;
39
40 /// Fetches a single row from the database
41 async fn fetch_one(&self, sql: &str, params: Vec<QueryValue>) -> Result<Row>;
42
43 /// Fetches all matching rows from the database
44 async fn fetch_all(&self, sql: &str, params: Vec<QueryValue>) -> Result<Vec<Row>>;
45
46 /// Fetches an optional single row from the database
47 async fn fetch_optional(&self, sql: &str, params: Vec<QueryValue>) -> Result<Option<Row>>;
48
49 /// Begin a database transaction and return a dedicated executor
50 ///
51 /// This method acquires a dedicated database connection and begins a
52 /// transaction on it. All queries executed through the returned
53 /// `TransactionExecutor` are guaranteed to run on the same physical
54 /// connection, ensuring proper transaction isolation.
55 ///
56 /// # Returns
57 ///
58 /// A boxed `TransactionExecutor` that holds the dedicated connection
59 /// and provides methods for executing queries within the transaction.
60 async fn begin(&self) -> Result<Box<dyn TransactionExecutor>>;
61
62 /// Begin a database transaction with a specific isolation level
63 ///
64 /// This method is similar to `begin()`, but allows specifying the
65 /// transaction isolation level. The isolation level controls the
66 /// visibility of changes made by other concurrent transactions.
67 ///
68 /// # Arguments
69 ///
70 /// * `isolation_level` - The desired isolation level for the transaction
71 ///
72 /// # Returns
73 ///
74 /// A boxed `TransactionExecutor` that holds the dedicated connection
75 /// with the specified isolation level.
76 ///
77 /// # Default Implementation
78 ///
79 /// Falls back to `begin()` with the database's default isolation level.
80 /// Backends that support custom isolation levels should override this.
81 async fn begin_with_isolation(
82 &self,
83 isolation_level: IsolationLevel,
84 ) -> Result<Box<dyn TransactionExecutor>> {
85 let _ = isolation_level;
86 // Default implementation: ignore isolation level and use default
87 self.begin().await
88 }
89
90 /// Returns self as &dyn std::any::Any for downcasting
91 fn as_any(&self) -> &dyn std::any::Any;
92}