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