Skip to main content

dinoco_engine/query/
helpers.rs

1use std::fmt::Write;
2
3use crate::{AdapterDialect, BinaryOperator, DinocoValue, Expression};
4
5pub fn push_joined<I, T, F>(buf: &mut String, iter: I, sep: &str, mut f: F)
6where
7    I: IntoIterator<Item = T>,
8    F: FnMut(&mut String, T),
9{
10    let mut first = true;
11
12    for item in iter {
13        if !first {
14            buf.push_str(sep);
15        }
16
17        f(buf, item);
18
19        first = false;
20    }
21}
22
23pub fn append_limit_skip<D: AdapterDialect + ?Sized>(
24    dialect: &D,
25    sql: &mut String,
26    params: &mut Vec<DinocoValue>,
27    limit: Option<usize>,
28    skip: Option<usize>,
29) {
30    match (limit, skip) {
31        (Some(limit), Some(skip)) => {
32            params.push(DinocoValue::Integer(limit as i64));
33            let _ = write!(sql, " LIMIT {}", dialect.bind_param(params.len()));
34
35            params.push(DinocoValue::Integer(skip as i64));
36            let _ = write!(sql, " OFFSET {}", dialect.bind_param(params.len()));
37        }
38        (Some(limit), None) => {
39            params.push(DinocoValue::Integer(limit as i64));
40            let _ = write!(sql, " LIMIT {}", dialect.bind_param(params.len()));
41        }
42        (None, Some(skip)) => {
43            let _ = write!(sql, " LIMIT {}", dialect.offset_without_limit());
44            params.push(DinocoValue::Integer(skip as i64));
45
46            let _ = write!(sql, " OFFSET {}", dialect.bind_param(params.len()));
47        }
48        (None, None) => {}
49    }
50}
51
52pub fn render_condition_group_into<D: AdapterDialect + ?Sized>(
53    dialect: &D,
54    conditions: &[Expression],
55    params: &mut Vec<DinocoValue>,
56    joiner: &str,
57    buf: &mut String,
58) {
59    push_joined(buf, conditions, joiner, |b, condition| {
60        render_expression_into(dialect, condition, params, b);
61    });
62}
63
64pub fn render_expression_into<D: AdapterDialect + ?Sized>(
65    dialect: &D,
66    expression: &Expression,
67    params: &mut Vec<DinocoValue>,
68    buf: &mut String,
69) {
70    match expression {
71        Expression::Column(name) => render_query_identifier_into(dialect, name, buf),
72        Expression::Value(value) => {
73            params.push(value.clone());
74
75            buf.push_str(&dialect.bind_value(params.len(), value));
76        }
77        Expression::Raw(value) => buf.push_str(value),
78        Expression::IsNull(inner) => {
79            buf.push('(');
80
81            render_expression_into(dialect, inner, params, buf);
82
83            buf.push_str(" IS NULL)");
84        }
85        Expression::IsNotNull(inner) => {
86            buf.push('(');
87
88            render_expression_into(dialect, inner, params, buf);
89
90            buf.push_str(" IS NOT NULL)");
91        }
92        Expression::In { expr, values } => {
93            if values.is_empty() {
94                buf.push_str("(1 = 0)");
95
96                return;
97            }
98
99            params.reserve(values.len());
100
101            buf.push('(');
102
103            render_expression_into(dialect, expr, params, buf);
104
105            buf.push_str(" IN (");
106
107            push_joined(buf, values, ", ", |b, value| {
108                params.push(value.clone());
109
110                b.push_str(&dialect.bind_value(params.len(), value));
111            });
112
113            buf.push_str("))");
114        }
115        Expression::NotIn { expr, values } => {
116            if values.is_empty() {
117                buf.push_str("(1 = 1)");
118
119                return;
120            }
121
122            params.reserve(values.len());
123
124            buf.push('(');
125
126            render_expression_into(dialect, expr, params, buf);
127
128            buf.push_str(" NOT IN (");
129
130            push_joined(buf, values, ", ", |b, value| {
131                params.push(value.clone());
132
133                b.push_str(&dialect.bind_value(params.len(), value));
134            });
135
136            buf.push_str("))");
137        }
138        Expression::And(expressions) => {
139            if expressions.is_empty() {
140                buf.push_str("(1 = 1)");
141
142                return;
143            }
144
145            buf.push('(');
146
147            render_condition_group_into(dialect, expressions, params, " AND ", buf);
148
149            buf.push(')');
150        }
151        Expression::Or(expressions) => {
152            if expressions.is_empty() {
153                buf.push_str("(1 = 0)");
154
155                return;
156            }
157
158            buf.push('(');
159
160            render_condition_group_into(dialect, expressions, params, " OR ", buf);
161
162            buf.push(')');
163        }
164        Expression::BinaryOp { left, op, right } => {
165            buf.push('(');
166
167            render_expression_into(dialect, left, params, buf);
168
169            let op_str = match op {
170                BinaryOperator::Eq => " = ",
171                BinaryOperator::Neq => " <> ",
172                BinaryOperator::Gt => " > ",
173                BinaryOperator::Lt => " < ",
174                BinaryOperator::Gte => " >= ",
175                BinaryOperator::Lte => " <= ",
176                BinaryOperator::Like => " LIKE ",
177            };
178
179            buf.push_str(op_str);
180
181            render_expression_into(dialect, right, params, buf);
182
183            buf.push(')');
184        }
185    }
186}
187
188pub fn render_query_identifier_into<D: AdapterDialect + ?Sized>(dialect: &D, value: &str, buf: &mut String) {
189    if value == "*" || value.contains(' ') || value.contains('(') || value.contains(')') || value.contains(',') {
190        buf.push_str(value);
191
192        return;
193    }
194
195    let mut first = true;
196
197    for part in value.split('.') {
198        if !first {
199            buf.push('.');
200        }
201
202        if part == "*" {
203            buf.push('*');
204        } else {
205            buf.push_str(&dialect.identifier(part));
206        }
207
208        first = false;
209    }
210}