odatav4_parser/renderers/
filter.rs1use crate::ast::{ArithmeticOp, ComparisonOp, FilterExpression, LambdaOp, LogicalOp};
2
3pub trait FilterRenderer {
5 fn quote_identifier(&self, ident: &str) -> String;
7
8 fn quote_string(&self, s: &str) -> String {
10 format!("'{}'", s.replace('\'', "''"))
11 }
12
13 fn render_filter(&self, expr: &FilterExpression) -> String {
15 match expr {
16 FilterExpression::Comparison { left, op, right } => {
17 let left_sql = self.render_filter(left);
18 let right_sql = self.render_filter(right);
19 let op_sql = match op {
20 ComparisonOp::Eq => "=",
21 ComparisonOp::Ne => "<>",
22 ComparisonOp::Gt => ">",
23 ComparisonOp::Ge => ">=",
24 ComparisonOp::Lt => "<",
25 ComparisonOp::Le => "<=",
26 ComparisonOp::Has => {
27 return format!("({} & {}) = {}", left_sql, right_sql, right_sql);
29 }
30 };
31 format!("{} {} {}", left_sql, op_sql, right_sql)
32 }
33 FilterExpression::Logical { left, op, right } => {
34 let left_sql = self.render_filter(left);
35 let right_sql = self.render_filter(right);
36 let op_sql = match op {
37 LogicalOp::And => "AND",
38 LogicalOp::Or => "OR",
39 };
40 format!("({} {} {})", left_sql, op_sql, right_sql)
41 }
42 FilterExpression::Not(inner) => {
43 format!("NOT ({})", self.render_filter(inner))
44 }
45 FilterExpression::Arithmetic { left, op, right } => {
46 let left_sql = self.render_filter(left);
47 let right_sql = self.render_filter(right);
48 let op_sql = match op {
49 ArithmeticOp::Add => "+",
50 ArithmeticOp::Sub => "-",
51 ArithmeticOp::Mul => "*",
52 ArithmeticOp::Div => "/",
53 ArithmeticOp::Mod => "%",
54 };
55 format!("({} {} {})", left_sql, op_sql, right_sql)
56 }
57 FilterExpression::UnaryMinus(inner) => {
58 format!("-({})", self.render_filter(inner))
59 }
60 FilterExpression::FunctionCall { name, args } => {
61 self.render_function(name, args)
62 }
63 FilterExpression::In { field, values } => {
64 let field_sql = self.render_filter(field);
65 let values_sql = values
66 .iter()
67 .map(|v| self.render_filter(v))
68 .collect::<Vec<_>>()
69 .join(", ");
70 format!("{} IN ({})", field_sql, values_sql)
71 }
72 FilterExpression::Lambda { collection, operator, variable, predicate } => {
73 self.render_lambda(collection, operator, variable, predicate)
74 }
75 FilterExpression::Field(name) => self.quote_identifier(name),
76 FilterExpression::StringLiteral(s) => self.quote_string(s),
77 FilterExpression::NumberLiteral(n) => {
78 if n.fract() == 0.0 {
79 format!("{:.0}", n)
80 } else {
81 n.to_string()
82 }
83 }
84 FilterExpression::BooleanLiteral(b) => {
85 if *b { "TRUE" } else { "FALSE" }.to_string()
86 }
87 FilterExpression::Null => "NULL".to_string(),
88 FilterExpression::GuidLiteral(guid) => self.quote_string(guid),
89 FilterExpression::DateLiteral(date) => self.quote_string(date),
90 }
91 }
92
93 fn render_function(&self, name: &str, args: &[FilterExpression]) -> String {
95 match name.to_lowercase().as_str() {
96 "contains" => self.render_contains(args),
98 "startswith" => self.render_startswith(args),
99 "endswith" => self.render_endswith(args),
100 "length" => self.render_length(args),
101 "indexof" => self.render_indexof(args),
102 "substring" => self.render_substring(args),
103 "tolower" => self.render_tolower(args),
104 "toupper" => self.render_toupper(args),
105 "trim" => self.render_trim(args),
106 "concat" => self.render_concat(args),
107
108 "year" => self.render_year(args),
110 "month" => self.render_month(args),
111 "day" => self.render_day(args),
112 "hour" => self.render_hour(args),
113 "minute" => self.render_minute(args),
114 "second" => self.render_second(args),
115 "now" => self.render_now(args),
116
117 "round" => self.render_round(args),
119 "floor" => self.render_floor(args),
120 "ceiling" => self.render_ceiling(args),
121
122 _ => format!("{}({})", name.to_uppercase(),
123 args.iter()
124 .map(|a| self.render_filter(a))
125 .collect::<Vec<_>>()
126 .join(", "))
127 }
128 }
129
130 fn render_contains(&self, args: &[FilterExpression]) -> String {
132 if args.len() != 2 {
133 return "1=0".to_string(); }
135 let field = self.render_filter(&args[0]);
136 let value = self.render_filter(&args[1]);
137 format!("{} LIKE CONCAT('%', {}, '%')", field, value.trim_matches('\''))
138 }
139
140 fn render_startswith(&self, args: &[FilterExpression]) -> String {
141 if args.len() != 2 {
142 return "1=0".to_string();
143 }
144 let field = self.render_filter(&args[0]);
145 let value = self.render_filter(&args[1]);
146 format!("{} LIKE CONCAT({}, '%')", field, value.trim_matches('\''))
147 }
148
149 fn render_endswith(&self, args: &[FilterExpression]) -> String {
150 if args.len() != 2 {
151 return "1=0".to_string();
152 }
153 let field = self.render_filter(&args[0]);
154 let value = self.render_filter(&args[1]);
155 format!("{} LIKE CONCAT('%', {})", field, value.trim_matches('\''))
156 }
157
158 fn render_length(&self, args: &[FilterExpression]) -> String {
159 if args.is_empty() {
160 return "0".to_string();
161 }
162 format!("LENGTH({})", self.render_filter(&args[0]))
163 }
164
165 fn render_indexof(&self, args: &[FilterExpression]) -> String {
166 if args.len() < 2 {
167 return "0".to_string();
168 }
169 format!("POSITION({} IN {})", self.render_filter(&args[1]), self.render_filter(&args[0]))
170 }
171
172 fn render_substring(&self, args: &[FilterExpression]) -> String {
173 if args.len() < 2 {
174 return "''".to_string();
175 }
176 let field = self.render_filter(&args[0]);
177 let start = self.render_filter(&args[1]);
178 if args.len() >= 3 {
179 let length = self.render_filter(&args[2]);
180 format!("SUBSTRING({}, {} + 1, {})", field, start, length)
181 } else {
182 format!("SUBSTRING({}, {} + 1)", field, start)
183 }
184 }
185
186 fn render_tolower(&self, args: &[FilterExpression]) -> String {
187 if args.is_empty() {
188 return "''".to_string();
189 }
190 format!("LOWER({})", self.render_filter(&args[0]))
191 }
192
193 fn render_toupper(&self, args: &[FilterExpression]) -> String {
194 if args.is_empty() {
195 return "''".to_string();
196 }
197 format!("UPPER({})", self.render_filter(&args[0]))
198 }
199
200 fn render_trim(&self, args: &[FilterExpression]) -> String {
201 if args.is_empty() {
202 return "''".to_string();
203 }
204 format!("TRIM({})", self.render_filter(&args[0]))
205 }
206
207 fn render_concat(&self, args: &[FilterExpression]) -> String {
208 if args.len() < 2 {
209 return "''".to_string();
210 }
211 let parts: Vec<String> = args.iter().map(|a| self.render_filter(a)).collect();
212 format!("CONCAT({})", parts.join(", "))
213 }
214
215 fn render_year(&self, args: &[FilterExpression]) -> String {
217 if args.is_empty() {
218 return "0".to_string();
219 }
220 format!("YEAR({})", self.render_filter(&args[0]))
221 }
222
223 fn render_month(&self, args: &[FilterExpression]) -> String {
224 if args.is_empty() {
225 return "0".to_string();
226 }
227 format!("MONTH({})", self.render_filter(&args[0]))
228 }
229
230 fn render_day(&self, args: &[FilterExpression]) -> String {
231 if args.is_empty() {
232 return "0".to_string();
233 }
234 format!("DAY({})", self.render_filter(&args[0]))
235 }
236
237 fn render_hour(&self, args: &[FilterExpression]) -> String {
238 if args.is_empty() {
239 return "0".to_string();
240 }
241 format!("HOUR({})", self.render_filter(&args[0]))
242 }
243
244 fn render_minute(&self, args: &[FilterExpression]) -> String {
245 if args.is_empty() {
246 return "0".to_string();
247 }
248 format!("MINUTE({})", self.render_filter(&args[0]))
249 }
250
251 fn render_second(&self, args: &[FilterExpression]) -> String {
252 if args.is_empty() {
253 return "0".to_string();
254 }
255 format!("SECOND({})", self.render_filter(&args[0]))
256 }
257
258 fn render_now(&self, _args: &[FilterExpression]) -> String {
259 "NOW()".to_string()
260 }
261
262 fn render_round(&self, args: &[FilterExpression]) -> String {
264 if args.is_empty() {
265 return "0".to_string();
266 }
267 format!("ROUND({}, 0)", self.render_filter(&args[0]))
268 }
269
270 fn render_floor(&self, args: &[FilterExpression]) -> String {
271 if args.is_empty() {
272 return "0".to_string();
273 }
274 format!("FLOOR({})", self.render_filter(&args[0]))
275 }
276
277 fn render_ceiling(&self, args: &[FilterExpression]) -> String {
278 if args.is_empty() {
279 return "0".to_string();
280 }
281 format!("CEILING({})", self.render_filter(&args[0]))
282 }
283
284 fn render_lambda(&self, collection: &str, operator: &LambdaOp, variable: &str, predicate: &FilterExpression) -> String {
286 let op_str = match operator {
287 LambdaOp::Any => "ANY",
288 LambdaOp::All => "ALL",
289 };
290 format!("/* TODO: {} lambda on {} with {} */", op_str, collection, variable)
291 }
292}