dbkit_core/
compile.rs

1use crate::expr::{BinaryOp, BoolOp, ExprNode, UnaryOp, Value};
2use crate::schema::ColumnRef;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct CompiledSql {
6    pub sql: String,
7    pub binds: Vec<Value>,
8}
9
10#[derive(Debug, Default)]
11pub struct SqlBuilder {
12    sql: String,
13    binds: Vec<Value>,
14}
15
16impl SqlBuilder {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    pub fn push_sql(&mut self, fragment: &str) {
22        self.sql.push_str(fragment);
23    }
24
25    pub fn push_value(&mut self, value: Value) {
26        if value == Value::Null {
27            self.sql.push_str("NULL");
28            return;
29        }
30        let idx = if let Some(existing) = self.binds.iter().position(|item| item == &value) {
31            existing + 1
32        } else {
33            self.binds.push(value);
34            self.binds.len()
35        };
36        self.sql.push('$');
37        self.sql.push_str(&idx.to_string());
38    }
39
40    pub fn push_column(&mut self, col: ColumnRef) {
41        self.sql.push_str(&col.qualified_name());
42    }
43
44    pub fn finish(self) -> CompiledSql {
45        CompiledSql {
46            sql: self.sql,
47            binds: self.binds,
48        }
49    }
50}
51
52pub trait ToSql {
53    fn to_sql(&self, builder: &mut SqlBuilder);
54}
55
56impl ToSql for ExprNode {
57    fn to_sql(&self, builder: &mut SqlBuilder) {
58        match self {
59            ExprNode::Column(col) => builder.push_column(*col),
60            ExprNode::Value(value) => builder.push_value(value.clone()),
61            ExprNode::Func { name, args } => {
62                builder.push_sql(name);
63                builder.push_sql("(");
64                for (idx, arg) in args.iter().enumerate() {
65                    if idx > 0 {
66                        builder.push_sql(", ");
67                    }
68                    arg.to_sql(builder);
69                }
70                builder.push_sql(")");
71            }
72            ExprNode::Binary { left, op, right } => {
73                builder.push_sql("(");
74                left.to_sql(builder);
75                builder.push_sql(match op {
76                    BinaryOp::Eq => " = ",
77                    BinaryOp::Ne => " <> ",
78                    BinaryOp::Lt => " < ",
79                    BinaryOp::Le => " <= ",
80                    BinaryOp::Gt => " > ",
81                    BinaryOp::Ge => " >= ",
82                });
83                right.to_sql(builder);
84                builder.push_sql(")");
85            }
86            ExprNode::Bool { left, op, right } => {
87                builder.push_sql("(");
88                left.to_sql(builder);
89                builder.push_sql(match op {
90                    BoolOp::And => " AND ",
91                    BoolOp::Or => " OR ",
92                });
93                right.to_sql(builder);
94                builder.push_sql(")");
95            }
96            ExprNode::Unary { op, expr } => {
97                builder.push_sql(match op {
98                    UnaryOp::Not => "NOT ",
99                });
100                builder.push_sql("(");
101                expr.to_sql(builder);
102                builder.push_sql(")");
103            }
104            ExprNode::In { expr, values } => {
105                if values.is_empty() {
106                    builder.push_sql("(FALSE)");
107                    return;
108                }
109                builder.push_sql("(");
110                expr.to_sql(builder);
111                builder.push_sql(" IN (");
112                for (idx, value) in values.iter().enumerate() {
113                    if idx > 0 {
114                        builder.push_sql(", ");
115                    }
116                    builder.push_value(value.clone());
117                }
118                builder.push_sql("))");
119            }
120            ExprNode::IsNull { expr, negated } => {
121                builder.push_sql("(");
122                expr.to_sql(builder);
123                if *negated {
124                    builder.push_sql(" IS NOT NULL)");
125                } else {
126                    builder.push_sql(" IS NULL)");
127                }
128            }
129            ExprNode::Like {
130                expr,
131                pattern,
132                case_insensitive,
133            } => {
134                builder.push_sql("(");
135                expr.to_sql(builder);
136                builder.push_sql(if *case_insensitive { " ILIKE " } else { " LIKE " });
137                builder.push_value(pattern.clone());
138                builder.push_sql(")");
139            }
140        }
141    }
142}