vortex_array/scalar_fn/fns/binary/compare/
mod.rs1use std::cmp::Ordering;
15
16use vortex_buffer::BitBuffer;
17use vortex_buffer::BufferMut;
18use vortex_compute::lane_kernels::IndexedSourceExt;
19use vortex_compute::lane_kernels::LaneZip;
20use vortex_error::VortexResult;
21use vortex_error::vortex_bail;
22use vortex_error::vortex_err;
23
24use crate::ArrayRef;
25use crate::Canonical;
26use crate::ExecutionCtx;
27use crate::IntoArray;
28use crate::array::ArrayView;
29use crate::array::VTable;
30use crate::arrays::Constant;
31use crate::arrays::ConstantArray;
32use crate::arrays::ExtensionArray;
33use crate::arrays::ScalarFn;
34use crate::arrays::extension::ExtensionArrayExt;
35use crate::arrays::scalar_fn::ExactScalarFn;
36use crate::arrays::scalar_fn::ScalarFnArrayExt;
37use crate::arrays::scalar_fn::ScalarFnArrayView;
38use crate::dtype::DType;
39use crate::dtype::Nullability;
40use crate::kernel::ExecuteParentKernel;
41use crate::scalar::Scalar;
42use crate::scalar_fn::fns::binary::Binary;
43use crate::scalar_fn::fns::operators::CompareOperator;
44use crate::validity::Validity;
45
46mod boolean;
47mod bytes;
48mod decimal;
49mod nested;
50mod primitive;
51#[cfg(test)]
52mod tests;
53
54pub trait CompareKernel: VTable {
60 fn compare(
61 lhs: ArrayView<'_, Self>,
62 rhs: &ArrayRef,
63 operator: CompareOperator,
64 ctx: &mut ExecutionCtx,
65 ) -> VortexResult<Option<ArrayRef>>;
66}
67
68#[derive(Default, Debug)]
74pub struct CompareExecuteAdaptor<V>(pub V);
75
76impl<V> ExecuteParentKernel<V> for CompareExecuteAdaptor<V>
77where
78 V: CompareKernel,
79{
80 type Parent = ExactScalarFn<Binary>;
81
82 fn execute_parent(
83 &self,
84 array: ArrayView<'_, V>,
85 parent: ScalarFnArrayView<'_, Binary>,
86 child_idx: usize,
87 ctx: &mut ExecutionCtx,
88 ) -> VortexResult<Option<ArrayRef>> {
89 let Ok(cmp_op) = CompareOperator::try_from(*parent.options) else {
91 return Ok(None);
92 };
93
94 let Some(scalar_fn_array) = parent.as_opt::<ScalarFn>() else {
96 return Ok(None);
97 };
98 let (cmp_op, other) = match child_idx {
101 0 => (cmp_op, scalar_fn_array.get_child(1)),
102 1 => (cmp_op.swap(), scalar_fn_array.get_child(0)),
103 _ => return Ok(None),
104 };
105
106 let len = array.len();
107 let nullable = array.dtype().is_nullable() || other.dtype().is_nullable();
108
109 if len == 0 {
111 return Ok(Some(
112 Canonical::empty(&DType::Bool(nullable.into())).into_array(),
113 ));
114 }
115
116 if other.as_constant().is_some_and(|s| s.is_null()) {
118 return Ok(Some(
119 ConstantArray::new(Scalar::null(DType::Bool(nullable.into())), len).into_array(),
120 ));
121 }
122
123 V::compare(array, other, cmp_op, ctx)
124 }
125}
126
127pub(crate) fn execute_compare(
133 lhs: &ArrayRef,
134 rhs: &ArrayRef,
135 op: CompareOperator,
136 ctx: &mut ExecutionCtx,
137) -> VortexResult<ArrayRef> {
138 let nullable = lhs.dtype().is_nullable() || rhs.dtype().is_nullable();
139
140 if lhs.len() != rhs.len() {
141 vortex_bail!(
142 "compare operator requires equal lengths, got {} and {}",
143 lhs.len(),
144 rhs.len()
145 );
146 }
147
148 if lhs.is_empty() {
149 return Ok(Canonical::empty(&DType::Bool(nullable.into())).into_array());
150 }
151
152 let left_constant_null = lhs.as_constant().map(|l| l.is_null()).unwrap_or(false);
153 let right_constant_null = rhs.as_constant().map(|r| r.is_null()).unwrap_or(false);
154 if left_constant_null || right_constant_null {
155 return Ok(
156 ConstantArray::new(Scalar::null(DType::Bool(nullable.into())), lhs.len()).into_array(),
157 );
158 }
159
160 if let (Some(lhs_const), Some(rhs_const)) = (lhs.as_opt::<Constant>(), rhs.as_opt::<Constant>())
162 {
163 let result = scalar_cmp(lhs_const.scalar(), rhs_const.scalar(), op)?;
164 return Ok(ConstantArray::new(result, lhs.len()).into_array());
165 }
166
167 compare_arrays(lhs, rhs, op, ctx)
168}
169
170fn compare_arrays(
172 lhs: &ArrayRef,
173 rhs: &ArrayRef,
174 op: CompareOperator,
175 ctx: &mut ExecutionCtx,
176) -> VortexResult<ArrayRef> {
177 let nullability = Nullability::from(lhs.dtype().is_nullable() || rhs.dtype().is_nullable());
178
179 if lhs.dtype().is_extension() || rhs.dtype().is_extension() {
183 if lhs.dtype().is_extension()
184 && rhs.dtype().is_extension()
185 && !lhs.dtype().eq_ignore_nullability(rhs.dtype())
186 {
187 vortex_bail!(
188 "Cannot compare extension dtypes {} and {}",
189 lhs.dtype(),
190 rhs.dtype()
191 );
192 }
193 let lhs = extension_storage(lhs, ctx)?;
194 let rhs = extension_storage(rhs, ctx)?;
195 return compare_arrays(&lhs, &rhs, op, ctx);
196 }
197
198 if !lhs.dtype().eq_ignore_nullability(rhs.dtype()) {
199 vortex_bail!(
200 "Cannot compare different DTypes {} and {}",
201 lhs.dtype(),
202 rhs.dtype()
203 );
204 }
205
206 match lhs.dtype() {
207 DType::Null => Ok(ConstantArray::new(
209 Scalar::null(DType::Bool(Nullability::Nullable)),
210 lhs.len(),
211 )
212 .into_array()),
213 DType::Bool(_) => boolean::compare_bool(lhs, rhs, op, nullability, ctx),
214 DType::Primitive(..) => primitive::compare_primitive(lhs, rhs, op, nullability, ctx),
215 DType::Decimal(..) => decimal::compare_decimal(lhs, rhs, op, nullability, ctx),
216 DType::Utf8(_) | DType::Binary(_) => bytes::compare_bytes(lhs, rhs, op, nullability, ctx),
217 DType::Struct(..) | DType::List(..) | DType::FixedSizeList(..) => {
218 nested::compare_nested(lhs, rhs, op, nullability, ctx)
219 }
220 DType::Union(_) | DType::Variant(_) | DType::Extension(_) => {
221 vortex_bail!("compare is not supported for dtype {}", lhs.dtype())
222 }
223 }
224}
225
226fn extension_storage(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
229 if !array.dtype().is_extension() {
230 return Ok(array.clone());
231 }
232 if let Some(constant) = array.as_opt::<Constant>() {
233 return Ok(ConstantArray::new(
234 constant.scalar().as_extension().to_storage_scalar(),
235 constant.len(),
236 )
237 .into_array());
238 }
239 let ext = array.clone().execute::<ExtensionArray>(ctx)?;
240 Ok(ext.storage_array().clone())
241}
242
243pub fn scalar_cmp(lhs: &Scalar, rhs: &Scalar, operator: CompareOperator) -> VortexResult<Scalar> {
244 if lhs.is_null() | rhs.is_null() {
245 return Ok(Scalar::null(DType::Bool(Nullability::Nullable)));
246 }
247
248 let nullability = lhs.dtype().nullability() | rhs.dtype().nullability();
249
250 let ordering = lhs.partial_cmp(rhs).ok_or_else(|| {
252 vortex_err!(
253 "Cannot compare scalars with incompatible types: {} and {}",
254 lhs.dtype(),
255 rhs.dtype()
256 )
257 })?;
258
259 let b = match operator {
260 CompareOperator::Eq => ordering.is_eq(),
261 CompareOperator::NotEq => ordering.is_ne(),
262 CompareOperator::Gt => ordering.is_gt(),
263 CompareOperator::Gte => ordering.is_ge(),
264 CompareOperator::Lt => ordering.is_lt(),
265 CompareOperator::Lte => ordering.is_le(),
266 };
267
268 Ok(Scalar::bool(b, nullability))
269}
270
271#[inline]
273pub(super) fn ordering_predicate(op: CompareOperator) -> fn(Ordering) -> bool {
274 match op {
275 CompareOperator::Eq => Ordering::is_eq,
276 CompareOperator::NotEq => Ordering::is_ne,
277 CompareOperator::Gt => Ordering::is_gt,
278 CompareOperator::Gte => Ordering::is_ge,
279 CompareOperator::Lt => Ordering::is_lt,
280 CompareOperator::Lte => Ordering::is_le,
281 }
282}
283
284pub(super) fn bit_buffer_from_words(words: BufferMut<u64>, len: usize) -> BitBuffer {
286 debug_assert!(words.len() * 64 >= len);
287 let mut bytes = words.into_byte_buffer();
288 bytes.truncate(len.div_ceil(8));
289 BitBuffer::new(bytes.freeze(), len)
290}
291
292pub(super) fn collect_zip_bits<T: Copy>(
294 lhs: &[T],
295 rhs: &[T],
296 mut f: impl FnMut(T, T) -> bool,
297) -> BitBuffer {
298 let len = lhs.len();
299 let mut words = BufferMut::<u64>::zeroed(len.div_ceil(64));
300 LaneZip::new(lhs, rhs).map_bits_into(words.as_mut_slice(), |(a, b)| f(a, b));
301 bit_buffer_from_words(words, len)
302}
303
304pub(super) fn collect_bits<T: Copy>(values: &[T], f: impl FnMut(T) -> bool) -> BitBuffer {
306 let len = values.len();
307 let mut words = BufferMut::<u64>::zeroed(len.div_ceil(64));
308 values.map_bits_into(words.as_mut_slice(), f);
309 bit_buffer_from_words(words, len)
310}
311
312pub(super) fn compare_validity(
315 lhs: Validity,
316 rhs: Validity,
317 nullability: Nullability,
318) -> VortexResult<Validity> {
319 Ok(lhs.and(rhs)?.union_nullability(nullability))
320}