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 db_type(&self) -> Option<DbType> {
421        Some(match self.backend {
422            AnyBackend::MySql => DbType::MySQL,
423            AnyBackend::Postgres => DbType::PostgreSQL,
424            AnyBackend::Sqlite => DbType::Sqlite,
425            AnyBackend::Oracle => DbType::Oracle,
426            AnyBackend::Mssql => DbType::SqlServer,
427        })
428    }
429
430    fn execute<'a>(
431        &'a mut self,
432        sql: &'a str,
433    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
434        self.inner.execute(sql)
435    }
436
437    fn query<'a>(
438        &'a mut self,
439        sql: &'a str,
440    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
441        self.inner.query(sql)
442    }
443
444    fn begin_transaction<'a>(
445        &'a mut self,
446    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
447        self.inner.begin_transaction()
448    }
449
450    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
451        self.inner.commit()
452    }
453
454    fn rollback<'a>(
455        &'a mut self,
456    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
457        self.inner.rollback()
458    }
459
460    fn in_transaction(&self) -> bool {
461        self.inner.in_transaction()
462    }
463
464    fn session_reset_sql(&self) -> Option<&'static str> {
465        self.inner.session_reset_sql()
466    }
467
468    fn is_connected(&self) -> bool {
469        self.inner.is_connected()
470    }
471
472    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
473        self.inner.ping()
474    }
475
476    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
477        self.inner.close()
478    }
479
480    fn execute_with_params<'a>(
481        &'a mut self,
482        sql: &'a str,
483        params: &'a [Value],
484    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
485        self.inner.execute_with_params(sql, params)
486    }
487
488    fn query_with_params<'a>(
489        &'a mut self,
490        sql: &'a str,
491        params: &'a [Value],
492    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
493        self.inner.query_with_params(sql, params)
494    }
495
496    fn query_values<'a>(
497        &'a mut self,
498        sql: &'a str,
499    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
500        self.inner.query_values(sql)
501    }
502
503    fn query_values_with_params<'a>(
504        &'a mut self,
505        sql: &'a str,
506        params: &'a [Value],
507    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
508        self.inner.query_values_with_params(sql, params)
509    }
510
511    fn query_stream<'a>(
512        &'a mut self,
513        sql: &'a str,
514    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
515        self.inner.query_stream(sql)
516    }
517
518    fn query_stream_cursor<'a>(
519        &'a mut self,
520        sql: &'a str,
521        batch_size: usize,
522    ) -> Pin<Box<dyn futures::Stream<Item = QueryStreamItem> + Send + 'a>> {
523        self.inner.query_stream_cursor(sql, batch_size)
524    }
525
526    fn execute_batch<'a>(
527        &'a mut self,
528        sqls: &'a [String],
529    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
530        self.inner.execute_batch(sqls)
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[test]
539    fn test_any_backend_from_dsn_mysql() {
540        assert_eq!(
541            AnyBackend::from_dsn("mysql://root:pass@127.0.0.1/db").unwrap(),
542            AnyBackend::MySql
543        );
544        assert_eq!(
545            AnyBackend::from_dsn("mariadb://root:pass@127.0.0.1/db").unwrap(),
546            AnyBackend::MySql
547        );
548    }
549
550    #[test]
551    fn test_any_backend_from_dsn_postgres() {
552        assert_eq!(
553            AnyBackend::from_dsn("postgres://user:pass@127.0.0.1/db").unwrap(),
554            AnyBackend::Postgres
555        );
556        assert_eq!(
557            AnyBackend::from_dsn("postgresql://user:pass@127.0.0.1/db").unwrap(),
558            AnyBackend::Postgres
559        );
560    }
561
562    #[test]
563    fn test_any_backend_from_dsn_sqlite() {
564        assert_eq!(
565            AnyBackend::from_dsn("sqlite::memory:").unwrap(),
566            AnyBackend::Sqlite
567        );
568        assert_eq!(
569            AnyBackend::from_dsn("sqlite://./test.db").unwrap(),
570            AnyBackend::Sqlite
571        );
572    }
573
574    #[test]
575    fn test_any_backend_from_dsn_oracle() {
576        assert_eq!(
577            AnyBackend::from_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap(),
578            AnyBackend::Oracle
579        );
580    }
581
582    #[test]
583    fn test_any_backend_from_dsn_mssql() {
584        assert_eq!(
585            AnyBackend::from_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap(),
586            AnyBackend::Mssql
587        );
588    }
589
590    #[test]
591    fn test_any_backend_from_dsn_sqlserver() {
592        assert_eq!(
593            AnyBackend::from_dsn("sqlserver://sa:test123@127.0.0.1:1433/testdb").unwrap(),
594            AnyBackend::Mssql
595        );
596    }
597
598    #[test]
599    fn test_any_backend_from_dsn_unknown_v22() {
600        let result = AnyBackend::from_dsn("redis://127.0.0.1");
601        assert!(result.is_err());
602        if let Err(e) = result {
603            let msg = format!("{}", e);
604            assert!(msg.contains("mysql"));
605            assert!(msg.contains("postgres"));
606            assert!(msg.contains("sqlite"));
607            assert!(msg.contains("oracle"));
608            assert!(msg.contains("mssql"));
609        }
610    }
611
612    #[test]
613    fn test_any_backend_name_v22() {
614        assert_eq!(AnyBackend::MySql.name(), "mysql");
615        assert_eq!(AnyBackend::Postgres.name(), "postgres");
616        assert_eq!(AnyBackend::Sqlite.name(), "sqlite");
617        assert_eq!(AnyBackend::Oracle.name(), "oracle");
618        assert_eq!(AnyBackend::Mssql.name(), "mssql");
619    }
620
621    #[test]
622    fn test_any_backend_equality() {
623        assert_eq!(AnyBackend::MySql, AnyBackend::MySql);
624        assert_ne!(AnyBackend::MySql, AnyBackend::Postgres);
625        assert_ne!(AnyBackend::Postgres, AnyBackend::Sqlite);
626        assert_ne!(AnyBackend::Oracle, AnyBackend::Mssql);
627        assert_ne!(AnyBackend::Oracle, AnyBackend::MySql);
628    }
629
630    #[test]
631    fn test_parse_oracle_dsn() {
632        let (user, pass, cs) =
633            parse_oracle_dsn("oracle://sys:test123@127.0.0.1:1521/freepdb1").unwrap();
634        assert_eq!(user, "sys");
635        assert_eq!(pass, "test123");
636        assert_eq!(cs, "127.0.0.1:1521/freepdb1");
637    }
638
639    #[test]
640    fn test_parse_mssql_dsn() {
641        let ado = parse_mssql_dsn("mssql://sa:test123@127.0.0.1:1433/testdb").unwrap();
642        assert!(ado.contains("Server=127.0.0.1,1433"));
643        assert!(ado.contains("Database=testdb"));
644        assert!(ado.contains("User Id=sa"));
645        assert!(ado.contains("Password=test123"));
646    }
647
648    #[test]
649    fn test_parse_mssql_dsn_sqlserver_scheme() {
650        let ado = parse_mssql_dsn("sqlserver://sa:pass@localhost/testdb").unwrap();
651        assert!(ado.contains("Server=localhost,1433"));
652        assert!(ado.contains("Database=testdb"));
653    }
654
655    // ---- 真实 SQLite 集成测试 ----
656
657    #[tokio::test]
658    async fn test_any_pool_sqlite_connect_and_query() {
659        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
660        assert_eq!(pool.backend(), AnyBackend::Sqlite);
661
662        let mut conn = pool.create().await.unwrap();
663        assert_eq!(conn.backend(), AnyBackend::Sqlite);
664
665        // 创建表并插入数据
666        conn.execute("CREATE TABLE test_any (id INTEGER PRIMARY KEY, name TEXT)")
667            .await
668            .unwrap();
669        conn.execute("INSERT INTO test_any (name) VALUES ('Alice')")
670            .await
671            .unwrap();
672        conn.execute("INSERT INTO test_any (name) VALUES ('Bob')")
673            .await
674            .unwrap();
675
676        // 查询验证
677        let rows = conn
678            .query("SELECT * FROM test_any ORDER BY id")
679            .await
680            .unwrap();
681        assert_eq!(rows.len(), 2);
682        assert_eq!(rows[0].get("name").and_then(|v| v.as_str()), Some("Alice"));
683        assert_eq!(rows[1].get("name").and_then(|v| v.as_str()), Some("Bob"));
684    }
685
686    #[tokio::test]
687    async fn test_any_pool_sqlite_transaction_commit() {
688        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
689        let mut conn = pool.create().await.unwrap();
690
691        conn.execute("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val INTEGER)")
692            .await
693            .unwrap();
694
695        // 事务提交
696        conn.begin_transaction().await.unwrap();
697        conn.execute("INSERT INTO tx_test (val) VALUES (1)")
698            .await
699            .unwrap();
700        conn.execute("INSERT INTO tx_test (val) VALUES (2)")
701            .await
702            .unwrap();
703        conn.commit().await.unwrap();
704
705        let rows = conn.query("SELECT * FROM tx_test").await.unwrap();
706        assert_eq!(rows.len(), 2);
707    }
708
709    #[tokio::test]
710    async fn test_any_pool_sqlite_transaction_rollback() {
711        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
712        let mut conn = pool.create().await.unwrap();
713
714        conn.execute("CREATE TABLE tx_rb (id INTEGER PRIMARY KEY, val INTEGER)")
715            .await
716            .unwrap();
717
718        conn.begin_transaction().await.unwrap();
719        conn.execute("INSERT INTO tx_rb (val) VALUES (1)")
720            .await
721            .unwrap();
722        conn.rollback().await.unwrap();
723
724        let rows = conn.query("SELECT * FROM tx_rb").await.unwrap();
725        assert_eq!(rows.len(), 0);
726    }
727
728    #[tokio::test]
729    async fn test_any_connection_in_transaction_state_tracking() {
730        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
731        let mut conn = pool.create().await.unwrap();
732
733        assert!(!conn.in_transaction(), "新建连接不应处于事务中");
734
735        conn.begin_transaction().await.unwrap();
736        assert!(conn.in_transaction(), "begin 后应处于事务中");
737
738        conn.execute("CREATE TABLE tx_track (id INTEGER PRIMARY KEY)")
739            .await
740            .unwrap();
741        assert!(conn.in_transaction(), "事务内执行 SQL 后仍应处于事务中");
742
743        conn.commit().await.unwrap();
744        assert!(!conn.in_transaction(), "commit 后不应处于事务中");
745
746        conn.begin_transaction().await.unwrap();
747        assert!(conn.in_transaction());
748        conn.rollback().await.unwrap();
749        assert!(!conn.in_transaction(), "rollback 后不应处于事务中");
750    }
751
752    #[tokio::test]
753    async fn test_any_connection_in_transaction_leak_detection() {
754        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
755        let mut conn = pool.create().await.unwrap();
756
757        conn.execute("CREATE TABLE tx_leak (id INTEGER PRIMARY KEY)")
758            .await
759            .unwrap();
760
761        conn.begin_transaction().await.unwrap();
762        conn.execute("INSERT INTO tx_leak (id) VALUES (1)")
763            .await
764            .unwrap();
765
766        assert!(
767            conn.in_transaction(),
768            "未提交事务应被检测到(泄漏防护前提)"
769        );
770
771        conn.rollback().await.unwrap();
772        assert!(!conn.in_transaction());
773    }
774
775    #[tokio::test]
776    async fn test_any_pool_sqlite_ping() {
777        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
778        let mut conn = pool.create().await.unwrap();
779        let ok = conn.ping().await;
780        assert!(ok);
781        assert!(conn.is_connected());
782    }
783
784    #[tokio::test]
785    async fn test_any_pool_invalid_dsn() {
786        let result = AnyPool::connect("invalid://dsn").await;
787        assert!(result.is_err());
788    }
789
790    #[tokio::test]
791    async fn test_any_pool_sqlite_count_query() {
792        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
793        let mut conn = pool.create().await.unwrap();
794
795        conn.execute("CREATE TABLE cnt (id INTEGER PRIMARY KEY)")
796            .await
797            .unwrap();
798        for i in 1..=5 {
799            conn.execute(&format!("INSERT INTO cnt (id) VALUES ({})", i))
800                .await
801                .unwrap();
802        }
803
804        // 通过 SELECT * 验证行数(避开 sqlx 适配器中 COUNT(*) 类型推断的既有问题)
805        let rows = conn.query("SELECT * FROM cnt").await.unwrap();
806        assert_eq!(rows.len(), 5);
807    }
808
809    // ---- v2.2.0 A-2: Dialect 与 AnyPool 集成测试 ----
810
811    #[test]
812    fn test_any_backend_dialect_mapping() {
813        use sz_orm_core::DbType;
814
815        let mysql_d = AnyBackend::MySql.dialect();
816        assert_eq!(mysql_d.db_type(), DbType::MySQL);
817
818        let pg_d = AnyBackend::Postgres.dialect();
819        assert_eq!(pg_d.db_type(), DbType::PostgreSQL);
820
821        let sqlite_d = AnyBackend::Sqlite.dialect();
822        assert_eq!(sqlite_d.db_type(), DbType::Sqlite);
823
824        let oracle_d = AnyBackend::Oracle.dialect();
825        assert_eq!(oracle_d.db_type(), DbType::Oracle);
826
827        let mssql_d = AnyBackend::Mssql.dialect();
828        assert_eq!(mssql_d.db_type(), DbType::SqlServer);
829    }
830
831    #[test]
832    fn test_oracle_dialect_pagination() {
833        let d = AnyBackend::Oracle.dialect();
834        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
835        let upper = sql.to_uppercase();
836        assert!(
837            upper.contains("OFFSET") || upper.contains("FETCH") || upper.contains("ROWNUM"),
838            "Oracle 分页 SQL 应含 OFFSET/FETCH/ROWNUM,实际: {}",
839            sql
840        );
841        assert!(
842            !upper.contains("LIMIT"),
843            "Oracle 分页 SQL 不应含 LIMIT,实际: {}",
844            sql
845        );
846    }
847
848    #[test]
849    fn test_mssql_dialect_pagination() {
850        let d = AnyBackend::Mssql.dialect();
851        let sql = d.build_pagination("SELECT * FROM users", 2, 10);
852        let upper = sql.to_uppercase();
853        assert!(
854            upper.contains("OFFSET") || upper.contains("FETCH"),
855            "MSSQL 分页 SQL 应含 OFFSET/FETCH,实际: {}",
856            sql
857        );
858        assert!(
859            !upper.contains("LIMIT"),
860            "MSSQL 分页 SQL 不应含 LIMIT,实际: {}",
861            sql
862        );
863    }
864
865    #[test]
866    fn test_oracle_dialect_no_placeholder() {
867        let d = AnyBackend::Oracle.dialect();
868        // 调用各 Dialect trait 方法,确认不 panic(无 todo!/unimplemented!)
869        let _ = d.db_type();
870        let _ = d.quote("col");
871        let _ = d.quote_checked("col").unwrap();
872        let _ = d.escape_string("val");
873        let _ = d.supports_returning();
874        let _ = d.build_pagination("SELECT 1", 1, 10);
875        let _ = d.json_type();
876        let _ = d.json_extract("col", "$.key");
877        let _ = d.full_text_search(&["col"], "kw");
878        let _ = d.bool_to_int("expr");
879        let _ = d.concat(&["a", "b"]);
880    }
881
882    #[test]
883    fn test_mssql_dialect_no_placeholder() {
884        let d = AnyBackend::Mssql.dialect();
885        let _ = d.db_type();
886        let _ = d.quote("col");
887        let _ = d.quote_checked("col").unwrap();
888        let _ = d.escape_string("val");
889        let _ = d.supports_returning();
890        let _ = d.build_pagination("SELECT 1", 1, 10);
891        let _ = d.json_type();
892        let _ = d.json_extract("col", "$.key");
893        let _ = d.full_text_search(&["col"], "kw");
894        let _ = d.bool_to_int("expr");
895        let _ = d.concat(&["a", "b"]);
896    }
897
898    #[tokio::test]
899    async fn test_any_pool_dialect_sqlite() {
900        let pool = AnyPool::connect("sqlite::memory:").await.unwrap();
901        let d = pool.dialect();
902        assert_eq!(d.db_type(), sz_orm_core::DbType::Sqlite);
903    }
904
905    // ---- v6.9.0: from_db_type / create_connection / create_connection_by_type ----
906
907    #[test]
908    fn test_from_db_type_supported() {
909        use sz_orm_core::DbType;
910        assert_eq!(
911            AnyBackend::from_db_type(DbType::MySQL),
912            Some(AnyBackend::MySql)
913        );
914        assert_eq!(
915            AnyBackend::from_db_type(DbType::MariaDB),
916            Some(AnyBackend::MySql)
917        );
918        assert_eq!(
919            AnyBackend::from_db_type(DbType::TiDB),
920            Some(AnyBackend::MySql)
921        );
922        assert_eq!(
923            AnyBackend::from_db_type(DbType::OceanBase),
924            Some(AnyBackend::MySql)
925        );
926        assert_eq!(
927            AnyBackend::from_db_type(DbType::PostgreSQL),
928            Some(AnyBackend::Postgres)
929        );
930        assert_eq!(
931            AnyBackend::from_db_type(DbType::Kingbase),
932            Some(AnyBackend::Postgres)
933        );
934        assert_eq!(
935            AnyBackend::from_db_type(DbType::PolarDB),
936            Some(AnyBackend::Postgres)
937        );
938        assert_eq!(
939            AnyBackend::from_db_type(DbType::GaussDB),
940            Some(AnyBackend::Postgres)
941        );
942        assert_eq!(
943            AnyBackend::from_db_type(DbType::Sqlite),
944            Some(AnyBackend::Sqlite)
945        );
946        assert_eq!(
947            AnyBackend::from_db_type(DbType::Oracle),
948            Some(AnyBackend::Oracle)
949        );
950        assert_eq!(
951            AnyBackend::from_db_type(DbType::Dameng),
952            Some(AnyBackend::Oracle)
953        );
954        assert_eq!(
955            AnyBackend::from_db_type(DbType::SqlServer),
956            Some(AnyBackend::Mssql)
957        );
958        assert_eq!(
959            AnyBackend::from_db_type(DbType::Sybase),
960            Some(AnyBackend::Mssql)
961        );
962        assert_eq!(
963            AnyBackend::from_db_type(DbType::GBase),
964            Some(AnyBackend::Mssql)
965        );
966    }
967
968    #[test]
969    fn test_from_db_type_unsupported() {
970        use sz_orm_core::DbType;
971        assert_eq!(AnyBackend::from_db_type(DbType::Redis), None);
972        assert_eq!(AnyBackend::from_db_type(DbType::MongoDB), None);
973        assert_eq!(AnyBackend::from_db_type(DbType::ClickHouse), None);
974        assert_eq!(AnyBackend::from_db_type(DbType::VectorDb), None);
975    }
976
977    #[tokio::test]
978    async fn test_create_connection_sqlite() {
979        let mut conn = create_connection("sqlite::memory:").await.unwrap();
980        conn.execute("CREATE TABLE cc (id INTEGER PRIMARY KEY, v TEXT)")
981            .await
982            .unwrap();
983        conn.execute("INSERT INTO cc (v) VALUES ('ok')")
984            .await
985            .unwrap();
986        let rows = conn.query("SELECT * FROM cc").await.unwrap();
987        assert_eq!(rows.len(), 1);
988    }
989
990    #[tokio::test]
991    async fn test_create_connection_invalid_dsn() {
992        let result = create_connection("invalid://dsn").await;
993        assert!(result.is_err());
994    }
995
996    #[tokio::test]
997    async fn test_create_connection_by_type_sqlite() {
998        use sz_orm_core::DbType;
999        let mut conn = create_connection_by_type(DbType::Sqlite, "sqlite::memory:")
1000            .await
1001            .unwrap();
1002        conn.execute("SELECT 1").await.unwrap();
1003    }
1004
1005    #[tokio::test]
1006    async fn test_create_connection_by_type_unsupported() {
1007        use sz_orm_core::DbType;
1008        let result = create_connection_by_type(DbType::Redis, "redis://127.0.0.1").await;
1009        assert!(result.is_err());
1010    }
1011
1012    #[test]
1013    fn test_normalize_dsn_scheme_already_correct() {
1014        assert_eq!(
1015            normalize_dsn_scheme(AnyBackend::MySql, "mysql://root:pass@host/db"),
1016            "mysql://root:pass@host/db"
1017        );
1018        assert_eq!(
1019            normalize_dsn_scheme(AnyBackend::Postgres, "postgres://user@host/db"),
1020            "postgres://user@host/db"
1021        );
1022        assert_eq!(
1023            normalize_dsn_scheme(AnyBackend::Oracle, "oracle://sys:pass@host/svc"),
1024            "oracle://sys:pass@host/svc"
1025        );
1026    }
1027
1028    #[test]
1029    fn test_normalize_dsn_scheme_fix_mismatch() {
1030        let fixed = normalize_dsn_scheme(AnyBackend::MySql, "postgres://root:pass@host/db");
1031        assert!(fixed.starts_with("mysql://"));
1032        assert!(fixed.contains("root:pass@host/db"));
1033    }
1034}