use std::cmp::Ordering;
use vortex_buffer::BitBuffer;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_err;
use crate::ArrayRef;
use crate::ExecutionCtx;
use crate::IntoArray;
use crate::arrays::BoolArray;
use crate::arrays::Constant;
use crate::arrays::VarBinViewArray;
use crate::arrays::varbinview::BinaryView;
use crate::dtype::DType;
use crate::dtype::Nullability;
use crate::scalar::Scalar;
use crate::scalar_fn::fns::binary::compare::compare_validity;
use crate::scalar_fn::fns::binary::compare::ordering_predicate;
use crate::scalar_fn::fns::operators::CompareOperator;
use crate::validity::Validity;
enum BytesOperand {
Array {
values: VarBinViewArray,
validity: Validity,
},
Constant {
value: Vec<u8>,
validity: Validity,
},
}
impl BytesOperand {
fn try_new(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
if let Some(constant) = array.as_opt::<Constant>() {
let value = constant_bytes(constant.scalar())?;
return Ok(Self::Constant {
value,
validity: if constant.scalar().dtype().is_nullable() {
Validity::AllValid
} else {
Validity::NonNullable
},
});
}
let values = array.clone().execute::<VarBinViewArray>(ctx)?;
let validity = values.validity()?;
Ok(Self::Array { values, validity })
}
fn validity(&self) -> Validity {
match self {
Self::Array { validity, .. } | Self::Constant { validity, .. } => validity.clone(),
}
}
}
fn constant_bytes(scalar: &Scalar) -> VortexResult<Vec<u8>> {
let value = match scalar.dtype() {
DType::Utf8(_) => scalar
.as_utf8()
.value()
.map(|s| s.as_str().as_bytes().to_vec()),
DType::Binary(_) => scalar.as_binary().value().map(|b| b.to_vec()),
_ => vortex_bail!("expected utf8 or binary scalar, got {}", scalar.dtype()),
};
value.ok_or_else(|| vortex_err!("null constant handled by execute_compare"))
}
struct ViewsSide<'a> {
views: &'a [BinaryView],
buffers: Vec<&'a [u8]>,
}
impl<'a> ViewsSide<'a> {
fn new(array: &'a VarBinViewArray) -> Self {
Self {
views: array.views(),
buffers: (0..array.data_buffers().len())
.map(|idx| array.buffer(idx).as_slice())
.collect(),
}
}
fn len(&self) -> usize {
self.views.len()
}
#[inline]
unsafe fn view_unchecked(&self, index: usize) -> &'a BinaryView {
unsafe { self.views.get_unchecked(index) }
}
#[inline]
fn view_bytes(&self, view: &'a BinaryView) -> &'a [u8] {
if view.is_inlined() {
view.as_inlined().value()
} else {
let view = view.as_view();
&self.buffers[view.buffer_index as usize][view.as_range()]
}
}
}
#[inline]
#[expect(clippy::cast_possible_truncation, reason = "intentional bit slicing")]
fn view_head(view: &BinaryView) -> u64 {
view.as_u128() as u64
}
#[inline]
#[expect(clippy::cast_possible_truncation, reason = "intentional bit slicing")]
fn view_prefix(view: &BinaryView) -> u32 {
((view.as_u128() >> 32) as u32).swap_bytes()
}
#[inline]
fn view_tail(view: &BinaryView) -> u64 {
((view.as_u128() >> 64) as u64).swap_bytes()
}
#[inline]
fn view_eq(
lhs: &ViewsSide<'_>,
lhs_view: &BinaryView,
rhs: &ViewsSide<'_>,
rhs_view: &BinaryView,
) -> bool {
if view_head(lhs_view) != view_head(rhs_view) {
return false;
}
if lhs_view.is_inlined() {
return lhs_view.as_u128() == rhs_view.as_u128();
}
lhs.view_bytes(lhs_view)[4..] == rhs.view_bytes(rhs_view)[4..]
}
#[inline]
fn view_cmp(
lhs: &ViewsSide<'_>,
lhs_view: &BinaryView,
rhs: &ViewsSide<'_>,
rhs_view: &BinaryView,
) -> Ordering {
let lhs_prefix = view_prefix(lhs_view);
let rhs_prefix = view_prefix(rhs_view);
if lhs_prefix != rhs_prefix {
return lhs_prefix.cmp(&rhs_prefix);
}
if lhs_view.is_inlined() && rhs_view.is_inlined() {
let lhs_tail = view_tail(lhs_view);
let rhs_tail = view_tail(rhs_view);
if lhs_tail != rhs_tail {
return lhs_tail.cmp(&rhs_tail);
}
return lhs_view.len().cmp(&rhs_view.len());
}
lhs.view_bytes(lhs_view).cmp(rhs.view_bytes(rhs_view))
}
pub(super) fn compare_bytes(
lhs: &ArrayRef,
rhs: &ArrayRef,
op: CompareOperator,
nullability: Nullability,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let len = lhs.len();
let lhs = BytesOperand::try_new(lhs, ctx)?;
let rhs = BytesOperand::try_new(rhs, ctx)?;
let validity = compare_validity(lhs.validity(), rhs.validity(), nullability)?;
let bits = match (&lhs, &rhs) {
(BytesOperand::Array { values: l, .. }, BytesOperand::Array { values: r, .. }) => {
compare_views(&ViewsSide::new(l), &ViewsSide::new(r), op)
}
(BytesOperand::Array { values, .. }, BytesOperand::Constant { value, .. }) => {
compare_views_constant(&ViewsSide::new(values), value, op)
}
(BytesOperand::Constant { value, .. }, BytesOperand::Array { values, .. }) => {
compare_views_constant(&ViewsSide::new(values), value, op.swap())
}
(BytesOperand::Constant { value: l, .. }, BytesOperand::Constant { value: r, .. }) => {
BitBuffer::full(ordering_predicate(op)(l.as_slice().cmp(r.as_slice())), len)
}
};
Ok(BoolArray::try_new(bits, validity)?.into_array())
}
fn compare_views(lhs: &ViewsSide<'_>, rhs: &ViewsSide<'_>, op: CompareOperator) -> BitBuffer {
let len = lhs.len();
assert_eq!(len, rhs.len(), "compared views must have equal lengths");
match op {
CompareOperator::Eq => BitBuffer::collect_bool(len, |i| {
unsafe { view_eq(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) }
}),
CompareOperator::NotEq => BitBuffer::collect_bool(len, |i| {
unsafe { !view_eq(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) }
}),
CompareOperator::Gt => collect_ordering_bits(lhs, rhs, Ordering::is_gt),
CompareOperator::Gte => collect_ordering_bits(lhs, rhs, Ordering::is_ge),
CompareOperator::Lt => collect_ordering_bits(lhs, rhs, Ordering::is_lt),
CompareOperator::Lte => collect_ordering_bits(lhs, rhs, Ordering::is_le),
}
}
fn collect_ordering_bits(
lhs: &ViewsSide<'_>,
rhs: &ViewsSide<'_>,
predicate: impl Fn(Ordering) -> bool,
) -> BitBuffer {
let len = lhs.len();
assert_eq!(len, rhs.len(), "compared views must have equal lengths");
BitBuffer::collect_bool(len, |i| {
predicate(unsafe { view_cmp(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) })
})
}
fn compare_views_constant(lhs: &ViewsSide<'_>, constant: &[u8], op: CompareOperator) -> BitBuffer {
let len = lhs.len();
let mut prefix_bytes = [0u8; 4];
let prefix_len = constant.len().min(4);
prefix_bytes[..prefix_len].copy_from_slice(&constant[..prefix_len]);
let mut tail_bytes = [0u8; 8];
if constant.len() > 4 {
let tail_len = (constant.len() - 4).min(8);
tail_bytes[..tail_len].copy_from_slice(&constant[4..4 + tail_len]);
}
let constant_head =
(constant.len() as u64) | (u64::from(u32::from_le_bytes(prefix_bytes)) << 32);
let constant_prefix = u32::from_be_bytes(prefix_bytes);
let constant_tail = u64::from_be_bytes(tail_bytes);
let constant_inlined =
u128::from(constant_head) | (u128::from(u64::from_le_bytes(tail_bytes)) << 64);
match op {
CompareOperator::Eq => BitBuffer::collect_bool(len, |i| {
let view = unsafe { lhs.view_unchecked(i) };
constant_eq(lhs, view, constant, constant_head, constant_inlined)
}),
CompareOperator::NotEq => BitBuffer::collect_bool(len, |i| {
let view = unsafe { lhs.view_unchecked(i) };
!constant_eq(lhs, view, constant, constant_head, constant_inlined)
}),
CompareOperator::Gt => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_gt,
),
CompareOperator::Gte => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_ge,
),
CompareOperator::Lt => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_lt,
),
CompareOperator::Lte => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_le,
),
}
}
fn collect_constant_ordering_bits(
lhs: &ViewsSide<'_>,
constant: &[u8],
constant_prefix: u32,
constant_tail: u64,
predicate: impl Fn(Ordering) -> bool,
) -> BitBuffer {
BitBuffer::collect_bool(lhs.len(), |i| {
let view = unsafe { lhs.view_unchecked(i) };
predicate(constant_cmp(
lhs,
view,
constant,
constant_prefix,
constant_tail,
))
})
}
#[inline]
fn constant_eq(
side: &ViewsSide<'_>,
view: &BinaryView,
constant: &[u8],
constant_head: u64,
constant_inlined: u128,
) -> bool {
if view_head(view) != constant_head {
return false;
}
if view.is_inlined() {
return view.as_u128() == constant_inlined;
}
side.view_bytes(view)[4..] == constant[4..]
}
#[inline]
fn constant_cmp(
side: &ViewsSide<'_>,
view: &BinaryView,
constant: &[u8],
constant_prefix: u32,
constant_tail: u64,
) -> Ordering {
let prefix = view_prefix(view);
if prefix != constant_prefix {
return prefix.cmp(&constant_prefix);
}
if view.is_inlined() {
let tail = view_tail(view);
if tail != constant_tail {
return tail.cmp(&constant_tail);
}
return (view.len() as usize).cmp(&constant.len());
}
side.view_bytes(view).cmp(constant)
}