halo/
expr.rs

1//! SQL 表达式片段。
2
3use crate::dialect::Dialect;
4use crate::value::SqlValue;
5
6#[derive(Debug, Clone, PartialEq)]
7pub(crate) enum Part {
8    Sql(String),
9    Arg(SqlValue),
10}
11
12/// 一个可组合的 SQL 片段表达式。
13#[derive(Debug, Clone, PartialEq)]
14pub struct Expr {
15    pub(crate) parts: Vec<Part>,
16}
17
18impl Expr {
19    /// 直接插入一段 SQL 文本(不会变成参数)。
20    pub fn raw(sql: impl Into<String>) -> Self {
21        Self {
22            parts: vec![Part::Sql(sql.into())],
23        }
24    }
25
26    /// 创建一个恒为 TRUE 的表达式(`TRUE`)。
27    pub fn true_() -> Self {
28        Self::raw("TRUE")
29    }
30
31    /// 创建一个恒为 FALSE 的表达式(`FALSE`)。
32    pub fn false_() -> Self {
33        Self::raw("FALSE")
34    }
35
36    /// 追加 SQL 文本。
37    pub fn push_raw(&mut self, sql: impl Into<String>) {
38        self.parts.push(Part::Sql(sql.into()));
39    }
40
41    /// 追加一个参数(构建时会生成占位符)。
42    pub fn push_arg(&mut self, v: impl Into<SqlValue>) {
43        self.parts.push(Part::Arg(v.into()));
44    }
45
46    /// 将当前表达式与另一个表达式连接(不自动添加空格)。
47    pub fn concat(mut self, other: Expr) -> Self {
48        self.parts.extend(other.parts);
49        self
50    }
51
52    #[allow(dead_code)]
53    pub(crate) fn build(&self, dialect: Dialect) -> (String, Vec<SqlValue>) {
54        let mut sql = String::new();
55        let mut args = Vec::new();
56
57        for part in &self.parts {
58            match part {
59                Part::Sql(s) => sql.push_str(s),
60                Part::Arg(v) => {
61                    let idx = args.len() + 1;
62                    dialect.write_placeholder(idx, &mut sql);
63                    args.push(v.clone());
64                }
65            }
66        }
67
68        (sql, args)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::Expr;
75    use crate::Dialect;
76    use crate::SqlValue;
77
78    #[test]
79    fn raw_is_not_parameterized() {
80        let e = Expr::raw("a = 1");
81        let (sql, args) = e.build(Dialect::QuestionMark);
82        assert_eq!(sql, "a = 1");
83        assert!(args.is_empty());
84    }
85
86    #[test]
87    fn push_arg_generates_placeholder_question_mark() {
88        let mut e = Expr::raw("id = ");
89        e.push_arg(7_i64);
90        let (sql, args) = e.build(Dialect::QuestionMark);
91        assert_eq!(sql, "id = ?");
92        assert_eq!(args, vec![SqlValue::I64(7)]);
93    }
94
95    #[test]
96    fn push_arg_generates_placeholder_dollar_numbered() {
97        let mut e = Expr::raw("id = ");
98        e.push_arg(7_i64);
99        let (sql, _args) = e.build(Dialect::DollarNumbered);
100        assert_eq!(sql, "id = $1");
101    }
102
103    #[test]
104    fn concat_keeps_arg_order() {
105        let mut a = Expr::raw("a = ");
106        a.push_arg(1_i64);
107        let mut b = Expr::raw(" AND b = ");
108        b.push_arg(2_i64);
109
110        let e = a.concat(b);
111        let (sql, args) = e.build(Dialect::QuestionMark);
112        assert_eq!(sql, "a = ? AND b = ?");
113        assert_eq!(args, vec![SqlValue::I64(1), SqlValue::I64(2)]);
114    }
115}