From 8243861ba3d95cdd40593f86856afea8a0ec1a7e Mon Sep 17 00:00:00 2001 From: Jorge Leitao Date: Sun, 22 Aug 2021 23:52:56 +0100 Subject: [PATCH] Added `compare_scalar` (#317) --- benches/comparison_kernels.rs | 3 +- src/compute/comparison/boolean.rs | 28 ++-- src/compute/comparison/mod.rs | 221 +++++++++++++++++++++------- src/compute/comparison/primitive.rs | 39 +++-- src/compute/comparison/utf8.rs | 18 ++- 5 files changed, 231 insertions(+), 78 deletions(-) diff --git a/benches/comparison_kernels.rs b/benches/comparison_kernels.rs index b1643bb1aae..3d61a00f111 100644 --- a/benches/comparison_kernels.rs +++ b/benches/comparison_kernels.rs @@ -38,8 +38,7 @@ where criterion::black_box(arr_a), criterion::black_box(value_b), op, - ) - .unwrap(); + ); } fn add_benchmark(c: &mut Criterion) { diff --git a/src/compute/comparison/boolean.rs b/src/compute/comparison/boolean.rs index 91e691a0d75..48be76c2753 100644 --- a/src/compute/comparison/boolean.rs +++ b/src/compute/comparison/boolean.rs @@ -17,6 +17,7 @@ use crate::array::*; use crate::bitmap::Bitmap; +use crate::scalar::{BooleanScalar, Scalar}; use crate::{ bitmap::MutableBitmap, error::{ArrowError, Result}, @@ -56,14 +57,14 @@ where /// Evaluate `op(left, right)` for [`BooleanArray`] and scalar using /// a specified comparison function. -pub fn compare_op_scalar(lhs: &BooleanArray, rhs: bool, op: F) -> Result +pub fn compare_op_scalar(lhs: &BooleanArray, rhs: bool, op: F) -> BooleanArray where F: Fn(bool, bool) -> bool, { let lhs_iter = lhs.values().iter(); let values = Bitmap::from_trusted_len_iter(lhs_iter.map(|x| op(x, rhs))); - Ok(BooleanArray::from_data(values, lhs.validity().clone())) + BooleanArray::from_data(values, lhs.validity().clone()) } /// Perform `lhs == rhs` operation on two arrays. @@ -72,7 +73,7 @@ pub fn eq(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { } /// Perform `left == right` operation on an array and a scalar value. -pub fn eq_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn eq_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| a == b) } @@ -82,7 +83,7 @@ pub fn neq(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { } /// Perform `left != right` operation on an array and a scalar value. -pub fn neq_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn neq_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| a != b) } @@ -92,7 +93,7 @@ pub fn lt(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { } /// Perform `left < right` operation on an array and a scalar value. -pub fn lt_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn lt_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| !a & b) } @@ -103,7 +104,7 @@ pub fn lt_eq(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { /// Perform `left <= right` operation on an array and a scalar value. /// Null values are less than non-null values. -pub fn lt_eq_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn lt_eq_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| a <= b) } @@ -115,7 +116,7 @@ pub fn gt(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { /// Perform `left > right` operation on an array and a scalar value. /// Non-null values are greater than null values. -pub fn gt_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn gt_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| a & !b) } @@ -127,7 +128,7 @@ pub fn gt_eq(lhs: &BooleanArray, rhs: &BooleanArray) -> Result { /// Perform `left >= right` operation on an array and a scalar value. /// Non-null values are greater than null values. -pub fn gt_eq_scalar(lhs: &BooleanArray, rhs: bool) -> Result { +pub fn gt_eq_scalar(lhs: &BooleanArray, rhs: bool) -> BooleanArray { compare_op_scalar(lhs, rhs, |a, b| a >= b) } @@ -142,7 +143,14 @@ pub fn compare(lhs: &BooleanArray, rhs: &BooleanArray, op: Operator) -> Result Result { +pub fn compare_scalar(lhs: &BooleanArray, rhs: &BooleanScalar, op: Operator) -> BooleanArray { + if !rhs.is_valid() { + return BooleanArray::new_null(lhs.len()); + } + compare_scalar_non_null(lhs, rhs.value(), op) +} + +pub fn compare_scalar_non_null(lhs: &BooleanArray, rhs: bool, op: Operator) -> BooleanArray { match op { Operator::Eq => eq_scalar(lhs, rhs), Operator::Neq => neq_scalar(lhs, rhs), @@ -180,7 +188,7 @@ mod tests { macro_rules! cmp_bool_scalar { ($KERNEL:ident, $A_VEC:expr, $B:literal, $EXPECTED:expr) => { let a = BooleanArray::from_slice($A_VEC); - let c = $KERNEL(&a, $B).unwrap(); + let c = $KERNEL(&a, $B); assert_eq!(BooleanArray::from_slice($EXPECTED), c); }; } diff --git a/src/compute/comparison/mod.rs b/src/compute/comparison/mod.rs index 69d7ed07b1b..ceaf548ca68 100644 --- a/src/compute/comparison/mod.rs +++ b/src/compute/comparison/mod.rs @@ -15,16 +15,12 @@ // specific language governing permissions and limitations // under the License. -//! Defines basic comparison kernels for [`PrimitiveArray`]s. -//! -//! These kernels can leverage SIMD if available on your system. Currently no runtime -//! detection is provided, you should enable the specific SIMD intrinsics using -//! `RUSTFLAGS="-C target-feature=+avx2"` for example. See the documentation -//! [here](https://doc.rust-lang.org/stable/core/arch/) for more information. +//! Defines basic comparison kernels. use crate::array::*; use crate::datatypes::{DataType, IntervalUnit}; use crate::error::{ArrowError, Result}; +use crate::scalar::Scalar; mod boolean; mod primitive; @@ -43,10 +39,16 @@ pub enum Operator { Neq, } +/// Compares each slot of `lhs` against each slot of `rhs`. +/// # Error +/// Errors iff: +/// * `lhs.data_type() != rhs.data_type()` or +/// * `lhs.len() != rhs.len()` or +/// * the datatype is not supported (use [`can_compare`] to tell whether it is supported) pub fn compare(lhs: &dyn Array, rhs: &dyn Array, operator: Operator) -> Result { let data_type = lhs.data_type(); if data_type != rhs.data_type() { - return Err(ArrowError::NotYetImplemented( + return Err(ArrowError::InvalidArgumentError( "Comparison is only supported for arrays of the same logical type".to_string(), )); } @@ -57,77 +59,77 @@ pub fn compare(lhs: &dyn Array, rhs: &dyn Array, operator: Operator) -> Result { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Int16 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Int32 | DataType::Date32 | DataType::Time32(_) | DataType::Interval(IntervalUnit::YearMonth) => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Int64 | DataType::Timestamp(_, None) | DataType::Date64 | DataType::Time64(_) | DataType::Duration(_) => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::UInt8 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::UInt16 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::UInt32 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::UInt64 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Float16 => unreachable!(), DataType::Float32 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Float64 => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } DataType::Utf8 => { - let lhs = lhs.as_any().downcast_ref::>().unwrap(); - let rhs = rhs.as_any().downcast_ref::>().unwrap(); - utf8::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + utf8::compare::(lhs, rhs, operator) } DataType::LargeUtf8 => { - let lhs = lhs.as_any().downcast_ref::>().unwrap(); - let rhs = rhs.as_any().downcast_ref::>().unwrap(); - utf8::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + utf8::compare::(lhs, rhs, operator) } DataType::Decimal(_, _) => { - let lhs = lhs.as_any().downcast_ref::().unwrap(); - let rhs = rhs.as_any().downcast_ref::().unwrap(); - primitive::compare(lhs, rhs, operator) + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare::(lhs, rhs, operator) } _ => Err(ArrowError::NotYetImplemented(format!( "Comparison between {:?} is not supported", @@ -136,10 +138,114 @@ pub fn compare(lhs: &dyn Array, rhs: &dyn Array, operator: Operator) -> Result Result { + let data_type = lhs.data_type(); + if data_type != rhs.data_type() { + return Err(ArrowError::InvalidArgumentError( + "Comparison is only supported for the same logical type".to_string(), + )); + } + Ok(match data_type { + DataType::Boolean => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + boolean::compare_scalar(lhs, rhs, operator) + } + DataType::Int8 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Int16 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Int32 + | DataType::Date32 + | DataType::Time32(_) + | DataType::Interval(IntervalUnit::YearMonth) => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Int64 + | DataType::Timestamp(_, None) + | DataType::Date64 + | DataType::Time64(_) + | DataType::Duration(_) => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::UInt8 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::UInt16 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::UInt32 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::UInt64 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Float16 => unreachable!(), + DataType::Float32 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Float64 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Decimal(_, _) => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + primitive::compare_scalar::(lhs, rhs, operator) + } + DataType::Utf8 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + utf8::compare_scalar::(lhs, rhs, operator) + } + DataType::LargeUtf8 => { + let lhs = lhs.as_any().downcast_ref().unwrap(); + let rhs = rhs.as_any().downcast_ref().unwrap(); + utf8::compare_scalar::(lhs, rhs, operator) + } + _ => { + return Err(ArrowError::NotYetImplemented(format!( + "Comparison between {:?} is not supported", + data_type + ))) + } + }) +} + +pub use boolean::compare_scalar_non_null as boolean_compare_scalar; +pub use primitive::compare_scalar_non_null as primitive_compare_scalar; pub(crate) use primitive::compare_values_op as primitive_compare_values_op; -pub use utf8::compare_scalar as utf8_compare_scalar; +pub use utf8::compare_scalar_non_null as utf8_compare_scalar; /// Checks if an array of type `datatype` can be compared with another array of /// the same type. @@ -184,6 +290,8 @@ pub fn can_compare(data_type: &DataType) -> bool { #[cfg(test)] mod tests { + use crate::scalar::new_scalar; + use super::*; #[test] @@ -225,7 +333,8 @@ mod tests { Duration(TimeUnit::Nanosecond), ]; - datatypes.into_iter().for_each(|d1| { + // array <> array + datatypes.clone().into_iter().for_each(|d1| { let array = new_null_array(d1.clone(), 10); let op = Operator::Eq; if can_compare(&d1) { @@ -234,5 +343,17 @@ mod tests { assert!(compare(array.as_ref(), array.as_ref(), op).is_err()); } }); + + // array <> scalar + datatypes.into_iter().for_each(|d1| { + let array = new_null_array(d1.clone(), 10); + let scalar = new_scalar(array.as_ref(), 0); + let op = Operator::Eq; + if can_compare(&d1) { + assert!(compare_scalar(array.as_ref(), scalar.as_ref(), op).is_ok()); + } else { + assert!(compare_scalar(array.as_ref(), scalar.as_ref(), op).is_err()); + } + }); } } diff --git a/src/compute/comparison/primitive.rs b/src/compute/comparison/primitive.rs index 04dc5aa078b..65f752426f0 100644 --- a/src/compute/comparison/primitive.rs +++ b/src/compute/comparison/primitive.rs @@ -16,6 +16,7 @@ // under the License. use crate::bitmap::Bitmap; +use crate::scalar::{PrimitiveScalar, Scalar}; use crate::{array::*, types::NativeType}; use crate::{ bitmap::MutableBitmap, @@ -76,7 +77,7 @@ where /// Evaluate `op(left, right)` for [`PrimitiveArray`] and scalar using /// a specified comparison function. -pub fn compare_op_scalar(lhs: &PrimitiveArray, rhs: T, op: F) -> Result +pub fn compare_op_scalar(lhs: &PrimitiveArray, rhs: T, op: F) -> BooleanArray where T: NativeType + Simd8, F: Fn(T::Simd, T::Simd) -> u8, @@ -99,10 +100,7 @@ where values.push(op(lhs, rhs)) }; - Ok(BooleanArray::from_data( - Bitmap::from_u8_buffer(values, lhs.len()), - validity, - )) + BooleanArray::from_data(Bitmap::from_u8_buffer(values, lhs.len()), validity) } /// Perform `lhs == rhs` operation on two arrays. @@ -114,7 +112,7 @@ where } /// Perform `left == right` operation on an array and a scalar value. -pub fn eq_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn eq_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -130,7 +128,7 @@ where } /// Perform `left != right` operation on an array and a scalar value. -pub fn neq_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn neq_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -146,7 +144,7 @@ where } /// Perform `left < right` operation on an array and a scalar value. -pub fn lt_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn lt_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -163,7 +161,7 @@ where /// Perform `left <= right` operation on an array and a scalar value. /// Null values are less than non-null values. -pub fn lt_eq_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn lt_eq_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -181,7 +179,7 @@ where /// Perform `left > right` operation on an array and a scalar value. /// Non-null values are greater than null values. -pub fn gt_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn gt_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -199,7 +197,7 @@ where /// Perform `left >= right` operation on an array and a scalar value. /// Non-null values are greater than null values. -pub fn gt_eq_scalar(lhs: &PrimitiveArray, rhs: T) -> Result +pub fn gt_eq_scalar(lhs: &PrimitiveArray, rhs: T) -> BooleanArray where T: NativeType + Simd8, { @@ -222,10 +220,21 @@ pub fn compare( } pub fn compare_scalar( + lhs: &PrimitiveArray, + rhs: &PrimitiveScalar, + op: Operator, +) -> BooleanArray { + if !rhs.is_valid() { + return BooleanArray::new_null(lhs.len()); + } + compare_scalar_non_null(lhs, rhs.value(), op) +} + +pub fn compare_scalar_non_null( lhs: &PrimitiveArray, rhs: T, op: Operator, -) -> Result { +) -> BooleanArray { match op { Operator::Eq => eq_scalar(lhs, rhs), Operator::Neq => neq_scalar(lhs, rhs), @@ -271,7 +280,7 @@ mod tests { macro_rules! cmp_i64_scalar_options { ($KERNEL:ident, $A_VEC:expr, $B:literal, $EXPECTED:expr) => { let a = Int64Array::from($A_VEC); - let c = $KERNEL(&a, $B).unwrap(); + let c = $KERNEL(&a, $B); assert_eq!(BooleanArray::from($EXPECTED), c); }; } @@ -279,7 +288,7 @@ mod tests { macro_rules! cmp_i64_scalar { ($KERNEL:ident, $A_VEC:expr, $B:literal, $EXPECTED:expr) => { let a = Int64Array::from_slice($A_VEC); - let c = $KERNEL(&a, $B).unwrap(); + let c = $KERNEL(&a, $B); assert_eq!(BooleanArray::from_slice($EXPECTED), c); }; } @@ -551,7 +560,7 @@ mod tests { fn test_primitive_array_compare_scalar_slice() { let a = (0..100).map(Some).collect::>(); let a = a.slice(50, 50); - let actual = lt_scalar(&a, 200).unwrap(); + let actual = lt_scalar(&a, 200); let expected: BooleanArray = (0..50).map(|_| Some(true)).collect(); assert_eq!(expected, actual); } diff --git a/src/compute/comparison/utf8.rs b/src/compute/comparison/utf8.rs index c9a19a3a32c..4f12f2059c1 100644 --- a/src/compute/comparison/utf8.rs +++ b/src/compute/comparison/utf8.rs @@ -16,6 +16,7 @@ // under the License. use crate::error::{ArrowError, Result}; +use crate::scalar::{Scalar, Utf8Scalar}; use crate::{array::*, bitmap::Bitmap}; use super::{super::utils::combine_validities, Operator}; @@ -140,7 +141,22 @@ pub fn compare( } } -pub fn compare_scalar(lhs: &Utf8Array, rhs: &str, op: Operator) -> BooleanArray { +pub fn compare_scalar( + lhs: &Utf8Array, + rhs: &Utf8Scalar, + op: Operator, +) -> BooleanArray { + if !rhs.is_valid() { + return BooleanArray::new_null(lhs.len()); + } + compare_scalar_non_null(lhs, rhs.value(), op) +} + +pub fn compare_scalar_non_null( + lhs: &Utf8Array, + rhs: &str, + op: Operator, +) -> BooleanArray { match op { Operator::Eq => eq_scalar(lhs, rhs), Operator::Neq => neq_scalar(lhs, rhs),