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        #[cfg(feature = "perf-box-str")]
549        Value::BoxedStr(_) => "VARCHAR",
550        Value::Bytes(_) => "BLOB",
551        Value::Array(_) | Value::Object(_) => "JSON",
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558
559    // ---- 测试用结构体 ----
560    // 注:sz-orm-core 内部测试模块不能使用 #[derive(FromRow)] 宏,
561    // 因为宏生成代码引用 ::sz_orm_core 绝对路径,在 crate 自身内部测试中不可用。
562    // 内部测试保留手动实现;外部测试(tests/)使用宏生成方案。
563    // 详见 tests/contracts/queryable_contract.rs 的宏迁移示例。
564
565    #[derive(Debug, Default, PartialEq)]
566    struct UserRow {
567        id: i64,
568        name: String,
569    }
570
571    impl Queryable for UserRow {
572        fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
573            if values.len() != 2 {
574                return Err(QueryError::ColumnCountMismatch {
575                    expected: 2,
576                    actual: values.len(),
577                });
578            }
579            let id = values[0].as_i64().ok_or(QueryError::TypeMismatch {
580                column: "0".into(),
581                expected: "i64",
582            })?;
583            let name = values[1]
584                .as_str()
585                .ok_or(QueryError::TypeMismatch {
586                    column: "1".into(),
587                    expected: "String",
588                })?
589                .to_string();
590            Ok(UserRow { id, name })
591        }
592    }
593
594    impl FromRow for UserRow {
595        fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError> {
596            let id = row
597                .get("id")
598                .ok_or(QueryError::MissingColumn { column: "id" })?
599                .as_i64()
600                .ok_or(QueryError::TypeMismatch {
601                    column: "id".into(),
602                    expected: "i64",
603                })?;
604            let name = row
605                .get("name")
606                .ok_or(QueryError::MissingColumn { column: "name" })?
607                .as_str()
608                .ok_or(QueryError::TypeMismatch {
609                    column: "name".into(),
610                    expected: "String",
611                })?
612                .to_string();
613            Ok(UserRow { id, name })
614        }
615    }
616
617    // ---- QueryError 测试 ----
618
619    #[test]
620    fn test_query_error_display() {
621        let e = QueryError::ColumnCountMismatch {
622            expected: 3,
623            actual: 2,
624        };
625        assert!(format!("{}", e).contains("3"));
626        assert!(format!("{}", e).contains("2"));
627
628        let e = QueryError::TypeMismatch {
629            column: "age".into(),
630            expected: "i64",
631        };
632        assert!(format!("{}", e).contains("age"));
633
634        let e = QueryError::MissingColumn { column: "id" };
635        assert!(format!("{}", e).contains("id"));
636
637        let e = QueryError::Custom("custom".into());
638        assert_eq!(format!("{}", e), "custom");
639    }
640
641    // ---- RowDesc 测试 ----
642
643    #[test]
644    fn test_row_desc_basic() {
645        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
646        assert_eq!(desc.len(), 3);
647        assert!(!desc.is_empty());
648        assert_eq!(desc.index_of("name"), Some(1));
649        assert_eq!(desc.index_of("missing"), None);
650    }
651
652    #[test]
653    fn test_row_desc_empty() {
654        let desc = RowDesc::new(vec![]);
655        assert!(desc.is_empty());
656        assert_eq!(desc.len(), 0);
657    }
658
659    // ---- Queryable for UserRow 测试 ----
660
661    #[test]
662    fn test_user_row_from_values_success() {
663        let row =
664            UserRow::from_values(vec![Value::I64(42), Value::String("Alice".into())]).unwrap();
665        assert_eq!(row.id, 42);
666        assert_eq!(row.name, "Alice");
667    }
668
669    #[test]
670    fn test_user_row_from_values_count_mismatch() {
671        let result = UserRow::from_values(vec![Value::I64(42)]);
672        assert!(matches!(
673            result,
674            Err(QueryError::ColumnCountMismatch {
675                expected: 2,
676                actual: 1
677            })
678        ));
679    }
680
681    #[test]
682    fn test_user_row_from_values_type_mismatch() {
683        let result = UserRow::from_values(vec![
684            Value::String("not_an_int".into()),
685            Value::String("Alice".into()),
686        ]);
687        assert!(matches!(result, Err(QueryError::TypeMismatch { .. })));
688    }
689
690    #[test]
691    fn test_user_row_from_values_with_desc() {
692        let desc = RowDesc::new(vec!["id".into(), "name".into()]);
693        let row =
694            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc)
695                .unwrap();
696        assert_eq!(row.id, 1);
697        assert_eq!(row.name, "Bob");
698    }
699
700    #[test]
701    fn test_user_row_from_values_with_desc_mismatch() {
702        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
703        let result =
704            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc);
705        assert!(matches!(
706            result,
707            Err(QueryError::ColumnCountMismatch { .. })
708        ));
709    }
710
711    // ---- FromRow for UserRow 测试 ----
712
713    #[test]
714    fn test_user_row_from_row_success() {
715        let mut map = HashMap::new();
716        map.insert("id".into(), Value::I64(99));
717        map.insert("name".into(), Value::String("Charlie".into()));
718        let row = UserRow::from_row(map).unwrap();
719        assert_eq!(row.id, 99);
720        assert_eq!(row.name, "Charlie");
721    }
722
723    #[test]
724    fn test_user_row_from_row_missing_column() {
725        let mut map = HashMap::new();
726        map.insert("id".into(), Value::I64(99));
727        // 缺少 name
728        let result = UserRow::from_row(map);
729        assert!(matches!(
730            result,
731            Err(QueryError::MissingColumn { column: "name" })
732        ));
733    }
734
735    #[test]
736    fn test_user_row_from_row_extra_columns_ignored() {
737        let mut map = HashMap::new();
738        map.insert("id".into(), Value::I64(1));
739        map.insert("name".into(), Value::String("X".into()));
740        map.insert("extra".into(), Value::String("ignored".into()));
741        let row = UserRow::from_row(map).unwrap();
742        assert_eq!(row.id, 1);
743    }
744
745    // ---- 基础类型 Queryable 实现 ----
746
747    #[test]
748    fn test_value_queryable_single() {
749        let v = Value::from_values(vec![Value::I64(42)]).unwrap();
750        assert_eq!(v.as_i64(), Some(42));
751    }
752
753    #[test]
754    fn test_value_queryable_count_mismatch() {
755        let result = Value::from_values(vec![Value::I64(1), Value::I64(2)]);
756        assert!(matches!(
757            result,
758            Err(QueryError::ColumnCountMismatch { .. })
759        ));
760    }
761
762    #[test]
763    fn test_tuple_2_queryable() {
764        let (a, b) =
765            <(Value, Value)>::from_values(vec![Value::I64(1), Value::String("hello".into())])
766                .unwrap();
767        assert_eq!(a.as_i64(), Some(1));
768        assert_eq!(b.as_str(), Some("hello"));
769    }
770
771    #[test]
772    fn test_tuple_3_queryable() {
773        let (a, b, c) = <(Value, Value, Value)>::from_values(vec![
774            Value::I64(1),
775            Value::String("two".into()),
776            Value::F64(3.5),
777        ])
778        .unwrap();
779        assert_eq!(a.as_i64(), Some(1));
780        assert_eq!(b.as_str(), Some("two"));
781        assert_eq!(c.as_f64(), Some(3.5));
782    }
783
784    // ---- 辅助函数测试 ----
785
786    #[test]
787    fn test_value_helpers() {
788        assert_eq!(value_as_i64(&Value::I64(42)), Some(42));
789        assert_eq!(value_as_i64(&Value::String("42".into())), Some(42));
790        assert_eq!(value_as_f64(&Value::F64(3.5)), Some(3.5));
791        assert_eq!(
792            value_as_string(&Value::String("hi".into())),
793            Some("hi".into())
794        );
795        assert_eq!(value_as_bool(&Value::Bool(true)), Some(true));
796    }
797
798    #[test]
799    fn test_nullable_helpers() {
800        assert_eq!(value_as_nullable_i64(&Value::Null), None);
801        assert_eq!(value_as_nullable_i64(&Value::I64(42)), Some(42));
802        assert_eq!(value_as_nullable_string(&Value::Null), None);
803        assert_eq!(
804            value_as_nullable_string(&Value::String("hi".into())),
805            Some("hi".into())
806        );
807    }
808
809    // ---- 完整流程测试 ----
810
811    #[test]
812    fn test_full_flow_queryable() {
813        // 模拟 SELECT id, name FROM users
814        let values = vec![Value::I64(1), Value::String("Alice".into())];
815        let row = UserRow::from_values(values).unwrap();
816        assert_eq!(
817            row,
818            UserRow {
819                id: 1,
820                name: "Alice".into()
821            }
822        );
823    }
824
825    #[test]
826    fn test_full_flow_from_row_with_extra_data() {
827        // 模拟 SELECT * FROM users(带额外列)
828        let mut map = HashMap::new();
829        map.insert("id".into(), Value::I64(7));
830        map.insert("name".into(), Value::String("Bob".into()));
831        map.insert("email".into(), Value::String("bob@example.com".into()));
832        map.insert("created_at".into(), Value::String("2026-01-01".into()));
833
834        let row = UserRow::from_row(map).unwrap();
835        assert_eq!(row.id, 7);
836        assert_eq!(row.name, "Bob");
837    }
838
839    // ---- Query 测试 ----
840
841    #[test]
842    fn test_query_new_and_sql() {
843        let q = Query::new("SELECT id, name FROM users");
844        assert_eq!(q.sql(), "SELECT id, name FROM users");
845    }
846
847    #[test]
848    fn test_query_from_str() {
849        let q = Query::new("SELECT 1");
850        assert_eq!(q.sql(), "SELECT 1");
851    }
852
853    #[test]
854    fn test_query_with_params() {
855        let q = Query::new("SELECT * FROM users WHERE id = ? AND name = ?");
856        assert!(q.sql().contains("WHERE id = ?"));
857    }
858
859    // ---- QueryAs<T> 测试 ----
860
861    #[test]
862    fn test_validate_columns_match() {
863        // 列名完全匹配
864        let actual = vec!["id", "name"];
865        let expected = vec!["id", "name"];
866        assert!(validate_columns(&actual, &expected).is_ok());
867    }
868
869    #[test]
870    fn test_validate_columns_missing() {
871        // DB 缺少 struct 期望的列
872        let actual = vec!["id"];
873        let expected = vec!["id", "name"];
874        let result = validate_columns(&actual, &expected);
875        assert!(result.is_err());
876        let err = result.unwrap_err().to_string();
877        assert!(err.contains("missing"));
878    }
879
880    #[test]
881    fn test_validate_columns_extra() {
882        // DB 返回了 struct 未定义的列
883        let actual = vec!["id", "name", "age"];
884        let expected = vec!["id", "name"];
885        let result = validate_columns(&actual, &expected);
886        assert!(result.is_err());
887        let err = result.unwrap_err().to_string();
888        assert!(err.contains("column mismatch"));
889    }
890
891    #[test]
892    fn test_query_as_new_and_sql() {
893        let q = QueryAs::<UserRow>::new("SELECT id, name FROM users");
894        assert_eq!(q.sql(), "SELECT id, name FROM users");
895    }
896
897    #[test]
898    fn test_query_as_fetch_all_empty() {
899        // QueryAs::fetch_all 需要真实 Connection,这里只验证构造不 panic
900        let _q = QueryAs::<UserRow>::new("SELECT 1");
901    }
902
903    #[test]
904    fn test_query_as_fetch_optional_empty_result() {
905        // 验证 QueryAs 类型参数约束编译通过
906        let _q = QueryAs::<(i64, String)>::new("SELECT id, name FROM t");
907    }
908
909    // ---- validate_column_types 测试(P0-2 运行时列类型验证) ----
910
911    #[test]
912    fn test_validate_column_types_compatible() {
913        // DB 返回 i64,struct 期望 BIGINT → 兼容
914        let mut row = HashMap::new();
915        row.insert("id".to_string(), Value::I64(42));
916        row.insert("name".to_string(), Value::String("Alice".into()));
917        let expected = vec![("id", "BIGINT"), ("name", "TEXT")];
918        assert!(validate_column_types(&row, &expected).is_ok());
919    }
920
921    #[test]
922    fn test_validate_column_types_case_insensitive() {
923        // 大小写不敏感:bigint vs BIGINT
924        let mut row = HashMap::new();
925        row.insert("id".to_string(), Value::I64(1));
926        let expected = vec![("id", "bigint")];
927        assert!(validate_column_types(&row, &expected).is_ok());
928    }
929
930    #[test]
931    fn test_validate_column_types_null_skipped() {
932        // NULL 值跳过类型校验
933        let mut row = HashMap::new();
934        row.insert("id".to_string(), Value::Null);
935        let expected = vec![("id", "TEXT")]; // 即使期望 TEXT,NULL 也不报错
936        assert!(validate_column_types(&row, &expected).is_ok());
937    }
938
939    #[test]
940    fn test_validate_column_types_missing_column_skipped() {
941        // 列不存在时跳过(由 validate_columns 处理)
942        let row = HashMap::new();
943        let expected = vec![("missing", "BIGINT")];
944        assert!(validate_column_types(&row, &expected).is_ok());
945    }
946
947    #[test]
948    fn test_validate_column_types_type_mismatch() {
949        // DB 返回 I64,struct 期望 TEXT → 不兼容
950        let mut row = HashMap::new();
951        row.insert("id".to_string(), Value::I64(42));
952        let expected = vec![("id", "TEXT")];
953        let result = validate_column_types(&row, &expected);
954        assert!(result.is_err());
955        let err = result.unwrap_err().to_string();
956        assert!(err.contains("TYPE mismatch"));
957        assert!(err.contains("id"));
958    }
959
960    #[test]
961    fn test_validate_column_types_bool_vs_int_mismatch() {
962        // DB 返回 Bool,struct 期望 INT → 不兼容
963        let mut row = HashMap::new();
964        row.insert("flag".to_string(), Value::Bool(true));
965        let expected = vec![("flag", "INT")];
966        let result = validate_column_types(&row, &expected);
967        assert!(result.is_err());
968    }
969
970    #[test]
971    fn test_validate_column_types_unknown_actual_skipped() {
972        // 实际类型未知时不报错(宽松模式)
973        let mut row = HashMap::new();
974        row.insert("data".to_string(), Value::String("x".into()));
975        let expected = vec![("data", "UNKNOWN_CUSTOM_TYPE")];
976        // expected 映射为 Unknown → 不报错
977        assert!(validate_column_types(&row, &expected).is_ok());
978    }
979
980    #[test]
981    fn test_value_to_sql_type_name_coverage() {
982        // 覆盖所有 Value 变体
983        use crate::value::Value;
984        assert_eq!(super::value_to_sql_type_name(&Value::Bool(true)), "BOOLEAN");
985        assert_eq!(super::value_to_sql_type_name(&Value::I64(1)), "BIGINT");
986        assert_eq!(super::value_to_sql_type_name(&Value::F64(1.0)), "DOUBLE");
987        assert_eq!(
988            super::value_to_sql_type_name(&Value::Decimal("1.0".into())),
989            "DECIMAL"
990        );
991        assert_eq!(
992            super::value_to_sql_type_name(&Value::Bytes(vec![1, 2])),
993            "BLOB"
994        );
995        assert_eq!(super::value_to_sql_type_name(&Value::Null), "NULL");
996    }
997}