Skip to main content

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}