use std::hash::BuildHasher;
use num_traits::ToPrimitive;
use num_traits::WrappingSub;
use vortex_buffer::BitBuffer;
use vortex_buffer::BitBufferMut;
use vortex_buffer::Buffer;
use vortex_buffer::BufferAllocatorRef;
use vortex_buffer::BufferMut;
use vortex_compute::lane_kernels::collect_bits;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_mask::Mask;
use vortex_utils::aliases::hash_map::HashTable;
use vortex_utils::aliases::hash_map::HashTableEntry;
use vortex_utils::aliases::hash_map::RandomState;
use crate::ArrayRef;
use crate::ExecutionCtx;
use crate::IntoArray;
use crate::RecursiveCanonical;
use crate::arrays::DecimalArray;
use crate::arrays::PrimitiveArray;
use crate::arrays::VarBinViewArray;
use crate::arrays::decimal::converted_buffer;
use crate::arrays::primitive::PrimitiveArrayExt;
use crate::arrays::varbinview::BinaryView;
use crate::dtype::DType;
use crate::dtype::DecimalType;
use crate::dtype::NativeDecimalType;
use crate::dtype::NativePType;
use crate::dtype::PType;
use crate::dtype::i256;
use crate::match_each_decimal_value_type;
use crate::match_each_integer_ptype;
use crate::scalar_fn::fns::binary::build_row_comparator;
use crate::validity::Validity;
const BITMAP_BITS_PER_ELEMENT: u128 = 64;
const BITMAP_MIN_BITS: u128 = 1 << 12;
pub(crate) struct ArrayHaystack {
state: Box<dyn HaystackState>,
}
impl ArrayHaystack {
pub(crate) fn try_new(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
let valid = array.validity()?.execute_mask(array.len(), ctx)?;
let members = if valid.all_true() {
array.clone()
} else {
array.filter(valid)?
};
Ok(Self {
state: new_state(members, ctx)?,
})
}
pub(crate) fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)> {
self.state.contains(needles, ctx)
}
}
trait HaystackState: Send + Sync {
fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)>;
}
fn new_state(elements: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Box<dyn HaystackState>> {
Ok(match elements.dtype() {
DType::Primitive(ptype, _) => {
let ptype = bit_pattern_ptype(*ptype);
let elements = elements
.execute::<PrimitiveArray>(ctx)?
.reinterpret_cast(ptype);
match_each_integer_ptype!(ptype, |T| {
let set = IntegerSet::<T>::new(elements.into_buffer(), ctx.allocator());
Box::new(PrimitiveSet(set)) as Box<dyn HaystackState>
})
}
DType::Decimal(decimal, _) => {
let values_type = DecimalType::smallest_decimal_value_type(decimal);
let elements = elements.execute::<DecimalArray>(ctx)?;
let all_valid = Mask::new_true(elements.len());
match_each_decimal_value_type!(values_type, |T| {
let values = converted_buffer::<T>(&elements, &all_valid)?;
Box::new(DecimalSet(IntegerSet::new(values, ctx.allocator())))
as Box<dyn HaystackState>
})
}
DType::Utf8(_) | DType::Binary(_) => {
Box::new(BytesSet::new(elements.execute::<VarBinViewArray>(ctx)?))
}
_ => Box::new(RowSet::try_new(elements, ctx)?),
})
}
enum IntegerSet<T> {
Bitmap { min: T, bitmap: BitBuffer },
Sorted(Buffer<T>),
}
impl<T: SetInteger> IntegerSet<T> {
fn new(values: Buffer<T>, allocator: &BufferAllocatorRef) -> Self {
match integer_bitmap(&values, allocator) {
Some((min, bitmap)) => Self::Bitmap { min, bitmap },
None => Self::Sorted(sorted_values(values)),
}
}
fn contains(&self, needles: &[T], allocator: &BufferAllocatorRef) -> BitBuffer {
match self {
Self::Bitmap { min, bitmap } => collect_bits(
needles,
|needle| {
needle
.offset_from(*min)
.is_some_and(|offset| offset < bitmap.len() && bitmap.value(offset))
},
allocator,
),
Self::Sorted(sorted) => collect_bits(
needles,
|needle| sorted.binary_search(&needle).is_ok(),
allocator,
),
}
}
}
struct PrimitiveSet<T>(IntegerSet<T>);
impl<T: NativePType + SetInteger> HaystackState for PrimitiveSet<T> {
fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)> {
let needles = needles
.clone()
.execute::<PrimitiveArray>(ctx)?
.reinterpret_cast(T::PTYPE);
let bits = self.0.contains(needles.as_slice::<T>(), ctx.allocator());
Ok((bits, needles.validity()?))
}
}
struct DecimalSet<T>(IntegerSet<T>);
impl<T: NativeDecimalType + SetInteger> HaystackState for DecimalSet<T> {
fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)> {
let needles = needles.clone().execute::<DecimalArray>(ctx)?;
let validity = needles.validity()?;
let valid = validity.execute_mask(needles.len(), ctx)?;
let values = converted_buffer::<T>(&needles, &valid)?;
Ok((self.0.contains(&values, ctx.allocator()), validity))
}
}
struct BytesSet {
hasher: RandomState,
short: HashTable<u128>,
elements: VarBinViewArray,
long: HashTable<u32>,
}
impl BytesSet {
fn new(elements: VarBinViewArray) -> Self {
let hasher = RandomState::default();
let views = elements.views();
let buffers = data_buffers(&elements);
let short_len = views.iter().filter(|view| view.is_inlined()).count();
let mut short = HashTable::with_capacity(short_len);
let mut long = HashTable::with_capacity(views.len() - short_len);
for (idx, view) in views.iter().enumerate() {
if view.is_inlined() {
let whole = view.as_u128();
if let HashTableEntry::Vacant(vacant) = short.entry(
hasher.hash_one(whole),
|&other| other == whole,
|&other| hasher.hash_one(other),
) {
vacant.insert(whole);
}
continue;
}
let value = view.bytes(&buffers);
let bytes = |other: u32| views[other as usize].bytes(&buffers);
if let HashTableEntry::Vacant(vacant) = long.entry(
hasher.hash_one(value),
|&other| bytes(other) == value,
|&other| hasher.hash_one(bytes(other)),
) {
vacant.insert(
u32::try_from(idx).vortex_expect("a list holds fewer than 2^32 elements"),
);
}
}
Self {
hasher,
short,
elements,
long,
}
}
#[inline]
fn contains_view(
&self,
view: &BinaryView,
buffers: &[&[u8]],
element_views: &[BinaryView],
element_buffers: &[&[u8]],
) -> bool {
if view.is_inlined() {
let whole = view.as_u128();
return self
.short
.find(self.hasher.hash_one(whole), |&other| other == whole)
.is_some();
}
let head = view_head(view);
let value = view.bytes(buffers);
self.long
.find(self.hasher.hash_one(value), |&idx| {
let element = &element_views[idx as usize];
view_head(element) == head && element.bytes(element_buffers)[4..] == value[4..]
})
.is_some()
}
}
impl HaystackState for BytesSet {
fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)> {
let element_views = self.elements.views();
let element_buffers = data_buffers(&self.elements);
let array = needles.clone().execute::<VarBinViewArray>(ctx)?;
let buffers = data_buffers(&array);
let bits = collect_bits(
array.views(),
|view: BinaryView| self.contains_view(&view, &buffers, element_views, &element_buffers),
ctx.allocator(),
);
Ok((bits, array.validity()?))
}
}
#[inline]
#[expect(
clippy::cast_possible_truncation,
reason = "the head is the low 8 bytes"
)]
fn view_head(view: &BinaryView) -> u64 {
view.as_u128() as u64
}
struct RowSet {
elements: ArrayRef,
indices: Vec<usize>,
}
impl RowSet {
fn try_new(elements: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
let elements = elements.execute::<RecursiveCanonical>(ctx)?.0.into_array();
let mut indices: Vec<usize> = (0..elements.len()).collect();
if !indices.is_empty() {
let compare = build_row_comparator(&elements, &elements, ctx)?;
if !indices.is_sorted_by(|&lhs, &rhs| compare(lhs, rhs).is_le()) {
indices.sort_unstable_by(|&lhs, &rhs| compare(lhs, rhs));
}
indices.dedup_by(|lhs, rhs| compare(*lhs, *rhs).is_eq());
}
Ok(Self { elements, indices })
}
}
impl HaystackState for RowSet {
fn contains(
&self,
needles: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(BitBuffer, Validity)> {
if self.indices.is_empty() {
return Ok((
BitBuffer::full_in(false, needles.len(), ctx.allocator().clone()),
needles.validity()?,
));
}
let needles = needles
.clone()
.execute::<RecursiveCanonical>(ctx)?
.0
.into_array();
let compare = build_row_comparator(&self.elements, &needles, ctx)?;
let bits = BitBuffer::collect_bool_in(
needles.len(),
|row| {
self.indices
.binary_search_by(|&element| compare(element, row))
.is_ok()
},
ctx.allocator().clone(),
);
Ok((bits, needles.validity()?))
}
}
fn data_buffers(array: &VarBinViewArray) -> Vec<&[u8]> {
(0..array.data_buffers().len())
.map(|idx| array.buffer(idx).as_slice())
.collect()
}
fn bit_pattern_ptype(ptype: PType) -> PType {
match ptype {
PType::F16 => PType::U16,
PType::F32 => PType::U32,
PType::F64 => PType::U64,
_ => ptype,
}
}
trait SetInteger: Copy + Ord + Send + Sync + 'static {
fn offset_from(self, min: Self) -> Option<usize>;
}
macro_rules! impl_set_integer {
($($signed:ty => $unsigned:ty),* $(,)?) => {
$(impl SetInteger for $signed {
fn offset_from(self, min: Self) -> Option<usize> {
usize::try_from((self as $unsigned).wrapping_sub(min as $unsigned)).ok()
}
})*
};
}
impl_set_integer!(
u8 => u8, u16 => u16, u32 => u32, u64 => u64,
i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128,
);
impl SetInteger for i256 {
fn offset_from(self, min: Self) -> Option<usize> {
self.wrapping_sub(&min).to_usize()
}
}
fn integer_bitmap<T: SetInteger>(
values: &[T],
allocator: &BufferAllocatorRef,
) -> Option<(T, BitBuffer)> {
let min = *values.iter().min()?;
let max = *values.iter().max()?;
let span = max.offset_from(min)?;
if span == usize::MAX
|| span as u128 >= (values.len() as u128 * BITMAP_BITS_PER_ELEMENT).max(BITMAP_MIN_BITS)
{
return None;
}
let mut bitmap = BitBufferMut::from_buffer(
BufferMut::zeroed_in((span + 1).div_ceil(8), allocator.clone()),
0,
span + 1,
);
for &value in values {
bitmap.set(
value
.offset_from(min)
.vortex_expect("value within bitmap span"),
);
}
Some((min, bitmap.freeze()))
}
fn sorted_values<T: Copy + Ord + Send + Sync + 'static>(values: Buffer<T>) -> Buffer<T> {
if values.is_sorted_by(|a, b| a < b) {
return values;
}
let mut sorted = values.to_vec();
sorted.sort_unstable();
sorted.dedup();
Buffer::from(sorted)
}