Skip to main content

rust_ef/provider/
traits.rs

1//! Provider traits: `ISqlGenerator`, `IAsyncConnection`, `IDatabaseProvider`.
2
3use crate::error::EFResult;
4use async_trait::async_trait;
5
6use super::db_value::DbValue;
7
8/// Represents a SQL dialect with specific syntax for common operations.
9pub trait ISqlGenerator: Send + Sync {
10    /// Generates a SELECT statement.
11    fn select(&self, table: &str, columns: &[&str]) -> String;
12    /// Generates an INSERT statement.
13    fn insert(&self, table: &str, columns: &[&str], returning: bool) -> String;
14    /// Generates a multi-row INSERT statement with `row_count` value groups
15    /// (`INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...`). Placeholders
16    /// follow the dialect's numbering (`?` for SQLite/MySQL, `$n` for PG).
17    fn insert_batch(&self, table: &str, columns: &[&str], row_count: usize) -> String {
18        let _ = (table, columns, row_count);
19        String::new()
20    }
21    /// Generates an UPDATE statement.
22    fn update(&self, table: &str, set_columns: &[&str], where_clause: &str) -> String;
23    /// Generates a batch UPDATE using `CASE pk_col WHEN ? THEN ?` for
24    /// `row_count` rows, reducing N round trips to 1.
25    ///
26    /// The SET clause uses `2 * set_columns.len() * row_count` placeholders
27    /// (numbered from 1). The caller-built `where_clause` must number its
28    /// placeholders starting from `2 * set_columns.len() * row_count + 1`.
29    ///
30    /// Parameter layout (caller must arrange params in this order):
31    /// - For each set column, for each row: `[pk_value, col_value]`
32    /// - Then the `where_clause` params (PK IN-list + optional filter)
33    fn update_batch(
34        &self,
35        table: &str,
36        set_columns: &[&str],
37        pk_col: &str,
38        row_count: usize,
39        where_clause: &str,
40    ) -> String {
41        let mut idx = 1usize;
42        let sets: Vec<String> = set_columns
43            .iter()
44            .map(|col| {
45                let whens: Vec<String> = (0..row_count)
46                    .map(|_| {
47                        let pk_ph = self.parameter_placeholder(idx);
48                        idx += 1;
49                        let val_ph = self.parameter_placeholder(idx);
50                        idx += 1;
51                        format!("WHEN {} THEN {}", pk_ph, val_ph)
52                    })
53                    .collect();
54                format!(
55                    "{} = CASE {} {} END",
56                    self.quote_identifier(col),
57                    self.quote_identifier(pk_col),
58                    whens.join(" ")
59                )
60            })
61            .collect();
62        format!(
63            "UPDATE {} SET {} WHERE {}",
64            self.quote_identifier(table),
65            sets.join(", "),
66            where_clause
67        )
68    }
69    /// Generates a DELETE statement.
70    fn delete(&self, table: &str, where_clause: &str) -> String;
71    /// Generates a CREATE TABLE statement.
72    fn create_table(&self, table: &str, columns: &[(String, String)]) -> String;
73    /// Generates a DROP TABLE statement.
74    fn drop_table(&self, table: &str) -> String;
75    /// Generates a pagination clause.
76    fn pagination(&self, skip: Option<usize>, take: Option<usize>) -> String;
77    /// Returns the parameter placeholder (e.g., `$1` for PG, `?` for MySQL).
78    fn parameter_placeholder(&self, index: usize) -> String;
79    /// Returns the identifier quoting character (e.g., `"` for PG, `` ` `` for MySQL).
80    fn quote_identifier(&self, identifier: &str) -> String;
81    /// Returns the dialect-specific auto-increment syntax.
82    fn auto_increment_syntax(&self) -> &'static str;
83
84    /// Whether `insert_batch` includes a `RETURNING *` clause (PostgreSQL).
85    /// When true, `execute_inserts` uses `query()` to read back generated PKs
86    /// directly from the INSERT result set.
87    fn supports_returning(&self) -> bool {
88        false
89    }
90
91    /// SQL that retrieves the auto-increment key generated by the most recent
92    /// batch INSERT. Returns `None` when the dialect uses `RETURNING` instead.
93    /// - SQLite: `SELECT last_insert_rowid()` (returns the LAST rowid)
94    /// - MySQL: `SELECT LAST_INSERT_ID()` (returns the FIRST generated ID)
95    fn last_insert_id_sql(&self) -> Option<&'static str> {
96        None
97    }
98
99    /// Whether `last_insert_id_sql()` returns the FIRST (MySQL) or LAST
100    /// (SQLite) generated ID in a batch INSERT. The executor uses this to
101    /// compute the full key sequence: `first_id..first_id+N` or
102    /// `last_id-N+1..last_id`.
103    fn last_insert_id_returns_first(&self) -> bool {
104        true
105    }
106
107    /// Generates a batch UPSERT statement (`row_count` value groups).
108    ///
109    /// - SQLite/PostgreSQL: `INSERT INTO t (cols) VALUES (...) ON CONFLICT(conflict_cols) DO UPDATE SET ...`
110    /// - MySQL: `INSERT INTO t (cols) VALUES (...) ON DUPLICATE KEY UPDATE ...`
111    ///
112    /// `columns` are the INSERT columns (excluding auto-increment).
113    /// `conflict_cols` are the PK (or unique constraint) column names used as
114    /// the conflict target. The UPDATE SET clause is generated for all
115    /// `columns` that are NOT in `conflict_cols`.
116    fn upsert_batch(
117        &self,
118        table: &str,
119        columns: &[&str],
120        conflict_cols: &[&str],
121        row_count: usize,
122    ) -> String {
123        let _ = (table, columns, conflict_cols, row_count);
124        String::new()
125    }
126}
127
128/// ANSI SQL transaction isolation levels.
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum IsolationLevel {
131    ReadUncommitted,
132    ReadCommitted,
133    RepeatableRead,
134    Serializable,
135}
136
137/// Trait for async database connections.
138#[async_trait]
139pub trait IAsyncConnection: Send + Sync {
140    /// Executes a query with parameters and returns the number of affected rows.
141    async fn execute(&mut self, sql: &str, params: &[DbValue]) -> EFResult<u64>;
142    /// Executes a query with parameters and returns rows.
143    async fn query(&mut self, sql: &str, params: &[DbValue]) -> EFResult<Vec<Vec<DbValue>>>;
144    /// Begins a transaction.
145    async fn begin_transaction(&mut self) -> EFResult<()>;
146    /// Commits the current transaction.
147    async fn commit_transaction(&mut self) -> EFResult<()>;
148    /// Rolls back the current transaction.
149    async fn rollback_transaction(&mut self) -> EFResult<()>;
150    /// Creates a savepoint within the current transaction.
151    async fn create_savepoint(&mut self, name: &str) -> EFResult<()>;
152    /// Releases (commits) a previously created savepoint, discarding its rollback point.
153    async fn release_savepoint(&mut self, name: &str) -> EFResult<()>;
154    /// Rolls back to the named savepoint, preserving the outer transaction.
155    async fn rollback_to_savepoint(&mut self, name: &str) -> EFResult<()>;
156    /// Sets the isolation level of the current transaction.
157    /// Must be called after `begin_transaction` and before any query.
158    async fn set_transaction_isolation(&mut self, level: IsolationLevel) -> EFResult<()>;
159
160    /// Sets the slow query threshold for this connection.
161    ///
162    /// Only available when the `tracing` feature is enabled on the core
163    /// crate. Default implementation is a no-op; provider connections
164    /// override to store the threshold for `QueryGuard` comparison.
165    #[cfg(feature = "tracing")]
166    fn set_slow_query_threshold(&mut self, _threshold: std::time::Duration) {}
167}
168
169/// The database provider abstraction.
170/// Corresponds to EFCore's provider model.
171#[async_trait]
172pub trait IDatabaseProvider: Send + Sync {
173    /// Returns the SQL dialect generator for this provider.
174    ///
175    /// Implementations are stateless, so a `&'static` reference is returned —
176    /// no heap allocation per call.
177    fn sql_generator(&self) -> &'static dyn ISqlGenerator;
178
179    /// Gets an async database connection from the pool.
180    async fn get_connection(&self) -> EFResult<Box<dyn IAsyncConnection>>;
181
182    /// Executes a migration command (DDL).
183    async fn execute_migration_command(&self, sql: &str) -> EFResult<()>;
184
185    /// Returns the provider name (e.g., "PostgreSQL", "MySQL").
186    fn name(&self) -> &str;
187
188    /// Returns the migration dialect for this provider.
189    fn migration_dialect(&self) -> crate::migration::MigrationDialect;
190
191    /// Sets the slow query threshold for all connections from this provider.
192    ///
193    /// Only available when the `tracing` feature is enabled on the core
194    /// crate. Default implementation is a no-op; providers override to
195    /// store the threshold and pass it to connections on acquisition.
196    #[cfg(feature = "tracing")]
197    fn set_slow_query_threshold(&self, _threshold: std::time::Duration) {}
198}