Skip to main content

sz_orm_sqlx/
any.rs

1//! sqlx 后端适配器实现
2//!
3//! 为 MySQL、PostgreSQL、SQLite 分别实现 Connection 和 ConnectionFactory。
4//! 不使用 sqlx::Any 以避免其类型限制和生命周期问题。
5//!
6//! 关键设计:
7//! Connection trait 已手动解糖(不使用 `#[async_trait]`),所有 async 方法
8//! 使用单一生命周期 `'a`(绑定 `&'a mut self` 和 `&'a str`),而非 HRTB。
9//! 这样 sqlx::Executor 对 `&'c mut XxxConnection` 的 impl(针对具体 `'c`)
10//! 即可满足约束,避免 "implementation of Executor is not general enough" 错误。
11
12use async_trait::async_trait;
13use futures::StreamExt;
14use sqlx::{Column, Executor, Row, TypeInfo};
15use std::collections::HashMap;
16use std::future::Future;
17use std::pin::Pin;
18use std::str::FromStr;
19use std::sync::Arc;
20use sz_orm_core::{
21    ColType, Connection, ConnectionFactory, DbError, DbType, QueryRows, QueryValues, Value,
22};
23
24use crate::error::map_sqlx_error;
25
26/// 判断 SQL 是否需要走 raw_sql 路径
27/// MySQL prepared statement 协议不支持 BEGIN/COMMIT/ROLLBACK/SAVEPOINT 等命令
28fn needs_raw_sql(sql: &str) -> bool {
29    let trimmed = sql.trim_start();
30    let upper = trimmed.to_uppercase();
31    upper.starts_with("BEGIN")
32        || upper.starts_with("COMMIT")
33        || upper.starts_with("ROLLBACK")
34        || upper.starts_with("SAVEPOINT")
35        || upper.starts_with("RELEASE")
36        || upper.starts_with("SET ")
37        || upper.starts_with("USE ")
38        || upper.starts_with("START TRANSACTION")
39}
40
41// ===================== SQLite 适配器 =====================
42
43// 注:sqlx 0.9 起 Executor trait 要求 'static lifetime,
44// 原先的 execute_sqlite_boxed / query_sqlite_boxed 已内联到调用点。
45// 见 SqlxSqliteConnection::execute / query 实现。
46
47// 注:原 row_to_value_sqlite(按 type_info().name() 字符串 match)已被
48// row_to_value_with_coltype_sqlite(按预解析 ColType 枚举分派)取代,
49// 性能更优且避免每行字符串比较。详见该函数。
50
51/// SQLite: 使用预解析的 ColType 进行类型分派(避免每行字符串 match)
52///
53/// 调用方预先通过 [`ColType::parse_sqlite`] 解析列类型,
54/// 后续行直接用枚举分派(编译器优化为跳转表),避免每行每列的字符串比较。
55///
56/// **注意**:必须使用 `parse_sqlite` 而非通用 `from_type_name`:SQLite 的 INTEGER 类型
57/// 实际为 64 位动态存储,通用映射会错误地归类为 I32,导致数值截断。
58fn row_to_value_with_coltype_sqlite(
59    row: &sqlx::sqlite::SqliteRow,
60    ordinal: usize,
61    col_type: ColType,
62) -> Value {
63    match col_type {
64        ColType::Bool => match row.try_get::<Option<bool>, usize>(ordinal) {
65            Ok(v) => v.map(Value::Bool).unwrap_or(Value::Null),
66            Err(_) => Value::Null,
67        },
68        ColType::I8 => match row.try_get::<Option<i8>, usize>(ordinal) {
69            Ok(v) => v.map(Value::I8).unwrap_or(Value::Null),
70            Err(_) => Value::Null,
71        },
72        ColType::I16 => match row.try_get::<Option<i16>, usize>(ordinal) {
73            Ok(v) => v.map(Value::I16).unwrap_or(Value::Null),
74            Err(_) => Value::Null,
75        },
76        ColType::I32 => match row.try_get::<Option<i32>, usize>(ordinal) {
77            Ok(v) => v.map(Value::I32).unwrap_or(Value::Null),
78            Err(_) => Value::Null,
79        },
80        ColType::I64 => match row.try_get::<Option<i64>, usize>(ordinal) {
81            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
82            Err(_) => Value::Null,
83        },
84        ColType::U8 => match row.try_get::<Option<u8>, usize>(ordinal) {
85            Ok(v) => v.map(Value::U8).unwrap_or(Value::Null),
86            Err(_) => Value::Null,
87        },
88        ColType::U16 => match row.try_get::<Option<u16>, usize>(ordinal) {
89            Ok(v) => v.map(Value::U16).unwrap_or(Value::Null),
90            Err(_) => Value::Null,
91        },
92        ColType::U32 => match row.try_get::<Option<u32>, usize>(ordinal) {
93            Ok(v) => v.map(Value::U32).unwrap_or(Value::Null),
94            Err(_) => Value::Null,
95        },
96        // SQLite 无原生 u64,按 i64 解码
97        ColType::U64 => match row.try_get::<Option<i64>, usize>(ordinal) {
98            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
99            Err(_) => Value::Null,
100        },
101        ColType::F32 => match row.try_get::<Option<f32>, usize>(ordinal) {
102            Ok(v) => v.map(Value::F32).unwrap_or(Value::Null),
103            Err(_) => Value::Null,
104        },
105        ColType::F64 => match row.try_get::<Option<f64>, usize>(ordinal) {
106            Ok(v) => v.map(Value::F64).unwrap_or(Value::Null),
107            Err(_) => Value::Null,
108        },
109        ColType::Decimal => match row.try_get::<Option<String>, usize>(ordinal) {
110            Ok(v) => v.map(Value::Decimal).unwrap_or(Value::Null),
111            Err(_) => Value::Null,
112        },
113        ColType::String => match row.try_get::<Option<String>, usize>(ordinal) {
114            Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
115            Err(_) => Value::Null,
116        },
117        ColType::Bytes => match row.try_get::<Option<Vec<u8>>, usize>(ordinal) {
118            Ok(v) => v.map(Value::Bytes).unwrap_or(Value::Null),
119            Err(_) => Value::Null,
120        },
121        // SQLite 中 DATE/DATETIME/TIME/JSON/UUID 通常以 TEXT 存储
122        ColType::Date | ColType::DateTime | ColType::Time | ColType::Json | ColType::Uuid => {
123            match row.try_get::<Option<String>, usize>(ordinal) {
124                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
125                Err(_) => Value::Null,
126            }
127        }
128        ColType::Unknown => {
129            // 未知类型(表达式/聚合列,SQLite type_info 返回空串),按
130            // i64 → f64 → bool → String 顺序回退(与 MySQL/PG 臂一致)。
131            // 修复前 bool 优先:任意非零整数被解码为 Bool(true),下游
132            // as_i64() 恒为 1 —— COUNT(*)=50 被读成 1(2026-09-25 实测
133            // 复现的静默读损坏)。表达式布尔值读为 I64(0/1),as_bool()
134            // 对整数类型语义不变。
135            if let Ok(v) = row.try_get::<Option<i64>, usize>(ordinal) {
136                return v.map(Value::I64).unwrap_or(Value::Null);
137            }
138            if let Ok(v) = row.try_get::<Option<f64>, usize>(ordinal) {
139                return v.map(Value::F64).unwrap_or(Value::Null);
140            }
141            if let Ok(v) = row.try_get::<Option<bool>, usize>(ordinal) {
142                return v.map(Value::Bool).unwrap_or(Value::Null);
143            }
144            if let Ok(v) = row.try_get::<Option<String>, usize>(ordinal) {
145                return v.map(Value::String).unwrap_or(Value::Null);
146            }
147            Value::Null
148        }
149        // ColType 标记为 #[non_exhaustive],未来新增变体按 Unknown 处理
150        _ => Value::Null,
151    }
152}
153
154pub struct SqlitePoolHandle {
155    pool: sqlx::SqlitePool,
156}
157
158impl SqlitePoolHandle {
159    pub async fn connect(url: &str) -> Result<Self, DbError> {
160        // 默认容量 10(向后兼容);上层应用 `connect_with_max` 对齐外层 PoolConfig::max_size
161        Self::connect_with_max(url, 10).await
162    }
163
164    /// 容量显式版 connect(2026-10-28 P3-4,PERFV2-03)。
165    ///
166    /// sz-orm 外层 `PoolConfig::max_size` 与内层 sqlx 池容量不一致时,
167    /// 实际并发 = min(max_size, 内层容量) 而外层按 max_size 记账——
168    /// `max_size > 内层容量` 时会确定性出现 acquire 超时。
169    /// 创建 handle 时用本方法把内层容量对齐到外层 max_size。
170    pub async fn connect_with_max(url: &str, max_connections: u32) -> Result<Self, DbError> {
171        // SQLite 生产配置:WAL + NORMAL 同步 + 5s 忙等 + 256MB mmap
172        // - WAL:写不阻塞读,崩溃后通过 WAL 恢复,远比 DELETE 模式安全
173        // - Synchronous=Normal:WAL 模式下仅在 checkpoint 时 fsync,性能 ~2x 于 Full
174        // - busy_timeout=5s:避免 "database is locked" 误报
175        // - mmap_size=256MB:大结果集减少 syscall,提升读取吞吐
176        let opts = sqlx::sqlite::SqliteConnectOptions::from_str(url)
177            .map_err(map_sqlx_error)?
178            .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
179            .synchronous(sqlx::sqlite::SqliteSynchronous::Normal)
180            .busy_timeout(std::time::Duration::from_secs(5))
181            .pragma("mmap_size", "268435456");
182        let pool = sqlx::sqlite::SqlitePoolOptions::new()
183            .max_connections(max_connections)
184            .acquire_timeout(std::time::Duration::from_secs(30))
185            .idle_timeout(Some(std::time::Duration::from_secs(600)))
186            .max_lifetime(Some(std::time::Duration::from_secs(1800)))
187            .connect_with(opts)
188            .await
189            .map_err(map_sqlx_error)?;
190        Ok(Self { pool })
191    }
192
193    pub fn from_pool(pool: sqlx::SqlitePool) -> Self {
194        Self { pool }
195    }
196
197    pub fn pool(&self) -> &sqlx::SqlitePool {
198        &self.pool
199    }
200}
201
202pub struct SqlxSqliteConnectionFactory {
203    pool: Arc<SqlitePoolHandle>,
204}
205
206impl SqlxSqliteConnectionFactory {
207    pub fn new(pool: Arc<SqlitePoolHandle>) -> Self {
208        Self { pool }
209    }
210}
211
212#[async_trait]
213impl ConnectionFactory for SqlxSqliteConnectionFactory {
214    async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
215        let conn = self.pool.pool.acquire().await.map_err(map_sqlx_error)?;
216        // 2026-10-28 P1-3:MetricsConn 包装统一上报查询指标(ops/latency/errors)
217        Ok(Box::new(crate::metrics_conn::MetricsConn::new(Box::new(
218            SqlxSqliteConnection {
219                conn: Some(conn),
220                connected: true,
221                in_transaction: false,
222            },
223        ))))
224    }
225}
226
227pub struct SqlxSqliteConnection {
228    conn: Option<sqlx::pool::PoolConnection<sqlx::Sqlite>>,
229    connected: bool,
230    in_transaction: bool,
231}
232
233impl Connection for SqlxSqliteConnection {
234    fn db_type(&self) -> Option<DbType> {
235        Some(DbType::Sqlite)
236    }
237
238    fn execute<'a>(
239        &'a mut self,
240        sql: &'a str,
241    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
242        Box::pin(async move {
243            let mut pool_conn = self
244                .conn
245                .take()
246                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
247            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
248            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
249            let result = if needs_raw_sql(sql) {
250                (&mut *pool_conn)
251                    .execute(sqlx::raw_sql(sqlx::AssertSqlSafe(sql)))
252                    .await
253            } else {
254                (&mut *pool_conn).execute(sqlx::AssertSqlSafe(sql)).await
255            };
256            self.conn = Some(pool_conn);
257
258            match result {
259                Ok(r) => Ok(r.rows_affected()),
260                Err(e) => {
261                    let db_err = map_sqlx_error(e);
262                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
263                        self.connected = false;
264                    }
265                    Err(db_err)
266                }
267            }
268        })
269    }
270
271    fn query<'a>(
272        &'a mut self,
273        sql: &'a str,
274    ) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, DbError>> + Send + 'a>>
275    {
276        Box::pin(async move {
277            let mut pool_conn = self
278                .conn
279                .take()
280                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
281            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
282            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
283            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
284            self.conn = Some(pool_conn);
285
286            let rows = rows_result.map_err(map_sqlx_error)?;
287            if rows.is_empty() {
288                return Ok(Vec::new());
289            }
290            // 预解析列类型(只在第一行解析,后续行复用,避免每行字符串 match)
291            // SQLite 专用解析:INTEGER 映射为 I64(SQLite 整数动态存储,可容纳 64 位)
292            let col_types: Vec<ColType> = rows[0]
293                .columns()
294                .iter()
295                .map(|col| ColType::parse_sqlite(col.type_info().name()))
296                .collect();
297            let col_names: Vec<String> = rows[0]
298                .columns()
299                .iter()
300                .map(|col| col.name().to_string())
301                .collect();
302            let mut result = Vec::with_capacity(rows.len());
303            for row in &rows {
304                let mut record = HashMap::with_capacity(col_types.len());
305                for (i, name) in col_names.iter().enumerate() {
306                    let value = row_to_value_with_coltype_sqlite(row, i, col_types[i]);
307                    record.insert(name.clone(), value);
308                }
309                result.push(record);
310            }
311            Ok(result)
312        })
313    }
314
315    fn begin_transaction<'a>(
316        &'a mut self,
317    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
318        Box::pin(async move {
319            if self.in_transaction {
320                return Err(DbError::Internal("transaction already started".to_string()));
321            }
322            self.execute("BEGIN").await?;
323            self.in_transaction = true;
324            Ok(())
325        })
326    }
327
328    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
329        Box::pin(async move {
330            if self.in_transaction {
331                self.execute("COMMIT").await?;
332                self.in_transaction = false;
333            }
334            Ok(())
335        })
336    }
337
338    fn rollback<'a>(
339        &'a mut self,
340    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
341        Box::pin(async move {
342            if self.in_transaction {
343                // 回滚失败时保留事务标志:非连接类错误下服务端事务可能仍开启,
344                // 若此刻清标志,连接带着开放事务回池将绕过归还路径的自动回滚
345                // (事务泄漏)。失败后由归还路径重试回滚或直接关闭连接。
346                // 语义与 Oracle/MSSQL 适配器一致。
347                let result = self.execute("ROLLBACK").await;
348                if result.is_ok() {
349                    self.in_transaction = false;
350                }
351                result.map(|_| ())
352            } else {
353                Ok(())
354            }
355        })
356    }
357
358    fn is_connected(&self) -> bool {
359        // 纳入 conn.is_some():query_stream 提前弃读时 PoolConnection 已从包装器
360        // take() 出去(self.conn = None),毒化状态不得被判定为可用连接
361        // (否则回池后下次 acquire 借出即报 "connection already closed")
362        self.connected && self.conn.is_some()
363    }
364
365    fn in_transaction(&self) -> bool {
366        self.in_transaction
367    }
368
369    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
370        Box::pin(async move {
371            match self.execute("SELECT 1").await {
372                Ok(_) => true,
373                Err(_) => {
374                    self.connected = false;
375                    false
376                }
377            }
378        })
379    }
380
381    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
382        Box::pin(async move {
383            if let Some(conn) = self.conn.take() {
384                drop(conn);
385            }
386            self.connected = false;
387            self.in_transaction = false;
388            Ok(())
389        })
390    }
391
392    /// SQLite 参数绑定执行(INSERT/UPDATE/DELETE)
393    ///
394    /// 使用 sqlx prepared statement 绑定参数,避免 SQL 注入与字符串转义开销。
395    /// 对 `Value::Bool`/`I8`..=`I64`/`U8`..=`U64`/`F32`/`F64`/`String`/`Bytes` 直接 bind;
396    /// 其他类型(Date/DateTime/Json/Array/Object)回退为 `to_string()` 后以 TEXT 绑定。
397    fn execute_with_params<'a>(
398        &'a mut self,
399        sql: &'a str,
400        params: &'a [Value],
401    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
402        Box::pin(async move {
403            if needs_raw_sql(sql) || params.is_empty() {
404                return self.execute(sql).await;
405            }
406            let mut pool_conn = self
407                .conn
408                .take()
409                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
410            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
411            for v in params {
412                q = match v {
413                    Value::Null => q.bind(None::<i64>),
414                    Value::Bool(b) => q.bind(*b),
415                    Value::I8(n) => q.bind(*n),
416                    Value::I16(n) => q.bind(*n),
417                    Value::I32(n) => q.bind(*n),
418                    Value::I64(n) => q.bind(*n),
419                    Value::U8(n) => q.bind(*n),
420                    Value::U16(n) => q.bind(*n),
421                    Value::U32(n) => q.bind(*n),
422                    Value::U64(n) => q.bind(*n as i64),
423                    Value::F32(f) => q.bind(*f),
424                    Value::F64(f) => q.bind(*f),
425                    Value::String(s) => q.bind(s.as_str()),
426                    Value::Decimal(s) => q.bind(s.as_str()),
427                    Value::Bytes(b) => q.bind(b.as_slice()),
428                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
429                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
430                    Value::Uuid(s)
431                    | Value::Date(s)
432                    | Value::DateTime(s)
433                    | Value::Time(s)
434                    | Value::Json(s) => q.bind(s.as_str()),
435                    other => q.bind(other.to_string()),
436                };
437            }
438            let result = q.execute(&mut *pool_conn).await;
439            self.conn = Some(pool_conn);
440            match result {
441                Ok(r) => Ok(r.rows_affected()),
442                Err(e) => {
443                    let db_err = map_sqlx_error(e);
444                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
445                        self.connected = false;
446                    }
447                    Err(db_err)
448                }
449            }
450        })
451    }
452
453    /// SQLite 参数绑定查询(SELECT,HashMap 映射)
454    ///
455    /// 使用 sqlx prepared statement 绑定参数,结果按预解析 ColType 解码为
456    /// `HashMap<String, Value>`。与 `query` 的区别仅在于参数绑定路径。
457    fn query_with_params<'a>(
458        &'a mut self,
459        sql: &'a str,
460        params: &'a [Value],
461    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
462        Box::pin(async move {
463            if params.is_empty() {
464                return self.query(sql).await;
465            }
466            let mut pool_conn = self
467                .conn
468                .take()
469                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
470            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
471            for v in params {
472                q = match v {
473                    Value::Null => q.bind(None::<i64>),
474                    Value::Bool(b) => q.bind(*b),
475                    Value::I8(n) => q.bind(*n),
476                    Value::I16(n) => q.bind(*n),
477                    Value::I32(n) => q.bind(*n),
478                    Value::I64(n) => q.bind(*n),
479                    Value::U8(n) => q.bind(*n),
480                    Value::U16(n) => q.bind(*n),
481                    Value::U32(n) => q.bind(*n),
482                    Value::U64(n) => q.bind(*n as i64),
483                    Value::F32(f) => q.bind(*f),
484                    Value::F64(f) => q.bind(*f),
485                    Value::String(s) => q.bind(s.as_str()),
486                    Value::Decimal(s) => q.bind(s.as_str()),
487                    Value::Bytes(b) => q.bind(b.as_slice()),
488                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
489                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
490                    Value::Uuid(s)
491                    | Value::Date(s)
492                    | Value::DateTime(s)
493                    | Value::Time(s)
494                    | Value::Json(s) => q.bind(s.as_str()),
495                    other => q.bind(other.to_string()),
496                };
497            }
498            let rows_result = q.fetch_all(&mut *pool_conn).await;
499            self.conn = Some(pool_conn);
500            let rows = rows_result.map_err(map_sqlx_error)?;
501            if rows.is_empty() {
502                return Ok(Vec::new());
503            }
504            // 预解析列类型(只在第一行解析,后续行复用)
505            // SQLite 专用解析:INTEGER → I64
506            let col_types: Vec<ColType> = rows[0]
507                .columns()
508                .iter()
509                .map(|col| ColType::parse_sqlite(col.type_info().name()))
510                .collect();
511            let col_names: Vec<String> = rows[0]
512                .columns()
513                .iter()
514                .map(|col| col.name().to_string())
515                .collect();
516            let mut result = Vec::with_capacity(rows.len());
517            for row in &rows {
518                let mut record = HashMap::with_capacity(col_types.len());
519                for (i, name) in col_names.iter().enumerate() {
520                    let value = row_to_value_with_coltype_sqlite(row, i, col_types[i]);
521                    record.insert(name.clone(), value);
522                }
523                result.push(record);
524            }
525            Ok(result)
526        })
527    }
528
529    /// SQLite 位置式查询(SELECT,无参数)
530    ///
531    /// 绕过 `HashMap<String, Value>` 行映射,返回 `(列名列表, 按列序号的值矩阵)`。
532    /// 列名与 ColType 仅 `to_string`/解析一次,后续行复用;每行值按列序号直接 `Vec::push`,
533    /// 无哈希计算与字符串克隆。适用于 SELECT ALL 大结果集场景。
534    fn query_values<'a>(
535        &'a mut self,
536        sql: &'a str,
537    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
538        Box::pin(async move {
539            let mut pool_conn = self
540                .conn
541                .take()
542                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
543            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
544            self.conn = Some(pool_conn);
545            let rows = rows_result.map_err(map_sqlx_error)?;
546            if rows.is_empty() {
547                return Ok((Vec::new(), Vec::new()));
548            }
549            let cols = rows[0].columns();
550            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
551            let mut col_types: Vec<ColType> = Vec::with_capacity(cols.len());
552            for col in cols {
553                col_names.push(col.name().to_string());
554                col_types.push(ColType::parse_sqlite(col.type_info().name()));
555            }
556            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
557            for row in &rows {
558                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
559                for (idx, _) in col_names.iter().enumerate() {
560                    row_values.push(row_to_value_with_coltype_sqlite(row, idx, col_types[idx]));
561                }
562                result_rows.push(row_values);
563            }
564            Ok((col_names, result_rows))
565        })
566    }
567
568    /// SQLite 参数绑定位置式查询(SELECT)
569    ///
570    /// 叠加 prepared statement + 位置式映射 + ColType 预解析三重优化。
571    fn query_values_with_params<'a>(
572        &'a mut self,
573        sql: &'a str,
574        params: &'a [Value],
575    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
576        Box::pin(async move {
577            if params.is_empty() {
578                return self.query_values(sql).await;
579            }
580            let mut pool_conn = self
581                .conn
582                .take()
583                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
584            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
585            for v in params {
586                q = match v {
587                    Value::Null => q.bind(None::<i64>),
588                    Value::Bool(b) => q.bind(*b),
589                    Value::I8(n) => q.bind(*n),
590                    Value::I16(n) => q.bind(*n),
591                    Value::I32(n) => q.bind(*n),
592                    Value::I64(n) => q.bind(*n),
593                    Value::U8(n) => q.bind(*n),
594                    Value::U16(n) => q.bind(*n),
595                    Value::U32(n) => q.bind(*n),
596                    Value::U64(n) => q.bind(*n as i64),
597                    Value::F32(f) => q.bind(*f),
598                    Value::F64(f) => q.bind(*f),
599                    Value::String(s) => q.bind(s.as_str()),
600                    Value::Decimal(s) => q.bind(s.as_str()),
601                    Value::Bytes(b) => q.bind(b.as_slice()),
602                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
603                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
604                    Value::Uuid(s)
605                    | Value::Date(s)
606                    | Value::DateTime(s)
607                    | Value::Time(s)
608                    | Value::Json(s) => q.bind(s.as_str()),
609                    other => q.bind(other.to_string()),
610                };
611            }
612            let rows_result = q.fetch_all(&mut *pool_conn).await;
613            self.conn = Some(pool_conn);
614            let rows = rows_result.map_err(map_sqlx_error)?;
615            if rows.is_empty() {
616                return Ok((Vec::new(), Vec::new()));
617            }
618            let cols = rows[0].columns();
619            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
620            let mut col_types: Vec<ColType> = Vec::with_capacity(cols.len());
621            for col in cols {
622                col_names.push(col.name().to_string());
623                col_types.push(ColType::parse_sqlite(col.type_info().name()));
624            }
625            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
626            for row in &rows {
627                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
628                for (idx, _) in col_names.iter().enumerate() {
629                    row_values.push(row_to_value_with_coltype_sqlite(row, idx, col_types[idx]));
630                }
631                result_rows.push(row_values);
632            }
633            Ok((col_names, result_rows))
634        })
635    }
636
637    /// SQLite 流式查询:逐行返回结果,避免大结果集 `fetch_all` 内存峰值
638    ///
639    /// 使用 `sqlx::query::fetch` 获取行流,逐行映射为 `HashMap<String, Value>`。
640    /// 流正常消费完毕后连接归还 `self.conn`;若提前 drop 流,连接通过
641    /// `PoolConnection::drop` 归还到 sqlx 池,但 `self.conn` 变为 `None`。
642    fn query_stream<'a>(
643        &'a mut self,
644        sql: &'a str,
645    ) -> Pin<Box<dyn futures::Stream<Item = Result<HashMap<String, Value>, DbError>> + Send + 'a>>
646    {
647        Box::pin(async_stream::try_stream! {
648            let mut pool_conn = self
649                .conn
650                .take()
651                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
652            let mut row_stream = sqlx::query(sqlx::AssertSqlSafe(sql)).fetch(&mut *pool_conn);
653            let mut col_types: Vec<ColType> = Vec::new();
654            let mut col_names: Vec<String> = Vec::new();
655            let mut first_row = true;
656            while let Some(row_result) = row_stream.next().await {
657                let row = row_result.map_err(map_sqlx_error)?;
658                if first_row {
659                    for col in row.columns() {
660                        col_names.push(col.name().to_string());
661                        col_types.push(ColType::parse_sqlite(col.type_info().name()));
662                    }
663                    first_row = false;
664                }
665                let mut record = HashMap::with_capacity(col_names.len());
666                for (i, name) in col_names.iter().enumerate() {
667                    let value = row_to_value_with_coltype_sqlite(&row, i, col_types[i]);
668                    record.insert(name.clone(), value);
669                }
670                yield record;
671            }
672            // 显式释放 row_stream 对 pool_conn 的可变借用,否则借用检查器
673            // 无法证明 row_stream 在移动 pool_conn 前已被销毁(E0505)
674            drop(row_stream);
675            self.conn = Some(pool_conn);
676        })
677    }
678}
679
680impl Drop for SqlxSqliteConnection {
681    fn drop(&mut self) {
682        if let Some(conn) = self.conn.take() {
683            drop(conn);
684        }
685    }
686}
687
688/// SQLite 在线备份(使用 VACUUM INTO,要求 SQLite 3.27+)
689///
690/// 将当前数据库完整复制到目标路径,原数据库可继续读写(在线备份)。
691/// 目标文件若已存在会报错。路径中的单引号会被转义以防 SQL 注入。
692pub async fn sqlite_backup(
693    conn: &mut SqlxSqliteConnection,
694    dest_path: &str,
695) -> Result<(), DbError> {
696    let escaped_path = dest_path.replace('\'', "''");
697    let sql = format!("VACUUM INTO '{}'", escaped_path);
698    conn.execute(&sql).await?;
699    Ok(())
700}
701
702// ===================== MySQL 适配器 =====================
703
704// 注:sqlx 0.9 起 Executor trait 要求 'static lifetime,
705// 原先的 execute_mysql_boxed / query_mysql_boxed 已内联到调用点。
706
707fn row_to_value_mysql(row: &sqlx::mysql::MySqlRow, ordinal: usize) -> Value {
708    use sqlx::TypeInfo;
709    let type_name = row.columns()[ordinal].type_info().name();
710    match type_name {
711        "BOOLEAN" => match row.try_get::<Option<bool>, usize>(ordinal) {
712            Ok(v) => v.map(Value::Bool).unwrap_or(Value::Null),
713            Err(_) => Value::Null,
714        },
715        "TINYINT" | "TINYINT UNSIGNED" => match row.try_get::<Option<i8>, usize>(ordinal) {
716            Ok(v) => v.map(Value::I8).unwrap_or(Value::Null),
717            Err(_) => match row.try_get::<Option<u8>, usize>(ordinal) {
718                Ok(v) => v.map(Value::U8).unwrap_or(Value::Null),
719                Err(_) => Value::Null,
720            },
721        },
722        "SMALLINT" | "SMALLINT UNSIGNED" => match row.try_get::<Option<i16>, usize>(ordinal) {
723            Ok(v) => v.map(Value::I16).unwrap_or(Value::Null),
724            Err(_) => match row.try_get::<Option<u16>, usize>(ordinal) {
725                Ok(v) => v.map(Value::U16).unwrap_or(Value::Null),
726                Err(_) => Value::Null,
727            },
728        },
729        "INT" | "INT UNSIGNED" | "MEDIUMINT" | "MEDIUMINT UNSIGNED" => {
730            match row.try_get::<Option<i32>, usize>(ordinal) {
731                Ok(v) => v.map(Value::I32).unwrap_or(Value::Null),
732                Err(_) => match row.try_get::<Option<u32>, usize>(ordinal) {
733                    Ok(v) => v.map(Value::U32).unwrap_or(Value::Null),
734                    Err(_) => Value::Null,
735                },
736            }
737        }
738        "BIGINT" | "BIGINT UNSIGNED" => match row.try_get::<Option<i64>, usize>(ordinal) {
739            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
740            Err(_) => match row.try_get::<Option<u64>, usize>(ordinal) {
741                Ok(v) => v.map(Value::U64).unwrap_or(Value::Null),
742                Err(_) => Value::Null,
743            },
744        },
745        "FLOAT" => match row.try_get::<Option<f32>, usize>(ordinal) {
746            Ok(v) => v.map(Value::F32).unwrap_or(Value::Null),
747            Err(_) => Value::Null,
748        },
749        "DOUBLE" => match row.try_get::<Option<f64>, usize>(ordinal) {
750            Ok(v) => v.map(Value::F64).unwrap_or(Value::Null),
751            Err(_) => Value::Null,
752        },
753        "VARCHAR" | "TEXT" | "CHAR" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => {
754            match row.try_get::<Option<String>, usize>(ordinal) {
755                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
756                Err(_) => Value::Null,
757            }
758        }
759        "BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB" | "BINARY" | "VARBINARY" => {
760            match row.try_get::<Option<Vec<u8>>, usize>(ordinal) {
761                Ok(v) => v.map(Value::Bytes).unwrap_or(Value::Null),
762                Err(_) => Value::Null,
763            }
764        }
765        // DECIMAL/NUMERIC 使用任意精度 BigDecimal 解码,38 位不截断
766        "DECIMAL" | "NUMERIC" | "NEWDECIMAL" => {
767            match row.try_get::<Option<bigdecimal::BigDecimal>, usize>(ordinal) {
768                Ok(Some(v)) => Value::Decimal(v.to_string()),
769                Ok(None) => Value::Null,
770                Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
771                    Ok(v) => v.map(Value::Decimal).unwrap_or(Value::Null),
772                    Err(_) => Value::Null,
773                },
774            }
775        }
776        // 时间类型必须按 chrono 解码:MySQL 二进制协议下按 String 解码必然失败 → 静默 NULL
777        "DATE" => match row.try_get::<Option<sqlx::types::chrono::NaiveDate>, usize>(ordinal) {
778            Ok(v) => v.map(|d| Value::Date(d.to_string())).unwrap_or(Value::Null),
779            Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
780                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
781                Err(_) => Value::Null,
782            },
783        },
784        "DATETIME" | "TIMESTAMP" => {
785            match row.try_get::<Option<sqlx::types::chrono::NaiveDateTime>, usize>(ordinal) {
786                Ok(v) => v
787                    .map(|d| Value::DateTime(d.to_string()))
788                    .unwrap_or(Value::Null),
789                Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
790                    Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
791                    Err(_) => Value::Null,
792                },
793            }
794        }
795        "TIME" => match row.try_get::<Option<sqlx::types::chrono::NaiveTime>, usize>(ordinal) {
796            Ok(v) => v.map(|t| Value::Time(t.to_string())).unwrap_or(Value::Null),
797            Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
798                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
799                Err(_) => Value::Null,
800            },
801        },
802        _ => {
803            // 未知类型回退:i64 → f64 → bool → String
804            if let Ok(v) = row.try_get::<Option<i64>, usize>(ordinal) {
805                return v.map(Value::I64).unwrap_or(Value::Null);
806            }
807            if let Ok(v) = row.try_get::<Option<f64>, usize>(ordinal) {
808                return v.map(Value::F64).unwrap_or(Value::Null);
809            }
810            if let Ok(v) = row.try_get::<Option<bool>, usize>(ordinal) {
811                return v.map(Value::Bool).unwrap_or(Value::Null);
812            }
813            if let Ok(v) = row.try_get::<Option<String>, usize>(ordinal) {
814                return v.map(Value::String).unwrap_or(Value::Null);
815            }
816            Value::Null
817        }
818    }
819}
820
821/// MySQL: 使用预解析的 ColType 进行类型分派(避免每行字符串 match)
822///
823/// 与 [`row_to_value_mysql`] 的区别:调用方预先通过 [`ColType::parse_mysql`] 解析列类型,
824/// 后续行直接用枚举分派(编译器优化为跳转表),避免每行每列的字符串比较。
825///
826/// **注意**:必须使用 `parse_mysql` 而非通用 `from_type_name`:MySQL 协议报告的类型名
827/// 包含 NEWDECIMAL/YEAR/ENUM/SET 等特有类型,通用映射无法识别。
828fn row_to_value_with_coltype_mysql(
829    row: &sqlx::mysql::MySqlRow,
830    ordinal: usize,
831    col_type: ColType,
832) -> Value {
833    match col_type {
834        ColType::Bool => match row.try_get::<Option<bool>, usize>(ordinal) {
835            Ok(v) => v.map(Value::Bool).unwrap_or(Value::Null),
836            Err(_) => Value::Null,
837        },
838        ColType::I8 => match row.try_get::<Option<i8>, usize>(ordinal) {
839            Ok(v) => v.map(Value::I8).unwrap_or(Value::Null),
840            Err(_) => match row.try_get::<Option<u8>, usize>(ordinal) {
841                Ok(v) => v.map(Value::U8).unwrap_or(Value::Null),
842                Err(_) => Value::Null,
843            },
844        },
845        ColType::I16 => match row.try_get::<Option<i16>, usize>(ordinal) {
846            Ok(v) => v.map(Value::I16).unwrap_or(Value::Null),
847            Err(_) => match row.try_get::<Option<u16>, usize>(ordinal) {
848                Ok(v) => v.map(Value::U16).unwrap_or(Value::Null),
849                Err(_) => Value::Null,
850            },
851        },
852        ColType::I32 => match row.try_get::<Option<i32>, usize>(ordinal) {
853            Ok(v) => v.map(Value::I32).unwrap_or(Value::Null),
854            Err(_) => match row.try_get::<Option<u32>, usize>(ordinal) {
855                Ok(v) => v.map(Value::U32).unwrap_or(Value::Null),
856                Err(_) => Value::Null,
857            },
858        },
859        ColType::I64 => match row.try_get::<Option<i64>, usize>(ordinal) {
860            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
861            Err(_) => match row.try_get::<Option<u64>, usize>(ordinal) {
862                Ok(v) => v.map(Value::U64).unwrap_or(Value::Null),
863                Err(_) => Value::Null,
864            },
865        },
866        ColType::U8 => match row.try_get::<Option<u8>, usize>(ordinal) {
867            Ok(v) => v.map(Value::U8).unwrap_or(Value::Null),
868            Err(_) => Value::Null,
869        },
870        ColType::U16 => match row.try_get::<Option<u16>, usize>(ordinal) {
871            Ok(v) => v.map(Value::U16).unwrap_or(Value::Null),
872            Err(_) => Value::Null,
873        },
874        ColType::U32 => match row.try_get::<Option<u32>, usize>(ordinal) {
875            Ok(v) => v.map(Value::U32).unwrap_or(Value::Null),
876            Err(_) => Value::Null,
877        },
878        ColType::U64 => match row.try_get::<Option<u64>, usize>(ordinal) {
879            Ok(v) => v.map(Value::U64).unwrap_or(Value::Null),
880            Err(_) => Value::Null,
881        },
882        ColType::F32 => match row.try_get::<Option<f32>, usize>(ordinal) {
883            Ok(v) => v.map(Value::F32).unwrap_or(Value::Null),
884            Err(_) => Value::Null,
885        },
886        ColType::F64 => match row.try_get::<Option<f64>, usize>(ordinal) {
887            Ok(v) => v.map(Value::F64).unwrap_or(Value::Null),
888            Err(_) => Value::Null,
889        },
890        // DECIMAL/NUMERIC 使用任意精度 BigDecimal 解码,38 位不截断
891        ColType::Decimal => match row.try_get::<Option<bigdecimal::BigDecimal>, usize>(ordinal) {
892            Ok(Some(v)) => Value::Decimal(v.to_string()),
893            Ok(None) => Value::Null,
894            Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
895                Ok(v) => v.map(Value::Decimal).unwrap_or(Value::Null),
896                Err(_) => Value::Null,
897            },
898        },
899        ColType::String => match row.try_get::<Option<String>, usize>(ordinal) {
900            Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
901            Err(_) => Value::Null,
902        },
903        ColType::Bytes => match row.try_get::<Option<Vec<u8>>, usize>(ordinal) {
904            Ok(v) => v.map(Value::Bytes).unwrap_or(Value::Null),
905            Err(_) => Value::Null,
906        },
907        // 时间类型必须按 chrono 解码:MySQL 二进制协议下按 String 解码必然失败 → 静默 NULL
908        ColType::Date => {
909            match row.try_get::<Option<sqlx::types::chrono::NaiveDate>, usize>(ordinal) {
910                Ok(v) => v.map(|d| Value::Date(d.to_string())).unwrap_or(Value::Null),
911                Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
912                    Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
913                    Err(_) => Value::Null,
914                },
915            }
916        }
917        ColType::DateTime => {
918            match row.try_get::<Option<sqlx::types::chrono::NaiveDateTime>, usize>(ordinal) {
919                Ok(v) => v
920                    .map(|d| Value::DateTime(d.to_string()))
921                    .unwrap_or(Value::Null),
922                Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
923                    Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
924                    Err(_) => Value::Null,
925                },
926            }
927        }
928        ColType::Time => {
929            match row.try_get::<Option<sqlx::types::chrono::NaiveTime>, usize>(ordinal) {
930                Ok(v) => v.map(|t| Value::Time(t.to_string())).unwrap_or(Value::Null),
931                Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
932                    Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
933                    Err(_) => Value::Null,
934                },
935            }
936        }
937        // JSON/UUID 在 MySQL 协议下以文本传输
938        ColType::Json | ColType::Uuid => match row.try_get::<Option<String>, usize>(ordinal) {
939            Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
940            Err(_) => Value::Null,
941        },
942        ColType::Unknown => {
943            // 未知类型回退:i64 → f64 → bool → String
944            if let Ok(v) = row.try_get::<Option<i64>, usize>(ordinal) {
945                return v.map(Value::I64).unwrap_or(Value::Null);
946            }
947            if let Ok(v) = row.try_get::<Option<f64>, usize>(ordinal) {
948                return v.map(Value::F64).unwrap_or(Value::Null);
949            }
950            if let Ok(v) = row.try_get::<Option<bool>, usize>(ordinal) {
951                return v.map(Value::Bool).unwrap_or(Value::Null);
952            }
953            if let Ok(v) = row.try_get::<Option<String>, usize>(ordinal) {
954                return v.map(Value::String).unwrap_or(Value::Null);
955            }
956            Value::Null
957        }
958        // ColType 标记为 #[non_exhaustive],未来新增变体按 Unknown 处理
959        _ => Value::Null,
960    }
961}
962
963pub struct MySqlPoolHandle {
964    pool: sqlx::MySqlPool,
965}
966
967impl MySqlPoolHandle {
968    pub async fn connect(url: &str) -> Result<Self, DbError> {
969        // 默认容量 10(向后兼容);上层应用 `connect_with_max` 对齐外层 PoolConfig::max_size
970        Self::connect_with_max(url, 10).await
971    }
972
973    /// 容量显式版 connect(2026-10-28 P3-4,PERFV2-03):与外层 `PoolConfig::max_size` 对齐,
974    /// 避免"外层按 max_size 记账、内层硬编码 10"的双层上限不一致(详见 SqlitePoolHandle::connect_with_max)。
975    pub async fn connect_with_max(url: &str, max_connections: u32) -> Result<Self, DbError> {
976        let pool = sqlx::pool::PoolOptions::<sqlx::MySql>::new()
977            .max_connections(max_connections)
978            .acquire_timeout(std::time::Duration::from_secs(30))
979            .idle_timeout(Some(std::time::Duration::from_secs(600)))
980            .max_lifetime(Some(std::time::Duration::from_secs(1800)))
981            .connect(url)
982            .await
983            .map_err(map_sqlx_error)?;
984        Ok(Self { pool })
985    }
986
987    pub fn from_pool(pool: sqlx::MySqlPool) -> Self {
988        Self { pool }
989    }
990
991    pub fn pool(&self) -> &sqlx::MySqlPool {
992        &self.pool
993    }
994}
995
996pub struct SqlxMySqlConnectionFactory {
997    pool: Arc<MySqlPoolHandle>,
998}
999
1000impl SqlxMySqlConnectionFactory {
1001    pub fn new(pool: Arc<MySqlPoolHandle>) -> Self {
1002        Self { pool }
1003    }
1004}
1005
1006#[async_trait]
1007impl ConnectionFactory for SqlxMySqlConnectionFactory {
1008    async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
1009        let conn = self.pool.pool.acquire().await.map_err(map_sqlx_error)?;
1010        // 2026-10-28 P1-3:MetricsConn 包装统一上报查询指标(ops/latency/errors)
1011        Ok(Box::new(crate::metrics_conn::MetricsConn::new(Box::new(
1012            SqlxMySqlConnection {
1013                conn: Some(conn),
1014                connected: true,
1015                in_transaction: false,
1016                // 新连接会话天然处于默认状态:干净(R5 脏标记跳过复位)
1017                session_dirty: false,
1018            },
1019        ))))
1020    }
1021}
1022
1023pub struct SqlxMySqlConnection {
1024    conn: Option<sqlx::pool::PoolConnection<sqlx::MySql>>,
1025    connected: bool,
1026    in_transaction: bool,
1027    /// R5 优化(v10.13.1):本次租用是否执行过会话级语句(SET/USE)。
1028    /// 连接池归还时据此跳过无条件复位(省 1 条语句 + 1 个往返/借用)。
1029    session_dirty: bool,
1030}
1031
1032/// 判断一条 SQL 是否可能在成功执行后改变会话级状态(R5 脏标记依据)。
1033///
1034/// 置脏:`SET`(含 `SET SESSION`/`SET NAMES`/`SET autocommit`/用户变量)与 `USE`。
1035/// 不置脏:`SET TRANSACTION`(按 R3 仅作用下一事务,事务结束不残留)、
1036/// 只读语句(`SELECT`/`SHOW`/`EXPLAIN`/`DESC`)、事务语句
1037/// (`START TRANSACTION`/`BEGIN`/`COMMIT`/`ROLLBACK`——autocommit 语义自动恢复,
1038/// 且池在归还时本就回滚未提交事务)。
1039pub(crate) fn marks_session_dirty(sql: &str) -> bool {
1040    // 大小写不敏感地截取语句头部比较(32 字符足够覆盖前两个词)
1041    let trimmed = sql.trim_start();
1042    let mut upper = String::with_capacity(32);
1043    for ch in trimmed.chars() {
1044        if upper.len() >= 32 {
1045            break;
1046        }
1047        upper.push(ch.to_ascii_uppercase());
1048    }
1049    if upper.starts_with("USE ") || upper.starts_with("USE\t") || upper == "USE" {
1050        return true;
1051    }
1052    if !upper.starts_with("SET") {
1053        return false;
1054    }
1055    // "SET" 后必须跟词边界(排除 "SETUP..." 这类误判)
1056    let after_set = &upper[3..];
1057    if !(after_set.is_empty() || after_set.starts_with(' ') || after_set.starts_with('\t')) {
1058        return false;
1059    }
1060    let rest = after_set.trim_start();
1061    // `SET TRANSACTION ...` 仅作用下一事务;`SET TRANSACTIONX` 是标识符语境,置脏
1062    !(rest.starts_with("TRANSACTION")
1063        && rest["TRANSACTION".len()..]
1064            .chars()
1065            .next()
1066            .map(|c| c.is_whitespace() || c == ';')
1067            .unwrap_or(true))
1068}
1069
1070impl Connection for SqlxMySqlConnection {
1071    fn db_type(&self) -> Option<DbType> {
1072        Some(DbType::MySQL)
1073    }
1074
1075    /// MySQL 的 `COM_RESET_CONNECTION` 是协议命令而非 SQL 语句(sqlx 未暴露),
1076    /// 归还连接时用单条 `SET SESSION` 复位关键会话变量。必须保持单语句且
1077    /// 版本无关:`transaction_isolation`/`max_execution_time` 在 MySQL 5.7、
1078    /// MariaDB 上名称为 `tx_isolation`/`max_statement_time`,故不在此复位;
1079    /// 隔离级别由事务层按“下一事务”作用域下发(R3),不会残留到会话。
1080    fn session_reset_sql(&self) -> Option<&'static str> {
1081        Some(
1082            "SET SESSION sql_mode = DEFAULT, \
1083             SESSION character_set_client = @@GLOBAL.character_set_client, \
1084             SESSION character_set_results = @@GLOBAL.character_set_results, \
1085             SESSION character_set_connection = @@GLOBAL.character_set_connection, \
1086             SESSION time_zone = @@GLOBAL.time_zone, \
1087             SESSION autocommit = 1",
1088        )
1089    }
1090
1091    /// R5 脏标记(v10.13.1):读取并清除脏标记(take 语义)。
1092    /// 返回 false 时池跳过复位语句——连接从未执行过 SET/USE,会话仍是默认状态。
1093    fn take_session_dirty(&mut self) -> bool {
1094        std::mem::take(&mut self.session_dirty)
1095    }
1096
1097    /// 复位语句执行成功后由池调用;复位 SQL 是 SET,执行路径会置脏,这里统一清零
1098    fn mark_session_clean(&mut self) {
1099        self.session_dirty = false;
1100    }
1101
1102    fn execute<'a>(
1103        &'a mut self,
1104        sql: &'a str,
1105    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
1106        Box::pin(async move {
1107            let mut pool_conn = self
1108                .conn
1109                .take()
1110                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1111            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
1112            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
1113            let result = if needs_raw_sql(sql) {
1114                (&mut *pool_conn)
1115                    .execute(sqlx::raw_sql(sqlx::AssertSqlSafe(sql)))
1116                    .await
1117            } else {
1118                (&mut *pool_conn).execute(sqlx::AssertSqlSafe(sql)).await
1119            };
1120            self.conn = Some(pool_conn);
1121
1122            match result {
1123                Ok(r) => {
1124                    // R5 脏标记(v10.13.1):仅执行成功的语句才可能改变会话状态
1125                    if marks_session_dirty(sql) {
1126                        self.session_dirty = true;
1127                    }
1128                    Ok(r.rows_affected())
1129                }
1130                Err(e) => {
1131                    let db_err = map_sqlx_error(e);
1132                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
1133                        self.connected = false;
1134                    }
1135                    Err(db_err)
1136                }
1137            }
1138        })
1139    }
1140
1141    fn query<'a>(
1142        &'a mut self,
1143        sql: &'a str,
1144    ) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, DbError>> + Send + 'a>>
1145    {
1146        Box::pin(async move {
1147            let mut pool_conn = self
1148                .conn
1149                .take()
1150                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1151            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
1152            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
1153            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
1154            self.conn = Some(pool_conn);
1155
1156            let rows = rows_result.map_err(map_sqlx_error)?;
1157            if rows.is_empty() {
1158                return Ok(Vec::new());
1159            }
1160            // 预解析列类型(只在第一行解析,后续行复用,避免每行字符串 match)
1161            // MySQL 专用解析:覆盖 MySQL 协议特有类型名(NEWDECIMAL/YEAR/ENUM/SET 等)
1162            let col_types: Vec<ColType> = rows[0]
1163                .columns()
1164                .iter()
1165                .map(|col| ColType::parse_mysql(col.type_info().name()))
1166                .collect();
1167            let col_names: Vec<String> = rows[0]
1168                .columns()
1169                .iter()
1170                .map(|col| col.name().to_string())
1171                .collect();
1172            let mut result = Vec::with_capacity(rows.len());
1173            for row in &rows {
1174                let mut record = HashMap::with_capacity(col_types.len());
1175                for (i, name) in col_names.iter().enumerate() {
1176                    let value = row_to_value_with_coltype_mysql(row, i, col_types[i]);
1177                    record.insert(name.clone(), value);
1178                }
1179                result.push(record);
1180            }
1181            Ok(result)
1182        })
1183    }
1184
1185    fn begin_transaction<'a>(
1186        &'a mut self,
1187    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
1188        Box::pin(async move {
1189            if self.in_transaction {
1190                return Err(DbError::Internal("transaction already started".to_string()));
1191            }
1192            self.execute("BEGIN").await?;
1193            self.in_transaction = true;
1194            Ok(())
1195        })
1196    }
1197
1198    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
1199        Box::pin(async move {
1200            if self.in_transaction {
1201                self.execute("COMMIT").await?;
1202                self.in_transaction = false;
1203            }
1204            Ok(())
1205        })
1206    }
1207
1208    fn rollback<'a>(
1209        &'a mut self,
1210    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
1211        Box::pin(async move {
1212            if self.in_transaction {
1213                // 回滚失败时保留事务标志:非连接类错误下服务端事务可能仍开启,
1214                // 若此刻清标志,连接带着开放事务回池将绕过归还路径的自动回滚
1215                // (事务泄漏)。失败后由归还路径重试回滚或直接关闭连接。
1216                // 语义与 Oracle/MSSQL 适配器一致。
1217                let result = self.execute("ROLLBACK").await;
1218                if result.is_ok() {
1219                    self.in_transaction = false;
1220                }
1221                result.map(|_| ())
1222            } else {
1223                Ok(())
1224            }
1225        })
1226    }
1227
1228    fn is_connected(&self) -> bool {
1229        // 纳入 conn.is_some():query_stream 提前弃读时 PoolConnection 已从包装器
1230        // take() 出去(self.conn = None),毒化状态不得被判定为可用连接
1231        // (否则回池后下次 acquire 借出即报 "connection already closed")
1232        self.connected && self.conn.is_some()
1233    }
1234
1235    fn in_transaction(&self) -> bool {
1236        self.in_transaction
1237    }
1238
1239    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
1240        Box::pin(async move {
1241            match self.execute("SELECT 1").await {
1242                Ok(_) => true,
1243                Err(_) => {
1244                    self.connected = false;
1245                    false
1246                }
1247            }
1248        })
1249    }
1250
1251    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
1252        Box::pin(async move {
1253            if let Some(conn) = self.conn.take() {
1254                drop(conn);
1255            }
1256            self.connected = false;
1257            self.in_transaction = false;
1258            Ok(())
1259        })
1260    }
1261
1262    /// MySQL 参数绑定执行(INSERT/UPDATE/DELETE)
1263    ///
1264    /// 使用 sqlx prepared statement 绑定参数。MySQL 协议原生支持 `?` 占位符。
1265    /// 对 `Value::Bool`/`I8`..=`I64`/`U8`..=`U64`/`F32`/`F64`/`String`/`Bytes` 直接 bind;
1266    /// 其他类型回退为 `to_string()` 后以 TEXT 绑定。
1267    fn execute_with_params<'a>(
1268        &'a mut self,
1269        sql: &'a str,
1270        params: &'a [Value],
1271    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
1272        Box::pin(async move {
1273            if needs_raw_sql(sql) || params.is_empty() {
1274                return self.execute(sql).await;
1275            }
1276            let mut pool_conn = self
1277                .conn
1278                .take()
1279                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1280            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
1281            for v in params {
1282                q = match v {
1283                    Value::Null => q.bind(None::<i64>),
1284                    Value::Bool(b) => q.bind(*b),
1285                    Value::I8(n) => q.bind(*n),
1286                    Value::I16(n) => q.bind(*n),
1287                    Value::I32(n) => q.bind(*n),
1288                    Value::I64(n) => q.bind(*n),
1289                    Value::U8(n) => q.bind(*n),
1290                    Value::U16(n) => q.bind(*n),
1291                    Value::U32(n) => q.bind(*n),
1292                    Value::U64(n) => q.bind(*n as i64),
1293                    Value::F32(f) => q.bind(*f),
1294                    Value::F64(f) => q.bind(*f),
1295                    Value::String(s) => q.bind(s.as_str()),
1296                    Value::Decimal(s) => q.bind(s.as_str()),
1297                    Value::Bytes(b) => q.bind(b.as_slice()),
1298                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
1299                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
1300                    Value::Uuid(s)
1301                    | Value::Date(s)
1302                    | Value::DateTime(s)
1303                    | Value::Time(s)
1304                    | Value::Json(s) => q.bind(s.as_str()),
1305                    other => q.bind(other.to_string()),
1306                };
1307            }
1308            let result = q.execute(&mut *pool_conn).await;
1309            self.conn = Some(pool_conn);
1310            match result {
1311                Ok(r) => {
1312                    // R5 脏标记(v10.13.1):参数化路径同样可能执行 SET(如 SET x = ?)
1313                    if marks_session_dirty(sql) {
1314                        self.session_dirty = true;
1315                    }
1316                    Ok(r.rows_affected())
1317                }
1318                Err(e) => {
1319                    let db_err = map_sqlx_error(e);
1320                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
1321                        self.connected = false;
1322                    }
1323                    Err(db_err)
1324                }
1325            }
1326        })
1327    }
1328
1329    /// MySQL 参数绑定查询(SELECT,HashMap 映射)
1330    fn query_with_params<'a>(
1331        &'a mut self,
1332        sql: &'a str,
1333        params: &'a [Value],
1334    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
1335        Box::pin(async move {
1336            if params.is_empty() {
1337                return self.query(sql).await;
1338            }
1339            let mut pool_conn = self
1340                .conn
1341                .take()
1342                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1343            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
1344            for v in params {
1345                q = match v {
1346                    Value::Null => q.bind(None::<i64>),
1347                    Value::Bool(b) => q.bind(*b),
1348                    Value::I8(n) => q.bind(*n),
1349                    Value::I16(n) => q.bind(*n),
1350                    Value::I32(n) => q.bind(*n),
1351                    Value::I64(n) => q.bind(*n),
1352                    Value::U8(n) => q.bind(*n),
1353                    Value::U16(n) => q.bind(*n),
1354                    Value::U32(n) => q.bind(*n),
1355                    Value::U64(n) => q.bind(*n as i64),
1356                    Value::F32(f) => q.bind(*f),
1357                    Value::F64(f) => q.bind(*f),
1358                    Value::String(s) => q.bind(s.as_str()),
1359                    Value::Decimal(s) => q.bind(s.as_str()),
1360                    Value::Bytes(b) => q.bind(b.as_slice()),
1361                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
1362                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
1363                    Value::Uuid(s)
1364                    | Value::Date(s)
1365                    | Value::DateTime(s)
1366                    | Value::Time(s)
1367                    | Value::Json(s) => q.bind(s.as_str()),
1368                    other => q.bind(other.to_string()),
1369                };
1370            }
1371            let rows_result = q.fetch_all(&mut *pool_conn).await;
1372            self.conn = Some(pool_conn);
1373            let rows = rows_result.map_err(map_sqlx_error)?;
1374            if rows.is_empty() {
1375                return Ok(Vec::new());
1376            }
1377            let col_names: Vec<String> = rows[0]
1378                .columns()
1379                .iter()
1380                .map(|col| col.name().to_string())
1381                .collect();
1382            let mut result = Vec::with_capacity(rows.len());
1383            for row in &rows {
1384                let mut record = HashMap::with_capacity(col_names.len());
1385                for (i, name) in col_names.iter().enumerate() {
1386                    let value = row_to_value_mysql(row, i);
1387                    record.insert(name.clone(), value);
1388                }
1389                result.push(record);
1390            }
1391            Ok(result)
1392        })
1393    }
1394
1395    /// MySQL 位置式查询(SELECT,无参数)
1396    ///
1397    /// 绕过 HashMap 行映射,返回 `(列名, 按列序号的值矩阵)`。适用于 SELECT ALL 大结果集。
1398    fn query_values<'a>(
1399        &'a mut self,
1400        sql: &'a str,
1401    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
1402        Box::pin(async move {
1403            let mut pool_conn = self
1404                .conn
1405                .take()
1406                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1407            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
1408            self.conn = Some(pool_conn);
1409            let rows = rows_result.map_err(map_sqlx_error)?;
1410            if rows.is_empty() {
1411                return Ok((Vec::new(), Vec::new()));
1412            }
1413            let cols = rows[0].columns();
1414            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
1415            let mut col_types: Vec<ColType> = Vec::with_capacity(cols.len());
1416            for col in cols {
1417                col_names.push(col.name().to_string());
1418                col_types.push(ColType::parse_mysql(col.type_info().name()));
1419            }
1420            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
1421            for row in &rows {
1422                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
1423                for (idx, _) in col_names.iter().enumerate() {
1424                    row_values.push(row_to_value_with_coltype_mysql(row, idx, col_types[idx]));
1425                }
1426                result_rows.push(row_values);
1427            }
1428            Ok((col_names, result_rows))
1429        })
1430    }
1431
1432    /// MySQL 参数绑定位置式查询(SELECT)
1433    fn query_values_with_params<'a>(
1434        &'a mut self,
1435        sql: &'a str,
1436        params: &'a [Value],
1437    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
1438        Box::pin(async move {
1439            if params.is_empty() {
1440                return self.query_values(sql).await;
1441            }
1442            let mut pool_conn = self
1443                .conn
1444                .take()
1445                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1446            let mut q = sqlx::query(sqlx::AssertSqlSafe(sql));
1447            for v in params {
1448                q = match v {
1449                    Value::Null => q.bind(None::<i64>),
1450                    Value::Bool(b) => q.bind(*b),
1451                    Value::I8(n) => q.bind(*n),
1452                    Value::I16(n) => q.bind(*n),
1453                    Value::I32(n) => q.bind(*n),
1454                    Value::I64(n) => q.bind(*n),
1455                    Value::U8(n) => q.bind(*n),
1456                    Value::U16(n) => q.bind(*n),
1457                    Value::U32(n) => q.bind(*n),
1458                    Value::U64(n) => q.bind(*n as i64),
1459                    Value::F32(f) => q.bind(*f),
1460                    Value::F64(f) => q.bind(*f),
1461                    Value::String(s) => q.bind(s.as_str()),
1462                    Value::Decimal(s) => q.bind(s.as_str()),
1463                    Value::Bytes(b) => q.bind(b.as_slice()),
1464                    // 字符串类变体直接按文本绑定:`Value::Display` 会给字符串加引号,
1465                    // 走 `to_string()` 会把 '2026-01-01 00:00:00' 连同引号一起写入
1466                    Value::Uuid(s)
1467                    | Value::Date(s)
1468                    | Value::DateTime(s)
1469                    | Value::Time(s)
1470                    | Value::Json(s) => q.bind(s.as_str()),
1471                    other => q.bind(other.to_string()),
1472                };
1473            }
1474            let rows_result = q.fetch_all(&mut *pool_conn).await;
1475            self.conn = Some(pool_conn);
1476            let rows = rows_result.map_err(map_sqlx_error)?;
1477            if rows.is_empty() {
1478                return Ok((Vec::new(), Vec::new()));
1479            }
1480            let cols = rows[0].columns();
1481            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
1482            let mut col_types: Vec<ColType> = Vec::with_capacity(cols.len());
1483            for col in cols {
1484                col_names.push(col.name().to_string());
1485                col_types.push(ColType::parse_mysql(col.type_info().name()));
1486            }
1487            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
1488            for row in &rows {
1489                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
1490                for (idx, _) in col_names.iter().enumerate() {
1491                    row_values.push(row_to_value_with_coltype_mysql(row, idx, col_types[idx]));
1492                }
1493                result_rows.push(row_values);
1494            }
1495            Ok((col_names, result_rows))
1496        })
1497    }
1498
1499    /// MySQL 流式查询:逐行返回结果,避免大结果集 `fetch_all` 内存峰值
1500    ///
1501    /// 使用 `sqlx::query::fetch` 获取行流,逐行映射为 `HashMap<String, Value>`。
1502    /// 流正常消费完毕后连接归还 `self.conn`;若提前 drop 流,连接通过
1503    /// `PoolConnection::drop` 归还到 sqlx 池,但 `self.conn` 变为 `None`。
1504    fn query_stream<'a>(
1505        &'a mut self,
1506        sql: &'a str,
1507    ) -> Pin<Box<dyn futures::Stream<Item = Result<HashMap<String, Value>, DbError>> + Send + 'a>>
1508    {
1509        Box::pin(async_stream::try_stream! {
1510            let mut pool_conn = self
1511                .conn
1512                .take()
1513                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
1514            let mut row_stream = sqlx::query(sqlx::AssertSqlSafe(sql)).fetch(&mut *pool_conn);
1515            let mut col_types: Vec<ColType> = Vec::new();
1516            let mut col_names: Vec<String> = Vec::new();
1517            let mut first_row = true;
1518            while let Some(row_result) = row_stream.next().await {
1519                let row = row_result.map_err(map_sqlx_error)?;
1520                if first_row {
1521                    for col in row.columns() {
1522                        col_names.push(col.name().to_string());
1523                        col_types.push(ColType::parse_mysql(col.type_info().name()));
1524                    }
1525                    first_row = false;
1526                }
1527                let mut record = HashMap::with_capacity(col_names.len());
1528                for (i, name) in col_names.iter().enumerate() {
1529                    let value = row_to_value_with_coltype_mysql(&row, i, col_types[i]);
1530                    record.insert(name.clone(), value);
1531                }
1532                yield record;
1533            }
1534            // 显式释放 row_stream 对 pool_conn 的可变借用,否则借用检查器
1535            // 无法证明 row_stream 在移动 pool_conn 前已被销毁(E0505)
1536            drop(row_stream);
1537            self.conn = Some(pool_conn);
1538        })
1539    }
1540}
1541
1542impl Drop for SqlxMySqlConnection {
1543    fn drop(&mut self) {
1544        if let Some(conn) = self.conn.take() {
1545            drop(conn);
1546        }
1547    }
1548}
1549
1550/// MySQL 批量导入(多行 INSERT,作为 LOAD DATA LOCAL INFILE 的安全替代)
1551///
1552/// 构建多行 INSERT 语句:`INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...`
1553/// 使用参数绑定避免 SQL 注入。MySQL 原生支持 `?` 占位符。
1554/// 当数据量极大时建议分批调用(每批 1000~10000 行),避免 SQL 过长或超出
1555/// `max_allowed_packet` 限制。
1556pub async fn mysql_bulk_insert(
1557    conn: &mut SqlxMySqlConnection,
1558    table: &str,
1559    columns: &[&str],
1560    rows: &[Vec<Value>],
1561) -> Result<u64, DbError> {
1562    if rows.is_empty() {
1563        return Ok(0);
1564    }
1565    let col_list = columns.join(", ");
1566    let cols_per_row = columns.len();
1567    // MySQL 占位符 ? 每列一个,跨行复用相同模式
1568    let row_placeholder = format!("({})", vec!["?"; cols_per_row].join(", "));
1569    let placeholders = vec![row_placeholder; rows.len()].join(", ");
1570    let sql = format!(
1571        "INSERT INTO {} ({}) VALUES {}",
1572        table, col_list, placeholders
1573    );
1574    // 展平所有行的值为单一参数数组
1575    let mut params: Vec<Value> = Vec::with_capacity(rows.len() * cols_per_row);
1576    for row in rows {
1577        for v in row {
1578            params.push(v.clone());
1579        }
1580    }
1581    conn.execute_with_params(&sql, &params).await
1582}
1583
1584// ===================== PostgreSQL 适配器 =====================
1585
1586// 注:sqlx 0.9 起 Executor trait 要求 'static lifetime,
1587// 原先的 execute_pg_boxed / query_pg_boxed 已内联到调用点。
1588
1589/// 构造带列名的解码错误:时间类型解码失败必须上抛,不得静默变成 NULL
1590fn pg_decode_err(
1591    row: &sqlx::postgres::PgRow,
1592    ordinal: usize,
1593    type_name: &str,
1594    err: sqlx::Error,
1595) -> DbError {
1596    let column = row
1597        .columns()
1598        .get(ordinal)
1599        .map(|c| c.name().to_string())
1600        .unwrap_or_else(|| format!("#{ordinal}"));
1601    DbError::QueryError(format!(
1602        "PostgreSQL 列 `{column}`(类型 {type_name},序号 {ordinal})解码失败:{err};\
1603         已拒绝以 NULL 代替真实值"
1604    ))
1605}
1606
1607fn row_to_value_pg(row: &sqlx::postgres::PgRow, ordinal: usize) -> Result<Value, DbError> {
1608    use sqlx::TypeInfo;
1609    let type_name = row.columns()[ordinal].type_info().name();
1610    macro_rules! dec {
1611        ($ty:ty) => {
1612            row.try_get::<Option<$ty>, usize>(ordinal)
1613                .map_err(|e| pg_decode_err(row, ordinal, type_name, e))
1614        };
1615    }
1616    match type_name {
1617        "BOOL" => Ok(dec!(bool)?.map(Value::Bool).unwrap_or(Value::Null)),
1618        "INT2" => Ok(dec!(i16)?.map(Value::I16).unwrap_or(Value::Null)),
1619        "INT4" | "OID" => Ok(dec!(i32)?.map(Value::I32).unwrap_or(Value::Null)),
1620        "INT8" => Ok(dec!(i64)?.map(Value::I64).unwrap_or(Value::Null)),
1621        "FLOAT4" => Ok(dec!(f32)?.map(Value::F32).unwrap_or(Value::Null)),
1622        "FLOAT8" => Ok(dec!(f64)?.map(Value::F64).unwrap_or(Value::Null)),
1623        "TEXT" | "VARCHAR" | "CHAR" | "NAME" | "BPCHAR" => {
1624            Ok(dec!(String)?.map(Value::String).unwrap_or(Value::Null))
1625        }
1626        "BYTEA" => Ok(dec!(Vec<u8>)?.map(Value::Bytes).unwrap_or(Value::Null)),
1627        // R2:NUMERIC 用任意精度 BigDecimal 解码,38 位不截断
1628        "NUMERIC" => Ok(dec!(bigdecimal::BigDecimal)?
1629            .map(|v| Value::Decimal(v.to_string()))
1630            .unwrap_or(Value::Null)),
1631        "UUID" => Ok(dec!(sqlx::types::Uuid)?
1632            .map(|uuid| Value::String(uuid.to_string()))
1633            .unwrap_or(Value::Null)),
1634        // R1:时间类型此前落入未知分支 → 静默 NULL
1635        "TIMESTAMP" => Ok(dec!(sqlx::types::chrono::NaiveDateTime)?
1636            .map(|v| Value::DateTime(v.to_string()))
1637            .unwrap_or(Value::Null)),
1638        "TIMESTAMPTZ" => Ok(
1639            dec!(sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>)?
1640                .map(|v| Value::DateTime(v.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string()))
1641                .unwrap_or(Value::Null),
1642        ),
1643        "DATE" => Ok(dec!(sqlx::types::chrono::NaiveDate)?
1644            .map(|v| Value::Date(v.to_string()))
1645            .unwrap_or(Value::Null)),
1646        "TIME" => Ok(dec!(sqlx::types::chrono::NaiveTime)?
1647            .map(|v| Value::Time(v.to_string()))
1648            .unwrap_or(Value::Null)),
1649        "JSON" | "JSONB" => Ok(dec!(serde_json::Value)?
1650            .map(|v| Value::Json(v.to_string()))
1651            .unwrap_or(Value::Null)),
1652        _ => {
1653            // 未知类型回退:i64 → f64 → bool → String;全部失败则报错(不再静默 NULL)
1654            if let Ok(v) = row.try_get::<Option<i64>, usize>(ordinal) {
1655                return Ok(v.map(Value::I64).unwrap_or(Value::Null));
1656            }
1657            if let Ok(v) = row.try_get::<Option<f64>, usize>(ordinal) {
1658                return Ok(v.map(Value::F64).unwrap_or(Value::Null));
1659            }
1660            if let Ok(v) = row.try_get::<Option<bool>, usize>(ordinal) {
1661                return Ok(v.map(Value::Bool).unwrap_or(Value::Null));
1662            }
1663            match row.try_get::<Option<String>, usize>(ordinal) {
1664                Ok(v) => Ok(v.map(Value::String).unwrap_or(Value::Null)),
1665                Err(e) => Err(pg_decode_err(row, ordinal, type_name, e)),
1666            }
1667        }
1668    }
1669}
1670
1671/// PostgreSQL: 使用预解析的 ColType 进行类型分派(避免每行字符串 match)
1672///
1673/// 与 [`row_to_value_pg`] 的区别:调用方预先通过 [`ColType::parse_postgres`] 解析列类型,
1674/// 后续行直接用枚举分派(编译器优化为跳转表),避免每行每列的字符串比较。
1675///
1676/// **注意**:必须使用 `parse_postgres` 而非通用 `from_type_name`:PostgreSQL 使用
1677/// PG 内部类型名(INT4/INT8/FLOAT8/BPCHAR/JSONB/TIMESTAMPTZ 等),通用映射无法识别。
1678fn row_to_value_with_coltype_pg(
1679    row: &sqlx::postgres::PgRow,
1680    ordinal: usize,
1681    col_type: ColType,
1682) -> Value {
1683    match col_type {
1684        ColType::Bool => match row.try_get::<Option<bool>, usize>(ordinal) {
1685            Ok(v) => v.map(Value::Bool).unwrap_or(Value::Null),
1686            Err(_) => Value::Null,
1687        },
1688        ColType::I8 => match row.try_get::<Option<i8>, usize>(ordinal) {
1689            Ok(v) => v.map(Value::I8).unwrap_or(Value::Null),
1690            Err(_) => Value::Null,
1691        },
1692        ColType::I16 => match row.try_get::<Option<i16>, usize>(ordinal) {
1693            Ok(v) => v.map(Value::I16).unwrap_or(Value::Null),
1694            Err(_) => Value::Null,
1695        },
1696        // PostgreSQL OID 在 parse_postgres 中归为 I32
1697        ColType::I32 => match row.try_get::<Option<i32>, usize>(ordinal) {
1698            Ok(v) => v.map(Value::I32).unwrap_or(Value::Null),
1699            Err(_) => Value::Null,
1700        },
1701        ColType::I64 => match row.try_get::<Option<i64>, usize>(ordinal) {
1702            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
1703            Err(_) => Value::Null,
1704        },
1705        // PostgreSQL 无原生无符号类型(u8/u16/u32):
1706        // - ColType::U8/U16/U32 仅在 parse_postgres 误判时出现(PG 无 UNSIGNED 关键字)
1707        // - 退化为最小可容纳的有符号类型:U8→i16、U16→i32、U32→i64
1708        // - 数值正确性保持,仅在 Value 枚举上为有符号变体(与 sqlx Type<Postgres> 实现一致)
1709        ColType::U8 => match row.try_get::<Option<i16>, usize>(ordinal) {
1710            Ok(v) => v.map(Value::I16).unwrap_or(Value::Null),
1711            Err(_) => Value::Null,
1712        },
1713        ColType::U16 => match row.try_get::<Option<i32>, usize>(ordinal) {
1714            Ok(v) => v.map(Value::I32).unwrap_or(Value::Null),
1715            Err(_) => Value::Null,
1716        },
1717        ColType::U32 => match row.try_get::<Option<i64>, usize>(ordinal) {
1718            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
1719            Err(_) => Value::Null,
1720        },
1721        // PostgreSQL 无原生 U64,按 i64 解码
1722        ColType::U64 => match row.try_get::<Option<i64>, usize>(ordinal) {
1723            Ok(v) => v.map(Value::I64).unwrap_or(Value::Null),
1724            Err(_) => Value::Null,
1725        },
1726        ColType::F32 => match row.try_get::<Option<f32>, usize>(ordinal) {
1727            Ok(v) => v.map(Value::F32).unwrap_or(Value::Null),
1728            Err(_) => Value::Null,
1729        },
1730        ColType::F64 => match row.try_get::<Option<f64>, usize>(ordinal) {
1731            Ok(v) => v.map(Value::F64).unwrap_or(Value::Null),
1732            Err(_) => Value::Null,
1733        },
1734        // NUMERIC/DECIMAL/MONEY 使用任意精度 BigDecimal 解码,避免 38 位精度截断
1735        ColType::Decimal => match row.try_get::<Option<bigdecimal::BigDecimal>, usize>(ordinal) {
1736            Ok(Some(v)) => Value::Decimal(v.to_string()),
1737            Ok(None) => Value::Null,
1738            Err(_) => match row.try_get::<Option<rust_decimal::Decimal>, usize>(ordinal) {
1739                Ok(Some(v)) => Value::Decimal(v.to_string()),
1740                Ok(None) => Value::Null,
1741                Err(_) => Value::Null,
1742            },
1743        },
1744        ColType::String => match row.try_get::<Option<String>, usize>(ordinal) {
1745            Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
1746            Err(_) => Value::Null,
1747        },
1748        ColType::Bytes => match row.try_get::<Option<Vec<u8>>, usize>(ordinal) {
1749            Ok(v) => v.map(Value::Bytes).unwrap_or(Value::Null),
1750            Err(_) => Value::Null,
1751        },
1752        // 时间类型必须按 chrono 解码:PG 二进制协议下按 String 解码必然失败
1753        ColType::Date => {
1754            match row.try_get::<Option<sqlx::types::chrono::NaiveDate>, usize>(ordinal) {
1755                Ok(v) => v.map(|d| Value::Date(d.to_string())).unwrap_or(Value::Null),
1756                Err(_) => Value::Null,
1757            }
1758        }
1759        ColType::DateTime => match row
1760            .try_get::<Option<sqlx::types::chrono::NaiveDateTime>, usize>(ordinal)
1761        {
1762            Ok(v) => v
1763                .map(|d| Value::DateTime(d.to_string()))
1764                .unwrap_or(Value::Null),
1765            Err(_) => match row
1766                .try_get::<Option<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>>, usize>(
1767                    ordinal,
1768                ) {
1769                Ok(v) => v
1770                    .map(|d| Value::DateTime(d.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string()))
1771                    .unwrap_or(Value::Null),
1772                Err(_) => Value::Null,
1773            },
1774        },
1775        ColType::Time => {
1776            match row.try_get::<Option<sqlx::types::chrono::NaiveTime>, usize>(ordinal) {
1777                Ok(v) => v.map(|t| Value::Time(t.to_string())).unwrap_or(Value::Null),
1778                Err(_) => Value::Null,
1779            }
1780        }
1781        ColType::Json => match row.try_get::<Option<serde_json::Value>, usize>(ordinal) {
1782            Ok(v) => v.map(|j| Value::Json(j.to_string())).unwrap_or(Value::Null),
1783            Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
1784                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
1785                Err(_) => Value::Null,
1786            },
1787        },
1788        // UUID:使用 sqlx::types::Uuid(16 字节)解码,避免 36 字符字符串的内存浪费
1789        ColType::Uuid => match row.try_get::<Option<sqlx::types::Uuid>, usize>(ordinal) {
1790            Ok(v) => v
1791                .map(|uuid| Value::String(uuid.to_string()))
1792                .unwrap_or(Value::Null),
1793            Err(_) => match row.try_get::<Option<String>, usize>(ordinal) {
1794                Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
1795                Err(_) => Value::Null,
1796            },
1797        },
1798        ColType::Unknown => {
1799            // 未知类型回退:i64 → f64 → bool → String
1800            if let Ok(v) = row.try_get::<Option<i64>, usize>(ordinal) {
1801                return v.map(Value::I64).unwrap_or(Value::Null);
1802            }
1803            if let Ok(v) = row.try_get::<Option<f64>, usize>(ordinal) {
1804                return v.map(Value::F64).unwrap_or(Value::Null);
1805            }
1806            if let Ok(v) = row.try_get::<Option<bool>, usize>(ordinal) {
1807                return v.map(Value::Bool).unwrap_or(Value::Null);
1808            }
1809            if let Ok(v) = row.try_get::<Option<String>, usize>(ordinal) {
1810                return v.map(Value::String).unwrap_or(Value::Null);
1811            }
1812            Value::Null
1813        }
1814        // ColType 标记为 #[non_exhaustive],未来新增变体按 Unknown 处理
1815        _ => Value::Null,
1816    }
1817}
1818
1819/// 将 SQL 中的 `?` 占位符重写为 PostgreSQL 原生 `$1..$n`
1820///
1821/// 仅改写字符串字面量、标识符引号、注释、dollar-quote(`$$...$$` / `$tag$...$tag$`)之外,
1822/// 且不属于 JSON 运算符(`?`、`?|`、`?&`、`@?`)的 `?`。
1823///
1824/// 若 SQL 在字符串/注释/dollar-quote 之外已包含 `$n` 形式原生占位符,则原样返回,
1825/// 避免混用导致编号错乱;字符串字面量内部的 `$1`(如 `'$1'`)不视为原生占位符,
1826/// 真实的 `?` 仍会被重写。
1827fn pg_rewrite_placeholders(sql: &str) -> std::borrow::Cow<'_, str> {
1828    if !sql.as_bytes().contains(&b'?') {
1829        return std::borrow::Cow::Borrowed(sql);
1830    }
1831    let mut out = String::with_capacity(sql.len() + 8);
1832    let mut chars = sql.chars().peekable();
1833    let mut in_single = false;
1834    let mut in_double = false;
1835    let mut in_line_comment = false;
1836    let mut in_block_comment = false;
1837    let mut dollar_tag: Option<String> = None;
1838    let mut prev = '\0';
1839    let mut n = 0usize;
1840    let mut saw_native = false;
1841    while let Some(c) = chars.next() {
1842        if in_line_comment {
1843            out.push(c);
1844            if c == '\n' {
1845                in_line_comment = false;
1846            }
1847            prev = c;
1848            continue;
1849        }
1850        if in_block_comment {
1851            out.push(c);
1852            if c == '*' && chars.peek() == Some(&'/') {
1853                out.push(chars.next().unwrap_or('/'));
1854                in_block_comment = false;
1855            }
1856            prev = c;
1857            continue;
1858        }
1859        if in_single {
1860            out.push(c);
1861            if c == '\'' {
1862                if chars.peek() == Some(&'\'') {
1863                    out.push(chars.next().unwrap_or('\''));
1864                } else {
1865                    in_single = false;
1866                }
1867            }
1868            prev = c;
1869            continue;
1870        }
1871        if in_double {
1872            out.push(c);
1873            if c == '"' {
1874                if chars.peek() == Some(&'"') {
1875                    out.push(chars.next().unwrap_or('"'));
1876                } else {
1877                    in_double = false;
1878                }
1879            }
1880            prev = c;
1881            continue;
1882        }
1883        if let Some(tag) = dollar_tag.as_deref() {
1884            // dollar-quote 内部:除结束定界符 `$tag$` 外原样输出
1885            if c == '$' {
1886                if let Some(extra) = pg_try_close_dollar_quote(&chars, tag) {
1887                    out.push(c);
1888                    for _ in 0..extra {
1889                        out.push(chars.next().unwrap_or('$'));
1890                    }
1891                    dollar_tag = None;
1892                } else {
1893                    out.push(c);
1894                }
1895            } else {
1896                out.push(c);
1897            }
1898            prev = c;
1899            continue;
1900        }
1901        match c {
1902            '\'' => {
1903                in_single = true;
1904                out.push(c);
1905            }
1906            '"' => {
1907                in_double = true;
1908                out.push(c);
1909            }
1910            '-' if chars.peek() == Some(&'-') => {
1911                out.push(c);
1912                out.push(chars.next().unwrap_or('-'));
1913                in_line_comment = true;
1914            }
1915            '/' if chars.peek() == Some(&'*') => {
1916                out.push(c);
1917                out.push(chars.next().unwrap_or('*'));
1918                in_block_comment = true;
1919            }
1920            '$' => {
1921                // 尝试解析 dollar-quote 开始定界符(`$$` 或 `$tag$`)
1922                if let Some(rest) = pg_peek_dollar_quote_rest(&chars) {
1923                    out.push('$');
1924                    for ch in rest.chars() {
1925                        out.push(ch);
1926                        chars.next();
1927                    }
1928                    dollar_tag = Some(rest[..rest.len() - 1].to_string());
1929                } else if chars.peek().is_some_and(|next| next.is_ascii_digit()) {
1930                    // 字符串外的 `$数字` 是 PG 原生占位符:整体不再重写任何 `?`
1931                    saw_native = true;
1932                    out.push(c);
1933                } else {
1934                    out.push(c);
1935                }
1936            }
1937            '?' if prev != '@'
1938                && !matches!(chars.peek(), Some('|') | Some('&'))
1939                && !pg_question_is_json_operator(&chars) =>
1940            {
1941                n += 1;
1942                out.push('$');
1943                out.push_str(&n.to_string());
1944            }
1945            _ => out.push(c),
1946        }
1947        prev = c;
1948    }
1949    if saw_native {
1950        return std::borrow::Cow::Borrowed(sql);
1951    }
1952    std::borrow::Cow::Owned(out)
1953}
1954
1955/// 判断当前 `?` 是否 PostgreSQL JSON 存在运算符(如 `doc ? 'k'`)而非占位符。
1956///
1957/// JSON 运算符右侧必然是字符串字面量/标识符,占位符右侧不会是引号。
1958fn pg_question_is_json_operator(chars: &std::iter::Peekable<std::str::Chars<'_>>) -> bool {
1959    let mut lookahead = chars.clone();
1960    while let Some(&c) = lookahead.peek() {
1961        match c {
1962            ' ' | '\t' => {
1963                lookahead.next();
1964            }
1965            '\'' | '"' => return true,
1966            _ => return false,
1967        }
1968    }
1969    false
1970}
1971
1972/// 解析 dollar-quote 开始定界符:`chars` 指向当前 `$` 之后的字符。
1973///
1974/// 返回 `Some(rest)` 表示命中,`rest` 为 `$` 之后的定界符片段:
1975/// - `$$` → `Some("$")`(空 tag)
1976/// - `$tag$` → `Some("tag$")`
1977///
1978/// 返回 `None` 表示不是 dollar-quote(如 `$1` 原生占位符、普通 `$`)。
1979fn pg_peek_dollar_quote_rest(chars: &std::iter::Peekable<std::str::Chars<'_>>) -> Option<String> {
1980    let mut lookahead = chars.clone();
1981    if lookahead.peek() == Some(&'$') {
1982        return Some("$".to_string());
1983    }
1984    let first = *lookahead.peek()?;
1985    if !(first.is_ascii_alphabetic() || first == '_') {
1986        return None;
1987    }
1988    let mut rest = String::new();
1989    while let Some(&c) = lookahead.peek() {
1990        match c {
1991            '$' => {
1992                rest.push('$');
1993                return Some(rest);
1994            }
1995            c if c.is_ascii_alphanumeric() || c == '_' => {
1996                rest.push(c);
1997                lookahead.next();
1998            }
1999            _ => return None,
2000        }
2001    }
2002    None
2003}
2004
2005/// 在 dollar-quote 内部遇到 `$` 时,检查是否为 `$tag$` 结束定界符。
2006///
2007/// `chars` 指向当前 `$` 之后的字符;返回需要额外消费的字符数(tag 字符加结束 `$`),
2008/// 不是结束定界符时返回 `None`。
2009fn pg_try_close_dollar_quote(
2010    chars: &std::iter::Peekable<std::str::Chars<'_>>,
2011    tag: &str,
2012) -> Option<usize> {
2013    let mut lookahead = chars.clone();
2014    for expected in tag.chars() {
2015        if lookahead.next() != Some(expected) {
2016            return None;
2017        }
2018    }
2019    if lookahead.next() == Some('$') {
2020        Some(tag.chars().count() + 1)
2021    } else {
2022        None
2023    }
2024}
2025
2026/// PostgreSQL 参数化查询语句类型别名
2027type PgQuery<'q> = sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>;
2028
2029/// 按 `Value` 变体绑定 PostgreSQL 参数
2030///
2031/// 字符串类变体优先转换为对应的原生类型(DECIMAL/DATE/TIMESTAMP/TIME/UUID/JSONB),
2032/// 转换失败时回退为 TEXT 绑定,避免参数类型不匹配导致整条语句失败。
2033fn bind_pg_value<'q>(q: PgQuery<'q>, v: &'q Value) -> PgQuery<'q> {
2034    match v {
2035        Value::Null => q.bind(None::<i64>),
2036        Value::Bool(b) => q.bind(*b),
2037        Value::I8(n) => q.bind(*n),
2038        Value::I16(n) => q.bind(*n),
2039        Value::I32(n) => q.bind(*n),
2040        Value::I64(n) => q.bind(*n),
2041        // PostgreSQL 无 u8/u16/u32/u64:按最小可容纳有符号类型转换
2042        Value::U8(n) => q.bind(*n as i16),
2043        Value::U16(n) => q.bind(*n as i32),
2044        Value::U32(n) => q.bind(*n as i64),
2045        Value::U64(n) => q.bind(*n as i64),
2046        Value::F32(f) => q.bind(*f),
2047        Value::F64(f) => q.bind(*f),
2048        Value::String(s) => q.bind(s.as_str()),
2049        // R2:以任意精度 BigDecimal 绑定,避免 38 位 DECIMAL 被 rust_decimal 截断
2050        Value::Decimal(s) => match s.parse::<bigdecimal::BigDecimal>() {
2051            Ok(d) => q.bind(d),
2052            Err(_) => q.bind(s.as_str()),
2053        },
2054        Value::Uuid(s) => match sqlx::types::Uuid::parse_str(s) {
2055            Ok(u) => q.bind(u),
2056            Err(_) => q.bind(s.as_str()),
2057        },
2058        Value::Date(s) => match parse_pg_date(s) {
2059            Some(d) => q.bind(d),
2060            None => q.bind(s.as_str()),
2061        },
2062        Value::DateTime(s) => match parse_pg_datetime(s) {
2063            // 带时区偏移的输入必须绑定为 TIMESTAMPTZ,否则 PG 会按会话时区解释
2064            Some(PgDateTime::Zoned(d)) => q.bind(d),
2065            Some(PgDateTime::Naive(d)) => q.bind(d),
2066            None => q.bind(s.as_str()),
2067        },
2068        Value::Time(s) => match parse_pg_time(s) {
2069            Some(t) => q.bind(t),
2070            None => q.bind(s.as_str()),
2071        },
2072        Value::Json(s) => match serde_json::from_str::<serde_json::Value>(s) {
2073            Ok(j) => q.bind(j),
2074            Err(_) => q.bind(s.as_str()),
2075        },
2076        Value::Bytes(b) => q.bind(b.as_slice()),
2077        other => q.bind(other.to_string()),
2078    }
2079}
2080
2081/// 解析 DATE:`YYYY-MM-DD`
2082fn parse_pg_date(s: &str) -> Option<sqlx::types::chrono::NaiveDate> {
2083    use sqlx::types::chrono::NaiveDate;
2084    let head = s.get(..10)?;
2085    NaiveDate::parse_from_str(head, "%Y-%m-%d").ok()
2086}
2087
2088/// TIMESTAMP/TIMESTAMPTZ 的绑定形态
2089enum PgDateTime {
2090    /// 无时区:绑定 TIMESTAMP
2091    Naive(sqlx::types::chrono::NaiveDateTime),
2092    /// 带时区偏移:绑定 TIMESTAMPTZ(UTC 时刻)
2093    Zoned(sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>),
2094}
2095
2096/// 解析 TIMESTAMP/TIMESTAMPTZ:
2097/// - 带 `Z` 或 ±HH:MM 偏移 → [`PgDateTime::Zoned`]
2098/// - 其余 → [`PgDateTime::Naive`]
2099fn parse_pg_datetime(s: &str) -> Option<PgDateTime> {
2100    use sqlx::types::chrono::{DateTime, NaiveDateTime, Utc};
2101    let has_offset = s.ends_with('Z')
2102        || s.ends_with('z')
2103        || s.get(10..).is_some_and(|rest| {
2104            rest.char_indices().any(|(i, c)| {
2105                (c == '+' || c == '-')
2106                    && rest[..i].chars().any(|p| p == ':')
2107                    // 排除日期部分本身的 '-'(位置 4/7 已在 get(10..) 之外)
2108                    && i > 0
2109            })
2110        });
2111    if has_offset {
2112        if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
2113            return Some(PgDateTime::Zoned(dt.with_timezone(&Utc)));
2114        }
2115        for fmt in [
2116            "%Y-%m-%d %H:%M:%S%.f%:z",
2117            "%Y-%m-%d %H:%M:%S%:z",
2118            "%Y-%m-%dT%H:%M:%S%.f%:z",
2119            "%Y-%m-%dT%H:%M:%S%:z",
2120            "%Y-%m-%d %H:%M:%S%.f%z",
2121        ] {
2122            if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
2123                return Some(PgDateTime::Zoned(dt.with_timezone(&Utc)));
2124            }
2125        }
2126    }
2127    for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] {
2128        if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) {
2129            return Some(PgDateTime::Naive(dt));
2130        }
2131        let with_frac = format!("{fmt}%.f");
2132        if let Ok(dt) = NaiveDateTime::parse_from_str(s, &with_frac) {
2133            return Some(PgDateTime::Naive(dt));
2134        }
2135    }
2136    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
2137        return Some(PgDateTime::Zoned(dt.with_timezone(&Utc)));
2138    }
2139    None
2140}
2141
2142/// 解析 TIME:支持 `HH:MM:SS` 与 `HH:MM:SS.f`
2143fn parse_pg_time(s: &str) -> Option<sqlx::types::chrono::NaiveTime> {
2144    use sqlx::types::chrono::NaiveTime;
2145    NaiveTime::parse_from_str(s, "%H:%M:%S")
2146        .or_else(|_| NaiveTime::parse_from_str(s, "%H:%M:%S%.f"))
2147        .ok()
2148}
2149
2150pub struct PgPoolHandle {
2151    pool: sqlx::PgPool,
2152}
2153
2154impl PgPoolHandle {
2155    pub async fn connect(url: &str) -> Result<Self, DbError> {
2156        // 默认容量 10(向后兼容);上层应用 `connect_with_max` 对齐外层 PoolConfig::max_size
2157        Self::connect_with_max(url, 10).await
2158    }
2159
2160    /// 容量显式版 connect(2026-10-28 P3-4,PERFV2-03):与外层 `PoolConfig::max_size` 对齐,
2161    /// 避免"外层按 max_size 记账、内层硬编码 10"的双层上限不一致(详见 SqlitePoolHandle::connect_with_max)。
2162    pub async fn connect_with_max(url: &str, max_connections: u32) -> Result<Self, DbError> {
2163        let pool = sqlx::pool::PoolOptions::<sqlx::Postgres>::new()
2164            .max_connections(max_connections)
2165            .acquire_timeout(std::time::Duration::from_secs(30))
2166            .idle_timeout(Some(std::time::Duration::from_secs(600)))
2167            .max_lifetime(Some(std::time::Duration::from_secs(1800)))
2168            .connect(url)
2169            .await
2170            .map_err(map_sqlx_error)?;
2171        Ok(Self { pool })
2172    }
2173
2174    pub fn from_pool(pool: sqlx::PgPool) -> Self {
2175        Self { pool }
2176    }
2177
2178    pub fn pool(&self) -> &sqlx::PgPool {
2179        &self.pool
2180    }
2181}
2182
2183pub struct SqlxPgConnectionFactory {
2184    pool: Arc<PgPoolHandle>,
2185}
2186
2187impl SqlxPgConnectionFactory {
2188    pub fn new(pool: Arc<PgPoolHandle>) -> Self {
2189        Self { pool }
2190    }
2191}
2192
2193#[async_trait]
2194impl ConnectionFactory for SqlxPgConnectionFactory {
2195    async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
2196        let conn = self.pool.pool.acquire().await.map_err(map_sqlx_error)?;
2197        // 2026-10-28 P1-3:MetricsConn 包装统一上报查询指标(ops/latency/errors)
2198        Ok(Box::new(crate::metrics_conn::MetricsConn::new(Box::new(
2199            SqlxPgConnection {
2200                conn: Some(conn),
2201                connected: true,
2202                in_transaction: false,
2203            },
2204        ))))
2205    }
2206}
2207
2208pub struct SqlxPgConnection {
2209    conn: Option<sqlx::pool::PoolConnection<sqlx::Postgres>>,
2210    connected: bool,
2211    in_transaction: bool,
2212}
2213
2214impl Connection for SqlxPgConnection {
2215    fn db_type(&self) -> Option<DbType> {
2216        Some(DbType::PostgreSQL)
2217    }
2218
2219    /// PostgreSQL 的 `DISCARD ALL` 复位全部会话状态:`SET` 参数、
2220    /// `search_path`、临时表、预备语句、游标、监听频道、序列缓存。
2221    /// 必须在事务外执行(连接池已在复位前回滚未提交事务)。
2222    fn session_reset_sql(&self) -> Option<&'static str> {
2223        Some("DISCARD ALL")
2224    }
2225
2226    fn execute<'a>(
2227        &'a mut self,
2228        sql: &'a str,
2229    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
2230        Box::pin(async move {
2231            let mut pool_conn = self
2232                .conn
2233                .take()
2234                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2235            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
2236            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
2237            let result = if needs_raw_sql(sql) {
2238                (&mut *pool_conn)
2239                    .execute(sqlx::raw_sql(sqlx::AssertSqlSafe(sql)))
2240                    .await
2241            } else {
2242                (&mut *pool_conn).execute(sqlx::AssertSqlSafe(sql)).await
2243            };
2244            self.conn = Some(pool_conn);
2245
2246            match result {
2247                Ok(r) => Ok(r.rows_affected()),
2248                Err(e) => {
2249                    let db_err = map_sqlx_error(e);
2250                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
2251                        self.connected = false;
2252                    }
2253                    Err(db_err)
2254                }
2255            }
2256        })
2257    }
2258
2259    fn query<'a>(
2260        &'a mut self,
2261        sql: &'a str,
2262    ) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, DbError>> + Send + 'a>>
2263    {
2264        Box::pin(async move {
2265            let mut pool_conn = self
2266                .conn
2267                .take()
2268                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2269            // sqlx 0.9: PoolConnection 不再实现 Executor,需通过 DerefMut 解引用到内部连接
2270            // sqlx 0.9: SqlSafeStr 只对 &'static str 直接实现,非 'static 的 &str 需用 AssertSqlSafe 包装
2271            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
2272            self.conn = Some(pool_conn);
2273
2274            let rows = rows_result.map_err(map_sqlx_error)?;
2275            if rows.is_empty() {
2276                return Ok(Vec::new());
2277            }
2278            // 预解析列类型(只在第一行解析,后续行复用,避免每行字符串 match)
2279            // PostgreSQL 专用解析:覆盖 PG 内部类型名(INT4/INT8/FLOAT8/BPCHAR/JSONB 等)
2280            let col_types: Vec<ColType> = rows[0]
2281                .columns()
2282                .iter()
2283                .map(|col| ColType::parse_postgres(col.type_info().name()))
2284                .collect();
2285            let col_names: Vec<String> = rows[0]
2286                .columns()
2287                .iter()
2288                .map(|col| col.name().to_string())
2289                .collect();
2290            let mut result = Vec::with_capacity(rows.len());
2291            for row in &rows {
2292                let mut record = HashMap::with_capacity(col_types.len());
2293                for (i, name) in col_names.iter().enumerate() {
2294                    let value = row_to_value_with_coltype_pg(row, i, col_types[i]);
2295                    record.insert(name.clone(), value);
2296                }
2297                result.push(record);
2298            }
2299            Ok(result)
2300        })
2301    }
2302
2303    fn begin_transaction<'a>(
2304        &'a mut self,
2305    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
2306        Box::pin(async move {
2307            if self.in_transaction {
2308                return Err(DbError::Internal("transaction already started".to_string()));
2309            }
2310            self.execute("BEGIN").await?;
2311            self.in_transaction = true;
2312            Ok(())
2313        })
2314    }
2315
2316    fn commit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
2317        Box::pin(async move {
2318            if self.in_transaction {
2319                self.execute("COMMIT").await?;
2320                self.in_transaction = false;
2321            }
2322            Ok(())
2323        })
2324    }
2325
2326    fn rollback<'a>(
2327        &'a mut self,
2328    ) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
2329        Box::pin(async move {
2330            if self.in_transaction {
2331                // 回滚失败时保留事务标志:非连接类错误下服务端事务可能仍开启,
2332                // 若此刻清标志,连接带着开放事务回池将绕过归还路径的自动回滚
2333                // (事务泄漏)。失败后由归还路径重试回滚或直接关闭连接。
2334                // 语义与 Oracle/MSSQL 适配器一致。
2335                let result = self.execute("ROLLBACK").await;
2336                if result.is_ok() {
2337                    self.in_transaction = false;
2338                }
2339                result.map(|_| ())
2340            } else {
2341                Ok(())
2342            }
2343        })
2344    }
2345
2346    fn is_connected(&self) -> bool {
2347        // 纳入 conn.is_some():query_stream 提前弃读时 PoolConnection 已从包装器
2348        // take() 出去(self.conn = None),毒化状态不得被判定为可用连接
2349        // (否则回池后下次 acquire 借出即报 "connection already closed")
2350        self.connected && self.conn.is_some()
2351    }
2352
2353    fn in_transaction(&self) -> bool {
2354        self.in_transaction
2355    }
2356
2357    fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
2358        Box::pin(async move {
2359            match self.execute("SELECT 1").await {
2360                Ok(_) => true,
2361                Err(_) => {
2362                    self.connected = false;
2363                    false
2364                }
2365            }
2366        })
2367    }
2368
2369    fn close<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
2370        Box::pin(async move {
2371            if let Some(conn) = self.conn.take() {
2372                drop(conn);
2373            }
2374            self.connected = false;
2375            self.in_transaction = false;
2376            Ok(())
2377        })
2378    }
2379
2380    /// PostgreSQL 参数绑定执行(INSERT/UPDATE/DELETE)
2381    ///
2382    /// 使用 sqlx prepared statement 绑定参数。PostgreSQL 协议使用 `$1, $2, ...` 占位符,
2383    /// 调用方传入的 `?` 占位符会由 `pg_rewrite_placeholders` 自动改写;
2384    /// 已使用 `$n` 的 SQL 不会被二次改写。
2385    ///
2386    /// # 类型映射说明
2387    ///
2388    /// PostgreSQL 不支持无符号整数类型(u8/u16/u32/u64),因此无符号 Value 绑定
2389    /// 时按"最小可容纳的有符号类型"转换:
2390    /// - `U8`  → `i16`(PostgreSQL `SMALLINT`)
2391    /// - `U16` → `i32`(PostgreSQL `INTEGER`)
2392    /// - `U32` → `i64`(PostgreSQL `BIGINT`)
2393    /// - `U64` → `i64`(可能截断,仅适用于 < `i64::MAX` 的值)
2394    ///
2395    /// `Decimal`/`Uuid`/`Date`/`DateTime`/`Time`/`Json` 会按各自原生类型绑定,
2396    /// 解析失败时回退为 TEXT 绑定;未知类型回退为 `to_string()` 后以 TEXT 绑定。
2397    fn execute_with_params<'a>(
2398        &'a mut self,
2399        sql: &'a str,
2400        params: &'a [Value],
2401    ) -> Pin<Box<dyn Future<Output = Result<u64, DbError>> + Send + 'a>> {
2402        Box::pin(async move {
2403            if needs_raw_sql(sql) || params.is_empty() {
2404                return self.execute(sql).await;
2405            }
2406            let mut pool_conn = self
2407                .conn
2408                .take()
2409                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2410            let rewritten = pg_rewrite_placeholders(sql);
2411            let mut q = sqlx::query(sqlx::AssertSqlSafe(rewritten.as_ref()));
2412            for v in params {
2413                q = bind_pg_value(q, v);
2414            }
2415            let result = q.execute(&mut *pool_conn).await;
2416            self.conn = Some(pool_conn);
2417            match result {
2418                Ok(r) => Ok(r.rows_affected()),
2419                Err(e) => {
2420                    let db_err = map_sqlx_error(e);
2421                    if matches!(db_err, DbError::ConnectionError(_) | DbError::IoError(_)) {
2422                        self.connected = false;
2423                    }
2424                    Err(db_err)
2425                }
2426            }
2427        })
2428    }
2429
2430    /// PostgreSQL 参数绑定查询(SELECT,HashMap 映射)
2431    ///
2432    /// 类型映射规则与 [`Self::execute_with_params`] 相同。
2433    fn query_with_params<'a>(
2434        &'a mut self,
2435        sql: &'a str,
2436        params: &'a [Value],
2437    ) -> Pin<Box<dyn Future<Output = Result<QueryRows, DbError>> + Send + 'a>> {
2438        Box::pin(async move {
2439            if params.is_empty() {
2440                return self.query(sql).await;
2441            }
2442            let mut pool_conn = self
2443                .conn
2444                .take()
2445                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2446            let rewritten = pg_rewrite_placeholders(sql);
2447            let mut q = sqlx::query(sqlx::AssertSqlSafe(rewritten.as_ref()));
2448            for v in params {
2449                q = bind_pg_value(q, v);
2450            }
2451            let rows_result = q.fetch_all(&mut *pool_conn).await;
2452            self.conn = Some(pool_conn);
2453            let rows = rows_result.map_err(map_sqlx_error)?;
2454            if rows.is_empty() {
2455                return Ok(Vec::new());
2456            }
2457            // 预解析列类型(只在第一行解析,后续行复用,避免每行字符串 match)
2458            let col_types: Vec<ColType> = rows[0]
2459                .columns()
2460                .iter()
2461                .map(|col| ColType::parse_postgres(col.type_info().name()))
2462                .collect();
2463            let col_names: Vec<String> = rows[0]
2464                .columns()
2465                .iter()
2466                .map(|col| col.name().to_string())
2467                .collect();
2468            let mut result = Vec::with_capacity(rows.len());
2469            for row in &rows {
2470                let mut record = HashMap::with_capacity(col_types.len());
2471                for (i, name) in col_names.iter().enumerate() {
2472                    let value = row_to_value_with_coltype_pg(row, i, col_types[i]);
2473                    record.insert(name.clone(), value);
2474                }
2475                result.push(record);
2476            }
2477            Ok(result)
2478        })
2479    }
2480
2481    /// PostgreSQL 位置式查询(SELECT,无参数)
2482    ///
2483    /// 绕过 HashMap 行映射,返回 `(列名, 按列序号的值矩阵)`。适用于 SELECT ALL 大结果集。
2484    fn query_values<'a>(
2485        &'a mut self,
2486        sql: &'a str,
2487    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
2488        Box::pin(async move {
2489            let mut pool_conn = self
2490                .conn
2491                .take()
2492                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2493            let rows_result = (&mut *pool_conn).fetch_all(sqlx::AssertSqlSafe(sql)).await;
2494            self.conn = Some(pool_conn);
2495            let rows = rows_result.map_err(map_sqlx_error)?;
2496            if rows.is_empty() {
2497                return Ok((Vec::new(), Vec::new()));
2498            }
2499            let cols = rows[0].columns();
2500            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
2501            let mut col_types: Vec<ColType> = Vec::with_capacity(cols.len());
2502            for col in cols {
2503                col_names.push(col.name().to_string());
2504                col_types.push(ColType::parse_postgres(col.type_info().name()));
2505            }
2506            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
2507            for row in &rows {
2508                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
2509                for (idx, _) in col_names.iter().enumerate() {
2510                    row_values.push(row_to_value_with_coltype_pg(row, idx, col_types[idx]));
2511                }
2512                result_rows.push(row_values);
2513            }
2514            Ok((col_names, result_rows))
2515        })
2516    }
2517
2518    /// PostgreSQL 参数绑定位置式查询(SELECT)
2519    ///
2520    /// 类型映射规则与 [`Self::execute_with_params`] 相同。
2521    fn query_values_with_params<'a>(
2522        &'a mut self,
2523        sql: &'a str,
2524        params: &'a [Value],
2525    ) -> Pin<Box<dyn Future<Output = Result<QueryValues, DbError>> + Send + 'a>> {
2526        Box::pin(async move {
2527            if params.is_empty() {
2528                return self.query_values(sql).await;
2529            }
2530            let mut pool_conn = self
2531                .conn
2532                .take()
2533                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2534            let rewritten = pg_rewrite_placeholders(sql);
2535            let mut q = sqlx::query(sqlx::AssertSqlSafe(rewritten.as_ref()));
2536            for v in params {
2537                q = bind_pg_value(q, v);
2538            }
2539            let rows_result = q.fetch_all(&mut *pool_conn).await;
2540            self.conn = Some(pool_conn);
2541            let rows = rows_result.map_err(map_sqlx_error)?;
2542            if rows.is_empty() {
2543                return Ok((Vec::new(), Vec::new()));
2544            }
2545            let cols = rows[0].columns();
2546            let mut col_names: Vec<String> = Vec::with_capacity(cols.len());
2547            for col in cols {
2548                col_names.push(col.name().to_string());
2549            }
2550            let mut result_rows: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
2551            for row in rows {
2552                let mut row_values: Vec<Value> = Vec::with_capacity(col_names.len());
2553                for (idx, _) in col_names.iter().enumerate() {
2554                    let ordinal = row.columns()[idx].ordinal();
2555                    row_values.push(row_to_value_pg(&row, ordinal)?);
2556                }
2557                result_rows.push(row_values);
2558            }
2559            Ok((col_names, result_rows))
2560        })
2561    }
2562
2563    /// PostgreSQL 流式查询:逐行返回结果,避免大结果集 `fetch_all` 内存峰值
2564    ///
2565    /// 使用 `sqlx::query::fetch` 获取行流,逐行映射为 `HashMap<String, Value>`。
2566    /// 流正常消费完毕后连接归还 `self.conn`;若提前 drop 流,连接通过
2567    /// `PoolConnection::drop` 归还到 sqlx 池,但 `self.conn` 变为 `None`。
2568    ///
2569    /// 注意:PostgreSQL 的 `fetch` 返回 `Pin<Box<dyn Stream<...>>>`(boxed trait object),
2570    /// 其析构函数可能持有 `pool_conn` 的借用,导致无法直接 `self.conn = Some(pool_conn)`。
2571    /// 通过显式 `drop(row_stream)` 提前结束借用,再归还连接。
2572    fn query_stream<'a>(
2573        &'a mut self,
2574        sql: &'a str,
2575    ) -> Pin<Box<dyn futures::Stream<Item = Result<HashMap<String, Value>, DbError>> + Send + 'a>>
2576    {
2577        Box::pin(async_stream::try_stream! {
2578            let mut pool_conn = self
2579                .conn
2580                .take()
2581                .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2582            let mut row_stream = sqlx::query(sqlx::AssertSqlSafe(sql)).fetch(&mut *pool_conn);
2583            // 流式查询无法预解析第一行列类型(流首行未知),使用每行 columns() 长度预分配 HashMap
2584            // 仍然使用 row_to_value_pg(按列类型字符串 match)以保证正确性
2585            while let Some(row_result) = row_stream.next().await {
2586                let row = row_result.map_err(map_sqlx_error)?;
2587                let cols = row.columns();
2588                let mut record = HashMap::with_capacity(cols.len());
2589                for (i, col) in cols.iter().enumerate() {
2590                    let name = col.name().to_string();
2591                    // 流式场景下每行都解析 ColType 反而增加开销,直接使用 row_to_value_pg
2592                    let value = row_to_value_pg(&row, i)?;
2593                    record.insert(name, value);
2594                }
2595                yield record;
2596            }
2597            // 显式 drop row_stream 以释放对 pool_conn 的借用
2598            // PostgreSQL 的 fetch 流是 boxed trait object,析构函数可能持有借用
2599            drop(row_stream);
2600            self.conn = Some(pool_conn);
2601        })
2602    }
2603}
2604
2605impl Drop for SqlxPgConnection {
2606    fn drop(&mut self) {
2607        if let Some(conn) = self.conn.take() {
2608            drop(conn);
2609        }
2610    }
2611}
2612
2613// ===================== PostgreSQL 扩展功能 =====================
2614
2615/// PostgreSQL 扩展功能 trait
2616///
2617/// 提供 PostgreSQL 特有的 LISTEN/NOTIFY 通道通信与 COPY FROM STDIN 批量导入功能。
2618/// 这些功能在其他数据库(MySQL/SQLite)中没有对应实现,因此单独定义为扩展 trait,
2619/// 仅由 [`SqlxPgConnection`] 实现。
2620///
2621/// # LISTEN/NOTIFY
2622///
2623/// PostgreSQL 的轻量级进程间通信机制:
2624/// - `LISTEN channel`:订阅通道
2625/// - `NOTIFY channel, payload`:向通道发送通知
2626///
2627/// 接收通知需配合 `PgListener` 或轮询 `pg_notification` 系列函数。
2628///
2629/// # COPY FROM STDIN
2630///
2631/// PostgreSQL 的高性能批量导入协议,比逐行 INSERT 快 10~100 倍。
2632/// 数据通过专用协议流式传输,绕过 SQL 解析器。
2633#[async_trait]
2634pub trait PgExtensions: Send + Sync {
2635    /// LISTEN 通道:订阅指定通道的通知
2636    ///
2637    /// # 参数
2638    ///
2639    /// - `channel`: 通道名(仅允许字母、数字、下划线,防止 SQL 注入)
2640    ///
2641    /// # 错误
2642    ///
2643    /// - 通道名包含非法字符时返回 `DbError::Internal`
2644    /// - 连接已关闭时返回 `DbError::Internal`
2645    async fn listen(&mut self, channel: &str) -> Result<(), DbError>;
2646
2647    /// NOTIFY 通道:向指定通道发送通知
2648    ///
2649    /// # 参数
2650    ///
2651    /// - `channel`: 通道名(仅允许字母、数字、下划线)
2652    /// - `payload`: 通知载荷字符串(单引号自动转义)
2653    ///
2654    /// # 错误
2655    ///
2656    /// - 通道名包含非法字符时返回 `DbError::Internal`
2657    async fn notify(&mut self, channel: &str, payload: &str) -> Result<(), DbError>;
2658
2659    /// COPY FROM STDIN:批量导入数据
2660    ///
2661    /// # 参数
2662    ///
2663    /// - `sql`: COPY 语句,如 `COPY mytable (col1, col2) FROM STDIN WITH (FORMAT csv, HEADER true)`
2664    /// - `data`: 完整的导入数据字节流
2665    ///
2666    /// # 返回
2667    ///
2668    /// 返回受影响的行数。
2669    ///
2670    /// # 性能
2671    ///
2672    /// 比逐行 INSERT 快 10~100 倍,适用于大批量数据导入(10 万行以上)。
2673    async fn copy_from_stdin(&mut self, sql: &str, data: &[u8]) -> Result<u64, DbError>;
2674}
2675
2676/// 校验 PostgreSQL 通道名合法性(仅允许字母、数字、下划线)
2677///
2678/// LISTEN/NOTIFY 的通道名不能通过参数化绑定,必须拼接 SQL,
2679/// 因此需严格校验防止 SQL 注入。
2680fn validate_pg_channel_name(channel: &str) -> Result<(), DbError> {
2681    if channel.is_empty() {
2682        return Err(DbError::Internal(
2683            "PG channel name must not be empty".to_string(),
2684        ));
2685    }
2686    if !channel
2687        .chars()
2688        .all(|c| c.is_ascii_alphanumeric() || c == '_')
2689    {
2690        return Err(DbError::Internal(format!(
2691            "invalid PG channel name: {} (only alphanumeric and underscore allowed)",
2692            channel
2693        )));
2694    }
2695    Ok(())
2696}
2697
2698#[async_trait]
2699impl PgExtensions for SqlxPgConnection {
2700    async fn listen(&mut self, channel: &str) -> Result<(), DbError> {
2701        validate_pg_channel_name(channel)?;
2702        // LISTEN 是简单 SQL 命令,channel 已校验,可安全拼接
2703        self.execute(&format!("LISTEN {}", channel)).await?;
2704        Ok(())
2705    }
2706
2707    async fn notify(&mut self, channel: &str, payload: &str) -> Result<(), DbError> {
2708        validate_pg_channel_name(channel)?;
2709        // payload 通过 PostgreSQL 字符串字面量转义(单引号加倍)防止注入
2710        let escaped_payload = payload.replace('\'', "''");
2711        self.execute(&format!("NOTIFY {}, '{}'", channel, escaped_payload))
2712            .await?;
2713        Ok(())
2714    }
2715
2716    async fn copy_from_stdin(&mut self, sql: &str, data: &[u8]) -> Result<u64, DbError> {
2717        let mut pool_conn = self
2718            .conn
2719            .take()
2720            .ok_or_else(|| DbError::Internal("connection already closed".to_string()))?;
2721        // sqlx 0.9: 通过 DerefMut 访问 PgConnection 的 copy_in_raw 方法
2722        // 启动 COPY 协议,返回 PgCopyIn 流式写入句柄
2723        // 注意:直接使用 *pool_conn 解引用,避免 clippy::needless_borrow 警告
2724        let mut copy = (*pool_conn)
2725            .copy_in_raw(sql)
2726            .await
2727            .map_err(map_sqlx_error)?;
2728        // 一次性发送全部数据(非流式,适用于数据可完整载入内存的场景)
2729        copy.send(data).await.map_err(map_sqlx_error)?;
2730        // 完成 COPY 操作,sqlx 0.9 的 PgCopyIn::finish() 直接返回 u64(受影响行数)
2731        let result = copy.finish().await.map_err(map_sqlx_error)?;
2732        self.conn = Some(pool_conn);
2733        Ok(result)
2734    }
2735}
2736
2737/// PostgreSQL 批量导入(多行 INSERT,作为 COPY 协议的简化替代)
2738///
2739/// 构建多行 INSERT 语句:`INSERT INTO t (c1, c2) VALUES ($1, $2), ($3, $4), ...`
2740/// 使用参数绑定避免 SQL 注入。PostgreSQL 占位符 `$N` 跨行递增。
2741///
2742/// 当数据量极大(>10 万行)时,建议分批调用或使用
2743/// [`PgExtensions::copy_from_stdin`] 走 COPY 协议以获得更高吞吐。
2744pub async fn pg_bulk_insert(
2745    conn: &mut SqlxPgConnection,
2746    table: &str,
2747    columns: &[&str],
2748    rows: &[Vec<Value>],
2749) -> Result<u64, DbError> {
2750    if rows.is_empty() {
2751        return Ok(0);
2752    }
2753    let col_list = columns.join(", ");
2754    let cols_per_row = columns.len();
2755    // PostgreSQL 占位符 $1, $2, ... 跨行递增
2756    let placeholders: Vec<String> = rows
2757        .iter()
2758        .enumerate()
2759        .map(|(row_idx, _)| {
2760            let base = row_idx * cols_per_row;
2761            let ph: Vec<String> = (0..cols_per_row)
2762                .map(|i| format!("${}", base + i + 1))
2763                .collect();
2764            format!("({})", ph.join(", "))
2765        })
2766        .collect();
2767    let sql = format!(
2768        "INSERT INTO {} ({}) VALUES {}",
2769        table,
2770        col_list,
2771        placeholders.join(", ")
2772    );
2773    // 展平所有行的值为单一参数数组
2774    let mut params: Vec<Value> = Vec::with_capacity(rows.len() * cols_per_row);
2775    for row in rows {
2776        for v in row {
2777            params.push(v.clone());
2778        }
2779    }
2780    conn.execute_with_params(&sql, &params).await
2781}
2782#[cfg(test)]
2783mod tests {
2784    use super::*;
2785
2786    #[test]
2787    fn test_pg_rewrite_placeholders_basic() {
2788        assert_eq!(
2789            pg_rewrite_placeholders("SELECT * FROM t WHERE a = ? AND b = ?"),
2790            "SELECT * FROM t WHERE a = $1 AND b = $2"
2791        );
2792    }
2793
2794    #[test]
2795    fn test_pg_rewrite_placeholders_skips_native() {
2796        assert_eq!(
2797            pg_rewrite_placeholders("SELECT * FROM t WHERE a = $1"),
2798            "SELECT * FROM t WHERE a = $1"
2799        );
2800    }
2801
2802    #[test]
2803    fn test_pg_rewrite_placeholders_skips_literals_and_comments() {
2804        assert_eq!(
2805            pg_rewrite_placeholders("SELECT '?' AS q -- ?\nFROM t WHERE id = ?"),
2806            "SELECT '?' AS q -- ?\nFROM t WHERE id = $1"
2807        );
2808        assert_eq!(
2809            pg_rewrite_placeholders("SELECT /* ? */ \"a?\" FROM t WHERE id = ?"),
2810            "SELECT /* ? */ \"a?\" FROM t WHERE id = $1"
2811        );
2812    }
2813
2814    #[test]
2815    fn test_pg_rewrite_placeholders_keeps_json_operators() {
2816        assert_eq!(
2817            pg_rewrite_placeholders("SELECT * FROM t WHERE doc ? 'k' AND b = ?"),
2818            "SELECT * FROM t WHERE doc ? 'k' AND b = $1"
2819        );
2820        assert_eq!(
2821            pg_rewrite_placeholders("SELECT * FROM t WHERE doc ?| array['a'] AND b = ?"),
2822            "SELECT * FROM t WHERE doc ?| array['a'] AND b = $1"
2823        );
2824        assert_eq!(
2825            pg_rewrite_placeholders("SELECT * FROM t WHERE doc ?& array['a', 'b'] AND b = ?"),
2826            "SELECT * FROM t WHERE doc ?& array['a', 'b'] AND b = $1"
2827        );
2828        assert_eq!(
2829            pg_rewrite_placeholders("SELECT * FROM t WHERE doc @? '$.a' AND b = ?"),
2830            "SELECT * FROM t WHERE doc @? '$.a' AND b = $1"
2831        );
2832    }
2833
2834    #[test]
2835    fn test_pg_rewrite_placeholders_dollar_in_literal_still_rewrites() {
2836        // 字符串字面量里的 `$1` 不是原生占位符,真实的 `?` 必须被重写
2837        assert_eq!(
2838            pg_rewrite_placeholders("SELECT '$1' AS lit, ? AS val"),
2839            "SELECT '$1' AS lit, $1 AS val"
2840        );
2841        assert_eq!(
2842            pg_rewrite_placeholders("SELECT 'price: $2' AS note FROM t WHERE id = ? AND code = ?"),
2843            "SELECT 'price: $2' AS note FROM t WHERE id = $1 AND code = $2"
2844        );
2845    }
2846
2847    #[test]
2848    fn test_pg_rewrite_placeholders_skips_dollar_quoted_strings() {
2849        // dollar-quote 内的 `?` 是字面量,不得改写
2850        assert_eq!(
2851            pg_rewrite_placeholders("SELECT $$?$$ AS lit, ? AS val"),
2852            "SELECT $$?$$ AS lit, $1 AS val"
2853        );
2854        assert_eq!(
2855            pg_rewrite_placeholders("SELECT $tag$a ? b$tag$ AS lit, ? AS val"),
2856            "SELECT $tag$a ? b$tag$ AS lit, $1 AS val"
2857        );
2858        // 函数体(plpgsql)使用 dollar-quote,体内的 `?` 保持原样
2859        let func = "CREATE FUNCTION f() RETURNS int AS $$ BEGIN RETURN (SELECT count(*) FROM t WHERE x = ?); END; $$ LANGUAGE plpgsql";
2860        assert_eq!(pg_rewrite_placeholders(func), func);
2861    }
2862
2863    #[test]
2864    fn test_pg_rewrite_placeholders_native_skips_even_with_question() {
2865        // 字符串外存在真实 `$n` 时整体不重写,`?` 保持原样(避免编号错乱)
2866        assert_eq!(
2867            pg_rewrite_placeholders("SELECT * FROM t WHERE a = $1 AND doc ? 'k'"),
2868            "SELECT * FROM t WHERE a = $1 AND doc ? 'k'"
2869        );
2870    }
2871
2872    #[test]
2873    fn test_parse_pg_date_and_datetime() {
2874        assert!(parse_pg_date("2026-10-28").is_some());
2875        assert!(parse_pg_date("2026-10-28 12:00:00").is_some());
2876        assert!(parse_pg_date("not-a-date").is_none());
2877        assert!(parse_pg_datetime("2026-10-28 12:00:00").is_some());
2878        assert!(parse_pg_datetime("2026-10-28T12:00:00").is_some());
2879        assert!(parse_pg_datetime("2026-10-28T12:00:00Z").is_some());
2880        assert!(parse_pg_datetime("bad").is_none());
2881    }
2882
2883    #[test]
2884    fn test_needs_raw_sql_begin() {
2885        assert!(needs_raw_sql("BEGIN"));
2886        assert!(needs_raw_sql("begin"));
2887        assert!(needs_raw_sql("  BEGIN"));
2888        assert!(needs_raw_sql("\tbegin"));
2889    }
2890
2891    #[test]
2892    fn test_needs_raw_sql_commit() {
2893        assert!(needs_raw_sql("COMMIT"));
2894        assert!(needs_raw_sql("commit"));
2895        assert!(needs_raw_sql(" COMMIT WORK"));
2896    }
2897
2898    #[test]
2899    fn test_needs_raw_sql_rollback() {
2900        assert!(needs_raw_sql("ROLLBACK"));
2901        assert!(needs_raw_sql("rollback"));
2902        assert!(needs_raw_sql(" ROLLBACK TO SAVEPOINT sp1"));
2903    }
2904
2905    #[test]
2906    fn test_needs_raw_sql_savepoint() {
2907        assert!(needs_raw_sql("SAVEPOINT sp1"));
2908        assert!(needs_raw_sql("savepoint s1"));
2909    }
2910
2911    #[test]
2912    fn test_needs_raw_sql_release() {
2913        assert!(needs_raw_sql("RELEASE sp1"));
2914        assert!(needs_raw_sql("release savepoint s1"));
2915    }
2916
2917    #[test]
2918    fn test_needs_raw_sql_set() {
2919        assert!(needs_raw_sql("SET autocommit = 0"));
2920        assert!(needs_raw_sql("set names utf8"));
2921    }
2922
2923    #[test]
2924    fn test_needs_raw_sql_use() {
2925        assert!(needs_raw_sql("USE mydb"));
2926        assert!(needs_raw_sql("use mydb"));
2927    }
2928
2929    #[test]
2930    fn test_needs_raw_sql_start_transaction() {
2931        assert!(needs_raw_sql("START TRANSACTION"));
2932        assert!(needs_raw_sql("start transaction"));
2933        assert!(needs_raw_sql("  START TRANSACTION READ ONLY"));
2934    }
2935
2936    #[test]
2937    fn test_needs_raw_sql_normal_queries() {
2938        assert!(!needs_raw_sql("SELECT * FROM users"));
2939        assert!(!needs_raw_sql("INSERT INTO t VALUES (1)"));
2940        assert!(!needs_raw_sql("UPDATE t SET x = 1"));
2941        assert!(!needs_raw_sql("DELETE FROM t"));
2942        assert!(!needs_raw_sql("CREATE TABLE t (id INT)"));
2943        assert!(!needs_raw_sql("DROP TABLE t"));
2944        assert!(!needs_raw_sql("ALTER TABLE t ADD COLUMN x INT"));
2945    }
2946
2947    #[test]
2948    fn test_needs_raw_sql_edge_cases() {
2949        assert!(!needs_raw_sql(""));
2950        assert!(!needs_raw_sql("   "));
2951        assert!(!needs_raw_sql("SELECT"));
2952        assert!(needs_raw_sql("BEGINNING"));
2953        assert!(needs_raw_sql("COMMITTEE"));
2954        assert!(!needs_raw_sql("INSERT"));
2955        assert!(!needs_raw_sql("DELETE"));
2956    }
2957
2958    // ---- v9.2.0 M18: validate_pg_channel_name 纯字符串校验 ----
2959
2960    #[test]
2961    fn test_validate_pg_channel_name_valid() {
2962        assert!(validate_pg_channel_name("my_channel").is_ok());
2963        assert!(validate_pg_channel_name("abc123").is_ok());
2964        assert!(validate_pg_channel_name("_test").is_ok());
2965        assert!(validate_pg_channel_name("a_b_c").is_ok());
2966    }
2967
2968    #[test]
2969    fn test_validate_pg_channel_name_empty() {
2970        let err = validate_pg_channel_name("").unwrap_err();
2971        assert!(matches!(err, DbError::Internal(ref s) if s.contains("empty")));
2972    }
2973
2974    #[test]
2975    fn test_validate_pg_channel_name_invalid_chars() {
2976        let err = validate_pg_channel_name("my-channel").unwrap_err();
2977        assert!(matches!(err, DbError::Internal(ref s) if s.contains("invalid")));
2978        assert!(validate_pg_channel_name("my.channel").is_err());
2979        assert!(validate_pg_channel_name("my channel").is_err());
2980        assert!(validate_pg_channel_name("my;channel").is_err());
2981        assert!(validate_pg_channel_name("my'channel").is_err());
2982    }
2983
2984    // ---- v9.2.0 M18: SqlxSqliteConnection conn=None 错误分支 ----
2985
2986    fn closed_sqlite() -> SqlxSqliteConnection {
2987        SqlxSqliteConnection {
2988            conn: None,
2989            connected: false,
2990            in_transaction: false,
2991        }
2992    }
2993
2994    #[tokio::test]
2995    async fn test_sqlite_execute_closed() {
2996        let mut c = closed_sqlite();
2997        let result = c.execute("SELECT 1").await;
2998        assert!(result.is_err());
2999        assert!(result.unwrap_err().to_string().contains("closed"));
3000    }
3001
3002    #[tokio::test]
3003    async fn test_sqlite_query_closed() {
3004        let mut c = closed_sqlite();
3005        let result = c.query("SELECT 1").await;
3006        assert!(result.is_err());
3007    }
3008
3009    #[tokio::test]
3010    async fn test_sqlite_begin_tx_already_started() {
3011        let mut c = SqlxSqliteConnection {
3012            conn: None,
3013            connected: false,
3014            in_transaction: true,
3015        };
3016        let result = c.begin_transaction().await;
3017        assert!(result.is_err());
3018        assert!(result.unwrap_err().to_string().contains("already started"));
3019    }
3020
3021    #[tokio::test]
3022    async fn test_sqlite_begin_tx_closed() {
3023        let mut c = closed_sqlite();
3024        let result = c.begin_transaction().await;
3025        assert!(result.is_err());
3026    }
3027
3028    #[tokio::test]
3029    async fn test_sqlite_commit_no_tx() {
3030        let mut c = closed_sqlite();
3031        let result = c.commit().await;
3032        assert!(result.is_ok());
3033    }
3034
3035    #[tokio::test]
3036    async fn test_sqlite_rollback_no_tx() {
3037        let mut c = closed_sqlite();
3038        let result = c.rollback().await;
3039        assert!(result.is_ok());
3040    }
3041
3042    #[test]
3043    fn test_sqlite_is_connected_false_when_conn_none() {
3044        let c = closed_sqlite();
3045        assert!(!c.is_connected());
3046    }
3047
3048    #[test]
3049    fn test_sqlite_is_connected_false_when_connected_false() {
3050        let c = SqlxSqliteConnection {
3051            conn: None,
3052            connected: true,
3053            in_transaction: false,
3054        };
3055        assert!(!c.is_connected());
3056    }
3057
3058    #[test]
3059    fn test_sqlite_in_transaction_flag() {
3060        let c = SqlxSqliteConnection {
3061            conn: None,
3062            connected: false,
3063            in_transaction: true,
3064        };
3065        assert!(c.in_transaction());
3066        let c2 = closed_sqlite();
3067        assert!(!c2.in_transaction());
3068    }
3069
3070    #[tokio::test]
3071    async fn test_sqlite_ping_closed() {
3072        let mut c = closed_sqlite();
3073        let result = c.ping().await;
3074        assert!(!result);
3075        assert!(!c.is_connected());
3076    }
3077
3078    #[tokio::test]
3079    async fn test_sqlite_close_closed() {
3080        let mut c = closed_sqlite();
3081        let result = c.close().await;
3082        assert!(result.is_ok());
3083        assert!(!c.is_connected());
3084        assert!(!c.in_transaction());
3085    }
3086
3087    #[tokio::test]
3088    async fn test_sqlite_execute_with_params_closed() {
3089        let mut c = closed_sqlite();
3090        let result = c
3091            .execute_with_params("INSERT INTO t VALUES (?)", &[Value::I64(1)])
3092            .await;
3093        assert!(result.is_err());
3094    }
3095
3096    #[tokio::test]
3097    async fn test_sqlite_execute_with_params_empty_delegates() {
3098        let mut c = closed_sqlite();
3099        let result = c.execute_with_params("INSERT INTO t VALUES (1)", &[]).await;
3100        assert!(result.is_err());
3101    }
3102
3103    #[tokio::test]
3104    async fn test_sqlite_query_with_params_empty_delegates() {
3105        let mut c = closed_sqlite();
3106        let result = c.query_with_params("SELECT * FROM t", &[]).await;
3107        assert!(result.is_err());
3108    }
3109
3110    #[tokio::test]
3111    async fn test_sqlite_query_with_params_closed() {
3112        let mut c = closed_sqlite();
3113        let result = c
3114            .query_with_params("SELECT * FROM t WHERE id = ?", &[Value::I64(1)])
3115            .await;
3116        assert!(result.is_err());
3117    }
3118
3119    // ---- v9.2.0 M18: SqlxMySqlConnection conn=None 错误分支 ----
3120
3121    fn closed_mysql() -> SqlxMySqlConnection {
3122        SqlxMySqlConnection {
3123            conn: None,
3124            connected: false,
3125            in_transaction: false,
3126            session_dirty: false,
3127        }
3128    }
3129
3130    #[tokio::test]
3131    async fn test_mysql_execute_closed() {
3132        let mut c = closed_mysql();
3133        assert!(c.execute("SELECT 1").await.is_err());
3134    }
3135
3136    #[tokio::test]
3137    async fn test_mysql_query_closed() {
3138        let mut c = closed_mysql();
3139        assert!(c.query("SELECT 1").await.is_err());
3140    }
3141
3142    #[test]
3143    fn test_mysql_is_connected_false() {
3144        let c = closed_mysql();
3145        assert!(!c.is_connected());
3146    }
3147
3148    #[tokio::test]
3149    async fn test_mysql_close_closed() {
3150        let mut c = closed_mysql();
3151        assert!(c.close().await.is_ok());
3152        assert!(!c.is_connected());
3153    }
3154
3155    #[tokio::test]
3156    async fn test_mysql_commit_no_tx() {
3157        let mut c = closed_mysql();
3158        assert!(c.commit().await.is_ok());
3159    }
3160
3161    #[tokio::test]
3162    async fn test_mysql_rollback_no_tx() {
3163        let mut c = closed_mysql();
3164        assert!(c.rollback().await.is_ok());
3165    }
3166
3167    #[tokio::test]
3168    async fn test_mysql_begin_tx_already_started() {
3169        let mut c = SqlxMySqlConnection {
3170            conn: None,
3171            connected: false,
3172            in_transaction: true,
3173            session_dirty: false,
3174        };
3175        let result = c.begin_transaction().await;
3176        assert!(result.is_err());
3177    }
3178
3179    #[tokio::test]
3180    async fn test_mysql_ping_closed() {
3181        let mut c = closed_mysql();
3182        assert!(!c.ping().await);
3183    }
3184
3185    // ---- v9.2.0 M18: SqlxPgConnection conn=None 错误分支 ----
3186
3187    fn closed_pg() -> SqlxPgConnection {
3188        SqlxPgConnection {
3189            conn: None,
3190            connected: false,
3191            in_transaction: false,
3192        }
3193    }
3194
3195    #[tokio::test]
3196    async fn test_pg_execute_closed() {
3197        let mut c = closed_pg();
3198        assert!(c.execute("SELECT 1").await.is_err());
3199    }
3200
3201    #[tokio::test]
3202    async fn test_pg_query_closed() {
3203        let mut c = closed_pg();
3204        assert!(c.query("SELECT 1").await.is_err());
3205    }
3206
3207    #[test]
3208    fn test_pg_is_connected_false() {
3209        let c = closed_pg();
3210        assert!(!c.is_connected());
3211    }
3212
3213    #[tokio::test]
3214    async fn test_pg_close_closed() {
3215        let mut c = closed_pg();
3216        assert!(c.close().await.is_ok());
3217        assert!(!c.is_connected());
3218    }
3219
3220    #[tokio::test]
3221    async fn test_pg_commit_no_tx() {
3222        let mut c = closed_pg();
3223        assert!(c.commit().await.is_ok());
3224    }
3225
3226    #[tokio::test]
3227    async fn test_pg_rollback_no_tx() {
3228        let mut c = closed_pg();
3229        assert!(c.rollback().await.is_ok());
3230    }
3231
3232    #[tokio::test]
3233    async fn test_pg_begin_tx_already_started() {
3234        let mut c = SqlxPgConnection {
3235            conn: None,
3236            connected: false,
3237            in_transaction: true,
3238        };
3239        assert!(c.begin_transaction().await.is_err());
3240    }
3241
3242    #[tokio::test]
3243    async fn test_pg_ping_closed() {
3244        let mut c = closed_pg();
3245        assert!(!c.ping().await);
3246    }
3247
3248    // ---- v9.2.0 M18: MySQL 扩展方法 conn=None ----
3249
3250    #[tokio::test]
3251    async fn test_mysql_execute_with_params_closed() {
3252        let mut c = closed_mysql();
3253        assert!(c
3254            .execute_with_params("INSERT INTO t VALUES (?)", &[Value::I64(1)])
3255            .await
3256            .is_err());
3257    }
3258
3259    #[tokio::test]
3260    async fn test_mysql_execute_with_params_empty_delegates() {
3261        let mut c = closed_mysql();
3262        assert!(c
3263            .execute_with_params("INSERT INTO t VALUES (1)", &[])
3264            .await
3265            .is_err());
3266    }
3267
3268    #[tokio::test]
3269    async fn test_mysql_query_with_params_closed() {
3270        let mut c = closed_mysql();
3271        assert!(c
3272            .query_with_params("SELECT * FROM t WHERE id = ?", &[Value::I64(1)])
3273            .await
3274            .is_err());
3275    }
3276
3277    #[tokio::test]
3278    async fn test_mysql_query_with_params_empty_delegates() {
3279        let mut c = closed_mysql();
3280        assert!(c.query_with_params("SELECT * FROM t", &[]).await.is_err());
3281    }
3282
3283    #[tokio::test]
3284    async fn test_mysql_query_values_closed() {
3285        let mut c = closed_mysql();
3286        assert!(c.query_values("SELECT * FROM t").await.is_err());
3287    }
3288
3289    #[tokio::test]
3290    async fn test_mysql_query_values_with_params_closed() {
3291        let mut c = closed_mysql();
3292        assert!(c
3293            .query_values_with_params("SELECT * FROM t WHERE id = ?", &[Value::I64(1)])
3294            .await
3295            .is_err());
3296    }
3297
3298    #[tokio::test]
3299    async fn test_mysql_query_values_with_params_empty_delegates() {
3300        let mut c = closed_mysql();
3301        assert!(c
3302            .query_values_with_params("SELECT * FROM t", &[])
3303            .await
3304            .is_err());
3305    }
3306
3307    // ---- v9.2.0 M18: PG 扩展方法 conn=None ----
3308
3309    #[tokio::test]
3310    async fn test_pg_execute_with_params_closed() {
3311        let mut c = closed_pg();
3312        assert!(c
3313            .execute_with_params("INSERT INTO t VALUES ($1)", &[Value::I64(1)])
3314            .await
3315            .is_err());
3316    }
3317
3318    #[tokio::test]
3319    async fn test_pg_execute_with_params_empty_delegates() {
3320        let mut c = closed_pg();
3321        assert!(c
3322            .execute_with_params("INSERT INTO t VALUES (1)", &[])
3323            .await
3324            .is_err());
3325    }
3326
3327    #[tokio::test]
3328    async fn test_pg_query_with_params_closed() {
3329        let mut c = closed_pg();
3330        assert!(c
3331            .query_with_params("SELECT * FROM t WHERE id = $1", &[Value::I64(1)])
3332            .await
3333            .is_err());
3334    }
3335
3336    #[tokio::test]
3337    async fn test_pg_query_with_params_empty_delegates() {
3338        let mut c = closed_pg();
3339        assert!(c.query_with_params("SELECT * FROM t", &[]).await.is_err());
3340    }
3341
3342    #[tokio::test]
3343    async fn test_pg_query_values_closed() {
3344        let mut c = closed_pg();
3345        assert!(c.query_values("SELECT * FROM t").await.is_err());
3346    }
3347
3348    #[tokio::test]
3349    async fn test_pg_query_values_with_params_closed() {
3350        let mut c = closed_pg();
3351        assert!(c
3352            .query_values_with_params("SELECT * FROM t WHERE id = $1", &[Value::I64(1)])
3353            .await
3354            .is_err());
3355    }
3356
3357    #[tokio::test]
3358    async fn test_pg_query_values_with_params_empty_delegates() {
3359        let mut c = closed_pg();
3360        assert!(c
3361            .query_values_with_params("SELECT * FROM t", &[])
3362            .await
3363            .is_err());
3364    }
3365
3366    // ---- v9.2.0 M18: sqlite_backup conn=None ----
3367
3368    #[tokio::test]
3369    async fn test_sqlite_backup_closed_conn() {
3370        let mut c = closed_sqlite();
3371        let result = sqlite_backup(&mut c, "/tmp/backup.db").await;
3372        assert!(result.is_err());
3373    }
3374
3375    // ---- v9.2.0 M18: SQLite query_values conn=None ----
3376
3377    #[tokio::test]
3378    async fn test_sqlite_query_values_closed() {
3379        let mut c = closed_sqlite();
3380        assert!(c.query_values("SELECT * FROM t").await.is_err());
3381    }
3382
3383    #[tokio::test]
3384    async fn test_sqlite_query_values_with_params_closed() {
3385        let mut c = closed_sqlite();
3386        assert!(c
3387            .query_values_with_params("SELECT * FROM t WHERE id = ?", &[Value::I64(1)])
3388            .await
3389            .is_err());
3390    }
3391
3392    #[tokio::test]
3393    async fn test_sqlite_query_values_with_params_empty_delegates() {
3394        let mut c = closed_sqlite();
3395        assert!(c
3396            .query_values_with_params("SELECT * FROM t", &[])
3397            .await
3398            .is_err());
3399    }
3400
3401    // ---- v9.2.0 M18: bulk_insert 空行 + conn=None ----
3402
3403    #[tokio::test]
3404    async fn test_mysql_bulk_insert_empty_rows() {
3405        let mut c = closed_mysql();
3406        let result = mysql_bulk_insert(&mut c, "t", &["a", "b"], &[]).await;
3407        assert_eq!(result.unwrap(), 0);
3408    }
3409
3410    #[tokio::test]
3411    async fn test_mysql_bulk_insert_closed_conn() {
3412        let mut c = closed_mysql();
3413        let rows = vec![vec![Value::I64(1), Value::String("x".to_string())]];
3414        let result = mysql_bulk_insert(&mut c, "t", &["a", "b"], &rows).await;
3415        assert!(result.is_err());
3416    }
3417
3418    #[tokio::test]
3419    async fn test_pg_bulk_insert_empty_rows() {
3420        let mut c = closed_pg();
3421        let result = pg_bulk_insert(&mut c, "t", &["a", "b"], &[]).await;
3422        assert_eq!(result.unwrap(), 0);
3423    }
3424
3425    #[tokio::test]
3426    async fn test_pg_bulk_insert_closed_conn() {
3427        let mut c = closed_pg();
3428        let rows = vec![vec![Value::I64(1), Value::String("x".to_string())]];
3429        let result = pg_bulk_insert(&mut c, "t", &["a", "b"], &rows).await;
3430        assert!(result.is_err());
3431    }
3432
3433    // ---- R1:TIMESTAMP/TIMESTAMPTZ 绑定解析 ----
3434
3435    #[test]
3436    fn test_parse_pg_datetime_naive() {
3437        match parse_pg_datetime("2026-10-28 13:14:15.123456") {
3438            Some(PgDateTime::Naive(dt)) => {
3439                assert_eq!(
3440                    dt.format("%Y-%m-%d %H:%M:%S%.6f").to_string(),
3441                    "2026-10-28 13:14:15.123456"
3442                );
3443            }
3444            _ => panic!("无偏移输入必须解析为 Naive"),
3445        }
3446    }
3447
3448    #[test]
3449    fn test_parse_pg_datetime_zoned_forms() {
3450        // RFC3339 / 空格分隔 + 偏移 / 无冒号偏移 都必须识别为 Zoned
3451        for s in [
3452            "2026-10-28T13:14:15+08:00",
3453            "2026-10-28 13:14:15+08:00",
3454            "2026-10-28 13:14:15 +0800",
3455            "2026-10-28T05:14:15Z",
3456        ] {
3457            match parse_pg_datetime(s) {
3458                Some(PgDateTime::Zoned(dt)) => {
3459                    // 同一时刻的 UTC 表示
3460                    assert_eq!(
3461                        dt.format("%Y-%m-%d %H:%M:%S").to_string(),
3462                        "2026-10-28 05:14:15",
3463                        "输入 {s}"
3464                    );
3465                }
3466                other => panic!("{s} 必须解析为 Zoned,实际 {:?}", other.is_some()),
3467            }
3468        }
3469    }
3470
3471    #[test]
3472    fn test_parse_pg_time_fractional() {
3473        assert!(parse_pg_time("13:14:15").is_some());
3474        assert!(parse_pg_time("13:14:15.123456").is_some());
3475        assert!(parse_pg_time("13:14").is_none());
3476    }
3477
3478    // ---- R2:DECIMAL 38 位无损 ----
3479
3480    #[test]
3481    fn test_bigdecimal_38_digits_no_truncation() {
3482        let s = "1234567890123456789012345678.9012345678"; // 38 位有效数字
3483        let d: bigdecimal::BigDecimal = s.parse().unwrap();
3484        assert_eq!(d.to_string(), s, "BigDecimal 往返必须逐位一致");
3485        // 反证:rust_decimal 在 96 位尾数处截断(记录回归动机)
3486        assert!(
3487            s.parse::<rust_decimal::Decimal>().is_err() || {
3488                let rd: rust_decimal::Decimal = s.parse().unwrap();
3489                rd.to_string() != s
3490            }
3491        );
3492    }
3493}
3494
3495#[cfg(test)]
3496mod session_dirty_tests {
3497    use super::marks_session_dirty;
3498
3499    #[test]
3500    fn set_statements_mark_dirty() {
3501        assert!(marks_session_dirty("SET SESSION sql_mode = DEFAULT"));
3502        assert!(marks_session_dirty("set names utf8mb4"));
3503        assert!(marks_session_dirty("SET autocommit = 0"));
3504        assert!(marks_session_dirty("SET @my_var = 1"));
3505        assert!(marks_session_dirty("  set\tsql_mode='x'"));
3506    }
3507
3508    #[test]
3509    fn use_marks_dirty() {
3510        assert!(marks_session_dirty("USE sz_orm_test"));
3511        assert!(marks_session_dirty("use other_db;"));
3512    }
3513
3514    #[test]
3515    fn set_transaction_not_dirty() {
3516        assert!(!marks_session_dirty(
3517            "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"
3518        ));
3519        assert!(!marks_session_dirty("set transaction read only"));
3520        assert!(!marks_session_dirty("SET TRANSACTION;"));
3521    }
3522
3523    #[test]
3524    fn set_transaction_identifier_still_dirty() {
3525        // `SET TRANSACTIONX` 不是 SET TRANSACTION 语句
3526        assert!(marks_session_dirty("SET TRANSACTIONX = 1"));
3527    }
3528
3529    #[test]
3530    fn read_and_tx_statements_not_dirty() {
3531        assert!(!marks_session_dirty("SELECT 1"));
3532        assert!(!marks_session_dirty("select * from t where a=1"));
3533        assert!(!marks_session_dirty("SHOW TABLES"));
3534        assert!(!marks_session_dirty("EXPLAIN SELECT 1"));
3535        assert!(!marks_session_dirty("START TRANSACTION"));
3536        assert!(!marks_session_dirty("BEGIN"));
3537        assert!(!marks_session_dirty("COMMIT"));
3538        assert!(!marks_session_dirty("ROLLBACK"));
3539        assert!(!marks_session_dirty("INSERT INTO t VALUES (1)"));
3540        assert!(!marks_session_dirty("UPDATE t SET a = 1 WHERE id = 1"));
3541        assert!(!marks_session_dirty("DELETE FROM t WHERE id = 1"));
3542    }
3543
3544    #[test]
3545    fn word_boundary_no_false_positive() {
3546        // "SETUP..." 不是 SET 语句
3547        assert!(!marks_session_dirty("SETUP_CHECK = 1"));
3548        // "USER" 表名查询不是 USE 语句
3549        assert!(!marks_session_dirty("SELECT * FROM USER"));
3550    }
3551}