odatav4_parser/renderers/
filter.rs

1use crate::ast::{ArithmeticOp, ComparisonOp, FilterExpression, LambdaOp, LogicalOp};
2
3/// Helper trait for rendering filter expressions with dialect-specific quoting
4pub trait FilterRenderer {
5    /// Quote an identifier (field name) for this SQL dialect
6    fn quote_identifier(&self, ident: &str) -> String;
7    
8    /// Quote a string literal for this SQL dialect
9    fn quote_string(&self, s: &str) -> String {
10        format!("'{}'", s.replace('\'', "''"))
11    }
12    
13    /// Render a filter expression to SQL WHERE clause
14    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                        // Special handling for 'has': (left & right) = right
28                        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    /// Render a function call - dialect-specific implementation
94    fn render_function(&self, name: &str, args: &[FilterExpression]) -> String {
95        match name.to_lowercase().as_str() {
96            // String functions
97            "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            // Date/time functions
109            "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            // Math functions
118            "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    /// Default implementations for string functions (ANSI SQL)
131    fn render_contains(&self, args: &[FilterExpression]) -> String {
132        if args.len() != 2 {
133            return "1=0".to_string(); // Invalid
134        }
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    /// Date/time functions
216    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    /// Math functions
263    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    /// Lambda operators - default implementation (may need override per dialect)
285    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}