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