Skip to main content

sz_orm_core/
value.rs

1//! Value 类型定义
2//!
3//! 数据库操作的统一值表示
4
5use std::borrow::Cow;
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10/// 数据库值类型
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
12#[non_exhaustive]
13pub enum Value {
14    /// Null 值
15    #[default]
16    Null,
17
18    /// 布尔值
19    Bool(bool),
20
21    /// 8 位有符号整数
22    I8(i8),
23
24    /// 16 位有符号整数
25    I16(i16),
26
27    /// 32 位有符号整数
28    I32(i32),
29
30    /// 64 位有符号整数
31    I64(i64),
32
33    /// 8 位无符号整数
34    U8(u8),
35
36    /// 16 位无符号整数
37    U16(u16),
38
39    /// 32 位无符号整数
40    U32(u32),
41
42    /// 64 位无符号整数
43    U64(u64),
44
45    /// 32 位浮点数
46    F32(f32),
47
48    /// 64 位浮点数
49    F64(f64),
50
51    /// 字符串值
52    String(String),
53
54    /// 字节值
55    Bytes(Vec<u8>),
56
57    /// UUID 值(以字符串形式存储)
58    Uuid(String),
59
60    /// 日期值(ISO 8601 格式)
61    Date(String),
62
63    /// 日期时间值(ISO 8601 格式)
64    DateTime(String),
65
66    /// 时间值
67    Time(String),
68
69    /// JSON 值
70    Json(String),
71
72    /// 值数组
73    Array(Vec<Value>),
74
75    /// 基于 HashMap 的对象值,用于存储关系数据
76    Object(std::collections::HashMap<String, Value>),
77}
78
79impl Value {
80    /// 判断是否为 null
81    pub fn is_null(&self) -> bool {
82        matches!(self, Value::Null)
83    }
84
85    /// 判断是否为布尔值
86    pub fn is_bool(&self) -> bool {
87        matches!(self, Value::Bool(_))
88    }
89
90    /// 判断是否为整数
91    pub fn is_i64(&self) -> bool {
92        matches!(self, Value::I64(_))
93    }
94
95    /// 判断是否为浮点数
96    pub fn is_f64(&self) -> bool {
97        matches!(self, Value::F64(_))
98    }
99
100    /// 判断是否为字符串
101    pub fn is_string(&self) -> bool {
102        matches!(self, Value::String(_))
103    }
104
105    /// 判断是否为字节
106    pub fn is_bytes(&self) -> bool {
107        matches!(self, Value::Bytes(_))
108    }
109
110    /// 判断是否为对象
111    pub fn is_object(&self) -> bool {
112        matches!(self, Value::Object(_))
113    }
114
115    /// 从 HashMap 构造 Value
116    pub fn from_map(map: std::collections::HashMap<String, Value>) -> Self {
117        Value::Object(map)
118    }
119
120    /// 若可能,返回 &str 形式的值
121    pub fn as_str(&self) -> Option<&str> {
122        match self {
123            Value::String(s) => Some(s),
124            _ => None,
125        }
126    }
127
128    /// 若可能,返回 i64 形式的值
129    /// 支持 F32/F64 → i64 的有损转换(数据库 SUM/AVG 等聚合函数常返回浮点类型)
130    /// U64 → i64 使用 `try_from`,超过 `i64::MAX` 时返回 `None`(避免静默截断为负数)
131    pub fn as_i64(&self) -> Option<i64> {
132        match self {
133            Value::I8(v) => Some(*v as i64),
134            Value::I16(v) => Some(*v as i64),
135            Value::I32(v) => Some(*v as i64),
136            Value::I64(v) => Some(*v),
137            Value::U8(v) => Some(*v as i64),
138            Value::U16(v) => Some(*v as i64),
139            Value::U32(v) => Some(*v as i64),
140            Value::U64(v) => i64::try_from(*v).ok(),
141            Value::F32(v) => Some(*v as i64),
142            Value::F64(v) => Some(*v as i64),
143            Value::Bool(v) => Some(if *v { 1 } else { 0 }),
144            Value::String(s) => s.parse::<i64>().ok(),
145            _ => None,
146        }
147    }
148
149    /// 若可能,返回 f64 形式的值
150    /// 支持整数类型 → f64 的转换
151    pub fn as_f64(&self) -> Option<f64> {
152        match self {
153            Value::F32(v) => Some(*v as f64),
154            Value::F64(v) => Some(*v),
155            Value::I8(v) => Some(*v as f64),
156            Value::I16(v) => Some(*v as f64),
157            Value::I32(v) => Some(*v as f64),
158            Value::I64(v) => Some(*v as f64),
159            Value::U8(v) => Some(*v as f64),
160            Value::U16(v) => Some(*v as f64),
161            Value::U32(v) => Some(*v as f64),
162            Value::U64(v) => Some(*v as f64),
163            Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
164            _ => None,
165        }
166    }
167
168    /// 若可能,返回 bool 形式的值
169    /// 支持整数(非 0 即真)、浮点(非 0.0 即真)、字符串("1"/"true"/"yes"/"on" 为真)的转换
170    pub fn as_bool(&self) -> Option<bool> {
171        match self {
172            Value::Bool(v) => Some(*v),
173            Value::I8(v) => Some(*v != 0),
174            Value::I16(v) => Some(*v != 0),
175            Value::I32(v) => Some(*v != 0),
176            Value::I64(v) => Some(*v != 0),
177            Value::U8(v) => Some(*v != 0),
178            Value::U16(v) => Some(*v != 0),
179            Value::U32(v) => Some(*v != 0),
180            Value::U64(v) => Some(*v != 0),
181            Value::F32(v) => Some(*v != 0.0),
182            Value::F64(v) => Some(*v != 0.0),
183            Value::String(s) => match s.to_lowercase().as_str() {
184                "1" | "true" | "yes" | "on" => Some(true),
185                "0" | "false" | "no" | "off" => Some(false),
186                _ => None,
187            },
188            Value::Null => Some(false),
189            _ => None,
190        }
191    }
192
193    /// 若可能,返回字节切片形式(&[u8])的值
194    /// 字符串类型会返回其 UTF-8 字节
195    pub fn as_bytes(&self) -> Option<&[u8]> {
196        match self {
197            Value::Bytes(v) => Some(v),
198            Value::String(s) => Some(s.as_bytes()),
199            _ => None,
200        }
201    }
202
203    /// 转换为 SQL 参数字符串(用于直接拼接 SQL 语句)
204    /// 字符串类型会进行转义并加引号;字节类型转换为 X'..' 形式
205    ///
206    /// # 安全性警告
207    ///
208    /// 本方法使用简单的 `'` → `''` 转义,对 PostgreSQL/SQLite 默认配置安全,
209    /// 但对 MySQL 默认配置(backslash 是转义字符)不安全:含 `\` 的字符串
210    /// 可能被 MySQL 误解。**生产环境请使用 [`Value::to_param_with_dialect`]**
211    /// 以获得方言感知的转义。
212    pub fn to_param(&self) -> Cow<'_, str> {
213        match self {
214            Value::Null => Cow::Borrowed("NULL"),
215            Value::Bool(b) => Cow::Owned(if *b { "TRUE" } else { "FALSE" }.to_string()),
216            Value::I8(v) => Cow::Owned(v.to_string()),
217            Value::I16(v) => Cow::Owned(v.to_string()),
218            Value::I32(v) => Cow::Owned(v.to_string()),
219            Value::I64(v) => Cow::Owned(v.to_string()),
220            Value::U8(v) => Cow::Owned(v.to_string()),
221            Value::U16(v) => Cow::Owned(v.to_string()),
222            Value::U32(v) => Cow::Owned(v.to_string()),
223            Value::U64(v) => Cow::Owned(v.to_string()),
224            Value::F32(v) => Cow::Owned(v.to_string()),
225            Value::F64(v) => Cow::Owned(v.to_string()),
226            Value::String(s) => Cow::Owned(format!("'{}'", escape_string(s))),
227            Value::Bytes(b) => Cow::Owned(format!("X'{}'", hex_encode(b))),
228            Value::Uuid(s) => Cow::Owned(format!("'{}'", escape_string(s))),
229            Value::Date(s) => Cow::Owned(format!("'{}'", escape_string(s))),
230            Value::DateTime(s) => Cow::Owned(format!("'{}'", escape_string(s))),
231            Value::Time(s) => Cow::Owned(format!("'{}'", escape_string(s))),
232            Value::Json(s) => Cow::Owned(format!("'{}'", escape_string(s))),
233            Value::Array(arr) => {
234                let params: Vec<String> = arr.iter().map(|v| v.to_param().into_owned()).collect();
235                Cow::Owned(format!("({})", params.join(", ")))
236            }
237            Value::Object(_) => Cow::Borrowed("NULL"),
238        }
239    }
240
241    /// v0.2.2 修复 H-1:方言感知的 SQL 参数转换
242    ///
243    /// 与 [`to_param`](Self::to_param) 的区别:字符串类型使用 `dialect.escape_string()`
244    /// 而非简单的 `'` → `''` 转义,确保在所有方言下都安全:
245    ///
246    /// - **MySQL**:转义 `\`、`'`、`\0`、`\n`、`\r`、`\t`、`\x1a`
247    /// - **PostgreSQL**:仅转义 `'`(依赖 `standard_conforming_strings=on` 默认配置)
248    /// - **SQLite**:仅转义 `'`
249    ///
250    /// # 推荐用法
251    ///
252    /// ```ignore
253    /// use sz_orm_core::{DbType, get_dialect};
254    /// let dialect = get_dialect(DbType::MySQL)?;
255    /// let v = Value::String("hello\\nworld".to_string());
256    /// let param = v.to_param_with_dialect(&**dialect);
257    /// ```
258    pub fn to_param_with_dialect(&self, dialect: &dyn crate::dialect::Dialect) -> Cow<'_, str> {
259        match self {
260            Value::Null => Cow::Borrowed("NULL"),
261            Value::Bool(b) => Cow::Owned(if *b { "TRUE" } else { "FALSE" }.to_string()),
262            Value::I8(v) => Cow::Owned(v.to_string()),
263            Value::I16(v) => Cow::Owned(v.to_string()),
264            Value::I32(v) => Cow::Owned(v.to_string()),
265            Value::I64(v) => Cow::Owned(v.to_string()),
266            Value::U8(v) => Cow::Owned(v.to_string()),
267            Value::U16(v) => Cow::Owned(v.to_string()),
268            Value::U32(v) => Cow::Owned(v.to_string()),
269            Value::U64(v) => Cow::Owned(v.to_string()),
270            Value::F32(v) => Cow::Owned(v.to_string()),
271            Value::F64(v) => Cow::Owned(v.to_string()),
272            Value::String(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
273            Value::Bytes(b) => Cow::Owned(format!("X'{}'", hex_encode(b))),
274            Value::Uuid(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
275            Value::Date(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
276            Value::DateTime(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
277            Value::Time(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
278            Value::Json(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
279            Value::Array(arr) => {
280                let params: Vec<String> = arr
281                    .iter()
282                    .map(|v| v.to_param_with_dialect(dialect).into_owned())
283                    .collect();
284                Cow::Owned(format!("({})", params.join(", ")))
285            }
286            Value::Object(_) => Cow::Borrowed("NULL"),
287        }
288    }
289
290    /// 从任何实现了 `Into<Value>` 的类型构造 Value
291    pub fn from<T: Into<Value>>(v: T) -> Self {
292        v.into()
293    }
294}
295
296impl fmt::Display for Value {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        match self {
299            Value::Null => write!(f, "NULL"),
300            Value::Bool(b) => write!(f, "{}", b),
301            Value::I8(v) => write!(f, "{}", v),
302            Value::I16(v) => write!(f, "{}", v),
303            Value::I32(v) => write!(f, "{}", v),
304            Value::I64(v) => write!(f, "{}", v),
305            Value::U8(v) => write!(f, "{}", v),
306            Value::U16(v) => write!(f, "{}", v),
307            Value::U32(v) => write!(f, "{}", v),
308            Value::U64(v) => write!(f, "{}", v),
309            Value::F32(v) => write!(f, "{}", v),
310            Value::F64(v) => write!(f, "{}", v),
311            Value::String(v) => write!(f, "'{}'", v),
312            Value::Bytes(v) => write!(f, "X'{}'", hex_encode(v)),
313            Value::Uuid(v) => write!(f, "'{}'", v),
314            Value::Date(v) => write!(f, "'{}'", v),
315            Value::DateTime(v) => write!(f, "'{}'", v),
316            Value::Time(v) => write!(f, "'{}'", v),
317            Value::Json(v) => write!(f, "'{}'", v),
318            Value::Array(v) => {
319                let items: Vec<String> = v.iter().map(|i| format!("{}", i)).collect();
320                write!(f, "({})", items.join(", "))
321            }
322            Value::Object(map) => {
323                let items: Vec<String> = map.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
324                write!(f, "{{{}}}", items.join(", "))
325            }
326        }
327    }
328}
329
330impl From<()> for Value {
331    fn from(_: ()) -> Self {
332        Value::Null
333    }
334}
335
336impl From<bool> for Value {
337    fn from(v: bool) -> Self {
338        Value::Bool(v)
339    }
340}
341
342impl From<i8> for Value {
343    fn from(v: i8) -> Self {
344        Value::I8(v)
345    }
346}
347
348impl From<i16> for Value {
349    fn from(v: i16) -> Self {
350        Value::I16(v)
351    }
352}
353
354impl From<i32> for Value {
355    fn from(v: i32) -> Self {
356        Value::I32(v)
357    }
358}
359
360impl From<i64> for Value {
361    fn from(v: i64) -> Self {
362        Value::I64(v)
363    }
364}
365
366impl From<u8> for Value {
367    fn from(v: u8) -> Self {
368        Value::U8(v)
369    }
370}
371
372impl From<u16> for Value {
373    fn from(v: u16) -> Self {
374        Value::U16(v)
375    }
376}
377
378impl From<u32> for Value {
379    fn from(v: u32) -> Self {
380        Value::U32(v)
381    }
382}
383
384impl From<u64> for Value {
385    fn from(v: u64) -> Self {
386        Value::U64(v)
387    }
388}
389
390impl From<f32> for Value {
391    fn from(v: f32) -> Self {
392        Value::F32(v)
393    }
394}
395
396impl From<f64> for Value {
397    fn from(v: f64) -> Self {
398        Value::F64(v)
399    }
400}
401
402impl From<String> for Value {
403    fn from(v: String) -> Self {
404        Value::String(v)
405    }
406}
407
408impl From<&str> for Value {
409    fn from(v: &str) -> Self {
410        Value::String(v.to_string())
411    }
412}
413
414impl From<Vec<u8>> for Value {
415    fn from(v: Vec<u8>) -> Self {
416        Value::Bytes(v)
417    }
418}
419
420impl From<&[u8]> for Value {
421    fn from(v: &[u8]) -> Self {
422        Value::Bytes(v.to_vec())
423    }
424}
425
426impl From<Vec<Value>> for Value {
427    fn from(v: Vec<Value>) -> Self {
428        Value::Array(v)
429    }
430}
431
432/// 字符串字面量转义(v0.2.1 修复 Critical D-1)
433///
434/// # 旧实现的问题
435///
436/// 旧实现同时使用 `'` → `''`(标准 SQL)和 `\` → `\\`(MySQL 风格)转义,
437/// 导致在 PostgreSQL/SQLite 等不把 `\` 作为转义字符的方言下数据完整性受损
438/// (写入 `\\n` 字面量而非 `\n`)。
439///
440/// # 新实现
441///
442/// 只使用标准 SQL 转义:`'` → `''`。
443///
444/// - **SQL 注入防御**:`'` 被转义为 `''`,攻击者无法突破字符串字面量
445/// - **数据完整性**:在所有方言(MySQL/PG/SQLite/Oracle)下数据保持原样
446/// - **MySQL 兼容性**:MySQL 默认把 `\` 作为转义字符,但我们不主动转义 `\`,
447///   所以写入的 `\` 会被 MySQL 解析为字面 `\`(与 PG/SQLite 一致)
448///
449/// # 注意
450///
451/// 对于需要方言感知转义的场景(如 MySQL 的 `NO_BACKSLASH_ESCAPES` 模式),
452/// 应使用 `Dialect::escape_string()` 方法。
453fn escape_string(s: &str) -> String {
454    let mut escaped = String::with_capacity(s.len() + s.chars().filter(|&c| c == '\'').count());
455    for c in s.chars() {
456        if c == '\'' {
457            escaped.push_str("''");
458        } else {
459            escaped.push(c);
460        }
461    }
462    escaped
463}
464
465fn hex_encode(bytes: &[u8]) -> String {
466    bytes.iter().map(|b| format!("{:02x}", b)).collect()
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_value_is_null() {
475        assert!(Value::Null.is_null());
476        assert!(!Value::I64(0).is_null());
477    }
478
479    #[test]
480    fn test_value_as_i64() {
481        assert_eq!(Value::I64(42).as_i64(), Some(42));
482        assert_eq!(Value::I32(42).as_i64(), Some(42));
483        assert_eq!(Value::Bool(true).as_i64(), Some(1));
484        assert!(Value::String("test".to_string()).as_i64().is_none());
485    }
486
487    #[test]
488    fn test_value_as_f64() {
489        assert_eq!(Value::F64(2.5).as_f64(), Some(2.5));
490        assert_eq!(Value::I64(42).as_f64(), Some(42.0));
491    }
492
493    #[test]
494    fn test_value_as_str() {
495        assert_eq!(Value::String("hello".to_string()).as_str(), Some("hello"));
496    }
497
498    #[test]
499    fn test_value_to_param() {
500        assert_eq!(Value::Null.to_param(), "NULL");
501        assert_eq!(Value::Bool(true).to_param(), "TRUE");
502        assert_eq!(Value::I64(42).to_param(), "42");
503        assert_eq!(Value::String("test".to_string()).to_param(), "'test'");
504        assert_eq!(Value::String("it's".to_string()).to_param(), "'it''s'");
505    }
506
507    #[test]
508    fn test_value_into() {
509        let v: Value = 42i64.into();
510        assert_eq!(v, Value::I64(42));
511
512        let v: Value = "hello".into();
513        assert_eq!(v, Value::String("hello".to_string()));
514
515        let arr: Vec<Value> = vec![Value::I64(1), Value::I64(2)];
516        let v: Value = arr.into();
517        assert_eq!(v, Value::Array(vec![Value::I64(1), Value::I64(2)]));
518    }
519
520    #[test]
521    fn test_value_display() {
522        assert_eq!(format!("{}", Value::Null), "NULL");
523        assert_eq!(format!("{}", Value::Bool(true)), "true");
524        assert_eq!(format!("{}", Value::I64(42)), "42");
525        assert_eq!(format!("{}", Value::String("test".to_string())), "'test'");
526    }
527}