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}