Skip to main content

featherdb_query/planner/
expr_conv.rs

1//! Expression conversion from SQL AST to internal representation
2
3use super::util::parse_placeholder;
4use crate::expr::{AggregateFunction, BinaryOp, Expr, UnaryOp};
5use crate::planner::Planner;
6use featherdb_core::{Error, Result, Value};
7use sqlparser::ast::{self, DateTimeField, Expr as SqlExpr, SelectItem};
8
9impl<'a> Planner<'a> {
10    /// Convert a SELECT item to expressions
11    pub(super) fn convert_select_item(&self, item: &SelectItem) -> Result<Vec<(Expr, String)>> {
12        match item {
13            SelectItem::UnnamedExpr(e) => {
14                let expr = self.convert_expr(e)?;
15                let name = format!("{}", e);
16                Ok(vec![(expr, name)])
17            }
18            SelectItem::ExprWithAlias { expr, alias } => {
19                let e = self.convert_expr(expr)?;
20                Ok(vec![(e, alias.value.clone())])
21            }
22            SelectItem::QualifiedWildcard(name, _) => {
23                let table = name.0.first().map(|i| i.value.clone()).unwrap_or_default();
24                Ok(vec![(
25                    Expr::QualifiedWildcard(table.clone()),
26                    format!("{}.*", table),
27                )])
28            }
29            SelectItem::Wildcard(_) => Ok(vec![(Expr::Wildcard, "*".to_string())]),
30        }
31    }
32
33    /// Convert SQL expression to internal representation  
34    pub fn convert_expr(&self, expr: &SqlExpr) -> Result<Expr> {
35        // Note: The full implementation is ~340 lines
36        // Due to size, we'll implement the core patterns
37        match expr {
38            SqlExpr::Identifier(ident) => Ok(Expr::Column {
39                table: None,
40                name: ident.value.clone(),
41                index: None,
42            }),
43
44            SqlExpr::CompoundIdentifier(parts) => {
45                if parts.len() == 2 {
46                    Ok(Expr::Column {
47                        table: Some(parts[0].value.clone()),
48                        name: parts[1].value.clone(),
49                        index: None,
50                    })
51                } else {
52                    Err(Error::InvalidQuery {
53                        message: format!("Invalid identifier: {:?}", parts),
54                    })
55                }
56            }
57
58            // Handle placeholders first (before general Value)
59            SqlExpr::Value(ast::Value::Placeholder(placeholder)) => {
60                let index = parse_placeholder(placeholder)?;
61                Ok(Expr::Parameter { index })
62            }
63
64            SqlExpr::Value(v) => Ok(Expr::Literal(self.convert_value(v)?)),
65
66            SqlExpr::BinaryOp { left, op, right } => {
67                let l = self.convert_expr(left)?;
68                let r = self.convert_expr(right)?;
69                let op = self.convert_binary_op(op)?;
70                Ok(Expr::BinaryOp {
71                    left: Box::new(l),
72                    op,
73                    right: Box::new(r),
74                })
75            }
76
77            SqlExpr::UnaryOp { op, expr } => {
78                let e = self.convert_expr(expr)?;
79                let op = match op {
80                    ast::UnaryOperator::Not => UnaryOp::Not,
81                    ast::UnaryOperator::Minus => UnaryOp::Negate,
82                    _ => {
83                        return Err(Error::Unsupported {
84                            feature: format!("Unary operator: {:?}", op),
85                        })
86                    }
87                };
88                Ok(Expr::UnaryOp {
89                    op,
90                    expr: Box::new(e),
91                })
92            }
93
94            SqlExpr::IsNull(e) => Ok(Expr::UnaryOp {
95                op: UnaryOp::IsNull,
96                expr: Box::new(self.convert_expr(e)?),
97            }),
98
99            SqlExpr::IsNotNull(e) => Ok(Expr::UnaryOp {
100                op: UnaryOp::IsNotNull,
101                expr: Box::new(self.convert_expr(e)?),
102            }),
103
104            SqlExpr::Between {
105                expr,
106                low,
107                high,
108                negated,
109            } => Ok(Expr::Between {
110                expr: Box::new(self.convert_expr(expr)?),
111                low: Box::new(self.convert_expr(low)?),
112                high: Box::new(self.convert_expr(high)?),
113                negated: *negated,
114            }),
115
116            SqlExpr::InList {
117                expr,
118                list,
119                negated,
120            } => {
121                let e = self.convert_expr(expr)?;
122                let l: Vec<Expr> = list
123                    .iter()
124                    .map(|i| self.convert_expr(i))
125                    .collect::<Result<_>>()?;
126                Ok(Expr::InList {
127                    expr: Box::new(e),
128                    list: l,
129                    negated: *negated,
130                })
131            }
132
133            SqlExpr::Like {
134                expr,
135                pattern,
136                negated,
137                ..
138            } => {
139                let e = self.convert_expr(expr)?;
140                let p = match pattern.as_ref() {
141                    SqlExpr::Value(ast::Value::SingleQuotedString(s)) => s.clone(),
142                    _ => format!("{}", pattern),
143                };
144                Ok(Expr::Like {
145                    expr: Box::new(e),
146                    pattern: p,
147                    negated: *negated,
148                })
149            }
150
151            SqlExpr::Function(func) => {
152                let name = func
153                    .name
154                    .0
155                    .first()
156                    .map(|i| i.value.clone())
157                    .unwrap_or_default();
158
159                // Check if this is a window function (has OVER clause)
160                if func.over.is_some() {
161                    return self.convert_window_function(func);
162                }
163
164                // Check for aggregate functions
165                let agg_func = match name.to_uppercase().as_str() {
166                    "COUNT" => Some(AggregateFunction::Count),
167                    "SUM" => Some(AggregateFunction::Sum),
168                    "AVG" => Some(AggregateFunction::Avg),
169                    "MIN" => Some(AggregateFunction::Min),
170                    "MAX" => Some(AggregateFunction::Max),
171                    _ => None,
172                };
173
174                if let Some(func_type) = agg_func {
175                    let arg = if func.args.is_empty() {
176                        None
177                    } else {
178                        match &func.args[0] {
179                            ast::FunctionArg::Unnamed(ast::FunctionArgExpr::Wildcard) => None,
180                            ast::FunctionArg::Unnamed(ast::FunctionArgExpr::Expr(e)) => {
181                                Some(Box::new(self.convert_expr(e)?))
182                            }
183                            _ => None,
184                        }
185                    };
186
187                    return Ok(Expr::Aggregate {
188                        func: func_type,
189                        arg,
190                        distinct: func.distinct,
191                    });
192                }
193
194                // Regular function
195                let args: Vec<Expr> = func
196                    .args
197                    .iter()
198                    .filter_map(|arg| match arg {
199                        ast::FunctionArg::Unnamed(ast::FunctionArgExpr::Expr(e)) => {
200                            self.convert_expr(e).ok()
201                        }
202                        _ => None,
203                    })
204                    .collect();
205
206                Ok(Expr::Function { name, args })
207            }
208
209            SqlExpr::Case {
210                operand,
211                conditions,
212                results,
213                else_result,
214            } => {
215                let op = operand
216                    .as_ref()
217                    .map(|e| self.convert_expr(e))
218                    .transpose()?
219                    .map(Box::new);
220
221                let when_clauses: Vec<(Expr, Expr)> = conditions
222                    .iter()
223                    .zip(results.iter())
224                    .map(|(c, r)| Ok((self.convert_expr(c)?, self.convert_expr(r)?)))
225                    .collect::<Result<_>>()?;
226
227                let else_res = else_result
228                    .as_ref()
229                    .map(|e| self.convert_expr(e))
230                    .transpose()?
231                    .map(Box::new);
232
233                Ok(Expr::Case {
234                    operand: op,
235                    when_clauses,
236                    else_result: else_res,
237                })
238            }
239
240            SqlExpr::Cast {
241                expr,
242                data_type,
243                format: _,
244            } => {
245                // CAST is handled as a function call
246                let e = self.convert_expr(expr)?;
247                let target_type = crate::Parser::convert_data_type(data_type)?;
248                // Convert to CAST function
249                Ok(Expr::Function {
250                    name: format!("CAST_TO_{:?}", target_type),
251                    args: vec![e],
252                })
253            }
254
255            SqlExpr::Nested(e) => self.convert_expr(e),
256
257            SqlExpr::Subquery(query) => {
258                // Generate a deterministic ID for this subquery (per planner instance)
259                let id = self.next_subquery_id();
260
261                // Plan the subquery and store it
262                let subquery_plan = self.plan_query(query)?;
263                self.subquery_plans.borrow_mut().insert(id, subquery_plan);
264
265                // Check if it's correlated
266                let correlated = self.detect_correlation(query).unwrap_or(false);
267
268                Ok(Expr::ScalarSubquery {
269                    subquery_id: id,
270                    correlated,
271                })
272            }
273
274            SqlExpr::InSubquery {
275                expr,
276                subquery,
277                negated,
278            } => {
279                let e = Box::new(self.convert_expr(expr)?);
280
281                // Generate a deterministic ID for this subquery (per planner instance)
282                let id = self.next_subquery_id();
283
284                // Plan the subquery and store it
285                let subquery_plan = self.plan_query(subquery)?;
286                self.subquery_plans.borrow_mut().insert(id, subquery_plan);
287
288                Ok(Expr::InSubquery {
289                    expr: e,
290                    subquery_id: id,
291                    negated: *negated,
292                })
293            }
294
295            SqlExpr::Exists { subquery, negated } => {
296                // Generate a deterministic ID for this subquery (per planner instance)
297                let id = self.next_subquery_id();
298
299                // Plan the subquery and store it
300                let subquery_plan = self.plan_query(subquery)?;
301                self.subquery_plans.borrow_mut().insert(id, subquery_plan);
302
303                Ok(Expr::Exists {
304                    subquery_id: id,
305                    negated: *negated,
306                })
307            }
308
309            // EXTRACT(field FROM expr) → mapped to YEAR/MONTH/DAY/etc functions
310            SqlExpr::Extract { field, expr } => {
311                let e = self.convert_expr(expr)?;
312                let func_name = match field {
313                    DateTimeField::Year => "YEAR",
314                    DateTimeField::Month => "MONTH",
315                    DateTimeField::Day => "DAY",
316                    DateTimeField::Hour => "HOUR",
317                    DateTimeField::Minute => "MINUTE",
318                    DateTimeField::Second => "SECOND",
319                    _ => {
320                        return Err(Error::Unsupported {
321                            feature: format!("EXTRACT field: {:?}", field),
322                        })
323                    }
324                };
325                Ok(Expr::Function {
326                    name: func_name.to_string(),
327                    args: vec![e],
328                })
329            }
330
331            // DATE '2024-01-15' → text literal (dates stored as text)
332            SqlExpr::TypedString {
333                data_type: _,
334                value,
335            } => Ok(Expr::Literal(Value::Text(value.clone()))),
336
337            // INTERVAL '3' MONTH → stored as text for date arithmetic
338            SqlExpr::Interval(interval) => {
339                let value_str = match interval.value.as_ref() {
340                    SqlExpr::Value(ast::Value::SingleQuotedString(s)) => s.clone(),
341                    SqlExpr::Value(ast::Value::Number(n, _)) => n.clone(),
342                    _ => format!("{}", interval.value),
343                };
344                let unit = interval
345                    .leading_field
346                    .map(|f| format!("{}", f))
347                    .unwrap_or_else(|| "DAY".to_string());
348                // Store as text "N UNIT" for date_add/date_sub to parse
349                Ok(Expr::Literal(Value::Text(format!(
350                    "{} {}",
351                    value_str, unit
352                ))))
353            }
354
355            // FLOOR(expr) - sqlparser parses as special expression
356            SqlExpr::Floor { expr, .. } => {
357                let e = self.convert_expr(expr)?;
358                Ok(Expr::Function {
359                    name: "FLOOR".to_string(),
360                    args: vec![e],
361                })
362            }
363
364            // CEIL/CEILING(expr) - sqlparser parses as special expression
365            SqlExpr::Ceil { expr, .. } => {
366                let e = self.convert_expr(expr)?;
367                Ok(Expr::Function {
368                    name: "CEIL".to_string(),
369                    args: vec![e],
370                })
371            }
372
373            // SUBSTRING(expr FROM start FOR length)
374            SqlExpr::Substring {
375                expr,
376                substring_from,
377                substring_for,
378                ..
379            } => {
380                let e = self.convert_expr(expr)?;
381                let mut args = vec![e];
382                if let Some(from) = substring_from {
383                    args.push(self.convert_expr(from)?);
384                }
385                if let Some(len) = substring_for {
386                    args.push(self.convert_expr(len)?);
387                }
388                Ok(Expr::Function {
389                    name: "SUBSTR".to_string(),
390                    args,
391                })
392            }
393
394            _ => Err(Error::Unsupported {
395                feature: format!("Expression: {:?}", expr),
396            }),
397        }
398    }
399
400    /// Convert SQL value to internal representation
401    pub(super) fn convert_value(&self, value: &ast::Value) -> Result<Value> {
402        match value {
403            ast::Value::Number(n, _) => {
404                if n.contains('.') {
405                    Ok(Value::Real(n.parse().map_err(|_| Error::InvalidQuery {
406                        message: format!("Invalid number: {}", n),
407                    })?))
408                } else {
409                    Ok(Value::Integer(n.parse().map_err(|_| {
410                        Error::InvalidQuery {
411                            message: format!("Invalid integer: {}", n),
412                        }
413                    })?))
414                }
415            }
416            ast::Value::SingleQuotedString(s) | ast::Value::DoubleQuotedString(s) => {
417                Ok(Value::Text(s.clone()))
418            }
419            ast::Value::Boolean(b) => Ok(Value::Boolean(*b)),
420            ast::Value::Null => Ok(Value::Null),
421            _ => Err(Error::Unsupported {
422                feature: format!("Value: {:?}", value),
423            }),
424        }
425    }
426
427    /// Convert SQL binary operator to internal representation
428    pub(super) fn convert_binary_op(&self, op: &ast::BinaryOperator) -> Result<BinaryOp> {
429        match op {
430            ast::BinaryOperator::Plus => Ok(BinaryOp::Add),
431            ast::BinaryOperator::Minus => Ok(BinaryOp::Sub),
432            ast::BinaryOperator::Multiply => Ok(BinaryOp::Mul),
433            ast::BinaryOperator::Divide => Ok(BinaryOp::Div),
434            ast::BinaryOperator::Modulo => Ok(BinaryOp::Mod),
435            ast::BinaryOperator::Eq => Ok(BinaryOp::Eq),
436            ast::BinaryOperator::NotEq => Ok(BinaryOp::Ne),
437            ast::BinaryOperator::Lt => Ok(BinaryOp::Lt),
438            ast::BinaryOperator::LtEq => Ok(BinaryOp::Le),
439            ast::BinaryOperator::Gt => Ok(BinaryOp::Gt),
440            ast::BinaryOperator::GtEq => Ok(BinaryOp::Ge),
441            ast::BinaryOperator::And => Ok(BinaryOp::And),
442            ast::BinaryOperator::Or => Ok(BinaryOp::Or),
443            ast::BinaryOperator::StringConcat => Ok(BinaryOp::Concat),
444            _ => Err(Error::Unsupported {
445                feature: format!("Binary operator: {:?}", op),
446            }),
447        }
448    }
449
450    /// Check if an expression contains aggregate functions (without OVER clause)
451    pub(super) fn expr_has_aggregate(&self, expr: &SqlExpr) -> bool {
452        match expr {
453            SqlExpr::Function(func) => {
454                // If OVER is present, it's a window function, not an aggregate
455                if func.over.is_some() {
456                    return false;
457                }
458                let name = func.name.0.first().map(|i| i.value.as_str()).unwrap_or("");
459                matches!(
460                    name.to_uppercase().as_str(),
461                    "COUNT" | "SUM" | "AVG" | "MIN" | "MAX"
462                )
463            }
464            SqlExpr::BinaryOp { left, right, .. } => {
465                self.expr_has_aggregate(left) || self.expr_has_aggregate(right)
466            }
467            SqlExpr::UnaryOp { expr, .. } => self.expr_has_aggregate(expr),
468            SqlExpr::Nested(e) => self.expr_has_aggregate(e),
469            _ => false,
470        }
471    }
472}