Skip to main content

featherdb_query/expr/
mod.rs

1//! Expression representation and evaluation
2
3mod aggregate;
4mod functions;
5pub mod helpers;
6mod ops;
7mod subquery;
8mod window;
9
10// Re-export all public types
11pub use aggregate::AggregateFunction;
12pub use functions::eval_function;
13pub use ops::{BinaryOp, UnaryOp};
14pub use subquery::{SubqueryCompareOp, SubqueryType};
15pub use window::{
16    FrameBound, FrameUnit, WindowFrame, WindowFunction, WindowFunctionType, WindowOrderByExpr,
17    WindowSortOrder,
18};
19
20use featherdb_core::{Error, Result, Value};
21use std::collections::HashMap;
22
23/// Expression tree
24#[derive(Debug, Clone, PartialEq)]
25pub enum Expr {
26    /// Column reference
27    Column {
28        table: Option<String>,
29        name: String,
30        index: Option<usize>,
31    },
32
33    /// Literal value
34    Literal(Value),
35
36    /// Parameter placeholder for prepared statements
37    /// Index is 0-based (? is index 0, $1 maps to index 0, $2 to index 1, etc.)
38    Parameter { index: usize },
39
40    /// Binary operation
41    BinaryOp {
42        left: Box<Expr>,
43        op: BinaryOp,
44        right: Box<Expr>,
45    },
46
47    /// Unary operation
48    UnaryOp { op: UnaryOp, expr: Box<Expr> },
49
50    /// Function call
51    Function { name: String, args: Vec<Expr> },
52
53    /// Aggregate function
54    Aggregate {
55        func: AggregateFunction,
56        arg: Option<Box<Expr>>,
57        distinct: bool,
58    },
59
60    /// CASE WHEN ... THEN ... ELSE ... END
61    Case {
62        operand: Option<Box<Expr>>,
63        when_clauses: Vec<(Expr, Expr)>,
64        else_result: Option<Box<Expr>>,
65    },
66
67    /// BETWEEN
68    Between {
69        expr: Box<Expr>,
70        low: Box<Expr>,
71        high: Box<Expr>,
72        negated: bool,
73    },
74
75    /// IN (list)
76    InList {
77        expr: Box<Expr>,
78        list: Vec<Expr>,
79        negated: bool,
80    },
81
82    /// LIKE pattern
83    Like {
84        expr: Box<Expr>,
85        pattern: String,
86        negated: bool,
87    },
88
89    /// Wildcard (*) for SELECT *
90    Wildcard,
91
92    /// Qualified wildcard (table.*) for SELECT table.*
93    QualifiedWildcard(String),
94
95    /// Window function expression
96    Window(WindowFunction),
97
98    /// Scalar subquery - returns a single value
99    /// e.g., SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = users.id)
100    ScalarSubquery {
101        /// Unique identifier for the subquery (for correlation tracking)
102        subquery_id: usize,
103        /// Whether this subquery is correlated (references outer query)
104        correlated: bool,
105    },
106
107    /// EXISTS subquery
108    /// e.g., WHERE EXISTS (SELECT 1 FROM orders WHERE user_id = users.id)
109    Exists { subquery_id: usize, negated: bool },
110
111    /// IN subquery
112    /// e.g., WHERE id IN (SELECT user_id FROM orders)
113    InSubquery {
114        expr: Box<Expr>,
115        subquery_id: usize,
116        negated: bool,
117    },
118
119    /// ANY/SOME subquery comparison
120    /// e.g., WHERE x > ANY (SELECT value FROM t)
121    AnySubquery {
122        expr: Box<Expr>,
123        op: SubqueryCompareOp,
124        subquery_id: usize,
125    },
126
127    /// ALL subquery comparison
128    /// e.g., WHERE x > ALL (SELECT value FROM t)
129    AllSubquery {
130        expr: Box<Expr>,
131        op: SubqueryCompareOp,
132        subquery_id: usize,
133    },
134}
135
136impl Expr {
137    /// Create a column reference
138    pub fn column(name: impl Into<String>) -> Self {
139        Expr::Column {
140            table: None,
141            name: name.into(),
142            index: None,
143        }
144    }
145
146    /// Create a qualified column reference
147    pub fn qualified_column(table: impl Into<String>, name: impl Into<String>) -> Self {
148        Expr::Column {
149            table: Some(table.into()),
150            name: name.into(),
151            index: None,
152        }
153    }
154
155    /// Create a literal
156    pub fn literal(value: impl Into<Value>) -> Self {
157        Expr::Literal(value.into())
158    }
159
160    /// Create a binary operation
161    pub fn binary(left: Expr, op: BinaryOp, right: Expr) -> Self {
162        Expr::BinaryOp {
163            left: Box::new(left),
164            op,
165            right: Box::new(right),
166        }
167    }
168
169    /// Create an AND expression
170    pub fn and(left: Expr, right: Expr) -> Self {
171        Expr::binary(left, BinaryOp::And, right)
172    }
173
174    /// Create an OR expression
175    pub fn or(left: Expr, right: Expr) -> Self {
176        Expr::binary(left, BinaryOp::Or, right)
177    }
178
179    /// Create a parameter placeholder
180    pub fn parameter(index: usize) -> Self {
181        Expr::Parameter { index }
182    }
183
184    /// Pre-resolve column indices from a column map so that eval() can use
185    /// direct index access (O(1)) instead of HashMap lookups per row.
186    /// Call once before a row loop, then use the resolved expression for all rows.
187    pub fn resolve_indices(&mut self, column_map: &HashMap<String, usize>) {
188        match self {
189            Expr::Column {
190                name, table, index, ..
191            } => {
192                if index.is_some() {
193                    return; // Already resolved
194                }
195                let resolved = if let Some(t) = table {
196                    let key = format!("{}.{}", t, name);
197                    column_map
198                        .get(&key)
199                        .or_else(|| column_map.get(name.as_str()))
200                        .or_else(|| column_map.get(&key.to_ascii_lowercase()))
201                        .or_else(|| column_map.get(&name.to_ascii_lowercase()))
202                        .copied()
203                } else {
204                    column_map
205                        .get(name.as_str())
206                        .or_else(|| {
207                            column_map
208                                .iter()
209                                .find(|(k, _)| {
210                                    k.strip_suffix(name.as_str())
211                                        .is_some_and(|prefix| prefix.ends_with('.'))
212                                })
213                                .map(|(_, v)| v)
214                        })
215                        .or_else(|| column_map.get(&name.to_ascii_lowercase()))
216                        .copied()
217                };
218                *index = resolved;
219            }
220            Expr::BinaryOp { left, right, .. } => {
221                left.resolve_indices(column_map);
222                right.resolve_indices(column_map);
223            }
224            Expr::UnaryOp { expr, .. } => expr.resolve_indices(column_map),
225            Expr::Function { args, .. } => {
226                for arg in args {
227                    arg.resolve_indices(column_map);
228                }
229            }
230            Expr::Aggregate { arg: Some(e), .. } => e.resolve_indices(column_map),
231            Expr::Case {
232                operand,
233                when_clauses,
234                else_result,
235            } => {
236                if let Some(e) = operand {
237                    e.resolve_indices(column_map);
238                }
239                for (w, t) in when_clauses {
240                    w.resolve_indices(column_map);
241                    t.resolve_indices(column_map);
242                }
243                if let Some(e) = else_result {
244                    e.resolve_indices(column_map);
245                }
246            }
247            Expr::Between {
248                expr, low, high, ..
249            } => {
250                expr.resolve_indices(column_map);
251                low.resolve_indices(column_map);
252                high.resolve_indices(column_map);
253            }
254            Expr::InList { expr, list, .. } => {
255                expr.resolve_indices(column_map);
256                for e in list {
257                    e.resolve_indices(column_map);
258                }
259            }
260            Expr::Like { expr, .. } => expr.resolve_indices(column_map),
261            Expr::InSubquery { expr, .. } => expr.resolve_indices(column_map),
262            Expr::AnySubquery { expr, .. } => expr.resolve_indices(column_map),
263            Expr::AllSubquery { expr, .. } => expr.resolve_indices(column_map),
264            _ => {} // Literals, parameters, etc. — nothing to resolve
265        }
266    }
267
268    /// Collect all resolved column indices referenced by this expression.
269    pub fn referenced_columns(&self, out: &mut Vec<usize>) {
270        match self {
271            Expr::Column { index: Some(i), .. } => out.push(*i),
272            Expr::BinaryOp { left, right, .. } => {
273                left.referenced_columns(out);
274                right.referenced_columns(out);
275            }
276            Expr::UnaryOp { expr, .. } => expr.referenced_columns(out),
277            Expr::Function { args, .. } => {
278                for arg in args {
279                    arg.referenced_columns(out);
280                }
281            }
282            Expr::Aggregate { arg: Some(e), .. } => e.referenced_columns(out),
283            Expr::Case {
284                operand,
285                when_clauses,
286                else_result,
287            } => {
288                if let Some(e) = operand {
289                    e.referenced_columns(out);
290                }
291                for (w, t) in when_clauses {
292                    w.referenced_columns(out);
293                    t.referenced_columns(out);
294                }
295                if let Some(e) = else_result {
296                    e.referenced_columns(out);
297                }
298            }
299            Expr::Between {
300                expr, low, high, ..
301            } => {
302                expr.referenced_columns(out);
303                low.referenced_columns(out);
304                high.referenced_columns(out);
305            }
306            Expr::InList { expr, list, .. } => {
307                expr.referenced_columns(out);
308                for e in list {
309                    e.referenced_columns(out);
310                }
311            }
312            Expr::Like { expr, .. } => expr.referenced_columns(out),
313            Expr::InSubquery { expr, .. } => expr.referenced_columns(out),
314            Expr::AnySubquery { expr, .. } => expr.referenced_columns(out),
315            Expr::AllSubquery { expr, .. } => expr.referenced_columns(out),
316            _ => {} // Literals, parameters, subqueries without expr, etc.
317        }
318    }
319
320    /// Evaluate expression against a row
321    pub fn eval(&self, row: &[Value], column_map: &HashMap<String, usize>) -> Result<Value> {
322        match self {
323            Expr::Column { name, index, table } => {
324                if let Some(idx) = index {
325                    Ok(row.get(*idx).cloned().unwrap_or(Value::Null))
326                } else if let Some(t) = table {
327                    // Qualified name: try "table.name" first
328                    let key = format!("{}.{}", t, name);
329                    let idx = column_map
330                        .get(&key)
331                        .or_else(|| column_map.get(name.as_str()))
332                        .or_else(|| column_map.get(&key.to_ascii_lowercase()))
333                        .or_else(|| column_map.get(&name.to_ascii_lowercase()));
334                    match idx {
335                        Some(&i) => Ok(row.get(i).cloned().unwrap_or(Value::Null)),
336                        None => Err(Error::ColumnNotFound {
337                            column: name.clone(),
338                            table: t.clone(),
339                            suggestion: None,
340                        }),
341                    }
342                } else {
343                    // Unqualified name (most common after predicate pushdown):
344                    // Try direct O(1) lookup first, then suffix match for
345                    // col_maps that only have qualified "table.col" entries
346                    let idx = column_map
347                        .get(name.as_str())
348                        .or_else(|| {
349                            column_map
350                                .iter()
351                                .find(|(k, _)| {
352                                    k.strip_suffix(name.as_str())
353                                        .is_some_and(|prefix| prefix.ends_with('.'))
354                                })
355                                .map(|(_, v)| v)
356                        })
357                        .or_else(|| {
358                            let lower = name.to_ascii_lowercase();
359                            column_map.get(lower.as_str()).or_else(|| {
360                                column_map
361                                    .iter()
362                                    .find(|(k, _)| {
363                                        k.to_ascii_lowercase()
364                                            .strip_suffix(lower.as_str())
365                                            .is_some_and(|prefix| prefix.ends_with('.'))
366                                    })
367                                    .map(|(_, v)| v)
368                            })
369                        });
370                    match idx {
371                        Some(&i) => Ok(row.get(i).cloned().unwrap_or(Value::Null)),
372                        None => Err(Error::ColumnNotFound {
373                            column: name.clone(),
374                            table: String::new(),
375                            suggestion: None,
376                        }),
377                    }
378                }
379            }
380
381            Expr::Literal(v) => Ok(v.clone()),
382
383            Expr::BinaryOp { left, op, right } => {
384                let l = left.eval(row, column_map)?;
385                let r = right.eval(row, column_map)?;
386                op.eval(&l, &r)
387            }
388
389            Expr::UnaryOp { op, expr } => {
390                let v = expr.eval(row, column_map)?;
391                op.eval(&v)
392            }
393
394            Expr::Case {
395                operand,
396                when_clauses,
397                else_result,
398            } => {
399                let operand_val = operand
400                    .as_ref()
401                    .map(|e| e.eval(row, column_map))
402                    .transpose()?;
403
404                for (when_expr, then_expr) in when_clauses {
405                    let when_val = when_expr.eval(row, column_map)?;
406                    let matches = if let Some(ref op_val) = operand_val {
407                        when_val == *op_val
408                    } else {
409                        when_val.as_bool().unwrap_or(false)
410                    };
411
412                    if matches {
413                        return then_expr.eval(row, column_map);
414                    }
415                }
416
417                if let Some(else_expr) = else_result {
418                    else_expr.eval(row, column_map)
419                } else {
420                    Ok(Value::Null)
421                }
422            }
423
424            Expr::Between {
425                expr,
426                low,
427                high,
428                negated,
429            } => {
430                let v = expr.eval(row, column_map)?;
431                let l = low.eval(row, column_map)?;
432                let h = high.eval(row, column_map)?;
433
434                let in_range = v >= l && v <= h;
435                Ok(Value::Boolean(if *negated { !in_range } else { in_range }))
436            }
437
438            Expr::InList {
439                expr,
440                list,
441                negated,
442            } => {
443                let v = expr.eval(row, column_map)?;
444                let mut found = false;
445
446                for item in list {
447                    let item_val = item.eval(row, column_map)?;
448                    if v == item_val {
449                        found = true;
450                        break;
451                    }
452                }
453
454                Ok(Value::Boolean(if *negated { !found } else { found }))
455            }
456
457            Expr::Like {
458                expr,
459                pattern,
460                negated,
461            } => {
462                let v = expr.eval(row, column_map)?;
463                let matches = match v {
464                    Value::Text(s) => helpers::like_match(&s, pattern),
465                    _ => false,
466                };
467                Ok(Value::Boolean(if *negated { !matches } else { matches }))
468            }
469
470            Expr::Function { name, args } => {
471                let arg_values: Vec<Value> = args
472                    .iter()
473                    .map(|a| a.eval(row, column_map))
474                    .collect::<Result<_>>()?;
475
476                eval_function(name, &arg_values)
477            }
478
479            Expr::Aggregate { .. } => {
480                // Aggregates are handled specially by the executor
481                Err(Error::Internal(
482                    "Aggregate should be handled by executor".into(),
483                ))
484            }
485
486            Expr::Parameter { index } => {
487                // Parameters should be substituted before execution
488                Err(Error::Internal(format!(
489                    "Parameter ${} was not substituted before execution",
490                    index + 1
491                )))
492            }
493
494            Expr::Wildcard | Expr::QualifiedWildcard(_) => {
495                Err(Error::Internal("Wildcard should be expanded".into()))
496            }
497
498            Expr::Window(_) => {
499                // Window functions are handled specially by the executor
500                Err(Error::Internal(
501                    "Window function should be handled by executor".into(),
502                ))
503            }
504
505            Expr::ScalarSubquery { .. } => {
506                // Scalar subqueries are handled specially by the executor
507                Err(Error::Internal(
508                    "Scalar subquery should be handled by executor".into(),
509                ))
510            }
511
512            Expr::Exists { .. } => {
513                // EXISTS subqueries are handled specially by the executor
514                Err(Error::Internal(
515                    "EXISTS subquery should be handled by executor".into(),
516                ))
517            }
518
519            Expr::InSubquery { .. } => {
520                // IN subqueries are handled specially by the executor
521                Err(Error::Internal(
522                    "IN subquery should be handled by executor".into(),
523                ))
524            }
525
526            Expr::AnySubquery { .. } => {
527                // ANY subqueries are handled specially by the executor
528                Err(Error::Internal(
529                    "ANY subquery should be handled by executor".into(),
530                ))
531            }
532
533            Expr::AllSubquery { .. } => {
534                // ALL subqueries are handled specially by the executor
535                Err(Error::Internal(
536                    "ALL subquery should be handled by executor".into(),
537                ))
538            }
539        }
540    }
541
542    /// Check if expression contains aggregates
543    pub fn contains_aggregate(&self) -> bool {
544        match self {
545            Expr::Aggregate { .. } => true,
546            Expr::BinaryOp { left, right, .. } => {
547                left.contains_aggregate() || right.contains_aggregate()
548            }
549            Expr::UnaryOp { expr, .. } => expr.contains_aggregate(),
550            Expr::Function { args, .. } => args.iter().any(|a| a.contains_aggregate()),
551            Expr::Case {
552                operand,
553                when_clauses,
554                else_result,
555            } => {
556                operand
557                    .as_ref()
558                    .map(|e| e.contains_aggregate())
559                    .unwrap_or(false)
560                    || when_clauses
561                        .iter()
562                        .any(|(w, t)| w.contains_aggregate() || t.contains_aggregate())
563                    || else_result
564                        .as_ref()
565                        .map(|e| e.contains_aggregate())
566                        .unwrap_or(false)
567            }
568            Expr::Between {
569                expr, low, high, ..
570            } => expr.contains_aggregate() || low.contains_aggregate() || high.contains_aggregate(),
571            Expr::InList { expr, list, .. } => {
572                expr.contains_aggregate() || list.iter().any(|e| e.contains_aggregate())
573            }
574            Expr::Like { expr, .. } => expr.contains_aggregate(),
575            Expr::InSubquery { expr, .. } => expr.contains_aggregate(),
576            Expr::AnySubquery { expr, .. } => expr.contains_aggregate(),
577            Expr::AllSubquery { expr, .. } => expr.contains_aggregate(),
578            _ => false,
579        }
580    }
581
582    /// Collect all column references in this expression
583    /// Returns tuples of (table, column_name)
584    pub fn collect_column_references(&self) -> Vec<(Option<String>, String)> {
585        let mut refs = Vec::new();
586        self.collect_column_references_recursive(&mut refs);
587        refs
588    }
589
590    fn collect_column_references_recursive(&self, refs: &mut Vec<(Option<String>, String)>) {
591        match self {
592            Expr::Column { table, name, .. } => {
593                refs.push((table.clone(), name.clone()));
594            }
595            Expr::BinaryOp { left, right, .. } => {
596                left.collect_column_references_recursive(refs);
597                right.collect_column_references_recursive(refs);
598            }
599            Expr::UnaryOp { expr, .. } => {
600                expr.collect_column_references_recursive(refs);
601            }
602            Expr::Function { args, .. } => {
603                for arg in args {
604                    arg.collect_column_references_recursive(refs);
605                }
606            }
607            Expr::Aggregate { arg: Some(e), .. } => {
608                e.collect_column_references_recursive(refs);
609            }
610            Expr::Aggregate { arg: None, .. } => {}
611            Expr::Case {
612                operand,
613                when_clauses,
614                else_result,
615            } => {
616                if let Some(e) = operand {
617                    e.collect_column_references_recursive(refs);
618                }
619                for (when, then) in when_clauses {
620                    when.collect_column_references_recursive(refs);
621                    then.collect_column_references_recursive(refs);
622                }
623                if let Some(e) = else_result {
624                    e.collect_column_references_recursive(refs);
625                }
626            }
627            Expr::Between {
628                expr, low, high, ..
629            } => {
630                expr.collect_column_references_recursive(refs);
631                low.collect_column_references_recursive(refs);
632                high.collect_column_references_recursive(refs);
633            }
634            Expr::InList { expr, list, .. } => {
635                expr.collect_column_references_recursive(refs);
636                for item in list {
637                    item.collect_column_references_recursive(refs);
638                }
639            }
640            Expr::Like { expr, .. } => {
641                expr.collect_column_references_recursive(refs);
642            }
643            Expr::InSubquery { expr, .. } => {
644                expr.collect_column_references_recursive(refs);
645            }
646            Expr::AnySubquery { expr, .. } => {
647                expr.collect_column_references_recursive(refs);
648            }
649            Expr::AllSubquery { expr, .. } => {
650                expr.collect_column_references_recursive(refs);
651            }
652            Expr::Window(window) => {
653                for expr in &window.partition_by {
654                    expr.collect_column_references_recursive(refs);
655                }
656                for order_expr in &window.order_by {
657                    order_expr.expr.collect_column_references_recursive(refs);
658                }
659                // Also collect from window function arguments
660                match &window.function {
661                    WindowFunctionType::Lag { expr, default, .. }
662                    | WindowFunctionType::Lead { expr, default, .. } => {
663                        expr.collect_column_references_recursive(refs);
664                        if let Some(d) = default {
665                            d.collect_column_references_recursive(refs);
666                        }
667                    }
668                    WindowFunctionType::FirstValue(e)
669                    | WindowFunctionType::LastValue(e)
670                    | WindowFunctionType::Sum(e)
671                    | WindowFunctionType::Avg(e)
672                    | WindowFunctionType::Min(e)
673                    | WindowFunctionType::Max(e) => {
674                        e.collect_column_references_recursive(refs);
675                    }
676                    WindowFunctionType::NthValue(e, _) => {
677                        e.collect_column_references_recursive(refs);
678                    }
679                    WindowFunctionType::Count(Some(e)) => {
680                        e.collect_column_references_recursive(refs);
681                    }
682                    _ => {}
683                }
684            }
685            // Literals, parameters, wildcards don't reference columns
686            _ => {}
687        }
688    }
689
690    /// Check if expression contains window functions
691    pub fn contains_window_function(&self) -> bool {
692        match self {
693            Expr::Window(_) => true,
694            Expr::BinaryOp { left, right, .. } => {
695                left.contains_window_function() || right.contains_window_function()
696            }
697            Expr::UnaryOp { expr, .. } => expr.contains_window_function(),
698            Expr::Function { args, .. } => args.iter().any(|a| a.contains_window_function()),
699            Expr::Case {
700                operand,
701                when_clauses,
702                else_result,
703            } => {
704                operand
705                    .as_ref()
706                    .map(|e| e.contains_window_function())
707                    .unwrap_or(false)
708                    || when_clauses
709                        .iter()
710                        .any(|(w, t)| w.contains_window_function() || t.contains_window_function())
711                    || else_result
712                        .as_ref()
713                        .map(|e| e.contains_window_function())
714                        .unwrap_or(false)
715            }
716            Expr::Between {
717                expr, low, high, ..
718            } => {
719                expr.contains_window_function()
720                    || low.contains_window_function()
721                    || high.contains_window_function()
722            }
723            Expr::InList { expr, list, .. } => {
724                expr.contains_window_function() || list.iter().any(|e| e.contains_window_function())
725            }
726            Expr::Like { expr, .. } => expr.contains_window_function(),
727            Expr::InSubquery { expr, .. } => expr.contains_window_function(),
728            Expr::AnySubquery { expr, .. } => expr.contains_window_function(),
729            Expr::AllSubquery { expr, .. } => expr.contains_window_function(),
730            _ => false,
731        }
732    }
733
734    /// Check if expression contains subqueries
735    pub fn contains_subquery(&self) -> bool {
736        match self {
737            Expr::ScalarSubquery { .. }
738            | Expr::Exists { .. }
739            | Expr::InSubquery { .. }
740            | Expr::AnySubquery { .. }
741            | Expr::AllSubquery { .. } => true,
742            Expr::BinaryOp { left, right, .. } => {
743                left.contains_subquery() || right.contains_subquery()
744            }
745            Expr::UnaryOp { expr, .. } => expr.contains_subquery(),
746            Expr::Function { args, .. } => args.iter().any(|a| a.contains_subquery()),
747            Expr::Case {
748                operand,
749                when_clauses,
750                else_result,
751            } => {
752                operand
753                    .as_ref()
754                    .map(|e| e.contains_subquery())
755                    .unwrap_or(false)
756                    || when_clauses
757                        .iter()
758                        .any(|(w, t)| w.contains_subquery() || t.contains_subquery())
759                    || else_result
760                        .as_ref()
761                        .map(|e| e.contains_subquery())
762                        .unwrap_or(false)
763            }
764            Expr::Between {
765                expr, low, high, ..
766            } => expr.contains_subquery() || low.contains_subquery() || high.contains_subquery(),
767            Expr::InList { expr, list, .. } => {
768                expr.contains_subquery() || list.iter().any(|e| e.contains_subquery())
769            }
770            Expr::Like { expr, .. } => expr.contains_subquery(),
771            _ => false,
772        }
773    }
774}
775
776#[cfg(test)]
777mod tests {
778    use super::*;
779
780    #[test]
781    fn test_expr_column() {
782        let expr = Expr::column("name");
783        assert!(matches!(expr, Expr::Column { .. }));
784    }
785
786    #[test]
787    fn test_expr_qualified_column() {
788        let expr = Expr::qualified_column("users", "name");
789        match expr {
790            Expr::Column { table, name, .. } => {
791                assert_eq!(table, Some("users".to_string()));
792                assert_eq!(name, "name");
793            }
794            _ => panic!("Expected Column variant"),
795        }
796    }
797
798    #[test]
799    fn test_expr_literal() {
800        let expr = Expr::literal(Value::Integer(42));
801        assert_eq!(expr, Expr::Literal(Value::Integer(42)));
802    }
803
804    #[test]
805    fn test_expr_binary() {
806        let expr = Expr::binary(
807            Expr::literal(Value::Integer(5)),
808            BinaryOp::Add,
809            Expr::literal(Value::Integer(3)),
810        );
811        assert!(matches!(expr, Expr::BinaryOp { .. }));
812    }
813
814    #[test]
815    fn test_expr_and() {
816        let expr = Expr::and(
817            Expr::literal(Value::Boolean(true)),
818            Expr::literal(Value::Boolean(false)),
819        );
820        match expr {
821            Expr::BinaryOp { op, .. } => assert_eq!(op, BinaryOp::And),
822            _ => panic!("Expected BinaryOp"),
823        }
824    }
825
826    #[test]
827    fn test_expr_or() {
828        let expr = Expr::or(
829            Expr::literal(Value::Boolean(true)),
830            Expr::literal(Value::Boolean(false)),
831        );
832        match expr {
833            Expr::BinaryOp { op, .. } => assert_eq!(op, BinaryOp::Or),
834            _ => panic!("Expected BinaryOp"),
835        }
836    }
837
838    #[test]
839    fn test_expr_parameter() {
840        let expr = Expr::parameter(0);
841        assert_eq!(expr, Expr::Parameter { index: 0 });
842    }
843
844    #[test]
845    fn test_expr_eval_literal() {
846        let expr = Expr::literal(Value::Integer(42));
847        let row = vec![];
848        let column_map = HashMap::new();
849        let result = expr.eval(&row, &column_map).unwrap();
850        assert_eq!(result, Value::Integer(42));
851    }
852
853    #[test]
854    fn test_expr_eval_binary_op() {
855        let expr = Expr::binary(
856            Expr::literal(Value::Integer(5)),
857            BinaryOp::Add,
858            Expr::literal(Value::Integer(3)),
859        );
860        let row = vec![];
861        let column_map = HashMap::new();
862        let result = expr.eval(&row, &column_map).unwrap();
863        assert_eq!(result, Value::Integer(8));
864    }
865
866    #[test]
867    fn test_contains_aggregate() {
868        let agg_expr = Expr::Aggregate {
869            func: AggregateFunction::Count,
870            arg: None,
871            distinct: false,
872        };
873        assert!(agg_expr.contains_aggregate());
874
875        let non_agg = Expr::literal(Value::Integer(42));
876        assert!(!non_agg.contains_aggregate());
877    }
878
879    #[test]
880    fn test_contains_window_function() {
881        let window_expr = Expr::Window(WindowFunction {
882            function: WindowFunctionType::RowNumber,
883            partition_by: vec![],
884            order_by: vec![],
885            frame: None,
886        });
887        assert!(window_expr.contains_window_function());
888
889        let non_window = Expr::literal(Value::Integer(42));
890        assert!(!non_window.contains_window_function());
891    }
892
893    #[test]
894    fn test_contains_subquery() {
895        let subquery_expr = Expr::ScalarSubquery {
896            subquery_id: 0,
897            correlated: false,
898        };
899        assert!(subquery_expr.contains_subquery());
900
901        let non_subquery = Expr::literal(Value::Integer(42));
902        assert!(!non_subquery.contains_subquery());
903    }
904
905    #[test]
906    fn test_collect_column_references() {
907        let expr = Expr::BinaryOp {
908            left: Box::new(Expr::Column {
909                table: Some("users".to_string()),
910                name: "age".to_string(),
911                index: None,
912            }),
913            op: BinaryOp::Gt,
914            right: Box::new(Expr::Column {
915                table: None,
916                name: "salary".to_string(),
917                index: None,
918            }),
919        };
920
921        let refs = expr.collect_column_references();
922        assert_eq!(refs.len(), 2);
923        assert!(refs.contains(&(Some("users".to_string()), "age".to_string())));
924        assert!(refs.contains(&(None, "salary".to_string())));
925    }
926
927    // =============================================================================
928    // Additional eval() tests
929    // =============================================================================
930
931    #[test]
932    fn test_eval_column_lookup() {
933        let row = vec![
934            Value::Integer(1),
935            Value::Text("Alice".into()),
936            Value::Integer(30),
937        ];
938        let col_map: HashMap<String, usize> = [("id", 0), ("name", 1), ("age", 2)]
939            .iter()
940            .map(|(k, v)| (k.to_string(), *v))
941            .collect();
942
943        let expr = Expr::column("name");
944        let result = expr.eval(&row, &col_map).unwrap();
945        assert_eq!(result, Value::Text("Alice".into()));
946
947        let expr = Expr::column("age");
948        let result = expr.eval(&row, &col_map).unwrap();
949        assert_eq!(result, Value::Integer(30));
950    }
951
952    #[test]
953    fn test_eval_qualified_column() {
954        let row = vec![Value::Integer(100)];
955        let mut col_map = HashMap::new();
956        col_map.insert("users.salary".to_string(), 0);
957
958        let expr = Expr::qualified_column("users", "salary");
959        let result = expr.eval(&row, &col_map).unwrap();
960        assert_eq!(result, Value::Integer(100));
961    }
962
963    #[test]
964    fn test_eval_column_not_found() {
965        let row = vec![Value::Integer(1)];
966        let col_map = HashMap::new();
967
968        let expr = Expr::column("missing");
969        let result = expr.eval(&row, &col_map);
970        assert!(result.is_err());
971        assert!(matches!(result.unwrap_err(), Error::ColumnNotFound { .. }));
972    }
973
974    #[test]
975    fn test_eval_between_inclusive() {
976        let row = vec![];
977        let col_map = HashMap::new();
978
979        // Test value = low boundary
980        let expr = Expr::Between {
981            expr: Box::new(Expr::literal(Value::Integer(5))),
982            low: Box::new(Expr::literal(Value::Integer(5))),
983            high: Box::new(Expr::literal(Value::Integer(10))),
984            negated: false,
985        };
986        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
987
988        // Test value = high boundary
989        let expr = Expr::Between {
990            expr: Box::new(Expr::literal(Value::Integer(10))),
991            low: Box::new(Expr::literal(Value::Integer(5))),
992            high: Box::new(Expr::literal(Value::Integer(10))),
993            negated: false,
994        };
995        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
996
997        // Test value in range
998        let expr = Expr::Between {
999            expr: Box::new(Expr::literal(Value::Integer(7))),
1000            low: Box::new(Expr::literal(Value::Integer(5))),
1001            high: Box::new(Expr::literal(Value::Integer(10))),
1002            negated: false,
1003        };
1004        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1005
1006        // Test value out of range (below)
1007        let expr = Expr::Between {
1008            expr: Box::new(Expr::literal(Value::Integer(3))),
1009            low: Box::new(Expr::literal(Value::Integer(5))),
1010            high: Box::new(Expr::literal(Value::Integer(10))),
1011            negated: false,
1012        };
1013        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1014
1015        // Test value out of range (above)
1016        let expr = Expr::Between {
1017            expr: Box::new(Expr::literal(Value::Integer(15))),
1018            low: Box::new(Expr::literal(Value::Integer(5))),
1019            high: Box::new(Expr::literal(Value::Integer(10))),
1020            negated: false,
1021        };
1022        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1023    }
1024
1025    #[test]
1026    fn test_eval_between_negated() {
1027        let row = vec![];
1028        let col_map = HashMap::new();
1029
1030        // NOT BETWEEN - value in range should return false
1031        let expr = Expr::Between {
1032            expr: Box::new(Expr::literal(Value::Integer(7))),
1033            low: Box::new(Expr::literal(Value::Integer(5))),
1034            high: Box::new(Expr::literal(Value::Integer(10))),
1035            negated: true,
1036        };
1037        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1038
1039        // NOT BETWEEN - value out of range should return true
1040        let expr = Expr::Between {
1041            expr: Box::new(Expr::literal(Value::Integer(15))),
1042            low: Box::new(Expr::literal(Value::Integer(5))),
1043            high: Box::new(Expr::literal(Value::Integer(10))),
1044            negated: true,
1045        };
1046        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1047    }
1048
1049    #[test]
1050    fn test_eval_in_list_found() {
1051        let row = vec![];
1052        let col_map = HashMap::new();
1053
1054        let expr = Expr::InList {
1055            expr: Box::new(Expr::literal(Value::Integer(5))),
1056            list: vec![
1057                Expr::literal(Value::Integer(1)),
1058                Expr::literal(Value::Integer(5)),
1059                Expr::literal(Value::Integer(10)),
1060            ],
1061            negated: false,
1062        };
1063        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1064    }
1065
1066    #[test]
1067    fn test_eval_in_list_not_found() {
1068        let row = vec![];
1069        let col_map = HashMap::new();
1070
1071        let expr = Expr::InList {
1072            expr: Box::new(Expr::literal(Value::Integer(7))),
1073            list: vec![
1074                Expr::literal(Value::Integer(1)),
1075                Expr::literal(Value::Integer(5)),
1076                Expr::literal(Value::Integer(10)),
1077            ],
1078            negated: false,
1079        };
1080        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1081    }
1082
1083    #[test]
1084    fn test_eval_in_list_negated() {
1085        let row = vec![];
1086        let col_map = HashMap::new();
1087
1088        // NOT IN - value not in list should return true
1089        let expr = Expr::InList {
1090            expr: Box::new(Expr::literal(Value::Integer(7))),
1091            list: vec![
1092                Expr::literal(Value::Integer(1)),
1093                Expr::literal(Value::Integer(5)),
1094                Expr::literal(Value::Integer(10)),
1095            ],
1096            negated: true,
1097        };
1098        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1099
1100        // NOT IN - value in list should return false
1101        let expr = Expr::InList {
1102            expr: Box::new(Expr::literal(Value::Integer(5))),
1103            list: vec![
1104                Expr::literal(Value::Integer(1)),
1105                Expr::literal(Value::Integer(5)),
1106                Expr::literal(Value::Integer(10)),
1107            ],
1108            negated: true,
1109        };
1110        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1111    }
1112
1113    #[test]
1114    fn test_eval_in_list_with_null() {
1115        let row = vec![];
1116        let col_map = HashMap::new();
1117
1118        // NULL in list - In FeatherDB, NULL == NULL uses Rust's PartialEq which returns true
1119        let expr = Expr::InList {
1120            expr: Box::new(Expr::literal(Value::Null)),
1121            list: vec![
1122                Expr::literal(Value::Integer(1)),
1123                Expr::literal(Value::Null),
1124                Expr::literal(Value::Integer(5)),
1125            ],
1126            negated: false,
1127        };
1128        // FeatherDB uses Rust's == operator, so NULL == NULL returns true
1129        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1130
1131        // Search for value with NULL in list (should not match NULL)
1132        let expr = Expr::InList {
1133            expr: Box::new(Expr::literal(Value::Integer(5))),
1134            list: vec![Expr::literal(Value::Null), Expr::literal(Value::Integer(5))],
1135            negated: false,
1136        };
1137        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1138    }
1139
1140    #[test]
1141    fn test_eval_like_percent() {
1142        let row = vec![];
1143        let col_map = HashMap::new();
1144
1145        // Starts with pattern
1146        let expr = Expr::Like {
1147            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1148            pattern: "h%".to_string(),
1149            negated: false,
1150        };
1151        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1152
1153        // Ends with pattern
1154        let expr = Expr::Like {
1155            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1156            pattern: "%lo".to_string(),
1157            negated: false,
1158        };
1159        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1160
1161        // Contains pattern
1162        let expr = Expr::Like {
1163            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1164            pattern: "%ll%".to_string(),
1165            negated: false,
1166        };
1167        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1168
1169        // No match
1170        let expr = Expr::Like {
1171            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1172            pattern: "%world%".to_string(),
1173            negated: false,
1174        };
1175        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1176    }
1177
1178    #[test]
1179    fn test_eval_like_underscore() {
1180        let row = vec![];
1181        let col_map = HashMap::new();
1182
1183        // Single character wildcard
1184        let expr = Expr::Like {
1185            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1186            pattern: "h_llo".to_string(),
1187            negated: false,
1188        };
1189        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1190
1191        // Multiple single character wildcards
1192        let expr = Expr::Like {
1193            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1194            pattern: "h__lo".to_string(),
1195            negated: false,
1196        };
1197        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1198
1199        // Wrong number of characters
1200        let expr = Expr::Like {
1201            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1202            pattern: "h_lo".to_string(),
1203            negated: false,
1204        };
1205        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1206    }
1207
1208    #[test]
1209    fn test_eval_like_exact() {
1210        let row = vec![];
1211        let col_map = HashMap::new();
1212
1213        // Exact match (no wildcards)
1214        let expr = Expr::Like {
1215            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1216            pattern: "hello".to_string(),
1217            negated: false,
1218        };
1219        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1220
1221        // Not exact match
1222        let expr = Expr::Like {
1223            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1224            pattern: "world".to_string(),
1225            negated: false,
1226        };
1227        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1228    }
1229
1230    #[test]
1231    fn test_eval_like_negated() {
1232        let row = vec![];
1233        let col_map = HashMap::new();
1234
1235        // NOT LIKE with match should return false
1236        let expr = Expr::Like {
1237            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1238            pattern: "h%".to_string(),
1239            negated: true,
1240        };
1241        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1242
1243        // NOT LIKE with no match should return true
1244        let expr = Expr::Like {
1245            expr: Box::new(Expr::literal(Value::Text("hello".into()))),
1246            pattern: "%world%".to_string(),
1247            negated: true,
1248        };
1249        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(true));
1250    }
1251
1252    #[test]
1253    fn test_eval_like_non_text() {
1254        let row = vec![];
1255        let col_map = HashMap::new();
1256
1257        // LIKE on non-text should return false (not error)
1258        let expr = Expr::Like {
1259            expr: Box::new(Expr::literal(Value::Integer(123))),
1260            pattern: "1%".to_string(),
1261            negated: false,
1262        };
1263        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Boolean(false));
1264    }
1265
1266    #[test]
1267    fn test_eval_case_simple() {
1268        let row = vec![];
1269        let col_map = HashMap::new();
1270
1271        // CASE WHEN condition matches
1272        let expr = Expr::Case {
1273            operand: None,
1274            when_clauses: vec![
1275                (
1276                    Expr::literal(Value::Boolean(false)),
1277                    Expr::literal(Value::Text("first".into())),
1278                ),
1279                (
1280                    Expr::literal(Value::Boolean(true)),
1281                    Expr::literal(Value::Text("second".into())),
1282                ),
1283            ],
1284            else_result: Some(Box::new(Expr::literal(Value::Text("else".into())))),
1285        };
1286        assert_eq!(
1287            expr.eval(&row, &col_map).unwrap(),
1288            Value::Text("second".into())
1289        );
1290    }
1291
1292    #[test]
1293    fn test_eval_case_else() {
1294        let row = vec![];
1295        let col_map = HashMap::new();
1296
1297        // CASE with no match, falls through to ELSE
1298        let expr = Expr::Case {
1299            operand: None,
1300            when_clauses: vec![
1301                (
1302                    Expr::literal(Value::Boolean(false)),
1303                    Expr::literal(Value::Text("first".into())),
1304                ),
1305                (
1306                    Expr::literal(Value::Boolean(false)),
1307                    Expr::literal(Value::Text("second".into())),
1308                ),
1309            ],
1310            else_result: Some(Box::new(Expr::literal(Value::Text("else".into())))),
1311        };
1312        assert_eq!(
1313            expr.eval(&row, &col_map).unwrap(),
1314            Value::Text("else".into())
1315        );
1316    }
1317
1318    #[test]
1319    fn test_eval_case_no_else() {
1320        let row = vec![];
1321        let col_map = HashMap::new();
1322
1323        // CASE with no match and no ELSE returns NULL
1324        let expr = Expr::Case {
1325            operand: None,
1326            when_clauses: vec![
1327                (
1328                    Expr::literal(Value::Boolean(false)),
1329                    Expr::literal(Value::Text("first".into())),
1330                ),
1331                (
1332                    Expr::literal(Value::Boolean(false)),
1333                    Expr::literal(Value::Text("second".into())),
1334                ),
1335            ],
1336            else_result: None,
1337        };
1338        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Null);
1339    }
1340
1341    #[test]
1342    fn test_eval_case_with_operand() {
1343        let row = vec![];
1344        let col_map = HashMap::new();
1345
1346        // CASE operand WHEN value THEN result
1347        let expr = Expr::Case {
1348            operand: Some(Box::new(Expr::literal(Value::Integer(2)))),
1349            when_clauses: vec![
1350                (
1351                    Expr::literal(Value::Integer(1)),
1352                    Expr::literal(Value::Text("one".into())),
1353                ),
1354                (
1355                    Expr::literal(Value::Integer(2)),
1356                    Expr::literal(Value::Text("two".into())),
1357                ),
1358                (
1359                    Expr::literal(Value::Integer(3)),
1360                    Expr::literal(Value::Text("three".into())),
1361                ),
1362            ],
1363            else_result: Some(Box::new(Expr::literal(Value::Text("other".into())))),
1364        };
1365        assert_eq!(
1366            expr.eval(&row, &col_map).unwrap(),
1367            Value::Text("two".into())
1368        );
1369
1370        // CASE operand with no match and ELSE
1371        let expr = Expr::Case {
1372            operand: Some(Box::new(Expr::literal(Value::Integer(5)))),
1373            when_clauses: vec![
1374                (
1375                    Expr::literal(Value::Integer(1)),
1376                    Expr::literal(Value::Text("one".into())),
1377                ),
1378                (
1379                    Expr::literal(Value::Integer(2)),
1380                    Expr::literal(Value::Text("two".into())),
1381                ),
1382            ],
1383            else_result: Some(Box::new(Expr::literal(Value::Text("other".into())))),
1384        };
1385        assert_eq!(
1386            expr.eval(&row, &col_map).unwrap(),
1387            Value::Text("other".into())
1388        );
1389    }
1390
1391    #[test]
1392    fn test_eval_binary_null_arithmetic() {
1393        let row = vec![];
1394        let col_map = HashMap::new();
1395
1396        // NULL + Integer should propagate NULL (SQL standard)
1397        let expr = Expr::binary(
1398            Expr::literal(Value::Null),
1399            BinaryOp::Add,
1400            Expr::literal(Value::Integer(1)),
1401        );
1402        let result = expr.eval(&row, &col_map).unwrap();
1403        assert_eq!(result, Value::Null);
1404
1405        // Integer + NULL should propagate NULL
1406        let expr = Expr::binary(
1407            Expr::literal(Value::Integer(5)),
1408            BinaryOp::Add,
1409            Expr::literal(Value::Null),
1410        );
1411        let result = expr.eval(&row, &col_map).unwrap();
1412        assert_eq!(result, Value::Null);
1413
1414        // NULL * Integer should propagate NULL
1415        let expr = Expr::binary(
1416            Expr::literal(Value::Null),
1417            BinaryOp::Mul,
1418            Expr::literal(Value::Integer(5)),
1419        );
1420        let result = expr.eval(&row, &col_map).unwrap();
1421        assert_eq!(result, Value::Null);
1422    }
1423
1424    #[test]
1425    fn test_eval_nested_binary() {
1426        let row = vec![];
1427        let col_map = HashMap::new();
1428
1429        // (1 + 2) * 3 = 9
1430        let expr = Expr::binary(
1431            Expr::binary(
1432                Expr::literal(Value::Integer(1)),
1433                BinaryOp::Add,
1434                Expr::literal(Value::Integer(2)),
1435            ),
1436            BinaryOp::Mul,
1437            Expr::literal(Value::Integer(3)),
1438        );
1439        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Integer(9));
1440
1441        // 10 - (3 + 2) = 5
1442        let expr = Expr::binary(
1443            Expr::literal(Value::Integer(10)),
1444            BinaryOp::Sub,
1445            Expr::binary(
1446                Expr::literal(Value::Integer(3)),
1447                BinaryOp::Add,
1448                Expr::literal(Value::Integer(2)),
1449            ),
1450        );
1451        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Integer(5));
1452    }
1453
1454    #[test]
1455    fn test_eval_function_upper() {
1456        let row = vec![];
1457        let col_map = HashMap::new();
1458
1459        let expr = Expr::Function {
1460            name: "UPPER".to_string(),
1461            args: vec![Expr::literal(Value::Text("hello".into()))],
1462        };
1463        assert_eq!(
1464            expr.eval(&row, &col_map).unwrap(),
1465            Value::Text("HELLO".into())
1466        );
1467    }
1468
1469    #[test]
1470    fn test_eval_function_lower() {
1471        let row = vec![];
1472        let col_map = HashMap::new();
1473
1474        let expr = Expr::Function {
1475            name: "LOWER".to_string(),
1476            args: vec![Expr::literal(Value::Text("HELLO".into()))],
1477        };
1478        assert_eq!(
1479            expr.eval(&row, &col_map).unwrap(),
1480            Value::Text("hello".into())
1481        );
1482    }
1483
1484    #[test]
1485    fn test_eval_function_length() {
1486        let row = vec![];
1487        let col_map = HashMap::new();
1488
1489        let expr = Expr::Function {
1490            name: "LENGTH".to_string(),
1491            args: vec![Expr::literal(Value::Text("hello".into()))],
1492        };
1493        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Integer(5));
1494    }
1495
1496    #[test]
1497    fn test_eval_function_coalesce() {
1498        let row = vec![];
1499        let col_map = HashMap::new();
1500
1501        // COALESCE returns first non-NULL value
1502        let expr = Expr::Function {
1503            name: "COALESCE".to_string(),
1504            args: vec![
1505                Expr::literal(Value::Null),
1506                Expr::literal(Value::Integer(5)),
1507                Expr::literal(Value::Integer(10)),
1508            ],
1509        };
1510        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Integer(5));
1511
1512        // COALESCE with all NULL returns NULL
1513        let expr = Expr::Function {
1514            name: "COALESCE".to_string(),
1515            args: vec![Expr::literal(Value::Null), Expr::literal(Value::Null)],
1516        };
1517        assert_eq!(expr.eval(&row, &col_map).unwrap(), Value::Null);
1518    }
1519
1520    #[test]
1521    fn test_eval_function_with_column() {
1522        let row = vec![Value::Text("world".into())];
1523        let mut col_map = HashMap::new();
1524        col_map.insert("greeting".to_string(), 0);
1525
1526        // UPPER(column)
1527        let expr = Expr::Function {
1528            name: "UPPER".to_string(),
1529            args: vec![Expr::column("greeting")],
1530        };
1531        assert_eq!(
1532            expr.eval(&row, &col_map).unwrap(),
1533            Value::Text("WORLD".into())
1534        );
1535    }
1536}