Skip to main content

sz_orm_query/
queryable.rs

1//! derive(Queryable) — 从 SELECT 结果自动派生结构体(Diesel 风格)
2//!
3//! Diesel 通过 `#[derive(Queryable)]` 让结构体自动从 SQL 行反序列化。
4//! SZ-ORM 在 [`sz_orm_model::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_query::value::Value;
20//! use sz_orm_query::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 std::collections::HashMap;
54use sz_orm_model::Value;
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[0].clone())
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        Ok((values[0].clone(), values[1].clone()))
187    }
188}
189
190/// 三列查询结果
191impl Queryable for (Value, Value, Value) {
192    fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
193        if values.len() != 3 {
194            return Err(QueryError::ColumnCountMismatch {
195                expected: 3,
196                actual: values.len(),
197            });
198        }
199        Ok((values[0].clone(), values[1].clone(), values[2].clone()))
200    }
201}
202
203// ---- 辅助函数:从 Value 提取 Rust 类型 ----
204
205/// 提取 i64(支持 I8/I16/I32/I64/U8/U16/U32/U64/F32/F64/Bool/String 转换)
206pub fn value_as_i64(v: &Value) -> Option<i64> {
207    v.as_i64()
208}
209
210/// 提取 f64
211pub fn value_as_f64(v: &Value) -> Option<f64> {
212    v.as_f64()
213}
214
215/// 提取 String
216pub fn value_as_string(v: &Value) -> Option<String> {
217    v.as_str().map(|s| s.to_string())
218}
219
220/// 提取 bool
221pub fn value_as_bool(v: &Value) -> Option<bool> {
222    v.as_bool()
223}
224
225/// 提取 `Option<i64>`(Null 返回 None)
226pub fn value_as_nullable_i64(v: &Value) -> Option<i64> {
227    if v.is_null() {
228        None
229    } else {
230        v.as_i64()
231    }
232}
233
234/// 提取 `Option<String>`(Null 返回 None)
235pub fn value_as_nullable_string(v: &Value) -> Option<String> {
236    if v.is_null() {
237        None
238    } else {
239        v.as_str().map(|s| s.to_string())
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    // ---- 测试用结构体 ----
248
249    #[derive(Debug, Default, PartialEq)]
250    struct UserRow {
251        id: i64,
252        name: String,
253    }
254
255    impl Queryable for UserRow {
256        fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
257            if values.len() != 2 {
258                return Err(QueryError::ColumnCountMismatch {
259                    expected: 2,
260                    actual: values.len(),
261                });
262            }
263            let id = values[0].as_i64().ok_or(QueryError::TypeMismatch {
264                column: "0".into(),
265                expected: "i64",
266            })?;
267            let name = values[1]
268                .as_str()
269                .ok_or(QueryError::TypeMismatch {
270                    column: "1".into(),
271                    expected: "String",
272                })?
273                .to_string();
274            Ok(UserRow { id, name })
275        }
276    }
277
278    impl FromRow for UserRow {
279        fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError> {
280            let id = row
281                .get("id")
282                .ok_or(QueryError::MissingColumn { column: "id" })?
283                .as_i64()
284                .ok_or(QueryError::TypeMismatch {
285                    column: "id".into(),
286                    expected: "i64",
287                })?;
288            let name = row
289                .get("name")
290                .ok_or(QueryError::MissingColumn { column: "name" })?
291                .as_str()
292                .ok_or(QueryError::TypeMismatch {
293                    column: "name".into(),
294                    expected: "String",
295                })?
296                .to_string();
297            Ok(UserRow { id, name })
298        }
299    }
300
301    // ---- QueryError 测试 ----
302
303    #[test]
304    fn test_query_error_display() {
305        let e = QueryError::ColumnCountMismatch {
306            expected: 3,
307            actual: 2,
308        };
309        assert!(format!("{}", e).contains("3"));
310        assert!(format!("{}", e).contains("2"));
311
312        let e = QueryError::TypeMismatch {
313            column: "age".into(),
314            expected: "i64",
315        };
316        assert!(format!("{}", e).contains("age"));
317
318        let e = QueryError::MissingColumn { column: "id" };
319        assert!(format!("{}", e).contains("id"));
320
321        let e = QueryError::Custom("custom".into());
322        assert_eq!(format!("{}", e), "custom");
323    }
324
325    // ---- RowDesc 测试 ----
326
327    #[test]
328    fn test_row_desc_basic() {
329        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
330        assert_eq!(desc.len(), 3);
331        assert!(!desc.is_empty());
332        assert_eq!(desc.index_of("name"), Some(1));
333        assert_eq!(desc.index_of("missing"), None);
334    }
335
336    #[test]
337    fn test_row_desc_empty() {
338        let desc = RowDesc::new(vec![]);
339        assert!(desc.is_empty());
340        assert_eq!(desc.len(), 0);
341    }
342
343    // ---- Queryable for UserRow 测试 ----
344
345    #[test]
346    fn test_user_row_from_values_success() {
347        let row =
348            UserRow::from_values(vec![Value::I64(42), Value::String("Alice".into())]).unwrap();
349        assert_eq!(row.id, 42);
350        assert_eq!(row.name, "Alice");
351    }
352
353    #[test]
354    fn test_user_row_from_values_count_mismatch() {
355        let result = UserRow::from_values(vec![Value::I64(42)]);
356        assert!(matches!(
357            result,
358            Err(QueryError::ColumnCountMismatch {
359                expected: 2,
360                actual: 1
361            })
362        ));
363    }
364
365    #[test]
366    fn test_user_row_from_values_type_mismatch() {
367        let result = UserRow::from_values(vec![
368            Value::String("not_an_int".into()),
369            Value::String("Alice".into()),
370        ]);
371        assert!(matches!(result, Err(QueryError::TypeMismatch { .. })));
372    }
373
374    #[test]
375    fn test_user_row_from_values_with_desc() {
376        let desc = RowDesc::new(vec!["id".into(), "name".into()]);
377        let row =
378            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc)
379                .unwrap();
380        assert_eq!(row.id, 1);
381        assert_eq!(row.name, "Bob");
382    }
383
384    #[test]
385    fn test_user_row_from_values_with_desc_mismatch() {
386        let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
387        let result =
388            UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc);
389        assert!(matches!(
390            result,
391            Err(QueryError::ColumnCountMismatch { .. })
392        ));
393    }
394
395    // ---- FromRow for UserRow 测试 ----
396
397    #[test]
398    fn test_user_row_from_row_success() {
399        let mut map = HashMap::new();
400        map.insert("id".into(), Value::I64(99));
401        map.insert("name".into(), Value::String("Charlie".into()));
402        let row = UserRow::from_row(map).unwrap();
403        assert_eq!(row.id, 99);
404        assert_eq!(row.name, "Charlie");
405    }
406
407    #[test]
408    fn test_user_row_from_row_missing_column() {
409        let mut map = HashMap::new();
410        map.insert("id".into(), Value::I64(99));
411        // 缺少 name
412        let result = UserRow::from_row(map);
413        assert!(matches!(
414            result,
415            Err(QueryError::MissingColumn { column: "name" })
416        ));
417    }
418
419    #[test]
420    fn test_user_row_from_row_extra_columns_ignored() {
421        let mut map = HashMap::new();
422        map.insert("id".into(), Value::I64(1));
423        map.insert("name".into(), Value::String("X".into()));
424        map.insert("extra".into(), Value::String("ignored".into()));
425        let row = UserRow::from_row(map).unwrap();
426        assert_eq!(row.id, 1);
427    }
428
429    // ---- 基础类型 Queryable 实现 ----
430
431    #[test]
432    fn test_value_queryable_single() {
433        let v = Value::from_values(vec![Value::I64(42)]).unwrap();
434        assert_eq!(v.as_i64(), Some(42));
435    }
436
437    #[test]
438    fn test_value_queryable_count_mismatch() {
439        let result = Value::from_values(vec![Value::I64(1), Value::I64(2)]);
440        assert!(matches!(
441            result,
442            Err(QueryError::ColumnCountMismatch { .. })
443        ));
444    }
445
446    #[test]
447    fn test_tuple_2_queryable() {
448        let (a, b) =
449            <(Value, Value)>::from_values(vec![Value::I64(1), Value::String("hello".into())])
450                .unwrap();
451        assert_eq!(a.as_i64(), Some(1));
452        assert_eq!(b.as_str(), Some("hello"));
453    }
454
455    #[test]
456    fn test_tuple_3_queryable() {
457        let (a, b, c) = <(Value, Value, Value)>::from_values(vec![
458            Value::I64(1),
459            Value::String("two".into()),
460            Value::F64(3.5),
461        ])
462        .unwrap();
463        assert_eq!(a.as_i64(), Some(1));
464        assert_eq!(b.as_str(), Some("two"));
465        assert_eq!(c.as_f64(), Some(3.5));
466    }
467
468    // ---- 辅助函数测试 ----
469
470    #[test]
471    fn test_value_helpers() {
472        assert_eq!(value_as_i64(&Value::I64(42)), Some(42));
473        assert_eq!(value_as_i64(&Value::String("42".into())), Some(42));
474        assert_eq!(value_as_f64(&Value::F64(3.5)), Some(3.5));
475        assert_eq!(
476            value_as_string(&Value::String("hi".into())),
477            Some("hi".into())
478        );
479        assert_eq!(value_as_bool(&Value::Bool(true)), Some(true));
480    }
481
482    #[test]
483    fn test_nullable_helpers() {
484        assert_eq!(value_as_nullable_i64(&Value::Null), None);
485        assert_eq!(value_as_nullable_i64(&Value::I64(42)), Some(42));
486        assert_eq!(value_as_nullable_string(&Value::Null), None);
487        assert_eq!(
488            value_as_nullable_string(&Value::String("hi".into())),
489            Some("hi".into())
490        );
491    }
492
493    // ---- 完整流程测试 ----
494
495    #[test]
496    fn test_full_flow_queryable() {
497        // 模拟 SELECT id, name FROM users
498        let values = vec![Value::I64(1), Value::String("Alice".into())];
499        let row = UserRow::from_values(values).unwrap();
500        assert_eq!(
501            row,
502            UserRow {
503                id: 1,
504                name: "Alice".into()
505            }
506        );
507    }
508
509    #[test]
510    fn test_full_flow_from_row_with_extra_data() {
511        // 模拟 SELECT * FROM users(带额外列)
512        let mut map = HashMap::new();
513        map.insert("id".into(), Value::I64(7));
514        map.insert("name".into(), Value::String("Bob".into()));
515        map.insert("email".into(), Value::String("bob@example.com".into()));
516        map.insert("created_at".into(), Value::String("2026-01-01".into()));
517
518        let row = UserRow::from_row(map).unwrap();
519        assert_eq!(row.id, 7);
520        assert_eq!(row.name, "Bob");
521    }
522}