1use 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
20pub type NumericArrayMap = FxHashMap<FieldId, NumericArray>;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum NumericKind {
26 Integer,
27 Float,
28}
29
30#[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#[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 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 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
298enum 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#[derive(Clone, Copy, Debug)]
329pub struct AffineExpr {
330 pub field: FieldId,
331 pub scale: f64,
332 pub offset: f64,
333}
334
335#[derive(Clone, Copy, Debug)]
337struct AffineState {
338 field: Option<FieldId>,
339 scale: f64,
340 offset: f64,
341}
342
343fn 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
360pub struct NumericKernels;
363
364impl NumericKernels {
365 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 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 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 }
433 ScalarExpr::ScalarSubquery(_) => {
434 }
436 }
437 }
438
439 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 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 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 #[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 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 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 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 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 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 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 #[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 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, ScalarExpr::GetField { .. } => None, 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 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 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 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 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 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}