vortex_array/scalar_fn/fns/binary/compare/
mod.rs1use std::cmp::Ordering;
15
16use vortex_error::VortexResult;
17use vortex_error::vortex_bail;
18use vortex_error::vortex_err;
19
20use crate::ArrayRef;
21use crate::Canonical;
22use crate::ExecutionCtx;
23use crate::IntoArray;
24use crate::array::ArrayView;
25use crate::array::VTable;
26use crate::arrays::Constant;
27use crate::arrays::ConstantArray;
28use crate::arrays::ExtensionArray;
29use crate::arrays::ScalarFn;
30use crate::arrays::extension::ExtensionArrayExt;
31use crate::arrays::scalar_fn::ExactScalarFn;
32use crate::arrays::scalar_fn::ScalarFnArrayExt;
33use crate::arrays::scalar_fn::ScalarFnArrayView;
34use crate::dtype::DType;
35use crate::dtype::Nullability;
36use crate::kernel::ExecuteParentKernel;
37use crate::scalar::Scalar;
38use crate::scalar_fn::fns::binary::Binary;
39use crate::scalar_fn::fns::operators::CompareOperator;
40use crate::validity::Validity;
41
42mod boolean;
43mod bytes;
44mod decimal;
45mod nested;
46pub(crate) use nested::build_comparator as build_row_comparator;
47mod primitive;
48#[cfg(test)]
49mod tests;
50
51pub trait CompareKernel: VTable {
57 fn compare(
58 lhs: ArrayView<'_, Self>,
59 rhs: &ArrayRef,
60 operator: CompareOperator,
61 ctx: &mut ExecutionCtx,
62 ) -> VortexResult<Option<ArrayRef>>;
63}
64
65#[derive(Default, Debug)]
71pub struct CompareExecuteAdaptor<V>(pub V);
72
73impl<V> ExecuteParentKernel<V> for CompareExecuteAdaptor<V>
74where
75 V: CompareKernel,
76{
77 type Parent = ExactScalarFn<Binary>;
78
79 fn execute_parent(
80 &self,
81 array: ArrayView<'_, V>,
82 parent: ScalarFnArrayView<'_, Binary>,
83 child_idx: usize,
84 ctx: &mut ExecutionCtx,
85 ) -> VortexResult<Option<ArrayRef>> {
86 let Some(cmp_op) = parent.options.to_compare_operator() else {
89 return Ok(None);
90 };
91
92 let Some(scalar_fn_array) = parent.as_opt::<ScalarFn>() else {
94 return Ok(None);
95 };
96 let (cmp_op, other) = match child_idx {
99 0 => (cmp_op, scalar_fn_array.get_child(1)),
100 1 => (cmp_op.swap(), scalar_fn_array.get_child(0)),
101 _ => return Ok(None),
102 };
103
104 let len = array.len();
105 let nullable = array.dtype().is_nullable() || other.dtype().is_nullable();
106
107 if len == 0 {
109 return Ok(Some(
110 Canonical::empty(&DType::Bool(nullable.into())).into_array(),
111 ));
112 }
113
114 if other.as_constant().is_some_and(|s| s.is_null()) {
116 return Ok(Some(
117 ConstantArray::new(Scalar::null(DType::Bool(nullable.into())), len).into_array(),
118 ));
119 }
120
121 V::compare(array, other, cmp_op, ctx)
122 }
123}
124
125pub(crate) fn execute_compare(
131 lhs: &ArrayRef,
132 rhs: &ArrayRef,
133 op: CompareOperator,
134 ctx: &mut ExecutionCtx,
135) -> VortexResult<ArrayRef> {
136 let nullable = lhs.dtype().is_nullable() || rhs.dtype().is_nullable();
137
138 if lhs.len() != rhs.len() {
139 vortex_bail!(
140 "compare operator requires equal lengths, got {} and {}",
141 lhs.len(),
142 rhs.len()
143 );
144 }
145
146 if lhs.is_empty() {
147 return Ok(Canonical::empty(&DType::Bool(nullable.into())).into_array());
148 }
149
150 let left_constant_null = lhs.as_constant().map(|l| l.is_null()).unwrap_or(false);
151 let right_constant_null = rhs.as_constant().map(|r| r.is_null()).unwrap_or(false);
152 if left_constant_null || right_constant_null {
153 return Ok(
154 ConstantArray::new(Scalar::null(DType::Bool(nullable.into())), lhs.len()).into_array(),
155 );
156 }
157
158 if let (Some(lhs_const), Some(rhs_const)) = (lhs.as_opt::<Constant>(), rhs.as_opt::<Constant>())
160 {
161 let result = scalar_cmp(lhs_const.scalar(), rhs_const.scalar(), op)?;
162 return Ok(ConstantArray::new(result, lhs.len()).into_array());
163 }
164
165 compare_arrays(lhs, rhs, op, ctx)
166}
167
168fn compare_arrays(
170 lhs: &ArrayRef,
171 rhs: &ArrayRef,
172 op: CompareOperator,
173 ctx: &mut ExecutionCtx,
174) -> VortexResult<ArrayRef> {
175 let nullability = Nullability::from(lhs.dtype().is_nullable() || rhs.dtype().is_nullable());
176
177 if lhs.dtype().is_extension() || rhs.dtype().is_extension() {
181 if lhs.dtype().is_extension()
182 && rhs.dtype().is_extension()
183 && !lhs.dtype().eq_ignore_nullability(rhs.dtype())
184 {
185 vortex_bail!(
186 "Cannot compare extension dtypes {} and {}",
187 lhs.dtype(),
188 rhs.dtype()
189 );
190 }
191 let lhs = extension_storage(lhs, ctx)?;
192 let rhs = extension_storage(rhs, ctx)?;
193 return compare_arrays(&lhs, &rhs, op, ctx);
194 }
195
196 if !lhs.dtype().eq_ignore_nullability(rhs.dtype()) {
197 vortex_bail!(
198 "Cannot compare different DTypes {} and {}",
199 lhs.dtype(),
200 rhs.dtype()
201 );
202 }
203
204 match lhs.dtype() {
205 DType::Null => Ok(ConstantArray::new(
207 Scalar::null(DType::Bool(Nullability::Nullable)),
208 lhs.len(),
209 )
210 .into_array()),
211 DType::Bool(_) => boolean::compare_bool(lhs, rhs, op, nullability, ctx),
212 DType::Primitive(..) => primitive::compare_primitive(lhs, rhs, op, nullability, ctx),
213 DType::Decimal(..) => decimal::compare_decimal(lhs, rhs, op, nullability, ctx),
214 DType::Utf8(_) | DType::Binary(_) => bytes::compare_bytes(lhs, rhs, op, nullability, ctx),
215 DType::Struct(..) | DType::List(..) | DType::FixedSizeList(..) | DType::Map(..) => {
216 nested::compare_nested(lhs, rhs, op, nullability, ctx)
217 }
218 DType::Union(..) | DType::Variant(_) | DType::Extension(_) => {
219 vortex_bail!("compare is not supported for dtype {}", lhs.dtype())
220 }
221 }
222}
223
224fn extension_storage(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
227 if !array.dtype().is_extension() {
228 return Ok(array.clone());
229 }
230 if let Some(constant) = array.as_opt::<Constant>() {
231 return Ok(ConstantArray::new(
232 constant.scalar().as_extension().to_storage_scalar(),
233 constant.len(),
234 )
235 .into_array());
236 }
237 let ext = array.clone().execute::<ExtensionArray>(ctx)?;
238 Ok(ext.storage_array().clone())
239}
240
241pub fn scalar_cmp(lhs: &Scalar, rhs: &Scalar, operator: CompareOperator) -> VortexResult<Scalar> {
242 if lhs.is_null() | rhs.is_null() {
243 return Ok(Scalar::null(DType::Bool(Nullability::Nullable)));
244 }
245
246 let nullability = lhs.dtype().nullability() | rhs.dtype().nullability();
247
248 let ordering = lhs.partial_cmp(rhs).ok_or_else(|| {
250 vortex_err!(
251 "Cannot compare scalars with incompatible types: {} and {}",
252 lhs.dtype(),
253 rhs.dtype()
254 )
255 })?;
256
257 let b = match operator {
258 CompareOperator::Eq => ordering.is_eq(),
259 CompareOperator::NotEq => ordering.is_ne(),
260 CompareOperator::Gt => ordering.is_gt(),
261 CompareOperator::Gte => ordering.is_ge(),
262 CompareOperator::Lt => ordering.is_lt(),
263 CompareOperator::Lte => ordering.is_le(),
264 };
265
266 Ok(Scalar::bool(b, nullability))
267}
268
269#[inline]
271pub(super) fn ordering_predicate(op: CompareOperator) -> fn(Ordering) -> bool {
272 match op {
273 CompareOperator::Eq => Ordering::is_eq,
274 CompareOperator::NotEq => Ordering::is_ne,
275 CompareOperator::Gt => Ordering::is_gt,
276 CompareOperator::Gte => Ordering::is_ge,
277 CompareOperator::Lt => Ordering::is_lt,
278 CompareOperator::Lte => Ordering::is_le,
279 }
280}
281
282pub(super) fn compare_validity(
285 lhs: Validity,
286 rhs: Validity,
287 nullability: Nullability,
288) -> VortexResult<Validity> {
289 Ok(lhs.and(rhs)?.union_nullability(nullability))
290}