llkv_table/
scalar_eval.rs

1//! Numeric scalar expression evaluation utilities for table scans.
2//!
3//! Planner and executor components leverage these helpers to coerce input columns
4//! into a minimal numeric representation and apply lightweight kernels without
5//! duplicating logic throughout the scan pipeline.
6
7use std::{convert::TryFrom, sync::Arc};
8
9use arrow::array::{Array, ArrayRef, Float64Array, Int64Array, StringArray};
10use arrow::compute::cast;
11use arrow::datatypes::DataType;
12use llkv_column_map::types::LogicalFieldId;
13use llkv_expr::literal::Literal;
14use llkv_expr::{BinaryOp, CompareOp, ScalarExpr};
15use llkv_result::{Error, Result as LlkvResult};
16use rustc_hash::{FxHashMap, FxHashSet};
17
18use crate::types::FieldId;
19
20/// Mapping from field identifiers to the numeric Arrow array used for evaluation.
21pub type NumericArrayMap = FxHashMap<FieldId, NumericArray>;
22
23/// Describes whether a numeric value is represented as an integer or a float.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum NumericKind {
26    Integer,
27    Float,
28}
29
30/// Holds a numeric value while preserving whether it originated as an integer or float.
31#[derive(Clone, Copy, Debug, PartialEq)]
32pub enum NumericValue {
33    Integer(i64),
34    Float(f64),
35}
36
37impl NumericValue {
38    #[inline]
39    pub fn as_f64(self) -> f64 {
40        match self {
41            NumericValue::Integer(v) => v as f64,
42            NumericValue::Float(v) => v,
43        }
44    }
45
46    #[inline]
47    pub fn as_i64(self) -> Option<i64> {
48        match self {
49            NumericValue::Integer(v) => Some(v),
50            NumericValue::Float(_) => None,
51        }
52    }
53
54    #[inline]
55    pub fn kind(self) -> NumericKind {
56        match self {
57            NumericValue::Integer(_) => NumericKind::Integer,
58            NumericValue::Float(_) => NumericKind::Float,
59        }
60    }
61}
62
63impl From<i64> for NumericValue {
64    fn from(value: i64) -> Self {
65        NumericValue::Integer(value)
66    }
67}
68
69impl From<f64> for NumericValue {
70    fn from(value: f64) -> Self {
71        NumericValue::Float(value)
72    }
73}
74
75/// Wraps an Arrow array that stores numeric values alongside its numeric kind.
76#[derive(Clone)]
77pub struct NumericArray {
78    kind: NumericKind,
79    len: usize,
80    int_data: Option<Arc<Int64Array>>,
81    float_data: Option<Arc<Float64Array>>,
82}
83
84impl NumericArray {
85    pub(crate) fn from_int(array: Arc<Int64Array>) -> Self {
86        let len = array.len();
87        Self {
88            kind: NumericKind::Integer,
89            len,
90            int_data: Some(array),
91            float_data: None,
92        }
93    }
94
95    pub(crate) fn from_float(array: Arc<Float64Array>) -> Self {
96        let len = array.len();
97        Self {
98            kind: NumericKind::Float,
99            len,
100            int_data: None,
101            float_data: Some(array),
102        }
103    }
104
105    /// Build a [`NumericArray`] from an Arrow array, casting when necessary.
106    pub fn try_from_arrow(array: &ArrayRef) -> LlkvResult<Self> {
107        match array.data_type() {
108            DataType::Int64 => {
109                let int_array = array
110                    .as_any()
111                    .downcast_ref::<Int64Array>()
112                    .ok_or_else(|| Error::Internal("expected Int64 array".into()))?
113                    .clone();
114                Ok(NumericArray::from_int(Arc::new(int_array)))
115            }
116            DataType::Float64 => {
117                let float_array = array
118                    .as_any()
119                    .downcast_ref::<Float64Array>()
120                    .ok_or_else(|| Error::Internal("expected Float64 array".into()))?
121                    .clone();
122                Ok(NumericArray::from_float(Arc::new(float_array)))
123            }
124            DataType::Int8 | DataType::Int16 | DataType::Int32 => {
125                let casted = cast(array.as_ref(), &DataType::Int64)
126                    .map_err(|e| Error::Internal(format!("cast to Int64 failed: {e}")))?;
127                let int_array = casted
128                    .as_any()
129                    .downcast_ref::<Int64Array>()
130                    .ok_or_else(|| Error::Internal("cast produced non-Int64 array".into()))?
131                    .clone();
132                Ok(NumericArray::from_int(Arc::new(int_array)))
133            }
134            DataType::UInt8
135            | DataType::UInt16
136            | DataType::UInt32
137            | DataType::UInt64
138            | DataType::Float32 => {
139                let casted = cast(array.as_ref(), &DataType::Float64)
140                    .map_err(|e| Error::Internal(format!("cast to Float64 failed: {e}")))?;
141                let float_array = casted
142                    .as_any()
143                    .downcast_ref::<Float64Array>()
144                    .ok_or_else(|| Error::Internal("cast produced non-Float64 array".into()))?
145                    .clone();
146                Ok(NumericArray::from_float(Arc::new(float_array)))
147            }
148            DataType::Boolean => {
149                let casted = cast(array.as_ref(), &DataType::Int64)
150                    .map_err(|e| Error::Internal(format!("cast to Int64 failed: {e}")))?;
151                let int_array = casted
152                    .as_any()
153                    .downcast_ref::<Int64Array>()
154                    .ok_or_else(|| Error::Internal("cast produced non-Int64 array".into()))?
155                    .clone();
156                Ok(NumericArray::from_int(Arc::new(int_array)))
157            }
158            DataType::Utf8 => {
159                // SQLite-style coercion: TEXT to numeric, non-numeric becomes 0
160                let string_array = array
161                    .as_any()
162                    .downcast_ref::<StringArray>()
163                    .ok_or_else(|| Error::Internal("expected StringArray".into()))?;
164                let mut int_values: Vec<Option<i64>> = Vec::with_capacity(string_array.len());
165                for i in 0..string_array.len() {
166                    if string_array.is_null(i) {
167                        int_values.push(None);
168                    } else {
169                        let text = string_array.value(i);
170                        let parsed = text.trim().parse::<i64>().unwrap_or(0);
171                        int_values.push(Some(parsed));
172                    }
173                }
174                let int_array = Int64Array::from(int_values);
175                Ok(NumericArray::from_int(Arc::new(int_array)))
176            }
177            DataType::Null => {
178                let float_array = Float64Array::from(vec![None; array.len()]);
179                Ok(NumericArray::from_float(Arc::new(float_array)))
180            }
181            other => Err(Error::InvalidArgumentError(format!(
182                "unsupported data type in numeric kernel: {other:?}"
183            ))),
184        }
185    }
186
187    #[inline]
188    pub fn kind(&self) -> NumericKind {
189        self.kind
190    }
191
192    #[inline]
193    pub fn len(&self) -> usize {
194        self.len
195    }
196
197    #[inline]
198    pub fn is_empty(&self) -> bool {
199        self.len == 0
200    }
201
202    pub fn value(&self, idx: usize) -> Option<NumericValue> {
203        match self.kind {
204            NumericKind::Integer => {
205                let array = self
206                    .int_data
207                    .as_ref()
208                    .expect("integer array missing backing data");
209                if array.is_null(idx) {
210                    None
211                } else {
212                    Some(NumericValue::Integer(array.value(idx)))
213                }
214            }
215            NumericKind::Float => {
216                let array = self
217                    .float_data
218                    .as_ref()
219                    .expect("float array missing backing data");
220                if array.is_null(idx) {
221                    None
222                } else {
223                    Some(NumericValue::Float(array.value(idx)))
224                }
225            }
226        }
227    }
228
229    fn to_array_ref(&self) -> ArrayRef {
230        match self.kind {
231            NumericKind::Integer => Arc::clone(
232                self.int_data
233                    .as_ref()
234                    .expect("integer array missing backing data"),
235            ) as ArrayRef,
236            NumericKind::Float => Arc::clone(
237                self.float_data
238                    .as_ref()
239                    .expect("float array missing backing data"),
240            ) as ArrayRef,
241        }
242    }
243
244    fn promote_to_float(&self) -> NumericArray {
245        match self.kind {
246            NumericKind::Float => self.clone(),
247            NumericKind::Integer => {
248                let array = self
249                    .int_data
250                    .as_ref()
251                    .expect("integer array missing backing data");
252                let iter = (0..self.len).map(|idx| {
253                    if array.is_null(idx) {
254                        None
255                    } else {
256                        Some(array.value(idx) as f64)
257                    }
258                });
259                let float_array = Float64Array::from_iter(iter);
260                NumericArray::from_float(Arc::new(float_array))
261            }
262        }
263    }
264
265    fn to_aligned_array_ref(&self, preferred: NumericKind) -> ArrayRef {
266        match (preferred, self.kind) {
267            (NumericKind::Float, NumericKind::Integer) => self.promote_to_float().to_array_ref(),
268            _ => self.to_array_ref(),
269        }
270    }
271
272    fn from_numeric_values(values: Vec<Option<NumericValue>>, preferred: NumericKind) -> Self {
273        let contains_float = values
274            .iter()
275            .any(|opt| matches!(opt, Some(NumericValue::Float(_))));
276        match (contains_float, preferred) {
277            (true, _) => {
278                let iter = values.into_iter().map(|opt| opt.map(|v| v.as_f64()));
279                let array = Float64Array::from_iter(iter);
280                NumericArray::from_float(Arc::new(array))
281            }
282            (false, NumericKind::Float) => {
283                let iter = values.into_iter().map(|opt| opt.map(|v| v.as_f64()));
284                let array = Float64Array::from_iter(iter);
285                NumericArray::from_float(Arc::new(array))
286            }
287            (false, NumericKind::Integer) => {
288                let iter = values
289                    .into_iter()
290                    .map(|opt| opt.map(|v| v.as_i64().expect("expected integer")));
291                let array = Int64Array::from_iter(iter);
292                NumericArray::from_int(Arc::new(array))
293            }
294        }
295    }
296}
297
298/// Intermediate representation for vectorized evaluators.
299enum VectorizedExpr {
300    Array(NumericArray),
301    Scalar(Option<NumericValue>),
302}
303
304impl VectorizedExpr {
305    fn materialize(self, len: usize, kind: NumericKind) -> ArrayRef {
306        match self {
307            VectorizedExpr::Array(array) => array.to_aligned_array_ref(kind),
308            VectorizedExpr::Scalar(Some(value)) => {
309                let target_kind = match (value.kind(), kind) {
310                    (NumericKind::Float, _) => NumericKind::Float,
311                    (NumericKind::Integer, NumericKind::Float) => NumericKind::Float,
312                    (NumericKind::Integer, NumericKind::Integer) => NumericKind::Integer,
313                };
314                let values = vec![Some(value); len];
315                let array = NumericArray::from_numeric_values(values, target_kind);
316                array.to_aligned_array_ref(kind)
317            }
318            VectorizedExpr::Scalar(None) => {
319                let values = vec![None; len];
320                let array = NumericArray::from_numeric_values(values, kind);
321                array.to_aligned_array_ref(kind)
322            }
323        }
324    }
325}
326
327/// Represents an affine transformation `scale * field + offset`.
328#[derive(Clone, Copy, Debug)]
329pub struct AffineExpr {
330    pub field: FieldId,
331    pub scale: f64,
332    pub offset: f64,
333}
334
335/// Internal accumulator representing a partially merged affine expression.
336#[derive(Clone, Copy, Debug)]
337struct AffineState {
338    field: Option<FieldId>,
339    scale: f64,
340    offset: f64,
341}
342
343// TODO: Place in impl?
344/// Combine field identifiers while tracking whether multiple fields were encountered.
345fn merge_field(lhs: Option<FieldId>, rhs: Option<FieldId>) -> Option<Option<FieldId>> {
346    match (lhs, rhs) {
347        (Some(a), Some(b)) => {
348            if a == b {
349                Some(Some(a))
350            } else {
351                None
352            }
353        }
354        (Some(a), None) => Some(Some(a)),
355        (None, Some(b)) => Some(Some(b)),
356        (None, None) => Some(None),
357    }
358}
359
360/// Centralizes the numeric kernels applied to scalar expressions so they can be
361/// tuned without touching the surrounding table scan logic.
362pub struct NumericKernels;
363
364impl NumericKernels {
365    /// Collect every field referenced by the scalar expression into `acc`.
366    pub fn collect_fields(expr: &ScalarExpr<FieldId>, acc: &mut FxHashSet<FieldId>) {
367        match expr {
368            ScalarExpr::Column(fid) => {
369                acc.insert(*fid);
370            }
371            ScalarExpr::Literal(_) => {}
372            ScalarExpr::Binary { left, right, .. } => {
373                Self::collect_fields(left, acc);
374                Self::collect_fields(right, acc);
375            }
376            ScalarExpr::Compare { left, right, .. } => {
377                Self::collect_fields(left, acc);
378                Self::collect_fields(right, acc);
379            }
380            ScalarExpr::Not(inner) => {
381                Self::collect_fields(inner, acc);
382            }
383            ScalarExpr::IsNull { expr, .. } => {
384                Self::collect_fields(expr, acc);
385            }
386            ScalarExpr::Aggregate(agg) => {
387                // Collect fields referenced by the aggregate expression
388                match agg {
389                    llkv_expr::expr::AggregateCall::CountStar => {}
390                    llkv_expr::expr::AggregateCall::Count { expr, .. }
391                    | llkv_expr::expr::AggregateCall::Sum { expr, .. }
392                    | llkv_expr::expr::AggregateCall::Total { expr, .. }
393                    | llkv_expr::expr::AggregateCall::Avg { expr, .. }
394                    | llkv_expr::expr::AggregateCall::Min(expr)
395                    | llkv_expr::expr::AggregateCall::Max(expr)
396                    | llkv_expr::expr::AggregateCall::CountNulls(expr)
397                    | llkv_expr::expr::AggregateCall::GroupConcat { expr, .. } => {
398                        Self::collect_fields(expr, acc);
399                    }
400                }
401            }
402            ScalarExpr::GetField { base, .. } => {
403                // Collect fields from the base expression
404                Self::collect_fields(base, acc);
405            }
406            ScalarExpr::Cast { expr, .. } => {
407                Self::collect_fields(expr, acc);
408            }
409            ScalarExpr::Case {
410                operand,
411                branches,
412                else_expr,
413            } => {
414                if let Some(inner) = operand.as_deref() {
415                    Self::collect_fields(inner, acc);
416                }
417                for (when_expr, then_expr) in branches {
418                    Self::collect_fields(when_expr, acc);
419                    Self::collect_fields(then_expr, acc);
420                }
421                if let Some(inner) = else_expr.as_deref() {
422                    Self::collect_fields(inner, acc);
423                }
424            }
425            ScalarExpr::Coalesce(items) => {
426                for item in items {
427                    Self::collect_fields(item, acc);
428                }
429            }
430            ScalarExpr::Random => {
431                // Random does not reference any fields
432            }
433            ScalarExpr::ScalarSubquery(_) => {
434                // Scalar subqueries don't directly reference fields from the outer query
435            }
436        }
437    }
438
439    /// Ensure each referenced column is materialized as a `NumericArray`, casting as needed.
440    pub fn prepare_numeric_arrays(
441        lfids: &[LogicalFieldId],
442        arrays: &[ArrayRef],
443        needed_fields: &FxHashSet<FieldId>,
444    ) -> LlkvResult<NumericArrayMap> {
445        let mut out: NumericArrayMap = FxHashMap::default();
446        if needed_fields.is_empty() {
447            return Ok(out);
448        }
449        for (lfid, array) in lfids.iter().zip(arrays.iter()) {
450            let fid = lfid.field_id();
451            if !needed_fields.contains(&fid) {
452                continue;
453            }
454            let numeric = Self::coerce_array(array)?;
455            out.insert(fid, numeric);
456        }
457        Ok(out)
458    }
459
460    /// Evaluate a scalar expression for the row at `idx` using the provided numeric arrays.
461    pub fn evaluate_value(
462        expr: &ScalarExpr<FieldId>,
463        idx: usize,
464        arrays: &NumericArrayMap,
465    ) -> LlkvResult<Option<NumericValue>> {
466        match expr {
467            ScalarExpr::Column(fid) => {
468                let array = arrays
469                    .get(fid)
470                    .ok_or_else(|| Error::Internal(format!("missing column for field {fid}")))?;
471                Ok(array.value(idx))
472            }
473            ScalarExpr::Literal(_) => Ok(Self::literal_numeric_value(expr)),
474            ScalarExpr::Binary { left, op, right } => {
475                let l = Self::evaluate_value(left, idx, arrays)?;
476                let r = Self::evaluate_value(right, idx, arrays)?;
477                Ok(Self::apply_binary(*op, l, r))
478            }
479            ScalarExpr::Compare { left, op, right } => {
480                let l = Self::evaluate_value(left, idx, arrays)?;
481                let r = Self::evaluate_value(right, idx, arrays)?;
482                match (l, r) {
483                    (Some(lhs), Some(rhs)) => {
484                        let result = Self::compare(*op, lhs, rhs);
485                        Ok(Some(NumericValue::Integer(result as i64)))
486                    }
487                    _ => Ok(None),
488                }
489            }
490            ScalarExpr::Not(inner) => {
491                let value = Self::evaluate_value(inner, idx, arrays)?;
492                match value {
493                    Some(v) => {
494                        let is_truthy = Self::truthy_numeric(v);
495                        Ok(Some(NumericValue::Integer(if is_truthy { 0 } else { 1 })))
496                    }
497                    None => Ok(None),
498                }
499            }
500            ScalarExpr::IsNull { expr, negated } => {
501                let value = Self::evaluate_value(expr, idx, arrays)?;
502                let is_null = value.is_none();
503                // XOR-style comparison keeps negated IS NULL readable.
504                let condition_holds = is_null != *negated;
505                Ok(Some(NumericValue::Integer(if condition_holds {
506                    1
507                } else {
508                    0
509                })))
510            }
511            ScalarExpr::Aggregate(_) => Err(Error::Internal(
512                "Aggregate expressions should not appear in row-level evaluation".into(),
513            )),
514            ScalarExpr::GetField { .. } => Err(Error::Internal(
515                "GetField expressions should not be evaluated in numeric kernels".into(),
516            )),
517            ScalarExpr::Cast { expr, data_type } => {
518                let value = Self::evaluate_value(expr, idx, arrays)?;
519                let target_kind = Self::kind_for_data_type(data_type).ok_or_else(|| {
520                    Error::InvalidArgumentError(format!(
521                        "unsupported cast target type {:?}",
522                        data_type
523                    ))
524                })?;
525                Self::cast_numeric_value_to_kind(value, target_kind)
526            }
527            ScalarExpr::Case {
528                operand,
529                branches,
530                else_expr,
531            } => {
532                let operand_value = match operand.as_deref() {
533                    Some(op) => Some(Self::evaluate_value(op, idx, arrays)?),
534                    None => None,
535                };
536
537                for (when_expr, then_expr) in branches {
538                    let matched = if let Some(op_val_opt) = &operand_value {
539                        let when_val = Self::evaluate_value(when_expr, idx, arrays)?;
540                        match (op_val_opt, &when_val) {
541                            (Some(op_val), Some(branch_val)) => {
542                                Self::numeric_equals(*op_val, *branch_val)
543                            }
544                            _ => false,
545                        }
546                    } else {
547                        let cond_val = Self::evaluate_value(when_expr, idx, arrays)?;
548                        cond_val.is_some_and(Self::truthy_numeric)
549                    };
550
551                    if matched {
552                        return Self::evaluate_value(then_expr, idx, arrays);
553                    }
554                }
555
556                if let Some(else_expr) = else_expr.as_deref() {
557                    Self::evaluate_value(else_expr, idx, arrays)
558                } else {
559                    Ok(None)
560                }
561            }
562            ScalarExpr::Coalesce(items) => {
563                for item in items {
564                    if let Some(value) = Self::evaluate_value(item, idx, arrays)? {
565                        return Ok(Some(value));
566                    }
567                }
568                Ok(None)
569            }
570            ScalarExpr::Random => Ok(Some(NumericValue::Float(rand::random::<f64>()))),
571            ScalarExpr::ScalarSubquery(_) => Err(Error::Internal(
572                "Scalar subquery evaluation requires a separate execution context".into(),
573            )),
574        }
575    }
576
577    /// Evaluate a scalar expression for every row in the batch.
578    #[allow(dead_code)]
579    pub fn evaluate_batch(
580        expr: &ScalarExpr<FieldId>,
581        len: usize,
582        arrays: &NumericArrayMap,
583    ) -> LlkvResult<ArrayRef> {
584        let simplified = Self::simplify(expr);
585        Self::evaluate_batch_simplified(&simplified, len, arrays)
586    }
587
588    /// Evaluate a scalar expression that has already been simplified.
589    pub fn evaluate_batch_simplified(
590        expr: &ScalarExpr<FieldId>,
591        len: usize,
592        arrays: &NumericArrayMap,
593    ) -> LlkvResult<ArrayRef> {
594        let preferred = Self::infer_result_kind(expr, arrays);
595        if let Some(vectorized) = Self::try_evaluate_vectorized(expr, len, arrays, preferred)? {
596            return Ok(vectorized.materialize(len, preferred));
597        }
598
599        let mut values: Vec<Option<NumericValue>> = Vec::with_capacity(len);
600        for idx in 0..len {
601            values.push(Self::evaluate_value(expr, idx, arrays)?);
602        }
603        let array = NumericArray::from_numeric_values(values, preferred);
604        Ok(array.to_aligned_array_ref(preferred))
605    }
606
607    fn try_evaluate_vectorized(
608        expr: &ScalarExpr<FieldId>,
609        len: usize,
610        arrays: &NumericArrayMap,
611        preferred: NumericKind,
612    ) -> LlkvResult<Option<VectorizedExpr>> {
613        match expr {
614            ScalarExpr::Column(fid) => {
615                let array = arrays
616                    .get(fid)
617                    .ok_or_else(|| Error::Internal(format!("missing column for field {fid}")))?;
618                Ok(Some(VectorizedExpr::Array(array.clone())))
619            }
620            ScalarExpr::Literal(_) => Ok(Some(VectorizedExpr::Scalar(
621                Self::literal_numeric_value(expr),
622            ))),
623            ScalarExpr::Binary { left, op, right } => {
624                let left_kind = Self::infer_result_kind(left, arrays);
625                let right_kind = Self::infer_result_kind(right, arrays);
626
627                let left_vec = Self::try_evaluate_vectorized(left, len, arrays, left_kind)?;
628                let right_vec = Self::try_evaluate_vectorized(right, len, arrays, right_kind)?;
629
630                match (left_vec, right_vec) {
631                    (Some(VectorizedExpr::Scalar(lhs)), Some(VectorizedExpr::Scalar(rhs))) => Ok(
632                        Some(VectorizedExpr::Scalar(Self::apply_binary(*op, lhs, rhs))),
633                    ),
634                    (Some(VectorizedExpr::Array(lhs)), Some(VectorizedExpr::Array(rhs))) => {
635                        let array =
636                            Self::compute_binary_array_array(&lhs, &rhs, len, *op, preferred)?;
637                        Ok(Some(VectorizedExpr::Array(array)))
638                    }
639                    (Some(VectorizedExpr::Array(lhs)), Some(VectorizedExpr::Scalar(rhs))) => {
640                        let array = Self::compute_binary_array_scalar(
641                            &lhs, rhs, len, *op, true, preferred,
642                        )?;
643                        Ok(Some(VectorizedExpr::Array(array)))
644                    }
645                    (Some(VectorizedExpr::Scalar(lhs)), Some(VectorizedExpr::Array(rhs))) => {
646                        let array = Self::compute_binary_array_scalar(
647                            &rhs, lhs, len, *op, false, preferred,
648                        )?;
649                        Ok(Some(VectorizedExpr::Array(array)))
650                    }
651                    _ => Ok(None),
652                }
653            }
654            ScalarExpr::Compare { .. } => Ok(None),
655            ScalarExpr::Not(_) => Ok(None),
656            ScalarExpr::IsNull { .. } => Ok(None),
657            ScalarExpr::Aggregate(_) => Err(Error::Internal(
658                "Aggregate expressions should not appear in row-level evaluation".into(),
659            )),
660            ScalarExpr::GetField { .. } => Err(Error::Internal(
661                "GetField expressions should not be evaluated in numeric kernels".into(),
662            )),
663            ScalarExpr::Cast { expr, data_type } => {
664                let inner_kind = Self::infer_result_kind(expr, arrays);
665                let inner_vec = Self::try_evaluate_vectorized(expr, len, arrays, inner_kind)?;
666                let target_kind = Self::kind_for_data_type(data_type).ok_or_else(|| {
667                    Error::InvalidArgumentError(format!(
668                        "unsupported cast target type {:?}",
669                        data_type
670                    ))
671                })?;
672
673                match inner_vec {
674                    Some(VectorizedExpr::Scalar(value)) => Ok(Some(VectorizedExpr::Scalar(
675                        Self::cast_numeric_value_to_kind(value, target_kind)?,
676                    ))),
677                    Some(VectorizedExpr::Array(array)) => Ok(Some(VectorizedExpr::Array(
678                        Self::cast_numeric_array_to_kind(&array, target_kind)?,
679                    ))),
680                    None => Ok(None),
681                }
682            }
683            ScalarExpr::Case { .. } => Ok(None),
684            ScalarExpr::Coalesce(_) => Ok(None),
685            ScalarExpr::Random => {
686                // Generate array of random float values
687                let values: Vec<f64> = (0..len).map(|_| rand::random::<f64>()).collect();
688                let array = Float64Array::from(values);
689                Ok(Some(VectorizedExpr::Array(NumericArray::from_float(
690                    Arc::new(array),
691                ))))
692            }
693            ScalarExpr::ScalarSubquery(_) => Ok(None),
694        }
695    }
696
697    fn compute_binary_array_array(
698        left: &NumericArray,
699        right: &NumericArray,
700        len: usize,
701        op: BinaryOp,
702        preferred: NumericKind,
703    ) -> LlkvResult<NumericArray> {
704        if left.len() != len || right.len() != len {
705            return Err(Error::Internal("scalar expression length mismatch".into()));
706        }
707
708        let iter = (0..len).map(|idx| {
709            let lhs = left.value(idx);
710            let rhs = right.value(idx);
711            Self::apply_binary(op, lhs, rhs)
712        });
713
714        let values = iter.collect::<Vec<_>>();
715        Ok(NumericArray::from_numeric_values(values, preferred))
716    }
717
718    fn compute_binary_array_scalar(
719        array: &NumericArray,
720        scalar: Option<NumericValue>,
721        len: usize,
722        op: BinaryOp,
723        array_is_left: bool,
724        preferred: NumericKind,
725    ) -> LlkvResult<NumericArray> {
726        if array.len() != len {
727            return Err(Error::Internal("scalar expression length mismatch".into()));
728        }
729
730        if scalar.is_none() {
731            return Ok(NumericArray::from_numeric_values(
732                vec![None; len],
733                preferred,
734            ));
735        }
736        let scalar_value = scalar.expect("checked above");
737
738        if array_is_left && matches!(op, BinaryOp::Divide | BinaryOp::Modulo) {
739            let is_zero = matches!(
740                scalar_value,
741                NumericValue::Integer(0) | NumericValue::Float(0.0)
742            );
743            if is_zero {
744                return Ok(NumericArray::from_numeric_values(
745                    vec![None; len],
746                    preferred,
747                ));
748            }
749        }
750
751        let iter = (0..len).map(|idx| {
752            let array_val = array.value(idx);
753            let (lhs, rhs) = if array_is_left {
754                (array_val, Some(scalar_value))
755            } else {
756                (Some(scalar_value), array_val)
757            };
758            Self::apply_binary(op, lhs, rhs)
759        });
760
761        let values = iter.collect::<Vec<_>>();
762        Ok(NumericArray::from_numeric_values(values, preferred))
763    }
764
765    /// Returns the column referenced by an expression when it's a direct or additive identity passthrough.
766    pub fn passthrough_column(expr: &ScalarExpr<FieldId>) -> Option<FieldId> {
767        match Self::simplify(expr) {
768            ScalarExpr::Column(fid) => Some(fid),
769            _ => None,
770        }
771    }
772
773    fn literal_numeric_value(expr: &ScalarExpr<FieldId>) -> Option<NumericValue> {
774        if let ScalarExpr::Literal(lit) = expr {
775            match lit {
776                llkv_expr::literal::Literal::Float(f) => Some(NumericValue::Float(*f)),
777                llkv_expr::literal::Literal::Integer(i) => {
778                    if let Ok(value) = i64::try_from(*i) {
779                        Some(NumericValue::Integer(value))
780                    } else {
781                        Some(NumericValue::Float(*i as f64))
782                    }
783                }
784                llkv_expr::literal::Literal::Boolean(b) => {
785                    Some(NumericValue::Integer(if *b { 1 } else { 0 }))
786                }
787                llkv_expr::literal::Literal::String(_) => None,
788                llkv_expr::literal::Literal::Struct(_) => None,
789                llkv_expr::literal::Literal::Null => None,
790            }
791        } else if let ScalarExpr::IsNull { expr, negated } = expr {
792            if let ScalarExpr::Literal(lit) = expr.as_ref() {
793                let is_null = matches!(lit, Literal::Null);
794                let condition = if is_null { !negated } else { *negated };
795                Some(NumericValue::Integer(if condition { 1 } else { 0 }))
796            } else {
797                None
798            }
799        } else {
800            None
801        }
802    }
803
804    fn literal_is_zero(expr: &ScalarExpr<FieldId>) -> bool {
805        matches!(
806            Self::literal_numeric_value(expr),
807            Some(NumericValue::Integer(0)) | Some(NumericValue::Float(0.0))
808        )
809    }
810
811    fn literal_is_one(expr: &ScalarExpr<FieldId>) -> bool {
812        matches!(
813            Self::literal_numeric_value(expr),
814            Some(NumericValue::Integer(1)) | Some(NumericValue::Float(1.0))
815        )
816    }
817
818    #[inline]
819    fn numeric_equals(lhs: NumericValue, rhs: NumericValue) -> bool {
820        match (lhs, rhs) {
821            (NumericValue::Integer(a), NumericValue::Integer(b)) => a == b,
822            _ => lhs.as_f64() == rhs.as_f64(),
823        }
824    }
825
826    #[inline]
827    fn truthy_numeric(value: NumericValue) -> bool {
828        match value {
829            NumericValue::Integer(v) => v != 0,
830            NumericValue::Float(v) => v != 0.0,
831        }
832    }
833
834    #[inline]
835    fn option_numeric_truthiness(value: Option<NumericValue>) -> Option<bool> {
836        value.map(Self::truthy_numeric)
837    }
838
839    #[inline]
840    fn evaluate_option_numeric_and(
841        lhs: Option<NumericValue>,
842        rhs: Option<NumericValue>,
843    ) -> Option<NumericValue> {
844        let left_truth = Self::option_numeric_truthiness(lhs);
845        if matches!(left_truth, Some(false)) {
846            return Some(NumericValue::Integer(0));
847        }
848
849        let right_truth = Self::option_numeric_truthiness(rhs);
850        if matches!(right_truth, Some(false)) {
851            return Some(NumericValue::Integer(0));
852        }
853
854        match (left_truth, right_truth) {
855            (Some(true), Some(true)) => Some(NumericValue::Integer(1)),
856            (Some(true), None) | (None, Some(true)) | (None, None) => None,
857            _ => None,
858        }
859    }
860
861    #[inline]
862    fn evaluate_option_numeric_or(
863        lhs: Option<NumericValue>,
864        rhs: Option<NumericValue>,
865    ) -> Option<NumericValue> {
866        let left_truth = Self::option_numeric_truthiness(lhs);
867        if matches!(left_truth, Some(true)) {
868            return Some(NumericValue::Integer(1));
869        }
870
871        let right_truth = Self::option_numeric_truthiness(rhs);
872        if matches!(right_truth, Some(true)) {
873            return Some(NumericValue::Integer(1));
874        }
875
876        match (left_truth, right_truth) {
877            (Some(false), Some(false)) => Some(NumericValue::Integer(0)),
878            (Some(false), None) | (None, Some(false)) | (None, None) => None,
879            _ => None,
880        }
881    }
882
883    /// Recursively simplify the expression by folding literals and eliminating identity operations.
884    pub fn simplify(expr: &ScalarExpr<FieldId>) -> ScalarExpr<FieldId> {
885        match expr {
886            ScalarExpr::Column(_)
887            | ScalarExpr::Literal(_)
888            | ScalarExpr::Aggregate(_)
889            | ScalarExpr::GetField { .. }
890            | ScalarExpr::Random => expr.clone(),
891            ScalarExpr::Binary { left, op, right } => {
892                let left_s = Self::simplify(left);
893                let right_s = Self::simplify(right);
894
895                // Any binary operation involving NULL yields NULL
896                if matches!(left_s, ScalarExpr::Literal(Literal::Null))
897                    || matches!(right_s, ScalarExpr::Literal(Literal::Null))
898                {
899                    return ScalarExpr::literal(Literal::Null);
900                }
901
902                if let (Some(lv), Some(rv)) = (
903                    Self::literal_numeric_value(&left_s),
904                    Self::literal_numeric_value(&right_s),
905                ) && let Some(lit) = Self::apply_binary_literal(*op, lv, rv)
906                {
907                    return lit;
908                }
909
910                match op {
911                    BinaryOp::Add => {
912                        if Self::literal_is_zero(&left_s) {
913                            return right_s;
914                        }
915                        if Self::literal_is_zero(&right_s) {
916                            return left_s;
917                        }
918                    }
919                    BinaryOp::Subtract => {
920                        if Self::literal_is_zero(&right_s) {
921                            return left_s;
922                        }
923                    }
924                    BinaryOp::Multiply => {
925                        if Self::literal_is_one(&left_s) {
926                            return right_s;
927                        }
928                        if Self::literal_is_one(&right_s) {
929                            return left_s;
930                        }
931                    }
932                    BinaryOp::Divide => {
933                        if Self::literal_is_one(&right_s) {
934                            return left_s;
935                        }
936                    }
937                    BinaryOp::Modulo => {}
938                    BinaryOp::And => {
939                        if Self::literal_is_zero(&left_s) || Self::literal_is_zero(&right_s) {
940                            return ScalarExpr::literal(0);
941                        }
942                        if Self::literal_is_one(&left_s) {
943                            return right_s;
944                        }
945                        if Self::literal_is_one(&right_s) {
946                            return left_s;
947                        }
948                    }
949                    BinaryOp::Or => {
950                        if Self::literal_is_one(&left_s) || Self::literal_is_one(&right_s) {
951                            return ScalarExpr::literal(1);
952                        }
953                        if Self::literal_is_zero(&left_s) {
954                            return right_s;
955                        }
956                        if Self::literal_is_zero(&right_s) {
957                            return left_s;
958                        }
959                    }
960                    BinaryOp::BitwiseShiftLeft | BinaryOp::BitwiseShiftRight => {}
961                }
962
963                ScalarExpr::binary(left_s, *op, right_s)
964            }
965            ScalarExpr::Compare { left, op, right } => {
966                let left_s = Self::simplify(left);
967                let right_s = Self::simplify(right);
968                ScalarExpr::compare(left_s, *op, right_s)
969            }
970            ScalarExpr::Not(inner) => {
971                let simplified_inner = Self::simplify(inner);
972                match &simplified_inner {
973                    ScalarExpr::Literal(lit) => match lit {
974                        Literal::Null => ScalarExpr::literal(Literal::Null),
975                        Literal::Integer(v) => {
976                            ScalarExpr::literal(Literal::Integer(if *v == 0 { 1 } else { 0 }))
977                        }
978                        Literal::Float(v) => {
979                            ScalarExpr::literal(Literal::Integer(if *v == 0.0 { 1 } else { 0 }))
980                        }
981                        Literal::Boolean(v) => {
982                            ScalarExpr::literal(Literal::Integer(if *v { 0 } else { 1 }))
983                        }
984                        _ => ScalarExpr::logical_not(simplified_inner),
985                    },
986                    _ => ScalarExpr::logical_not(simplified_inner),
987                }
988            }
989            ScalarExpr::IsNull { expr, negated } => {
990                let simplified_inner = Self::simplify(expr);
991                match &simplified_inner {
992                    ScalarExpr::Literal(lit) => match lit {
993                        Literal::Null => {
994                            ScalarExpr::literal(Literal::Integer(if *negated { 0 } else { 1 }))
995                        }
996                        _ => ScalarExpr::literal(Literal::Integer(if *negated { 1 } else { 0 })),
997                    },
998                    _ => ScalarExpr::is_null(simplified_inner, *negated),
999                }
1000            }
1001            ScalarExpr::Cast { expr, data_type } => {
1002                let inner = Self::simplify(expr);
1003                // Preserve explicit casts even when the operand is NULL so we keep the
1004                // target type information for downstream consumers (e.g. aggregate typing).
1005                ScalarExpr::cast(inner, data_type.clone())
1006            }
1007            ScalarExpr::Case {
1008                operand,
1009                branches,
1010                else_expr,
1011            } => {
1012                let operand_s = operand.as_ref().map(|inner| Self::simplify(inner));
1013                let mut branch_vec = Vec::with_capacity(branches.len());
1014                for (when_expr, then_expr) in branches {
1015                    branch_vec.push((Self::simplify(when_expr), Self::simplify(then_expr)));
1016                }
1017                let else_s = else_expr.as_ref().map(|inner| Self::simplify(inner));
1018                ScalarExpr::case(operand_s, branch_vec, else_s)
1019            }
1020            ScalarExpr::Coalesce(items) => {
1021                let simplified_items = items.iter().map(Self::simplify).collect();
1022                ScalarExpr::coalesce(simplified_items)
1023            }
1024            ScalarExpr::ScalarSubquery(_) => expr.clone(),
1025        }
1026    }
1027
1028    /// Attempts to represent the expression as `scale * column + offset`.
1029    /// Returns `None` when the expression depends on multiple columns or is non-linear.
1030    #[allow(dead_code)]
1031    pub fn extract_affine(expr: &ScalarExpr<FieldId>) -> Option<AffineExpr> {
1032        let simplified = Self::simplify(expr);
1033        Self::extract_affine_simplified(&simplified)
1034    }
1035
1036    /// Variant of \[`extract_affine`\] that assumes `expr` is already simplified.
1037    pub fn extract_affine_simplified(expr: &ScalarExpr<FieldId>) -> Option<AffineExpr> {
1038        let state = Self::affine_state(expr)?;
1039        let field = state.field?;
1040        Some(AffineExpr {
1041            field,
1042            scale: state.scale,
1043            offset: state.offset,
1044        })
1045    }
1046
1047    fn affine_state(expr: &ScalarExpr<FieldId>) -> Option<AffineState> {
1048        match expr {
1049            ScalarExpr::Column(fid) => Some(AffineState {
1050                field: Some(*fid),
1051                scale: 1.0,
1052                offset: 0.0,
1053            }),
1054            ScalarExpr::Literal(_) => {
1055                let value = Self::literal_numeric_value(expr)?.as_f64();
1056                Some(AffineState {
1057                    field: None,
1058                    scale: 0.0,
1059                    offset: value,
1060                })
1061            }
1062            ScalarExpr::Aggregate(_) => None, // Aggregates not supported in affine transformations
1063            ScalarExpr::GetField { .. } => None, // GetField not supported in affine transformations
1064            ScalarExpr::Binary { left, op, right } => {
1065                let left_state = Self::affine_state(left)?;
1066                let right_state = Self::affine_state(right)?;
1067                match op {
1068                    BinaryOp::Add => Self::affine_add(left_state, right_state),
1069                    BinaryOp::Subtract => Self::affine_sub(left_state, right_state),
1070                    BinaryOp::Multiply => Self::affine_mul(left_state, right_state),
1071                    BinaryOp::Divide => Self::affine_div(left_state, right_state),
1072                    BinaryOp::Modulo
1073                    | BinaryOp::And
1074                    | BinaryOp::Or
1075                    | BinaryOp::BitwiseShiftLeft
1076                    | BinaryOp::BitwiseShiftRight => None,
1077                }
1078            }
1079            ScalarExpr::Compare { .. } => None,
1080            ScalarExpr::Not(_) => None,
1081            ScalarExpr::IsNull { .. } => None,
1082            ScalarExpr::Cast { expr, .. } => Self::affine_state(expr),
1083            ScalarExpr::Case { .. } => None,
1084            ScalarExpr::Coalesce(_) => None,
1085            ScalarExpr::Random => None,
1086            ScalarExpr::ScalarSubquery(_) => None,
1087        }
1088    }
1089
1090    fn affine_add(lhs: AffineState, rhs: AffineState) -> Option<AffineState> {
1091        let field = merge_field(lhs.field, rhs.field)?;
1092        Some(AffineState {
1093            field,
1094            scale: lhs.scale + rhs.scale,
1095            offset: lhs.offset + rhs.offset,
1096        })
1097    }
1098
1099    fn affine_sub(lhs: AffineState, rhs: AffineState) -> Option<AffineState> {
1100        let neg_rhs = AffineState {
1101            field: rhs.field,
1102            scale: -rhs.scale,
1103            offset: -rhs.offset,
1104        };
1105        Self::affine_add(lhs, neg_rhs)
1106    }
1107
1108    fn affine_mul(lhs: AffineState, rhs: AffineState) -> Option<AffineState> {
1109        if rhs.field.is_none() {
1110            let factor = rhs.offset;
1111            return Some(AffineState {
1112                field: lhs.field,
1113                scale: lhs.scale * factor,
1114                offset: lhs.offset * factor,
1115            });
1116        }
1117        if lhs.field.is_none() {
1118            let factor = lhs.offset;
1119            return Some(AffineState {
1120                field: rhs.field,
1121                scale: rhs.scale * factor,
1122                offset: rhs.offset * factor,
1123            });
1124        }
1125        None
1126    }
1127
1128    fn affine_div(lhs: AffineState, rhs: AffineState) -> Option<AffineState> {
1129        if rhs.field.is_some() {
1130            return None;
1131        }
1132        let denom = rhs.offset;
1133        if denom == 0.0 {
1134            return None;
1135        }
1136        Some(AffineState {
1137            field: lhs.field,
1138            scale: lhs.scale / denom,
1139            offset: lhs.offset / denom,
1140        })
1141    }
1142
1143    fn apply_binary_literal(
1144        op: BinaryOp,
1145        lhs: NumericValue,
1146        rhs: NumericValue,
1147    ) -> Option<ScalarExpr<FieldId>> {
1148        match op {
1149            BinaryOp::Add => Some(Self::literal_from_numeric(Self::add_values(lhs, rhs))),
1150            BinaryOp::Subtract => Some(Self::literal_from_numeric(Self::sub_values(lhs, rhs))),
1151            BinaryOp::Multiply => Some(Self::literal_from_numeric(Self::mul_values(lhs, rhs))),
1152            BinaryOp::Divide => Self::div_values(lhs, rhs).map(Self::literal_from_numeric),
1153            BinaryOp::Modulo => Self::mod_values(lhs, rhs).map(Self::literal_from_numeric),
1154            BinaryOp::And => {
1155                let truthy = Self::truthy_numeric(lhs) && Self::truthy_numeric(rhs);
1156                Some(ScalarExpr::literal(if truthy { 1 } else { 0 }))
1157            }
1158            BinaryOp::Or => {
1159                let truthy = Self::truthy_numeric(lhs) || Self::truthy_numeric(rhs);
1160                Some(ScalarExpr::literal(if truthy { 1 } else { 0 }))
1161            }
1162            BinaryOp::BitwiseShiftLeft => {
1163                let lhs_i64 = match lhs {
1164                    NumericValue::Integer(i) => i,
1165                    NumericValue::Float(f) => f as i64,
1166                };
1167                let rhs_i64 = match rhs {
1168                    NumericValue::Integer(i) => i,
1169                    NumericValue::Float(f) => f as i64,
1170                };
1171                let result = lhs_i64.wrapping_shl(rhs_i64 as u32);
1172                Some(ScalarExpr::literal(result))
1173            }
1174            BinaryOp::BitwiseShiftRight => {
1175                let lhs_i64 = match lhs {
1176                    NumericValue::Integer(i) => i,
1177                    NumericValue::Float(f) => f as i64,
1178                };
1179                let rhs_i64 = match rhs {
1180                    NumericValue::Integer(i) => i,
1181                    NumericValue::Float(f) => f as i64,
1182                };
1183                let result = lhs_i64.wrapping_shr(rhs_i64 as u32);
1184                Some(ScalarExpr::literal(result))
1185            }
1186        }
1187    }
1188
1189    fn literal_from_numeric(value: NumericValue) -> ScalarExpr<FieldId> {
1190        match value {
1191            NumericValue::Integer(i) => ScalarExpr::literal(i),
1192            NumericValue::Float(f) => ScalarExpr::literal(f),
1193        }
1194    }
1195
1196    /// Apply an arithmetic kernel. Returns `None` when the computation results in a null (e.g. divide by zero).
1197    pub fn apply_binary(
1198        op: BinaryOp,
1199        lhs: Option<NumericValue>,
1200        rhs: Option<NumericValue>,
1201    ) -> Option<NumericValue> {
1202        match op {
1203            BinaryOp::And => Self::evaluate_option_numeric_and(lhs, rhs),
1204            BinaryOp::Or => Self::evaluate_option_numeric_or(lhs, rhs),
1205            _ => match (lhs, rhs) {
1206                (Some(lv), Some(rv)) => Self::apply_binary_values(op, lv, rv),
1207                _ => None,
1208            },
1209        }
1210    }
1211
1212    fn apply_binary_values(
1213        op: BinaryOp,
1214        lhs: NumericValue,
1215        rhs: NumericValue,
1216    ) -> Option<NumericValue> {
1217        match op {
1218            BinaryOp::Add => Some(Self::add_values(lhs, rhs)),
1219            BinaryOp::Subtract => Some(Self::sub_values(lhs, rhs)),
1220            BinaryOp::Multiply => Some(Self::mul_values(lhs, rhs)),
1221            BinaryOp::Divide => Self::div_values(lhs, rhs),
1222            BinaryOp::Modulo => Self::mod_values(lhs, rhs),
1223            BinaryOp::And => Some(NumericValue::Integer(
1224                if Self::truthy_numeric(lhs) && Self::truthy_numeric(rhs) {
1225                    1
1226                } else {
1227                    0
1228                },
1229            )),
1230            BinaryOp::Or => Some(NumericValue::Integer(
1231                if Self::truthy_numeric(lhs) || Self::truthy_numeric(rhs) {
1232                    1
1233                } else {
1234                    0
1235                },
1236            )),
1237            BinaryOp::BitwiseShiftLeft => {
1238                let lhs_i64 = match lhs {
1239                    NumericValue::Integer(i) => i,
1240                    NumericValue::Float(f) => f as i64,
1241                };
1242                let rhs_i64 = match rhs {
1243                    NumericValue::Integer(i) => i,
1244                    NumericValue::Float(f) => f as i64,
1245                };
1246                let result = lhs_i64.wrapping_shl(rhs_i64 as u32);
1247                Some(NumericValue::Integer(result))
1248            }
1249            BinaryOp::BitwiseShiftRight => {
1250                let lhs_i64 = match lhs {
1251                    NumericValue::Integer(i) => i,
1252                    NumericValue::Float(f) => f as i64,
1253                };
1254                let rhs_i64 = match rhs {
1255                    NumericValue::Integer(i) => i,
1256                    NumericValue::Float(f) => f as i64,
1257                };
1258                let result = lhs_i64.wrapping_shr(rhs_i64 as u32);
1259                Some(NumericValue::Integer(result))
1260            }
1261        }
1262    }
1263
1264    fn add_values(lhs: NumericValue, rhs: NumericValue) -> NumericValue {
1265        match (lhs, rhs) {
1266            (NumericValue::Integer(li), NumericValue::Integer(ri)) => match li.checked_add(ri) {
1267                Some(sum) => NumericValue::Integer(sum),
1268                None => NumericValue::Float(li as f64 + ri as f64),
1269            },
1270            _ => NumericValue::Float(lhs.as_f64() + rhs.as_f64()),
1271        }
1272    }
1273
1274    fn sub_values(lhs: NumericValue, rhs: NumericValue) -> NumericValue {
1275        match (lhs, rhs) {
1276            (NumericValue::Integer(li), NumericValue::Integer(ri)) => match li.checked_sub(ri) {
1277                Some(diff) => NumericValue::Integer(diff),
1278                None => NumericValue::Float(li as f64 - ri as f64),
1279            },
1280            _ => NumericValue::Float(lhs.as_f64() - rhs.as_f64()),
1281        }
1282    }
1283
1284    fn mul_values(lhs: NumericValue, rhs: NumericValue) -> NumericValue {
1285        match (lhs, rhs) {
1286            (NumericValue::Integer(li), NumericValue::Integer(ri)) => match li.checked_mul(ri) {
1287                Some(prod) => NumericValue::Integer(prod),
1288                None => NumericValue::Float(li as f64 * ri as f64),
1289            },
1290            _ => NumericValue::Float(lhs.as_f64() * rhs.as_f64()),
1291        }
1292    }
1293
1294    fn div_values(lhs: NumericValue, rhs: NumericValue) -> Option<NumericValue> {
1295        match rhs {
1296            NumericValue::Integer(0) | NumericValue::Float(0.0) => return None,
1297            _ => {}
1298        }
1299
1300        match (lhs, rhs) {
1301            (NumericValue::Integer(li), NumericValue::Integer(ri)) => {
1302                if li == i64::MIN && ri == -1 {
1303                    Some(NumericValue::Float(li as f64 / ri as f64))
1304                } else {
1305                    Some(NumericValue::Integer(li / ri))
1306                }
1307            }
1308            _ => Some(NumericValue::Float(lhs.as_f64() / rhs.as_f64())),
1309        }
1310    }
1311
1312    fn mod_values(lhs: NumericValue, rhs: NumericValue) -> Option<NumericValue> {
1313        match rhs {
1314            NumericValue::Integer(0) | NumericValue::Float(0.0) => return None,
1315            _ => {}
1316        }
1317
1318        match (lhs, rhs) {
1319            (NumericValue::Integer(li), NumericValue::Integer(ri)) => {
1320                Some(NumericValue::Integer(li % ri))
1321            }
1322            _ => Some(NumericValue::Float(lhs.as_f64() % rhs.as_f64())),
1323        }
1324    }
1325
1326    fn cast_numeric_value_to_kind(
1327        value: Option<NumericValue>,
1328        target: NumericKind,
1329    ) -> LlkvResult<Option<NumericValue>> {
1330        match value {
1331            None => Ok(None),
1332            Some(NumericValue::Integer(v)) => Ok(Some(match target {
1333                NumericKind::Integer => NumericValue::Integer(v),
1334                NumericKind::Float => NumericValue::Float(v as f64),
1335            })),
1336            Some(NumericValue::Float(v)) => {
1337                if !v.is_finite() {
1338                    return Err(Error::InvalidArgumentError(
1339                        "cannot cast non-finite float value".into(),
1340                    ));
1341                }
1342                match target {
1343                    NumericKind::Float => Ok(Some(NumericValue::Float(v))),
1344                    NumericKind::Integer => {
1345                        let truncated = v.trunc();
1346                        if truncated < i64::MIN as f64 || truncated > i64::MAX as f64 {
1347                            return Err(Error::InvalidArgumentError(
1348                                "float out of range for INT64 cast".into(),
1349                            ));
1350                        }
1351                        Ok(Some(NumericValue::Integer(truncated as i64)))
1352                    }
1353                }
1354            }
1355        }
1356    }
1357
1358    fn cast_numeric_array_to_kind(
1359        array: &NumericArray,
1360        target: NumericKind,
1361    ) -> LlkvResult<NumericArray> {
1362        match target {
1363            NumericKind::Float => Ok(array.promote_to_float()),
1364            NumericKind::Integer => {
1365                if array.kind() == NumericKind::Integer {
1366                    Ok(array.clone())
1367                } else {
1368                    let mut values = Vec::with_capacity(array.len());
1369                    for idx in 0..array.len() {
1370                        let value = array.value(idx);
1371                        values.push(Self::cast_numeric_value_to_kind(value, target)?);
1372                    }
1373                    Ok(NumericArray::from_numeric_values(
1374                        values,
1375                        NumericKind::Integer,
1376                    ))
1377                }
1378            }
1379        }
1380    }
1381
1382    fn infer_result_kind(expr: &ScalarExpr<FieldId>, arrays: &NumericArrayMap) -> NumericKind {
1383        match expr {
1384            ScalarExpr::Literal(lit) => match lit {
1385                llkv_expr::literal::Literal::Float(_) => NumericKind::Float,
1386                llkv_expr::literal::Literal::Integer(_) => NumericKind::Integer,
1387                llkv_expr::literal::Literal::Boolean(_) => NumericKind::Integer,
1388                llkv_expr::literal::Literal::Null => NumericKind::Integer,
1389                llkv_expr::literal::Literal::String(_) => NumericKind::Float,
1390                llkv_expr::literal::Literal::Struct(_) => NumericKind::Float,
1391            },
1392            ScalarExpr::Column(fid) => arrays
1393                .get(fid)
1394                .map(|arr| arr.kind())
1395                .unwrap_or(NumericKind::Float),
1396            ScalarExpr::Binary { left, op, right } => {
1397                let left_kind = Self::infer_result_kind(left, arrays);
1398                let right_kind = Self::infer_result_kind(right, arrays);
1399                Self::binary_result_kind(*op, left_kind, right_kind)
1400            }
1401            ScalarExpr::Compare { .. } => NumericKind::Integer,
1402            ScalarExpr::Not(_) => NumericKind::Integer,
1403            ScalarExpr::IsNull { .. } => NumericKind::Integer,
1404            ScalarExpr::Aggregate(_) => NumericKind::Float,
1405            ScalarExpr::GetField { .. } => NumericKind::Float,
1406            ScalarExpr::Cast { expr, data_type } => {
1407                let target_kind = Self::kind_for_data_type(data_type);
1408                target_kind.unwrap_or_else(|| Self::infer_result_kind(expr, arrays))
1409            }
1410            ScalarExpr::Case {
1411                branches,
1412                else_expr,
1413                ..
1414            } => {
1415                let mut result_kind = NumericKind::Integer;
1416                for (_, then_expr) in branches {
1417                    if matches!(
1418                        Self::infer_result_kind(then_expr, arrays),
1419                        NumericKind::Float
1420                    ) {
1421                        result_kind = NumericKind::Float;
1422                        break;
1423                    }
1424                }
1425                if result_kind != NumericKind::Float
1426                    && let Some(inner) = else_expr.as_deref()
1427                    && matches!(Self::infer_result_kind(inner, arrays), NumericKind::Float)
1428                {
1429                    result_kind = NumericKind::Float;
1430                }
1431                result_kind
1432            }
1433            ScalarExpr::Coalesce(items) => {
1434                let mut result_kind = NumericKind::Integer;
1435                for item in items {
1436                    if matches!(Self::infer_result_kind(item, arrays), NumericKind::Float) {
1437                        result_kind = NumericKind::Float;
1438                        break;
1439                    }
1440                }
1441                result_kind
1442            }
1443            ScalarExpr::Random => NumericKind::Float,
1444            ScalarExpr::ScalarSubquery(_) => NumericKind::Float,
1445        }
1446    }
1447
1448    /// Infer the numeric kind of an expression using only the kinds of its referenced columns.
1449    pub fn infer_result_kind_from_types<F>(
1450        expr: &ScalarExpr<FieldId>,
1451        resolve_kind: &mut F,
1452    ) -> Option<NumericKind>
1453    where
1454        F: FnMut(FieldId) -> Option<NumericKind>,
1455    {
1456        match expr {
1457            ScalarExpr::Literal(_) => Self::literal_numeric_value(expr).map(|v| v.kind()),
1458            ScalarExpr::Column(fid) => resolve_kind(*fid),
1459            ScalarExpr::Binary { left, op, right } => {
1460                let left_kind = Self::infer_result_kind_from_types(left, resolve_kind)?;
1461                let right_kind = Self::infer_result_kind_from_types(right, resolve_kind)?;
1462                Some(Self::binary_result_kind(*op, left_kind, right_kind))
1463            }
1464            ScalarExpr::Compare { .. } => Some(NumericKind::Integer),
1465            ScalarExpr::Not(_) => Some(NumericKind::Integer),
1466            ScalarExpr::IsNull { .. } => Some(NumericKind::Integer),
1467            ScalarExpr::Aggregate(_) => Some(NumericKind::Float),
1468            ScalarExpr::GetField { .. } => None,
1469            ScalarExpr::Cast { expr, data_type } => {
1470                let target_kind = Self::kind_for_data_type(data_type);
1471                target_kind.or_else(|| Self::infer_result_kind_from_types(expr, resolve_kind))
1472            }
1473            ScalarExpr::Case {
1474                branches,
1475                else_expr,
1476                ..
1477            } => {
1478                let mut result_kind = NumericKind::Integer;
1479                for (_, then_expr) in branches {
1480                    let kind = Self::infer_result_kind_from_types(then_expr, resolve_kind)?;
1481                    if matches!(kind, NumericKind::Float) {
1482                        result_kind = NumericKind::Float;
1483                        break;
1484                    }
1485                }
1486                if result_kind != NumericKind::Float
1487                    && let Some(inner) = else_expr.as_deref()
1488                    && let Some(kind) = Self::infer_result_kind_from_types(inner, resolve_kind)
1489                    && matches!(kind, NumericKind::Float)
1490                {
1491                    result_kind = NumericKind::Float;
1492                }
1493                Some(result_kind)
1494            }
1495            ScalarExpr::Coalesce(items) => {
1496                let mut result_kind = NumericKind::Integer;
1497                for item in items {
1498                    let kind = Self::infer_result_kind_from_types(item, resolve_kind)?;
1499                    if matches!(kind, NumericKind::Float) {
1500                        result_kind = NumericKind::Float;
1501                        break;
1502                    }
1503                }
1504                Some(result_kind)
1505            }
1506            ScalarExpr::Random => Some(NumericKind::Float),
1507            ScalarExpr::ScalarSubquery(_) => Some(NumericKind::Float),
1508        }
1509    }
1510
1511    /// Map an Arrow `DataType` to the corresponding numeric kind when supported.
1512    pub fn kind_for_data_type(dtype: &DataType) -> Option<NumericKind> {
1513        match dtype {
1514            DataType::Int8
1515            | DataType::Int16
1516            | DataType::Int32
1517            | DataType::Int64
1518            | DataType::Boolean => Some(NumericKind::Integer),
1519            DataType::UInt8
1520            | DataType::UInt16
1521            | DataType::UInt32
1522            | DataType::UInt64
1523            | DataType::Float32
1524            | DataType::Float64
1525            | DataType::Null => Some(NumericKind::Float),
1526            _ => None,
1527        }
1528    }
1529
1530    fn binary_result_kind(
1531        op: BinaryOp,
1532        lhs_kind: NumericKind,
1533        rhs_kind: NumericKind,
1534    ) -> NumericKind {
1535        let lhs_value = match lhs_kind {
1536            NumericKind::Integer => NumericValue::Integer(1),
1537            NumericKind::Float => NumericValue::Float(1.0),
1538        };
1539        let rhs_value = match rhs_kind {
1540            NumericKind::Integer => NumericValue::Integer(1),
1541            NumericKind::Float => NumericValue::Float(1.0),
1542        };
1543
1544        Self::apply_binary_values(op, lhs_value, rhs_value)
1545            .unwrap_or(NumericValue::Float(0.0))
1546            .kind()
1547    }
1548
1549    /// Compare two numeric values using the provided operator.
1550    pub fn compare(op: CompareOp, lhs: NumericValue, rhs: NumericValue) -> bool {
1551        match (lhs, rhs) {
1552            (NumericValue::Integer(li), NumericValue::Integer(ri)) => match op {
1553                CompareOp::Eq => li == ri,
1554                CompareOp::NotEq => li != ri,
1555                CompareOp::Lt => li < ri,
1556                CompareOp::LtEq => li <= ri,
1557                CompareOp::Gt => li > ri,
1558                CompareOp::GtEq => li >= ri,
1559            },
1560            (lv, rv) => {
1561                let lf = lv.as_f64();
1562                let rf = rv.as_f64();
1563                match op {
1564                    CompareOp::Eq => lf == rf,
1565                    CompareOp::NotEq => lf != rf,
1566                    CompareOp::Lt => lf < rf,
1567                    CompareOp::LtEq => lf <= rf,
1568                    CompareOp::Gt => lf > rf,
1569                    CompareOp::GtEq => lf >= rf,
1570                }
1571            }
1572        }
1573    }
1574
1575    fn coerce_array(array: &ArrayRef) -> LlkvResult<NumericArray> {
1576        NumericArray::try_from_arrow(array)
1577    }
1578}
1579
1580#[cfg(test)]
1581mod tests {
1582    use super::*;
1583    use arrow::array::{Float64Array, Int64Array};
1584    use llkv_expr::Literal;
1585
1586    fn float_array(values: &[Option<f64>]) -> NumericArray {
1587        let array = Float64Array::from(values.to_vec());
1588        NumericArray::from_float(Arc::new(array))
1589    }
1590
1591    fn int_array(values: &[Option<i64>]) -> NumericArray {
1592        let array = Int64Array::from(values.to_vec());
1593        NumericArray::from_int(Arc::new(array))
1594    }
1595
1596    #[test]
1597    fn integer_addition_preserves_int_type() {
1598        const F1: FieldId = 30;
1599        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1600        arrays.insert(F1, int_array(&[Some(1), Some(-5), None, Some(42)]));
1601
1602        let expr = ScalarExpr::binary(
1603            ScalarExpr::column(F1),
1604            BinaryOp::Add,
1605            ScalarExpr::literal(3),
1606        );
1607
1608        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1609        let array = result
1610            .as_ref()
1611            .as_any()
1612            .downcast_ref::<Int64Array>()
1613            .expect("expected Int64Array");
1614
1615        assert_eq!(array.len(), 4);
1616        assert_eq!(array.value(0), 4);
1617        assert_eq!(array.value(1), -2);
1618        assert!(array.is_null(2));
1619        assert_eq!(array.value(3), 45);
1620    }
1621
1622    #[test]
1623    fn integer_division_matches_sqlite_semantics() {
1624        const F1: FieldId = 31;
1625        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1626        arrays.insert(F1, int_array(&[Some(5), Some(-7), Some(0), None]));
1627
1628        let expr = ScalarExpr::binary(
1629            ScalarExpr::column(F1),
1630            BinaryOp::Divide,
1631            ScalarExpr::literal(2),
1632        );
1633
1634        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1635        let array = result
1636            .as_ref()
1637            .as_any()
1638            .downcast_ref::<Int64Array>()
1639            .expect("expected Int64Array");
1640
1641        assert_eq!(array.len(), 4);
1642        assert_eq!(array.value(0), 2);
1643        assert_eq!(array.value(1), -3);
1644        assert_eq!(array.value(2), 0);
1645        assert!(array.is_null(3));
1646    }
1647
1648    #[test]
1649    fn integer_overflow_promotes_to_float_array() {
1650        const F1: FieldId = 32;
1651        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1652        arrays.insert(F1, int_array(&[Some(i64::MAX), Some(10)]));
1653
1654        let expr = ScalarExpr::binary(
1655            ScalarExpr::column(F1),
1656            BinaryOp::Add,
1657            ScalarExpr::literal(1),
1658        );
1659
1660        let result = NumericKernels::evaluate_batch(&expr, 2, &arrays).unwrap();
1661        assert!(
1662            result
1663                .as_ref()
1664                .as_any()
1665                .downcast_ref::<Int64Array>()
1666                .is_none()
1667        );
1668
1669        let array = result
1670            .as_ref()
1671            .as_any()
1672            .downcast_ref::<Float64Array>()
1673            .expect("expected Float64Array after overflow");
1674
1675        assert_eq!(array.len(), 2);
1676        assert!(array.value(0).is_finite());
1677        assert_eq!(array.value(1), 11.0);
1678    }
1679
1680    #[test]
1681    fn vectorized_add_columns() {
1682        const F1: FieldId = 1;
1683        const F2: FieldId = 2;
1684        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1685        arrays.insert(F1, float_array(&[Some(1.0), Some(2.0), None, Some(-1.0)]));
1686        arrays.insert(
1687            F2,
1688            float_array(&[Some(5.0), Some(-1.0), Some(3.0), Some(4.0)]),
1689        );
1690
1691        let expr = ScalarExpr::binary(
1692            ScalarExpr::column(F1),
1693            BinaryOp::Add,
1694            ScalarExpr::column(F2),
1695        );
1696
1697        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1698        let result = result
1699            .as_ref()
1700            .as_any()
1701            .downcast_ref::<Float64Array>()
1702            .unwrap();
1703
1704        assert_eq!(result.len(), 4);
1705        assert_eq!(result.value(0), 6.0);
1706        assert_eq!(result.value(1), 1.0);
1707        assert!(result.is_null(2));
1708        assert_eq!(result.value(3), 3.0);
1709    }
1710
1711    #[test]
1712    fn coalesce_evaluation_in_comparison() {
1713        const A: FieldId = 401;
1714        const B: FieldId = 402;
1715        const C: FieldId = 403;
1716        const D: FieldId = 404;
1717        const E: FieldId = 405;
1718        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1719        arrays.insert(A, int_array(&[Some(1), None, None, None, None, None]));
1720        arrays.insert(B, int_array(&[Some(2), Some(2), None, None, None, None]));
1721        arrays.insert(C, int_array(&[Some(3), None, Some(3), None, None, None]));
1722        arrays.insert(D, int_array(&[Some(4), None, None, Some(4), None, None]));
1723        arrays.insert(E, int_array(&[Some(5), None, None, None, Some(5), None]));
1724
1725        let coalesce_expr = ScalarExpr::coalesce(vec![
1726            ScalarExpr::column(A),
1727            ScalarExpr::column(B),
1728            ScalarExpr::column(C),
1729            ScalarExpr::column(D),
1730            ScalarExpr::column(E),
1731        ]);
1732
1733        let expected_values = [Some(1), Some(2), Some(3), Some(4), Some(5), None];
1734        for (idx, expected) in expected_values.iter().enumerate() {
1735            let value = NumericKernels::evaluate_value(&coalesce_expr, idx, &arrays).unwrap();
1736            let actual = value.map(|num| match num {
1737                NumericValue::Integer(v) => v,
1738                NumericValue::Float(v) => v as i64,
1739            });
1740            assert_eq!(actual, *expected, "row {idx} did not match");
1741        }
1742
1743        let compare_expr =
1744            ScalarExpr::compare(coalesce_expr, CompareOp::NotEq, ScalarExpr::literal(0));
1745
1746        let expected_flags = [Some(1), Some(1), Some(1), Some(1), Some(1), None];
1747        for (idx, expected) in expected_flags.iter().enumerate() {
1748            let value = NumericKernels::evaluate_value(&compare_expr, idx, &arrays).unwrap();
1749            let actual = value.map(|num| match num {
1750                NumericValue::Integer(v) => v,
1751                NumericValue::Float(v) => v as i64,
1752            });
1753            assert_eq!(actual, *expected, "comparison row {idx} mismatch");
1754        }
1755    }
1756
1757    #[test]
1758    fn vectorized_multiply_literal() {
1759        const F1: FieldId = 10;
1760        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1761        arrays.insert(F1, float_array(&[Some(1.0), Some(-2.5), Some(0.0), None]));
1762
1763        let expr = ScalarExpr::binary(
1764            ScalarExpr::column(F1),
1765            BinaryOp::Multiply,
1766            ScalarExpr::literal(3),
1767        );
1768
1769        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1770        let result = result
1771            .as_ref()
1772            .as_any()
1773            .downcast_ref::<Float64Array>()
1774            .unwrap();
1775
1776        assert_eq!(result.len(), 4);
1777        assert_eq!(result.value(0), 3.0);
1778        assert_eq!(result.value(1), -7.5);
1779        assert_eq!(result.value(2), 0.0);
1780        assert!(result.is_null(3));
1781    }
1782
1783    #[test]
1784    fn vectorized_add_column_scalar_literal() {
1785        const F1: FieldId = 11;
1786        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1787        arrays.insert(F1, float_array(&[Some(2.0), None, Some(-5.5)]));
1788
1789        let expr = ScalarExpr::binary(
1790            ScalarExpr::column(F1),
1791            BinaryOp::Add,
1792            ScalarExpr::literal(4),
1793        );
1794
1795        let result = NumericKernels::evaluate_batch(&expr, 3, &arrays).unwrap();
1796        let result = result
1797            .as_ref()
1798            .as_any()
1799            .downcast_ref::<Float64Array>()
1800            .unwrap();
1801
1802        assert_eq!(result.len(), 3);
1803        assert_eq!(result.value(0), 6.0);
1804        assert!(result.is_null(1));
1805        assert!((result.value(2) - (-1.5)).abs() < f64::EPSILON);
1806    }
1807
1808    #[test]
1809    fn vectorized_literal_minus_column() {
1810        const F1: FieldId = 12;
1811        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1812        arrays.insert(F1, float_array(&[Some(3.0), Some(-2.0), None]));
1813
1814        let expr = ScalarExpr::binary(
1815            ScalarExpr::literal(10),
1816            BinaryOp::Subtract,
1817            ScalarExpr::column(F1),
1818        );
1819
1820        let result = NumericKernels::evaluate_batch(&expr, 3, &arrays).unwrap();
1821        let result = result
1822            .as_ref()
1823            .as_any()
1824            .downcast_ref::<Float64Array>()
1825            .unwrap();
1826
1827        assert_eq!(result.len(), 3);
1828        assert_eq!(result.value(0), 7.0);
1829        assert_eq!(result.value(1), 12.0);
1830        assert!(result.is_null(2));
1831    }
1832
1833    #[test]
1834    fn zero_minus_cast_null_remains_null() {
1835        use arrow::array::Int64Array;
1836        use arrow::datatypes::DataType;
1837
1838        let expr = ScalarExpr::binary(
1839            ScalarExpr::literal(0),
1840            BinaryOp::Subtract,
1841            ScalarExpr::cast(ScalarExpr::literal(Literal::Null), DataType::Int64),
1842        );
1843
1844        let arrays = NumericArrayMap::default();
1845        let array = NumericKernels::evaluate_batch(&expr, 5, &arrays).unwrap();
1846        let typed = array
1847            .as_ref()
1848            .as_any()
1849            .downcast_ref::<Int64Array>()
1850            .expect("expected Int64Array result");
1851
1852        assert_eq!(typed.null_count(), typed.len());
1853        for idx in 0..typed.len() {
1854            assert!(typed.is_null(idx), "value at {idx} should be NULL");
1855        }
1856    }
1857
1858    #[test]
1859    fn vectorized_divide_by_zero_yields_null() {
1860        const NUM: FieldId = 20;
1861        const DEN: FieldId = 21;
1862        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1863        arrays.insert(
1864            NUM,
1865            float_array(&[Some(4.0), Some(9.0), Some(5.0), Some(-6.0)]),
1866        );
1867        arrays.insert(DEN, float_array(&[Some(2.0), Some(0.0), None, Some(-3.0)]));
1868
1869        let expr = ScalarExpr::binary(
1870            ScalarExpr::column(NUM),
1871            BinaryOp::Divide,
1872            ScalarExpr::column(DEN),
1873        );
1874
1875        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1876        let result = result
1877            .as_ref()
1878            .as_any()
1879            .downcast_ref::<Float64Array>()
1880            .unwrap();
1881
1882        assert_eq!(result.len(), 4);
1883        assert_eq!(result.value(0), 2.0);
1884        assert!(result.is_null(1));
1885        assert!(result.is_null(2));
1886        assert_eq!(result.value(3), 2.0);
1887    }
1888
1889    #[test]
1890    fn vectorized_divide_by_zero_literal_rhs_yields_nulls() {
1891        const F1: FieldId = 22;
1892        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1893        arrays.insert(F1, float_array(&[Some(1.0), Some(-4.0), None]));
1894
1895        let expr = ScalarExpr::binary(
1896            ScalarExpr::column(F1),
1897            BinaryOp::Divide,
1898            ScalarExpr::literal(0),
1899        );
1900
1901        let result = NumericKernels::evaluate_batch(&expr, 3, &arrays).unwrap();
1902        let result = result
1903            .as_ref()
1904            .as_any()
1905            .downcast_ref::<Float64Array>()
1906            .unwrap();
1907
1908        assert_eq!(result.len(), 3);
1909        assert!(result.is_null(0));
1910        assert!(result.is_null(1));
1911        assert!(result.is_null(2));
1912    }
1913
1914    #[test]
1915    fn vectorized_modulo_literals() {
1916        let expr = ScalarExpr::binary(
1917            ScalarExpr::literal(13),
1918            BinaryOp::Modulo,
1919            ScalarExpr::literal(5),
1920        );
1921
1922        let simplified = NumericKernels::simplify(&expr);
1923        let ScalarExpr::Literal(Literal::Integer(value)) = simplified else {
1924            panic!("expected literal result");
1925        };
1926        assert_eq!(value, 3);
1927    }
1928
1929    #[test]
1930    fn vectorized_modulo_column_rhs_zero_yields_null() {
1931        const NUM: FieldId = 23;
1932        const DEN: FieldId = 24;
1933        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1934        arrays.insert(NUM, float_array(&[Some(4.0), Some(7.0), None, Some(-6.0)]));
1935        arrays.insert(
1936            DEN,
1937            float_array(&[Some(2.0), Some(0.0), Some(3.0), Some(-4.0)]),
1938        );
1939
1940        let expr = ScalarExpr::binary(
1941            ScalarExpr::column(NUM),
1942            BinaryOp::Modulo,
1943            ScalarExpr::column(DEN),
1944        );
1945
1946        let result = NumericKernels::evaluate_batch(&expr, 4, &arrays).unwrap();
1947        let result = result
1948            .as_ref()
1949            .as_any()
1950            .downcast_ref::<Float64Array>()
1951            .unwrap();
1952
1953        assert_eq!(result.len(), 4);
1954        assert_eq!(result.value(0), 0.0);
1955        assert!(result.is_null(1));
1956        assert!(result.is_null(2));
1957        assert_eq!(result.value(3), -6.0 % -4.0);
1958    }
1959
1960    #[test]
1961    fn evaluate_simple_case_expression() {
1962        const F1: FieldId = 200;
1963        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1964        arrays.insert(F1, int_array(&[Some(1), Some(2), None]));
1965
1966        let expr = ScalarExpr::case(
1967            Some(ScalarExpr::column(F1)),
1968            vec![(ScalarExpr::literal(1), ScalarExpr::literal(10))],
1969            Some(ScalarExpr::literal(20)),
1970        );
1971
1972        let result = NumericKernels::evaluate_batch(&expr, 3, &arrays).unwrap();
1973        let array = result
1974            .as_ref()
1975            .as_any()
1976            .downcast_ref::<Int64Array>()
1977            .expect("expected Int64Array");
1978
1979        assert_eq!(array.len(), 3);
1980        assert_eq!(array.value(0), 10);
1981        assert_eq!(array.value(1), 20);
1982        assert_eq!(array.value(2), 20);
1983    }
1984
1985    #[test]
1986    fn evaluate_searched_case_expression() {
1987        const F1: FieldId = 201;
1988        let mut arrays: NumericArrayMap = NumericArrayMap::default();
1989        arrays.insert(F1, int_array(&[Some(2), Some(5), None]));
1990
1991        let condition = ScalarExpr::compare(
1992            ScalarExpr::column(F1),
1993            CompareOp::Gt,
1994            ScalarExpr::literal(3),
1995        );
1996        let expr = ScalarExpr::case(
1997            None,
1998            vec![(condition, ScalarExpr::column(F1))],
1999            Some(ScalarExpr::literal(0)),
2000        );
2001
2002        let result = NumericKernels::evaluate_batch(&expr, 3, &arrays).unwrap();
2003        let array = result
2004            .as_ref()
2005            .as_any()
2006            .downcast_ref::<Int64Array>()
2007            .expect("expected Int64Array");
2008
2009        assert_eq!(array.len(), 3);
2010        assert_eq!(array.value(0), 0);
2011        assert_eq!(array.value(1), 5);
2012        assert_eq!(array.value(2), 0);
2013    }
2014
2015    #[test]
2016    fn passthrough_detects_identity_ops() {
2017        const F1: FieldId = 99;
2018
2019        let expr_add = ScalarExpr::binary(
2020            ScalarExpr::column(F1),
2021            BinaryOp::Add,
2022            ScalarExpr::literal(0),
2023        );
2024        assert_eq!(NumericKernels::passthrough_column(&expr_add), Some(F1));
2025
2026        let expr_sub = ScalarExpr::binary(
2027            ScalarExpr::column(F1),
2028            BinaryOp::Subtract,
2029            ScalarExpr::literal(0),
2030        );
2031        assert_eq!(NumericKernels::passthrough_column(&expr_sub), Some(F1));
2032
2033        let expr_mul = ScalarExpr::binary(
2034            ScalarExpr::column(F1),
2035            BinaryOp::Multiply,
2036            ScalarExpr::literal(1),
2037        );
2038        assert_eq!(NumericKernels::passthrough_column(&expr_mul), Some(F1));
2039
2040        let expr_div = ScalarExpr::binary(
2041            ScalarExpr::column(F1),
2042            BinaryOp::Divide,
2043            ScalarExpr::literal(1),
2044        );
2045        assert_eq!(NumericKernels::passthrough_column(&expr_div), Some(F1));
2046
2047        // Non-identity literal should not passthrough.
2048        let expr_add_two = ScalarExpr::binary(
2049            ScalarExpr::column(F1),
2050            BinaryOp::Add,
2051            ScalarExpr::literal(2),
2052        );
2053        assert_eq!(NumericKernels::passthrough_column(&expr_add_two), None);
2054    }
2055}