Skip to main content

sz_orm_sqlx/
any_driver.rs

1//! Any driver — 一套代码多 DB 后端透明切换(SQLx 风格)
2//!
3//! SQLx 提供 `sqlx::Any` 适配器,让同一份代码可以在 MySQL/PostgreSQL/SQLite
4//! 之间透明切换。SZ-ORM 在 `sz-orm-sqlx` 已有各后端独立实现,
5//! 此模块在上层提供统一的 [`AnyConnection`] 和 [`AnyPool`] 抽象,
6//! 让运行时切换数据库后端成为可能。
7//!
8//! # 设计
9//!
10//! - [`AnyBackend`]:枚举后端类型
11//! - [`AnyPool`]:持有具体后端的 `Box<dyn ConnectionFactory>`
12//! - [`AnyConnection`]:持有具体后端的 `Box<dyn Connection>`
13//! - 通过 DSN 自动识别后端类型,运行时透明切换
14//!
15//! # 用法
16//!
17//! ```text
18//! use sz_orm_sqlx::any_driver::{AnyBackend, AnyPool};
19//!
20//! // 从 DSN 自动识别后端
21//! let pool = AnyPool::connect("mysql://root:pass@127.0.0.1/db").await?;
22//! let mut conn = pool.create().await?;
23//! let rows = conn.query("SELECT 1").await?;
24//!
25//! // 运行时切换后端
26//! let pg_pool = AnyPool::connect("postgres://user:pass@127.0.0.1/db").await?;
27//! let mut pg_conn = pg_pool.create().await?;
28//! let rows = pg_conn.query("SELECT 1").await?;
29//! ```
30
31use std::future::Future;
32use std::pin::Pin;
33use std::sync::Arc;
34use sz_orm_core::{
35    Connection, ConnectionFactory, DbError, Dialect, MySqlDialect, OracleDialect,
36    PostgreSqlDialect, QueryRows, QueryStreamItem, QueryValues, SqlServerDialect, SqliteDialect,
37    Value,
38};
39
40use sz_orm_core::DbType;
41
42use crate::any::{
43    MySqlPoolHandle, PgPoolHandle, SqlitePoolHandle, SqlxMySqlConnectionFactory,
44    SqlxPgConnectionFactory, SqlxSqliteConnectionFactory,
45};
46
47#[cfg(feature = "oracle")]
48use sz_orm_oracle::{OracleConnectionFactory, OraclePoolHandle};
49
50#[cfg(feature = "mssql")]
51use sz_orm_mssql::{MssqlConnectionFactory, MssqlPoolHandle};
52
53/// 数据库后端类型
54///
55/// v2.2.0 新增 `Oracle` 和 `Mssql` 变体(需启用 `oracle`/`mssql` feature)。
56/// `#[non_exhaustive]` 标注确保外部 crate match 时必须使用 wildcard 臂,
57/// 未来新增变体不会破坏现有代码。
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[non_exhaustive]
60pub enum AnyBackend {
61    /// MySQL / MariaDB
62    MySql,
63    /// PostgreSQL
64    Postgres,
65    /// SQLite
66    Sqlite,
67    /// Oracle(v2.2.0 新增,需启用 `oracle` feature)
68    Oracle,
69    /// SQL Server / MSSQL(v2.2.0 新增,需启用 `mssql` feature)
70    Mssql,
71}
72
73impl AnyBackend {
74    /// 从 DSN 自动识别后端类型
75    ///
76    /// # 支持的 scheme
77    ///
78    /// - `mysql://` / `mariadb://` → MySQL
79    /// - `postgres://` / `postgresql://` → Postgres
80    /// - `sqlite://` / `sqlite:` → Sqlite
81    /// - `oracle://` → Oracle(v2.2.0 新增)
82    /// - `mssql://` / `sqlserver://` → Mssql(v2.2.0 新增)
83    pub fn from_dsn(dsn: &str) -> Result<Self, DbError> {
84        if dsn.starts_with("mysql://") || dsn.starts_with("mariadb://") {
85            Ok(AnyBackend::MySql)
86        } else if dsn.starts_with("postgres://") || dsn.starts_with("postgresql://") {
87            Ok(AnyBackend::Postgres)
88        } else if dsn.starts_with("sqlite://") || dsn.starts_with("sqlite:") {
89            Ok(AnyBackend::Sqlite)
90        } else if dsn.starts_with("oracle://") {
91            Ok(AnyBackend::Oracle)
92        } else if dsn.starts_with("mssql://") || dsn.starts_with("sqlserver://") {
93            Ok(AnyBackend::Mssql)
94        } else {
95            Err(DbError::ConnectionRefused(format!(
96                "未知的 DSN scheme: {}(支持 mysql/postgres/sqlite/oracle/mssql)",
97                dsn
98            )))
99        }
100    }
101
102    /// 从 [`DbType`] 转换为 [`AnyBackend`]
103    ///
104    /// 返回 `None` 的情况:DbType 对应的后端不在 sz-orm-sqlx 支持范围内
105    /// (如 Redis、MongoDB、ClickHouse 等非关系型数据库)。
106    ///
107    /// # 映射
108    ///
109    /// - `DbType::MySQL` / `DbType::MariaDB` / `DbType::TiDB` / `DbType::OceanBase` → MySql
110    /// - `DbType::PostgreSQL` / `DbType::Kingbase` / `DbType::PolarDB` / `DbType::GaussDB` → Postgres
111    /// - `DbType::Sqlite` → Sqlite
112    /// - `DbType::Oracle` / `DbType::Dameng` → Oracle
113    /// - `DbType::SqlServer` / `DbType::Sybase` / `DbType::GBase` → Mssql
114    /// - 其他 → None
115    pub fn from_db_type(db_type: DbType) -> Option<Self> {
116        match db_type {
117            DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => {
118                Some(AnyBackend::MySql)
119            }
120            DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
121                Some(AnyBackend::Postgres)
122            }
123            DbType::Sqlite => Some(AnyBackend::Sqlite),
124            DbType::Oracle | DbType::Dameng => Some(AnyBackend::Oracle),
125            DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(AnyBackend::Mssql),
126            _ => None,
127        }
128    }
129
130    /// 后端名称
131    pub fn name(&self) -> &'static str {
132        match self {
133            AnyBackend::MySql => "mysql",
134            AnyBackend::Postgres => "postgres",
135            AnyBackend::Sqlite => "sqlite",
136            AnyBackend::Oracle => "oracle",
137            AnyBackend::Mssql => "mssql",
138        }
139    }
140
141    /// 返回对应后端的 Dialect 实例(v2.2.0 新增)
142    ///
143    /// - MySql → [`MySqlDialect`]
144    /// - Postgres → [`PostgreSqlDialect`]
145    /// - Sqlite → [`SqliteDialect`]
146    /// - Oracle → [`OracleDialect`]
147    /// - Mssql → [`SqlServerDialect`]
148    pub fn dialect(&self) -> Box<dyn Dialect> {
149        match self {
150            AnyBackend::MySql => Box::new(MySqlDialect),
151            AnyBackend::Postgres => Box::new(PostgreSqlDialect),
152            AnyBackend::Sqlite => Box::new(SqliteDialect),
153            AnyBackend::Oracle => Box::new(OracleDialect),
154            AnyBackend::Mssql => Box::new(SqlServerDialect),
155        }
156    }
157}
158
159/// 后端无关的连接工厂
160pub struct AnyPool {
161    backend: AnyBackend,
162    factory: Arc<dyn ConnectionFactory>,
163}
164
165impl AnyPool {
166    /// 连接数据库,根据 DSN 自动识别后端
167    ///
168    /// # 错误
169    ///
170    /// - DSN scheme 不识别 → [`DbError::ConnectionRefused`]
171    /// - 连接失败 → [`DbError::ConnectionError`]
172    /// - Oracle/MSSQL 后端未启用对应 feature → [`DbError::ConnectionRefused`] 含提示
173    pub async fn connect(dsn: &str) -> Result<Self, DbError> {
174        let backend = AnyBackend::from_dsn(dsn)?;
175        let factory: Arc<dyn ConnectionFactory> = match backend {
176            AnyBackend::MySql => {
177                let handle = Arc::new(MySqlPoolHandle::connect(dsn).await?);
178                Arc::new(SqlxMySqlConnectionFactory::new(handle))
179            }
180            AnyBackend::Postgres => {
181                let handle = Arc::new(PgPoolHandle::connect(dsn).await?);
182                Arc::new(SqlxPgConnectionFactory::new(handle))
183            }
184            AnyBackend::Sqlite => {
185                let handle = Arc::new(SqlitePoolHandle::connect(dsn).await?);
186                Arc::new(SqlxSqliteConnectionFactory::new(handle))
187            }
188            AnyBackend::Oracle => {
189                #[cfg(feature = "oracle")]
190                {
191                    let (username, password, connect_string) = parse_oracle_dsn(dsn)?;
192                    let handle = Arc::new(OraclePoolHandle::connect(
193                        &username,
194                        &password,
195                        &connect_string,
196                    )?);
197                    Arc::new(OracleConnectionFactory::new(handle))
198                }
199                #[cfg(not(feature = "oracle"))]
200                {
201                    return Err(DbError::ConnectionRefused(
202                        "Oracle 后端未启用,请在 Cargo.toml 中添加 features = [\"oracle\"]"
203                            .to_string(),
204                    ));
205                }
206            }
207            AnyBackend::Mssql => {
208                #[cfg(feature = "mssql")]
209                {
210                    let ado_string = parse_mssql_dsn(dsn)?;
211                    let handle = Arc::new(MssqlPoolHandle::connect(&ado_string).await?);
212                    Arc::new(MssqlConnectionFactory::new(handle))
213                }
214                #[cfg(not(feature = "mssql"))]
215                {
216                    return Err(DbError::ConnectionRefused(
217                        "MSSQL 后端未启用,请在 Cargo.toml 中添加 features = [\"mssql\"]"
218                            .to_string(),
219                    ));
220                }
221            }
222        };
223        Ok(Self { backend, factory })
224    }
225
226    /// 从已有的连接工厂构造
227    pub fn from_factory(backend: AnyBackend, factory: Arc<dyn ConnectionFactory>) -> Self {
228        Self { backend, factory }
229    }
230
231    /// 获取后端类型
232    pub fn backend(&self) -> AnyBackend {
233        self.backend
234    }
235
236    /// 返回对应后端的 Dialect 实例(v2.2.0 新增)
237    ///
238    /// 委托 [`AnyBackend::dialect()`],根据后端自动选择方言。
239    pub fn dialect(&self) -> Box<dyn Dialect> {
240        self.backend.dialect()
241    }
242
243    /// 创建一个新连接
244    pub async fn create(&self) -> Result<AnyConnection, DbError> {
245        let conn = self.factory.create().await?;
246        Ok(AnyConnection {
247            backend: self.backend,
248            inner: conn,
249        })
250    }
251}
252
253/// 统一连接便利函数:按 DSN scheme 自动识别后端并创建连接
254///
255/// 这是 sz-orm-sqlx 对外提供的统一入口,下游项目(如 sz-rust)只需调用此函数,
256/// 无需为每种数据库编写独立的连接逻辑。Oracle/MSSQL 支持通过 feature gate 启用。
257///
258/// # 支持的 DSN scheme
259///
260/// - `mysql://` / `mariadb://` → MySQL
261/// - `postgres://` / `postgresql://` → PostgreSQL
262/// - `sqlite://` / `sqlite:` → SQLite
263/// - `oracle://` → Oracle(需启用 `oracle` feature)
264/// - `mssql://` / `sqlserver://` → MSSQL(需启用 `mssql` feature)
265///
266/// # 错误
267///
268/// - DSN scheme 不识别 → [`DbError::ConnectionRefused`]
269/// - Oracle/MSSQL 后端未启用对应 feature → [`DbError::ConnectionRefused`] 含提示
270/// - 连接失败 → [`DbError::ConnectionError`]
271///
272/// # 示例
273///
274/// ```no_run
275/// # #[tokio::main]
276/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
277/// use sz_orm_core::Connection;
278/// let mut conn = sz_orm_sqlx::create_connection("sqlite::memory:").await?;
279/// conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY)").await?;
280/// # Ok(())
281/// # }
282/// ```
283pub async fn create_connection(dsn: &str) -> Result<Box<dyn Connection>, DbError> {
284    let pool = AnyPool::connect(dsn).await?;
285    let conn = pool.create().await?;
286    Ok(Box::new(conn))
287}
288
289/// 按 [`DbType`] 创建连接
290///
291/// 当 DSN 的 scheme 可能与 `db_type` 不一致时使用此函数。
292/// 会根据 `db_type` 修正 DSN 的 scheme 后再连接。
293///
294/// # 示例
295///
296/// ```no_run
297/// # #[tokio::main]
298/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
299/// use sz_orm_core::{Connection, DbType};
300/// let mut conn = sz_orm_sqlx::create_connection_by_type(
301///     DbType::Sqlite, "sqlite::memory:"
302/// ).await?;
303/// conn.execute("SELECT 1").await?;
304/// # Ok(())
305/// # }
306/// ```
307pub async fn create_connection_by_type(
308    db_type: DbType,
309    dsn: &str,
310) -> Result<Box<dyn Connection>, DbError> {
311    let backend = AnyBackend::from_db_type(db_type).ok_or_else(|| {
312        DbError::ConnectionRefused(format!(
313            "DbType {:?} 不在 sz-orm-sqlx 支持范围内(支持 MySQL/PostgreSQL/SQLite/Oracle/MSSQL 及兼容方言)",
314            db_type
315        ))
316    })?;
317    let normalized = normalize_dsn_scheme(backend, dsn);
318    create_connection(&normalized).await
319}
320
321/// 根据 [`AnyBackend`] 修正 DSN 的 scheme
322///
323/// 如果 DSN 已有正确的 scheme 则原样返回;
324/// 否则替换 scheme 部分以匹配目标后端。
325fn normalize_dsn_scheme(backend: AnyBackend, dsn: &str) -> String {
326    let expected_scheme = match backend {
327        AnyBackend::MySql => "mysql://",
328        AnyBackend::Postgres => "postgres://",
329        AnyBackend::Sqlite => "sqlite:",
330        AnyBackend::Oracle => "oracle://",
331        AnyBackend::Mssql => "mssql://",
332    };
333    let known_schemes = [
334        "mysql://",
335        "mariadb://",
336        "postgres://",
337        "postgresql://",
338        "sqlite://",
339        "sqlite:",
340        "oracle://",
341        "mssql://",
342        "sqlserver://",
343    ];
344    for scheme in &known_schemes {
345        if let Some(rest) = dsn.strip_prefix(scheme) {
346            if backend == AnyBackend::from_dsn(dsn).unwrap_or(AnyBackend::MySql) {
347                return dsn.to_string();
348            }
349            return format!("{}{}", expected_scheme, rest);
350        }
351    }
352    dsn.to_string()
353}
354
355/// 解析 Oracle DSN 为 (username, password, connect_string)
356///
357/// 格式:`oracle://user:pass@host:port/service`
358///
359/// 返回:username="user", password="pass", connect_string="host:port/service"
360#[allow(dead_code)]
361pub(crate) fn parse_oracle_dsn(dsn: &str) -> Result<(String, String, String), DbError> {
362    let rest = dsn
363        .strip_prefix("oracle://")
364        .ok_or_else(|| DbError::ConnectionRefused(format!("无效的 Oracle DSN: {}", dsn)))?;
365    parse_user_pass_host(rest, "Oracle")
366}
367
368/// 解析 MSSQL DSN 为 ADO.NET 连接字符串
369///
370/// 格式:`mssql://user:pass@host:port/database` 或 `sqlserver://user:pass@host:port/database`
371///
372/// 返回:`Server=host,port;Database=database;User Id=user;Password=pass;`
373#[allow(dead_code)]
374pub(crate) fn parse_mssql_dsn(dsn: &str) -> Result<String, DbError> {
375    let rest = dsn
376        .strip_prefix("mssql://")
377        .or_else(|| dsn.strip_prefix("sqlserver://"))
378        .ok_or_else(|| DbError::ConnectionRefused(format!("无效的 MSSQL DSN: {}", dsn)))?;
379    let (username, password, host_port_db) = parse_user_pass_host(rest, "MSSQL")?;
380    let (host_port, database) = host_port_db
381        .split_once('/')
382        .ok_or_else(|| DbError::ConnectionRefused(format!("MSSQL DSN 缺少 database: {}", dsn)))?;
383    let (host, port) = host_port.split_once(':').unwrap_or((host_port, "1433"));
384    Ok(format!(
385        "Server={},{};Database={};User Id={};Password={};",
386        host, port, database, username, password
387    ))
388}
389
390/// 通用 DSN 解析:`user:pass@host:port/...` → (user, pass, host:port/...)
391#[allow(dead_code)]
392fn parse_user_pass_host(rest: &str, backend: &str) -> Result<(String, String, String), DbError> {
393    let (userinfo, hostinfo) = rest
394        .split_once('@')
395        .ok_or_else(|| DbError::ConnectionRefused(format!("{} DSN 缺少 @: {}", backend, rest)))?;
396    let (username, password) = userinfo.split_once(':').ok_or_else(|| {
397        DbError::ConnectionRefused(format!("{} DSN 缺少 password: {}", backend, rest))
398    })?;
399    Ok((
400        username.to_string(),
401        password.to_string(),
402        hostinfo.to_string(),
403    ))
404}
405
406/// 后端无关的连接
407pub struct AnyConnection {
408    backend: AnyBackend,
409    inner: Box<dyn Connection>,
410}
411
412impl AnyConnection {
413    /// 获取后端类型
414    pub fn backend(&self) -> AnyBackend {
415        self.backend
416    }
417}
418
419impl Connection for AnyConnection {
420    fn execute<'a>(
421        &'a mut self,
422        sql: &'a str,
423    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
424        self.inner.execute(sql)
425    }
426
427    fn query<'a>(
428        &'a mut self,
429        sql: &'a str,
430    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
431        self.inner.query(sql)
432    }
433
434    fn begin_transaction<'a>(
435        &'a mut self,
436    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
437        self.inner.begin_transaction()
438    }
439
440    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
441        self.inner.commit()
442    }
443
444    fn rollback<'a>(
445        &'a mut self,
446    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
447        self.inner.rollback()
448    }
449
450    fn in_transaction(&self) -> bool {
451        self.inner.in_transaction()
452    }
453
454    fn is_connected(&self) -> bool {
455        self.inner.is_connected()
456    }
457
458    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
459        self.inner.ping()
460    }
461
462    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
463        self.inner.close()
464    }
465
466    fn execute_with_params<'a>(
467        &'a mut self,
468        sql: &'a str,
469        params: &'a [Value],
470    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
471        self.inner.execute_with_params(sql, params)
472    }
473
474    fn query_with_params<'a>(
475        &'a mut self,
476        sql: &'a str,
477        params: &'a [Value],
478    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
479        self.inner.query_with_params(sql, params)
480    }
481
482    fn query_values<'a>(
483        &'a mut self,
484        sql: &'a str,
485    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
486        self.inner.query_values(sql)
487    }
488
489    fn query_values_with_params<'a>(
490        &'a mut self,
491        sql: &'a str,
492        params: &'a [Value],
493    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
494        self.inner.query_values_with_params(sql, params)
495    }
496
497    fn query_stream<'a>(
498        &'a mut self,
499        sql: &'a str,
500    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
501        self.inner.query_stream(sql)
502    }
503
504    fn query_stream_cursor<'a>(
505        &'a mut self,
506        sql: &'a str,
507        batch_size: usize,
508    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
509        self.inner.query_stream_cursor(sql, batch_size)
510    }
511
512    fn execute_batch<'a>(
513        &'a mut self,
514        sqls: &'a [String],
515    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
516        self.inner.execute_batch(sqls)
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523
524    #[test]
525    fn test_any_backend_from_dsn_mysql() {
526        assert_eq!(
527            AnyBackend::from_dsn("mysql://root:pass@127.0.0.1/db").unwrap(),
528            AnyBackend::MySql
529        );
530        assert_eq!(
531            AnyBackend::from_dsn("mariadb://root:pass@127.0.0.1/db").unwrap(),
532            AnyBackend::MySql
533        );
534    }
535
536    #[test]
537    fn test_any_backend_from_dsn_postgres() {
538        assert_eq!(
539            AnyBackend::from_dsn("postgres://user:pass@127.0.0.1/db").unwrap(),
540            AnyBackend::Postgres
541        );
542        assert_eq!(
543            AnyBackend::from_dsn("postgresql://user:pass@127.0.0.1/db").unwrap(),
544            AnyBackend::Postgres
545        );
546    }
547
548    #[test]
549    fn test_any_backend_from_dsn_sqlite() {
550        assert_eq!(
551            AnyBackend::from_dsn("sqlite::memory:").unwrap(),
552            AnyBackend::Sqlite
553        );
554        assert_eq!(
555            AnyBackend::from_dsn("sqlite://./test.db").unwrap(),
556            AnyBackend::Sqlite
557        );
558    }
559
560    #[test]
561    fn test_any_backend_from_dsn_oracle() {
562        assert_eq!(
563            AnyBackend::from_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap(),
564            AnyBackend::Oracle
565        );
566    }
567
568    #[test]
569    fn test_any_backend_from_dsn_mssql() {
570        assert_eq!(
571            AnyBackend::from_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap(),
572            AnyBackend::Mssql
573        );
574    }
575
576    #[test]
577    fn test_any_backend_from_dsn_sqlserver() {
578        assert_eq!(
579            AnyBackend::from_dsn("sqlserver://sa:test123@127.0.0.1:1433/testdb").unwrap(),
580            AnyBackend::Mssql
581        );
582    }
583
584    #[test]
585    fn test_any_backend_from_dsn_unknown_v22() {
586        let result = AnyBackend::from_dsn("redis://127.0.0.1");
587        assert!(result.is_err());
588        if let Err(e) = result {
589            let msg = format!("{}", e);
590            assert!(msg.contains("mysql"));
591            assert!(msg.contains("postgres"));
592            assert!(msg.contains("sqlite"));
593            assert!(msg.contains("oracle"));
594            assert!(msg.contains("mssql"));
595        }
596    }
597
598    #[test]
599    fn test_any_backend_name_v22() {
600        assert_eq!(AnyBackend::MySql.name(), "mysql");
601        assert_eq!(AnyBackend::Postgres.name(), "postgres");
602        assert_eq!(AnyBackend::Sqlite.name(), "sqlite");
603        assert_eq!(AnyBackend::Oracle.name(), "oracle");
604        assert_eq!(AnyBackend::Mssql.name(), "mssql");
605    }
606
607    #[test]
608    fn test_any_backend_equality() {
609        assert_eq!(AnyBackend::MySql, AnyBackend::MySql);
610        assert_ne!(AnyBackend::MySql, AnyBackend::Postgres);
611        assert_ne!(AnyBackend::Postgres, AnyBackend::Sqlite);
612        assert_ne!(AnyBackend::Oracle, AnyBackend::Mssql);
613        assert_ne!(AnyBackend::Oracle, AnyBackend::MySql);
614    }
615
616    #[test]
617    fn test_parse_oracle_dsn() {
618        let (user, pass, cs) =
619            parse_oracle_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap();
620        assert_eq!(user, "sys");
621        assert_eq!(pass, "test123");
622        assert_eq!(cs, "127.0.0.1:1521/freepdb1");
623    }
624
625    #[test]
626    fn test_parse_mssql_dsn() {
627        let ado = parse_mssql_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap();
628        assert!(ado.contains("Server=127.0.0.1,1433"));
629        assert!(ado.contains("Database=testdb"));
630        assert!(ado.contains("User Id=sa"));
631        assert!(ado.contains("Password=test123"));
632    }
633
634    #[test]
635    fn test_parse_mssql_dsn_sqlserver_scheme() {
636        let ado = parse_mssql_dsn("sqlserver://sa:pass@localhost/testdb").unwrap();
637        assert!(ado.contains("Server=localhost,1433"));
638        assert!(ado.contains("Database=testdb"));
639    }
640
641    // ---- 真实 SQLite 集成测试 ----
642
643    #[tokio::test]
644    async fn test_any_pool_sqlite_connect_and_query() {
645        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
646        assert_eq!(pool.backend(), AnyBackend::Sqlite);
647
648        let mut conn = pool.create().await.unwrap();
649        assert_eq!(conn.backend(), AnyBackend::Sqlite);
650
651        // 创建表并插入数据
652        conn.execute("CREATE TABLE test_any (id INTEGER PRIMARY KEY, name TEXT)")
653            .await
654            .unwrap();
655        conn.execute("INSERT INTO test_any (name) VALUES ('Alice')")
656            .await
657            .unwrap();
658        conn.execute("INSERT INTO test_any (name) VALUES ('Bob')")
659            .await
660            .unwrap();
661
662        // 查询验证
663        let rows = conn
664            .query("SELECT * FROM test_any ORDER BY id")
665            .await
666            .unwrap();
667        assert_eq!(rows.len(), 2);
668        assert_eq!(rows[0].get("name").and_then(|v| v.as_str()), Some("Alice"));
669        assert_eq!(rows[1].get("name").and_then(|v| v.as_str()), Some("Bob"));
670    }
671
672    #[tokio::test]
673    async fn test_any_pool_sqlite_transaction_commit() {
674        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
675        let mut conn = pool.create().await.unwrap();
676
677        conn.execute("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val INTEGER)")
678            .await
679            .unwrap();
680
681        // 事务提交
682        conn.begin_transaction().await.unwrap();
683        conn.execute("INSERT INTO tx_test (val) VALUES (1)")
684            .await
685            .unwrap();
686        conn.execute("INSERT INTO tx_test (val) VALUES (2)")
687            .await
688            .unwrap();
689        conn.commit().await.unwrap();
690
691        let rows = conn.query("SELECT * FROM tx_test").await.unwrap();
692        assert_eq!(rows.len(), 2);
693    }
694
695    #[tokio::test]
696    async fn test_any_pool_sqlite_transaction_rollback() {
697        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
698        let mut conn = pool.create().await.unwrap();
699
700        conn.execute("CREATE TABLE tx_rb (id INTEGER PRIMARY KEY, val INTEGER)")
701            .await
702            .unwrap();
703
704        conn.begin_transaction().await.unwrap();
705        conn.execute("INSERT INTO tx_rb (val) VALUES (1)")
706            .await
707            .unwrap();
708        conn.rollback().await.unwrap();
709
710        let rows = conn.query("SELECT * FROM tx_rb").await.unwrap();
711        assert_eq!(rows.len(), 0);
712    }
713
714    #[tokio::test]
715    async fn test_any_connection_in_transaction_state_tracking() {
716        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
717        let mut conn = pool.create().await.unwrap();
718
719        assert!(!conn.in_transaction(), "新建连接不应处于事务中");
720
721        conn.begin_transaction().await.unwrap();
722        assert!(conn.in_transaction(), "begin 后应处于事务中");
723
724        conn.execute("CREATE TABLE tx_track (id INTEGER PRIMARY KEY)")
725            .await
726            .unwrap();
727        assert!(conn.in_transaction(), "事务内执行 SQL 后仍应处于事务中");
728
729        conn.commit().await.unwrap();
730        assert!(!conn.in_transaction(), "commit 后不应处于事务中");
731
732        conn.begin_transaction().await.unwrap();
733        assert!(conn.in_transaction());
734        conn.rollback().await.unwrap();
735        assert!(!conn.in_transaction(), "rollback 后不应处于事务中");
736    }
737
738    #[tokio::test]
739    async fn test_any_connection_in_transaction_leak_detection() {
740        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
741        let mut conn = pool.create().await.unwrap();
742
743        conn.execute("CREATE TABLE tx_leak (id INTEGER PRIMARY KEY)")
744            .await
745            .unwrap();
746
747        conn.begin_transaction().await.unwrap();
748        conn.execute("INSERT INTO tx_leak (id) VALUES (1)")
749            .await
750            .unwrap();
751
752        assert!(
753            conn.in_transaction(),
754            "未提交事务应被检测到(泄漏防护前提)"
755        );
756
757        conn.rollback().await.unwrap();
758        assert!(!conn.in_transaction());
759    }
760
761    #[tokio::test]
762    async fn test_any_pool_sqlite_ping() {
763        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
764        let mut conn = pool.create().await.unwrap();
765        let ok = conn.ping().await;
766        assert!(ok);
767        assert!(conn.is_connected());
768    }
769
770    #[tokio::test]
771    async fn test_any_pool_invalid_dsn() {
772        let result = AnyPool::connect("invalid://dsn").await;
773        assert!(result.is_err());
774    }
775
776    #[tokio::test]
777    async fn test_any_pool_sqlite_count_query() {
778        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
779        let mut conn = pool.create().await.unwrap();
780
781        conn.execute("CREATE TABLE cnt (id INTEGER PRIMARY KEY)")
782            .await
783            .unwrap();
784        for i in 1..=5 {
785            conn.execute(&format!("INSERT INTO cnt (id) VALUES ({})", i))
786                .await
787                .unwrap();
788        }
789
790        // 通过 SELECT * 验证行数(避开 sqlx 适配器中 COUNT(*) 类型推断的既有问题)
791        let rows = conn.query("SELECT * FROM cnt").await.unwrap();
792        assert_eq!(rows.len(), 5);
793    }
794
795    // ---- v2.2.0 A-2: Dialect 与 AnyPool 集成测试 ----
796
797    #[test]
798    fn test_any_backend_dialect_mapping() {
799        use sz_orm_core::DbType;
800
801        let mysql_d = AnyBackend::MySql.dialect();
802        assert_eq!(mysql_d.db_type(), DbType::MySQL);
803
804        let pg_d = AnyBackend::Postgres.dialect();
805        assert_eq!(pg_d.db_type(), DbType::PostgreSQL);
806
807        let sqlite_d = AnyBackend::Sqlite.dialect();
808        assert_eq!(sqlite_d.db_type(), DbType::Sqlite);
809
810        let oracle_d = AnyBackend::Oracle.dialect();
811        assert_eq!(oracle_d.db_type(), DbType::Oracle);
812
813        let mssql_d = AnyBackend::Mssql.dialect();
814        assert_eq!(mssql_d.db_type(), DbType::SqlServer);
815    }
816
817    #[test]
818    fn test_oracle_dialect_pagination() {
819        let d = AnyBackend::Oracle.dialect();
820        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
821        let upper = sql.to_uppercase();
822        assert!(
823            upper.contains("OFFSET") || upper.contains("FETCH") || upper.contains("ROWNUM"),
824            "Oracle 分页 SQL 应含 OFFSET/FETCH/ROWNUM,实际: {}",
825            sql
826        );
827        assert!(
828            !upper.contains("LIMIT"),
829            "Oracle 分页 SQL 不应含 LIMIT,实际: {}",
830            sql
831        );
832    }
833
834    #[test]
835    fn test_mssql_dialect_pagination() {
836        let d = AnyBackend::Mssql.dialect();
837        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
838        let upper = sql.to_uppercase();
839        assert!(
840            upper.contains("OFFSET") || upper.contains("FETCH"),
841            "MSSQL 分页 SQL 应含 OFFSET/FETCH,实际: {}",
842            sql
843        );
844        assert!(
845            !upper.contains("LIMIT"),
846            "MSSQL 分页 SQL 不应含 LIMIT,实际: {}",
847            sql
848        );
849    }
850
851    #[test]
852    fn test_oracle_dialect_no_placeholder() {
853        let d = AnyBackend::Oracle.dialect();
854        // 调用各 Dialect trait 方法,确认不 panic(无 todo!/unimplemented!)
855        let _ = d.db_type();
856        let _ = d.quote("col");
857        let _ = d.quote_checked("col").unwrap();
858        let _ = d.escape_string("val");
859        let _ = d.supports_returning();
860        let _ = d.build_pagination("SELECT 1", 1, 10);
861        let _ = d.json_type();
862        let _ = d.json_extract("col", "$.key");
863        let _ = d.full_text_search(&["col"], "kw");
864        let _ = d.bool_to_int("expr");
865        let _ = d.concat(&["a", "b"]);
866    }
867
868    #[test]
869    fn test_mssql_dialect_no_placeholder() {
870        let d = AnyBackend::Mssql.dialect();
871        let _ = d.db_type();
872        let _ = d.quote("col");
873        let _ = d.quote_checked("col").unwrap();
874        let _ = d.escape_string("val");
875        let _ = d.supports_returning();
876        let _ = d.build_pagination("SELECT 1", 1, 10);
877        let _ = d.json_type();
878        let _ = d.json_extract("col", "$.key");
879        let _ = d.full_text_search(&["col"], "kw");
880        let _ = d.bool_to_int("expr");
881        let _ = d.concat(&["a", "b"]);
882    }
883
884    #[tokio::test]
885    async fn test_any_pool_dialect_sqlite() {
886        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
887        let d = pool.dialect();
888        assert_eq!(d.db_type(), sz_orm_core::DbType::Sqlite);
889    }
890
891    // ---- v6.9.0: from_db_type / create_connection / create_connection_by_type ----
892
893    #[test]
894    fn test_from_db_type_supported() {
895        use sz_orm_core::DbType;
896        assert_eq!(
897            AnyBackend::from_db_type(DbType::MySQL),
898            Some(AnyBackend::MySql)
899        );
900        assert_eq!(
901            AnyBackend::from_db_type(DbType::MariaDB),
902            Some(AnyBackend::MySql)
903        );
904        assert_eq!(
905            AnyBackend::from_db_type(DbType::TiDB),
906            Some(AnyBackend::MySql)
907        );
908        assert_eq!(
909            AnyBackend::from_db_type(DbType::OceanBase),
910            Some(AnyBackend::MySql)
911        );
912        assert_eq!(
913            AnyBackend::from_db_type(DbType::PostgreSQL),
914            Some(AnyBackend::Postgres)
915        );
916        assert_eq!(
917            AnyBackend::from_db_type(DbType::Kingbase),
918            Some(AnyBackend::Postgres)
919        );
920        assert_eq!(
921            AnyBackend::from_db_type(DbType::PolarDB),
922            Some(AnyBackend::Postgres)
923        );
924        assert_eq!(
925            AnyBackend::from_db_type(DbType::GaussDB),
926            Some(AnyBackend::Postgres)
927        );
928        assert_eq!(
929            AnyBackend::from_db_type(DbType::Sqlite),
930            Some(AnyBackend::Sqlite)
931        );
932        assert_eq!(
933            AnyBackend::from_db_type(DbType::Oracle),
934            Some(AnyBackend::Oracle)
935        );
936        assert_eq!(
937            AnyBackend::from_db_type(DbType::Dameng),
938            Some(AnyBackend::Oracle)
939        );
940        assert_eq!(
941            AnyBackend::from_db_type(DbType::SqlServer),
942            Some(AnyBackend::Mssql)
943        );
944        assert_eq!(
945            AnyBackend::from_db_type(DbType::Sybase),
946            Some(AnyBackend::Mssql)
947        );
948        assert_eq!(
949            AnyBackend::from_db_type(DbType::GBase),
950            Some(AnyBackend::Mssql)
951        );
952    }
953
954    #[test]
955    fn test_from_db_type_unsupported() {
956        use sz_orm_core::DbType;
957        assert_eq!(AnyBackend::from_db_type(DbType::Redis), None);
958        assert_eq!(AnyBackend::from_db_type(DbType::MongoDB), None);
959        assert_eq!(AnyBackend::from_db_type(DbType::ClickHouse), None);
960        assert_eq!(AnyBackend::from_db_type(DbType::VectorDb), None);
961    }
962
963    #[tokio::test]
964    async fn test_create_connection_sqlite() {
965        let mut conn = create_connection("sqlite::memory:").await.unwrap();
966        conn.execute("CREATE TABLE cc (id INTEGER PRIMARY KEY, v TEXT)")
967            .await
968            .unwrap();
969        conn.execute("INSERT INTO cc (v) VALUES ('ok')")
970            .await
971            .unwrap();
972        let rows = conn.query("SELECT * FROM cc").await.unwrap();
973        assert_eq!(rows.len(), 1);
974    }
975
976    #[tokio::test]
977    async fn test_create_connection_invalid_dsn() {
978        let result = create_connection("invalid://dsn").await;
979        assert!(result.is_err());
980    }
981
982    #[tokio::test]
983    async fn test_create_connection_by_type_sqlite() {
984        use sz_orm_core::DbType;
985        let mut conn = create_connection_by_type(DbType::Sqlite, "sqlite::memory:")
986            .await
987            .unwrap();
988        conn.execute("SELECT 1").await.unwrap();
989    }
990
991    #[tokio::test]
992    async fn test_create_connection_by_type_unsupported() {
993        use sz_orm_core::DbType;
994        let result = create_connection_by_type(DbType::Redis, "redis://127.0.0.1").await;
995        assert!(result.is_err());
996    }
997
998    #[test]
999    fn test_normalize_dsn_scheme_already_correct() {
1000        assert_eq!(
1001            normalize_dsn_scheme(AnyBackend::MySql, "mysql://root:pass@host/db"),
1002            "mysql://root:pass@host/db"
1003        );
1004        assert_eq!(
1005            normalize_dsn_scheme(AnyBackend::Postgres, "postgres://user@host/db"),
1006            "postgres://user@host/db"
1007        );
1008        assert_eq!(
1009            normalize_dsn_scheme(AnyBackend::Oracle, "oracle://sys:pass@host/svc"),
1010            "oracle://sys:pass@host/svc"
1011        );
1012    }
1013
1014    #[test]
1015    fn test_normalize_dsn_scheme_fix_mismatch() {
1016        let fixed = normalize_dsn_scheme(AnyBackend::MySql, "postgres://root:pass@host/db");
1017        assert!(fixed.starts_with("mysql://"));
1018        assert!(fixed.contains("root:pass@host/db"));
1019    }
1020}