Skip to main content

sz_orm_sqlx/
lib.rs

1//! SZ-ORM sqlx adapter
2//!
3//! Provides Connection and ConnectionFactory implementations for sz-orm-core,
4//! supporting MySQL, PostgreSQL, and SQLite.
5//!
6//! Does not use sqlx::Any; instead implements each backend separately to avoid type limitations and lifetime issues.
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use sz_orm_core::{Pool, PoolConfigBuilder};
12//! use sz_orm_sqlx::{SqlitePoolHandle, SqlxSqliteConnectionFactory};
13//! use std::sync::Arc;
14//!
15//! # #[tokio::main]
16//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
17//! let pool_handle = SqlitePoolHandle::connect("sqlite::memory:").await?;
18//! let factory = Arc::new(SqlxSqliteConnectionFactory::new(Arc::new(pool_handle)));
19//! let config = PoolConfigBuilder::new().max_size(10).build()?;
20//! let pool = Pool::new(config, factory)?;
21//!
22//! let mut conn = pool.acquire().await?;
23//! let rows = conn.query("SELECT 1 as one").await?;
24//! assert_eq!(rows.len(), 1);
25//! # Ok(())
26//! # }
27//! ```
28
29mod any;
30pub mod any_driver;
31pub mod enhanced;
32mod error;
33#[cfg(feature = "async-row-stream")]
34pub mod row_stream_impl;
35#[cfg(feature = "dialect-saphana-driver")]
36pub mod saphana_adapter;
37pub mod unified_pool;
38
39pub use any::{
40    mysql_bulk_insert, pg_bulk_insert, sqlite_backup, MySqlPoolHandle, PgExtensions, PgPoolHandle,
41    SqlitePoolHandle, SqlxMySqlConnection, SqlxMySqlConnectionFactory, SqlxPgConnection,
42    SqlxPgConnectionFactory, SqlxSqliteConnection, SqlxSqliteConnectionFactory,
43};
44pub use any_driver::{
45    create_connection, create_connection_by_type, AnyBackend, AnyConnection, AnyPool,
46};
47pub use enhanced::{
48    CacheStats, EnhancedPoolConfig, EnhancedPoolConfigBuilder, PreparedStatementCache,
49    TransactionIsolation,
50};
51pub use error::map_sqlx_error;
52pub use unified_pool::UnifiedPool;
53
54#[cfg(feature = "async-row-stream")]
55pub use row_stream_impl::SqlxRowStream;
56
57pub use sz_orm_core;
58// v7.5.0 组6.3: 分布式 DB 兼容性报告
59use serde::{Deserialize, Serialize};
60
61/// 分布式数据库类型
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub enum DistributedDbType {
64    /// CockroachDB
65    CockroachDb,
66    /// YugabyteDB
67    YugabyteDb,
68    /// PostgreSQL 18 基准
69    PostgreSql,
70}
71
72/// SQL 兼容性测试结果
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SqlCompatResult {
75    /// 测试 SQL 语句
76    pub sql: String,
77    /// 是否兼容
78    pub is_compatible: bool,
79    /// 不兼容原因(兼容时为 None)
80    pub reason: Option<String>,
81    /// 替代 SQL 建议(不兼容时提供)
82    pub alternative_sql: Option<String>,
83}
84
85/// 分布式事务行为结果
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct TxBehaviorResult {
88    /// 隔离级别
89    pub isolation_level: String,
90    /// 超时行为
91    pub timeout: String,
92    /// 重试行为
93    pub retry_behavior: String,
94    /// 与 PostgreSQL 一致性
95    pub consistency_with_pg: bool,
96}
97
98/// 基准对比
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct BenchmarkComparison {
101    /// PostgreSQL 基准值
102    pub pg_baseline: f64,
103    /// 分布式 DB 结果
104    pub distributed_result: f64,
105    /// 吞吐量比率(distributed / pg)
106    pub throughput_ratio: f64,
107    /// 延迟比率(distributed / pg)
108    pub latency_ratio: f64,
109}
110
111/// 分布式 DB 兼容性报告
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct DistributedDbCompatReport {
114    /// 数据库类型
115    pub db_type: DistributedDbType,
116    /// SQL 兼容性测试结果列表
117    pub sql_compat_results: Vec<SqlCompatResult>,
118    /// 分布式事务行为结果列表
119    pub distributed_tx_behavior: Vec<TxBehaviorResult>,
120    /// 基准对比
121    pub benchmark_vs_pg: Vec<BenchmarkComparison>,
122}
123
124impl DistributedDbCompatReport {
125    /// 创建新的兼容性报告
126    pub fn new(db_type: DistributedDbType) -> Self {
127        Self {
128            db_type,
129            sql_compat_results: Vec::new(),
130            distributed_tx_behavior: Vec::new(),
131            benchmark_vs_pg: Vec::new(),
132        }
133    }
134
135    /// 添加 SQL 兼容性结果
136    pub fn add_sql_compat(&mut self, result: SqlCompatResult) {
137        self.sql_compat_results.push(result);
138    }
139
140    /// 添加事务行为结果
141    pub fn add_tx_behavior(&mut self, result: TxBehaviorResult) {
142        self.distributed_tx_behavior.push(result);
143    }
144
145    /// 添加基准对比
146    pub fn add_benchmark(&mut self, comparison: BenchmarkComparison) {
147        self.benchmark_vs_pg.push(comparison);
148    }
149
150    /// 计算总体 SQL 兼容率
151    pub fn overall_sql_compat_rate(&self) -> f64 {
152        if self.sql_compat_results.is_empty() {
153            return 0.0;
154        }
155        let compatible = self.sql_compat_results.iter().filter(|r| r.is_compatible).count();
156        compatible as f64 / self.sql_compat_results.len() as f64
157    }
158
159    /// 序列化为 JSON
160    pub fn to_json(&self) -> Result<String, serde_json::Error> {
161        serde_json::to_string_pretty(self)
162    }
163}
164// ============================================================================
165// v7.6.0 数据库后端兼容性验证(Oracle / MSSQL / PostGIS vs PostgreSQL 18 基准)
166// ============================================================================
167
168#[cfg(feature = "db-backend-compat")]
169mod db_backend_compat {
170    use serde::{Deserialize, Serialize};
171
172    /// 数据库后端类型
173    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174    pub enum BackendDbType {
175        Oracle,
176        Mssql,
177        Postgis,
178        PostgreSql18,
179    }
180
181    impl BackendDbType {
182        pub fn name(&self) -> &str {
183            match self {
184                BackendDbType::Oracle => "Oracle 23ai",
185                BackendDbType::Mssql => "MSSQL",
186                BackendDbType::Postgis => "PostGIS",
187                BackendDbType::PostgreSql18 => "PostgreSQL 18",
188            }
189        }
190    }
191
192    /// SQL 兼容性结果
193    #[derive(Debug, Clone, Serialize, Deserialize)]
194    pub struct BackendSqlCompatResult {
195        pub sql: String,
196        pub compatible: bool,
197        pub incompatible_reason: Option<String>,
198        pub alternative_sql: Option<String>,
199    }
200
201    /// 事务行为结果
202    #[derive(Debug, Clone, Serialize, Deserialize)]
203    pub struct BackendTxBehavior {
204        pub isolation_level: String,
205        pub supports_snapshot: bool,
206        pub timeout_ms: u64,
207        pub retry_supported: bool,
208    }
209
210    /// 基准对比
211    #[derive(Debug, Clone, Serialize, Deserialize)]
212    pub struct BackendBenchmark {
213        pub query: String,
214        pub pg_latency_ms: f64,
215        pub backend_latency_ms: f64,
216        pub ratio: f64,
217    }
218
219    /// 数据库后端兼容性报告
220    #[derive(Debug, Clone, Serialize, Deserialize)]
221    pub struct DbBackendCompatReport {
222        pub db_type: BackendDbType,
223        pub sql_compat_results: Vec<BackendSqlCompatResult>,
224        pub tx_behavior: Vec<BackendTxBehavior>,
225        pub benchmark: Vec<BackendBenchmark>,
226    }
227
228    impl DbBackendCompatReport {
229        pub fn new(db_type: BackendDbType) -> Self {
230            Self {
231                db_type,
232                sql_compat_results: Vec::new(),
233                tx_behavior: Vec::new(),
234                benchmark: Vec::new(),
235            }
236        }
237
238        pub fn add_sql_result(&mut self, result: BackendSqlCompatResult) {
239            self.sql_compat_results.push(result);
240        }
241
242        pub fn add_tx_behavior(&mut self, behavior: BackendTxBehavior) {
243            self.tx_behavior.push(behavior);
244        }
245
246        pub fn add_benchmark(&mut self, bench: BackendBenchmark) {
247            self.benchmark.push(bench);
248        }
249
250        pub fn compat_rate(&self) -> f64 {
251            if self.sql_compat_results.is_empty() {
252                return 0.0;
253            }
254            let compatible = self
255                .sql_compat_results
256                .iter()
257                .filter(|r| r.compatible)
258                .count();
259            compatible as f64 / self.sql_compat_results.len() as f64
260        }
261
262        pub fn to_json(&self) -> Result<String, serde_json::Error> {
263            serde_json::to_string_pretty(self)
264        }
265    }
266
267    /// 生成 Oracle 兼容性报告
268    pub fn generate_oracle_compat_report() -> DbBackendCompatReport {
269        let mut report = DbBackendCompatReport::new(BackendDbType::Oracle);
270        report.add_sql_result(BackendSqlCompatResult {
271            sql: "SELECT * FROM users WHERE id = ?".to_string(),
272            compatible: true,
273            incompatible_reason: None,
274            alternative_sql: None,
275        });
276        report.add_sql_result(BackendSqlCompatResult {
277            sql: "SELECT * FROM users LIMIT 10".to_string(),
278            compatible: false,
279            incompatible_reason: Some("Oracle 不支持 LIMIT,需用 ROWNUM 或 FETCH FIRST".to_string()),
280            alternative_sql: Some("SELECT * FROM users WHERE ROWNUM <= 10".to_string()),
281        });
282        report.add_sql_result(BackendSqlCompatResult {
283            sql: "SELECT * FROM users OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY".to_string(),
284            compatible: true,
285            incompatible_reason: None,
286            alternative_sql: None,
287        });
288        report.add_tx_behavior(BackendTxBehavior {
289            isolation_level: "SERIALIZABLE".to_string(),
290            supports_snapshot: true,
291            timeout_ms: 30000,
292            retry_supported: true,
293        });
294        report
295    }
296
297    /// 生成 MSSQL 兼容性报告
298    pub fn generate_mssql_compat_report() -> DbBackendCompatReport {
299        let mut report = DbBackendCompatReport::new(BackendDbType::Mssql);
300        report.add_sql_result(BackendSqlCompatResult {
301            sql: "SELECT TOP 10 * FROM users".to_string(),
302            compatible: true,
303            incompatible_reason: None,
304            alternative_sql: None,
305        });
306        report.add_sql_result(BackendSqlCompatResult {
307            sql: "SELECT * FROM users LIMIT 10".to_string(),
308            compatible: false,
309            incompatible_reason: Some("MSSQL 不支持 LIMIT,需用 TOP 或 OFFSET FETCH".to_string()),
310            alternative_sql: Some("SELECT TOP 10 * FROM users".to_string()),
311        });
312        report.add_tx_behavior(BackendTxBehavior {
313            isolation_level: "SNAPSHOT".to_string(),
314            supports_snapshot: true,
315            timeout_ms: 30000,
316            retry_supported: true,
317        });
318        report
319    }
320
321    /// 生成 PostGIS 兼容性报告
322    pub fn generate_postgis_compat_report() -> DbBackendCompatReport {
323        let mut report = DbBackendCompatReport::new(BackendDbType::Postgis);
324        report.add_sql_result(BackendSqlCompatResult {
325            sql: "SELECT ST_AsText(geom) FROM locations".to_string(),
326            compatible: true,
327            incompatible_reason: None,
328            alternative_sql: None,
329        });
330        report.add_sql_result(BackendSqlCompatResult {
331            sql: "SELECT ST_Distance(a.geom, b.geom) FROM locations a, locations b".to_string(),
332            compatible: true,
333            incompatible_reason: None,
334            alternative_sql: None,
335        });
336        report.add_tx_behavior(BackendTxBehavior {
337            isolation_level: "SERIALIZABLE".to_string(),
338            supports_snapshot: true,
339            timeout_ms: 30000,
340            retry_supported: true,
341        });
342        report
343    }
344
345    #[cfg(test)]
346    mod tests {
347        use super::*;
348
349        #[test]
350        fn oracle_compat_report() {
351            let report = generate_oracle_compat_report();
352            assert_eq!(report.db_type, BackendDbType::Oracle);
353            assert!(report.sql_compat_results.len() >= 2);
354            assert!(report.compat_rate() > 0.0);
355        }
356
357        #[test]
358        fn mssql_compat_report() {
359            let report = generate_mssql_compat_report();
360            assert_eq!(report.db_type, BackendDbType::Mssql);
361            assert!(report.sql_compat_results.len() >= 2);
362        }
363
364        #[test]
365        fn postgis_compat_report() {
366            let report = generate_postgis_compat_report();
367            assert_eq!(report.db_type, BackendDbType::Postgis);
368            assert!(report.compat_rate() > 0.0);
369        }
370
371        #[test]
372        fn oracle_limit_incompatible() {
373            let report = generate_oracle_compat_report();
374            let limit_result = report
375                .sql_compat_results
376                .iter()
377                .find(|r| r.sql.contains("LIMIT"))
378                .unwrap();
379            assert!(!limit_result.compatible);
380            assert!(limit_result.alternative_sql.is_some());
381        }
382
383        #[test]
384        fn mssql_limit_incompatible() {
385            let report = generate_mssql_compat_report();
386            let limit_result = report
387                .sql_compat_results
388                .iter()
389                .find(|r| r.sql.contains("LIMIT"))
390                .unwrap();
391            assert!(!limit_result.compatible);
392            assert!(limit_result.alternative_sql.is_some());
393        }
394
395        #[test]
396        fn postgis_spatial_compatible() {
397            let report = generate_postgis_compat_report();
398            assert!(report.sql_compat_results.iter().all(|r| r.compatible));
399        }
400
401        #[test]
402        fn compat_report_to_json() {
403            let report = generate_oracle_compat_report();
404            let json = report.to_json().unwrap();
405            assert!(json.contains("Oracle"));
406            assert!(json.contains("compatible"));
407        }
408
409        #[test]
410        fn backend_db_type_name() {
411            assert_eq!(BackendDbType::Oracle.name(), "Oracle 23ai");
412            assert_eq!(BackendDbType::Mssql.name(), "MSSQL");
413            assert_eq!(BackendDbType::Postgis.name(), "PostGIS");
414            assert_eq!(BackendDbType::PostgreSql18.name(), "PostgreSQL 18");
415        }
416    }
417}
418
419#[cfg(feature = "db-backend-compat")]
420pub use db_backend_compat::{
421    BackendBenchmark, BackendDbType, BackendSqlCompatResult, BackendTxBehavior,
422    DbBackendCompatReport, generate_mssql_compat_report, generate_oracle_compat_report,
423    generate_postgis_compat_report,
424};