vortex_array/arrays/varbin/compute/
compare.rs1use std::cmp::Ordering;
5
6use vortex_buffer::BitBuffer;
7use vortex_error::VortexExpect as _;
8use vortex_error::VortexResult;
9use vortex_error::vortex_bail;
10
11use crate::ArrayRef;
12use crate::ExecutionCtx;
13use crate::IntoArray;
14use crate::array::ArrayView;
15use crate::arrays::BoolArray;
16use crate::arrays::PrimitiveArray;
17use crate::arrays::VarBin;
18use crate::arrays::varbin::VarBinArrayExt;
19use crate::dtype::DType;
20use crate::dtype::IntegerPType;
21use crate::match_each_integer_ptype;
22use crate::scalar_fn::fns::binary::CompareKernel;
23use crate::scalar_fn::fns::operators::CompareOperator;
24
25impl CompareKernel for VarBin {
28 fn compare(
29 lhs: ArrayView<'_, VarBin>,
30 rhs: &ArrayRef,
31 operator: CompareOperator,
32 ctx: &mut ExecutionCtx,
33 ) -> VortexResult<Option<ArrayRef>> {
34 let Some(rhs_const) = rhs.as_constant() else {
35 return Ok(None);
36 };
37
38 let len = lhs.len();
39
40 let rhs_bytes: &[u8] = match rhs_const.dtype() {
43 DType::Binary(_) => rhs_const
44 .as_binary()
45 .value()
46 .vortex_expect("RHS should not be null")
47 .as_slice(),
48 DType::Utf8(_) => rhs_const
49 .as_utf8()
50 .value()
51 .vortex_expect("RHS should not be null")
52 .as_str()
53 .as_bytes(),
54 _ => vortex_bail!("VarBinArray can only have type of Binary or Utf8"),
55 };
56
57 let buffer = if rhs_bytes.is_empty() {
58 match operator {
60 CompareOperator::Gte => BitBuffer::new_set(len), CompareOperator::Lt => BitBuffer::new_unset(len), CompareOperator::Eq | CompareOperator::Lte => {
63 let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
64 match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
65 compare_offsets_to_empty::<P>(lhs_offsets, true)
66 })
67 }
68 CompareOperator::NotEq | CompareOperator::Gt => {
69 let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
70 match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
71 compare_offsets_to_empty::<P>(lhs_offsets, false)
72 })
73 }
74 }
75 } else {
76 let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
77 match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
78 compare_bytes_to_constant(
79 lhs_offsets.as_slice::<P>(),
80 lhs.bytes().as_slice(),
81 rhs_bytes,
82 operator,
83 )
84 })
85 };
86
87 Ok(Some(
88 BoolArray::new(
89 buffer,
90 lhs.validity()?.union_nullability(rhs.dtype().nullability()),
91 )
92 .into_array(),
93 ))
94 }
95}
96
97fn compare_offsets_to_empty<P: IntegerPType>(offsets: PrimitiveArray, eq: bool) -> BitBuffer {
98 let fn_ = if eq { P::eq } else { P::ne };
99 let offsets = offsets.as_slice::<P>();
100 BitBuffer::collect_bool(offsets.len() - 1, |idx| {
101 let left = unsafe { offsets.get_unchecked(idx) };
102 let right = unsafe { offsets.get_unchecked(idx + 1) };
103 fn_(left, right)
104 })
105}
106
107fn compare_bytes_to_constant<P: IntegerPType>(
111 offsets: &[P],
112 bytes: &[u8],
113 constant: &[u8],
114 operator: CompareOperator,
115) -> BitBuffer {
116 match operator {
117 CompareOperator::Eq => {
118 collect_lane_bits(offsets, |start, end| value_eq(bytes, start, end, constant))
119 }
120 CompareOperator::NotEq => {
121 collect_lane_bits(offsets, |start, end| !value_eq(bytes, start, end, constant))
122 }
123 CompareOperator::Gt => collect_lane_bits(offsets, |start, end| {
124 value_cmp(bytes, start, end, constant).is_gt()
125 }),
126 CompareOperator::Gte => collect_lane_bits(offsets, |start, end| {
127 value_cmp(bytes, start, end, constant).is_ge()
128 }),
129 CompareOperator::Lt => collect_lane_bits(offsets, |start, end| {
130 value_cmp(bytes, start, end, constant).is_lt()
131 }),
132 CompareOperator::Lte => collect_lane_bits(offsets, |start, end| {
133 value_cmp(bytes, start, end, constant).is_le()
134 }),
135 }
136}
137
138fn collect_lane_bits<P: IntegerPType>(
140 offsets: &[P],
141 predicate: impl Fn(usize, usize) -> bool,
142) -> BitBuffer {
143 BitBuffer::collect_bool(offsets.len() - 1, |idx| {
144 let start = unsafe { offsets.get_unchecked(idx) }.as_();
146 let end = unsafe { offsets.get_unchecked(idx + 1) }.as_();
147 predicate(start, end)
148 })
149}
150
151#[inline(always)]
157fn value_eq(bytes: &[u8], start: usize, end: usize, constant: &[u8]) -> bool {
158 end.wrapping_sub(start) == constant.len()
162 && bytes.get(start..end).is_some_and(|value| value == constant)
163}
164
165#[inline(always)]
168fn value_cmp(bytes: &[u8], start: usize, end: usize, constant: &[u8]) -> Ordering {
169 bytes.get(start..end).unwrap_or_default().cmp(constant)
170}
171
172#[cfg(test)]
173mod test {
174 use vortex_buffer::BitBuffer;
175 use vortex_buffer::ByteBuffer;
176
177 use crate::IntoArray;
178 use crate::VortexSessionExecute;
179 use crate::array_session;
180 use crate::arrays::BoolArray;
181 use crate::arrays::ConstantArray;
182 use crate::arrays::VarBinArray;
183 use crate::arrays::VarBinViewArray;
184 use crate::arrays::bool::BoolArrayExt;
185 use crate::builtins::ArrayBuiltins;
186 use crate::dtype::DType;
187 use crate::dtype::Nullability;
188 use crate::scalar::Scalar;
189 use crate::scalar_fn::fns::operators::Operator;
190
191 #[test]
192 fn test_binary_compare() {
193 let mut ctx = array_session().create_execution_ctx();
194 let array = VarBinArray::from_iter(
195 [Some(b"abc".to_vec()), None, Some(b"def".to_vec())],
196 DType::Binary(Nullability::Nullable),
197 );
198 let result = array
199 .into_array()
200 .binary(
201 ConstantArray::new(
202 Scalar::binary(ByteBuffer::copy_from(b"abc"), Nullability::Nullable),
203 3,
204 )
205 .into_array(),
206 Operator::Eq,
207 )
208 .unwrap()
209 .execute::<BoolArray>(&mut ctx)
210 .unwrap();
211
212 assert_eq!(
213 &result
214 .as_ref()
215 .validity()
216 .unwrap()
217 .execute_mask(result.as_ref().len(), &mut ctx)
218 .unwrap()
219 .to_bit_buffer(),
220 &BitBuffer::from_iter([true, false, true])
221 );
222 assert_eq!(
223 result.to_bit_buffer(),
224 BitBuffer::from_iter([true, false, false])
225 );
226 }
227
228 #[test]
229 fn varbinview_compare() {
230 let mut ctx = array_session().create_execution_ctx();
231 let array = VarBinArray::from_iter(
232 [Some(b"abc".to_vec()), None, Some(b"def".to_vec())],
233 DType::Binary(Nullability::Nullable),
234 );
235 let vbv = VarBinViewArray::from_iter(
236 [None, None, Some(b"def".to_vec())],
237 DType::Binary(Nullability::Nullable),
238 );
239 let result = array
240 .into_array()
241 .binary(vbv.into_array(), Operator::Eq)
242 .unwrap()
243 .execute::<BoolArray>(&mut ctx)
244 .unwrap();
245
246 assert_eq!(
247 result
248 .as_ref()
249 .validity()
250 .unwrap()
251 .execute_mask(result.as_ref().len(), &mut ctx)
252 .unwrap()
253 .to_bit_buffer(),
254 BitBuffer::from_iter([false, false, true])
255 );
256 assert_eq!(
257 result.to_bit_buffer(),
258 BitBuffer::from_iter([false, true, true])
259 );
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use vortex_buffer::ByteBuffer;
266
267 use crate::IntoArray;
268 use crate::VortexSessionExecute;
269 use crate::array_session;
270 use crate::arrays::BoolArray;
271 use crate::arrays::ConstantArray;
272 use crate::arrays::VarBinArray;
273 use crate::arrays::varbin::builder::VarBinBuilder;
274 use crate::assert_arrays_eq;
275 use crate::builtins::ArrayBuiltins;
276 use crate::dtype::DType;
277 use crate::dtype::Nullability;
278 use crate::scalar::Scalar;
279 use crate::scalar_fn::fns::operators::Operator;
280
281 #[test]
282 fn test_null_compare() {
283 let arr = VarBinArray::from_iter([Some("h")], DType::Utf8(Nullability::NonNullable));
284
285 let const_ = ConstantArray::new(Scalar::utf8("", Nullability::Nullable), 1);
286
287 assert_eq!(
288 arr.into_array()
289 .binary(const_.into_array(), Operator::Eq)
290 .unwrap()
291 .dtype(),
292 &DType::Bool(Nullability::Nullable)
293 );
294 }
295
296 #[test]
302 fn varbin_i64_offsets_compare_constant() {
303 let mut ctx = array_session().create_execution_ctx();
304 let mut builder = VarBinBuilder::<i64>::with_capacity(3);
305 builder.append_value(b"abc");
306 builder.append_value(b"xyz");
307 builder.append_value(b"abc");
308 let array = builder.finish(DType::Utf8(Nullability::NonNullable));
309
310 let result = array
311 .into_array()
312 .binary(
313 ConstantArray::new(Scalar::utf8("abc", Nullability::NonNullable), 3).into_array(),
314 Operator::Eq,
315 )
316 .unwrap();
317
318 let expected = BoolArray::from_iter([true, false, true]);
319 assert_arrays_eq!(result, expected, &mut ctx);
320 }
321
322 #[test]
323 fn varbin_i64_offsets_compare_constant_binary() {
324 let mut ctx = array_session().create_execution_ctx();
325 let mut builder = VarBinBuilder::<i64>::with_capacity(3);
326 builder.append_value(b"abc");
327 builder.append_value(b"xyz");
328 builder.append_value(b"abc");
329 let array = builder.finish(DType::Binary(Nullability::NonNullable));
330
331 let result = array
332 .into_array()
333 .binary(
334 ConstantArray::new(
335 Scalar::binary(ByteBuffer::copy_from(b"abc"), Nullability::NonNullable),
336 3,
337 )
338 .into_array(),
339 Operator::Eq,
340 )
341 .unwrap();
342
343 let expected = BoolArray::from_iter([true, false, true]);
344 assert_arrays_eq!(result, expected, &mut ctx);
345 }
346}