Skip to main content

sz_orm_core/
queryable.rs

1//! derive(Queryable) — 从 SELECT 结果自动派生结构体(Diesel 风格)
2//!
3//! Diesel 通过 `#[derive(Queryable)]` 让结构体自动从 SQL 行反序列化。
4//! SZ-ORM 在 [`crate::value::Value`] 之上提供类似的 trait + 派生辅助。
5//!
6//! 由于 proc-macro derive 需要在 `sz-orm-macros` 包中实现,
7//! 此模块提供 trait 定义和运行时反序列化逻辑;
8//! 派生宏 `#[derive(Queryable)]` 在 `sz-orm-macros` 中实现。
9//!
10//! # 设计
11//!
12//! - [`Queryable`] trait:从 `Vec<Value>` 按列顺序填充结构体字段
13//! - [`FromRow`] trait:从 `HashMap<String, Value>` 按列名填充(更鲁棒)
14//! - [`RowDesc`]:行描述(列名 + 列数),用于反序列化校验
15//!
16//! # 用法
17//!
18//! ```ignore
19//! use sz_orm_core::value::Value;
20//! use sz_orm_core::queryable::{Queryable, FromRow, RowDesc};
21//!
22//! #[derive(Debug, Default, PartialEq)]
23//! struct UserRow {
24//!     id: i64,
25//!     name: String,
26//! }
27//!
28//! impl Queryable for UserRow {
29//!     fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
30//!         if values.len() != 2 {
31//!             return Err(QueryError::ColumnCountMismatch {
32//!                 expected: 2,
33//!                 actual: values.len(),
34//!             });
35//!         }
36//!         let id = values[0].as_i64().ok_or(QueryError::TypeMismatch {
37//!             column: 0,
38//!             expected: "i64",
39//!         })?;
40//!         let name = values[1].as_str().ok_or(QueryError::TypeMismatch {
41//!             column: 1,
42//!             expected: "String",
43//!         })?.to_string();
44//!         Ok(UserRow { id, name })
45//!     }
46//! }
47//!
48//! let row = UserRow::from_values(vec![Value::I64(42), Value::String("Alice".into())]).unwrap();
49//! assert_eq!(row.id, 42);
50//! assert_eq!(row.name, "Alice");
51//! ```
52
53use crate::value::Value;
54use std::collections::HashMap;
55
56/// 反序列化错误
57#[derive(Debug, Clone, PartialEq)]
58pub enum QueryError {
59    /// 列数不匹配
60    ColumnCountMismatch {
61        /// 期望的列数
62        expected: usize,
63        /// 实际的列数
64        actual: usize,
65    },
66    /// 类型不匹配
67    TypeMismatch {
68        /// 列索引(按位置反序列化时)或列名(按名反序列化时)
69        column: std::borrow::Cow<'static, str>,
70        /// 期望的 Rust 类型名
71        expected: &'static str,
72    },
73    /// 缺少列
74    MissingColumn {
75        /// 缺失的列名
76        column: &'static str,
77    },
78    /// 自定义错误
79    Custom(String),
80}
81
82impl std::fmt::Display for QueryError {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            QueryError::ColumnCountMismatch { expected, actual } => {
86                write!(f, "列数不匹配: 期望 {}, 实际 {}", expected, actual)
87            }
88            QueryError::TypeMismatch { column, expected } => {
89                write!(f, "列 {:?} 类型不匹配, 期望 {}", column, expected)
90            }
91            QueryError::MissingColumn { column } => {
92                write!(f, "缺少列: {}", column)
93            }
94            QueryError::Custom(msg) => write!(f, "{}", msg),
95        }
96    }
97}
98
99impl std::error::Error for QueryError {}
100
101/// 行描述:列名 + 列数
102///
103/// 用于在按位置反序列化([`Queryable`])时提供列名信息,
104/// 或在按名反序列化([`FromRow`])时校验列存在性。
105#[derive(Debug, Clone)]
106pub struct RowDesc {
107    /// 列名列表(按 SELECT 顺序)
108    pub columns: Vec<String>,
109}
110
111impl RowDesc {
112    /// 创建行描述
113    pub fn new(columns: Vec<String>) -> Self {
114        Self { columns }
115    }
116
117    /// 列数
118    pub fn len(&self) -> usize {
119        self.columns.len()
120    }
121
122    /// 是否为空
123    pub fn is_empty(&self) -> bool {
124        self.columns.is_empty()
125    }
126
127    /// 查找列索引(按名)
128    pub fn index_of(&self, name: &str) -> Option<usize> {
129        self.columns.iter().position(|c| c == name)
130    }
131}
132
133/// 从 `Vec<Value>` 按列顺序反序列化(Diesel 风格)
134///
135/// 适用于 `SELECT id, name FROM users` 这种列顺序已知的查询。
136/// 列顺序由 SQL 决定,结构体字段顺序需与之对应。
137pub trait Queryable: Sized {
138    /// 从按 SELECT 顺序排列的值列表构造实例
139    fn from_values(values: Vec<Value>) -> Result<Self, QueryError>;
140
141    /// 从带行描述的值列表构造(默认实现忽略描述)
142    fn from_values_with_desc(values: Vec<Value>, desc: &RowDesc) -> Result<Self, QueryError> {
143        if values.len() != desc.len() {
144            return Err(QueryError::ColumnCountMismatch {
145                expected: desc.len(),
146                actual: values.len(),
147            });
148        }
149        Self::from_values(values)
150    }
151}
152
153/// 从 `HashMap<String, Value>` 按列名反序列化(更鲁棒)
154///
155/// 适用于列顺序不固定或查询使用 `*` 的场景。
156/// 按列名查找,不受 SQL 列顺序影响。
157pub trait FromRow: Sized {
158    /// 从列名到值的映射构造实例
159    fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError>;
160}
161
162// ---- 基础类型的 Queryable 实现 ----
163
164/// 单列查询结果(如 `SELECT COUNT(*)`)
165impl Queryable for Value {
166    fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
167        if values.len() != 1 {
168            return Err(QueryError::ColumnCountMismatch {
169                expected: 1,
170                actual: values.len(),
171            });
172        }
173        Ok(values.into_iter().next().unwrap()) // SAFETY: 前置 len == 1 校验保证 next() 返回 Some
174    }
175}
176
177/// 双列查询结果
178impl Queryable for (Value, Value) {
179    fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
180        if values.len() != 2 {
181            return Err(QueryError::ColumnCountMismatch {
182                expected: 2,
183                actual: values.len(),
184            });
185        }
186        let mut iter = values.into_iter();
187        Ok((iter.next().unwrap(), iter.next().unwrap())) // SAFETY: 前置 len == 2 校验保证两次 next() 返回 Some
188    }
189}
190
191/// 三列查询结果
192impl Queryable for (Value, Value, Value) {
193    fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
194        if values.len() != 3 {
195            return Err(QueryError::ColumnCountMismatch {
196                expected: 3,
197                actual: values.len(),
198            });
199        }
200        let mut iter = values.into_iter();
201        Ok((
202            iter.next().unwrap(), // SAFETY: 前置 len == 3 校验保证 next() 返回 Some
203            iter.next().unwrap(),
204            iter.next().unwrap(),
205        ))
206    }
207}
208
209// ---- 辅助函数:从 Value 提取 Rust 类型 ----
210
211/// 提取 i64(支持 I8/I16/I32/I64/U8/U16/U32/U64/F32/F64/Bool/String 转换)
212pub fn value_as_i64(v: &Value) -> Option<i64> {
213    v.as_i64()
214}
215
216/// 提取 f64
217pub fn value_as_f64(v: &Value) -> Option<f64> {
218    v.as_f64()
219}
220
221/// 提取 String
222pub fn value_as_string(v: &Value) -> Option<String> {
223    v.as_str().map(|s| s.to_string())
224}
225
226/// 提取 bool
227pub fn value_as_bool(v: &Value) -> Option<bool> {
228    v.as_bool()
229}
230
231/// 提取 `Option<i64>`(Null 返回 None)
232pub fn value_as_nullable_i64(v: &Value) -> Option<i64> {
233    if v.is_null() {
234        None
235    } else {
236        v.as_i64()
237    }
238}
239
240/// 提取 `Option<String>`(Null 返回 None)
241pub fn value_as_nullable_string(v: &Value) -> Option<String> {
242    if v.is_null() {
243        None
244    } else {
245        v.as_str().map(|s| s.to_string())
246    }
247}
248
249// ===== QueryAs<T>:类型化裸 SQL 查询(SQLx query_as! 风格) =====
250
251/// 类型化裸 SQL 查询构建器。
252///
253/// 由 `query_as!` 宏生成,等效于 SQLx 的 `query_as!(Record, "SELECT ...")`:
254/// 在编译期验证 SQL 语法(`db-verify` feature 下连真 DB 验证列名),
255/// 运行时将结果行按列名映射到 `T: FromQueryResult`。
256///
257/// # 用法
258///
259/// ```ignore
260/// use sz_orm_core::queryable::QueryAs;
261///
262/// #[derive(FromQueryResult)]
263/// struct User { id: i64, name: String }
264///
265/// let q = QueryAs::<User>::new("SELECT id, name FROM users WHERE id = ?");
266/// let users: Vec<User> = q.fetch_all(&mut conn).await?;
267/// let one: User = QueryAs::<User>::new("SELECT id, name FROM users LIMIT 1")
268///     .fetch_one(&mut conn)
269///     .await?;
270/// ```
271/// 无类型裸 SQL 查询对象(SQLx `query!` 的 sz-orm 等效物)。
272///
273/// 与 [`QueryAs<T>`] 的区别:`Query` 返回 `Vec<HashMap<String, Value>>`,
274/// 不绑定具体结构体类型;`QueryAs<T>` 将行映射为 `T: FromQueryResult`。
275///
276/// # 用法
277///
278/// ```ignore
279/// use sz_orm_core::queryable::Query;
280///
281/// let q = Query::new("SELECT id, name FROM users WHERE id = ?");
282/// let rows = q.fetch_all(&mut conn).await?;
283/// ```
284pub struct Query {
285    sql: String,
286}
287
288impl Query {
289    /// 从 SQL 字符串构造查询对象
290    pub fn new(sql: impl Into<String>) -> Self {
291        Self { sql: sql.into() }
292    }
293
294    /// 获取底层 SQL(用于日志/调试)
295    pub fn sql(&self) -> &str {
296        &self.sql
297    }
298}
299
300impl std::fmt::Display for Query {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        write!(f, "{}", self.sql)
303    }
304}
305
306impl Query {
307    /// 执行查询,返回所有行(`Vec<HashMap<列名, Value>>`)
308    pub async fn fetch_all(
309        &self,
310        conn: &mut dyn crate::Connection,
311    ) -> Result<Vec<std::collections::HashMap<String, crate::value::Value>>, crate::DbError> {
312        conn.query(&self.sql).await
313    }
314
315    /// 执行查询,期望恰好一行;0 行或多行均返回错误
316    pub async fn fetch_one(
317        &self,
318        conn: &mut dyn crate::Connection,
319    ) -> Result<std::collections::HashMap<String, crate::value::Value>, crate::DbError> {
320        let rows = conn.query(&self.sql).await?;
321        match rows.len() {
322            0 => Err(crate::DbError::NotFound(
323                "fetch_one: no rows returned".into(),
324            )),
325            1 => Ok(rows.into_iter().next().unwrap()), // SAFETY: 前置 len == 1 校验保证 next() 返回 Some
326            n => Err(crate::DbError::QueryError(format!(
327                "fetch_one expected 1 row, got {}",
328                n
329            ))),
330        }
331    }
332
333    /// 执行查询,返回 0 或 1 行;0 行返回 `Ok(None)`
334    pub async fn fetch_optional(
335        &self,
336        conn: &mut dyn crate::Connection,
337    ) -> Result<Option<std::collections::HashMap<String, crate::value::Value>>, crate::DbError>
338    {
339        let rows = conn.query(&self.sql).await?;
340        Ok(rows.into_iter().next())
341    }
342}
343
344/// 类型化裸 SQL 查询对象(SQLx `query_as!` 的 sz-orm 等效物)。
345///
346/// 与 [`Query`] 的区别:`QueryAs<T>` 将行映射为 `T: FromQueryResult`,
347/// 提供类型安全的查询结果;`Query` 返回 `Vec<HashMap<String, Value>>`。
348///
349/// # 用法
350///
351/// ```ignore
352/// use sz_orm_core::queryable::QueryAs;
353///
354/// #[derive(FromQueryResult)]
355/// struct User { id: i64, name: String }
356///
357/// let q = QueryAs::<User>::new("SELECT id, name FROM users WHERE id = ?");
358/// let users: Vec<User> = q.fetch_all(&mut conn).await?;
359/// let one: User = QueryAs::<User>::new("SELECT id, name FROM users LIMIT 1")
360///     .fetch_one(&mut conn)
361///     .await?;
362/// ```
363pub struct QueryAs<T> {
364    sql: String,
365    _marker: std::marker::PhantomData<T>,
366}
367
368impl<T> QueryAs<T> {
369    /// 从 SQL 字符串构造类型化查询
370    pub fn new(sql: impl Into<String>) -> Self {
371        Self {
372            sql: sql.into(),
373            _marker: std::marker::PhantomData,
374        }
375    }
376
377    /// 获取底层 SQL(用于日志/调试)
378    pub fn sql(&self) -> &str {
379        &self.sql
380    }
381}
382
383impl<T> std::fmt::Display for QueryAs<T> {
384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385        write!(f, "{}", self.sql)
386    }
387}
388
389impl<T: crate::value::FromQueryResult> QueryAs<T> {
390    /// 执行查询,返回所有行映射为 `Vec<T>`
391    ///
392    /// **运行时列名验证**:比对 DB 返回的列名与 `T::row_desc()`(由
393    /// `#[derive(FromQueryResult)]` 自动生成)。若 SQL SELECT 列不在 struct 字段中,
394    /// 返回 `DbError::QueryError`,避免静默忽略多余列或类型不匹配。
395    ///
396    /// **运行时列类型验证**(P0-2):比对 DB 返回值的实际类型与
397    /// `T::column_types()` 期望类型。若不兼容(如 DB 返回 `TEXT` 但 struct 期望
398    /// `i64`),返回 `DbError::QueryError`,防止静默类型截断。
399    pub async fn fetch_all(
400        &self,
401        conn: &mut dyn crate::Connection,
402    ) -> Result<Vec<T>, crate::DbError> {
403        let rows = conn.query(&self.sql).await?;
404        if rows.is_empty() {
405            return Ok(Vec::new());
406        }
407        // 运行时列名交叉验证(P0-2)
408        let expected: Vec<&str> = T::row_desc();
409        if !expected.is_empty() {
410            let actual: Vec<&str> = rows[0].keys().map(|s| s.as_str()).collect();
411            validate_columns(&actual, &expected)?;
412            // 运行时列类型交叉验证(P0-2)
413            validate_column_types(&rows[0], T::column_types())?;
414        }
415        rows.into_iter()
416            .map(|row| {
417                T::from_query_result(&row).map_err(|e| crate::DbError::QueryError(e.to_string()))
418            })
419            .collect()
420    }
421
422    /// 执行查询,期望恰好一行;0 行或多行均返回错误
423    pub async fn fetch_one(&self, conn: &mut dyn crate::Connection) -> Result<T, crate::DbError> {
424        let rows = conn.query(&self.sql).await?;
425        match rows.len() {
426            0 => Err(crate::DbError::NotFound(
427                "fetch_one: no rows returned".into(),
428            )),
429            1 => {
430                let row = &rows[0];
431                let expected: Vec<&str> = T::row_desc();
432                if !expected.is_empty() {
433                    let actual: Vec<&str> = row.keys().map(|s| s.as_str()).collect();
434                    validate_columns(&actual, &expected)?;
435                    validate_column_types(row, T::column_types())?;
436                }
437                T::from_query_result(row).map_err(|e| crate::DbError::QueryError(e.to_string()))
438            }
439            n => Err(crate::DbError::QueryError(format!(
440                "fetch_one expected 1 row, got {}",
441                n
442            ))),
443        }
444    }
445
446    /// 执行查询,返回 0 或 1 行;0 行返回 `Ok(None)`
447    pub async fn fetch_optional(
448        &self,
449        conn: &mut dyn crate::Connection,
450    ) -> Result<Option<T>, crate::DbError> {
451        let rows = conn.query(&self.sql).await?;
452        match rows.len() {
453            0 => Ok(None),
454            _ => {
455                let row = &rows[0];
456                let expected: Vec<&str> = T::row_desc();
457                if !expected.is_empty() {
458                    let actual: Vec<&str> = row.keys().map(|s| s.as_str()).collect();
459                    validate_columns(&actual, &expected)?;
460                    validate_column_types(row, T::column_types())?;
461                }
462                T::from_query_result(row)
463                    .map_err(|e| crate::DbError::QueryError(e.to_string()))
464                    .map(Some)
465            }
466        }
467    }
468}
469
470/// 比对 DB 实际返回列名与 struct 期望列名(P0-2 运行时列名验证)。
471fn validate_columns(actual: &[&str], expected: &[&str]) -> Result<(), crate::DbError> {
472    use std::collections::HashSet;
473    let actual_set: HashSet<&str> = actual.iter().copied().collect();
474    let expected_set: HashSet<&str> = expected.iter().copied().collect();
475    let missing: Vec<&str> = expected_set.difference(&actual_set).copied().collect();
476    let extra: Vec<&str> = actual_set.difference(&expected_set).copied().collect();
477    if !missing.is_empty() || !extra.is_empty() {
478        return Err(crate::DbError::QueryError(format!(
479            "query_as! column mismatch: struct expects {:?}, DB returned {:?}{}",
480            expected,
481            actual,
482            if missing.is_empty() {
483                String::new()
484            } else {
485                format!(" (missing: {:?})", missing)
486            }
487        )));
488    }
489    Ok(())
490}
491
492/// 比对 DB 实际返回值的类型与 struct 期望的列类型(P0-2 运行时列类型验证)。
493///
494/// 从 `Value` 变体推导实际 SQL 类型名,与 `T::column_types()` 中的期望类型
495/// 通过 `ColType::from_type_name()` 做兼容性匹配。
496///
497/// 例如:DB 返回 `Value::I64`(→ `"BIGINT"`)但 struct 期望 `"TEXT"`,
498/// 两者 `ColType` 分类不同(I64 vs String),返回类型不匹配错误。
499fn validate_column_types(
500    row: &std::collections::HashMap<String, crate::value::Value>,
501    expected: &[(&str, &str)],
502) -> Result<(), crate::DbError> {
503    use crate::value::{ColType, Value};
504
505    for (col_name, expected_type) in expected {
506        let value = match row.get(*col_name) {
507            Some(v) => v,
508            None => continue, // 列缺失由 validate_columns 处理
509        };
510        if matches!(value, Value::Null) {
511            continue; // NULL 不校验类型
512        }
513        let actual_type = value_to_sql_type_name(value);
514        let actual_col = ColType::from_type_name(actual_type);
515        let expected_col = ColType::from_type_name(expected_type);
516        if actual_col != expected_col
517            && actual_col != ColType::Unknown
518            && expected_col != ColType::Unknown
519        {
520            return Err(crate::DbError::QueryError(format!(
521                "query_as! column TYPE mismatch: column '{}' has actual type '{}' (ColType::{:?}), \
522                 but struct expects '{}' (ColType::{:?})",
523                col_name, actual_type, actual_col, expected_type, expected_col
524            )));
525        }
526    }
527    Ok(())
528}
529
530/// 从 `Value` 变体推导 SQL 类型名(大写,与 `ColType::from_type_name` 对齐)。
531fn value_to_sql_type_name(value: &Value) -> &'static str {
532    match value {
533        Value::Null => "NULL",
534        Value::Bool(_) => "BOOLEAN",
535        Value::I8(_) | Value::U8(_) => "TINYINT",
536        Value::I16(_) | Value::U16(_) => "SMALLINT",
537        Value::I32(_) | Value::U32(_) => "INT",
538        Value::I64(_) | Value::U64(_) => "BIGINT",
539        Value::F32(_) => "FLOAT",
540        Value::F64(_) => "DOUBLE",
541        Value::Decimal(_) => "DECIMAL",
542        Value::String(_)
543        | Value::Uuid(_)
544        | Value::Date(_)
545        | Value::DateTime(_)
546        | Value::Time(_)
547        | Value::Json(_) => "VARCHAR",
548        Value::Bytes(_) => "BLOB",
549        Value::Array(_) | Value::Object(_) => "JSON",
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556
557    // ---- 测试用结构体 ----
558
559    #[derive(Debug, Default, PartialEq)]
560    struct UserRow {
561        id: i64,
562        name: String,
563    }
564
565    impl Queryable for UserRow {
566        fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
567            if values.len() != 2 {
568                return Err(QueryError::ColumnCountMismatch {
569                    expected: 2,
570                    actual: values.len(),
571                });
572            }
573            let id = values[0].as_i64().ok_or(QueryError::TypeMismatch {
574                column: "0".into(),
575                expected: "i64",
576            })?;
577            let name = values[1]
578                .as_str()
579                .ok_or(QueryError::TypeMismatch {
580                    column: "1".into(),
581                    expected: "String",
582                })?
583                .to_string();
584            Ok(UserRow { id, name })
585        }
586    }
587
588    impl FromRow for UserRow {
589        fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError> {
590            let id = row
591                .get("id")
592                .ok_or(QueryError::MissingColumn { column: "id" })?
593                .as_i64()
594                .ok_or(QueryError::TypeMismatch {
595                    column: "id".into(),
596                    expected: "i64",
597                })?;
598            let name = row
599                .get("name")
600                .ok_or(QueryError::MissingColumn { column: "name" })?
601                .as_str()
602                .ok_or(QueryError::TypeMismatch {
603                    column: "name".into(),
604                    expected: "String",
605                })?
606                .to_string();
607            Ok(UserRow { id, name })
608        }
609    }
610
611    // ---- QueryError 测试 ----
612
613    #[test]
614    fn test_query_error_display() {
615        let e = QueryError::ColumnCountMismatch {
616            expected: 3,
617            actual: 2,
618        };
619        assert!(format!("{}", e).contains("3"));
620        assert!(format!("{}", e).contains("2"));
621
622        let e = QueryError::TypeMismatch {
623            column: "age".into(),
624            expected: "i64",
625        };
626        assert!(format!("{}", e).contains("age"));
627
628        let e = QueryError::MissingColumn { column: "id" };
629        assert!(format!("{}", e).contains("id"));
630
631        let e = QueryError::Custom("custom".into());
632        assert_eq!(format!("{}", e), "custom");
633    }
634
635    // ---- RowDesc 测试 ----
636
637    #[test]
638    fn test_row_desc_basic() {
639        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
640        assert_eq!(desc.len(), 3);
641        assert!(!desc.is_empty());
642        assert_eq!(desc.index_of("name"), Some(1));
643        assert_eq!(desc.index_of("missing"), None);
644    }
645
646    #[test]
647    fn test_row_desc_empty() {
648        let desc = RowDesc::new(vec![]);
649        assert!(desc.is_empty());
650        assert_eq!(desc.len(), 0);
651    }
652
653    // ---- Queryable for UserRow 测试 ----
654
655    #[test]
656    fn test_user_row_from_values_success() {
657        let row =
658            UserRow::from_values(vec![Value::I64(42), Value::String("Alice".into())]).unwrap();
659        assert_eq!(row.id, 42);
660        assert_eq!(row.name, "Alice");
661    }
662
663    #[test]
664    fn test_user_row_from_values_count_mismatch() {
665        let result = UserRow::from_values(vec![Value::I64(42)]);
666        assert!(matches!(
667            result,
668            Err(QueryError::ColumnCountMismatch {
669                expected: 2,
670                actual: 1
671            })
672        ));
673    }
674
675    #[test]
676    fn test_user_row_from_values_type_mismatch() {
677        let result = UserRow::from_values(vec![
678            Value::String("not_an_int".into()),
679            Value::String("Alice".into()),
680        ]);
681        assert!(matches!(result, Err(QueryError::TypeMismatch { .. })));
682    }
683
684    #[test]
685    fn test_user_row_from_values_with_desc() {
686        let desc = RowDesc::new(vec!["id".into(), "name".into()]);
687        let row =
688            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc)
689                .unwrap();
690        assert_eq!(row.id, 1);
691        assert_eq!(row.name, "Bob");
692    }
693
694    #[test]
695    fn test_user_row_from_values_with_desc_mismatch() {
696        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
697        let result =
698            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc);
699        assert!(matches!(
700            result,
701            Err(QueryError::ColumnCountMismatch { .. })
702        ));
703    }
704
705    // ---- FromRow for UserRow 测试 ----
706
707    #[test]
708    fn test_user_row_from_row_success() {
709        let mut map = HashMap::new();
710        map.insert("id".into(), Value::I64(99));
711        map.insert("name".into(), Value::String("Charlie".into()));
712        let row = UserRow::from_row(map).unwrap();
713        assert_eq!(row.id, 99);
714        assert_eq!(row.name, "Charlie");
715    }
716
717    #[test]
718    fn test_user_row_from_row_missing_column() {
719        let mut map = HashMap::new();
720        map.insert("id".into(), Value::I64(99));
721        // 缺少 name
722        let result = UserRow::from_row(map);
723        assert!(matches!(
724            result,
725            Err(QueryError::MissingColumn { column: "name" })
726        ));
727    }
728
729    #[test]
730    fn test_user_row_from_row_extra_columns_ignored() {
731        let mut map = HashMap::new();
732        map.insert("id".into(), Value::I64(1));
733        map.insert("name".into(), Value::String("X".into()));
734        map.insert("extra".into(), Value::String("ignored".into()));
735        let row = UserRow::from_row(map).unwrap();
736        assert_eq!(row.id, 1);
737    }
738
739    // ---- 基础类型 Queryable 实现 ----
740
741    #[test]
742    fn test_value_queryable_single() {
743        let v = Value::from_values(vec![Value::I64(42)]).unwrap();
744        assert_eq!(v.as_i64(), Some(42));
745    }
746
747    #[test]
748    fn test_value_queryable_count_mismatch() {
749        let result = Value::from_values(vec![Value::I64(1), Value::I64(2)]);
750        assert!(matches!(
751            result,
752            Err(QueryError::ColumnCountMismatch { .. })
753        ));
754    }
755
756    #[test]
757    fn test_tuple_2_queryable() {
758        let (a, b) =
759            <(Value, Value)>::from_values(vec![Value::I64(1), Value::String("hello".into())])
760                .unwrap();
761        assert_eq!(a.as_i64(), Some(1));
762        assert_eq!(b.as_str(), Some("hello"));
763    }
764
765    #[test]
766    fn test_tuple_3_queryable() {
767        let (a, b, c) = <(Value, Value, Value)>::from_values(vec![
768            Value::I64(1),
769            Value::String("two".into()),
770            Value::F64(3.5),
771        ])
772        .unwrap();
773        assert_eq!(a.as_i64(), Some(1));
774        assert_eq!(b.as_str(), Some("two"));
775        assert_eq!(c.as_f64(), Some(3.5));
776    }
777
778    // ---- 辅助函数测试 ----
779
780    #[test]
781    fn test_value_helpers() {
782        assert_eq!(value_as_i64(&Value::I64(42)), Some(42));
783        assert_eq!(value_as_i64(&Value::String("42".into())), Some(42));
784        assert_eq!(value_as_f64(&Value::F64(3.5)), Some(3.5));
785        assert_eq!(
786            value_as_string(&Value::String("hi".into())),
787            Some("hi".into())
788        );
789        assert_eq!(value_as_bool(&Value::Bool(true)), Some(true));
790    }
791
792    #[test]
793    fn test_nullable_helpers() {
794        assert_eq!(value_as_nullable_i64(&Value::Null), None);
795        assert_eq!(value_as_nullable_i64(&Value::I64(42)), Some(42));
796        assert_eq!(value_as_nullable_string(&Value::Null), None);
797        assert_eq!(
798            value_as_nullable_string(&Value::String("hi".into())),
799            Some("hi".into())
800        );
801    }
802
803    // ---- 完整流程测试 ----
804
805    #[test]
806    fn test_full_flow_queryable() {
807        // 模拟 SELECT id, name FROM users
808        let values = vec![Value::I64(1), Value::String("Alice".into())];
809        let row = UserRow::from_values(values).unwrap();
810        assert_eq!(
811            row,
812            UserRow {
813                id: 1,
814                name: "Alice".into()
815            }
816        );
817    }
818
819    #[test]
820    fn test_full_flow_from_row_with_extra_data() {
821        // 模拟 SELECT * FROM users(带额外列)
822        let mut map = HashMap::new();
823        map.insert("id".into(), Value::I64(7));
824        map.insert("name".into(), Value::String("Bob".into()));
825        map.insert("email".into(), Value::String("bob@example.com".into()));
826        map.insert("created_at".into(), Value::String("2026-01-01".into()));
827
828        let row = UserRow::from_row(map).unwrap();
829        assert_eq!(row.id, 7);
830        assert_eq!(row.name, "Bob");
831    }
832
833    // ---- Query 测试 ----
834
835    #[test]
836    fn test_query_new_and_sql() {
837        let q = Query::new("SELECT id, name FROM users");
838        assert_eq!(q.sql(), "SELECT id, name FROM users");
839    }
840
841    #[test]
842    fn test_query_from_str() {
843        let q = Query::new("SELECT 1");
844        assert_eq!(q.sql(), "SELECT 1");
845    }
846
847    #[test]
848    fn test_query_with_params() {
849        let q = Query::new("SELECT * FROM users WHERE id = ? AND name = ?");
850        assert!(q.sql().contains("WHERE id = ?"));
851    }
852
853    // ---- QueryAs<T> 测试 ----
854
855    #[test]
856    fn test_validate_columns_match() {
857        // 列名完全匹配
858        let actual = vec!["id", "name"];
859        let expected = vec!["id", "name"];
860        assert!(validate_columns(&actual, &expected).is_ok());
861    }
862
863    #[test]
864    fn test_validate_columns_missing() {
865        // DB 缺少 struct 期望的列
866        let actual = vec!["id"];
867        let expected = vec!["id", "name"];
868        let result = validate_columns(&actual, &expected);
869        assert!(result.is_err());
870        let err = result.unwrap_err().to_string();
871        assert!(err.contains("missing"));
872    }
873
874    #[test]
875    fn test_validate_columns_extra() {
876        // DB 返回了 struct 未定义的列
877        let actual = vec!["id", "name", "age"];
878        let expected = vec!["id", "name"];
879        let result = validate_columns(&actual, &expected);
880        assert!(result.is_err());
881        let err = result.unwrap_err().to_string();
882        assert!(err.contains("column mismatch"));
883    }
884
885    #[test]
886    fn test_query_as_new_and_sql() {
887        let q = QueryAs::<UserRow>::new("SELECT id, name FROM users");
888        assert_eq!(q.sql(), "SELECT id, name FROM users");
889    }
890
891    #[test]
892    fn test_query_as_fetch_all_empty() {
893        // QueryAs::fetch_all 需要真实 Connection,这里只验证构造不 panic
894        let _q = QueryAs::<UserRow>::new("SELECT 1");
895    }
896
897    #[test]
898    fn test_query_as_fetch_optional_empty_result() {
899        // 验证 QueryAs 类型参数约束编译通过
900        let _q = QueryAs::<(i64, String)>::new("SELECT id, name FROM t");
901    }
902
903    // ---- validate_column_types 测试(P0-2 运行时列类型验证) ----
904
905    #[test]
906    fn test_validate_column_types_compatible() {
907        // DB 返回 i64,struct 期望 BIGINT → 兼容
908        let mut row = HashMap::new();
909        row.insert("id".to_string(), Value::I64(42));
910        row.insert("name".to_string(), Value::String("Alice".into()));
911        let expected = vec![("id", "BIGINT"), ("name", "TEXT")];
912        assert!(validate_column_types(&row, &expected).is_ok());
913    }
914
915    #[test]
916    fn test_validate_column_types_case_insensitive() {
917        // 大小写不敏感:bigint vs BIGINT
918        let mut row = HashMap::new();
919        row.insert("id".to_string(), Value::I64(1));
920        let expected = vec![("id", "bigint")];
921        assert!(validate_column_types(&row, &expected).is_ok());
922    }
923
924    #[test]
925    fn test_validate_column_types_null_skipped() {
926        // NULL 值跳过类型校验
927        let mut row = HashMap::new();
928        row.insert("id".to_string(), Value::Null);
929        let expected = vec![("id", "TEXT")]; // 即使期望 TEXT,NULL 也不报错
930        assert!(validate_column_types(&row, &expected).is_ok());
931    }
932
933    #[test]
934    fn test_validate_column_types_missing_column_skipped() {
935        // 列不存在时跳过(由 validate_columns 处理)
936        let row = HashMap::new();
937        let expected = vec![("missing", "BIGINT")];
938        assert!(validate_column_types(&row, &expected).is_ok());
939    }
940
941    #[test]
942    fn test_validate_column_types_type_mismatch() {
943        // DB 返回 I64,struct 期望 TEXT → 不兼容
944        let mut row = HashMap::new();
945        row.insert("id".to_string(), Value::I64(42));
946        let expected = vec![("id", "TEXT")];
947        let result = validate_column_types(&row, &expected);
948        assert!(result.is_err());
949        let err = result.unwrap_err().to_string();
950        assert!(err.contains("TYPE mismatch"));
951        assert!(err.contains("id"));
952    }
953
954    #[test]
955    fn test_validate_column_types_bool_vs_int_mismatch() {
956        // DB 返回 Bool,struct 期望 INT → 不兼容
957        let mut row = HashMap::new();
958        row.insert("flag".to_string(), Value::Bool(true));
959        let expected = vec![("flag", "INT")];
960        let result = validate_column_types(&row, &expected);
961        assert!(result.is_err());
962    }
963
964    #[test]
965    fn test_validate_column_types_unknown_actual_skipped() {
966        // 实际类型未知时不报错(宽松模式)
967        let mut row = HashMap::new();
968        row.insert("data".to_string(), Value::String("x".into()));
969        let expected = vec![("data", "UNKNOWN_CUSTOM_TYPE")];
970        // expected 映射为 Unknown → 不报错
971        assert!(validate_column_types(&row, &expected).is_ok());
972    }
973
974    #[test]
975    fn test_value_to_sql_type_name_coverage() {
976        // 覆盖所有 Value 变体
977        use crate::value::Value;
978        assert_eq!(super::value_to_sql_type_name(&Value::Bool(true)), "BOOLEAN");
979        assert_eq!(super::value_to_sql_type_name(&Value::I64(1)), "BIGINT");
980        assert_eq!(super::value_to_sql_type_name(&Value::F64(1.0)), "DOUBLE");
981        assert_eq!(
982            super::value_to_sql_type_name(&Value::Decimal("1.0".into())),
983            "DECIMAL"
984        );
985        assert_eq!(
986            super::value_to_sql_type_name(&Value::Bytes(vec![1, 2])),
987            "BLOB"
988        );
989        assert_eq!(super::value_to_sql_type_name(&Value::Null), "NULL");
990    }
991}