use std::cmp::Ordering;
use vortex_buffer::BitBuffer;
use vortex_buffer::BufferAllocatorRef;
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::arrays::varbinview::ResolvedViews;
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"))
}
#[inline]
fn view_eq(
lhs: &ResolvedViews<'_>,
lhs_view: &BinaryView,
rhs: &ResolvedViews<'_>,
rhs_view: &BinaryView,
) -> bool {
if lhs_view.head() != rhs_view.head() {
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: &ResolvedViews<'_>,
lhs_view: &BinaryView,
rhs: &ResolvedViews<'_>,
rhs_view: &BinaryView,
) -> Ordering {
let lhs_prefix = lhs_view.order_prefix();
let rhs_prefix = rhs_view.order_prefix();
if lhs_prefix != rhs_prefix {
return lhs_prefix.cmp(&rhs_prefix);
}
if lhs_view.is_inlined() && rhs_view.is_inlined() {
let lhs_tail = lhs_view.order_tail();
let rhs_tail = rhs_view.order_tail();
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(
&l.resolved_views(),
&r.resolved_views(),
op,
ctx.allocator(),
)
}
(BytesOperand::Array { values, .. }, BytesOperand::Constant { value, .. }) => {
compare_views_constant(&values.resolved_views(), value, op, ctx.allocator())
}
(BytesOperand::Constant { value, .. }, BytesOperand::Array { values, .. }) => {
compare_views_constant(&values.resolved_views(), value, op.swap(), ctx.allocator())
}
(BytesOperand::Constant { value: l, .. }, BytesOperand::Constant { value: r, .. }) => {
BitBuffer::full_in(
ordering_predicate(op)(l.as_slice().cmp(r.as_slice())),
len,
ctx.allocator().clone(),
)
}
};
Ok(BoolArray::try_new(bits, validity)?.into_array())
}
fn compare_views(
lhs: &ResolvedViews<'_>,
rhs: &ResolvedViews<'_>,
op: CompareOperator,
allocator: &BufferAllocatorRef,
) -> BitBuffer {
let len = lhs.len();
assert_eq!(len, rhs.len(), "compared views must have equal lengths");
match op {
CompareOperator::Eq => BitBuffer::collect_bool_in(
len,
|i| {
unsafe { view_eq(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) }
},
allocator.clone(),
),
CompareOperator::NotEq => BitBuffer::collect_bool_in(
len,
|i| {
unsafe { !view_eq(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) }
},
allocator.clone(),
),
CompareOperator::Gt => collect_ordering_bits(lhs, rhs, Ordering::is_gt, allocator),
CompareOperator::Gte => collect_ordering_bits(lhs, rhs, Ordering::is_ge, allocator),
CompareOperator::Lt => collect_ordering_bits(lhs, rhs, Ordering::is_lt, allocator),
CompareOperator::Lte => collect_ordering_bits(lhs, rhs, Ordering::is_le, allocator),
}
}
fn collect_ordering_bits(
lhs: &ResolvedViews<'_>,
rhs: &ResolvedViews<'_>,
predicate: impl Fn(Ordering) -> bool,
allocator: &BufferAllocatorRef,
) -> BitBuffer {
let len = lhs.len();
assert_eq!(len, rhs.len(), "compared views must have equal lengths");
BitBuffer::collect_bool_in(
len,
|i| {
predicate(unsafe { view_cmp(lhs, lhs.view_unchecked(i), rhs, rhs.view_unchecked(i)) })
},
allocator.clone(),
)
}
fn compare_views_constant(
lhs: &ResolvedViews<'_>,
constant: &[u8],
op: CompareOperator,
allocator: &BufferAllocatorRef,
) -> BitBuffer {
let len = lhs.len();
let constant_head = BinaryView::head_of(constant);
let constant_prefix = BinaryView::order_prefix_of(constant);
let constant_tail = BinaryView::order_tail_of(constant);
let constant_inlined = if constant.len() <= BinaryView::MAX_INLINED_SIZE {
BinaryView::new_inlined(constant).as_u128()
} else {
0
};
match op {
CompareOperator::Eq => BitBuffer::collect_bool_in(
len,
|i| {
let view = unsafe { lhs.view_unchecked(i) };
constant_eq(lhs, view, constant, constant_head, constant_inlined)
},
allocator.clone(),
),
CompareOperator::NotEq => BitBuffer::collect_bool_in(
len,
|i| {
let view = unsafe { lhs.view_unchecked(i) };
!constant_eq(lhs, view, constant, constant_head, constant_inlined)
},
allocator.clone(),
),
CompareOperator::Gt => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_gt,
allocator,
),
CompareOperator::Gte => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_ge,
allocator,
),
CompareOperator::Lt => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_lt,
allocator,
),
CompareOperator::Lte => collect_constant_ordering_bits(
lhs,
constant,
constant_prefix,
constant_tail,
Ordering::is_le,
allocator,
),
}
}
fn collect_constant_ordering_bits(
lhs: &ResolvedViews<'_>,
constant: &[u8],
constant_prefix: u32,
constant_tail: u64,
predicate: impl Fn(Ordering) -> bool,
allocator: &BufferAllocatorRef,
) -> BitBuffer {
BitBuffer::collect_bool_in(
lhs.len(),
|i| {
let view = unsafe { lhs.view_unchecked(i) };
predicate(constant_cmp(
lhs,
view,
constant,
constant_prefix,
constant_tail,
))
},
allocator.clone(),
)
}
#[inline]
fn constant_eq(
side: &ResolvedViews<'_>,
view: &BinaryView,
constant: &[u8],
constant_head: u64,
constant_inlined: u128,
) -> bool {
if view.head() != 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: &ResolvedViews<'_>,
view: &BinaryView,
constant: &[u8],
constant_prefix: u32,
constant_tail: u64,
) -> Ordering {
let prefix = view.order_prefix();
if prefix != constant_prefix {
return prefix.cmp(&constant_prefix);
}
if view.is_inlined() {
let tail = view.order_tail();
if tail != constant_tail {
return tail.cmp(&constant_tail);
}
return (view.len() as usize).cmp(&constant.len());
}
side.view_bytes(view).cmp(constant)
}