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