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    /// 获取连接工厂引用(v9.5.1 新增)
253    ///
254    /// 供 `Pool::new(config, factory)` 创建 `Pool` 以使用 `AutoHealer` 等池级功能。
255    pub fn factory(&self) -> Arc<dyn ConnectionFactory> {
256        self.factory.clone()
257    }
258}
259
260/// 统一连接便利函数:按 DSN scheme 自动识别后端并创建连接
261///
262/// 这是 sz-orm-sqlx 对外提供的统一入口,下游项目(如 sz-rust)只需调用此函数,
263/// 无需为每种数据库编写独立的连接逻辑。Oracle/MSSQL 支持通过 feature gate 启用。
264///
265/// # 支持的 DSN scheme
266///
267/// - `mysql://` / `mariadb://` → MySQL
268/// - `postgres://` / `postgresql://` → PostgreSQL
269/// - `sqlite://` / `sqlite:` → SQLite
270/// - `oracle://` → Oracle(需启用 `oracle` feature)
271/// - `mssql://` / `sqlserver://` → MSSQL(需启用 `mssql` feature)
272///
273/// # 错误
274///
275/// - DSN scheme 不识别 → [`DbError::ConnectionRefused`]
276/// - Oracle/MSSQL 后端未启用对应 feature → [`DbError::ConnectionRefused`] 含提示
277/// - 连接失败 → [`DbError::ConnectionError`]
278///
279/// # 示例
280///
281/// ```no_run
282/// # #[tokio::main]
283/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
284/// use sz_orm_core::Connection;
285/// let mut conn = sz_orm_sqlx::create_connection("sqlite::memory:").await?;
286/// conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY)").await?;
287/// # Ok(())
288/// # }
289/// ```
290pub async fn create_connection(dsn: &str) -> Result<Box<dyn Connection>, DbError> {
291    let pool = AnyPool::connect(dsn).await?;
292    let conn = pool.create().await?;
293    Ok(Box::new(conn))
294}
295
296/// 按 [`DbType`] 创建连接
297///
298/// 当 DSN 的 scheme 可能与 `db_type` 不一致时使用此函数。
299/// 会根据 `db_type` 修正 DSN 的 scheme 后再连接。
300///
301/// # 示例
302///
303/// ```no_run
304/// # #[tokio::main]
305/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
306/// use sz_orm_core::{Connection, DbType};
307/// let mut conn = sz_orm_sqlx::create_connection_by_type(
308///     DbType::Sqlite, "sqlite::memory:"
309/// ).await?;
310/// conn.execute("SELECT 1").await?;
311/// # Ok(())
312/// # }
313/// ```
314pub async fn create_connection_by_type(
315    db_type: DbType,
316    dsn: &str,
317) -> Result<Box<dyn Connection>, DbError> {
318    let backend = AnyBackend::from_db_type(db_type).ok_or_else(|| {
319        DbError::ConnectionRefused(format!(
320            "DbType {:?} 不在 sz-orm-sqlx 支持范围内(支持 MySQL/PostgreSQL/SQLite/Oracle/MSSQL 及兼容方言)",
321            db_type
322        ))
323    })?;
324    let normalized = normalize_dsn_scheme(backend, dsn);
325    create_connection(&normalized).await
326}
327
328/// 根据 [`AnyBackend`] 修正 DSN 的 scheme
329///
330/// 如果 DSN 已有正确的 scheme 则原样返回;
331/// 否则替换 scheme 部分以匹配目标后端。
332fn normalize_dsn_scheme(backend: AnyBackend, dsn: &str) -> String {
333    let expected_scheme = match backend {
334        AnyBackend::MySql => "mysql://",
335        AnyBackend::Postgres => "postgres://",
336        AnyBackend::Sqlite => "sqlite:",
337        AnyBackend::Oracle => "oracle://",
338        AnyBackend::Mssql => "mssql://",
339    };
340    let known_schemes = [
341        "mysql://",
342        "mariadb://",
343        "postgres://",
344        "postgresql://",
345        "sqlite://",
346        "sqlite:",
347        "oracle://",
348        "mssql://",
349        "sqlserver://",
350    ];
351    for scheme in &known_schemes {
352        if let Some(rest) = dsn.strip_prefix(scheme) {
353            if backend == AnyBackend::from_dsn(dsn).unwrap_or(AnyBackend::MySql) {
354                return dsn.to_string();
355            }
356            return format!("{}{}", expected_scheme, rest);
357        }
358    }
359    dsn.to_string()
360}
361
362/// 解析 Oracle DSN 为 (username, password, connect_string)
363///
364/// 格式:`oracle://user:pass@host:port/service`
365///
366/// 返回:username="user", password="pass", connect_string="host:port/service"
367#[allow(dead_code)]
368pub(crate) fn parse_oracle_dsn(dsn: &str) -> Result<(String, String, String), DbError> {
369    let rest = dsn
370        .strip_prefix("oracle://")
371        .ok_or_else(|| DbError::ConnectionRefused(format!("无效的 Oracle DSN: {}", dsn)))?;
372    parse_user_pass_host(rest, "Oracle")
373}
374
375/// 解析 MSSQL DSN 为 ADO.NET 连接字符串
376///
377/// 格式:`mssql://user:pass@host:port/database` 或 `sqlserver://user:pass@host:port/database`
378///
379/// 返回:`Server=host,port;Database=database;User Id=user;Password=pass;`
380#[allow(dead_code)]
381pub(crate) fn parse_mssql_dsn(dsn: &str) -> Result<String, DbError> {
382    let rest = dsn
383        .strip_prefix("mssql://")
384        .or_else(|| dsn.strip_prefix("sqlserver://"))
385        .ok_or_else(|| DbError::ConnectionRefused(format!("无效的 MSSQL DSN: {}", dsn)))?;
386    let (username, password, host_port_db) = parse_user_pass_host(rest, "MSSQL")?;
387    let (host_port, database) = host_port_db
388        .split_once('/')
389        .ok_or_else(|| DbError::ConnectionRefused(format!("MSSQL DSN 缺少 database: {}", dsn)))?;
390    let (host, port) = host_port.split_once(':').unwrap_or((host_port, "1433"));
391    Ok(format!(
392        "Server={},{};Database={};User Id={};Password={};",
393        host, port, database, username, password
394    ))
395}
396
397/// 通用 DSN 解析:`user:pass@host:port/...` → (user, pass, host:port/...)
398#[allow(dead_code)]
399fn parse_user_pass_host(rest: &str, backend: &str) -> Result<(String, String, String), DbError> {
400    let (userinfo, hostinfo) = rest
401        .split_once('@')
402        .ok_or_else(|| DbError::ConnectionRefused(format!("{} DSN 缺少 @: {}", backend, rest)))?;
403    let (username, password) = userinfo.split_once(':').ok_or_else(|| {
404        DbError::ConnectionRefused(format!("{} DSN 缺少 password: {}", backend, rest))
405    })?;
406    Ok((
407        username.to_string(),
408        password.to_string(),
409        hostinfo.to_string(),
410    ))
411}
412
413/// 后端无关的连接
414pub struct AnyConnection {
415    backend: AnyBackend,
416    inner: Box<dyn Connection>,
417}
418
419impl AnyConnection {
420    /// 获取后端类型
421    pub fn backend(&self) -> AnyBackend {
422        self.backend
423    }
424}
425
426impl Connection for AnyConnection {
427    fn execute<'a>(
428        &'a mut self,
429        sql: &'a str,
430    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
431        self.inner.execute(sql)
432    }
433
434    fn query<'a>(
435        &'a mut self,
436        sql: &'a str,
437    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
438        self.inner.query(sql)
439    }
440
441    fn begin_transaction<'a>(
442        &'a mut self,
443    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
444        self.inner.begin_transaction()
445    }
446
447    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
448        self.inner.commit()
449    }
450
451    fn rollback<'a>(
452        &'a mut self,
453    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
454        self.inner.rollback()
455    }
456
457    fn in_transaction(&self) -> bool {
458        self.inner.in_transaction()
459    }
460
461    fn is_connected(&self) -> bool {
462        self.inner.is_connected()
463    }
464
465    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
466        self.inner.ping()
467    }
468
469    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
470        self.inner.close()
471    }
472
473    fn execute_with_params<'a>(
474        &'a mut self,
475        sql: &'a str,
476        params: &'a [Value],
477    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
478        self.inner.execute_with_params(sql, params)
479    }
480
481    fn query_with_params<'a>(
482        &'a mut self,
483        sql: &'a str,
484        params: &'a [Value],
485    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
486        self.inner.query_with_params(sql, params)
487    }
488
489    fn query_values<'a>(
490        &'a mut self,
491        sql: &'a str,
492    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
493        self.inner.query_values(sql)
494    }
495
496    fn query_values_with_params<'a>(
497        &'a mut self,
498        sql: &'a str,
499        params: &'a [Value],
500    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
501        self.inner.query_values_with_params(sql, params)
502    }
503
504    fn query_stream<'a>(
505        &'a mut self,
506        sql: &'a str,
507    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
508        self.inner.query_stream(sql)
509    }
510
511    fn query_stream_cursor<'a>(
512        &'a mut self,
513        sql: &'a str,
514        batch_size: usize,
515    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
516        self.inner.query_stream_cursor(sql, batch_size)
517    }
518
519    fn execute_batch<'a>(
520        &'a mut self,
521        sqls: &'a [String],
522    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
523        self.inner.execute_batch(sqls)
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530
531    #[test]
532    fn test_any_backend_from_dsn_mysql() {
533        assert_eq!(
534            AnyBackend::from_dsn("mysql://root:pass@127.0.0.1/db").unwrap(),
535            AnyBackend::MySql
536        );
537        assert_eq!(
538            AnyBackend::from_dsn("mariadb://root:pass@127.0.0.1/db").unwrap(),
539            AnyBackend::MySql
540        );
541    }
542
543    #[test]
544    fn test_any_backend_from_dsn_postgres() {
545        assert_eq!(
546            AnyBackend::from_dsn("postgres://user:pass@127.0.0.1/db").unwrap(),
547            AnyBackend::Postgres
548        );
549        assert_eq!(
550            AnyBackend::from_dsn("postgresql://user:pass@127.0.0.1/db").unwrap(),
551            AnyBackend::Postgres
552        );
553    }
554
555    #[test]
556    fn test_any_backend_from_dsn_sqlite() {
557        assert_eq!(
558            AnyBackend::from_dsn("sqlite::memory:").unwrap(),
559            AnyBackend::Sqlite
560        );
561        assert_eq!(
562            AnyBackend::from_dsn("sqlite://./test.db").unwrap(),
563            AnyBackend::Sqlite
564        );
565    }
566
567    #[test]
568    fn test_any_backend_from_dsn_oracle() {
569        assert_eq!(
570            AnyBackend::from_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap(),
571            AnyBackend::Oracle
572        );
573    }
574
575    #[test]
576    fn test_any_backend_from_dsn_mssql() {
577        assert_eq!(
578            AnyBackend::from_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap(),
579            AnyBackend::Mssql
580        );
581    }
582
583    #[test]
584    fn test_any_backend_from_dsn_sqlserver() {
585        assert_eq!(
586            AnyBackend::from_dsn("sqlserver://sa:test123@127.0.0.1:1433/testdb").unwrap(),
587            AnyBackend::Mssql
588        );
589    }
590
591    #[test]
592    fn test_any_backend_from_dsn_unknown_v22() {
593        let result = AnyBackend::from_dsn("redis://127.0.0.1");
594        assert!(result.is_err());
595        if let Err(e) = result {
596            let msg = format!("{}", e);
597            assert!(msg.contains("mysql"));
598            assert!(msg.contains("postgres"));
599            assert!(msg.contains("sqlite"));
600            assert!(msg.contains("oracle"));
601            assert!(msg.contains("mssql"));
602        }
603    }
604
605    #[test]
606    fn test_any_backend_name_v22() {
607        assert_eq!(AnyBackend::MySql.name(), "mysql");
608        assert_eq!(AnyBackend::Postgres.name(), "postgres");
609        assert_eq!(AnyBackend::Sqlite.name(), "sqlite");
610        assert_eq!(AnyBackend::Oracle.name(), "oracle");
611        assert_eq!(AnyBackend::Mssql.name(), "mssql");
612    }
613
614    #[test]
615    fn test_any_backend_equality() {
616        assert_eq!(AnyBackend::MySql, AnyBackend::MySql);
617        assert_ne!(AnyBackend::MySql, AnyBackend::Postgres);
618        assert_ne!(AnyBackend::Postgres, AnyBackend::Sqlite);
619        assert_ne!(AnyBackend::Oracle, AnyBackend::Mssql);
620        assert_ne!(AnyBackend::Oracle, AnyBackend::MySql);
621    }
622
623    #[test]
624    fn test_parse_oracle_dsn() {
625        let (user, pass, cs) =
626            parse_oracle_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap();
627        assert_eq!(user, "sys");
628        assert_eq!(pass, "test123");
629        assert_eq!(cs, "127.0.0.1:1521/freepdb1");
630    }
631
632    #[test]
633    fn test_parse_mssql_dsn() {
634        let ado = parse_mssql_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap();
635        assert!(ado.contains("Server=127.0.0.1,1433"));
636        assert!(ado.contains("Database=testdb"));
637        assert!(ado.contains("User Id=sa"));
638        assert!(ado.contains("Password=test123"));
639    }
640
641    #[test]
642    fn test_parse_mssql_dsn_sqlserver_scheme() {
643        let ado = parse_mssql_dsn("sqlserver://sa:pass@localhost/testdb").unwrap();
644        assert!(ado.contains("Server=localhost,1433"));
645        assert!(ado.contains("Database=testdb"));
646    }
647
648    // ---- 真实 SQLite 集成测试 ----
649
650    #[tokio::test]
651    async fn test_any_pool_sqlite_connect_and_query() {
652        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
653        assert_eq!(pool.backend(), AnyBackend::Sqlite);
654
655        let mut conn = pool.create().await.unwrap();
656        assert_eq!(conn.backend(), AnyBackend::Sqlite);
657
658        // 创建表并插入数据
659        conn.execute("CREATE TABLE test_any (id INTEGER PRIMARY KEY, name TEXT)")
660            .await
661            .unwrap();
662        conn.execute("INSERT INTO test_any (name) VALUES ('Alice')")
663            .await
664            .unwrap();
665        conn.execute("INSERT INTO test_any (name) VALUES ('Bob')")
666            .await
667            .unwrap();
668
669        // 查询验证
670        let rows = conn
671            .query("SELECT * FROM test_any ORDER BY id")
672            .await
673            .unwrap();
674        assert_eq!(rows.len(), 2);
675        assert_eq!(rows[0].get("name").and_then(|v| v.as_str()), Some("Alice"));
676        assert_eq!(rows[1].get("name").and_then(|v| v.as_str()), Some("Bob"));
677    }
678
679    #[tokio::test]
680    async fn test_any_pool_sqlite_transaction_commit() {
681        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
682        let mut conn = pool.create().await.unwrap();
683
684        conn.execute("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val INTEGER)")
685            .await
686            .unwrap();
687
688        // 事务提交
689        conn.begin_transaction().await.unwrap();
690        conn.execute("INSERT INTO tx_test (val) VALUES (1)")
691            .await
692            .unwrap();
693        conn.execute("INSERT INTO tx_test (val) VALUES (2)")
694            .await
695            .unwrap();
696        conn.commit().await.unwrap();
697
698        let rows = conn.query("SELECT * FROM tx_test").await.unwrap();
699        assert_eq!(rows.len(), 2);
700    }
701
702    #[tokio::test]
703    async fn test_any_pool_sqlite_transaction_rollback() {
704        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
705        let mut conn = pool.create().await.unwrap();
706
707        conn.execute("CREATE TABLE tx_rb (id INTEGER PRIMARY KEY, val INTEGER)")
708            .await
709            .unwrap();
710
711        conn.begin_transaction().await.unwrap();
712        conn.execute("INSERT INTO tx_rb (val) VALUES (1)")
713            .await
714            .unwrap();
715        conn.rollback().await.unwrap();
716
717        let rows = conn.query("SELECT * FROM tx_rb").await.unwrap();
718        assert_eq!(rows.len(), 0);
719    }
720
721    #[tokio::test]
722    async fn test_any_connection_in_transaction_state_tracking() {
723        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
724        let mut conn = pool.create().await.unwrap();
725
726        assert!(!conn.in_transaction(), "新建连接不应处于事务中");
727
728        conn.begin_transaction().await.unwrap();
729        assert!(conn.in_transaction(), "begin 后应处于事务中");
730
731        conn.execute("CREATE TABLE tx_track (id INTEGER PRIMARY KEY)")
732            .await
733            .unwrap();
734        assert!(conn.in_transaction(), "事务内执行 SQL 后仍应处于事务中");
735
736        conn.commit().await.unwrap();
737        assert!(!conn.in_transaction(), "commit 后不应处于事务中");
738
739        conn.begin_transaction().await.unwrap();
740        assert!(conn.in_transaction());
741        conn.rollback().await.unwrap();
742        assert!(!conn.in_transaction(), "rollback 后不应处于事务中");
743    }
744
745    #[tokio::test]
746    async fn test_any_connection_in_transaction_leak_detection() {
747        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
748        let mut conn = pool.create().await.unwrap();
749
750        conn.execute("CREATE TABLE tx_leak (id INTEGER PRIMARY KEY)")
751            .await
752            .unwrap();
753
754        conn.begin_transaction().await.unwrap();
755        conn.execute("INSERT INTO tx_leak (id) VALUES (1)")
756            .await
757            .unwrap();
758
759        assert!(
760            conn.in_transaction(),
761            "未提交事务应被检测到(泄漏防护前提)"
762        );
763
764        conn.rollback().await.unwrap();
765        assert!(!conn.in_transaction());
766    }
767
768    #[tokio::test]
769    async fn test_any_pool_sqlite_ping() {
770        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
771        let mut conn = pool.create().await.unwrap();
772        let ok = conn.ping().await;
773        assert!(ok);
774        assert!(conn.is_connected());
775    }
776
777    #[tokio::test]
778    async fn test_any_pool_invalid_dsn() {
779        let result = AnyPool::connect("invalid://dsn").await;
780        assert!(result.is_err());
781    }
782
783    #[tokio::test]
784    async fn test_any_pool_sqlite_count_query() {
785        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
786        let mut conn = pool.create().await.unwrap();
787
788        conn.execute("CREATE TABLE cnt (id INTEGER PRIMARY KEY)")
789            .await
790            .unwrap();
791        for i in 1..=5 {
792            conn.execute(&format!("INSERT INTO cnt (id) VALUES ({})", i))
793                .await
794                .unwrap();
795        }
796
797        // 通过 SELECT * 验证行数(避开 sqlx 适配器中 COUNT(*) 类型推断的既有问题)
798        let rows = conn.query("SELECT * FROM cnt").await.unwrap();
799        assert_eq!(rows.len(), 5);
800    }
801
802    // ---- v2.2.0 A-2: Dialect 与 AnyPool 集成测试 ----
803
804    #[test]
805    fn test_any_backend_dialect_mapping() {
806        use sz_orm_core::DbType;
807
808        let mysql_d = AnyBackend::MySql.dialect();
809        assert_eq!(mysql_d.db_type(), DbType::MySQL);
810
811        let pg_d = AnyBackend::Postgres.dialect();
812        assert_eq!(pg_d.db_type(), DbType::PostgreSQL);
813
814        let sqlite_d = AnyBackend::Sqlite.dialect();
815        assert_eq!(sqlite_d.db_type(), DbType::Sqlite);
816
817        let oracle_d = AnyBackend::Oracle.dialect();
818        assert_eq!(oracle_d.db_type(), DbType::Oracle);
819
820        let mssql_d = AnyBackend::Mssql.dialect();
821        assert_eq!(mssql_d.db_type(), DbType::SqlServer);
822    }
823
824    #[test]
825    fn test_oracle_dialect_pagination() {
826        let d = AnyBackend::Oracle.dialect();
827        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
828        let upper = sql.to_uppercase();
829        assert!(
830            upper.contains("OFFSET") || upper.contains("FETCH") || upper.contains("ROWNUM"),
831            "Oracle 分页 SQL 应含 OFFSET/FETCH/ROWNUM,实际: {}",
832            sql
833        );
834        assert!(
835            !upper.contains("LIMIT"),
836            "Oracle 分页 SQL 不应含 LIMIT,实际: {}",
837            sql
838        );
839    }
840
841    #[test]
842    fn test_mssql_dialect_pagination() {
843        let d = AnyBackend::Mssql.dialect();
844        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
845        let upper = sql.to_uppercase();
846        assert!(
847            upper.contains("OFFSET") || upper.contains("FETCH"),
848            "MSSQL 分页 SQL 应含 OFFSET/FETCH,实际: {}",
849            sql
850        );
851        assert!(
852            !upper.contains("LIMIT"),
853            "MSSQL 分页 SQL 不应含 LIMIT,实际: {}",
854            sql
855        );
856    }
857
858    #[test]
859    fn test_oracle_dialect_no_placeholder() {
860        let d = AnyBackend::Oracle.dialect();
861        // 调用各 Dialect trait 方法,确认不 panic(无 todo!/unimplemented!)
862        let _ = d.db_type();
863        let _ = d.quote("col");
864        let _ = d.quote_checked("col").unwrap();
865        let _ = d.escape_string("val");
866        let _ = d.supports_returning();
867        let _ = d.build_pagination("SELECT 1", 1, 10);
868        let _ = d.json_type();
869        let _ = d.json_extract("col", "$.key");
870        let _ = d.full_text_search(&["col"], "kw");
871        let _ = d.bool_to_int("expr");
872        let _ = d.concat(&["a", "b"]);
873    }
874
875    #[test]
876    fn test_mssql_dialect_no_placeholder() {
877        let d = AnyBackend::Mssql.dialect();
878        let _ = d.db_type();
879        let _ = d.quote("col");
880        let _ = d.quote_checked("col").unwrap();
881        let _ = d.escape_string("val");
882        let _ = d.supports_returning();
883        let _ = d.build_pagination("SELECT 1", 1, 10);
884        let _ = d.json_type();
885        let _ = d.json_extract("col", "$.key");
886        let _ = d.full_text_search(&["col"], "kw");
887        let _ = d.bool_to_int("expr");
888        let _ = d.concat(&["a", "b"]);
889    }
890
891    #[tokio::test]
892    async fn test_any_pool_dialect_sqlite() {
893        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
894        let d = pool.dialect();
895        assert_eq!(d.db_type(), sz_orm_core::DbType::Sqlite);
896    }
897
898    // ---- v6.9.0: from_db_type / create_connection / create_connection_by_type ----
899
900    #[test]
901    fn test_from_db_type_supported() {
902        use sz_orm_core::DbType;
903        assert_eq!(
904            AnyBackend::from_db_type(DbType::MySQL),
905            Some(AnyBackend::MySql)
906        );
907        assert_eq!(
908            AnyBackend::from_db_type(DbType::MariaDB),
909            Some(AnyBackend::MySql)
910        );
911        assert_eq!(
912            AnyBackend::from_db_type(DbType::TiDB),
913            Some(AnyBackend::MySql)
914        );
915        assert_eq!(
916            AnyBackend::from_db_type(DbType::OceanBase),
917            Some(AnyBackend::MySql)
918        );
919        assert_eq!(
920            AnyBackend::from_db_type(DbType::PostgreSQL),
921            Some(AnyBackend::Postgres)
922        );
923        assert_eq!(
924            AnyBackend::from_db_type(DbType::Kingbase),
925            Some(AnyBackend::Postgres)
926        );
927        assert_eq!(
928            AnyBackend::from_db_type(DbType::PolarDB),
929            Some(AnyBackend::Postgres)
930        );
931        assert_eq!(
932            AnyBackend::from_db_type(DbType::GaussDB),
933            Some(AnyBackend::Postgres)
934        );
935        assert_eq!(
936            AnyBackend::from_db_type(DbType::Sqlite),
937            Some(AnyBackend::Sqlite)
938        );
939        assert_eq!(
940            AnyBackend::from_db_type(DbType::Oracle),
941            Some(AnyBackend::Oracle)
942        );
943        assert_eq!(
944            AnyBackend::from_db_type(DbType::Dameng),
945            Some(AnyBackend::Oracle)
946        );
947        assert_eq!(
948            AnyBackend::from_db_type(DbType::SqlServer),
949            Some(AnyBackend::Mssql)
950        );
951        assert_eq!(
952            AnyBackend::from_db_type(DbType::Sybase),
953            Some(AnyBackend::Mssql)
954        );
955        assert_eq!(
956            AnyBackend::from_db_type(DbType::GBase),
957            Some(AnyBackend::Mssql)
958        );
959    }
960
961    #[test]
962    fn test_from_db_type_unsupported() {
963        use sz_orm_core::DbType;
964        assert_eq!(AnyBackend::from_db_type(DbType::Redis), None);
965        assert_eq!(AnyBackend::from_db_type(DbType::MongoDB), None);
966        assert_eq!(AnyBackend::from_db_type(DbType::ClickHouse), None);
967        assert_eq!(AnyBackend::from_db_type(DbType::VectorDb), None);
968    }
969
970    #[tokio::test]
971    async fn test_create_connection_sqlite() {
972        let mut conn = create_connection("sqlite::memory:").await.unwrap();
973        conn.execute("CREATE TABLE cc (id INTEGER PRIMARY KEY, v TEXT)")
974            .await
975            .unwrap();
976        conn.execute("INSERT INTO cc (v) VALUES ('ok')")
977            .await
978            .unwrap();
979        let rows = conn.query("SELECT * FROM cc").await.unwrap();
980        assert_eq!(rows.len(), 1);
981    }
982
983    #[tokio::test]
984    async fn test_create_connection_invalid_dsn() {
985        let result = create_connection("invalid://dsn").await;
986        assert!(result.is_err());
987    }
988
989    #[tokio::test]
990    async fn test_create_connection_by_type_sqlite() {
991        use sz_orm_core::DbType;
992        let mut conn = create_connection_by_type(DbType::Sqlite, "sqlite::memory:")
993            .await
994            .unwrap();
995        conn.execute("SELECT 1").await.unwrap();
996    }
997
998    #[tokio::test]
999    async fn test_create_connection_by_type_unsupported() {
1000        use sz_orm_core::DbType;
1001        let result = create_connection_by_type(DbType::Redis, "redis://127.0.0.1").await;
1002        assert!(result.is_err());
1003    }
1004
1005    #[test]
1006    fn test_normalize_dsn_scheme_already_correct() {
1007        assert_eq!(
1008            normalize_dsn_scheme(AnyBackend::MySql, "mysql://root:pass@host/db"),
1009            "mysql://root:pass@host/db"
1010        );
1011        assert_eq!(
1012            normalize_dsn_scheme(AnyBackend::Postgres, "postgres://user@host/db"),
1013            "postgres://user@host/db"
1014        );
1015        assert_eq!(
1016            normalize_dsn_scheme(AnyBackend::Oracle, "oracle://sys:pass@host/svc"),
1017            "oracle://sys:pass@host/svc"
1018        );
1019    }
1020
1021    #[test]
1022    fn test_normalize_dsn_scheme_fix_mismatch() {
1023        let fixed = normalize_dsn_scheme(AnyBackend::MySql, "postgres://root:pass@host/db");
1024        assert!(fixed.starts_with("mysql://"));
1025        assert!(fixed.contains("root:pass@host/db"));
1026    }
1027}