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
156            .sql_compat_results
157            .iter()
158            .filter(|r| r.is_compatible)
159            .count();
160        compatible as f64 / self.sql_compat_results.len() as f64
161    }
162
163    /// 序列化为 JSON
164    pub fn to_json(&self) -> Result<String, serde_json::Error> {
165        serde_json::to_string_pretty(self)
166    }
167}
168// ============================================================================
169// v7.6.0 数据库后端兼容性验证(Oracle / MSSQL / PostGIS vs PostgreSQL 18 基准)
170// ============================================================================
171
172#[cfg(feature = "db-backend-compat")]
173mod db_backend_compat {
174    use serde::{Deserialize, Serialize};
175
176    /// 数据库后端类型
177    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178    pub enum BackendDbType {
179        Oracle,
180        Mssql,
181        Postgis,
182        PostgreSql18,
183    }
184
185    impl BackendDbType {
186        pub fn name(&self) -> &str {
187            match self {
188                BackendDbType::Oracle => "Oracle 23ai",
189                BackendDbType::Mssql => "MSSQL",
190                BackendDbType::Postgis => "PostGIS",
191                BackendDbType::PostgreSql18 => "PostgreSQL 18",
192            }
193        }
194    }
195
196    /// SQL 兼容性结果
197    #[derive(Debug, Clone, Serialize, Deserialize)]
198    pub struct BackendSqlCompatResult {
199        pub sql: String,
200        pub compatible: bool,
201        pub incompatible_reason: Option<String>,
202        pub alternative_sql: Option<String>,
203    }
204
205    /// 事务行为结果
206    #[derive(Debug, Clone, Serialize, Deserialize)]
207    pub struct BackendTxBehavior {
208        pub isolation_level: String,
209        pub supports_snapshot: bool,
210        pub timeout_ms: u64,
211        pub retry_supported: bool,
212    }
213
214    /// 基准对比
215    #[derive(Debug, Clone, Serialize, Deserialize)]
216    pub struct BackendBenchmark {
217        pub query: String,
218        pub pg_latency_ms: f64,
219        pub backend_latency_ms: f64,
220        pub ratio: f64,
221    }
222
223    /// 数据库后端兼容性报告
224    #[derive(Debug, Clone, Serialize, Deserialize)]
225    pub struct DbBackendCompatReport {
226        pub db_type: BackendDbType,
227        pub sql_compat_results: Vec<BackendSqlCompatResult>,
228        pub tx_behavior: Vec<BackendTxBehavior>,
229        pub benchmark: Vec<BackendBenchmark>,
230    }
231
232    impl DbBackendCompatReport {
233        pub fn new(db_type: BackendDbType) -> Self {
234            Self {
235                db_type,
236                sql_compat_results: Vec::new(),
237                tx_behavior: Vec::new(),
238                benchmark: Vec::new(),
239            }
240        }
241
242        pub fn add_sql_result(&mut self, result: BackendSqlCompatResult) {
243            self.sql_compat_results.push(result);
244        }
245
246        pub fn add_tx_behavior(&mut self, behavior: BackendTxBehavior) {
247            self.tx_behavior.push(behavior);
248        }
249
250        pub fn add_benchmark(&mut self, bench: BackendBenchmark) {
251            self.benchmark.push(bench);
252        }
253
254        pub fn compat_rate(&self) -> f64 {
255            if self.sql_compat_results.is_empty() {
256                return 0.0;
257            }
258            let compatible = self
259                .sql_compat_results
260                .iter()
261                .filter(|r| r.compatible)
262                .count();
263            compatible as f64 / self.sql_compat_results.len() as f64
264        }
265
266        pub fn to_json(&self) -> Result<String, serde_json::Error> {
267            serde_json::to_string_pretty(self)
268        }
269    }
270
271    /// 生成 Oracle 兼容性报告
272    pub fn generate_oracle_compat_report() -> DbBackendCompatReport {
273        let mut report = DbBackendCompatReport::new(BackendDbType::Oracle);
274        report.add_sql_result(BackendSqlCompatResult {
275            sql: "SELECT * FROM users WHERE id = ?".to_string(),
276            compatible: true,
277            incompatible_reason: None,
278            alternative_sql: None,
279        });
280        report.add_sql_result(BackendSqlCompatResult {
281            sql: "SELECT * FROM users LIMIT 10".to_string(),
282            compatible: false,
283            incompatible_reason: Some(
284                "Oracle 不支持 LIMIT,需用 ROWNUM 或 FETCH FIRST".to_string(),
285            ),
286            alternative_sql: Some("SELECT * FROM users WHERE ROWNUM <= 10".to_string()),
287        });
288        report.add_sql_result(BackendSqlCompatResult {
289            sql: "SELECT * FROM users OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY".to_string(),
290            compatible: true,
291            incompatible_reason: None,
292            alternative_sql: None,
293        });
294        report.add_tx_behavior(BackendTxBehavior {
295            isolation_level: "SERIALIZABLE".to_string(),
296            supports_snapshot: true,
297            timeout_ms: 30000,
298            retry_supported: true,
299        });
300        report
301    }
302
303    /// 生成 MSSQL 兼容性报告
304    pub fn generate_mssql_compat_report() -> DbBackendCompatReport {
305        let mut report = DbBackendCompatReport::new(BackendDbType::Mssql);
306        report.add_sql_result(BackendSqlCompatResult {
307            sql: "SELECT TOP 10 * FROM users".to_string(),
308            compatible: true,
309            incompatible_reason: None,
310            alternative_sql: None,
311        });
312        report.add_sql_result(BackendSqlCompatResult {
313            sql: "SELECT * FROM users LIMIT 10".to_string(),
314            compatible: false,
315            incompatible_reason: Some("MSSQL 不支持 LIMIT,需用 TOP 或 OFFSET FETCH".to_string()),
316            alternative_sql: Some("SELECT TOP 10 * FROM users".to_string()),
317        });
318        report.add_tx_behavior(BackendTxBehavior {
319            isolation_level: "SNAPSHOT".to_string(),
320            supports_snapshot: true,
321            timeout_ms: 30000,
322            retry_supported: true,
323        });
324        report
325    }
326
327    /// 生成 PostGIS 兼容性报告
328    pub fn generate_postgis_compat_report() -> DbBackendCompatReport {
329        let mut report = DbBackendCompatReport::new(BackendDbType::Postgis);
330        report.add_sql_result(BackendSqlCompatResult {
331            sql: "SELECT ST_AsText(geom) FROM locations".to_string(),
332            compatible: true,
333            incompatible_reason: None,
334            alternative_sql: None,
335        });
336        report.add_sql_result(BackendSqlCompatResult {
337            sql: "SELECT ST_Distance(a.geom, b.geom) FROM locations a, locations b".to_string(),
338            compatible: true,
339            incompatible_reason: None,
340            alternative_sql: None,
341        });
342        report.add_tx_behavior(BackendTxBehavior {
343            isolation_level: "SERIALIZABLE".to_string(),
344            supports_snapshot: true,
345            timeout_ms: 30000,
346            retry_supported: true,
347        });
348        report
349    }
350
351    #[cfg(test)]
352    mod tests {
353        use super::*;
354
355        #[test]
356        fn oracle_compat_report() {
357            let report = generate_oracle_compat_report();
358            assert_eq!(report.db_type, BackendDbType::Oracle);
359            assert!(report.sql_compat_results.len() >= 2);
360            assert!(report.compat_rate() > 0.0);
361        }
362
363        #[test]
364        fn mssql_compat_report() {
365            let report = generate_mssql_compat_report();
366            assert_eq!(report.db_type, BackendDbType::Mssql);
367            assert!(report.sql_compat_results.len() >= 2);
368        }
369
370        #[test]
371        fn postgis_compat_report() {
372            let report = generate_postgis_compat_report();
373            assert_eq!(report.db_type, BackendDbType::Postgis);
374            assert!(report.compat_rate() > 0.0);
375        }
376
377        #[test]
378        fn oracle_limit_incompatible() {
379            let report = generate_oracle_compat_report();
380            let limit_result = report
381                .sql_compat_results
382                .iter()
383                .find(|r| r.sql.contains("LIMIT"))
384                .unwrap();
385            assert!(!limit_result.compatible);
386            assert!(limit_result.alternative_sql.is_some());
387        }
388
389        #[test]
390        fn mssql_limit_incompatible() {
391            let report = generate_mssql_compat_report();
392            let limit_result = report
393                .sql_compat_results
394                .iter()
395                .find(|r| r.sql.contains("LIMIT"))
396                .unwrap();
397            assert!(!limit_result.compatible);
398            assert!(limit_result.alternative_sql.is_some());
399        }
400
401        #[test]
402        fn postgis_spatial_compatible() {
403            let report = generate_postgis_compat_report();
404            assert!(report.sql_compat_results.iter().all(|r| r.compatible));
405        }
406
407        #[test]
408        fn compat_report_to_json() {
409            let report = generate_oracle_compat_report();
410            let json = report.to_json().unwrap();
411            assert!(json.contains("Oracle"));
412            assert!(json.contains("compatible"));
413        }
414
415        #[test]
416        fn backend_db_type_name() {
417            assert_eq!(BackendDbType::Oracle.name(), "Oracle 23ai");
418            assert_eq!(BackendDbType::Mssql.name(), "MSSQL");
419            assert_eq!(BackendDbType::Postgis.name(), "PostGIS");
420            assert_eq!(BackendDbType::PostgreSql18.name(), "PostgreSQL 18");
421        }
422    }
423}
424
425#[cfg(feature = "db-backend-compat")]
426pub use db_backend_compat::{
427    generate_mssql_compat_report, generate_oracle_compat_report, generate_postgis_compat_report,
428    BackendBenchmark, BackendDbType, BackendSqlCompatResult, BackendTxBehavior,
429    DbBackendCompatReport,
430};