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
9#[derive(Debug)]
10pub struct ColumnMap {
11    exact: FxHashMap<String, usize>,
12    short: FxHashMap<String, ShortMatch>,
13    collations: Vec<crate::types::Collation>,
14    has_non_binary_collation: bool,
15}
16
17#[derive(Clone, Debug)]
18enum ShortMatch {
19    Unique(usize),
20    Ambiguous,
21}
22
23impl Clone for ColumnMap {
24    fn clone(&self) -> Self {
25        Self {
26            exact: self.exact.clone(),
27            short: self.short.clone(),
28            collations: self.collations.clone(),
29            has_non_binary_collation: self.has_non_binary_collation,
30        }
31    }
32}
33
34impl ColumnMap {
35    pub fn new(columns: &[ColumnDef]) -> Self {
36        let mut exact = FxHashMap::with_capacity_and_hasher(columns.len() * 2, Default::default());
37        let mut short: FxHashMap<String, ShortMatch> =
38            FxHashMap::with_capacity_and_hasher(columns.len(), Default::default());
39        let mut collations = Vec::with_capacity(columns.len());
40        let mut has_non_binary_collation = false;
41
42        for (i, col) in columns.iter().enumerate() {
43            let lower = col.name.to_ascii_lowercase();
44            exact.insert(lower.clone(), i);
45
46            let unqualified = if let Some(dot) = lower.rfind('.') {
47                &lower[dot + 1..]
48            } else {
49                &lower
50            };
51            short
52                .entry(unqualified.to_string())
53                .and_modify(|e| *e = ShortMatch::Ambiguous)
54                .or_insert(ShortMatch::Unique(i));
55            collations.push(col.collation);
56            if col.collation != crate::types::Collation::Binary {
57                has_non_binary_collation = true;
58            }
59        }
60
61        Self {
62            exact,
63            short,
64            collations,
65            has_non_binary_collation,
66        }
67    }
68
69    pub(crate) fn collation_at(&self, idx: usize) -> crate::types::Collation {
70        self.collations
71            .get(idx)
72            .copied()
73            .unwrap_or(crate::types::Collation::Binary)
74    }
75
76    #[inline]
77    pub(crate) fn has_non_binary_collation(&self) -> bool {
78        self.has_non_binary_collation
79    }
80
81    pub(crate) fn resolve(&self, name: &str) -> Result<usize> {
82        if let Some(&idx) = self.exact.get(name) {
83            return Ok(idx);
84        }
85        match self.short.get(name) {
86            Some(ShortMatch::Unique(idx)) => Ok(*idx),
87            Some(ShortMatch::Ambiguous) => Err(SqlError::AmbiguousColumn(name.to_string())),
88            None => Err(SqlError::ColumnNotFound(name.to_string())),
89        }
90    }
91
92    pub(crate) fn resolve_qualified(&self, table: &str, column: &str) -> Result<usize> {
93        let qualified = format!("{table}.{column}");
94        if let Some(&idx) = self.exact.get(&qualified) {
95            return Ok(idx);
96        }
97        match self.short.get(column) {
98            Some(ShortMatch::Unique(idx)) => Ok(*idx),
99            _ => Err(SqlError::ColumnNotFound(format!("{table}.{column}"))),
100        }
101    }
102}
103
104pub struct EvalCtx<'a> {
105    pub col_map: &'a ColumnMap,
106    pub row: &'a [Value],
107    pub params: &'a [Value],
108    pub excluded: Option<ExcludedRow<'a>>,
109    pub old_new: Option<OldNewRows<'a>>,
110    pub session_tz: Option<jiff::tz::TimeZone>,
111}
112
113pub struct ExcludedRow<'a> {
114    pub col_map: &'a ColumnMap,
115    pub row: &'a [Value],
116}
117
118pub struct OldNewRows<'a> {
119    pub col_map: &'a ColumnMap,
120    pub old_row: Option<&'a [Value]>,
121    pub new_row: Option<&'a [Value]>,
122}
123
124impl<'a> EvalCtx<'a> {
125    pub fn new(col_map: &'a ColumnMap, row: &'a [Value]) -> Self {
126        Self {
127            col_map,
128            row,
129            params: &[],
130            excluded: None,
131            old_new: None,
132            session_tz: None,
133        }
134    }
135
136    pub fn with_session_tz(mut self, tz: Option<jiff::tz::TimeZone>) -> Self {
137        self.session_tz = tz;
138        self
139    }
140
141    pub fn with_params(col_map: &'a ColumnMap, row: &'a [Value], params: &'a [Value]) -> Self {
142        Self {
143            col_map,
144            row,
145            params,
146            excluded: None,
147            old_new: None,
148            session_tz: None,
149        }
150    }
151
152    pub fn with_excluded(
153        col_map: &'a ColumnMap,
154        row: &'a [Value],
155        excluded_col_map: &'a ColumnMap,
156        excluded_row: &'a [Value],
157    ) -> Self {
158        Self {
159            col_map,
160            row,
161            params: &[],
162            excluded: Some(ExcludedRow {
163                col_map: excluded_col_map,
164                row: excluded_row,
165            }),
166            old_new: None,
167            session_tz: None,
168        }
169    }
170
171    pub fn with_old_new(
172        col_map: &'a ColumnMap,
173        row: &'a [Value],
174        old_row: Option<&'a [Value]>,
175        new_row: Option<&'a [Value]>,
176    ) -> Self {
177        Self {
178            col_map,
179            row,
180            params: &[],
181            excluded: None,
182            old_new: Some(OldNewRows {
183                col_map,
184                old_row,
185                new_row,
186            }),
187            session_tz: None,
188        }
189    }
190}
191
192thread_local! {
193    static SCOPED_PARAMS: std::cell::Cell<(*const Value, usize)> =
194        const { std::cell::Cell::new((std::ptr::null(), 0)) };
195}
196
197pub fn with_scoped_params<R>(params: &[Value], f: impl FnOnce() -> R) -> R {
198    struct Guard((*const Value, usize));
199    impl Drop for Guard {
200        fn drop(&mut self) {
201            SCOPED_PARAMS.with(|slot| slot.set(self.0));
202        }
203    }
204    SCOPED_PARAMS.with(|slot| {
205        let prev = slot.get();
206        slot.set((params.as_ptr(), params.len()));
207        let _guard = Guard(prev);
208        f()
209    })
210}
211
212fn resolve_parameter(n: usize, ctx_params: &[Value]) -> Result<Value> {
213    if !ctx_params.is_empty() {
214        if n == 0 || n > ctx_params.len() {
215            return Err(SqlError::ParameterCountMismatch {
216                expected: n,
217                got: ctx_params.len(),
218            });
219        }
220        return Ok(ctx_params[n - 1].clone());
221    }
222    resolve_scoped_param(n)
223}
224
225pub fn resolve_scoped_param(n: usize) -> Result<Value> {
226    SCOPED_PARAMS.with(|slot| {
227        let (ptr, len) = slot.get();
228        if n == 0 || n > len {
229            return Err(SqlError::ParameterCountMismatch {
230                expected: n,
231                got: len,
232            });
233        }
234        // SAFETY: `with_scoped_params` keeps the slice alive for the body's run.
235        unsafe { Ok((*ptr.add(n - 1)).clone()) }
236    })
237}
238
239pub fn eval_expr(expr: &Expr, ctx: &EvalCtx) -> Result<Value> {
240    match expr {
241        Expr::Literal(v) => Ok(v.clone()),
242
243        Expr::Column(name) => {
244            let idx = ctx.col_map.resolve(name)?;
245            Ok(ctx.row[idx].clone())
246        }
247
248        Expr::QualifiedColumn { table, column } => {
249            if let Some(excluded) = ctx.excluded.as_ref() {
250                if table.eq_ignore_ascii_case("excluded") {
251                    let lowered = column.to_ascii_lowercase();
252                    let idx = excluded.col_map.resolve(&lowered)?;
253                    return Ok(excluded.row[idx].clone());
254                }
255            }
256            if let Some(on) = ctx.old_new.as_ref() {
257                if table.eq_ignore_ascii_case("old") {
258                    let lowered = column.to_ascii_lowercase();
259                    let idx = on.col_map.resolve(&lowered)?;
260                    return Ok(on.old_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
261                }
262                if table.eq_ignore_ascii_case("new") {
263                    let lowered = column.to_ascii_lowercase();
264                    let idx = on.col_map.resolve(&lowered)?;
265                    return Ok(on.new_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
266                }
267            }
268            // Trigger-body fallback: nested executor calls don't carry `ctx.old_new`.
269            if table.eq_ignore_ascii_case("old") || table.eq_ignore_ascii_case("new") {
270                if let Some(b) = crate::executor::triggers::current_bindings() {
271                    let lowered = column.to_ascii_lowercase();
272                    if table.eq_ignore_ascii_case("old") {
273                        let cm = ColumnMap::new(&b.old_columns);
274                        let idx = cm.resolve(&lowered)?;
275                        return Ok(b.old_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
276                    } else {
277                        let cm = ColumnMap::new(&b.new_columns);
278                        let idx = cm.resolve(&lowered)?;
279                        return Ok(b.new_row.map(|r| r[idx].clone()).unwrap_or(Value::Null));
280                    }
281                }
282            }
283            let idx = ctx.col_map.resolve_qualified(table, column)?;
284            Ok(ctx.row[idx].clone())
285        }
286
287        Expr::BinaryOp { left, op, right } => {
288            let lval = eval_expr(left, ctx)?;
289            let rval = eval_expr(right, ctx)?;
290            let needs_collation_check = ctx.col_map.has_non_binary_collation()
291                || matches!(left.as_ref(), Expr::Collate { .. })
292                || matches!(right.as_ref(), Expr::Collate { .. });
293            if needs_collation_check {
294                let coll = collation_of(left)
295                    .or_else(|| collation_of(right))
296                    .or_else(|| {
297                        column_collation(left, ctx).or_else(|| column_collation(right, ctx))
298                    });
299                if let Some(c) = coll {
300                    if c != crate::types::Collation::Binary {
301                        if let Some(b) = eval_text_compare(&lval, *op, &rval, c) {
302                            return Ok(Value::Boolean(b));
303                        }
304                    }
305                }
306            }
307            eval_binary_op(&lval, *op, &rval)
308        }
309
310        Expr::UnaryOp { op, expr } => {
311            let val = eval_expr(expr, ctx)?;
312            eval_unary_op(*op, &val)
313        }
314
315        Expr::IsNull(e) => {
316            let val = eval_expr(e, ctx)?;
317            Ok(Value::Boolean(val.is_null()))
318        }
319
320        Expr::IsNotNull(e) => {
321            let val = eval_expr(e, ctx)?;
322            Ok(Value::Boolean(!val.is_null()))
323        }
324
325        Expr::Function { name, args, .. } => eval_scalar_function(name, args, ctx),
326
327        Expr::CountStar => Err(SqlError::Unsupported(
328            "COUNT(*) in non-aggregate context".into(),
329        )),
330
331        Expr::InList {
332            expr: e,
333            list,
334            negated,
335        } => {
336            let lhs = eval_expr(e, ctx)?;
337            eval_in_values(&lhs, list, ctx, *negated)
338        }
339
340        Expr::InSet {
341            expr: e,
342            values,
343            has_null,
344            negated,
345        } => {
346            let lhs = eval_expr(e, ctx)?;
347            eval_in_set(&lhs, values, *has_null, *negated)
348        }
349
350        Expr::Between {
351            expr: e,
352            low,
353            high,
354            negated,
355        } => {
356            let val = eval_expr(e, ctx)?;
357            let lo = eval_expr(low, ctx)?;
358            let hi = eval_expr(high, ctx)?;
359            eval_between(&val, &lo, &hi, *negated)
360        }
361
362        Expr::Like {
363            expr: e,
364            pattern,
365            escape,
366            negated,
367        } => {
368            let val = eval_expr(e, ctx)?;
369            let pat = eval_expr(pattern, ctx)?;
370            let esc = escape.as_ref().map(|e| eval_expr(e, ctx)).transpose()?;
371            eval_like(&val, &pat, esc.as_ref(), *negated)
372        }
373
374        Expr::Case {
375            operand,
376            conditions,
377            else_result,
378        } => eval_case(operand.as_deref(), conditions, else_result.as_deref(), ctx),
379
380        Expr::Coalesce(args) => {
381            for arg in args {
382                let val = eval_expr(arg, ctx)?;
383                if !val.is_null() {
384                    return Ok(val);
385                }
386            }
387            Ok(Value::Null)
388        }
389
390        Expr::Cast { expr: e, data_type } => {
391            let val = eval_expr(e, ctx)?;
392            eval_cast(&val, *data_type)
393        }
394
395        Expr::Collate { expr: e, .. } => eval_expr(e, ctx),
396
397        Expr::InSubquery { .. } | Expr::Exists { .. } | Expr::ScalarSubquery(_) => Err(
398            SqlError::Unsupported("subquery not materialized (internal error)".into()),
399        ),
400
401        Expr::Parameter(n) => resolve_parameter(*n, ctx.params),
402
403        Expr::WindowFunction { .. } => Err(SqlError::Unsupported(
404            "window functions are only allowed in SELECT columns".into(),
405        )),
406
407        Expr::TypedNullRecord(_) => Ok(Value::Null),
408
409        Expr::ArrayLiteral(elems) => {
410            let mut out = Vec::with_capacity(elems.len());
411            for e in elems {
412                out.push(eval_expr(e, ctx)?);
413            }
414            Ok(Value::Array(std::sync::Arc::new(out)))
415        }
416
417        Expr::Quantified {
418            left,
419            op,
420            quantifier,
421            right,
422        } => eval_quantified(left, *op, *quantifier, right, ctx),
423    }
424}
425
426fn eval_quantified(
427    left: &Expr,
428    op: crate::parser::BinOp,
429    quantifier: crate::parser::Quantifier,
430    right: &crate::parser::QuantifiedRhs,
431    ctx: &EvalCtx,
432) -> Result<Value> {
433    use crate::parser::{QuantifiedRhs, Quantifier};
434    let lhs = eval_expr(left, ctx)?;
435    let elems: Vec<Value> = match right {
436        QuantifiedRhs::Array(e) => match eval_expr(e, ctx)? {
437            Value::Array(a) => (*a).clone(),
438            Value::Null => return Ok(Value::Null),
439            other => {
440                return Err(SqlError::TypeMismatch {
441                    expected: "ARRAY".into(),
442                    got: other.data_type().to_string(),
443                });
444            }
445        },
446        QuantifiedRhs::Subquery(_) => {
447            return Err(SqlError::Unsupported(
448                "ANY/ALL subquery not materialized (internal error)".into(),
449            ));
450        }
451    };
452
453    if lhs.is_null() {
454        return if elems.is_empty() {
455            match quantifier {
456                Quantifier::Any => Ok(Value::Boolean(false)),
457                Quantifier::All => Ok(Value::Boolean(true)),
458            }
459        } else {
460            Ok(Value::Null)
461        };
462    }
463
464    let mut any_unknown = false;
465    let mut any_match = false;
466    let mut any_mismatch = false;
467    for elem in &elems {
468        if elem.is_null() {
469            any_unknown = true;
470            continue;
471        }
472        let result = eval_binary_compare(&lhs, op, elem)?;
473        match result {
474            Value::Boolean(true) => any_match = true,
475            Value::Boolean(false) => any_mismatch = true,
476            Value::Null => any_unknown = true,
477            _ => {
478                return Err(SqlError::TypeMismatch {
479                    expected: "BOOLEAN".into(),
480                    got: result.data_type().to_string(),
481                });
482            }
483        }
484    }
485
486    match quantifier {
487        Quantifier::Any => {
488            if any_match {
489                Ok(Value::Boolean(true))
490            } else if any_unknown {
491                Ok(Value::Null)
492            } else {
493                Ok(Value::Boolean(false))
494            }
495        }
496        Quantifier::All => {
497            if any_mismatch {
498                Ok(Value::Boolean(false))
499            } else if any_unknown {
500                Ok(Value::Null)
501            } else {
502                Ok(Value::Boolean(true))
503            }
504        }
505    }
506}
507
508fn eval_binary_compare(left: &Value, op: crate::parser::BinOp, right: &Value) -> Result<Value> {
509    use crate::parser::BinOp;
510    if left.is_null() || right.is_null() {
511        return Ok(Value::Null);
512    }
513    let cmp = match (left, right) {
514        (Value::Text(a), Value::Text(b)) => Some(a.cmp(b)),
515        _ => left.partial_cmp(right),
516    };
517    let Some(cmp) = cmp else {
518        return Ok(Value::Null);
519    };
520    use std::cmp::Ordering;
521    let result = match op {
522        BinOp::Eq => cmp == Ordering::Equal,
523        BinOp::NotEq => cmp != Ordering::Equal,
524        BinOp::Lt => cmp == Ordering::Less,
525        BinOp::Gt => cmp == Ordering::Greater,
526        BinOp::LtEq => cmp != Ordering::Greater,
527        BinOp::GtEq => cmp != Ordering::Less,
528        _ => {
529            return Err(SqlError::Unsupported(format!(
530                "ANY/ALL comparison op {op:?}"
531            )));
532        }
533    };
534    Ok(Value::Boolean(result))
535}
536
537fn collation_of(expr: &Expr) -> Option<crate::types::Collation> {
538    match expr {
539        Expr::Collate { collation, .. } => Some(*collation),
540        _ => None,
541    }
542}
543
544fn column_collation(expr: &Expr, ctx: &EvalCtx<'_>) -> Option<crate::types::Collation> {
545    match expr {
546        Expr::Column(name) => ctx
547            .col_map
548            .resolve(name)
549            .ok()
550            .map(|i| ctx.col_map.collation_at(i)),
551        Expr::QualifiedColumn { table, column } => ctx
552            .col_map
553            .resolve_qualified(table, column)
554            .ok()
555            .map(|i| ctx.col_map.collation_at(i)),
556        _ => None,
557    }
558}
559
560fn eval_text_compare(
561    left: &Value,
562    op: BinOp,
563    right: &Value,
564    coll: crate::types::Collation,
565) -> Option<bool> {
566    let (a, b) = match (left, right) {
567        (Value::Null, _) | (_, Value::Null) => return None,
568        (Value::Text(a), Value::Text(b)) => (a.as_str(), b.as_str()),
569        _ => return None,
570    };
571    let ord = coll.cmp_text(a, b);
572    Some(match op {
573        BinOp::Eq => ord == std::cmp::Ordering::Equal,
574        BinOp::NotEq => ord != std::cmp::Ordering::Equal,
575        BinOp::Lt => ord == std::cmp::Ordering::Less,
576        BinOp::Gt => ord == std::cmp::Ordering::Greater,
577        BinOp::LtEq => ord != std::cmp::Ordering::Greater,
578        BinOp::GtEq => ord != std::cmp::Ordering::Less,
579        _ => return None,
580    })
581}
582
583pub fn eval_binary_op_public(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
584    eval_binary_op(left, op, right)
585}
586
587fn eval_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
588    match op {
589        BinOp::And => return eval_and(left, right),
590        BinOp::Or => return eval_or(left, right),
591        _ => {}
592    }
593
594    if left.is_null() || right.is_null() {
595        return Ok(Value::Null);
596    }
597
598    if let Some(res) = eval_temporal_op(left, op, right) {
599        return res;
600    }
601
602    match op {
603        BinOp::Eq => Ok(Value::Boolean(left == right)),
604        BinOp::NotEq => Ok(Value::Boolean(left != right)),
605        BinOp::Lt => Ok(Value::Boolean(left < right)),
606        BinOp::Gt => Ok(Value::Boolean(left > right)),
607        BinOp::LtEq => Ok(Value::Boolean(left <= right)),
608        BinOp::GtEq => Ok(Value::Boolean(left >= right)),
609        BinOp::Add => eval_arithmetic(left, right, i64::checked_add, |a, b| a + b),
610        BinOp::Sub => match left {
611            Value::Json(_) | Value::Jsonb(_) => crate::json::op_delete_one(left, right),
612            _ => eval_arithmetic(left, right, i64::checked_sub, |a, b| a - b),
613        },
614        BinOp::Mul => eval_arithmetic(left, right, i64::checked_mul, |a, b| a * b),
615        BinOp::Div => {
616            match right {
617                Value::Integer(0) => return Err(SqlError::DivisionByZero),
618                Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
619                _ => {}
620            }
621            eval_arithmetic(left, right, i64::checked_div, |a, b| a / b)
622        }
623        BinOp::Mod => {
624            match right {
625                Value::Integer(0) => return Err(SqlError::DivisionByZero),
626                Value::Real(r) if *r == 0.0 => return Err(SqlError::DivisionByZero),
627                _ => {}
628            }
629            eval_arithmetic(left, right, i64::checked_rem, |a, b| a % b)
630        }
631        BinOp::Concat => match (left, right) {
632            (Value::TsVector(a), Value::TsVector(b)) => crate::fts::op_concat(a, b),
633            (Value::Json(_) | Value::Jsonb(_), _) | (_, Value::Json(_) | Value::Jsonb(_)) => {
634                crate::json::op_concat(left, right)
635            }
636            _ => {
637                let ls = value_to_text(left);
638                let rs = value_to_text(right);
639                Ok(Value::Text(format!("{ls}{rs}").into()))
640            }
641        },
642        BinOp::JsonGet
643        | BinOp::JsonGetText
644        | BinOp::JsonPath
645        | BinOp::JsonPathText
646        | BinOp::JsonContains
647        | BinOp::JsonContainedBy
648        | BinOp::JsonHasKey
649        | BinOp::JsonHasAnyKey
650        | BinOp::JsonHasAllKeys
651        | BinOp::JsonDeletePath
652        | BinOp::JsonPathExists
653        | BinOp::JsonPathMatch
654        | BinOp::JsonPathExistsTz
655        | BinOp::JsonPathMatchTz => eval_json_binary_op(left, op, right),
656        BinOp::VectorL2 => eval_vector_distance(left, right, VectorMetric::L2),
657        BinOp::VectorInner => eval_vector_distance(left, right, VectorMetric::Inner),
658        BinOp::VectorCosine => eval_vector_distance(left, right, VectorMetric::Cosine),
659        BinOp::And | BinOp::Or => unreachable!(),
660    }
661}
662
663#[cold]
664fn eval_json_binary_op(left: &Value, op: BinOp, right: &Value) -> Result<Value> {
665    match op {
666        BinOp::JsonGet => crate::json::op_get(left, right),
667        BinOp::JsonGetText => crate::json::op_get_text(left, right),
668        BinOp::JsonPath => crate::json::op_path(left, right),
669        BinOp::JsonPathText => crate::json::op_path_text(left, right),
670        BinOp::JsonContains => crate::json::op_contains(left, right),
671        BinOp::JsonContainedBy => crate::json::op_contained_by(left, right),
672        BinOp::JsonHasKey => crate::json::op_has_key(left, right),
673        BinOp::JsonHasAnyKey => crate::json::op_has_any_key(left, right),
674        BinOp::JsonHasAllKeys => crate::json::op_has_all_keys(left, right),
675        BinOp::JsonDeletePath => crate::json::op_delete_path(left, right),
676        BinOp::JsonPathExists => crate::json::op_path_exists(left, right),
677        BinOp::JsonPathMatch => eval_at_at(left, right),
678        BinOp::JsonPathExistsTz => {
679            crate::json::fn_jsonb_path_exists_tz(&[left.clone(), right.clone()])
680        }
681        BinOp::JsonPathMatchTz => {
682            crate::json::fn_jsonb_path_match_tz(&[left.clone(), right.clone()])
683        }
684        BinOp::VectorL2 => eval_vector_distance(left, right, VectorMetric::L2),
685        BinOp::VectorInner => eval_vector_distance(left, right, VectorMetric::Inner),
686        BinOp::VectorCosine => eval_vector_distance(left, right, VectorMetric::Cosine),
687        _ => unreachable!(),
688    }
689}
690
691#[derive(Copy, Clone)]
692enum VectorMetric {
693    L2,
694    Inner,
695    Cosine,
696}
697
698fn eval_vector_distance(left: &Value, right: &Value, metric: VectorMetric) -> Result<Value> {
699    if left.is_null() || right.is_null() {
700        return Ok(Value::Null);
701    }
702    let (a, b) = match (left, right) {
703        (Value::Vector(a), Value::Vector(b)) => (a.as_ref(), b.as_ref()),
704        _ => {
705            return Err(SqlError::TypeMismatch {
706                expected: "VECTOR".into(),
707                got: format!("{} vs {}", left.data_type(), right.data_type()),
708            });
709        }
710    };
711    if a.len() != b.len() {
712        return Err(SqlError::InvalidValue(format!(
713            "vector dimension mismatch: {} vs {}",
714            a.len(),
715            b.len()
716        )));
717    }
718    let d = match metric {
719        VectorMetric::L2 => {
720            let mut sum = 0.0f64;
721            for (x, y) in a.iter().zip(b.iter()) {
722                let diff = (*x as f64) - (*y as f64);
723                sum += diff * diff;
724            }
725            sum.sqrt()
726        }
727        VectorMetric::Inner => {
728            let mut sum = 0.0f64;
729            for (x, y) in a.iter().zip(b.iter()) {
730                sum += (*x as f64) * (*y as f64);
731            }
732            -sum
733        }
734        VectorMetric::Cosine => {
735            let mut dot = 0.0f64;
736            let mut na = 0.0f64;
737            let mut nb = 0.0f64;
738            for (x, y) in a.iter().zip(b.iter()) {
739                let xf = *x as f64;
740                let yf = *y as f64;
741                dot += xf * yf;
742                na += xf * xf;
743                nb += yf * yf;
744            }
745            let denom = na.sqrt() * nb.sqrt();
746            if denom == 0.0 {
747                return Ok(Value::Null);
748            }
749            1.0 - dot / denom
750        }
751    };
752    Ok(Value::Real(d))
753}
754
755fn eval_at_at(left: &Value, right: &Value) -> Result<Value> {
756    use crate::types::DataType as D;
757    if left.is_null() || right.is_null() {
758        return Ok(Value::Null);
759    }
760    match (left.data_type(), right.data_type()) {
761        (D::Json | D::Jsonb, D::Text) => crate::json::op_path_match(left, right),
762        (D::TsVector, D::TsQuery) => match (left, right) {
763            (Value::TsVector(v), Value::TsQuery(q)) => crate::fts::op_match(v, q),
764            _ => unreachable!(),
765        },
766        (D::TsQuery, D::TsVector) => match (left, right) {
767            (Value::TsQuery(q), Value::TsVector(v)) => crate::fts::op_match(v, q),
768            _ => unreachable!(),
769        },
770        (D::Text, D::TsQuery) => {
771            let s = match left {
772                Value::Text(s) => s.as_str(),
773                _ => unreachable!(),
774            };
775            let lhs = crate::fts::fn_to_tsvector(s)?;
776            eval_at_at(&lhs, right)
777        }
778        (D::TsVector, D::Text) => {
779            let s = match right {
780                Value::Text(s) => s.as_str(),
781                _ => unreachable!(),
782            };
783            let rhs = crate::fts::fn_plainto_tsquery(s)?;
784            eval_at_at(left, &rhs)
785        }
786        (D::Text, D::Text) => {
787            let ls = match left {
788                Value::Text(s) => s.as_str(),
789                _ => unreachable!(),
790            };
791            let rs = match right {
792                Value::Text(s) => s.as_str(),
793                _ => unreachable!(),
794            };
795            let lhs = crate::fts::fn_to_tsvector(ls)?;
796            let rhs = crate::fts::fn_plainto_tsquery(rs)?;
797            eval_at_at(&lhs, &rhs)
798        }
799        (lt, rt) => Err(SqlError::TypeMismatch {
800            expected: "JSONB @@ text, tsvector @@ tsquery".into(),
801            got: format!("{lt} @@ {rt}"),
802        }),
803    }
804}
805
806/// Returns `Some` when `(left, op, right)` is a temporal operation; `None` to fall through.
807fn eval_temporal_op(left: &Value, op: BinOp, right: &Value) -> Option<Result<Value>> {
808    use crate::datetime as dt;
809    use std::cmp::Ordering;
810
811    let is_temporal = |v: &Value| {
812        matches!(
813            v,
814            Value::Date(_) | Value::Time(_) | Value::Timestamp(_) | Value::Interval { .. }
815        )
816    };
817    if !is_temporal(left) && !is_temporal(right) {
818        return None;
819    }
820    if matches!(op, BinOp::Add | BinOp::Sub)
821        && ((is_temporal(left) && matches!(right, Value::Real(_)))
822            || (matches!(left, Value::Real(_)) && is_temporal(right)))
823    {
824        return Some(Err(SqlError::TypeMismatch {
825            expected: "INTEGER or INTERVAL for date/time arithmetic (use CAST for REAL)".into(),
826            got: format!("{} and {}", left.data_type(), right.data_type()),
827        }));
828    }
829
830    match (left, op, right) {
831        (Value::Date(d), BinOp::Add, Value::Integer(n))
832        | (Value::Integer(n), BinOp::Add, Value::Date(d)) => {
833            Some(dt::add_days_to_date(*d, *n).map(Value::Date))
834        }
835        (Value::Date(d), BinOp::Sub, Value::Integer(n)) => {
836            Some(dt::add_days_to_date(*d, -*n).map(Value::Date))
837        }
838        (Value::Date(a), BinOp::Sub, Value::Date(b)) => {
839            Some(Ok(Value::Integer(*a as i64 - *b as i64)))
840        }
841        // DATE ± INTERVAL → TIMESTAMP (PG rule).
842        (
843            Value::Date(d),
844            BinOp::Add,
845            Value::Interval {
846                months,
847                days,
848                micros,
849            },
850        )
851        | (
852            Value::Interval {
853                months,
854                days,
855                micros,
856            },
857            BinOp::Add,
858            Value::Date(d),
859        ) => Some(dt::add_interval_to_date(*d, *months, *days, *micros).map(Value::Timestamp)),
860        (
861            Value::Date(d),
862            BinOp::Sub,
863            Value::Interval {
864                months,
865                days,
866                micros,
867            },
868        ) => Some(dt::add_interval_to_date(*d, -*months, -*days, -*micros).map(Value::Timestamp)),
869        (
870            Value::Timestamp(t),
871            BinOp::Add,
872            Value::Interval {
873                months,
874                days,
875                micros,
876            },
877        )
878        | (
879            Value::Interval {
880                months,
881                days,
882                micros,
883            },
884            BinOp::Add,
885            Value::Timestamp(t),
886        ) => Some(dt::add_interval_to_timestamp(*t, *months, *days, *micros).map(Value::Timestamp)),
887        (
888            Value::Timestamp(t),
889            BinOp::Sub,
890            Value::Interval {
891                months,
892                days,
893                micros,
894            },
895        ) => Some(
896            dt::add_interval_to_timestamp(*t, -*months, -*days, -*micros).map(Value::Timestamp),
897        ),
898        (Value::Timestamp(a), BinOp::Sub, Value::Timestamp(b)) => {
899            let (days, micros) = dt::subtract_timestamps(*a, *b);
900            Some(Ok(Value::Interval {
901                months: 0,
902                days,
903                micros,
904            }))
905        }
906        (
907            Value::Time(t),
908            BinOp::Add,
909            Value::Interval {
910                months,
911                days,
912                micros,
913            },
914        ) => Some(dt::add_interval_to_time(*t, *months, *days, *micros).map(Value::Time)),
915        (
916            Value::Time(t),
917            BinOp::Sub,
918            Value::Interval {
919                months,
920                days,
921                micros,
922            },
923        ) => Some(dt::add_interval_to_time(*t, -*months, -*days, -*micros).map(Value::Time)),
924        (Value::Time(a), BinOp::Sub, Value::Time(b)) => Some(Ok(Value::Interval {
925            months: 0,
926            days: 0,
927            micros: *a - *b,
928        })),
929        (
930            Value::Interval {
931                months: am,
932                days: ad,
933                micros: au,
934            },
935            BinOp::Add,
936            Value::Interval {
937                months: bm,
938                days: bd,
939                micros: bu,
940            },
941        ) => Some(Ok(Value::Interval {
942            months: am.saturating_add(*bm),
943            days: ad.saturating_add(*bd),
944            micros: au.saturating_add(*bu),
945        })),
946        (
947            Value::Interval {
948                months: am,
949                days: ad,
950                micros: au,
951            },
952            BinOp::Sub,
953            Value::Interval {
954                months: bm,
955                days: bd,
956                micros: bu,
957            },
958        ) => Some(Ok(Value::Interval {
959            months: am.saturating_sub(*bm),
960            days: ad.saturating_sub(*bd),
961            micros: au.saturating_sub(*bu),
962        })),
963        (
964            Value::Interval {
965                months,
966                days,
967                micros,
968            },
969            BinOp::Mul,
970            Value::Integer(n),
971        )
972        | (
973            Value::Integer(n),
974            BinOp::Mul,
975            Value::Interval {
976                months,
977                days,
978                micros,
979            },
980        ) => {
981            let n32 = (*n).clamp(i32::MIN as i64, i32::MAX as i64) as i32;
982            Some(Ok(Value::Interval {
983                months: months.saturating_mul(n32),
984                days: days.saturating_mul(n32),
985                micros: micros.saturating_mul(*n),
986            }))
987        }
988        // INTERVAL * REAL — fractional months → days, fractional days → micros (PG).
989        (
990            Value::Interval {
991                months,
992                days,
993                micros,
994            },
995            BinOp::Mul,
996            Value::Real(r),
997        )
998        | (
999            Value::Real(r),
1000            BinOp::Mul,
1001            Value::Interval {
1002                months,
1003                days,
1004                micros,
1005            },
1006        ) => Some(Ok(scale_interval_by_real(*months, *days, *micros, *r))),
1007        (
1008            Value::Interval {
1009                months,
1010                days,
1011                micros,
1012            },
1013            BinOp::Div,
1014            Value::Integer(n),
1015        ) if *n != 0 => Some(Ok(Value::Interval {
1016            months: (*months as i64 / *n) as i32,
1017            days: (*days as i64 / *n) as i32,
1018            micros: *micros / *n,
1019        })),
1020        (
1021            Value::Interval {
1022                months,
1023                days,
1024                micros,
1025            },
1026            BinOp::Div,
1027            Value::Real(r),
1028        ) if *r != 0.0 => Some(Ok(scale_interval_by_real(*months, *days, *micros, 1.0 / r))),
1029        // PG-normalized INTERVAL compare: 30-day month, 24-hour day.
1030        (
1031            Value::Interval {
1032                months: am,
1033                days: ad,
1034                micros: au,
1035            },
1036            op,
1037            Value::Interval {
1038                months: bm,
1039                days: bd,
1040                micros: bu,
1041            },
1042        ) if matches!(
1043            op,
1044            BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
1045        ) =>
1046        {
1047            let ord = dt::pg_normalized_interval_cmp((*am, *ad, *au), (*bm, *bd, *bu));
1048            let b = match op {
1049                BinOp::Eq => ord == Ordering::Equal,
1050                BinOp::NotEq => ord != Ordering::Equal,
1051                BinOp::Lt => ord == Ordering::Less,
1052                BinOp::Gt => ord == Ordering::Greater,
1053                BinOp::LtEq => ord != Ordering::Greater,
1054                BinOp::GtEq => ord != Ordering::Less,
1055                _ => unreachable!(),
1056            };
1057            Some(Ok(Value::Boolean(b)))
1058        }
1059        // PG rejects TIMESTAMP ± INTEGER; require CAST to INTERVAL.
1060        (Value::Timestamp(_), BinOp::Add | BinOp::Sub, Value::Integer(_))
1061        | (Value::Integer(_), BinOp::Add, Value::Timestamp(_)) => {
1062            Some(Err(SqlError::TypeMismatch {
1063                expected: "INTERVAL (use CAST or explicit unit)".into(),
1064                got: format!("{} and {}", left.data_type(), right.data_type()),
1065            }))
1066        }
1067        _ => None,
1068    }
1069}
1070
1071/// PG fractional-propagation: month frac → days (×30), day frac → micros (×86.4G).
1072fn scale_interval_by_real(months: i32, days: i32, micros: i64, factor: f64) -> Value {
1073    let raw_months = months as f64 * factor;
1074    let whole_months = raw_months.trunc() as i64;
1075    let frac_months = raw_months - whole_months as f64;
1076    let months_frac_as_days = frac_months * 30.0;
1077
1078    let raw_days = days as f64 * factor + months_frac_as_days;
1079    let whole_days = raw_days.trunc() as i64;
1080    let frac_days = raw_days - whole_days as f64;
1081    let days_frac_as_micros = (frac_days * crate::datetime::MICROS_PER_DAY as f64).round() as i64;
1082
1083    let raw_micros = (micros as f64 * factor).round() as i64;
1084    let total_micros = raw_micros.saturating_add(days_frac_as_micros);
1085
1086    let clamp_i32 = |n: i64| n.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
1087    Value::Interval {
1088        months: clamp_i32(whole_months),
1089        days: clamp_i32(whole_days),
1090        micros: total_micros,
1091    }
1092}
1093
1094/// SQL three-valued AND: NULL AND false = false, NULL AND true = NULL
1095fn eval_and(left: &Value, right: &Value) -> Result<Value> {
1096    let l = to_bool_or_null(left)?;
1097    let r = to_bool_or_null(right)?;
1098    match (l, r) {
1099        (Some(false), _) | (_, Some(false)) => Ok(Value::Boolean(false)),
1100        (Some(true), Some(true)) => Ok(Value::Boolean(true)),
1101        _ => Ok(Value::Null),
1102    }
1103}
1104
1105/// SQL three-valued OR: NULL OR true = true, NULL OR false = NULL
1106fn eval_or(left: &Value, right: &Value) -> Result<Value> {
1107    let l = to_bool_or_null(left)?;
1108    let r = to_bool_or_null(right)?;
1109    match (l, r) {
1110        (Some(true), _) | (_, Some(true)) => Ok(Value::Boolean(true)),
1111        (Some(false), Some(false)) => Ok(Value::Boolean(false)),
1112        _ => Ok(Value::Null),
1113    }
1114}
1115
1116fn to_bool_or_null(val: &Value) -> Result<Option<bool>> {
1117    match val {
1118        Value::Boolean(b) => Ok(Some(*b)),
1119        Value::Null => Ok(None),
1120        Value::Integer(i) => Ok(Some(*i != 0)),
1121        _ => Err(SqlError::TypeMismatch {
1122            expected: "BOOLEAN".into(),
1123            got: format!("{}", val.data_type()),
1124        }),
1125    }
1126}
1127
1128fn eval_arithmetic(
1129    left: &Value,
1130    right: &Value,
1131    int_op: fn(i64, i64) -> Option<i64>,
1132    real_op: fn(f64, f64) -> f64,
1133) -> Result<Value> {
1134    match (left, right) {
1135        (Value::Integer(a), Value::Integer(b)) => int_op(*a, *b)
1136            .map(Value::Integer)
1137            .ok_or(SqlError::IntegerOverflow),
1138        (Value::Real(a), Value::Real(b)) => Ok(Value::Real(real_op(*a, *b))),
1139        (Value::Integer(a), Value::Real(b)) => Ok(Value::Real(real_op(*a as f64, *b))),
1140        (Value::Real(a), Value::Integer(b)) => Ok(Value::Real(real_op(*a, *b as f64))),
1141        _ => Err(SqlError::TypeMismatch {
1142            expected: "numeric".into(),
1143            got: format!("{} and {}", left.data_type(), right.data_type()),
1144        }),
1145    }
1146}
1147
1148fn eval_in_values(lhs: &Value, list: &[Expr], ctx: &EvalCtx, negated: bool) -> Result<Value> {
1149    if list.is_empty() {
1150        return Ok(Value::Boolean(negated));
1151    }
1152    if lhs.is_null() {
1153        return Ok(Value::Null);
1154    }
1155    let mut has_null = false;
1156    for item in list {
1157        let rhs = eval_expr(item, ctx)?;
1158        if rhs.is_null() {
1159            has_null = true;
1160        } else if lhs == &rhs {
1161            return Ok(Value::Boolean(!negated));
1162        }
1163    }
1164    if has_null {
1165        Ok(Value::Null)
1166    } else {
1167        Ok(Value::Boolean(negated))
1168    }
1169}
1170
1171fn eval_in_set(
1172    lhs: &Value,
1173    values: &rustc_hash::FxHashSet<Value>,
1174    has_null: bool,
1175    negated: bool,
1176) -> Result<Value> {
1177    if values.is_empty() && !has_null {
1178        return Ok(Value::Boolean(negated));
1179    }
1180    if lhs.is_null() {
1181        return Ok(Value::Null);
1182    }
1183    if values.contains(lhs) {
1184        return Ok(Value::Boolean(!negated));
1185    }
1186    if has_null {
1187        Ok(Value::Null)
1188    } else {
1189        Ok(Value::Boolean(negated))
1190    }
1191}
1192
1193fn eval_unary_op(op: UnaryOp, val: &Value) -> Result<Value> {
1194    if val.is_null() {
1195        return Ok(Value::Null);
1196    }
1197    match op {
1198        UnaryOp::Neg => match val {
1199            Value::Integer(i) => i
1200                .checked_neg()
1201                .map(Value::Integer)
1202                .ok_or(SqlError::IntegerOverflow),
1203            Value::Real(r) => Ok(Value::Real(-r)),
1204            Value::Interval {
1205                months,
1206                days,
1207                micros,
1208            } => {
1209                let m = months.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1210                let d = days.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1211                let u = micros.checked_neg().ok_or(SqlError::IntegerOverflow)?;
1212                Ok(Value::Interval {
1213                    months: m,
1214                    days: d,
1215                    micros: u,
1216                })
1217            }
1218            _ => Err(SqlError::TypeMismatch {
1219                expected: "numeric or INTERVAL".into(),
1220                got: format!("{}", val.data_type()),
1221            }),
1222        },
1223        UnaryOp::Not => match val {
1224            Value::Boolean(b) => Ok(Value::Boolean(!b)),
1225            Value::Integer(i) => Ok(Value::Boolean(*i == 0)),
1226            _ => Err(SqlError::TypeMismatch {
1227                expected: "BOOLEAN".into(),
1228                got: format!("{}", val.data_type()),
1229            }),
1230        },
1231    }
1232}
1233
1234fn value_to_text(val: &Value) -> String {
1235    match val {
1236        Value::Text(s) => s.to_string(),
1237        Value::Integer(i) => i.to_string(),
1238        Value::Real(r) => {
1239            if r.fract() == 0.0 && r.is_finite() {
1240                format!("{r:.1}")
1241            } else {
1242                format!("{r}")
1243            }
1244        }
1245        Value::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.into(),
1246        Value::Null => String::new(),
1247        Value::Blob(b) => {
1248            let mut s = String::with_capacity(b.len() * 2);
1249            for byte in b {
1250                s.push_str(&format!("{byte:02X}"));
1251            }
1252            s
1253        }
1254        Value::Date(d) => crate::datetime::format_date(*d),
1255        Value::Time(t) => crate::datetime::format_time(*t),
1256        Value::Timestamp(t) => crate::datetime::format_timestamp(*t),
1257        Value::Interval {
1258            months,
1259            days,
1260            micros,
1261        } => crate::datetime::format_interval(*months, *days, *micros),
1262        Value::Json(s) => s.to_string(),
1263        Value::Jsonb(b) => crate::json::decode_to_text(b).unwrap_or_default(),
1264        Value::TsVector(b) => crate::fts::tsvector_display(b),
1265        Value::TsQuery(b) => crate::fts::tsquery_display(b),
1266        Value::Array(_) => val.to_string(),
1267        Value::Vector(_) => val.to_string(),
1268    }
1269}
1270
1271fn eval_between(val: &Value, low: &Value, high: &Value, negated: bool) -> Result<Value> {
1272    if val.is_null() || low.is_null() || high.is_null() {
1273        let ge = if val.is_null() || low.is_null() {
1274            None
1275        } else {
1276            Some(*val >= *low)
1277        };
1278        let le = if val.is_null() || high.is_null() {
1279            None
1280        } else {
1281            Some(*val <= *high)
1282        };
1283
1284        let result = match (ge, le) {
1285            (Some(false), _) | (_, Some(false)) => Some(false),
1286            (Some(true), Some(true)) => Some(true),
1287            _ => None,
1288        };
1289
1290        return match result {
1291            Some(b) => Ok(Value::Boolean(if negated { !b } else { b })),
1292            None => Ok(Value::Null),
1293        };
1294    }
1295
1296    let in_range = *val >= *low && *val <= *high;
1297    Ok(Value::Boolean(if negated { !in_range } else { in_range }))
1298}
1299
1300const MAX_LIKE_PATTERN_LEN: usize = 10_000;
1301
1302fn eval_like(val: &Value, pattern: &Value, escape: Option<&Value>, negated: bool) -> Result<Value> {
1303    if val.is_null() || pattern.is_null() {
1304        return Ok(Value::Null);
1305    }
1306    let text = match val {
1307        Value::Text(s) => s.as_str(),
1308        _ => {
1309            return Err(SqlError::TypeMismatch {
1310                expected: "TEXT".into(),
1311                got: val.data_type().to_string(),
1312            })
1313        }
1314    };
1315    let pat = match pattern {
1316        Value::Text(s) => s.as_str(),
1317        _ => {
1318            return Err(SqlError::TypeMismatch {
1319                expected: "TEXT".into(),
1320                got: pattern.data_type().to_string(),
1321            })
1322        }
1323    };
1324
1325    if pat.len() > MAX_LIKE_PATTERN_LEN {
1326        return Err(SqlError::InvalidValue(format!(
1327            "LIKE pattern too long ({} chars, max {MAX_LIKE_PATTERN_LEN})",
1328            pat.len()
1329        )));
1330    }
1331
1332    let esc_char = match escape {
1333        Some(Value::Text(s)) => {
1334            let mut chars = s.chars();
1335            let c = chars.next().ok_or_else(|| {
1336                SqlError::InvalidValue("ESCAPE must be a single character".into())
1337            })?;
1338            if chars.next().is_some() {
1339                return Err(SqlError::InvalidValue(
1340                    "ESCAPE must be a single character".into(),
1341                ));
1342            }
1343            Some(c)
1344        }
1345        Some(Value::Null) => return Ok(Value::Null),
1346        Some(_) => {
1347            return Err(SqlError::TypeMismatch {
1348                expected: "TEXT".into(),
1349                got: "non-text".into(),
1350            })
1351        }
1352        None => None,
1353    };
1354
1355    let matched = like_match(text, pat, esc_char);
1356    Ok(Value::Boolean(if negated { !matched } else { matched }))
1357}
1358
1359fn like_match(text: &str, pattern: &str, escape: Option<char>) -> bool {
1360    let t: Vec<char> = text.chars().collect();
1361    let p: Vec<char> = pattern.chars().collect();
1362    like_match_impl(&t, &p, 0, 0, escape)
1363}
1364
1365fn like_match_impl(
1366    t: &[char],
1367    p: &[char],
1368    mut ti: usize,
1369    mut pi: usize,
1370    esc: Option<char>,
1371) -> bool {
1372    let mut star_pi: Option<usize> = None;
1373    let mut star_ti: usize = 0;
1374
1375    while ti < t.len() {
1376        if pi < p.len() {
1377            if let Some(ec) = esc {
1378                if p[pi] == ec && pi + 1 < p.len() {
1379                    pi += 1;
1380                    let pc_lower = p[pi].to_ascii_lowercase();
1381                    let tc_lower = t[ti].to_ascii_lowercase();
1382                    if pc_lower == tc_lower {
1383                        pi += 1;
1384                        ti += 1;
1385                        continue;
1386                    } else if let Some(sp) = star_pi {
1387                        pi = sp + 1;
1388                        star_ti += 1;
1389                        ti = star_ti;
1390                        continue;
1391                    } else {
1392                        return false;
1393                    }
1394                }
1395            }
1396            if p[pi] == '%' {
1397                star_pi = Some(pi);
1398                star_ti = ti;
1399                pi += 1;
1400                continue;
1401            }
1402            if p[pi] == '_' {
1403                pi += 1;
1404                ti += 1;
1405                continue;
1406            }
1407            if p[pi].eq_ignore_ascii_case(&t[ti]) {
1408                pi += 1;
1409                ti += 1;
1410                continue;
1411            }
1412        }
1413        if let Some(sp) = star_pi {
1414            pi = sp + 1;
1415            star_ti += 1;
1416            ti = star_ti;
1417        } else {
1418            return false;
1419        }
1420    }
1421
1422    while pi < p.len() && p[pi] == '%' {
1423        pi += 1;
1424    }
1425    pi == p.len()
1426}
1427
1428fn eval_case(
1429    operand: Option<&Expr>,
1430    conditions: &[(Expr, Expr)],
1431    else_result: Option<&Expr>,
1432    ctx: &EvalCtx,
1433) -> Result<Value> {
1434    if let Some(op_expr) = operand {
1435        let op_val = eval_expr(op_expr, ctx)?;
1436        for (cond, result) in conditions {
1437            let cond_val = eval_expr(cond, ctx)?;
1438            if !op_val.is_null() && !cond_val.is_null() && op_val == cond_val {
1439                return eval_expr(result, ctx);
1440            }
1441        }
1442    } else {
1443        for (cond, result) in conditions {
1444            let cond_val = eval_expr(cond, ctx)?;
1445            if is_truthy(&cond_val) {
1446                return eval_expr(result, ctx);
1447            }
1448        }
1449    }
1450    match else_result {
1451        Some(e) => eval_expr(e, ctx),
1452        None => Ok(Value::Null),
1453    }
1454}
1455
1456pub(crate) fn eval_cast(val: &Value, target: DataType) -> Result<Value> {
1457    if val.is_null() {
1458        return Ok(Value::Null);
1459    }
1460    match target {
1461        DataType::Integer => match val {
1462            Value::Integer(_) => Ok(val.clone()),
1463            Value::Real(r) => Ok(Value::Integer(*r as i64)),
1464            Value::Boolean(b) => Ok(Value::Integer(if *b { 1 } else { 0 })),
1465            Value::Text(s) => s
1466                .trim()
1467                .parse::<i64>()
1468                .map(Value::Integer)
1469                .or_else(|_| s.trim().parse::<f64>().map(|f| Value::Integer(f as i64)))
1470                .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to INTEGER"))),
1471            _ => Err(SqlError::InvalidValue(format!(
1472                "cannot cast {} to INTEGER",
1473                val.data_type()
1474            ))),
1475        },
1476        DataType::Real => match val {
1477            Value::Real(_) => Ok(val.clone()),
1478            Value::Integer(i) => Ok(Value::Real(*i as f64)),
1479            Value::Boolean(b) => Ok(Value::Real(if *b { 1.0 } else { 0.0 })),
1480            Value::Text(s) => s
1481                .trim()
1482                .parse::<f64>()
1483                .map(Value::Real)
1484                .map_err(|_| SqlError::InvalidValue(format!("cannot cast '{s}' to REAL"))),
1485            _ => Err(SqlError::InvalidValue(format!(
1486                "cannot cast {} to REAL",
1487                val.data_type()
1488            ))),
1489        },
1490        DataType::Text => Ok(Value::Text(value_to_text(val).into())),
1491        DataType::Boolean => match val {
1492            Value::Boolean(_) => Ok(val.clone()),
1493            Value::Integer(i) => Ok(Value::Boolean(*i != 0)),
1494            Value::Text(s) => {
1495                let lower = s.trim().to_ascii_lowercase();
1496                match lower.as_str() {
1497                    "true" | "1" | "yes" | "on" => Ok(Value::Boolean(true)),
1498                    "false" | "0" | "no" | "off" => Ok(Value::Boolean(false)),
1499                    _ => Err(SqlError::InvalidValue(format!(
1500                        "cannot cast '{s}' to BOOLEAN"
1501                    ))),
1502                }
1503            }
1504            _ => Err(SqlError::InvalidValue(format!(
1505                "cannot cast {} to BOOLEAN",
1506                val.data_type()
1507            ))),
1508        },
1509        DataType::Blob => match val {
1510            Value::Blob(_) => Ok(val.clone()),
1511            Value::Text(s) => Ok(Value::Blob(s.as_bytes().to_vec())),
1512            _ => Err(SqlError::InvalidValue(format!(
1513                "cannot cast {} to BLOB",
1514                val.data_type()
1515            ))),
1516        },
1517        DataType::Null => Ok(Value::Null),
1518        DataType::Date => val.clone().coerce_into(DataType::Date).ok_or_else(|| {
1519            SqlError::InvalidValue(format!("cannot cast {} to DATE", val.data_type()))
1520        }),
1521        DataType::Time => val.clone().coerce_into(DataType::Time).ok_or_else(|| {
1522            SqlError::InvalidValue(format!("cannot cast {} to TIME", val.data_type()))
1523        }),
1524        DataType::Timestamp => val.clone().coerce_into(DataType::Timestamp).ok_or_else(|| {
1525            SqlError::InvalidValue(format!("cannot cast {} to TIMESTAMP", val.data_type()))
1526        }),
1527        DataType::Interval => val.clone().coerce_into(DataType::Interval).ok_or_else(|| {
1528            SqlError::InvalidValue(format!("cannot cast {} to INTERVAL", val.data_type()))
1529        }),
1530        DataType::Json => val.clone().coerce_into(DataType::Json).ok_or_else(|| {
1531            SqlError::InvalidValue(format!("cannot cast {} to JSON", val.data_type()))
1532        }),
1533        DataType::Jsonb => val.clone().coerce_into(DataType::Jsonb).ok_or_else(|| {
1534            SqlError::InvalidValue(format!("cannot cast {} to JSONB", val.data_type()))
1535        }),
1536        DataType::TsVector => val.clone().coerce_into(DataType::TsVector).ok_or_else(|| {
1537            SqlError::InvalidValue(format!("cannot cast {} to TSVECTOR", val.data_type()))
1538        }),
1539        DataType::TsQuery => val.clone().coerce_into(DataType::TsQuery).ok_or_else(|| {
1540            SqlError::InvalidValue(format!("cannot cast {} to TSQUERY", val.data_type()))
1541        }),
1542        DataType::Array => val.clone().coerce_into(DataType::Array).ok_or_else(|| {
1543            SqlError::InvalidValue(format!("cannot cast {} to ARRAY", val.data_type()))
1544        }),
1545        DataType::Vector { dim } => match val {
1546            Value::Vector(v) if v.len() as u16 == dim => Ok(val.clone()),
1547            Value::Vector(_) => Err(SqlError::InvalidValue(format!(
1548                "cannot cast {} to VECTOR({dim}) (dim mismatch)",
1549                val.data_type()
1550            ))),
1551            Value::Text(s) => parse_vector_literal(s.as_str(), dim).map(Value::Vector),
1552            _ => Err(SqlError::InvalidValue(format!(
1553                "cannot cast {} to VECTOR({dim})",
1554                val.data_type()
1555            ))),
1556        },
1557    }
1558}
1559
1560fn parse_vector_literal(s: &str, expected_dim: u16) -> Result<std::sync::Arc<[f32]>> {
1561    let trimmed = s.trim();
1562    let inner = trimmed
1563        .strip_prefix('[')
1564        .and_then(|s| s.strip_suffix(']'))
1565        .unwrap_or(trimmed);
1566    let mut out: Vec<f32> = Vec::with_capacity(expected_dim as usize);
1567    for tok in inner.split(',') {
1568        let tok = tok.trim();
1569        if tok.is_empty() {
1570            continue;
1571        }
1572        let x: f32 = tok
1573            .parse()
1574            .map_err(|_| SqlError::InvalidValue(format!("invalid vector element: '{tok}'")))?;
1575        out.push(x);
1576    }
1577    if out.len() as u16 != expected_dim {
1578        return Err(SqlError::InvalidValue(format!(
1579            "vector literal has {} elements, expected {expected_dim}",
1580            out.len()
1581        )));
1582    }
1583    Ok(std::sync::Arc::from(out.into_boxed_slice()))
1584}
1585
1586fn eval_scalar_function(name: &str, args: &[Expr], ctx: &EvalCtx) -> Result<Value> {
1587    let evaluated: Vec<Value> = args
1588        .iter()
1589        .map(|a| eval_expr(a, ctx))
1590        .collect::<Result<Vec<_>>>()?;
1591
1592    match name {
1593        "LENGTH" => {
1594            check_args(name, &evaluated, 1)?;
1595            match &evaluated[0] {
1596                Value::Null => Ok(Value::Null),
1597                Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
1598                Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
1599                Value::TsVector(b) => crate::fts::fn_length_tsvector(b),
1600                _ => Ok(Value::Integer(
1601                    value_to_text(&evaluated[0]).chars().count() as i64
1602                )),
1603            }
1604        }
1605        "UPPER" => {
1606            check_args(name, &evaluated, 1)?;
1607            match &evaluated[0] {
1608                Value::Null => Ok(Value::Null),
1609                Value::Text(s) => Ok(Value::Text(s.to_ascii_uppercase())),
1610                _ => Ok(Value::Text(
1611                    value_to_text(&evaluated[0]).to_ascii_uppercase().into(),
1612                )),
1613            }
1614        }
1615        "LOWER" => {
1616            check_args(name, &evaluated, 1)?;
1617            match &evaluated[0] {
1618                Value::Null => Ok(Value::Null),
1619                Value::Text(s) => Ok(Value::Text(s.to_ascii_lowercase())),
1620                _ => Ok(Value::Text(
1621                    value_to_text(&evaluated[0]).to_ascii_lowercase().into(),
1622                )),
1623            }
1624        }
1625        "SUBSTR" | "SUBSTRING" => {
1626            if evaluated.len() < 2 || evaluated.len() > 3 {
1627                return Err(SqlError::InvalidValue(format!(
1628                    "{name} requires 2 or 3 arguments"
1629                )));
1630            }
1631            if evaluated.iter().any(|v| v.is_null()) {
1632                return Ok(Value::Null);
1633            }
1634            let s = value_to_text(&evaluated[0]);
1635            let chars: Vec<char> = s.chars().collect();
1636            let start = match &evaluated[1] {
1637                Value::Integer(i) => *i,
1638                _ => {
1639                    return Err(SqlError::TypeMismatch {
1640                        expected: "INTEGER".into(),
1641                        got: evaluated[1].data_type().to_string(),
1642                    })
1643                }
1644            };
1645            let len = chars.len() as i64;
1646
1647            let (begin, count) = if evaluated.len() == 3 {
1648                let cnt = match &evaluated[2] {
1649                    Value::Integer(i) => *i,
1650                    _ => {
1651                        return Err(SqlError::TypeMismatch {
1652                            expected: "INTEGER".into(),
1653                            got: evaluated[2].data_type().to_string(),
1654                        })
1655                    }
1656                };
1657                if start >= 1 {
1658                    let b = (start - 1).min(len) as usize;
1659                    let c = cnt.max(0) as usize;
1660                    (b, c)
1661                } else if start == 0 {
1662                    let c = (cnt - 1).max(0) as usize;
1663                    (0usize, c)
1664                } else {
1665                    let adjusted_cnt = (cnt + start - 1).max(0) as usize;
1666                    (0usize, adjusted_cnt)
1667                }
1668            } else if start >= 1 {
1669                let b = (start - 1).min(len) as usize;
1670                (b, chars.len() - b)
1671            } else if start == 0 {
1672                (0usize, chars.len())
1673            } else {
1674                let b = (len + start).max(0) as usize;
1675                (b, chars.len() - b)
1676            };
1677
1678            let result: String = chars.iter().skip(begin).take(count).collect();
1679            Ok(Value::Text(result.into()))
1680        }
1681        "TRIM" | "LTRIM" | "RTRIM" => {
1682            if evaluated.is_empty() || evaluated.len() > 2 {
1683                return Err(SqlError::InvalidValue(format!(
1684                    "{name} requires 1 or 2 arguments"
1685                )));
1686            }
1687            if evaluated[0].is_null() {
1688                return Ok(Value::Null);
1689            }
1690            let s = value_to_text(&evaluated[0]);
1691            let trim_chars: Vec<char> = if evaluated.len() == 2 {
1692                if evaluated[1].is_null() {
1693                    return Ok(Value::Null);
1694                }
1695                value_to_text(&evaluated[1]).chars().collect()
1696            } else {
1697                vec![' ']
1698            };
1699            let result = match name {
1700                "TRIM" => s
1701                    .trim_matches(|c: char| trim_chars.contains(&c))
1702                    .to_string(),
1703                "LTRIM" => s
1704                    .trim_start_matches(|c: char| trim_chars.contains(&c))
1705                    .to_string(),
1706                "RTRIM" => s
1707                    .trim_end_matches(|c: char| trim_chars.contains(&c))
1708                    .to_string(),
1709                _ => unreachable!(),
1710            };
1711            Ok(Value::Text(result.into()))
1712        }
1713        "REPLACE" => {
1714            check_args(name, &evaluated, 3)?;
1715            if evaluated.iter().any(|v| v.is_null()) {
1716                return Ok(Value::Null);
1717            }
1718            let s = value_to_text(&evaluated[0]);
1719            let from = value_to_text(&evaluated[1]);
1720            let to = value_to_text(&evaluated[2]);
1721            if from.is_empty() {
1722                return Ok(Value::Text(s.into()));
1723            }
1724            Ok(Value::Text(s.replace(&from, &to).into()))
1725        }
1726        "INSTR" => {
1727            check_args(name, &evaluated, 2)?;
1728            if evaluated.iter().any(|v| v.is_null()) {
1729                return Ok(Value::Null);
1730            }
1731            let haystack = value_to_text(&evaluated[0]);
1732            let needle = value_to_text(&evaluated[1]);
1733            let pos = haystack
1734                .find(&needle)
1735                .map(|i| haystack[..i].chars().count() as i64 + 1)
1736                .unwrap_or(0);
1737            Ok(Value::Integer(pos))
1738        }
1739        "CONCAT" => {
1740            if evaluated.is_empty() {
1741                return Ok(Value::Text(CompactString::default()));
1742            }
1743            let mut result = String::new();
1744            for v in &evaluated {
1745                match v {
1746                    Value::Null => {}
1747                    _ => result.push_str(&value_to_text(v)),
1748                }
1749            }
1750            Ok(Value::Text(result.into()))
1751        }
1752        "ABS" => {
1753            check_args(name, &evaluated, 1)?;
1754            match &evaluated[0] {
1755                Value::Null => Ok(Value::Null),
1756                Value::Integer(i) => i
1757                    .checked_abs()
1758                    .map(Value::Integer)
1759                    .ok_or(SqlError::IntegerOverflow),
1760                Value::Real(r) => Ok(Value::Real(r.abs())),
1761                _ => Err(SqlError::TypeMismatch {
1762                    expected: "numeric".into(),
1763                    got: evaluated[0].data_type().to_string(),
1764                }),
1765            }
1766        }
1767        "ROUND" => {
1768            if evaluated.is_empty() || evaluated.len() > 2 {
1769                return Err(SqlError::InvalidValue(
1770                    "ROUND requires 1 or 2 arguments".into(),
1771                ));
1772            }
1773            if evaluated[0].is_null() {
1774                return Ok(Value::Null);
1775            }
1776            let val = match &evaluated[0] {
1777                Value::Integer(i) => *i as f64,
1778                Value::Real(r) => *r,
1779                _ => {
1780                    return Err(SqlError::TypeMismatch {
1781                        expected: "numeric".into(),
1782                        got: evaluated[0].data_type().to_string(),
1783                    })
1784                }
1785            };
1786            let places = if evaluated.len() == 2 {
1787                match &evaluated[1] {
1788                    Value::Null => return Ok(Value::Null),
1789                    Value::Integer(i) => *i,
1790                    _ => {
1791                        return Err(SqlError::TypeMismatch {
1792                            expected: "INTEGER".into(),
1793                            got: evaluated[1].data_type().to_string(),
1794                        })
1795                    }
1796                }
1797            } else {
1798                0
1799            };
1800            let factor = 10f64.powi(places as i32);
1801            let rounded = (val * factor).round() / factor;
1802            Ok(Value::Real(rounded))
1803        }
1804        "CEIL" | "CEILING" => {
1805            check_args(name, &evaluated, 1)?;
1806            match &evaluated[0] {
1807                Value::Null => Ok(Value::Null),
1808                Value::Integer(i) => Ok(Value::Integer(*i)),
1809                Value::Real(r) => Ok(Value::Integer(r.ceil() as i64)),
1810                _ => Err(SqlError::TypeMismatch {
1811                    expected: "numeric".into(),
1812                    got: evaluated[0].data_type().to_string(),
1813                }),
1814            }
1815        }
1816        "FLOOR" => {
1817            check_args(name, &evaluated, 1)?;
1818            match &evaluated[0] {
1819                Value::Null => Ok(Value::Null),
1820                Value::Integer(i) => Ok(Value::Integer(*i)),
1821                Value::Real(r) => Ok(Value::Integer(r.floor() as i64)),
1822                _ => Err(SqlError::TypeMismatch {
1823                    expected: "numeric".into(),
1824                    got: evaluated[0].data_type().to_string(),
1825                }),
1826            }
1827        }
1828        "SIGN" => {
1829            check_args(name, &evaluated, 1)?;
1830            match &evaluated[0] {
1831                Value::Null => Ok(Value::Null),
1832                Value::Integer(i) => Ok(Value::Integer(i.signum())),
1833                Value::Real(r) => {
1834                    if *r > 0.0 {
1835                        Ok(Value::Integer(1))
1836                    } else if *r < 0.0 {
1837                        Ok(Value::Integer(-1))
1838                    } else {
1839                        Ok(Value::Integer(0))
1840                    }
1841                }
1842                _ => Err(SqlError::TypeMismatch {
1843                    expected: "numeric".into(),
1844                    got: evaluated[0].data_type().to_string(),
1845                }),
1846            }
1847        }
1848        "SQRT" => {
1849            check_args(name, &evaluated, 1)?;
1850            match &evaluated[0] {
1851                Value::Null => Ok(Value::Null),
1852                Value::Integer(i) => {
1853                    if *i < 0 {
1854                        Ok(Value::Null)
1855                    } else {
1856                        Ok(Value::Real((*i as f64).sqrt()))
1857                    }
1858                }
1859                Value::Real(r) => {
1860                    if *r < 0.0 {
1861                        Ok(Value::Null)
1862                    } else {
1863                        Ok(Value::Real(r.sqrt()))
1864                    }
1865                }
1866                _ => Err(SqlError::TypeMismatch {
1867                    expected: "numeric".into(),
1868                    got: evaluated[0].data_type().to_string(),
1869                }),
1870            }
1871        }
1872        "RANDOM" => {
1873            check_args(name, &evaluated, 0)?;
1874            use std::collections::hash_map::DefaultHasher;
1875            use std::hash::{Hash, Hasher};
1876            let mut hasher = DefaultHasher::new();
1877            crate::datetime::now_micros().hash(&mut hasher);
1878            std::thread::current().id().hash(&mut hasher);
1879            let mut val = hasher.finish() as i64;
1880            if val == i64::MIN {
1881                val = i64::MAX;
1882            }
1883            Ok(Value::Integer(val))
1884        }
1885        "TYPEOF" => {
1886            check_args(name, &evaluated, 1)?;
1887            let type_name = match &evaluated[0] {
1888                Value::Null => "null",
1889                Value::Integer(_) => "integer",
1890                Value::Real(_) => "real",
1891                Value::Text(_) => "text",
1892                Value::Blob(_) => "blob",
1893                Value::Boolean(_) => "boolean",
1894                Value::Date(_) => "date",
1895                Value::Time(_) => "time",
1896                Value::Timestamp(_) => "timestamp",
1897                Value::Interval { .. } => "interval",
1898                Value::Json(_) => "json",
1899                Value::Jsonb(_) => "jsonb",
1900                Value::TsVector(_) => "tsvector",
1901                Value::TsQuery(_) => "tsquery",
1902                Value::Array(_) => "array",
1903                Value::Vector(_) => "vector",
1904            };
1905            Ok(Value::Text(type_name.into()))
1906        }
1907        "MIN" => {
1908            check_args(name, &evaluated, 2)?;
1909            if evaluated[0].is_null() {
1910                return Ok(evaluated[1].clone());
1911            }
1912            if evaluated[1].is_null() {
1913                return Ok(evaluated[0].clone());
1914            }
1915            if evaluated[0] <= evaluated[1] {
1916                Ok(evaluated[0].clone())
1917            } else {
1918                Ok(evaluated[1].clone())
1919            }
1920        }
1921        "MAX" => {
1922            check_args(name, &evaluated, 2)?;
1923            if evaluated[0].is_null() {
1924                return Ok(evaluated[1].clone());
1925            }
1926            if evaluated[1].is_null() {
1927                return Ok(evaluated[0].clone());
1928            }
1929            if evaluated[0] >= evaluated[1] {
1930                Ok(evaluated[0].clone())
1931            } else {
1932                Ok(evaluated[1].clone())
1933            }
1934        }
1935        "HEX" => {
1936            check_args(name, &evaluated, 1)?;
1937            match &evaluated[0] {
1938                Value::Null => Ok(Value::Null),
1939                Value::Blob(b) => {
1940                    let mut s = String::with_capacity(b.len() * 2);
1941                    for byte in b {
1942                        s.push_str(&format!("{byte:02X}"));
1943                    }
1944                    Ok(Value::Text(s.into()))
1945                }
1946                Value::Text(s) => {
1947                    let mut r = String::with_capacity(s.len() * 2);
1948                    for byte in s.as_bytes() {
1949                        r.push_str(&format!("{byte:02X}"));
1950                    }
1951                    Ok(Value::Text(r.into()))
1952                }
1953                _ => Ok(Value::Text(value_to_text(&evaluated[0]).into())),
1954            }
1955        }
1956        "NOW" | "CURRENT_TIMESTAMP" | "LOCALTIMESTAMP" => {
1957            check_args(name, &evaluated, 0)?;
1958            Ok(Value::Timestamp(crate::datetime::txn_or_clock_micros()))
1959        }
1960        "CURRENT_DATE" => {
1961            check_args(name, &evaluated, 0)?;
1962            Ok(Value::Date(crate::datetime::ts_to_date_floor(
1963                crate::datetime::txn_or_clock_micros(),
1964            )))
1965        }
1966        "CURRENT_TIME" | "LOCALTIME" => {
1967            check_args(name, &evaluated, 0)?;
1968            Ok(Value::Time(
1969                crate::datetime::ts_split(crate::datetime::txn_or_clock_micros()).1,
1970            ))
1971        }
1972        "CLOCK_TIMESTAMP" | "STATEMENT_TIMESTAMP" | "TRANSACTION_TIMESTAMP" => {
1973            check_args(name, &evaluated, 0)?;
1974            let ts = match name {
1975                "CLOCK_TIMESTAMP" => crate::datetime::now_micros(),
1976                _ => crate::datetime::txn_or_clock_micros(),
1977            };
1978            Ok(Value::Timestamp(ts))
1979        }
1980        "EXTRACT" | "DATE_PART" | "DATEPART" => {
1981            check_args(name, &evaluated, 2)?;
1982            let field: &str = match &evaluated[0] {
1983                Value::Null => return Ok(Value::Null),
1984                Value::Text(s) => s.as_str(),
1985                _ => {
1986                    return Err(SqlError::TypeMismatch {
1987                        expected: "TEXT field name".into(),
1988                        got: evaluated[0].data_type().to_string(),
1989                    })
1990                }
1991            };
1992            if evaluated[1].is_null() {
1993                return Ok(Value::Null);
1994            }
1995            crate::datetime::extract(field, &evaluated[1])
1996        }
1997        "DATE_TRUNC" => {
1998            if evaluated.len() < 2 || evaluated.len() > 3 {
1999                return Err(SqlError::InvalidValue(
2000                    "DATE_TRUNC requires 2 or 3 arguments".into(),
2001                ));
2002            }
2003            let unit = match &evaluated[0] {
2004                Value::Null => return Ok(Value::Null),
2005                Value::Text(s) => s.to_string(),
2006                _ => {
2007                    return Err(SqlError::TypeMismatch {
2008                        expected: "TEXT unit name".into(),
2009                        got: evaluated[0].data_type().to_string(),
2010                    })
2011                }
2012            };
2013            if evaluated[1].is_null() {
2014                return Ok(Value::Null);
2015            }
2016            // Optional tz arg: truncate in that zone, then convert back to UTC.
2017            if evaluated.len() == 3 {
2018                if let Value::Text(tz) = &evaluated[2] {
2019                    if !tz.eq_ignore_ascii_case("UTC") {
2020                        if let Value::Timestamp(ts) = &evaluated[1] {
2021                            return date_trunc_in_zone(&unit, *ts, tz);
2022                        }
2023                    }
2024                }
2025            }
2026            crate::datetime::date_trunc(&unit, &evaluated[1])
2027        }
2028        "DATE_BIN" => {
2029            check_args(name, &evaluated, 3)?;
2030            if evaluated.iter().any(|v| v.is_null()) {
2031                return Ok(Value::Null);
2032            }
2033            let stride = match &evaluated[0] {
2034                Value::Interval {
2035                    months: _,
2036                    days,
2037                    micros,
2038                } => *days as i64 * crate::datetime::MICROS_PER_DAY + *micros,
2039                _ => {
2040                    return Err(SqlError::TypeMismatch {
2041                        expected: "INTERVAL stride".into(),
2042                        got: evaluated[0].data_type().to_string(),
2043                    })
2044                }
2045            };
2046            if stride <= 0 {
2047                return Err(SqlError::InvalidValue(
2048                    "DATE_BIN stride must be positive".into(),
2049                ));
2050            }
2051            let (src, origin) = match (&evaluated[1], &evaluated[2]) {
2052                (Value::Timestamp(s), Value::Timestamp(o)) => (*s, *o),
2053                _ => {
2054                    return Err(SqlError::TypeMismatch {
2055                        expected: "TIMESTAMP, TIMESTAMP".into(),
2056                        got: format!("{}, {}", evaluated[1].data_type(), evaluated[2].data_type()),
2057                    })
2058                }
2059            };
2060            let diff = src - origin;
2061            let binned = origin + (diff.div_euclid(stride)) * stride;
2062            Ok(Value::Timestamp(binned))
2063        }
2064        "AGE" => {
2065            if evaluated.len() == 1 {
2066                if evaluated[0].is_null() {
2067                    return Ok(Value::Null);
2068                }
2069                let ts = match &evaluated[0] {
2070                    Value::Timestamp(t) => *t,
2071                    Value::Date(d) => crate::datetime::date_to_ts(*d),
2072                    _ => {
2073                        return Err(SqlError::TypeMismatch {
2074                            expected: "TIMESTAMP or DATE".into(),
2075                            got: evaluated[0].data_type().to_string(),
2076                        })
2077                    }
2078                };
2079                // Implicit reference: today at midnight UTC.
2080                let today = crate::datetime::today_days();
2081                let midnight = crate::datetime::date_to_ts(today);
2082                let (m, d, u) = crate::datetime::age(midnight, ts)?;
2083                return Ok(Value::Interval {
2084                    months: m,
2085                    days: d,
2086                    micros: u,
2087                });
2088            }
2089            check_args(name, &evaluated, 2)?;
2090            if evaluated.iter().any(|v| v.is_null()) {
2091                return Ok(Value::Null);
2092            }
2093            let a = ts_of(&evaluated[0])?;
2094            let b = ts_of(&evaluated[1])?;
2095            let (m, d, u) = crate::datetime::age(a, b)?;
2096            Ok(Value::Interval {
2097                months: m,
2098                days: d,
2099                micros: u,
2100            })
2101        }
2102        "MAKE_DATE" => {
2103            check_args(name, &evaluated, 3)?;
2104            if evaluated.iter().any(|v| v.is_null()) {
2105                return Ok(Value::Null);
2106            }
2107            let y = int_arg(&evaluated[0], "MAKE_DATE year")? as i32;
2108            let m = int_arg(&evaluated[1], "MAKE_DATE month")? as u8;
2109            let d = int_arg(&evaluated[2], "MAKE_DATE day")? as u8;
2110            crate::datetime::ymd_to_days(y, m, d)
2111                .map(Value::Date)
2112                .ok_or_else(|| SqlError::InvalidDateLiteral(format!("make_date({y}, {m}, {d})")))
2113        }
2114        "MAKE_TIME" => {
2115            check_args(name, &evaluated, 3)?;
2116            if evaluated.iter().any(|v| v.is_null()) {
2117                return Ok(Value::Null);
2118            }
2119            let h = int_arg(&evaluated[0], "MAKE_TIME hour")? as u8;
2120            let mi = int_arg(&evaluated[1], "MAKE_TIME minute")? as u8;
2121            let (s, us) = real_sec_arg(&evaluated[2])?;
2122            crate::datetime::hmsn_to_micros(h, mi, s, us)
2123                .map(Value::Time)
2124                .ok_or_else(|| SqlError::InvalidTimeLiteral(format!("make_time({h}, {mi}, ...)")))
2125        }
2126        "MAKE_TIMESTAMP" => {
2127            check_args(name, &evaluated, 6)?;
2128            if evaluated.iter().any(|v| v.is_null()) {
2129                return Ok(Value::Null);
2130            }
2131            let y = int_arg(&evaluated[0], "MAKE_TIMESTAMP year")? as i32;
2132            let mo = int_arg(&evaluated[1], "MAKE_TIMESTAMP month")? as u8;
2133            let d = int_arg(&evaluated[2], "MAKE_TIMESTAMP day")? as u8;
2134            let h = int_arg(&evaluated[3], "MAKE_TIMESTAMP hour")? as u8;
2135            let mi = int_arg(&evaluated[4], "MAKE_TIMESTAMP min")? as u8;
2136            let (s, us) = real_sec_arg(&evaluated[5])?;
2137            let days = crate::datetime::ymd_to_days(y, mo, d).ok_or_else(|| {
2138                SqlError::InvalidTimestampLiteral(format!("make_timestamp year={y}"))
2139            })?;
2140            let tmicros = crate::datetime::hmsn_to_micros(h, mi, s, us)
2141                .ok_or_else(|| SqlError::InvalidTimestampLiteral("time out of range".into()))?;
2142            Ok(Value::Timestamp(crate::datetime::ts_combine(days, tmicros)))
2143        }
2144        "MAKE_INTERVAL" => {
2145            // Positional args: years, months, weeks, days, hours, mins, secs.
2146            if evaluated.len() > 7 {
2147                return Err(SqlError::InvalidValue(
2148                    "MAKE_INTERVAL accepts at most 7 arguments".into(),
2149                ));
2150            }
2151            let mut months: i64 = 0;
2152            let mut days: i64 = 0;
2153            let mut micros: i64 = 0;
2154            for (i, v) in evaluated.iter().enumerate() {
2155                if v.is_null() {
2156                    continue;
2157                }
2158                let n = match v {
2159                    Value::Integer(n) => *n,
2160                    Value::Real(r) => *r as i64,
2161                    _ => {
2162                        return Err(SqlError::TypeMismatch {
2163                            expected: "numeric".into(),
2164                            got: v.data_type().to_string(),
2165                        })
2166                    }
2167                };
2168                match i {
2169                    0 => months = months.saturating_add(n.saturating_mul(12)),
2170                    1 => months = months.saturating_add(n),
2171                    2 => days = days.saturating_add(n.saturating_mul(7)),
2172                    3 => days = days.saturating_add(n),
2173                    4 => {
2174                        micros = micros
2175                            .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_HOUR))
2176                    }
2177                    5 => {
2178                        micros =
2179                            micros.saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_MIN))
2180                    }
2181                    6 => {
2182                        // Seconds may be fractional — also check Real.
2183                        if let Value::Real(r) = v {
2184                            micros = micros.saturating_add(
2185                                (*r * crate::datetime::MICROS_PER_SEC as f64) as i64,
2186                            );
2187                        } else {
2188                            micros = micros
2189                                .saturating_add(n.saturating_mul(crate::datetime::MICROS_PER_SEC));
2190                        }
2191                    }
2192                    _ => unreachable!(),
2193                }
2194            }
2195            Ok(Value::Interval {
2196                months: months.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2197                days: days.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
2198                micros,
2199            })
2200        }
2201        "JUSTIFY_DAYS" => {
2202            check_args(name, &evaluated, 1)?;
2203            match &evaluated[0] {
2204                Value::Null => Ok(Value::Null),
2205                Value::Interval {
2206                    months,
2207                    days,
2208                    micros,
2209                } => {
2210                    let (m, d, u) = crate::datetime::justify_days(*months, *days, *micros);
2211                    Ok(Value::Interval {
2212                        months: m,
2213                        days: d,
2214                        micros: u,
2215                    })
2216                }
2217                other => Err(SqlError::TypeMismatch {
2218                    expected: "INTERVAL".into(),
2219                    got: other.data_type().to_string(),
2220                }),
2221            }
2222        }
2223        "JUSTIFY_HOURS" => {
2224            check_args(name, &evaluated, 1)?;
2225            match &evaluated[0] {
2226                Value::Null => Ok(Value::Null),
2227                Value::Interval {
2228                    months,
2229                    days,
2230                    micros,
2231                } => {
2232                    let (m, d, u) = crate::datetime::justify_hours(*months, *days, *micros);
2233                    Ok(Value::Interval {
2234                        months: m,
2235                        days: d,
2236                        micros: u,
2237                    })
2238                }
2239                other => Err(SqlError::TypeMismatch {
2240                    expected: "INTERVAL".into(),
2241                    got: other.data_type().to_string(),
2242                }),
2243            }
2244        }
2245        "JUSTIFY_INTERVAL" => {
2246            check_args(name, &evaluated, 1)?;
2247            match &evaluated[0] {
2248                Value::Null => Ok(Value::Null),
2249                Value::Interval {
2250                    months,
2251                    days,
2252                    micros,
2253                } => {
2254                    let (m, d, u) = crate::datetime::justify_interval(*months, *days, *micros);
2255                    Ok(Value::Interval {
2256                        months: m,
2257                        days: d,
2258                        micros: u,
2259                    })
2260                }
2261                other => Err(SqlError::TypeMismatch {
2262                    expected: "INTERVAL".into(),
2263                    got: other.data_type().to_string(),
2264                }),
2265            }
2266        }
2267        "ISFINITE" => {
2268            check_args(name, &evaluated, 1)?;
2269            if evaluated[0].is_null() {
2270                return Ok(Value::Null);
2271            }
2272            Ok(Value::Boolean(evaluated[0].is_finite_temporal()))
2273        }
2274        "DATE" => {
2275            if evaluated.is_empty() {
2276                return Err(SqlError::InvalidValue(
2277                    "DATE requires at least 1 argument".into(),
2278                ));
2279            }
2280            if evaluated[0].is_null() {
2281                return Ok(Value::Null);
2282            }
2283            let d = match &evaluated[0] {
2284                Value::Date(d) => *d,
2285                Value::Timestamp(t) => crate::datetime::ts_to_date_floor(*t),
2286                Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::today_days(),
2287                Value::Text(s) => crate::datetime::parse_date(s)?,
2288                Value::Integer(n) => {
2289                    crate::datetime::ts_to_date_floor(*n * crate::datetime::MICROS_PER_SEC)
2290                }
2291                other => {
2292                    return Err(SqlError::TypeMismatch {
2293                        expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2294                        got: other.data_type().to_string(),
2295                    })
2296                }
2297            };
2298            Ok(Value::Date(d))
2299        }
2300        "TIME" => {
2301            if evaluated.is_empty() {
2302                return Err(SqlError::InvalidValue(
2303                    "TIME requires at least 1 argument".into(),
2304                ));
2305            }
2306            if evaluated[0].is_null() {
2307                return Ok(Value::Null);
2308            }
2309            let t = match &evaluated[0] {
2310                Value::Time(t) => *t,
2311                Value::Timestamp(t) => crate::datetime::ts_split(*t).1,
2312                Value::Text(s) if s.eq_ignore_ascii_case("now") => {
2313                    crate::datetime::current_time_micros()
2314                }
2315                Value::Text(s) => crate::datetime::parse_time(s)?,
2316                other => {
2317                    return Err(SqlError::TypeMismatch {
2318                        expected: "TIMESTAMP, TIME, or TEXT".into(),
2319                        got: other.data_type().to_string(),
2320                    })
2321                }
2322            };
2323            Ok(Value::Time(t))
2324        }
2325        "DATETIME" => {
2326            if evaluated.is_empty() {
2327                return Err(SqlError::InvalidValue(
2328                    "DATETIME requires at least 1 argument".into(),
2329                ));
2330            }
2331            if evaluated[0].is_null() {
2332                return Ok(Value::Null);
2333            }
2334            let t = match &evaluated[0] {
2335                Value::Timestamp(t) => *t,
2336                Value::Date(d) => crate::datetime::date_to_ts(*d),
2337                Value::Text(s) if s.eq_ignore_ascii_case("now") => crate::datetime::now_micros(),
2338                Value::Text(s) => crate::datetime::parse_timestamp(s)?,
2339                Value::Integer(n) => n * crate::datetime::MICROS_PER_SEC,
2340                other => {
2341                    return Err(SqlError::TypeMismatch {
2342                        expected: "TIMESTAMP, DATE, TEXT, or INTEGER".into(),
2343                        got: other.data_type().to_string(),
2344                    })
2345                }
2346            };
2347            Ok(Value::Timestamp(t))
2348        }
2349        "STRFTIME" => {
2350            if evaluated.len() < 2 {
2351                return Err(SqlError::InvalidValue(
2352                    "STRFTIME requires format + value".into(),
2353                ));
2354            }
2355            if evaluated.iter().take(2).any(|v| v.is_null()) {
2356                return Ok(Value::Null);
2357            }
2358            let fmt = match &evaluated[0] {
2359                Value::Text(s) => s.to_string(),
2360                _ => {
2361                    return Err(SqlError::TypeMismatch {
2362                        expected: "TEXT format".into(),
2363                        got: evaluated[0].data_type().to_string(),
2364                    })
2365                }
2366            };
2367            let out = crate::datetime::strftime(&fmt, &evaluated[1])?;
2368            Ok(Value::Text(out.into()))
2369        }
2370        "JULIANDAY" => {
2371            if evaluated.is_empty() {
2372                return Err(SqlError::InvalidValue(
2373                    "JULIANDAY requires at least 1 argument".into(),
2374                ));
2375            }
2376            if evaluated[0].is_null() {
2377                return Ok(Value::Null);
2378            }
2379            let micros = ts_of(&evaluated[0])?;
2380            let (days, tmicros) = crate::datetime::ts_split(micros);
2381            // Julian Day 2440587.5 = 1970-01-01 00:00:00 UTC (Julian days start at noon).
2382            let julian =
2383                days as f64 + 2_440_587.5 + tmicros as f64 / crate::datetime::MICROS_PER_DAY as f64;
2384            Ok(Value::Real(julian))
2385        }
2386        "UNIXEPOCH" => {
2387            if evaluated.is_empty() {
2388                return Err(SqlError::InvalidValue(
2389                    "UNIXEPOCH requires at least 1 argument".into(),
2390                ));
2391            }
2392            if evaluated[0].is_null() {
2393                return Ok(Value::Null);
2394            }
2395            let micros = ts_of(&evaluated[0])?;
2396            let subsec = evaluated
2397                .get(1)
2398                .and_then(|v| {
2399                    if let Value::Text(s) = v {
2400                        Some(s.to_string())
2401                    } else {
2402                        None
2403                    }
2404                })
2405                .map(|s| s.eq_ignore_ascii_case("subsec") || s.eq_ignore_ascii_case("subsecond"))
2406                .unwrap_or(false);
2407            if subsec {
2408                Ok(Value::Real(
2409                    micros as f64 / crate::datetime::MICROS_PER_SEC as f64,
2410                ))
2411            } else {
2412                Ok(Value::Integer(micros / crate::datetime::MICROS_PER_SEC))
2413            }
2414        }
2415        "TIMEDIFF" => {
2416            check_args(name, &evaluated, 2)?;
2417            if evaluated.iter().any(|v| v.is_null()) {
2418                return Ok(Value::Null);
2419            }
2420            let a = ts_of(&evaluated[0])?;
2421            let b = ts_of(&evaluated[1])?;
2422            let (days, micros) = crate::datetime::subtract_timestamps(a, b);
2423            let sign = if days < 0 || (days == 0 && micros < 0) {
2424                "-"
2425            } else {
2426                "+"
2427            };
2428            let abs_days = days.unsigned_abs() as i64;
2429            let abs_us = micros.unsigned_abs() as i64;
2430            // PG-compat format string: "(+|-)YYYY-MM-DD HH:MM:SS.SSS", days-only.
2431            let (h, m, s, us) = crate::datetime::micros_to_hmsn(abs_us);
2432            Ok(Value::Text(
2433                format!("{sign}{abs_days:04}-00-00 {h:02}:{m:02}:{s:02}.{us:06}").into(),
2434            ))
2435        }
2436        "AT_TIMEZONE" => {
2437            check_args(name, &evaluated, 2)?;
2438            if evaluated.iter().any(|v| v.is_null()) {
2439                return Ok(Value::Null);
2440            }
2441            let ts = match &evaluated[0] {
2442                Value::Timestamp(t) => *t,
2443                Value::Date(d) => crate::datetime::date_to_ts(*d),
2444                other => {
2445                    return Err(SqlError::TypeMismatch {
2446                        expected: "TIMESTAMP or DATE".into(),
2447                        got: other.data_type().to_string(),
2448                    })
2449                }
2450            };
2451            let zone = match &evaluated[1] {
2452                Value::Text(s) => s.to_string(),
2453                _ => {
2454                    return Err(SqlError::TypeMismatch {
2455                        expected: "TEXT time zone".into(),
2456                        got: evaluated[1].data_type().to_string(),
2457                    })
2458                }
2459            };
2460            // Reject POSIX-style 'UTC+5' (ambiguous sign convention).
2461            let upper = zone.to_ascii_uppercase();
2462            if (upper.starts_with("UTC+") || upper.starts_with("UTC-")) && zone.len() > 3 {
2463                return Err(SqlError::InvalidTimezone(format!(
2464                    "'{zone}' is ambiguous — use ISO-8601 offset like '+05:00' or named zone like 'Etc/GMT-5'"
2465                )));
2466            }
2467            let formatted = crate::datetime::format_timestamp_in_zone(ts, &zone)?;
2468            Ok(Value::Text(formatted.into()))
2469        }
2470        "JSONB_TYPEOF" | "JSON_TYPEOF" => {
2471            check_args(name, &evaluated, 1)?;
2472            if evaluated[0].is_null() {
2473                return Ok(Value::Null);
2474            }
2475            crate::json::fn_typeof(&evaluated[0])
2476        }
2477        "JSONB_ARRAY_LENGTH" | "JSON_ARRAY_LENGTH" => {
2478            check_args(name, &evaluated, 1)?;
2479            if evaluated[0].is_null() {
2480                return Ok(Value::Null);
2481            }
2482            crate::json::fn_array_length(&evaluated[0])
2483        }
2484        "JSONB_OBJECT_LENGTH" | "JSON_OBJECT_LENGTH" => {
2485            check_args(name, &evaluated, 1)?;
2486            if evaluated[0].is_null() {
2487                return Ok(Value::Null);
2488            }
2489            crate::json::fn_object_length(&evaluated[0])
2490        }
2491        "JSONB_EXTRACT_PATH" | "JSON_EXTRACT_PATH" => {
2492            if evaluated.is_empty() {
2493                return Err(SqlError::InvalidValue(format!(
2494                    "{name} requires at least 1 argument"
2495                )));
2496            }
2497            if evaluated[0].is_null() {
2498                return Ok(Value::Null);
2499            }
2500            let target = if name.eq_ignore_ascii_case("JSONB_EXTRACT_PATH") {
2501                crate::types::DataType::Jsonb
2502            } else {
2503                crate::types::DataType::Json
2504            };
2505            crate::json::fn_extract_path(&evaluated, target, false)
2506        }
2507        "JSONB_EXTRACT_PATH_TEXT" | "JSON_EXTRACT_PATH_TEXT" => {
2508            if evaluated.is_empty() {
2509                return Err(SqlError::InvalidValue(format!(
2510                    "{name} requires at least 1 argument"
2511                )));
2512            }
2513            if evaluated[0].is_null() {
2514                return Ok(Value::Null);
2515            }
2516            crate::json::fn_extract_path(&evaluated, crate::types::DataType::Text, true)
2517        }
2518        "JSON_EXTRACT" => {
2519            check_args(name, &evaluated, 2)?;
2520            if evaluated[0].is_null() || evaluated[1].is_null() {
2521                return Ok(Value::Null);
2522            }
2523            crate::json::fn_sqlite_extract(&evaluated[0], &evaluated[1])
2524        }
2525        "JSON_VALID" => {
2526            check_args(name, &evaluated, 1)?;
2527            if evaluated[0].is_null() {
2528                return Ok(Value::Null);
2529            }
2530            crate::json::fn_valid(&evaluated[0])
2531        }
2532        "JSONB_STRIP_NULLS" | "JSON_STRIP_NULLS" => {
2533            check_args(name, &evaluated, 1)?;
2534            if evaluated[0].is_null() {
2535                return Ok(Value::Null);
2536            }
2537            let target = if name.eq_ignore_ascii_case("JSONB_STRIP_NULLS") {
2538                crate::types::DataType::Jsonb
2539            } else {
2540                crate::types::DataType::Json
2541            };
2542            crate::json::fn_strip_nulls(&evaluated[0], target)
2543        }
2544        "JSONB_PRETTY" | "JSON_PRETTY" => {
2545            check_args(name, &evaluated, 1)?;
2546            if evaluated[0].is_null() {
2547                return Ok(Value::Null);
2548            }
2549            crate::json::fn_pretty(&evaluated[0])
2550        }
2551        "JSONB_BUILD_OBJECT" | "JSON_BUILD_OBJECT" => {
2552            let target = if name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT") {
2553                crate::types::DataType::Jsonb
2554            } else {
2555                crate::types::DataType::Json
2556            };
2557            crate::json::fn_build_object(&evaluated, target)
2558        }
2559        "JSONB_BUILD_ARRAY" | "JSON_BUILD_ARRAY" => {
2560            let target = if name.eq_ignore_ascii_case("JSONB_BUILD_ARRAY") {
2561                crate::types::DataType::Jsonb
2562            } else {
2563                crate::types::DataType::Json
2564            };
2565            crate::json::fn_build_array(&evaluated, target)
2566        }
2567        "JSONB_SET" | "JSON_SET" => {
2568            if !(3..=4).contains(&evaluated.len()) {
2569                return Err(SqlError::InvalidValue(format!(
2570                    "{name} requires 3 or 4 arguments"
2571                )));
2572            }
2573            if evaluated[0].is_null() {
2574                return Ok(Value::Null);
2575            }
2576            let target = if name.eq_ignore_ascii_case("JSONB_SET") {
2577                crate::types::DataType::Jsonb
2578            } else {
2579                crate::types::DataType::Json
2580            };
2581            let create_missing = evaluated
2582                .get(3)
2583                .map(|v| matches!(v, Value::Boolean(true)))
2584                .unwrap_or(true);
2585            crate::json::fn_set(
2586                &evaluated[0],
2587                &evaluated[1],
2588                &evaluated[2],
2589                create_missing,
2590                target,
2591            )
2592        }
2593        "JSONB_INSERT" | "JSON_INSERT" => {
2594            if !(3..=4).contains(&evaluated.len()) {
2595                return Err(SqlError::InvalidValue(format!(
2596                    "{name} requires 3 or 4 arguments"
2597                )));
2598            }
2599            if evaluated[0].is_null() {
2600                return Ok(Value::Null);
2601            }
2602            let target = if name.eq_ignore_ascii_case("JSONB_INSERT") {
2603                crate::types::DataType::Jsonb
2604            } else {
2605                crate::types::DataType::Json
2606            };
2607            let insert_after = evaluated
2608                .get(3)
2609                .map(|v| matches!(v, Value::Boolean(true)))
2610                .unwrap_or(false);
2611            crate::json::fn_insert(
2612                &evaluated[0],
2613                &evaluated[1],
2614                &evaluated[2],
2615                insert_after,
2616                target,
2617            )
2618        }
2619        "TO_JSONB" | "TO_JSON" => {
2620            check_args(name, &evaluated, 1)?;
2621            let target = if name.eq_ignore_ascii_case("TO_JSONB") {
2622                crate::types::DataType::Jsonb
2623            } else {
2624                crate::types::DataType::Json
2625            };
2626            crate::json::fn_to_json(&evaluated[0], target)
2627        }
2628        "ROW_TO_JSON" | "ROW_TO_JSONB" => {
2629            check_args(name, &evaluated, 1)?;
2630            let target = if name.eq_ignore_ascii_case("ROW_TO_JSONB") {
2631                crate::types::DataType::Jsonb
2632            } else {
2633                crate::types::DataType::Json
2634            };
2635            crate::json::fn_to_json(&evaluated[0], target)
2636        }
2637        "JSON_OBJECT" => crate::json::fn_json_object(&evaluated),
2638        "JSON_EXISTS" => {
2639            check_args(name, &evaluated, 2)?;
2640            if evaluated[0].is_null() || evaluated[1].is_null() {
2641                return Ok(Value::Null);
2642            }
2643            crate::json::fn_json_exists(&evaluated[0], &evaluated[1])
2644        }
2645        "JSON_VALUE" => {
2646            check_args(name, &evaluated, 2)?;
2647            if evaluated[0].is_null() || evaluated[1].is_null() {
2648                return Ok(Value::Null);
2649            }
2650            crate::json::fn_json_value(&evaluated[0], &evaluated[1])
2651        }
2652        "JSON_QUERY" => {
2653            check_args(name, &evaluated, 2)?;
2654            if evaluated[0].is_null() || evaluated[1].is_null() {
2655                return Ok(Value::Null);
2656            }
2657            crate::json::fn_json_query(&evaluated[0], &evaluated[1], crate::types::DataType::Jsonb)
2658        }
2659        "JSONB_PATH_EXISTS" => {
2660            if evaluated[0].is_null() || evaluated[1].is_null() {
2661                return Ok(Value::Null);
2662            }
2663            crate::json::fn_jsonb_path_exists(&evaluated)
2664        }
2665        "JSONB_PATH_MATCH" => {
2666            if evaluated[0].is_null() || evaluated[1].is_null() {
2667                return Ok(Value::Null);
2668            }
2669            crate::json::fn_jsonb_path_match(&evaluated)
2670        }
2671        "JSONB_PATH_QUERY_FIRST" => {
2672            if evaluated[0].is_null() || evaluated[1].is_null() {
2673                return Ok(Value::Null);
2674            }
2675            crate::json::fn_jsonb_path_query_first(&evaluated)
2676        }
2677        "JSONB_PATH_QUERY_ARRAY" => {
2678            if evaluated[0].is_null() || evaluated[1].is_null() {
2679                return Ok(Value::Null);
2680            }
2681            crate::json::fn_jsonb_path_query_array(&evaluated)
2682        }
2683        "JSONB_PATH_EXISTS_TZ" => {
2684            if evaluated[0].is_null() || evaluated[1].is_null() {
2685                return Ok(Value::Null);
2686            }
2687            crate::json::fn_jsonb_path_exists_tz(&evaluated)
2688        }
2689        "JSONB_PATH_MATCH_TZ" => {
2690            if evaluated[0].is_null() || evaluated[1].is_null() {
2691                return Ok(Value::Null);
2692            }
2693            crate::json::fn_jsonb_path_match_tz(&evaluated)
2694        }
2695        "JSONB_PATH_QUERY_TZ" => {
2696            if evaluated[0].is_null() || evaluated[1].is_null() {
2697                return Ok(Value::Null);
2698            }
2699            crate::json::fn_jsonb_path_query_tz(&evaluated)
2700        }
2701        "JSONB_PATH_QUERY_FIRST_TZ" => {
2702            if evaluated[0].is_null() || evaluated[1].is_null() {
2703                return Ok(Value::Null);
2704            }
2705            crate::json::fn_jsonb_path_query_first_tz(&evaluated)
2706        }
2707        "JSONB_PATH_QUERY_ARRAY_TZ" => {
2708            if evaluated[0].is_null() || evaluated[1].is_null() {
2709                return Ok(Value::Null);
2710            }
2711            crate::json::fn_jsonb_path_query_array_tz(&evaluated)
2712        }
2713        "JSONB_HAS_KEY" | "JSON_HAS_KEY" => {
2714            check_args(name, &evaluated, 2)?;
2715            if evaluated[0].is_null() || evaluated[1].is_null() {
2716                return Ok(Value::Null);
2717            }
2718            crate::json::op_has_key(&evaluated[0], &evaluated[1])
2719        }
2720        "JSONB_HAS_ANY_KEY" | "JSON_HAS_ANY_KEY" => {
2721            check_args(name, &evaluated, 2)?;
2722            if evaluated[0].is_null() || evaluated[1].is_null() {
2723                return Ok(Value::Null);
2724            }
2725            crate::json::op_has_any_key(&evaluated[0], &evaluated[1])
2726        }
2727        "JSONB_HAS_ALL_KEYS" | "JSON_HAS_ALL_KEYS" => {
2728            check_args(name, &evaluated, 2)?;
2729            if evaluated[0].is_null() || evaluated[1].is_null() {
2730                return Ok(Value::Null);
2731            }
2732            crate::json::op_has_all_keys(&evaluated[0], &evaluated[1])
2733        }
2734        "TO_TSVECTOR" => fts_to_tsvector(&evaluated),
2735        "TO_TSQUERY" => fts_to_tsquery(&evaluated),
2736        "PLAINTO_TSQUERY" => fts_plainto_tsquery(&evaluated),
2737        "PHRASETO_TSQUERY" => fts_phraseto_tsquery(&evaluated),
2738        "WEBSEARCH_TO_TSQUERY" => fts_websearch_to_tsquery(&evaluated),
2739        "TS_RANK" => fts_ts_rank(&evaluated, false),
2740        "TS_RANK_CD" => fts_ts_rank(&evaluated, true),
2741        "TS_HEADLINE" => fts_ts_headline(&evaluated),
2742        "TS_LEXIZE" => fts_ts_lexize(&evaluated),
2743        "NUMNODE" => fts_numnode(&evaluated),
2744        "SETWEIGHT" => fts_setweight(&evaluated),
2745        "STRIP" => fts_strip(&evaluated),
2746        _ => Err(SqlError::Unsupported(format!("scalar function: {name}"))),
2747    }
2748}
2749
2750fn fts_resolve_config_and_text(
2751    args: &[Value],
2752    fname: &str,
2753) -> Result<(crate::fts::TokenizerKind, String)> {
2754    if args.is_empty() || args.len() > 2 {
2755        return Err(SqlError::InvalidValue(format!(
2756            "{fname} requires 1 or 2 arguments"
2757        )));
2758    }
2759    let (config_name, text) = if args.len() == 2 {
2760        let cfg = match &args[0] {
2761            Value::Text(s) => Some(s.as_str().to_string()),
2762            v => {
2763                return Err(SqlError::TypeMismatch {
2764                    expected: "TEXT (config)".into(),
2765                    got: v.data_type().to_string(),
2766                })
2767            }
2768        };
2769        let txt = match &args[1] {
2770            Value::Text(s) => s.as_str().to_string(),
2771            v => {
2772                return Err(SqlError::TypeMismatch {
2773                    expected: "TEXT".into(),
2774                    got: v.data_type().to_string(),
2775                })
2776            }
2777        };
2778        (cfg, txt)
2779    } else {
2780        let txt = match &args[0] {
2781            Value::Text(s) => s.as_str().to_string(),
2782            v => {
2783                return Err(SqlError::TypeMismatch {
2784                    expected: "TEXT".into(),
2785                    got: v.data_type().to_string(),
2786                })
2787            }
2788        };
2789        (None, txt)
2790    };
2791    let kind = match config_name {
2792        Some(name) => crate::fts::TokenizerKind::from_name(&name)?,
2793        None => crate::fts::TokenizerKind::English,
2794    };
2795    Ok((kind, text))
2796}
2797
2798fn fts_to_tsvector(args: &[Value]) -> Result<Value> {
2799    if args.iter().any(|v| v.is_null()) {
2800        return Ok(Value::Null);
2801    }
2802    let (kind, text) = fts_resolve_config_and_text(args, "to_tsvector")?;
2803    crate::fts::fn_to_tsvector_with(kind, &text)
2804}
2805
2806fn fts_to_tsquery(args: &[Value]) -> Result<Value> {
2807    if args.iter().any(|v| v.is_null()) {
2808        return Ok(Value::Null);
2809    }
2810    let (kind, text) = fts_resolve_config_and_text(args, "to_tsquery")?;
2811    crate::fts::fn_to_tsquery_with(kind, &text)
2812}
2813
2814fn fts_plainto_tsquery(args: &[Value]) -> Result<Value> {
2815    if args.iter().any(|v| v.is_null()) {
2816        return Ok(Value::Null);
2817    }
2818    let (kind, text) = fts_resolve_config_and_text(args, "plainto_tsquery")?;
2819    crate::fts::fn_plainto_tsquery_with(kind, &text)
2820}
2821
2822fn fts_phraseto_tsquery(args: &[Value]) -> Result<Value> {
2823    if args.iter().any(|v| v.is_null()) {
2824        return Ok(Value::Null);
2825    }
2826    let (kind, text) = fts_resolve_config_and_text(args, "phraseto_tsquery")?;
2827    crate::fts::fn_phraseto_tsquery_with(kind, &text)
2828}
2829
2830fn fts_websearch_to_tsquery(args: &[Value]) -> Result<Value> {
2831    if args.iter().any(|v| v.is_null()) {
2832        return Ok(Value::Null);
2833    }
2834    let (kind, text) = fts_resolve_config_and_text(args, "websearch_to_tsquery")?;
2835    crate::fts::fn_websearch_to_tsquery_with(kind, &text)
2836}
2837
2838fn fts_ts_rank(args: &[Value], cover_density: bool) -> Result<Value> {
2839    let fname = if cover_density {
2840        "ts_rank_cd"
2841    } else {
2842        "ts_rank"
2843    };
2844    if args.len() != 2 && args.len() != 3 {
2845        return Err(SqlError::InvalidValue(format!(
2846            "{fname} requires 2 or 3 arguments"
2847        )));
2848    }
2849    if args[0].is_null() || args[1].is_null() {
2850        return Ok(Value::Null);
2851    }
2852    let tsv = match &args[0] {
2853        Value::TsVector(b) => b,
2854        v => {
2855            return Err(SqlError::TypeMismatch {
2856                expected: "TSVECTOR".into(),
2857                got: v.data_type().to_string(),
2858            })
2859        }
2860    };
2861    let tsq = match &args[1] {
2862        Value::TsQuery(b) => b,
2863        v => {
2864            return Err(SqlError::TypeMismatch {
2865                expected: "TSQUERY".into(),
2866                got: v.data_type().to_string(),
2867            })
2868        }
2869    };
2870    let norm = if args.len() == 3 {
2871        match &args[2] {
2872            Value::Integer(n) => *n,
2873            Value::Null => return Ok(Value::Null),
2874            v => {
2875                return Err(SqlError::TypeMismatch {
2876                    expected: "INTEGER (norm)".into(),
2877                    got: v.data_type().to_string(),
2878                })
2879            }
2880        }
2881    } else {
2882        0
2883    };
2884    if cover_density {
2885        crate::fts::fn_ts_rank_cd(tsv, tsq, norm)
2886    } else {
2887        crate::fts::fn_ts_rank(tsv, tsq, norm)
2888    }
2889}
2890
2891fn fts_ts_headline(args: &[Value]) -> Result<Value> {
2892    if args.len() < 2 || args.len() > 4 {
2893        return Err(SqlError::InvalidValue(
2894            "ts_headline requires 2 to 4 arguments".into(),
2895        ));
2896    }
2897    if args.iter().any(|v| v.is_null()) {
2898        return Ok(Value::Null);
2899    }
2900    let kind = if args.len() >= 3 {
2901        match &args[0] {
2902            Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2903            v => {
2904                return Err(SqlError::TypeMismatch {
2905                    expected: "TEXT (config)".into(),
2906                    got: v.data_type().to_string(),
2907                })
2908            }
2909        }
2910    } else {
2911        crate::fts::TokenizerKind::English
2912    };
2913    let text_idx = if args.len() >= 3 { 1 } else { 0 };
2914    let tsq_idx = if args.len() >= 3 { 2 } else { 1 };
2915    let text = match args.get(text_idx) {
2916        Some(Value::Text(s)) => s.as_str(),
2917        _ => {
2918            return Err(SqlError::TypeMismatch {
2919                expected: "TEXT".into(),
2920                got: "non-text".into(),
2921            })
2922        }
2923    };
2924    let tsq = match args.get(tsq_idx) {
2925        Some(Value::TsQuery(b)) => b.as_ref(),
2926        _ => {
2927            return Err(SqlError::TypeMismatch {
2928                expected: "TSQUERY".into(),
2929                got: "non-tsquery".into(),
2930            })
2931        }
2932    };
2933    crate::fts::fn_ts_headline_with(kind, text, tsq)
2934}
2935
2936fn fts_ts_lexize(args: &[Value]) -> Result<Value> {
2937    if args.len() != 2 {
2938        return Err(SqlError::InvalidValue(
2939            "ts_lexize requires 2 arguments (config, word)".into(),
2940        ));
2941    }
2942    if args.iter().any(|v| v.is_null()) {
2943        return Ok(Value::Null);
2944    }
2945    let kind = match &args[0] {
2946        Value::Text(s) => crate::fts::TokenizerKind::from_name(s.as_str())?,
2947        v => {
2948            return Err(SqlError::TypeMismatch {
2949                expected: "TEXT (config)".into(),
2950                got: v.data_type().to_string(),
2951            })
2952        }
2953    };
2954    let word = match &args[1] {
2955        Value::Text(s) => s.as_str(),
2956        v => {
2957            return Err(SqlError::TypeMismatch {
2958                expected: "TEXT (word)".into(),
2959                got: v.data_type().to_string(),
2960            })
2961        }
2962    };
2963    crate::fts::fn_ts_lexize_with(kind, word)
2964}
2965
2966fn fts_numnode(args: &[Value]) -> Result<Value> {
2967    check_args("numnode", args, 1)?;
2968    if args[0].is_null() {
2969        return Ok(Value::Null);
2970    }
2971    let tsq = match &args[0] {
2972        Value::TsQuery(b) => b,
2973        v => {
2974            return Err(SqlError::TypeMismatch {
2975                expected: "TSQUERY".into(),
2976                got: v.data_type().to_string(),
2977            })
2978        }
2979    };
2980    crate::fts::fn_numnode(tsq)
2981}
2982
2983fn fts_setweight(args: &[Value]) -> Result<Value> {
2984    if args.len() == 3 {
2985        return fts_setweight_selective(args);
2986    }
2987    check_args("setweight", args, 2)?;
2988    if args[0].is_null() || args[1].is_null() {
2989        return Ok(Value::Null);
2990    }
2991    let tsv = match &args[0] {
2992        Value::TsVector(b) => b,
2993        v => {
2994            return Err(SqlError::TypeMismatch {
2995                expected: "TSVECTOR".into(),
2996                got: v.data_type().to_string(),
2997            })
2998        }
2999    };
3000    let weight_text = match &args[1] {
3001        Value::Text(s) => s.as_str(),
3002        v => {
3003            return Err(SqlError::TypeMismatch {
3004                expected: "TEXT".into(),
3005                got: v.data_type().to_string(),
3006            })
3007        }
3008    };
3009    let weight = crate::fts::parse_weight_char(weight_text)?;
3010    crate::fts::fn_setweight(tsv, weight)
3011}
3012
3013fn fts_setweight_selective(args: &[Value]) -> Result<Value> {
3014    check_args("setweight", args, 3)?;
3015    if args[0].is_null() || args[1].is_null() || args[2].is_null() {
3016        return Ok(Value::Null);
3017    }
3018    let tsv = match &args[0] {
3019        Value::TsVector(b) => b,
3020        v => {
3021            return Err(SqlError::TypeMismatch {
3022                expected: "TSVECTOR".into(),
3023                got: v.data_type().to_string(),
3024            })
3025        }
3026    };
3027    let weight_text = match &args[1] {
3028        Value::Text(s) => s.as_str(),
3029        v => {
3030            return Err(SqlError::TypeMismatch {
3031                expected: "TEXT".into(),
3032                got: v.data_type().to_string(),
3033            })
3034        }
3035    };
3036    let weight = crate::fts::parse_weight_char(weight_text)?;
3037    let filter = match &args[2] {
3038        Value::Array(a) => a.as_ref().as_slice(),
3039        v => {
3040            return Err(SqlError::TypeMismatch {
3041                expected: "TEXT[]".into(),
3042                got: v.data_type().to_string(),
3043            })
3044        }
3045    };
3046    crate::fts::fn_setweight_selective(tsv, weight, filter)
3047}
3048
3049fn fts_strip(args: &[Value]) -> Result<Value> {
3050    check_args("strip", args, 1)?;
3051    if args[0].is_null() {
3052        return Ok(Value::Null);
3053    }
3054    let tsv = match &args[0] {
3055        Value::TsVector(b) => b,
3056        v => {
3057            return Err(SqlError::TypeMismatch {
3058                expected: "TSVECTOR".into(),
3059                got: v.data_type().to_string(),
3060            })
3061        }
3062    };
3063    crate::fts::fn_strip(tsv)
3064}
3065
3066/// Extract a timestamp (µs UTC) from a Value, coercing DATE → midnight.
3067fn ts_of(v: &Value) -> Result<i64> {
3068    match v {
3069        Value::Timestamp(t) => Ok(*t),
3070        Value::Date(d) => Ok(crate::datetime::date_to_ts(*d)),
3071        _ => Err(SqlError::TypeMismatch {
3072            expected: "TIMESTAMP or DATE".into(),
3073            got: v.data_type().to_string(),
3074        }),
3075    }
3076}
3077
3078fn int_arg(v: &Value, label: &str) -> Result<i64> {
3079    match v {
3080        Value::Integer(n) => Ok(*n),
3081        _ => Err(SqlError::TypeMismatch {
3082            expected: format!("INTEGER ({label})"),
3083            got: v.data_type().to_string(),
3084        }),
3085    }
3086}
3087
3088/// Extract (whole_seconds: u8, frac_micros: u32) from a numeric argument for MAKE_TIME-style calls.
3089fn real_sec_arg(v: &Value) -> Result<(u8, u32)> {
3090    match v {
3091        Value::Integer(n) => {
3092            if !(0..=60).contains(n) {
3093                return Err(SqlError::InvalidValue(format!("second out of range: {n}")));
3094            }
3095            Ok((*n as u8, 0))
3096        }
3097        Value::Real(r) => {
3098            let whole = r.trunc() as i64;
3099            if !(0..=60).contains(&whole) {
3100                return Err(SqlError::InvalidValue(format!("second out of range: {r}")));
3101            }
3102            let frac = ((r - whole as f64) * 1_000_000.0).round() as i64;
3103            Ok((whole as u8, frac.max(0) as u32))
3104        }
3105        _ => Err(SqlError::TypeMismatch {
3106            expected: "numeric seconds".into(),
3107            got: v.data_type().to_string(),
3108        }),
3109    }
3110}
3111
3112/// DATE_TRUNC with a non-UTC IANA zone: convert → truncate in that zone → convert back to UTC.
3113fn date_trunc_in_zone(unit: &str, ts_utc: i64, tz: &str) -> Result<Value> {
3114    use jiff::{tz::TimeZone, Timestamp as JTimestamp};
3115    let zone = TimeZone::get(tz).map_err(|e| SqlError::InvalidTimezone(format!("{tz}: {e}")))?;
3116    let ts = JTimestamp::from_microsecond(ts_utc)
3117        .map_err(|e| SqlError::InvalidValue(format!("ts: {e}")))?;
3118    let zoned = ts.to_zoned(zone.clone());
3119    let unit_lower = unit.to_ascii_lowercase();
3120    let rounded = match unit_lower.as_str() {
3121        "microseconds" => return Ok(Value::Timestamp(ts_utc)),
3122        "second" => zoned
3123            .start_of_day()
3124            .map_err(|e| SqlError::InvalidValue(format!("{e}")))?,
3125        _ => {
3126            let naive_ts = zoned.timestamp().as_microsecond();
3127            return crate::datetime::date_trunc(unit, &Value::Timestamp(naive_ts));
3128        }
3129    };
3130    Ok(Value::Timestamp(rounded.timestamp().as_microsecond()))
3131}
3132
3133fn check_args(name: &str, args: &[Value], expected: usize) -> Result<()> {
3134    if args.len() != expected {
3135        Err(SqlError::InvalidValue(format!(
3136            "{name} requires {expected} argument(s), got {}",
3137            args.len()
3138        )))
3139    } else {
3140        Ok(())
3141    }
3142}
3143
3144pub fn referenced_columns(expr: &Expr, columns: &[ColumnDef]) -> Vec<usize> {
3145    let mut indices = Vec::new();
3146    collect_column_refs(expr, columns, &mut indices);
3147    indices.sort_unstable();
3148    indices.dedup();
3149    indices
3150}
3151
3152fn collect_column_refs(expr: &Expr, columns: &[ColumnDef], out: &mut Vec<usize>) {
3153    match expr {
3154        Expr::Column(name) => {
3155            for (i, c) in columns.iter().enumerate() {
3156                if c.name == *name
3157                    || (c.name.len() > name.len()
3158                        && c.name.as_bytes()[c.name.len() - name.len() - 1] == b'.'
3159                        && c.name.ends_with(name.as_str()))
3160                {
3161                    out.push(i);
3162                    break;
3163                }
3164            }
3165        }
3166        Expr::QualifiedColumn { table, column } => {
3167            let mut found: Option<usize> = None;
3168            let mut bare_match: Option<usize> = None;
3169            let mut bare_count = 0usize;
3170            for (i, c) in columns.iter().enumerate() {
3171                if c.name.len() == table.len() + 1 + column.len()
3172                    && c.name.as_bytes()[table.len()] == b'.'
3173                    && c.name.starts_with(table.as_str())
3174                    && c.name.ends_with(column.as_str())
3175                {
3176                    found = Some(i);
3177                    break;
3178                }
3179                if c.name == *column {
3180                    bare_match = Some(i);
3181                    bare_count += 1;
3182                }
3183            }
3184            if let Some(idx) = found {
3185                out.push(idx);
3186            } else if bare_count == 1 {
3187                out.push(bare_match.unwrap());
3188            }
3189        }
3190        Expr::BinaryOp { left, right, .. } => {
3191            collect_column_refs(left, columns, out);
3192            collect_column_refs(right, columns, out);
3193        }
3194        Expr::UnaryOp { expr, .. } => {
3195            collect_column_refs(expr, columns, out);
3196        }
3197        Expr::IsNull(e) | Expr::IsNotNull(e) => {
3198            collect_column_refs(e, columns, out);
3199        }
3200        Expr::Function { args, .. } => {
3201            for arg in args {
3202                collect_column_refs(arg, columns, out);
3203            }
3204        }
3205        Expr::InSubquery { expr, .. } => {
3206            collect_column_refs(expr, columns, out);
3207        }
3208        Expr::InList { expr, list, .. } => {
3209            collect_column_refs(expr, columns, out);
3210            for item in list {
3211                collect_column_refs(item, columns, out);
3212            }
3213        }
3214        Expr::InSet { expr, .. } => {
3215            collect_column_refs(expr, columns, out);
3216        }
3217        Expr::Between {
3218            expr, low, high, ..
3219        } => {
3220            collect_column_refs(expr, columns, out);
3221            collect_column_refs(low, columns, out);
3222            collect_column_refs(high, columns, out);
3223        }
3224        Expr::Like {
3225            expr,
3226            pattern,
3227            escape,
3228            ..
3229        } => {
3230            collect_column_refs(expr, columns, out);
3231            collect_column_refs(pattern, columns, out);
3232            if let Some(esc) = escape {
3233                collect_column_refs(esc, columns, out);
3234            }
3235        }
3236        Expr::Case {
3237            operand,
3238            conditions,
3239            else_result,
3240        } => {
3241            if let Some(op) = operand {
3242                collect_column_refs(op, columns, out);
3243            }
3244            for (when, then) in conditions {
3245                collect_column_refs(when, columns, out);
3246                collect_column_refs(then, columns, out);
3247            }
3248            if let Some(e) = else_result {
3249                collect_column_refs(e, columns, out);
3250            }
3251        }
3252        Expr::Coalesce(args) => {
3253            for arg in args {
3254                collect_column_refs(arg, columns, out);
3255            }
3256        }
3257        Expr::Cast { expr, .. } => {
3258            collect_column_refs(expr, columns, out);
3259        }
3260        Expr::Collate { expr, .. } => {
3261            collect_column_refs(expr, columns, out);
3262        }
3263        Expr::WindowFunction { args, spec, .. } => {
3264            for arg in args {
3265                collect_column_refs(arg, columns, out);
3266            }
3267            for pb in &spec.partition_by {
3268                collect_column_refs(pb, columns, out);
3269            }
3270            for ob in &spec.order_by {
3271                collect_column_refs(&ob.expr, columns, out);
3272            }
3273        }
3274        Expr::ArrayLiteral(elems) => {
3275            for e in elems {
3276                collect_column_refs(e, columns, out);
3277            }
3278        }
3279        Expr::Quantified { left, right, .. } => {
3280            collect_column_refs(left, columns, out);
3281            if let crate::parser::QuantifiedRhs::Array(e) = right {
3282                collect_column_refs(e, columns, out);
3283            }
3284        }
3285        Expr::Literal(_)
3286        | Expr::Parameter(_)
3287        | Expr::CountStar
3288        | Expr::Exists { .. }
3289        | Expr::ScalarSubquery(_)
3290        | Expr::TypedNullRecord(_) => {}
3291    }
3292}
3293
3294pub fn is_truthy(val: &Value) -> bool {
3295    match val {
3296        Value::Boolean(b) => *b,
3297        Value::Integer(i) => *i != 0,
3298        Value::Null => false,
3299        _ => true,
3300    }
3301}
3302
3303#[cfg(test)]
3304#[path = "eval_tests.rs"]
3305mod tests;