Skip to main content

rust_ef/
provider.rs

1//! Database provider abstraction trait.
2//!
3//! Corresponds to EFCore's database provider model, allowing multiple
4//! database backends (PostgreSQL, MySQL, SQLite, etc.) to be plugged in.
5
6use crate::error::EFResult;
7use async_trait::async_trait;
8use std::fmt;
9
10/// A typed database parameter value for parameterized queries.
11///
12/// Native variants (`DateTime`/`NaiveDateTime`/`NaiveDate`/`Uuid`/`Decimal`)
13/// are enabled by the `chrono`/`uuid`/`decimal` Cargo features. When enabled,
14/// the PostgreSQL provider binds these via `tokio_postgres`'s binary protocol
15/// (`with-chrono-0_4`/`with-uuid-1` features) for type-safe, lossless
16/// parameter transmission. SQLite and MySQL providers collapse native
17/// variants to their canonical string representation (matching v1.0 behavior)
18/// since neither driver requires native type binding.
19#[derive(Debug, Clone, PartialEq)]
20pub enum DbValue {
21    Null,
22    Bool(bool),
23    I16(i16),
24    I32(i32),
25    I64(i64),
26    F32(f32),
27    F64(f64),
28    String(String),
29    Bytes(Vec<u8>),
30    /// UTC timestamp — bound natively as `TIMESTAMPTZ` on PostgreSQL.
31    #[cfg(feature = "chrono")]
32    DateTime(chrono::DateTime<chrono::Utc>),
33    /// Naive (timezone-less) timestamp — bound natively as `TIMESTAMP` on PG.
34    #[cfg(feature = "chrono")]
35    NaiveDateTime(chrono::NaiveDateTime),
36    /// Calendar date — bound natively as `DATE` on PostgreSQL.
37    #[cfg(feature = "chrono")]
38    NaiveDate(chrono::NaiveDate),
39    /// UUID — bound natively as `UUID` on PostgreSQL.
40    #[cfg(feature = "uuid")]
41    Uuid(uuid::Uuid),
42    /// Fixed-precision decimal — bound as `NUMERIC` string on PostgreSQL
43    /// (tokio_postgres lacks a native `rust_decimal` adapter; the string
44    /// form round-trips losslessly through PG's `NUMERIC` type).
45    #[cfg(feature = "decimal")]
46    Decimal(rust_decimal::Decimal),
47}
48
49impl fmt::Display for DbValue {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            DbValue::Null => write!(f, "NULL"),
53            DbValue::Bool(v) => write!(f, "{}", if *v { "TRUE" } else { "FALSE" }),
54            DbValue::I16(v) => write!(f, "{}", v),
55            DbValue::I32(v) => write!(f, "{}", v),
56            DbValue::I64(v) => write!(f, "{}", v),
57            DbValue::F32(v) => write!(f, "{}", v),
58            DbValue::F64(v) => write!(f, "{}", v),
59            DbValue::String(v) => write!(f, "'{}'", v.replace('\'', "''")),
60            DbValue::Bytes(v) => write!(f, "{}", hex::encode(v)),
61            #[cfg(feature = "chrono")]
62            DbValue::DateTime(v) => write!(f, "'{}'", v.to_rfc3339()),
63            #[cfg(feature = "chrono")]
64            DbValue::NaiveDateTime(v) => write!(f, "'{}'", v),
65            #[cfg(feature = "chrono")]
66            DbValue::NaiveDate(v) => write!(f, "'{}'", v),
67            #[cfg(feature = "uuid")]
68            DbValue::Uuid(v) => write!(f, "'{}'", v),
69            #[cfg(feature = "decimal")]
70            DbValue::Decimal(v) => write!(f, "'{}'", v),
71        }
72    }
73}
74
75mod hex {
76    pub fn encode(bytes: &[u8]) -> String {
77        bytes.iter().map(|b| format!("{:02x}", b)).collect()
78    }
79}
80
81impl From<i32> for DbValue {
82    fn from(v: i32) -> Self {
83        DbValue::I32(v)
84    }
85}
86impl From<&i32> for DbValue {
87    fn from(v: &i32) -> Self {
88        DbValue::I32(*v)
89    }
90}
91impl From<i64> for DbValue {
92    fn from(v: i64) -> Self {
93        DbValue::I64(v)
94    }
95}
96impl From<&i64> for DbValue {
97    fn from(v: &i64) -> Self {
98        DbValue::I64(*v)
99    }
100}
101impl From<String> for DbValue {
102    fn from(v: String) -> Self {
103        DbValue::String(v)
104    }
105}
106impl From<&str> for DbValue {
107    fn from(v: &str) -> Self {
108        DbValue::String(v.to_string())
109    }
110}
111impl From<bool> for DbValue {
112    fn from(v: bool) -> Self {
113        DbValue::Bool(v)
114    }
115}
116impl From<f64> for DbValue {
117    fn from(v: f64) -> Self {
118        DbValue::F64(v)
119    }
120}
121impl From<f32> for DbValue {
122    fn from(v: f32) -> Self {
123        DbValue::F32(v)
124    }
125}
126impl From<i16> for DbValue {
127    fn from(v: i16) -> Self {
128        DbValue::I16(v)
129    }
130}
131impl From<Vec<u8>> for DbValue {
132    fn from(v: Vec<u8>) -> Self {
133        DbValue::Bytes(v)
134    }
135}
136
137// --- Feature-gated From impls for chrono / uuid / decimal ---
138//
139// These construct native `DbValue` variants (not `String`) so that the
140// PostgreSQL provider can bind them via tokio_postgres's binary protocol.
141// SQLite and MySQL providers collapse these variants to their canonical
142// string form in their respective `type_conversion.rs`.
143
144#[cfg(feature = "chrono")]
145impl From<chrono::DateTime<chrono::Utc>> for DbValue {
146    fn from(dt: chrono::DateTime<chrono::Utc>) -> Self {
147        DbValue::DateTime(dt)
148    }
149}
150
151#[cfg(feature = "chrono")]
152impl From<chrono::NaiveDateTime> for DbValue {
153    fn from(ndt: chrono::NaiveDateTime) -> Self {
154        DbValue::NaiveDateTime(ndt)
155    }
156}
157
158#[cfg(feature = "chrono")]
159impl From<chrono::NaiveDate> for DbValue {
160    fn from(nd: chrono::NaiveDate) -> Self {
161        DbValue::NaiveDate(nd)
162    }
163}
164
165#[cfg(feature = "uuid")]
166impl From<uuid::Uuid> for DbValue {
167    fn from(u: uuid::Uuid) -> Self {
168        DbValue::Uuid(u)
169    }
170}
171
172#[cfg(feature = "decimal")]
173impl From<rust_decimal::Decimal> for DbValue {
174    fn from(d: rust_decimal::Decimal) -> Self {
175        DbValue::Decimal(d)
176    }
177}
178impl<T> From<Option<T>> for DbValue
179where
180    T: Into<DbValue>,
181{
182    fn from(v: Option<T>) -> Self {
183        match v {
184            Some(val) => val.into(),
185            None => DbValue::Null,
186        }
187    }
188}
189
190/// Error returned when a [`DbValue`] cannot be converted to the requested type.
191///
192/// Used by `TryFrom<DbValue>` impls for `i32`/`i64`/`f64`/`String`/`bool`/...
193/// and by `QueryBuilder::min_internal` / `max_internal` to surface type
194/// mismatches when reading aggregation results.
195#[derive(Debug, Clone, PartialEq)]
196pub struct DbValueConvertError {
197    /// The [`DbValue`] that could not be converted.
198    pub source: DbValue,
199    /// The target Rust type name (e.g. `"i32"`).
200    pub target_type: &'static str,
201}
202
203impl fmt::Display for DbValueConvertError {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(
206            f,
207            "cannot convert {:?} to {}",
208            self.source, self.target_type
209        )
210    }
211}
212
213impl std::error::Error for DbValueConvertError {}
214
215impl From<DbValueConvertError> for crate::error::EFError {
216    fn from(e: DbValueConvertError) -> Self {
217        crate::error::EFError::type_conversion(e.to_string())
218    }
219}
220
221impl TryFrom<DbValue> for i32 {
222    type Error = DbValueConvertError;
223    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
224        match v {
225            DbValue::I32(n) => Ok(n),
226            DbValue::I16(n) => Ok(n as i32),
227            DbValue::I64(n) => n.try_into().map_err(|_| DbValueConvertError {
228                source: DbValue::I64(n),
229                target_type: "i32",
230            }),
231            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
232                source: DbValue::String(s),
233                target_type: "i32",
234            }),
235            other => Err(DbValueConvertError {
236                source: other,
237                target_type: "i32",
238            }),
239        }
240    }
241}
242
243impl TryFrom<DbValue> for i64 {
244    type Error = DbValueConvertError;
245    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
246        match v {
247            DbValue::I64(n) => Ok(n),
248            DbValue::I32(n) => Ok(n as i64),
249            DbValue::I16(n) => Ok(n as i64),
250            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
251                source: DbValue::String(s),
252                target_type: "i64",
253            }),
254            other => Err(DbValueConvertError {
255                source: other,
256                target_type: "i64",
257            }),
258        }
259    }
260}
261
262impl TryFrom<DbValue> for f64 {
263    type Error = DbValueConvertError;
264    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
265        match v {
266            DbValue::F64(x) => Ok(x),
267            DbValue::F32(x) => Ok(x as f64),
268            DbValue::I32(n) => Ok(n as f64),
269            DbValue::I64(n) => Ok(n as f64),
270            DbValue::I16(n) => Ok(n as f64),
271            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
272                source: DbValue::String(s),
273                target_type: "f64",
274            }),
275            other => Err(DbValueConvertError {
276                source: other,
277                target_type: "f64",
278            }),
279        }
280    }
281}
282
283impl TryFrom<DbValue> for f32 {
284    type Error = DbValueConvertError;
285    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
286        match v {
287            DbValue::F32(x) => Ok(x),
288            DbValue::F64(x) => Ok(x as f32),
289            DbValue::I32(n) => Ok(n as f32),
290            DbValue::I64(n) => Ok(n as f32),
291            DbValue::I16(n) => Ok(n as f32),
292            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
293                source: DbValue::String(s),
294                target_type: "f32",
295            }),
296            other => Err(DbValueConvertError {
297                source: other,
298                target_type: "f32",
299            }),
300        }
301    }
302}
303
304impl TryFrom<DbValue> for String {
305    type Error = DbValueConvertError;
306    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
307        match v {
308            DbValue::String(s) => Ok(s),
309            DbValue::Bool(b) => Ok(b.to_string()),
310            DbValue::I16(n) => Ok(n.to_string()),
311            DbValue::I32(n) => Ok(n.to_string()),
312            DbValue::I64(n) => Ok(n.to_string()),
313            DbValue::F32(x) => Ok(x.to_string()),
314            DbValue::F64(x) => Ok(x.to_string()),
315            #[cfg(feature = "chrono")]
316            DbValue::DateTime(dt) => Ok(dt.to_rfc3339()),
317            #[cfg(feature = "chrono")]
318            DbValue::NaiveDateTime(ndt) => Ok(ndt.to_string()),
319            #[cfg(feature = "chrono")]
320            DbValue::NaiveDate(nd) => Ok(nd.to_string()),
321            #[cfg(feature = "uuid")]
322            DbValue::Uuid(u) => Ok(u.to_string()),
323            #[cfg(feature = "decimal")]
324            DbValue::Decimal(d) => Ok(d.to_string()),
325            other => Err(DbValueConvertError {
326                source: other,
327                target_type: "String",
328            }),
329        }
330    }
331}
332
333impl TryFrom<DbValue> for bool {
334    type Error = DbValueConvertError;
335    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
336        match v {
337            DbValue::Bool(b) => Ok(b),
338            DbValue::I64(n) => Ok(n != 0),
339            DbValue::I32(n) => Ok(n != 0),
340            DbValue::I16(n) => Ok(n != 0),
341            DbValue::String(s) => {
342                let lower = s.to_ascii_lowercase();
343                match lower.as_str() {
344                    "true" | "t" | "1" => Ok(true),
345                    "false" | "f" | "0" => Ok(false),
346                    _ => Err(DbValueConvertError {
347                        source: DbValue::String(s),
348                        target_type: "bool",
349                    }),
350                }
351            }
352            other => Err(DbValueConvertError {
353                source: other,
354                target_type: "bool",
355            }),
356        }
357    }
358}
359
360impl TryFrom<DbValue> for Vec<u8> {
361    type Error = DbValueConvertError;
362    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
363        match v {
364            DbValue::Bytes(b) => Ok(b),
365            other => Err(DbValueConvertError {
366                source: other,
367                target_type: "Vec<u8>",
368            }),
369        }
370    }
371}
372
373impl TryFrom<DbValue> for i16 {
374    type Error = DbValueConvertError;
375    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
376        match v {
377            DbValue::I16(n) => Ok(n),
378            DbValue::I32(n) => n.try_into().map_err(|_| DbValueConvertError {
379                source: DbValue::I32(n),
380                target_type: "i16",
381            }),
382            DbValue::I64(n) => n.try_into().map_err(|_| DbValueConvertError {
383                source: DbValue::I64(n),
384                target_type: "i16",
385            }),
386            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
387                source: DbValue::String(s),
388                target_type: "i16",
389            }),
390            other => Err(DbValueConvertError {
391                source: other,
392                target_type: "i16",
393            }),
394        }
395    }
396}
397
398impl TryFrom<DbValue> for i8 {
399    type Error = DbValueConvertError;
400    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
401        match v {
402            DbValue::I16(n) => n.try_into().map_err(|_| DbValueConvertError {
403                source: DbValue::I16(n),
404                target_type: "i8",
405            }),
406            DbValue::I32(n) => n.try_into().map_err(|_| DbValueConvertError {
407                source: DbValue::I32(n),
408                target_type: "i8",
409            }),
410            DbValue::I64(n) => n.try_into().map_err(|_| DbValueConvertError {
411                source: DbValue::I64(n),
412                target_type: "i8",
413            }),
414            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
415                source: DbValue::String(s),
416                target_type: "i8",
417            }),
418            other => Err(DbValueConvertError {
419                source: other,
420                target_type: "i8",
421            }),
422        }
423    }
424}
425
426impl TryFrom<DbValue> for u32 {
427    type Error = DbValueConvertError;
428    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
429        match v {
430            DbValue::I16(n) => (n as i32).try_into().map_err(|_| DbValueConvertError {
431                source: DbValue::I16(n),
432                target_type: "u32",
433            }),
434            DbValue::I32(n) => n.try_into().map_err(|_| DbValueConvertError {
435                source: DbValue::I32(n),
436                target_type: "u32",
437            }),
438            DbValue::I64(n) => n.try_into().map_err(|_| DbValueConvertError {
439                source: DbValue::I64(n),
440                target_type: "u32",
441            }),
442            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
443                source: DbValue::String(s),
444                target_type: "u32",
445            }),
446            other => Err(DbValueConvertError {
447                source: other,
448                target_type: "u32",
449            }),
450        }
451    }
452}
453
454impl TryFrom<DbValue> for u64 {
455    type Error = DbValueConvertError;
456    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
457        match v {
458            DbValue::I16(n) => (n as i64).try_into().map_err(|_| DbValueConvertError {
459                source: DbValue::I16(n),
460                target_type: "u64",
461            }),
462            DbValue::I32(n) => (n as i64).try_into().map_err(|_| DbValueConvertError {
463                source: DbValue::I32(n),
464                target_type: "u64",
465            }),
466            DbValue::I64(n) => n.try_into().map_err(|_| DbValueConvertError {
467                source: DbValue::I64(n),
468                target_type: "u64",
469            }),
470            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
471                source: DbValue::String(s),
472                target_type: "u64",
473            }),
474            other => Err(DbValueConvertError {
475                source: other,
476                target_type: "u64",
477            }),
478        }
479    }
480}
481
482// --- Feature-gated TryFrom impls for native chrono / uuid / decimal types ---
483
484#[cfg(feature = "chrono")]
485impl TryFrom<DbValue> for chrono::DateTime<chrono::Utc> {
486    type Error = DbValueConvertError;
487    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
488        match v {
489            DbValue::DateTime(dt) => Ok(dt),
490            DbValue::String(s) => chrono::DateTime::parse_from_rfc3339(&s)
491                .map(|dt| dt.with_timezone(&chrono::Utc))
492                .map_err(|_| DbValueConvertError {
493                    source: DbValue::String(s),
494                    target_type: "DateTime<Utc>",
495                }),
496            other => Err(DbValueConvertError {
497                source: other,
498                target_type: "DateTime<Utc>",
499            }),
500        }
501    }
502}
503
504#[cfg(feature = "chrono")]
505impl TryFrom<DbValue> for chrono::NaiveDateTime {
506    type Error = DbValueConvertError;
507    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
508        match v {
509            DbValue::NaiveDateTime(ndt) => Ok(ndt),
510            DbValue::String(s) => {
511                chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S")
512                    .or_else(|_| {
513                        // Fallback: ISO 8601 / RFC 3339 without timezone
514                        chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S")
515                    })
516                    .map_err(|_| DbValueConvertError {
517                        source: DbValue::String(s),
518                        target_type: "NaiveDateTime",
519                    })
520            }
521            other => Err(DbValueConvertError {
522                source: other,
523                target_type: "NaiveDateTime",
524            }),
525        }
526    }
527}
528
529#[cfg(feature = "chrono")]
530impl TryFrom<DbValue> for chrono::NaiveDate {
531    type Error = DbValueConvertError;
532    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
533        match v {
534            DbValue::NaiveDate(nd) => Ok(nd),
535            DbValue::String(s) => {
536                chrono::NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(|_| DbValueConvertError {
537                    source: DbValue::String(s),
538                    target_type: "NaiveDate",
539                })
540            }
541            other => Err(DbValueConvertError {
542                source: other,
543                target_type: "NaiveDate",
544            }),
545        }
546    }
547}
548
549#[cfg(feature = "uuid")]
550impl TryFrom<DbValue> for uuid::Uuid {
551    type Error = DbValueConvertError;
552    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
553        match v {
554            DbValue::Uuid(u) => Ok(u),
555            DbValue::String(s) => uuid::Uuid::parse_str(&s).map_err(|_| DbValueConvertError {
556                source: DbValue::String(s),
557                target_type: "Uuid",
558            }),
559            other => Err(DbValueConvertError {
560                source: other,
561                target_type: "Uuid",
562            }),
563        }
564    }
565}
566
567#[cfg(feature = "decimal")]
568impl TryFrom<DbValue> for rust_decimal::Decimal {
569    type Error = DbValueConvertError;
570    fn try_from(v: DbValue) -> Result<Self, Self::Error> {
571        match v {
572            DbValue::Decimal(d) => Ok(d),
573            DbValue::String(s) => s.parse().map_err(|_| DbValueConvertError {
574                source: DbValue::String(s),
575                target_type: "Decimal",
576            }),
577            DbValue::I32(n) => Ok(rust_decimal::Decimal::from(n)),
578            DbValue::I64(n) => Ok(rust_decimal::Decimal::from(n)),
579            other => Err(DbValueConvertError {
580                source: other,
581                target_type: "Decimal",
582            }),
583        }
584    }
585}
586
587/// Represents a SQL dialect with specific syntax for common operations.
588pub trait ISqlGenerator: Send + Sync {
589    /// Generates a SELECT statement.
590    fn select(&self, table: &str, columns: &[&str]) -> String;
591    /// Generates an INSERT statement.
592    fn insert(&self, table: &str, columns: &[&str], returning: bool) -> String;
593    /// Generates a multi-row INSERT statement with `row_count` value groups
594    /// (`INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...`). Placeholders
595    /// follow the dialect's numbering (`?` for SQLite/MySQL, `$n` for PG).
596    fn insert_batch(&self, table: &str, columns: &[&str], row_count: usize) -> String {
597        let _ = (table, columns, row_count);
598        String::new()
599    }
600    /// Generates an UPDATE statement.
601    fn update(&self, table: &str, set_columns: &[&str], where_clause: &str) -> String;
602    /// Generates a batch UPDATE using `CASE pk_col WHEN ? THEN ?` for
603    /// `row_count` rows, reducing N round trips to 1.
604    ///
605    /// The SET clause uses `2 * set_columns.len() * row_count` placeholders
606    /// (numbered from 1). The caller-built `where_clause` must number its
607    /// placeholders starting from `2 * set_columns.len() * row_count + 1`.
608    ///
609    /// Parameter layout (caller must arrange params in this order):
610    /// - For each set column, for each row: `[pk_value, col_value]`
611    /// - Then the `where_clause` params (PK IN-list + optional filter)
612    fn update_batch(
613        &self,
614        table: &str,
615        set_columns: &[&str],
616        pk_col: &str,
617        row_count: usize,
618        where_clause: &str,
619    ) -> String {
620        let mut idx = 1usize;
621        let sets: Vec<String> = set_columns
622            .iter()
623            .map(|col| {
624                let whens: Vec<String> = (0..row_count)
625                    .map(|_| {
626                        let pk_ph = self.parameter_placeholder(idx);
627                        idx += 1;
628                        let val_ph = self.parameter_placeholder(idx);
629                        idx += 1;
630                        format!("WHEN {} THEN {}", pk_ph, val_ph)
631                    })
632                    .collect();
633                format!(
634                    "{} = CASE {} {} END",
635                    self.quote_identifier(col),
636                    self.quote_identifier(pk_col),
637                    whens.join(" ")
638                )
639            })
640            .collect();
641        format!(
642            "UPDATE {} SET {} WHERE {}",
643            self.quote_identifier(table),
644            sets.join(", "),
645            where_clause
646        )
647    }
648    /// Generates a DELETE statement.
649    fn delete(&self, table: &str, where_clause: &str) -> String;
650    /// Generates a CREATE TABLE statement.
651    fn create_table(&self, table: &str, columns: &[(String, String)]) -> String;
652    /// Generates a DROP TABLE statement.
653    fn drop_table(&self, table: &str) -> String;
654    /// Generates a pagination clause.
655    fn pagination(&self, skip: Option<usize>, take: Option<usize>) -> String;
656    /// Returns the parameter placeholder (e.g., `$1` for PG, `?` for MySQL).
657    fn parameter_placeholder(&self, index: usize) -> String;
658    /// Returns the identifier quoting character (e.g., `"` for PG, `` ` `` for MySQL).
659    fn quote_identifier(&self, identifier: &str) -> String;
660    /// Returns the dialect-specific auto-increment syntax.
661    fn auto_increment_syntax(&self) -> &'static str;
662
663    /// Whether `insert_batch` includes a `RETURNING *` clause (PostgreSQL).
664    /// When true, `execute_inserts` uses `query()` to read back generated PKs
665    /// directly from the INSERT result set.
666    fn supports_returning(&self) -> bool {
667        false
668    }
669
670    /// SQL that retrieves the auto-increment key generated by the most recent
671    /// batch INSERT. Returns `None` when the dialect uses `RETURNING` instead.
672    /// - SQLite: `SELECT last_insert_rowid()` (returns the LAST rowid)
673    /// - MySQL: `SELECT LAST_INSERT_ID()` (returns the FIRST generated ID)
674    fn last_insert_id_sql(&self) -> Option<&'static str> {
675        None
676    }
677
678    /// Whether `last_insert_id_sql()` returns the FIRST (MySQL) or LAST
679    /// (SQLite) generated ID in a batch INSERT. The executor uses this to
680    /// compute the full key sequence: `first_id..first_id+N` or
681    /// `last_id-N+1..last_id`.
682    fn last_insert_id_returns_first(&self) -> bool {
683        true
684    }
685
686    /// Generates a batch UPSERT statement (`row_count` value groups).
687    ///
688    /// - SQLite/PostgreSQL: `INSERT INTO t (cols) VALUES (...) ON CONFLICT(conflict_cols) DO UPDATE SET ...`
689    /// - MySQL: `INSERT INTO t (cols) VALUES (...) ON DUPLICATE KEY UPDATE ...`
690    ///
691    /// `columns` are the INSERT columns (excluding auto-increment).
692    /// `conflict_cols` are the PK (or unique constraint) column names used as
693    /// the conflict target. The UPDATE SET clause is generated for all
694    /// `columns` that are NOT in `conflict_cols`.
695    fn upsert_batch(
696        &self,
697        table: &str,
698        columns: &[&str],
699        conflict_cols: &[&str],
700        row_count: usize,
701    ) -> String {
702        let _ = (table, columns, conflict_cols, row_count);
703        String::new()
704    }
705}
706
707/// ANSI SQL transaction isolation levels.
708#[derive(Debug, Clone, Copy, PartialEq, Eq)]
709pub enum IsolationLevel {
710    ReadUncommitted,
711    ReadCommitted,
712    RepeatableRead,
713    Serializable,
714}
715
716/// Trait for async database connections.
717#[async_trait]
718pub trait IAsyncConnection: Send + Sync {
719    /// Executes a query with parameters and returns the number of affected rows.
720    async fn execute(&mut self, sql: &str, params: &[DbValue]) -> EFResult<u64>;
721    /// Executes a query with parameters and returns rows.
722    async fn query(&mut self, sql: &str, params: &[DbValue]) -> EFResult<Vec<Vec<DbValue>>>;
723    /// Begins a transaction.
724    async fn begin_transaction(&mut self) -> EFResult<()>;
725    /// Commits the current transaction.
726    async fn commit_transaction(&mut self) -> EFResult<()>;
727    /// Rolls back the current transaction.
728    async fn rollback_transaction(&mut self) -> EFResult<()>;
729    /// Creates a savepoint within the current transaction.
730    async fn create_savepoint(&mut self, name: &str) -> EFResult<()>;
731    /// Releases (commits) a previously created savepoint, discarding its rollback point.
732    async fn release_savepoint(&mut self, name: &str) -> EFResult<()>;
733    /// Rolls back to the named savepoint, preserving the outer transaction.
734    async fn rollback_to_savepoint(&mut self, name: &str) -> EFResult<()>;
735    /// Sets the isolation level of the current transaction.
736    /// Must be called after `begin_transaction` and before any query.
737    async fn set_transaction_isolation(&mut self, level: IsolationLevel) -> EFResult<()>;
738
739    /// Sets the slow query threshold for this connection.
740    ///
741    /// Only available when the `tracing` feature is enabled on the core
742    /// crate. Default implementation is a no-op; provider connections
743    /// override to store the threshold for `QueryGuard` comparison.
744    #[cfg(feature = "tracing")]
745    fn set_slow_query_threshold(&mut self, _threshold: std::time::Duration) {}
746}
747
748/// The database provider abstraction.
749/// Corresponds to EFCore's provider model.
750#[async_trait]
751pub trait IDatabaseProvider: Send + Sync {
752    /// Returns the SQL dialect generator for this provider.
753    ///
754    /// Implementations are stateless, so a `&'static` reference is returned —
755    /// no heap allocation per call.
756    fn sql_generator(&self) -> &'static dyn ISqlGenerator;
757
758    /// Gets an async database connection from the pool.
759    async fn get_connection(&self) -> EFResult<Box<dyn IAsyncConnection>>;
760
761    /// Executes a migration command (DDL).
762    async fn execute_migration_command(&self, sql: &str) -> EFResult<()>;
763
764    /// Returns the provider name (e.g., "PostgreSQL", "MySQL").
765    fn name(&self) -> &str;
766
767    /// Returns the migration dialect for this provider.
768    fn migration_dialect(&self) -> crate::migration::MigrationDialect;
769
770    /// Sets the slow query threshold for all connections from this provider.
771    ///
772    /// Only available when the `tracing` feature is enabled on the core
773    /// crate. Default implementation is a no-op; providers override to
774    /// store the threshold and pass it to connections on acquisition.
775    #[cfg(feature = "tracing")]
776    fn set_slow_query_threshold(&self, _threshold: std::time::Duration) {}
777}