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 DELETE statement.
603    fn delete(&self, table: &str, where_clause: &str) -> String;
604    /// Generates a CREATE TABLE statement.
605    fn create_table(&self, table: &str, columns: &[(String, String)]) -> String;
606    /// Generates a DROP TABLE statement.
607    fn drop_table(&self, table: &str) -> String;
608    /// Generates a pagination clause.
609    fn pagination(&self, skip: Option<usize>, take: Option<usize>) -> String;
610    /// Returns the parameter placeholder (e.g., `$1` for PG, `?` for MySQL).
611    fn parameter_placeholder(&self, index: usize) -> String;
612    /// Returns the identifier quoting character (e.g., `"` for PG, `` ` `` for MySQL).
613    fn quote_identifier(&self, identifier: &str) -> String;
614    /// Returns the dialect-specific auto-increment syntax.
615    fn auto_increment_syntax(&self) -> &'static str;
616}
617
618/// ANSI SQL transaction isolation levels.
619#[derive(Debug, Clone, Copy, PartialEq, Eq)]
620pub enum IsolationLevel {
621    ReadUncommitted,
622    ReadCommitted,
623    RepeatableRead,
624    Serializable,
625}
626
627/// Trait for async database connections.
628#[async_trait]
629pub trait IAsyncConnection: Send + Sync {
630    /// Executes a query with parameters and returns the number of affected rows.
631    async fn execute(&mut self, sql: &str, params: &[DbValue]) -> EFResult<u64>;
632    /// Executes a query with parameters and returns rows.
633    async fn query(&mut self, sql: &str, params: &[DbValue]) -> EFResult<Vec<Vec<DbValue>>>;
634    /// Begins a transaction.
635    async fn begin_transaction(&mut self) -> EFResult<()>;
636    /// Commits the current transaction.
637    async fn commit_transaction(&mut self) -> EFResult<()>;
638    /// Rolls back the current transaction.
639    async fn rollback_transaction(&mut self) -> EFResult<()>;
640    /// Creates a savepoint within the current transaction.
641    async fn create_savepoint(&mut self, name: &str) -> EFResult<()>;
642    /// Releases (commits) a previously created savepoint, discarding its rollback point.
643    async fn release_savepoint(&mut self, name: &str) -> EFResult<()>;
644    /// Rolls back to the named savepoint, preserving the outer transaction.
645    async fn rollback_to_savepoint(&mut self, name: &str) -> EFResult<()>;
646    /// Sets the isolation level of the current transaction.
647    /// Must be called after `begin_transaction` and before any query.
648    async fn set_transaction_isolation(&mut self, level: IsolationLevel) -> EFResult<()>;
649
650    /// Sets the slow query threshold for this connection.
651    ///
652    /// Only available when the `tracing` feature is enabled on the core
653    /// crate. Default implementation is a no-op; provider connections
654    /// override to store the threshold for `QueryGuard` comparison.
655    #[cfg(feature = "tracing")]
656    fn set_slow_query_threshold(&mut self, _threshold: std::time::Duration) {}
657}
658
659/// The database provider abstraction.
660/// Corresponds to EFCore's provider model.
661#[async_trait]
662pub trait IDatabaseProvider: Send + Sync {
663    /// Returns the SQL dialect generator for this provider.
664    ///
665    /// Implementations are stateless, so a `&'static` reference is returned —
666    /// no heap allocation per call.
667    fn sql_generator(&self) -> &'static dyn ISqlGenerator;
668
669    /// Gets an async database connection from the pool.
670    async fn get_connection(&self) -> EFResult<Box<dyn IAsyncConnection>>;
671
672    /// Executes a migration command (DDL).
673    async fn execute_migration_command(&self, sql: &str) -> EFResult<()>;
674
675    /// Returns the provider name (e.g., "PostgreSQL", "MySQL").
676    fn name(&self) -> &str;
677
678    /// Returns the migration dialect for this provider.
679    fn migration_dialect(&self) -> crate::migration::MigrationDialect;
680
681    /// Sets the slow query threshold for all connections from this provider.
682    ///
683    /// Only available when the `tracing` feature is enabled on the core
684    /// crate. Default implementation is a no-op; providers override to
685    /// store the threshold and pass it to connections on acquisition.
686    #[cfg(feature = "tracing")]
687    fn set_slow_query_threshold(&self, _threshold: std::time::Duration) {}
688}