use std::cmp::Ordering;
use vortex_buffer::BitBuffer;
use vortex_error::VortexExpect as _;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use crate::ArrayRef;
use crate::ExecutionCtx;
use crate::IntoArray;
use crate::array::ArrayView;
use crate::arrays::BoolArray;
use crate::arrays::PrimitiveArray;
use crate::arrays::VarBin;
use crate::arrays::varbin::VarBinArrayExt;
use crate::dtype::DType;
use crate::dtype::IntegerPType;
use crate::match_each_integer_ptype;
use crate::scalar_fn::fns::binary::CompareKernel;
use crate::scalar_fn::fns::operators::CompareOperator;
impl CompareKernel for VarBin {
fn compare(
lhs: ArrayView<'_, VarBin>,
rhs: &ArrayRef,
operator: CompareOperator,
ctx: &mut ExecutionCtx,
) -> VortexResult<Option<ArrayRef>> {
let Some(rhs_const) = rhs.as_constant() else {
return Ok(None);
};
let len = lhs.len();
let rhs_bytes: &[u8] = match rhs_const.dtype() {
DType::Binary(_) => rhs_const
.as_binary()
.value()
.vortex_expect("RHS should not be null")
.as_slice(),
DType::Utf8(_) => rhs_const
.as_utf8()
.value()
.vortex_expect("RHS should not be null")
.as_str()
.as_bytes(),
_ => vortex_bail!("VarBinArray can only have type of Binary or Utf8"),
};
let buffer = if rhs_bytes.is_empty() {
match operator {
CompareOperator::Gte => BitBuffer::new_set(len),
CompareOperator::Lt => BitBuffer::new_unset(len), CompareOperator::Eq | CompareOperator::Lte => {
let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
compare_offsets_to_empty::<P>(lhs_offsets, true)
})
}
CompareOperator::NotEq | CompareOperator::Gt => {
let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
compare_offsets_to_empty::<P>(lhs_offsets, false)
})
}
}
} else {
let lhs_offsets = lhs.offsets().clone().execute::<PrimitiveArray>(ctx)?;
match_each_integer_ptype!(lhs_offsets.ptype(), |P| {
compare_bytes_to_constant(
lhs_offsets.as_slice::<P>(),
lhs.bytes().as_slice(),
rhs_bytes,
operator,
)
})
};
Ok(Some(
BoolArray::new(
buffer,
lhs.validity()?.union_nullability(rhs.dtype().nullability()),
)
.into_array(),
))
}
}
fn compare_offsets_to_empty<P: IntegerPType>(offsets: PrimitiveArray, eq: bool) -> BitBuffer {
let fn_ = if eq { P::eq } else { P::ne };
let offsets = offsets.as_slice::<P>();
BitBuffer::collect_bool(offsets.len() - 1, |idx| {
let left = unsafe { offsets.get_unchecked(idx) };
let right = unsafe { offsets.get_unchecked(idx + 1) };
fn_(left, right)
})
}
fn compare_bytes_to_constant<P: IntegerPType>(
offsets: &[P],
bytes: &[u8],
constant: &[u8],
operator: CompareOperator,
) -> BitBuffer {
match operator {
CompareOperator::Eq => {
collect_lane_bits(offsets, |start, end| value_eq(bytes, start, end, constant))
}
CompareOperator::NotEq => {
collect_lane_bits(offsets, |start, end| !value_eq(bytes, start, end, constant))
}
CompareOperator::Gt => collect_lane_bits(offsets, |start, end| {
value_cmp(bytes, start, end, constant).is_gt()
}),
CompareOperator::Gte => collect_lane_bits(offsets, |start, end| {
value_cmp(bytes, start, end, constant).is_ge()
}),
CompareOperator::Lt => collect_lane_bits(offsets, |start, end| {
value_cmp(bytes, start, end, constant).is_lt()
}),
CompareOperator::Lte => collect_lane_bits(offsets, |start, end| {
value_cmp(bytes, start, end, constant).is_le()
}),
}
}
fn collect_lane_bits<P: IntegerPType>(
offsets: &[P],
predicate: impl Fn(usize, usize) -> bool,
) -> BitBuffer {
BitBuffer::collect_bool(offsets.len() - 1, |idx| {
let start = unsafe { offsets.get_unchecked(idx) }.as_();
let end = unsafe { offsets.get_unchecked(idx + 1) }.as_();
predicate(start, end)
})
}
#[inline(always)]
fn value_eq(bytes: &[u8], start: usize, end: usize, constant: &[u8]) -> bool {
end.wrapping_sub(start) == constant.len()
&& bytes.get(start..end).is_some_and(|value| value == constant)
}
#[inline(always)]
fn value_cmp(bytes: &[u8], start: usize, end: usize, constant: &[u8]) -> Ordering {
bytes.get(start..end).unwrap_or_default().cmp(constant)
}
#[cfg(test)]
mod test {
use vortex_buffer::BitBuffer;
use vortex_buffer::ByteBuffer;
use crate::IntoArray;
use crate::VortexSessionExecute;
use crate::array_session;
use crate::arrays::BoolArray;
use crate::arrays::ConstantArray;
use crate::arrays::VarBinArray;
use crate::arrays::VarBinViewArray;
use crate::arrays::bool::BoolArrayExt;
use crate::builtins::ArrayBuiltins;
use crate::dtype::DType;
use crate::dtype::Nullability;
use crate::scalar::Scalar;
use crate::scalar_fn::fns::operators::Operator;
#[test]
fn test_binary_compare() {
let mut ctx = array_session().create_execution_ctx();
let array = VarBinArray::from_iter(
[Some(b"abc".to_vec()), None, Some(b"def".to_vec())],
DType::Binary(Nullability::Nullable),
);
let result = array
.into_array()
.binary(
ConstantArray::new(
Scalar::binary(ByteBuffer::copy_from(b"abc"), Nullability::Nullable),
3,
)
.into_array(),
Operator::Eq,
)
.unwrap()
.execute::<BoolArray>(&mut ctx)
.unwrap();
assert_eq!(
&result
.as_ref()
.validity()
.unwrap()
.execute_mask(result.as_ref().len(), &mut ctx)
.unwrap()
.to_bit_buffer(),
&BitBuffer::from_iter([true, false, true])
);
assert_eq!(
result.to_bit_buffer(),
BitBuffer::from_iter([true, false, false])
);
}
#[test]
fn varbinview_compare() {
let mut ctx = array_session().create_execution_ctx();
let array = VarBinArray::from_iter(
[Some(b"abc".to_vec()), None, Some(b"def".to_vec())],
DType::Binary(Nullability::Nullable),
);
let vbv = VarBinViewArray::from_iter(
[None, None, Some(b"def".to_vec())],
DType::Binary(Nullability::Nullable),
);
let result = array
.into_array()
.binary(vbv.into_array(), Operator::Eq)
.unwrap()
.execute::<BoolArray>(&mut ctx)
.unwrap();
assert_eq!(
result
.as_ref()
.validity()
.unwrap()
.execute_mask(result.as_ref().len(), &mut ctx)
.unwrap()
.to_bit_buffer(),
BitBuffer::from_iter([false, false, true])
);
assert_eq!(
result.to_bit_buffer(),
BitBuffer::from_iter([false, true, true])
);
}
}
#[cfg(test)]
mod tests {
use vortex_buffer::ByteBuffer;
use crate::IntoArray;
use crate::VortexSessionExecute;
use crate::array_session;
use crate::arrays::BoolArray;
use crate::arrays::ConstantArray;
use crate::arrays::VarBinArray;
use crate::arrays::varbin::builder::VarBinBuilder;
use crate::assert_arrays_eq;
use crate::builtins::ArrayBuiltins;
use crate::dtype::DType;
use crate::dtype::Nullability;
use crate::scalar::Scalar;
use crate::scalar_fn::fns::operators::Operator;
#[test]
fn test_null_compare() {
let arr = VarBinArray::from_iter([Some("h")], DType::Utf8(Nullability::NonNullable));
let const_ = ConstantArray::new(Scalar::utf8("", Nullability::Nullable), 1);
assert_eq!(
arr.into_array()
.binary(const_.into_array(), Operator::Eq)
.unwrap()
.dtype(),
&DType::Bool(Nullability::Nullable)
);
}
#[test]
fn varbin_i64_offsets_compare_constant() {
let mut ctx = array_session().create_execution_ctx();
let mut builder = VarBinBuilder::<i64>::with_capacity(3);
builder.append_value(b"abc");
builder.append_value(b"xyz");
builder.append_value(b"abc");
let array = builder.finish(DType::Utf8(Nullability::NonNullable));
let result = array
.into_array()
.binary(
ConstantArray::new(Scalar::utf8("abc", Nullability::NonNullable), 3).into_array(),
Operator::Eq,
)
.unwrap();
let expected = BoolArray::from_iter([true, false, true]);
assert_arrays_eq!(result, expected, &mut ctx);
}
#[test]
fn varbin_i64_offsets_compare_constant_binary() {
let mut ctx = array_session().create_execution_ctx();
let mut builder = VarBinBuilder::<i64>::with_capacity(3);
builder.append_value(b"abc");
builder.append_value(b"xyz");
builder.append_value(b"abc");
let array = builder.finish(DType::Binary(Nullability::NonNullable));
let result = array
.into_array()
.binary(
ConstantArray::new(
Scalar::binary(ByteBuffer::copy_from(b"abc"), Nullability::NonNullable),
3,
)
.into_array(),
Operator::Eq,
)
.unwrap();
let expected = BoolArray::from_iter([true, false, true]);
assert_arrays_eq!(result, expected, &mut ctx);
}
}