Skip to main content

citadel_sql/
eval.rs

1//! Expression evaluator with SQL three-valued logic.
2
3use rustc_hash::FxHashMap;
4
5use crate::error::{Result, SqlError};
6use crate::parser::{BinOp, Expr, UnaryOp};
7use crate::types::{ColumnDef, CompactString, DataType, Value};
8
9pub struct ColumnMap {
10    exact: FxHashMap<String, usize>,
11    short: FxHashMap<String, ShortMatch>,
12}
13
14#[derive(Clone)]
15enum ShortMatch {
16    Unique(usize),
17    Ambiguous,
18}
19
20impl Clone for ColumnMap {
21    fn clone(&self) -> Self {
22        Self {
23            exact: self.exact.clone(),
24            short: self.short.clone(),
25        }
26    }
27}
28
29impl ColumnMap {
30    pub fn new(columns: &[ColumnDef]) -> Self {
31        let mut exact = FxHashMap::with_capacity_and_hasher(columns.len() * 2, Default::default());
32        let mut short: FxHashMap<String, ShortMatch> =
33            FxHashMap::with_capacity_and_hasher(columns.len(), Default::default());
34
35        for (i, col) in columns.iter().enumerate() {
36            let lower = col.name.to_ascii_lowercase();
37            exact.insert(lower.clone(), i);
38
39            let unqualified = if let Some(dot) = lower.rfind('.') {
40                &lower[dot + 1..]
41            } else {
42                &lower
43            };
44            short
45                .entry(unqualified.to_string())
46                .and_modify(|e| *e = ShortMatch::Ambiguous)
47                .or_insert(ShortMatch::Unique(i));
48        }
49
50        Self { exact, short }
51    }
52
53    pub(crate) fn resolve(&self, name: &str) -> Result<usize> {
54        if let Some(&idx) = self.exact.get(name) {
55            return Ok(idx);
56        }
57        match self.short.get(name) {
58            Some(ShortMatch::Unique(idx)) => Ok(*idx),
59            Some(ShortMatch::Ambiguous) => Err(SqlError::AmbiguousColumn(name.to_string())),
60            None => Err(SqlError::ColumnNotFound(name.to_string())),
61        }
62    }
63
64    pub(crate) fn resolve_qualified(&self, table: &str, column: &str) -> Result<usize> {
65        let qualified = format!("{table}.{column}");
66        if let Some(&idx) = self.exact.get(&qualified) {
67            return Ok(idx);
68        }
69        match self.short.get(column) {
70            Some(ShortMatch::Unique(idx)) => Ok(*idx),
71            _ => Err(SqlError::ColumnNotFound(format!("{table}.{column}"))),
72        }
73    }
74}
75
76pub struct EvalCtx<'a> {
77    pub col_map: &'a ColumnMap,
78    pub row: &'a [Value],
79    pub params: &'a [Value],
80    pub excluded: Option<ExcludedRow<'a>>,
81    pub old_new: Option<OldNewRows<'a>>,
82}
83
84pub struct ExcludedRow<'a> {
85    pub col_map: &'a ColumnMap,
86    pub row: &'a [Value],
87}
88
89pub struct OldNewRows<'a> {
90    pub col_map: &'a ColumnMap,
91    pub old_row: Option<&'a [Value]>,
92    pub new_row: Option<&'a [Value]>,
93}
94
95impl<'a> EvalCtx<'a> {
96    pub fn new(col_map: &'a ColumnMap, row: &'a [Value]) -> Self {
97        Self {
98            col_map,
99            row,
100            params: &[],
101            excluded: None,
102            old_new: None,
103        }
104    }
105
106    pub fn with_params(col_map: &'a ColumnMap, row: &'a [Value], params: &'a [Value]) -> Self {
107        Self {
108            col_map,
109            row,
110            params,
111            excluded: None,
112            old_new: None,
113        }
114    }
115
116    pub fn with_excluded(
117        col_map: &'a ColumnMap,
118        row: &'a [Value],
119        excluded_col_map: &'a ColumnMap,
120        excluded_row: &'a [Value],
121    ) -> Self {
122        Self {
123            col_map,
124            row,
125            params: &[],
126            excluded: Some(ExcludedRow {
127                col_map: excluded_col_map,
128                row: excluded_row,
129            }),
130            old_new: None,
131        }
132    }
133
134    pub fn with_old_new(
135        col_map: &'a ColumnMap,
136        row: &'a [Value],
137        old_row: Option<&'a [Value]>,
138        new_row: Option<&'a [Value]>,
139    ) -> Self {
140        Self {
141            col_map,
142            row,
143            params: &[],
144            excluded: None,
145            old_new: Some(OldNewRows {
146                col_map,
147                old_row,
148                new_row,
149            }),
150        }
151    }
152}
153
154thread_local! {
155    static SCOPED_PARAMS: std::cell::Cell<(*const Value, usize)> =
156        const { std::cell::Cell::new((std::ptr::null(), 0)) };
157}
158
159/// Install positional parameters for `Expr::Parameter` resolution during `f`.
160pub fn with_scoped_params<R>(params: &[Value], f: impl FnOnce() -> R) -> R {
161    struct Guard((*const Value, usize));
162    impl Drop for Guard {
163        fn drop(&mut self) {
164            SCOPED_PARAMS.with(|slot| slot.set(self.0));
165        }
166    }
167    SCOPED_PARAMS.with(|slot| {
168        let prev = slot.get();
169        slot.set((params.as_ptr(), params.len()));
170        let _guard = Guard(prev);
171        f()
172    })
173}
174
175fn resolve_parameter(n: usize, ctx_params: &[Value]) -> Result<Value> {
176    if !ctx_params.is_empty() {
177        if n == 0 || n > ctx_params.len() {
178            return Err(SqlError::ParameterCountMismatch {
179                expected: n,
180                got: ctx_params.len(),
181            });
182        }
183        return Ok(ctx_params[n - 1].clone());
184    }
185    resolve_scoped_param(n)
186}
187
188pub fn resolve_scoped_param(n: usize) -> Result<Value> {
189    SCOPED_PARAMS.with(|slot| {
190        let (ptr, len) = slot.get();
191        if n == 0 || n > len {
192            return Err(SqlError::ParameterCountMismatch {
193                expected: n,
194                got: len,
195            });
196        }
197        // SAFETY: `with_scoped_params` keeps the slice alive for the duration of `f()`
198        // and restores the previous pointer on return. Reads only happen inside `f()`.
199        unsafe { Ok((*ptr.add(n - 1)).clone()) }
200    })
201}
202
203pub fn eval_expr(expr: &Expr, ctx: &EvalCtx) -> Result<Value> {
204    match expr {
205        Expr::Literal(v) => Ok(v.clone()),
206
207        Expr::Column(name) => {
208            let idx = ctx.col_map.resolve(name)?;
209            Ok(ctx.row[idx].clone())
210        }
211
212        Expr::QualifiedColumn { table, column } => {
213            if let Some(excluded) = ctx.excluded.as_ref() {
214                if table.eq_ignore_ascii_case("excluded") {
215                    let lowered = column.to_ascii_lowercase();
216                    let idx = excluded.col_map.resolve(&lowered)?;
217                    return Ok(excluded.row[idx].clone());
218                }
219            }
220            if let Some(on) = ctx.old_new.as_ref() {
221                if table.eq_ignore_ascii_case("old") {
222                    let lowered = column.to_ascii_lowercase();
223                    let idx = on.col_map.resolve(&lowered)?;
224                    return Ok(on.old_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
225                }
226                if table.eq_ignore_ascii_case("new") {
227                    let lowered = column.to_ascii_lowercase();
228                    let idx = on.col_map.resolve(&lowered)?;
229                    return Ok(on.new_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
230                }
231            }
232            let idx = ctx.col_map.resolve_qualified(table, column)?;
233            Ok(ctx.row[idx].clone())
234        }
235
236        Expr::BinaryOp { left, op, right } => {
237            let lval = eval_expr(left, ctx)?;
238            let rval = eval_expr(right, ctx)?;
239            eval_binary_op(&lval, *op, &rval)
240        }
241
242        Expr::UnaryOp { op, expr } => {
243            let val = eval_expr(expr, ctx)?;
244            eval_unary_op(*op, &val)
245        }
246
247        Expr::IsNull(e) => {
248            let val = eval_expr(e, ctx)?;
249            Ok(Value::Boolean(val.is_null()))
250        }
251
252        Expr::IsNotNull(e) => {
253            let val = eval_expr(e, ctx)?;
254            Ok(Value::Boolean(!val.is_null()))
255        }
256
257        Expr::Function { name, args, .. } => eval_scalar_function(name, args, ctx),
258
259        Expr::CountStar => Err(SqlError::Unsupported(
260            "COUNT(*) in non-aggregate context".into(),
261        )),
262
263        Expr::InList {
264            expr: e,
265            list,
266            negated,
267        } => {
268            let lhs = eval_expr(e, ctx)?;
269            eval_in_values(&lhs, list, ctx, *negated)
270        }
271
272        Expr::InSet {
273            expr: e,
274            values,
275            has_null,
276            negated,
277        } => {
278            let lhs = eval_expr(e, ctx)?;
279            eval_in_set(&lhs, values, *has_null, *negated)
280        }
281
282        Expr::Between {
283            expr: e,
284            low,
285            high,
286            negated,
287        } => {
288            let val = eval_expr(e, ctx)?;
289            let lo = eval_expr(low, ctx)?;
290            let hi = eval_expr(high, ctx)?;
291            eval_between(&val, &lo, &hi, *negated)
292        }
293
294        Expr::Like {
295            expr: e,
296            pattern,
297            escape,
298            negated,
299        } => {
300            let val = eval_expr(e, ctx)?;
301            let pat = eval_expr(pattern, ctx)?;
302            let esc = escape.as_ref().map(|e| eval_expr(e, ctx)).transpose()?;
303            eval_like(&val, &pat, esc.as_ref(), *negated)
304        }
305
306        Expr::Case {
307            operand,
308            conditions,
309            else_result,
310        } => eval_case(operand.as_deref(), conditions, else_result.as_deref(), ctx),
311
312        Expr::Coalesce(args) => {
313            for arg in args {
314                let val = eval_expr(arg, ctx)?;
315                if !val.is_null() {
316                    return Ok(val);
317                }
318            }
319            Ok(Value::Null)
320        }
321
322        Expr::Cast { expr: e, data_type } => {
323            let val = eval_expr(e, ctx)?;
324            eval_cast(&val, *data_type)
325        }
326
327        Expr::InSubquery { .. } | Expr::Exists { .. } | Expr::ScalarSubquery(_) => Err(
328            SqlError::Unsupported("subquery not materialized (internal error)".into()),
329        ),
330
331        Expr::Parameter(n) => resolve_parameter(*n, ctx.params),
332
333        Expr::WindowFunction { .. } => Err(SqlError::Unsupported(
334            "window functions are only allowed in SELECT columns".into(),
335        )),
336    }
337}
338
339/// Planner-level constant folding hook; shares semantics with row evaluation.
340pub fn eval_binary_op_public(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
341    eval_binary_op(left, op, right)
342}
343
344fn eval_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
345    match op {
346        BinOp::And => return eval_and(left, right),
347        BinOp::Or => return eval_or(left, right),
348        _ => {}
349    }
350
351    if left.is_null() || right.is_null() {
352        return Ok(Value::Null);
353    }
354
355    if let Some(res) = eval_temporal_op(left, op, right) {
356        return res;
357    }
358
359    match op {
360        BinOp::Eq => Ok(Value::Boolean(left == right)),
361        BinOp::NotEq => Ok(Value::Boolean(left != right)),
362        BinOp::Lt => Ok(Value::Boolean(left < right)),
363        BinOp::Gt => Ok(Value::Boolean(left > right)),
364        BinOp::LtEq => Ok(Value::Boolean(left <= right)),
365        BinOp::GtEq => Ok(Value::Boolean(left >= right)),
366        BinOp::Add => eval_arithmetic(left, right, i64::checked_add, |a, b| a + b),
367        BinOp::Sub => eval_arithmetic(left, right, i64::checked_sub, |a, b| a - b),
368        BinOp::Mul => eval_arithmetic(left, right, i64::checked_mul, |a, b| a * b),
369        BinOp::Div => {
370            match right {
371                Value::Integer(0) => return Err(SqlError::DivisionByZero),
372                Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
373                _ => {}
374            }
375            eval_arithmetic(left, right, i64::checked_div, |a, b| a / b)
376        }
377        BinOp::Mod => {
378            match right {
379                Value::Integer(0) => return Err(SqlError::DivisionByZero),
380                Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
381                _ => {}
382            }
383            eval_arithmetic(left, right, i64::checked_rem, |a, b| a % b)
384        }
385        BinOp::Concat => {
386            let ls = value_to_text(left);
387            let rs = value_to_text(right);
388            Ok(Value::Text(format!("{ls}{rs}").into()))
389        }
390        BinOp::And | BinOp::Or => unreachable!(),
391    }
392}
393
394/// Returns `Some` when `(left, op, right)` is a temporal operation; `None` to fall through.
395fn eval_temporal_op(left: &Value, op: BinOp, right: &Value) -> Option<Result<Value>> {
396    use crate::datetime as dt;
397    use std::cmp::Ordering;
398
399    let is_temporal = |v: &Value| {
400        matches!(
401            v,
402            Value::Date(_) | Value::Time(_) | Value::Timestamp(_) | Value::Interval { .. }
403        )
404    };
405    if matches!(op, BinOp::Add | BinOp::Sub)
406        && ((is_temporal(left) && matches!(right, Value::Real(_)))
407            || (matches!(left, Value::Real(_)) && is_temporal(right)))
408    {
409        return Some(Err(SqlError::TypeMismatch {
410            expected: "INTEGER or INTERVAL for date/time arithmetic (use CAST for REAL)".into(),
411            got: format!("{} and {}", left.data_type(), right.data_type()),
412        }));
413    }
414
415    match (left, op, right) {
416        (Value::Date(d), BinOp::Add, Value::Integer(n))
417        | (Value::Integer(n), BinOp::Add, Value::Date(d)) => {
418            Some(dt::add_days_to_date(*d, *n).map(Value::Date))
419        }
420        (Value::Date(d), BinOp::Sub, Value::Integer(n)) => {
421            Some(dt::add_days_to_date(*d, -*n).map(Value::Date))
422        }
423        (Value::Date(a), BinOp::Sub, Value::Date(b)) => {
424            Some(Ok(Value::Integer(*a as i64 - *b as i64)))
425        }
426        // DATE ± INTERVAL → TIMESTAMP (PG rule).
427        (
428            Value::Date(d),
429            BinOp::Add,
430            Value::Interval {
431                months,
432                days,
433                micros,
434            },
435        )
436        | (
437            Value::Interval {
438                months,
439                days,
440                micros,
441            },
442            BinOp::Add,
443            Value::Date(d),
444        ) => Some(dt::add_interval_to_date(*d, *months, *days, *micros).map(Value::Timestamp)),
445        (
446            Value::Date(d),
447            BinOp::Sub,
448            Value::Interval {
449                months,
450                days,
451                micros,
452            },
453        ) => Some(dt::add_interval_to_date(*d, -*months, -*days, -*micros).map(Value::Timestamp)),
454        (
455            Value::Timestamp(t),
456            BinOp::Add,
457            Value::Interval {
458                months,
459                days,
460                micros,
461            },
462        )
463        | (
464            Value::Interval {
465                months,
466                days,
467                micros,
468            },
469            BinOp::Add,
470            Value::Timestamp(t),
471        ) => Some(dt::add_interval_to_timestamp(*t, *months, *days, *micros).map(Value::Timestamp)),
472        (
473            Value::Timestamp(t),
474            BinOp::Sub,
475            Value::Interval {
476                months,
477                days,
478                micros,
479            },
480        ) => Some(
481            dt::add_interval_to_timestamp(*t, -*months, -*days, -*micros).map(Value::Timestamp),
482        ),
483        (Value::Timestamp(a), BinOp::Sub, Value::Timestamp(b)) => {
484            let (days, micros) = dt::subtract_timestamps(*a, *b);
485            Some(Ok(Value::Interval {
486                months: 0,
487                days,
488                micros,
489            }))
490        }
491        (
492            Value::Time(t),
493            BinOp::Add,
494            Value::Interval {
495                months,
496                days,
497                micros,
498            },
499        ) => Some(dt::add_interval_to_time(*t, *months, *days, *micros).map(Value::Time)),
500        (
501            Value::Time(t),
502            BinOp::Sub,
503            Value::Interval {
504                months,
505                days,
506                micros,
507            },
508        ) => Some(dt::add_interval_to_time(*t, -*months, -*days, -*micros).map(Value::Time)),
509        (Value::Time(a), BinOp::Sub, Value::Time(b)) => Some(Ok(Value::Interval {
510            months: 0,
511            days: 0,
512            micros: *a - *b,
513        })),
514        (
515            Value::Interval {
516                months: am,
517                days: ad,
518                micros: au,
519            },
520            BinOp::Add,
521            Value::Interval {
522                months: bm,
523                days: bd,
524                micros: bu,
525            },
526        ) => Some(Ok(Value::Interval {
527            months: am.saturating_add(*bm),
528            days: ad.saturating_add(*bd),
529            micros: au.saturating_add(*bu),
530        })),
531        (
532            Value::Interval {
533                months: am,
534                days: ad,
535                micros: au,
536            },
537            BinOp::Sub,
538            Value::Interval {
539                months: bm,
540                days: bd,
541                micros: bu,
542            },
543        ) => Some(Ok(Value::Interval {
544            months: am.saturating_sub(*bm),
545            days: ad.saturating_sub(*bd),
546            micros: au.saturating_sub(*bu),
547        })),
548        (
549            Value::Interval {
550                months,
551                days,
552                micros,
553            },
554            BinOp::Mul,
555            Value::Integer(n),
556        )
557        | (
558            Value::Integer(n),
559            BinOp::Mul,
560            Value::Interval {
561                months,
562                days,
563                micros,
564            },
565        ) => {
566            let n32 = (*n).clamp(i32::MIN as i64, i32::MAX as i64) as i32;
567            Some(Ok(Value::Interval {
568                months: months.saturating_mul(n32),
569                days: days.saturating_mul(n32),
570                micros: micros.saturating_mul(*n),
571            }))
572        }
573        // INTERVAL * REAL — fractional months → days, fractional days → micros (PG).
574        (
575            Value::Interval {
576                months,
577                days,
578                micros,
579            },
580            BinOp::Mul,
581            Value::Real(r),
582        )
583        | (
584            Value::Real(r),
585            BinOp::Mul,
586            Value::Interval {
587                months,
588                days,
589                micros,
590            },
591        ) => Some(Ok(scale_interval_by_real(*months, *days, *micros, *r))),
592        (
593            Value::Interval {
594                months,
595                days,
596                micros,
597            },
598            BinOp::Div,
599            Value::Integer(n),
600        ) if *n != 0 => Some(Ok(Value::Interval {
601            months: (*months as i64 / *n) as i32,
602            days: (*days as i64 / *n) as i32,
603            micros: *micros / *n,
604        })),
605        (
606            Value::Interval {
607                months,
608                days,
609                micros,
610            },
611            BinOp::Div,
612            Value::Real(r),
613        ) if *r != 0.0 => Some(Ok(scale_interval_by_real(*months, *days, *micros, 1.0 / r))),
614        // PG-normalized INTERVAL compare: 30-day month, 24-hour day.
615        (
616            Value::Interval {
617                months: am,
618                days: ad,
619                micros: au,
620            },
621            op,
622            Value::Interval {
623                months: bm,
624                days: bd,
625                micros: bu,
626            },
627        ) if matches!(
628            op,
629            BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
630        ) =>
631        {
632            let ord = dt::pg_normalized_interval_cmp((*am, *ad, *au), (*bm, *bd, *bu));
633            let b = match op {
634                BinOp::Eq => ord == Ordering::Equal,
635                BinOp::NotEq => ord != Ordering::Equal,
636                BinOp::Lt => ord == Ordering::Less,
637                BinOp::Gt => ord == Ordering::Greater,
638                BinOp::LtEq => ord != Ordering::Greater,
639                BinOp::GtEq => ord != Ordering::Less,
640                _ => unreachable!(),
641            };
642            Some(Ok(Value::Boolean(b)))
643        }
644        // PG rejects TIMESTAMP ± INTEGER; require CAST to INTERVAL.
645        (Value::Timestamp(_), BinOp::Add | BinOp::Sub, Value::Integer(_))
646        | (Value::Integer(_), BinOp::Add, Value::Timestamp(_)) => {
647            Some(Err(SqlError::TypeMismatch {
648                expected: "INTERVAL (use CAST or explicit unit)".into(),
649                got: format!("{} and {}", left.data_type(), right.data_type()),
650            }))
651        }
652        _ => None,
653    }
654}
655
656/// PG fractional-propagation: month frac → days (×30), day frac → micros (×86.4G).
657fn scale_interval_by_real(months: i32, days: i32, micros: i64, factor: f64) -> Value {
658    let raw_months = months as f64 * factor;
659    let whole_months = raw_months.trunc() as i64;
660    let frac_months = raw_months - whole_months as f64;
661    let months_frac_as_days = frac_months * 30.0;
662
663    let raw_days = days as f64 * factor + months_frac_as_days;
664    let whole_days = raw_days.trunc() as i64;
665    let frac_days = raw_days - whole_days as f64;
666    let days_frac_as_micros = (frac_days * crate::datetime::MICROS_PER_DAY as f64).round() as i64;
667
668    let raw_micros = (micros as f64 * factor).round() as i64;
669    let total_micros = raw_micros.saturating_add(days_frac_as_micros);
670
671    let clamp_i32 = |n: i64| n.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
672    Value::Interval {
673        months: clamp_i32(whole_months),
674        days: clamp_i32(whole_days),
675        micros: total_micros,
676    }
677}
678
679/// SQL three-valued AND: NULL AND false = false, NULL AND true = NULL
680fn eval_and(left: &Value, right: &Value) -> Result<Value> {
681    let l = to_bool_or_null(left)?;
682    let r = to_bool_or_null(right)?;
683    match (l, r) {
684        (Some(false), _) | (_, Some(false)) => Ok(Value::Boolean(false)),
685        (Some(true), Some(true)) => Ok(Value::Boolean(true)),
686        _ => Ok(Value::Null),
687    }
688}
689
690/// SQL three-valued OR: NULL OR true = true, NULL OR false = NULL
691fn eval_or(left: &Value, right: &Value) -> Result<Value> {
692    let l = to_bool_or_null(left)?;
693    let r = to_bool_or_null(right)?;
694    match (l, r) {
695        (Some(true), _) | (_, Some(true)) => Ok(Value::Boolean(true)),
696        (Some(false), Some(false)) => Ok(Value::Boolean(false)),
697        _ => Ok(Value::Null),
698    }
699}
700
701fn to_bool_or_null(val: &Value) -> Result<Option<bool>> {
702    match val {
703        Value::Boolean(b) => Ok(Some(*b)),
704        Value::Null => Ok(None),
705        Value::Integer(i) => Ok(Some(*i != 0)),
706        _ => Err(SqlError::TypeMismatch {
707            expected: "BOOLEAN".into(),
708            got: format!("{}", val.data_type()),
709        }),
710    }
711}
712
713fn eval_arithmetic(
714    left: &Value,
715    right: &Value,
716    int_op: fn(i64, i64) -> Option<i64>,
717    real_op: fn(f64, f64) -> f64,
718) -> Result<Value> {
719    match (left, right) {
720        (Value::Integer(a), Value::Integer(b)) => int_op(*a, *b)
721            .map(Value::Integer)
722            .ok_or(SqlError::IntegerOverflow),
723        (Value::Real(a), Value::Real(b)) => Ok(Value::Real(real_op(*a, *b))),
724        (Value::Integer(a), Value::Real(b)) => Ok(Value::Real(real_op(*a as f64, *b))),
725        (Value::Real(a), Value::Integer(b)) => Ok(Value::Real(real_op(*a, *b as f64))),
726        _ => Err(SqlError::TypeMismatch {
727            expected: "numeric".into(),
728            got: format!("{} and {}", left.data_type(), right.data_type()),
729        }),
730    }
731}
732
733fn eval_in_values(lhs: &Value, list: &[Expr], ctx: &EvalCtx, negated: bool) -> Result<Value> {
734    if list.is_empty() {
735        return Ok(Value::Boolean(negated));
736    }
737    if lhs.is_null() {
738        return Ok(Value::Null);
739    }
740    let mut has_null = false;
741    for item in list {
742        let rhs = eval_expr(item, ctx)?;
743        if rhs.is_null() {
744            has_null = true;
745        } else if lhs == &rhs {
746            return Ok(Value::Boolean(!negated));
747        }
748    }
749    if has_null {
750        Ok(Value::Null)
751    } else {
752        Ok(Value::Boolean(negated))
753    }
754}
755
756fn eval_in_set(
757    lhs: &Value,
758    values: &rustc_hash::FxHashSet<Value>,
759    has_null: bool,
760    negated: bool,
761) -> Result<Value> {
762    if values.is_empty() && !has_null {
763        return Ok(Value::Boolean(negated));
764    }
765    if lhs.is_null() {
766        return Ok(Value::Null);
767    }
768    if values.contains(lhs) {
769        return Ok(Value::Boolean(!negated));
770    }
771    if has_null {
772        Ok(Value::Null)
773    } else {
774        Ok(Value::Boolean(negated))
775    }
776}
777
778fn eval_unary_op(op: UnaryOp, val: &Value) -> Result<Value> {
779    if val.is_null() {
780        return Ok(Value::Null);
781    }
782    match op {
783        UnaryOp::Neg => match val {
784            Value::Integer(i) => i
785                .checked_neg()
786                .map(Value::Integer)
787                .ok_or(SqlError::IntegerOverflow),
788            Value::Real(r) => Ok(Value::Real(-r)),
789            Value::Interval {
790                months,
791                days,
792                micros,
793            } => {
794                let m = months.checked_neg().ok_or(SqlError::IntegerOverflow)?;
795                let d = days.checked_neg().ok_or(SqlError::IntegerOverflow)?;
796                let u = micros.checked_neg().ok_or(SqlError::IntegerOverflow)?;
797                Ok(Value::Interval {
798                    months: m,
799                    days: d,
800                    micros: u,
801                })
802            }
803            _ => Err(SqlError::TypeMismatch {
804                expected: "numeric or INTERVAL".into(),
805                got: format!("{}", val.data_type()),
806            }),
807        },
808        UnaryOp::Not => match val {
809            Value::Boolean(b) => Ok(Value::Boolean(!b)),
810            Value::Integer(i) => Ok(Value::Boolean(*i == 0)),
811            _ => Err(SqlError::TypeMismatch {
812                expected: "BOOLEAN".into(),
813                got: format!("{}", val.data_type()),
814            }),
815        },
816    }
817}
818
819fn value_to_text(val: &Value) -> String {
820    match val {
821        Value::Text(s) => s.to_string(),
822        Value::Integer(i) => i.to_string(),
823        Value::Real(r) => {
824            if r.fract() == 0.0 && r.is_finite() {
825                format!("{r:.1}")
826            } else {
827                format!("{r}")
828            }
829        }
830        Value::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.into(),
831        Value::Null => String::new(),
832        Value::Blob(b) => {
833            let mut s = String::with_capacity(b.len() * 2);
834            for byte in b {
835                s.push_str(&format!("{byte:02X}"));
836            }
837            s
838        }
839        Value::Date(d) => crate::datetime::format_date(*d),
840        Value::Time(t) => crate::datetime::format_time(*t),
841        Value::Timestamp(t) => crate::datetime::format_timestamp(*t),
842        Value::Interval {
843            months,
844            days,
845            micros,
846        } => crate::datetime::format_interval(*months, *days, *micros),
847    }
848}
849
850fn eval_between(val: &Value, low: &Value, high: &Value, negated: bool) -> Result<Value> {
851    if val.is_null() || low.is_null() || high.is_null() {
852        let ge = if val.is_null() || low.is_null() {
853            None
854        } else {
855            Some(*val >= *low)
856        };
857        let le = if val.is_null() || high.is_null() {
858            None
859        } else {
860            Some(*val <= *high)
861        };
862
863        let result = match (ge, le) {
864            (Some(false), _) | (_, Some(false)) => Some(false),
865            (Some(true), Some(true)) => Some(true),
866            _ => None,
867        };
868
869        return match result {
870            Some(b) => Ok(Value::Boolean(if negated { !b } else { b })),
871            None => Ok(Value::Null),
872        };
873    }
874
875    let in_range = *val >= *low && *val <= *high;
876    Ok(Value::Boolean(if negated { !in_range } else { in_range }))
877}
878
879const MAX_LIKE_PATTERN_LEN: usize = 10_000;
880
881fn eval_like(val: &Value, pattern: &Value, escape: Option<&Value>, negated: bool) -> Result<Value> {
882    if val.is_null() || pattern.is_null() {
883        return Ok(Value::Null);
884    }
885    let text = match val {
886        Value::Text(s) => s.as_str(),
887        _ => {
888            return Err(SqlError::TypeMismatch {
889                expected: "TEXT".into(),
890                got: val.data_type().to_string(),
891            })
892        }
893    };
894    let pat = match pattern {
895        Value::Text(s) => s.as_str(),
896        _ => {
897            return Err(SqlError::TypeMismatch {
898                expected: "TEXT".into(),
899                got: pattern.data_type().to_string(),
900            })
901        }
902    };
903
904    if pat.len() > MAX_LIKE_PATTERN_LEN {
905        return Err(SqlError::InvalidValue(format!(
906            "LIKE pattern too long ({} chars, max {MAX_LIKE_PATTERN_LEN})",
907            pat.len()
908        )));
909    }
910
911    let esc_char = match escape {
912        Some(Value::Text(s)) => {
913            let mut chars = s.chars();
914            let c = chars.next().ok_or_else(|| {
915                SqlError::InvalidValue("ESCAPE must be a single character".into())
916            })?;
917            if chars.next().is_some() {
918                return Err(SqlError::InvalidValue(
919                    "ESCAPE must be a single character".into(),
920                ));
921            }
922            Some(c)
923        }
924        Some(Value::Null) => return Ok(Value::Null),
925        Some(_) => {
926            return Err(SqlError::TypeMismatch {
927                expected: "TEXT".into(),
928                got: "non-text".into(),
929            })
930        }
931        None => None,
932    };
933
934    let matched = like_match(text, pat, esc_char);
935    Ok(Value::Boolean(if negated { !matched } else { matched }))
936}
937
938fn like_match(text: &str, pattern: &str, escape: Option<char>) -> bool {
939    let t: Vec<char> = text.chars().collect();
940    let p: Vec<char> = pattern.chars().collect();
941    like_match_impl(&t, &p, 0, 0, escape)
942}
943
944fn like_match_impl(
945    t: &[char],
946    p: &[char],
947    mut ti: usize,
948    mut pi: usize,
949    esc: Option<char>,
950) -> bool {
951    let mut star_pi: Option<usize> = None;
952    let mut star_ti: usize = 0;
953
954    while ti < t.len() {
955        if pi < p.len() {
956            if let Some(ec) = esc {
957                if p[pi] == ec && pi + 1 < p.len() {
958                    pi += 1;
959                    let pc_lower = p[pi].to_ascii_lowercase();
960                    let tc_lower = t[ti].to_ascii_lowercase();
961                    if pc_lower == tc_lower {
962                        pi += 1;
963                        ti += 1;
964                        continue;
965                    } else if let Some(sp) = star_pi {
966                        pi = sp + 1;
967                        star_ti += 1;
968                        ti = star_ti;
969                        continue;
970                    } else {
971                        return false;
972                    }
973                }
974            }
975            if p[pi] == '%' {
976                star_pi = Some(pi);
977                star_ti = ti;
978                pi += 1;
979                continue;
980            }
981            if p[pi] == '_' {
982                pi += 1;
983                ti += 1;
984                continue;
985            }
986            if p[pi].eq_ignore_ascii_case(&t[ti]) {
987                pi += 1;
988                ti += 1;
989                continue;
990            }
991        }
992        if let Some(sp) = star_pi {
993            pi = sp + 1;
994            star_ti += 1;
995            ti = star_ti;
996        } else {
997            return false;
998        }
999    }
1000
1001    while pi < p.len() && p[pi] == '%' {
1002        pi += 1;
1003    }
1004    pi == p.len()
1005}
1006
1007fn eval_case(
1008    operand: Option<&Expr>,
1009    conditions: &[(Expr, Expr)],
1010    else_result: Option<&Expr>,
1011    ctx: &EvalCtx,
1012) -> Result<Value> {
1013    if let Some(op_expr) = operand {
1014        let op_val = eval_expr(op_expr, ctx)?;
1015        for (cond, result) in conditions {
1016            let cond_val = eval_expr(cond, ctx)?;
1017            if !op_val.is_null() && !cond_val.is_null() && op_val == cond_val {
1018                return eval_expr(result, ctx);
1019            }
1020        }
1021    } else {
1022        for (cond, result) in conditions {
1023            let cond_val = eval_expr(cond, ctx)?;
1024            if is_truthy(&cond_val) {
1025                return eval_expr(result, ctx);
1026            }
1027        }
1028    }
1029    match else_result {
1030        Some(e) => eval_expr(e, ctx),
1031        None => Ok(Value::Null),
1032    }
1033}
1034
1035fn eval_cast(val: &Value, target: DataType) -> Result<Value> {
1036    if val.is_null() {
1037        return Ok(Value::Null);
1038    }
1039    match target {
1040        DataType::Integer => match val {
1041            Value::Integer(_) => Ok(val.clone()),
1042            Value::Real(r) => Ok(Value::Integer(*r as i64)),
1043            Value::Boolean(b) => Ok(Value::Integer(if *b { 1 } else { 0 })),
1044            Value::Text(s) => s
1045                .trim()
1046                .parse::<i64>()
1047                .map(Value::Integer)
1048                .or_else(|_| s.trim().parse::<f64>().map(|f| Value::Integer(f as i64)))
1049                .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to INTEGER"))),
1050            _ => Err(SqlError::InvalidValue(format!(
1051                "cannot cast {} to INTEGER",
1052                val.data_type()
1053            ))),
1054        },
1055        DataType::Real => match val {
1056            Value::Real(_) => Ok(val.clone()),
1057            Value::Integer(i) => Ok(Value::Real(*i as f64)),
1058            Value::Boolean(b) => Ok(Value::Real(if *b { 1.0 } else { 0.0 })),
1059            Value::Text(s) => s
1060                .trim()
1061                .parse::<f64>()
1062                .map(Value::Real)
1063                .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to REAL"))),
1064            _ => Err(SqlError::InvalidValue(format!(
1065                "cannot cast {} to REAL",
1066                val.data_type()
1067            ))),
1068        },
1069        DataType::Text => Ok(Value::Text(value_to_text(val).into())),
1070        DataType::Boolean => match val {
1071            Value::Boolean(_) => Ok(val.clone()),
1072            Value::Integer(i) => Ok(Value::Boolean(*i != 0)),
1073            Value::Text(s) => {
1074                let lower = s.trim().to_ascii_lowercase();
1075                match lower.as_str() {
1076                    "true" | "1" | "yes" | "on" => Ok(Value::Boolean(true)),
1077                    "false" | "0" | "no" | "off" => Ok(Value::Boolean(false)),
1078                    _ => Err(SqlError::InvalidValue(format!(
1079                        "cannot cast '{s}' to BOOLEAN"
1080                    ))),
1081                }
1082            }
1083            _ => Err(SqlError::InvalidValue(format!(
1084                "cannot cast {} to BOOLEAN",
1085                val.data_type()
1086            ))),
1087        },
1088        DataType::Blob => match val {
1089            Value::Blob(_) => Ok(val.clone()),
1090            Value::Text(s) => Ok(Value::Blob(s.as_bytes().to_vec())),
1091            _ => Err(SqlError::InvalidValue(format!(
1092                "cannot cast {} to BLOB",
1093                val.data_type()
1094            ))),
1095        },
1096        DataType::Null => Ok(Value::Null),
1097        DataType::Date => val.clone().coerce_into(DataType::Date).ok_or_else(|| {
1098            SqlError::InvalidValue(format!("cannot cast {} to DATE", val.data_type()))
1099        }),
1100        DataType::Time => val.clone().coerce_into(DataType::Time).ok_or_else(|| {
1101            SqlError::InvalidValue(format!("cannot cast {} to TIME", val.data_type()))
1102        }),
1103        DataType::Timestamp => val.clone().coerce_into(DataType::Timestamp).ok_or_else(|| {
1104            SqlError::InvalidValue(format!("cannot cast {} to TIMESTAMP", val.data_type()))
1105        }),
1106        DataType::Interval => val.clone().coerce_into(DataType::Interval).ok_or_else(|| {
1107            SqlError::InvalidValue(format!("cannot cast {} to INTERVAL", val.data_type()))
1108        }),
1109    }
1110}
1111
1112fn eval_scalar_function(name: &str, args: &[Expr], ctx: &EvalCtx) -> Result<Value> {
1113    let evaluated: Vec<Value> = args
1114        .iter()
1115        .map(|a| eval_expr(a, ctx))
1116        .collect::<Result<Vec<_>>>()?;
1117
1118    match name {
1119        "LENGTH" => {
1120            check_args(name, &evaluated, 1)?;
1121            match &evaluated[0] {
1122                Value::Null => Ok(Value::Null),
1123                Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
1124                Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
1125                _ => Ok(Value::Integer(
1126                    value_to_text(&evaluated[0]).chars().count() as i64
1127                )),
1128            }
1129        }
1130        "UPPER" => {
1131            check_args(name, &evaluated, 1)?;
1132            match &evaluated[0] {
1133                Value::Null => Ok(Value::Null),
1134                Value::Text(s) => Ok(Value::Text(s.to_ascii_uppercase())),
1135                _ => Ok(Value::Text(
1136                    value_to_text(&evaluated[0]).to_ascii_uppercase().into(),
1137                )),
1138            }
1139        }
1140        "LOWER" => {
1141            check_args(name, &evaluated, 1)?;
1142            match &evaluated[0] {
1143                Value::Null => Ok(Value::Null),
1144                Value::Text(s) => Ok(Value::Text(s.to_ascii_lowercase())),
1145                _ => Ok(Value::Text(
1146                    value_to_text(&evaluated[0]).to_ascii_lowercase().into(),
1147                )),
1148            }
1149        }
1150        "SUBSTR" | "SUBSTRING" => {
1151            if evaluated.len() < 2 || evaluated.len() > 3 {
1152                return Err(SqlError::InvalidValue(format!(
1153                    "{name} requires 2 or 3 arguments"
1154                )));
1155            }
1156            if evaluated.iter().any(|v| v.is_null()) {
1157                return Ok(Value::Null);
1158            }
1159            let s = value_to_text(&evaluated[0]);
1160            let chars: Vec<char> = s.chars().collect();
1161            let start = match &evaluated[1] {
1162                Value::Integer(i) => *i,
1163                _ => {
1164                    return Err(SqlError::TypeMismatch {
1165                        expected: "INTEGER".into(),
1166                        got: evaluated[1].data_type().to_string(),
1167                    })
1168                }
1169            };
1170            let len = chars.len() as i64;
1171
1172            let (begin, count) = if evaluated.len() == 3 {
1173                let cnt = match &evaluated[2] {
1174                    Value::Integer(i) => *i,
1175                    _ => {
1176                        return Err(SqlError::TypeMismatch {
1177                            expected: "INTEGER".into(),
1178                            got: evaluated[2].data_type().to_string(),
1179                        })
1180                    }
1181                };
1182                if start >= 1 {
1183                    let b = (start - 1).min(len) as usize;
1184                    let c = cnt.max(0) as usize;
1185                    (b, c)
1186                } else if start == 0 {
1187                    let c = (cnt - 1).max(0) as usize;
1188                    (0usize, c)
1189                } else {
1190                    let adjusted_cnt = (cnt + start - 1).max(0) as usize;
1191                    (0usize, adjusted_cnt)
1192                }
1193            } else if start >= 1 {
1194                let b = (start - 1).min(len) as usize;
1195                (b, chars.len() - b)
1196            } else if start == 0 {
1197                (0usize, chars.len())
1198            } else {
1199                let b = (len + start).max(0) as usize;
1200                (b, chars.len() - b)
1201            };
1202
1203            let result: String = chars.iter().skip(begin).take(count).collect();
1204            Ok(Value::Text(result.into()))
1205        }
1206        "TRIM" | "LTRIM" | "RTRIM" => {
1207            if evaluated.is_empty() || evaluated.len() > 2 {
1208                return Err(SqlError::InvalidValue(format!(
1209                    "{name} requires 1 or 2 arguments"
1210                )));
1211            }
1212            if evaluated[0].is_null() {
1213                return Ok(Value::Null);
1214            }
1215            let s = value_to_text(&evaluated[0]);
1216            let trim_chars: Vec<char> = if evaluated.len() == 2 {
1217                if evaluated[1].is_null() {
1218                    return Ok(Value::Null);
1219                }
1220                value_to_text(&evaluated[1]).chars().collect()
1221            } else {
1222                vec![' ']
1223            };
1224            let result = match name {
1225                "TRIM" => s
1226                    .trim_matches(|c: char| trim_chars.contains(&c))
1227                    .to_string(),
1228                "LTRIM" => s
1229                    .trim_start_matches(|c: char| trim_chars.contains(&c))
1230                    .to_string(),
1231                "RTRIM" => s
1232                    .trim_end_matches(|c: char| trim_chars.contains(&c))
1233                    .to_string(),
1234                _ => unreachable!(),
1235            };
1236            Ok(Value::Text(result.into()))
1237        }
1238        "REPLACE" => {
1239            check_args(name, &evaluated, 3)?;
1240            if evaluated.iter().any(|v| v.is_null()) {
1241                return Ok(Value::Null);
1242            }
1243            let s = value_to_text(&evaluated[0]);
1244            let from = value_to_text(&evaluated[1]);
1245            let to = value_to_text(&evaluated[2]);
1246            if from.is_empty() {
1247                return Ok(Value::Text(s.into()));
1248            }
1249            Ok(Value::Text(s.replace(&from, &to).into()))
1250        }
1251        "INSTR" => {
1252            check_args(name, &evaluated, 2)?;
1253            if evaluated.iter().any(|v| v.is_null()) {
1254                return Ok(Value::Null);
1255            }
1256            let haystack = value_to_text(&evaluated[0]);
1257            let needle = value_to_text(&evaluated[1]);
1258            let pos = haystack
1259                .find(&needle)
1260                .map(|i| haystack[..i].chars().count() as i64 + 1)
1261                .unwrap_or(0);
1262            Ok(Value::Integer(pos))
1263        }
1264        "CONCAT" => {
1265            if evaluated.is_empty() {
1266                return Ok(Value::Text(CompactString::default()));
1267            }
1268            let mut result = String::new();
1269            for v in &evaluated {
1270                match v {
1271                    Value::Null => {}
1272                    _ => result.push_str(&value_to_text(v)),
1273                }
1274            }
1275            Ok(Value::Text(result.into()))
1276        }
1277        "ABS" => {
1278            check_args(name, &evaluated, 1)?;
1279            match &evaluated[0] {
1280                Value::Null => Ok(Value::Null),
1281                Value::Integer(i) => i
1282                    .checked_abs()
1283                    .map(Value::Integer)
1284                    .ok_or(SqlError::IntegerOverflow),
1285                Value::Real(r) => Ok(Value::Real(r.abs())),
1286                _ => Err(SqlError::TypeMismatch {
1287                    expected: "numeric".into(),
1288                    got: evaluated[0].data_type().to_string(),
1289                }),
1290            }
1291        }
1292        "ROUND" => {
1293            if evaluated.is_empty() || evaluated.len() > 2 {
1294                return Err(SqlError::InvalidValue(
1295                    "ROUND requires 1 or 2 arguments".into(),
1296                ));
1297            }
1298            if evaluated[0].is_null() {
1299                return Ok(Value::Null);
1300            }
1301            let val = match &evaluated[0] {
1302                Value::Integer(i) => *i as f64,
1303                Value::Real(r) => *r,
1304                _ => {
1305                    return Err(SqlError::TypeMismatch {
1306                        expected: "numeric".into(),
1307                        got: evaluated[0].data_type().to_string(),
1308                    })
1309                }
1310            };
1311            let places = if evaluated.len() == 2 {
1312                match &evaluated[1] {
1313                    Value::Null => return Ok(Value::Null),
1314                    Value::Integer(i) => *i,
1315                    _ => {
1316                        return Err(SqlError::TypeMismatch {
1317                            expected: "INTEGER".into(),
1318                            got: evaluated[1].data_type().to_string(),
1319                        })
1320                    }
1321                }
1322            } else {
1323                0
1324            };
1325            let factor = 10f64.powi(places as i32);
1326            let rounded = (val * factor).round() / factor;
1327            Ok(Value::Real(rounded))
1328        }
1329        "CEIL" | "CEILING" => {
1330            check_args(name, &evaluated, 1)?;
1331            match &evaluated[0] {
1332                Value::Null => Ok(Value::Null),
1333                Value::Integer(i) => Ok(Value::Integer(*i)),
1334                Value::Real(r) => Ok(Value::Integer(r.ceil() as i64)),
1335                _ => Err(SqlError::TypeMismatch {
1336                    expected: "numeric".into(),
1337                    got: evaluated[0].data_type().to_string(),
1338                }),
1339            }
1340        }
1341        "FLOOR" => {
1342            check_args(name, &evaluated, 1)?;
1343            match &evaluated[0] {
1344                Value::Null => Ok(Value::Null),
1345                Value::Integer(i) => Ok(Value::Integer(*i)),
1346                Value::Real(r) => Ok(Value::Integer(r.floor() as i64)),
1347                _ => Err(SqlError::TypeMismatch {
1348                    expected: "numeric".into(),
1349                    got: evaluated[0].data_type().to_string(),
1350                }),
1351            }
1352        }
1353        "SIGN" => {
1354            check_args(name, &evaluated, 1)?;
1355            match &evaluated[0] {
1356                Value::Null => Ok(Value::Null),
1357                Value::Integer(i) => Ok(Value::Integer(i.signum())),
1358                Value::Real(r) => {
1359                    if *r > 0.0 {
1360                        Ok(Value::Integer(1))
1361                    } else if *r < 0.0 {
1362                        Ok(Value::Integer(-1))
1363                    } else {
1364                        Ok(Value::Integer(0))
1365                    }
1366                }
1367                _ => Err(SqlError::TypeMismatch {
1368                    expected: "numeric".into(),
1369                    got: evaluated[0].data_type().to_string(),
1370                }),
1371            }
1372        }
1373        "SQRT" => {
1374            check_args(name, &evaluated, 1)?;
1375            match &evaluated[0] {
1376                Value::Null => Ok(Value::Null),
1377                Value::Integer(i) => {
1378                    if *i < 0 {
1379                        Ok(Value::Null)
1380                    } else {
1381                        Ok(Value::Real((*i as f64).sqrt()))
1382                    }
1383                }
1384                Value::Real(r) => {
1385                    if *r < 0.0 {
1386                        Ok(Value::Null)
1387                    } else {
1388                        Ok(Value::Real(r.sqrt()))
1389                    }
1390                }
1391                _ => Err(SqlError::TypeMismatch {
1392                    expected: "numeric".into(),
1393                    got: evaluated[0].data_type().to_string(),
1394                }),
1395            }
1396        }
1397        "RANDOM" => {
1398            check_args(name, &evaluated, 0)?;
1399            use std::collections::hash_map::DefaultHasher;
1400            use std::hash::{Hash, Hasher};
1401            use std::time::SystemTime;
1402            let mut hasher = DefaultHasher::new();
1403            SystemTime::now().hash(&mut hasher);
1404            std::thread::current().id().hash(&mut hasher);
1405            let mut val = hasher.finish() as i64;
1406            if val == i64::MIN {
1407                val = i64::MAX;
1408            }
1409            Ok(Value::Integer(val))
1410        }
1411        "TYPEOF" => {
1412            check_args(name, &evaluated, 1)?;
1413            let type_name = match &evaluated[0] {
1414                Value::Null => "null",
1415                Value::Integer(_) => "integer",
1416                Value::Real(_) => "real",
1417                Value::Text(_) => "text",
1418                Value::Blob(_) => "blob",
1419                Value::Boolean(_) => "boolean",
1420                Value::Date(_) => "date",
1421                Value::Time(_) => "time",
1422                Value::Timestamp(_) => "timestamp",
1423                Value::Interval { .. } => "interval",
1424            };
1425            Ok(Value::Text(type_name.into()))
1426        }
1427        "MIN" => {
1428            check_args(name, &evaluated, 2)?;
1429            if evaluated[0].is_null() {
1430                return Ok(evaluated[1].clone());
1431            }
1432            if evaluated[1].is_null() {
1433                return Ok(evaluated[0].clone());
1434            }
1435            if evaluated[0] <= evaluated[1] {
1436                Ok(evaluated[0].clone())
1437            } else {
1438                Ok(evaluated[1].clone())
1439            }
1440        }
1441        "MAX" => {
1442            check_args(name, &evaluated, 2)?;
1443            if evaluated[0].is_null() {
1444                return Ok(evaluated[1].clone());
1445            }
1446            if evaluated[1].is_null() {
1447                return Ok(evaluated[0].clone());
1448            }
1449            if evaluated[0] >= evaluated[1] {
1450                Ok(evaluated[0].clone())
1451            } else {
1452                Ok(evaluated[1].clone())
1453            }
1454        }
1455        "HEX" => {
1456            check_args(name, &evaluated, 1)?;
1457            match &evaluated[0] {
1458                Value::Null => Ok(Value::Null),
1459                Value::Blob(b) => {
1460                    let mut s = String::with_capacity(b.len() * 2);
1461                    for byte in b {
1462                        s.push_str(&format!("{byte:02X}"));
1463                    }
1464                    Ok(Value::Text(s.into()))
1465                }
1466                Value::Text(s) => {
1467                    let mut r = String::with_capacity(s.len() * 2);
1468                    for byte in s.as_bytes() {
1469                        r.push_str(&format!("{byte:02X}"));
1470                    }
1471                    Ok(Value::Text(r.into()))
1472                }
1473                _ => Ok(Value::Text(value_to_text(&evaluated[0]).into())),
1474            }
1475        }
1476        "NOW" | "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
1477            check_args(name, &evaluated, 0)?;
1478            Ok(Value::Timestamp(crate::datetime::txn_or_clock_micros()))
1479        }
1480        "CURRENT_DATE" => {
1481            check_args(name, &evaluated, 0)?;
1482            Ok(Value::Date(crate::datetime::ts_to_date_floor(
1483                crate::datetime::txn_or_clock_micros(),
1484            )))
1485        }
1486        "CURRENT_TIME" | "LOCALTIME" => {
1487            check_args(name, &evaluated, 0)?;
1488            Ok(Value::Time(
1489                crate::datetime::ts_split(crate::datetime::txn_or_clock_micros()).1,
1490            ))
1491        }
1492        "CLOCK_TIMESTAMP" | "STATEMENT_TIMESTAMP" | "TRANSACTION_TIMESTAMP" => {
1493            check_args(name, &evaluated, 0)?;
1494            let ts = match name {
1495                "CLOCK_TIMESTAMP" => crate::datetime::now_micros(),
1496                _ => crate::datetime::txn_or_clock_micros(),
1497            };
1498            Ok(Value::Timestamp(ts))
1499        }
1500        "EXTRACT" | "DATE_PART" | "DATEPART" => {
1501            check_args(name, &evaluated, 2)?;
1502            // Borrow the field str without allocating; datetime::extract accepts &str.
1503            let field: &str = match &evaluated[0] {
1504                Value::Null => return Ok(Value::Null),
1505                Value::Text(s) => s.as_str(),
1506                _ => {
1507                    return Err(SqlError::TypeMismatch {
1508                        expected: "TEXT field name".into(),
1509                        got: evaluated[0].data_type().to_string(),
1510                    })
1511                }
1512            };
1513            if evaluated[1].is_null() {
1514                return Ok(Value::Null);
1515            }
1516            crate::datetime::extract(field, &evaluated[1])
1517        }
1518        "DATE_TRUNC" => {
1519            if evaluated.len() < 2 || evaluated.len() > 3 {
1520                return Err(SqlError::InvalidValue(
1521                    "DATE_TRUNC requires 2 or 3 arguments".into(),
1522                ));
1523            }
1524            let unit = match &evaluated[0] {
1525                Value::Null => return Ok(Value::Null),
1526                Value::Text(s) => s.to_string(),
1527                _ => {
1528                    return Err(SqlError::TypeMismatch {
1529                        expected: "TEXT unit name".into(),
1530                        got: evaluated[0].data_type().to_string(),
1531                    })
1532                }
1533            };
1534            if evaluated[1].is_null() {
1535                return Ok(Value::Null);
1536            }
1537            // Optional tz arg: truncate in that zone, then convert back to UTC.
1538            if evaluated.len() == 3 {
1539                if let Value::Text(tz) = &evaluated[2] {
1540                    if !tz.eq_ignore_ascii_case("UTC") {
1541                        if let Value::Timestamp(ts) = &evaluated[1] {
1542                            return date_trunc_in_zone(&unit, *ts, tz);
1543                        }
1544                    }
1545                }
1546            }
1547            crate::datetime::date_trunc(&unit, &evaluated[1])
1548        }
1549        "DATE_BIN" => {
1550            check_args(name, &evaluated, 3)?;
1551            if evaluated.iter().any(|v| v.is_null()) {
1552                return Ok(Value::Null);
1553            }
1554            let stride = match &evaluated[0] {
1555                Value::Interval {
1556                    months: _,
1557                    days,
1558                    micros,
1559                } => *days as i64 * crate::datetime::MICROS_PER_DAY + *micros,
1560                _ => {
1561                    return Err(SqlError::TypeMismatch {
1562                        expected: "INTERVAL stride".into(),
1563                        got: evaluated[0].data_type().to_string(),
1564                    })
1565                }
1566            };
1567            if stride <= 0 {
1568                return Err(SqlError::InvalidValue(
1569                    "DATE_BIN stride must be positive".into(),
1570                ));
1571            }
1572            let (src, origin) = match (&evaluated[1], &evaluated[2]) {
1573                (Value::Timestamp(s), Value::Timestamp(o)) => (*s, *o),
1574                _ => {
1575                    return Err(SqlError::TypeMismatch {
1576                        expected: "TIMESTAMP, TIMESTAMP".into(),
1577                        got: format!("{}, {}", evaluated[1].data_type(), evaluated[2].data_type()),
1578                    })
1579                }
1580            };
1581            let diff = src - origin;
1582            let binned = origin + (diff.div_euclid(stride)) * stride;
1583            Ok(Value::Timestamp(binned))
1584        }
1585        "AGE" => {
1586            if evaluated.len() == 1 {
1587                if evaluated[0].is_null() {
1588                    return Ok(Value::Null);
1589                }
1590                let ts = match &evaluated[0] {
1591                    Value::Timestamp(t) => *t,
1592                    Value::Date(d) => crate::datetime::date_to_ts(*d),
1593                    _ => {
1594                        return Err(SqlError::TypeMismatch {
1595                            expected: "TIMESTAMP or DATE".into(),
1596                            got: evaluated[0].data_type().to_string(),
1597                        })
1598                    }
1599                };
1600                // Implicit reference: today at midnight UTC.
1601                let today = crate::datetime::today_days();
1602                let midnight = crate::datetime::date_to_ts(today);
1603                let (m, d, u) = crate::datetime::age(midnight, ts)?;
1604                return Ok(Value::Interval {
1605                    months: m,
1606                    days: d,
1607                    micros: u,
1608                });
1609            }
1610            check_args(name, &evaluated, 2)?;
1611            if evaluated.iter().any(|v| v.is_null()) {
1612                return Ok(Value::Null);
1613            }
1614            let a = ts_of(&evaluated[0])?;
1615            let b = ts_of(&evaluated[1])?;
1616            let (m, d, u) = crate::datetime::age(a, b)?;
1617            Ok(Value::Interval {
1618                months: m,
1619                days: d,
1620                micros: u,
1621            })
1622        }
1623        "MAKE_DATE" => {
1624            check_args(name, &evaluated, 3)?;
1625            if evaluated.iter().any(|v| v.is_null()) {
1626                return Ok(Value::Null);
1627            }
1628            let y = int_arg(&evaluated[0], "MAKE_DATE year")? as i32;
1629            let m = int_arg(&evaluated[1], "MAKE_DATE month")? as u8;
1630            let d = int_arg(&evaluated[2], "MAKE_DATE day")? as u8;
1631            crate::datetime::ymd_to_days(y, m, d)
1632                .map(Value::Date)
1633                .ok_or_else(|| SqlError::InvalidDateLiteral(format!("make_date({y}, {m}, {d})")))
1634        }
1635        "MAKE_TIME" => {
1636            check_args(name, &evaluated, 3)?;
1637            if evaluated.iter().any(|v| v.is_null()) {
1638                return Ok(Value::Null);
1639            }
1640            let h = int_arg(&evaluated[0], "MAKE_TIME hour")? as u8;
1641            let mi = int_arg(&evaluated[1], "MAKE_TIME minute")? as u8;
1642            let (s, us) = real_sec_arg(&evaluated[2])?;
1643            crate::datetime::hmsn_to_micros(h, mi, s, us)
1644                .map(Value::Time)
1645                .ok_or_else(|| SqlError::InvalidTimeLiteral(format!("make_time({h}, {mi}, ...)")))
1646        }
1647        "MAKE_TIMESTAMP" => {
1648            check_args(name, &evaluated, 6)?;
1649            if evaluated.iter().any(|v| v.is_null()) {
1650                return Ok(Value::Null);
1651            }
1652            let y = int_arg(&evaluated[0], "MAKE_TIMESTAMP year")? as i32;
1653            let mo = int_arg(&evaluated[1], "MAKE_TIMESTAMP month")? as u8;
1654            let d = int_arg(&evaluated[2], "MAKE_TIMESTAMP day")? as u8;
1655            let h = int_arg(&evaluated[3], "MAKE_TIMESTAMP hour")? as u8;
1656            let mi = int_arg(&evaluated[4], "MAKE_TIMESTAMP min")? as u8;
1657            let (s, us) = real_sec_arg(&evaluated[5])?;
1658            let days = crate::datetime::ymd_to_days(y, mo, d).ok_or_else(|| {
1659                SqlError::InvalidTimestampLiteral(format!("make_timestamp year={y}"))
1660            })?;
1661            let tmicros = crate::datetime::hmsn_to_micros(h, mi, s, us)
1662                .ok_or_else(|| SqlError::InvalidTimestampLiteral("time out of range".into()))?;
1663            Ok(Value::Timestamp(crate::datetime::ts_combine(days, tmicros)))
1664        }
1665        "MAKE_INTERVAL" => {
1666            // Positional args: years, months, weeks, days, hours, mins, secs.
1667            if evaluated.len() > 7 {
1668                return Err(SqlError::InvalidValue(
1669                    "MAKE_INTERVAL accepts at most 7 arguments".into(),
1670                ));
1671            }
1672            let mut months: i64 = 0;
1673            let mut days: i64 = 0;
1674            let mut micros: i64 = 0;
1675            for (i, v) in evaluated.iter().enumerate() {
1676                if v.is_null() {
1677                    continue;
1678                }
1679                let n = match v {
1680                    Value::Integer(n) => *n,
1681                    Value::Real(r) => *r as i64,
1682                    _ => {
1683                        return Err(SqlError::TypeMismatch {
1684                            expected: "numeric".into(),
1685                            got: v.data_type().to_string(),
1686                        })
1687                    }
1688                };
1689                match i {
1690                    0 => months = months.saturating_add(n.saturating_mul(12)),
1691                    1 => months = months.saturating_add(n),
1692                    2 => days = days.saturating_add(n.saturating_mul(7)),
1693                    3 => days = days.saturating_add(n),
1694                    4 => {
1695                        micros = micros
1696                            .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_HOUR))
1697                    }
1698                    5 => {
1699                        micros =
1700                            micros.saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_MIN))
1701                    }
1702                    6 => {
1703                        // Seconds may be fractional — also check Real.
1704                        if let Value::Real(r) = v {
1705                            micros = micros.saturating_add(
1706                                (*r * crate::datetime::MICROS_PER_SEC as f64) as i64,
1707                            );
1708                        } else {
1709                            micros = micros
1710                                .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_SEC));
1711                        }
1712                    }
1713                    _ => unreachable!(),
1714                }
1715            }
1716            Ok(Value::Interval {
1717                months: months.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
1718                days: days.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
1719                micros,
1720            })
1721        }
1722        "JUSTIFY_DAYS" => {
1723            check_args(name, &evaluated, 1)?;
1724            match &evaluated[0] {
1725                Value::Null => Ok(Value::Null),
1726                Value::Interval {
1727                    months,
1728                    days,
1729                    micros,
1730                } => {
1731                    let (m, d, u) = crate::datetime::justify_days(*months, *days, *micros);
1732                    Ok(Value::Interval {
1733                        months: m,
1734                        days: d,
1735                        micros: u,
1736                    })
1737                }
1738                other => Err(SqlError::TypeMismatch {
1739                    expected: "INTERVAL".into(),
1740                    got: other.data_type().to_string(),
1741                }),
1742            }
1743        }
1744        "JUSTIFY_HOURS" => {
1745            check_args(name, &evaluated, 1)?;
1746            match &evaluated[0] {
1747                Value::Null => Ok(Value::Null),
1748                Value::Interval {
1749                    months,
1750                    days,
1751                    micros,
1752                } => {
1753                    let (m, d, u) = crate::datetime::justify_hours(*months, *days, *micros);
1754                    Ok(Value::Interval {
1755                        months: m,
1756                        days: d,
1757                        micros: u,
1758                    })
1759                }
1760                other => Err(SqlError::TypeMismatch {
1761                    expected: "INTERVAL".into(),
1762                    got: other.data_type().to_string(),
1763                }),
1764            }
1765        }
1766        "JUSTIFY_INTERVAL" => {
1767            check_args(name, &evaluated, 1)?;
1768            match &evaluated[0] {
1769                Value::Null => Ok(Value::Null),
1770                Value::Interval {
1771                    months,
1772                    days,
1773                    micros,
1774                } => {
1775                    let (m, d, u) = crate::datetime::justify_interval(*months, *days, *micros);
1776                    Ok(Value::Interval {
1777                        months: m,
1778                        days: d,
1779                        micros: u,
1780                    })
1781                }
1782                other => Err(SqlError::TypeMismatch {
1783                    expected: "INTERVAL".into(),
1784                    got: other.data_type().to_string(),
1785                }),
1786            }
1787        }
1788        "ISFINITE" => {
1789            check_args(name, &evaluated, 1)?;
1790            if evaluated[0].is_null() {
1791                return Ok(Value::Null);
1792            }
1793            Ok(Value::Boolean(evaluated[0].is_finite_temporal()))
1794        }
1795        "DATE" => {
1796            if evaluated.is_empty() {
1797                return Err(SqlError::InvalidValue(
1798                    "DATE requires at least 1 argument".into(),
1799                ));
1800            }
1801            if evaluated[0].is_null() {
1802                return Ok(Value::Null);
1803            }
1804            let d = match &evaluated[0] {
1805                Value::Date(d) => *d,
1806                Value::Timestamp(t) => crate::datetime::ts_to_date_floor(*t),
1807                Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::today_days(),
1808                Value::Text(s) => crate::datetime::parse_date(s)?,
1809                Value::Integer(n) => {
1810                    crate::datetime::ts_to_date_floor(*n * crate::datetime::MICROS_PER_SEC)
1811                }
1812                other => {
1813                    return Err(SqlError::TypeMismatch {
1814                        expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
1815                        got: other.data_type().to_string(),
1816                    })
1817                }
1818            };
1819            Ok(Value::Date(d))
1820        }
1821        "TIME" => {
1822            if evaluated.is_empty() {
1823                return Err(SqlError::InvalidValue(
1824                    "TIME requires at least 1 argument".into(),
1825                ));
1826            }
1827            if evaluated[0].is_null() {
1828                return Ok(Value::Null);
1829            }
1830            let t = match &evaluated[0] {
1831                Value::Time(t) => *t,
1832                Value::Timestamp(t) => crate::datetime::ts_split(*t).1,
1833                Value::Text(s) if s.eq_ignore_ascii_case("now") => {
1834                    crate::datetime::current_time_micros()
1835                }
1836                Value::Text(s) => crate::datetime::parse_time(s)?,
1837                other => {
1838                    return Err(SqlError::TypeMismatch {
1839                        expected: "TIMESTAMP, TIME, or TEXT".into(),
1840                        got: other.data_type().to_string(),
1841                    })
1842                }
1843            };
1844            Ok(Value::Time(t))
1845        }
1846        "DATETIME" => {
1847            if evaluated.is_empty() {
1848                return Err(SqlError::InvalidValue(
1849                    "DATETIME requires at least 1 argument".into(),
1850                ));
1851            }
1852            if evaluated[0].is_null() {
1853                return Ok(Value::Null);
1854            }
1855            let t = match &evaluated[0] {
1856                Value::Timestamp(t) => *t,
1857                Value::Date(d) => crate::datetime::date_to_ts(*d),
1858                Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::now_micros(),
1859                Value::Text(s) => crate::datetime::parse_timestamp(s)?,
1860                Value::Integer(n) => n * crate::datetime::MICROS_PER_SEC,
1861                other => {
1862                    return Err(SqlError::TypeMismatch {
1863                        expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
1864                        got: other.data_type().to_string(),
1865                    })
1866                }
1867            };
1868            Ok(Value::Timestamp(t))
1869        }
1870        "STRFTIME" => {
1871            if evaluated.len() < 2 {
1872                return Err(SqlError::InvalidValue(
1873                    "STRFTIME requires format + value".into(),
1874                ));
1875            }
1876            if evaluated.iter().take(2).any(|v| v.is_null()) {
1877                return Ok(Value::Null);
1878            }
1879            let fmt = match &evaluated[0] {
1880                Value::Text(s) => s.to_string(),
1881                _ => {
1882                    return Err(SqlError::TypeMismatch {
1883                        expected: "TEXT format".into(),
1884                        got: evaluated[0].data_type().to_string(),
1885                    })
1886                }
1887            };
1888            let out = crate::datetime::strftime(&fmt, &evaluated[1])?;
1889            Ok(Value::Text(out.into()))
1890        }
1891        "JULIANDAY" => {
1892            if evaluated.is_empty() {
1893                return Err(SqlError::InvalidValue(
1894                    "JULIANDAY requires at least 1 argument".into(),
1895                ));
1896            }
1897            if evaluated[0].is_null() {
1898                return Ok(Value::Null);
1899            }
1900            let micros = ts_of(&evaluated[0])?;
1901            let (days, tmicros) = crate::datetime::ts_split(micros);
1902            // Julian Day 2440587.5 = 1970-01-01 00:00:00 UTC (Julian days start at noon).
1903            let julian =
1904                days as f64 + 2_440_587.5 + tmicros as f64 / crate::datetime::MICROS_PER_DAY as f64;
1905            Ok(Value::Real(julian))
1906        }
1907        "UNIXEPOCH" => {
1908            if evaluated.is_empty() {
1909                return Err(SqlError::InvalidValue(
1910                    "UNIXEPOCH requires at least 1 argument".into(),
1911                ));
1912            }
1913            if evaluated[0].is_null() {
1914                return Ok(Value::Null);
1915            }
1916            let micros = ts_of(&evaluated[0])?;
1917            let subsec = evaluated
1918                .get(1)
1919                .and_then(|v| {
1920                    if let Value::Text(s) = v {
1921                        Some(s.to_string())
1922                    } else {
1923                        None
1924                    }
1925                })
1926                .map(|s| s.eq_ignore_ascii_case("subsec") || s.eq_ignore_ascii_case("subsecond"))
1927                .unwrap_or(false);
1928            if subsec {
1929                Ok(Value::Real(
1930                    micros as f64 / crate::datetime::MICROS_PER_SEC as f64,
1931                ))
1932            } else {
1933                Ok(Value::Integer(micros / crate::datetime::MICROS_PER_SEC))
1934            }
1935        }
1936        "TIMEDIFF" => {
1937            check_args(name, &evaluated, 2)?;
1938            if evaluated.iter().any(|v| v.is_null()) {
1939                return Ok(Value::Null);
1940            }
1941            let a = ts_of(&evaluated[0])?;
1942            let b = ts_of(&evaluated[1])?;
1943            let (days, micros) = crate::datetime::subtract_timestamps(a, b);
1944            let sign = if days < 0 || (days == 0 && micros < 0) {
1945                "-"
1946            } else {
1947                "+"
1948            };
1949            let abs_days = days.unsigned_abs() as i64;
1950            let abs_us = micros.unsigned_abs() as i64;
1951            // PG-compat format string: "(+|-)YYYY-MM-DD HH:MM:SS.SSS", days-only.
1952            let (h, m, s, us) = crate::datetime::micros_to_hmsn(abs_us);
1953            Ok(Value::Text(
1954                format!("{sign}{abs_days:04}-00-00 {h:02}:{m:02}:{s:02}.{us:06}").into(),
1955            ))
1956        }
1957        "AT_TIMEZONE" => {
1958            check_args(name, &evaluated, 2)?;
1959            if evaluated.iter().any(|v| v.is_null()) {
1960                return Ok(Value::Null);
1961            }
1962            let ts = match &evaluated[0] {
1963                Value::Timestamp(t) => *t,
1964                Value::Date(d) => crate::datetime::date_to_ts(*d),
1965                other => {
1966                    return Err(SqlError::TypeMismatch {
1967                        expected: "TIMESTAMP or DATE".into(),
1968                        got: other.data_type().to_string(),
1969                    })
1970                }
1971            };
1972            let zone = match &evaluated[1] {
1973                Value::Text(s) => s.to_string(),
1974                _ => {
1975                    return Err(SqlError::TypeMismatch {
1976                        expected: "TEXT time zone".into(),
1977                        got: evaluated[1].data_type().to_string(),
1978                    })
1979                }
1980            };
1981            // Reject POSIX-style 'UTC+5' (ambiguous sign convention).
1982            let upper = zone.to_ascii_uppercase();
1983            if (upper.starts_with("UTC+") || upper.starts_with("UTC-")) && zone.len() > 3 {
1984                return Err(SqlError::InvalidTimezone(format!(
1985                    "'{zone}' is ambiguous — use ISO-8601 offset like '+05:00' or named zone like 'Etc/GMT-5'"
1986                )));
1987            }
1988            let formatted = crate::datetime::format_timestamp_in_zone(ts, &zone)?;
1989            Ok(Value::Text(formatted.into()))
1990        }
1991        _ => Err(SqlError::Unsupported(format!("scalar function: {name}"))),
1992    }
1993}
1994
1995/// Extract a timestamp (µs UTC) from a Value, coercing DATE → midnight.
1996fn ts_of(v: &Value) -> Result<i64> {
1997    match v {
1998        Value::Timestamp(t) => Ok(*t),
1999        Value::Date(d) => Ok(crate::datetime::date_to_ts(*d)),
2000        _ => Err(SqlError::TypeMismatch {
2001            expected: "TIMESTAMP or DATE".into(),
2002            got: v.data_type().to_string(),
2003        }),
2004    }
2005}
2006
2007fn int_arg(v: &Value, label: &str) -> Result<i64> {
2008    match v {
2009        Value::Integer(n) => Ok(*n),
2010        _ => Err(SqlError::TypeMismatch {
2011            expected: format!("INTEGER ({label})"),
2012            got: v.data_type().to_string(),
2013        }),
2014    }
2015}
2016
2017/// Extract (whole_seconds: u8, frac_micros: u32) from a numeric argument for MAKE_TIME-style calls.
2018fn real_sec_arg(v: &Value) -> Result<(u8, u32)> {
2019    match v {
2020        Value::Integer(n) => {
2021            if !(0..=60).contains(n) {
2022                return Err(SqlError::InvalidValue(format!("second out of range: {n}")));
2023            }
2024            Ok((*n as u8, 0))
2025        }
2026        Value::Real(r) => {
2027            let whole = r.trunc() as i64;
2028            if !(0..=60).contains(&whole) {
2029                return Err(SqlError::InvalidValue(format!("second out of range: {r}")));
2030            }
2031            let frac = ((r - whole as f64) * 1_000_000.0).round() as i64;
2032            Ok((whole as u8, frac.max(0) as u32))
2033        }
2034        _ => Err(SqlError::TypeMismatch {
2035            expected: "numeric seconds".into(),
2036            got: v.data_type().to_string(),
2037        }),
2038    }
2039}
2040
2041/// DATE_TRUNC with a non-UTC IANA zone: convert → truncate in that zone → convert back to UTC.
2042fn date_trunc_in_zone(unit: &str, ts_utc: i64, tz: &str) -> Result<Value> {
2043    use jiff::{tz::TimeZone, Timestamp as JTimestamp};
2044    let zone = TimeZone::get(tz).map_err(|e| SqlError::InvalidTimezone(format!("{tz}: {e}")))?;
2045    let ts = JTimestamp::from_microsecond(ts_utc)
2046        .map_err(|e| SqlError::InvalidValue(format!("ts: {e}")))?;
2047    let zoned = ts.to_zoned(zone.clone());
2048    let unit_lower = unit.to_ascii_lowercase();
2049    let rounded = match unit_lower.as_str() {
2050        "microseconds" => return Ok(Value::Timestamp(ts_utc)),
2051        "second" => zoned
2052            .start_of_day()
2053            .map_err(|e| SqlError::InvalidValue(format!("{e}")))?,
2054        _ => {
2055            let naive_ts = zoned.timestamp().as_microsecond();
2056            return crate::datetime::date_trunc(unit, &Value::Timestamp(naive_ts));
2057        }
2058    };
2059    Ok(Value::Timestamp(rounded.timestamp().as_microsecond()))
2060}
2061
2062fn check_args(name: &str, args: &[Value], expected: usize) -> Result<()> {
2063    if args.len() != expected {
2064        Err(SqlError::InvalidValue(format!(
2065            "{name} requires {expected} argument(s), got {}",
2066            args.len()
2067        )))
2068    } else {
2069        Ok(())
2070    }
2071}
2072
2073pub fn referenced_columns(expr: &Expr, columns: &[ColumnDef]) -> Vec<usize> {
2074    let mut indices = Vec::new();
2075    collect_column_refs(expr, columns, &mut indices);
2076    indices.sort_unstable();
2077    indices.dedup();
2078    indices
2079}
2080
2081fn collect_column_refs(expr: &Expr, columns: &[ColumnDef], out: &mut Vec<usize>) {
2082    match expr {
2083        Expr::Column(name) => {
2084            for (i, c) in columns.iter().enumerate() {
2085                if c.name == *name || c.name.ends_with(&format!(".{name}")) {
2086                    out.push(i);
2087                    break;
2088                }
2089            }
2090        }
2091        Expr::QualifiedColumn { table, column } => {
2092            let qualified = format!("{table}.{column}");
2093            if let Some(idx) = columns.iter().position(|c| c.name == qualified) {
2094                out.push(idx);
2095            } else {
2096                let matches: Vec<usize> = columns
2097                    .iter()
2098                    .enumerate()
2099                    .filter(|(_, c)| c.name == *column)
2100                    .map(|(i, _)| i)
2101                    .collect();
2102                if matches.len() == 1 {
2103                    out.push(matches[0]);
2104                }
2105            }
2106        }
2107        Expr::BinaryOp { left, right, .. } => {
2108            collect_column_refs(left, columns, out);
2109            collect_column_refs(right, columns, out);
2110        }
2111        Expr::UnaryOp { expr, .. } => {
2112            collect_column_refs(expr, columns, out);
2113        }
2114        Expr::IsNull(e) | Expr::IsNotNull(e) => {
2115            collect_column_refs(e, columns, out);
2116        }
2117        Expr::Function { args, .. } => {
2118            for arg in args {
2119                collect_column_refs(arg, columns, out);
2120            }
2121        }
2122        Expr::InSubquery { expr, .. } => {
2123            collect_column_refs(expr, columns, out);
2124        }
2125        Expr::InList { expr, list, .. } => {
2126            collect_column_refs(expr, columns, out);
2127            for item in list {
2128                collect_column_refs(item, columns, out);
2129            }
2130        }
2131        Expr::InSet { expr, .. } => {
2132            collect_column_refs(expr, columns, out);
2133        }
2134        Expr::Between {
2135            expr, low, high, ..
2136        } => {
2137            collect_column_refs(expr, columns, out);
2138            collect_column_refs(low, columns, out);
2139            collect_column_refs(high, columns, out);
2140        }
2141        Expr::Like {
2142            expr,
2143            pattern,
2144            escape,
2145            ..
2146        } => {
2147            collect_column_refs(expr, columns, out);
2148            collect_column_refs(pattern, columns, out);
2149            if let Some(esc) = escape {
2150                collect_column_refs(esc, columns, out);
2151            }
2152        }
2153        Expr::Case {
2154            operand,
2155            conditions,
2156            else_result,
2157        } => {
2158            if let Some(op) = operand {
2159                collect_column_refs(op, columns, out);
2160            }
2161            for (when, then) in conditions {
2162                collect_column_refs(when, columns, out);
2163                collect_column_refs(then, columns, out);
2164            }
2165            if let Some(e) = else_result {
2166                collect_column_refs(e, columns, out);
2167            }
2168        }
2169        Expr::Coalesce(args) => {
2170            for arg in args {
2171                collect_column_refs(arg, columns, out);
2172            }
2173        }
2174        Expr::Cast { expr, .. } => {
2175            collect_column_refs(expr, columns, out);
2176        }
2177        Expr::WindowFunction { args, spec, .. } => {
2178            for arg in args {
2179                collect_column_refs(arg, columns, out);
2180            }
2181            for pb in &spec.partition_by {
2182                collect_column_refs(pb, columns, out);
2183            }
2184            for ob in &spec.order_by {
2185                collect_column_refs(&ob.expr, columns, out);
2186            }
2187        }
2188        Expr::Literal(_)
2189        | Expr::Parameter(_)
2190        | Expr::CountStar
2191        | Expr::Exists { .. }
2192        | Expr::ScalarSubquery(_) => {}
2193    }
2194}
2195
2196/// Check if an expression result is truthy (for WHERE/HAVING).
2197pub fn is_truthy(val: &Value) -> bool {
2198    match val {
2199        Value::Boolean(b) => *b,
2200        Value::Integer(i) => *i != 0,
2201        Value::Null => false,
2202        _ => true,
2203    }
2204}
2205
2206#[cfg(test)]
2207#[path = "eval_tests.rs"]
2208mod tests;