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}