Skip to content

Commit 5bea57b

Browse files
committed
less
Signed-off-by: Robert Kruszewski <github@robertk.io>
1 parent 9925126 commit 5bea57b

4 files changed

Lines changed: 114 additions & 75 deletions

File tree

‎vortex-array/src/arrays/constant/vtable/canonical.rs‎

Lines changed: 7 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use itertools::Itertools;
77
use vortex_buffer::BitBuffer;
88
use vortex_buffer::Buffer;
99
use vortex_buffer::BufferAllocatorRef;
10-
use vortex_buffer::BufferMut;
1110
use vortex_buffer::BufferString;
1211
use vortex_buffer::ByteBuffer;
1312
use vortex_buffer::buffer;
@@ -35,8 +34,6 @@ use crate::arrays::UnionArray;
3534
use crate::arrays::VarBinViewArray;
3635
use crate::arrays::VariantArray;
3736
use crate::arrays::varbinview::BinaryView;
38-
use crate::builders::ArrayBuilder;
39-
use crate::builders::VarBinViewBuilder;
4037
use crate::builders::builder_with_capacity_in;
4138
use crate::dtype::DType;
4239
use crate::dtype::DecimalType;
@@ -48,7 +45,6 @@ use crate::match_smallest_list_offset_type;
4845
use crate::scalar::DecimalValue;
4946
use crate::scalar::ListScalar;
5047
use crate::scalar::Scalar;
51-
use crate::scalar::ScalarValue;
5248
use crate::validity::Validity;
5349

5450
/// Shared implementation for both `canonicalize` and `execute` methods.
@@ -310,65 +306,19 @@ fn constant_canonical_list_array(
310306
}
311307

312308
/// The elements of a list scalar as an array, one row per element; empty for a null list.
313-
///
314-
/// Primitive and byte elements are written straight from their values rather than through a scalar
315-
/// each.
316309
pub(crate) fn list_scalar_elements(list: &ListScalar, allocator: &BufferAllocatorRef) -> ArrayRef {
317310
let element_dtype = list.element_dtype();
318-
let Some(values) = list.element_values() else {
311+
let Some(elements) = list.elements() else {
319312
return Canonical::empty(element_dtype).into_array();
320313
};
321314

322-
match element_dtype {
323-
DType::Primitive(ptype, nullability) => match_each_native_ptype!(ptype, |T| {
324-
let mut buffer = BufferMut::<T>::with_capacity_in(values.len(), allocator.clone());
325-
buffer.extend(values.iter().map(|value| {
326-
value.as_ref().map_or_else(T::default, |value| {
327-
value
328-
.as_primitive()
329-
.cast::<T>()
330-
.vortex_expect("list element of the list's element ptype")
331-
})
332-
}));
333-
PrimitiveArray::new(buffer.freeze(), element_validity(values, *nullability))
334-
.into_array()
335-
}),
336-
DType::Utf8(_) | DType::Binary(_) => {
337-
let mut builder = VarBinViewBuilder::with_capacity_in(
338-
element_dtype.clone(),
339-
values.len(),
340-
allocator.clone(),
341-
);
342-
for value in values {
343-
match value {
344-
None => builder.append_null(),
345-
Some(ScalarValue::Utf8(value)) => builder.append_value(value.as_bytes()),
346-
Some(value) => builder.append_value(value.as_binary().as_slice()),
347-
}
348-
}
349-
builder.finish()
350-
}
351-
_ => {
352-
let mut builder = builder_with_capacity_in(element_dtype, values.len(), allocator);
353-
for idx in 0..values.len() {
354-
builder
355-
.append_scalar(&list.element(idx).vortex_expect("index within the list"))
356-
.vortex_expect("list element scalar was invalid");
357-
}
358-
builder.finish()
359-
}
360-
}
361-
}
362-
363-
/// The validity of a list's elements, a null element being a `None` value.
364-
fn element_validity(values: &[Option<ScalarValue>], nullability: Nullability) -> Validity {
365-
match nullability {
366-
Nullability::NonNullable => Validity::NonNullable,
367-
Nullability::Nullable if values.iter().all(Option::is_some) => Validity::AllValid,
368-
Nullability::Nullable => {
369-
Validity::from(BitBuffer::from_iter(values.iter().map(Option::is_some)))
370-
}
315+
let mut builder = builder_with_capacity_in(element_dtype, elements.len(), allocator);
316+
for element in &elements {
317+
builder
318+
.append_scalar(element)
319+
.vortex_expect("list element scalar was invalid");
371320
}
321+
builder.finish()
372322
}
373323

374324
/// Creates a [`FixedSizeListArray`] whose every row holds the same list.

‎vortex-array/src/scalar_fn/fns/list_contains/kernel.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@ pub trait ListContainsElementReduce: VTable {
4141
/// For a needle that is not canonical, execution prepares a constant list into a
4242
/// [`PreparedSetArray`] and runs the kernels again. Thus a kernel can get the prepared set with
4343
/// `list.as_opt::<PreparedSet>()`, and probe its own values with [`PreparedSetData::contains`].
44-
/// For example, a dictionary probes only its values.
44+
/// For example, a dictionary probes only its values. A single value, such as the fill value of a
45+
/// sparse needle, is probed with [`PreparedSetData::contains_scalar`]. A kernel that finds its
46+
/// matches in another way makes its result with [`PreparedSetData::result_from_bits`], which applies
47+
/// the same null semantics.
4548
///
4649
/// [`PreparedSetArray`]: crate::scalar_fn::fns::list_contains::PreparedSetArray
4750
/// [`PreparedSetData::contains`]: crate::scalar_fn::fns::list_contains::PreparedSetData::contains
51+
/// [`PreparedSetData::contains_scalar`]: crate::scalar_fn::fns::list_contains::PreparedSetData::contains_scalar
52+
/// [`PreparedSetData::result_from_bits`]: crate::scalar_fn::fns::list_contains::PreparedSetData::result_from_bits
4853
pub trait ListContainsElementKernel: VTable {
4954
fn list_contains(
5055
list: &ArrayRef,

‎vortex-array/src/scalar_fn/fns/list_contains/prepared/array.rs‎

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -142,54 +142,74 @@ impl PreparedSetData {
142142
/// a null element gives `null` for a needle that matches no element.
143143
///
144144
/// A constant needle is probed once, and gives a constant result.
145+
///
146+
/// # Errors
147+
///
148+
/// Fails when the needles do not have the dtype of the list's elements.
145149
pub fn contains(
146150
&self,
147151
needles: &ArrayRef,
148152
options: &ListContainsOptions,
149153
ctx: &mut ExecutionCtx,
150154
) -> VortexResult<ArrayRef> {
151-
let element_dtype = self.elements.dtype();
152-
if !element_dtype.eq_ignore_nullability(needles.dtype()) {
153-
vortex_bail!(
154-
"Element type {} of list does not match search value {}",
155-
element_dtype,
156-
needles.dtype(),
157-
);
158-
}
159-
160155
if let Some(needle) = needles.as_constant() {
161156
let result = self.contains_scalar(&needle, options, ctx)?;
162157
return Ok(ConstantArray::new(result, needles.len()).into_array());
163158
}
164159

160+
self.check_needle_dtype(needles.dtype())?;
165161
let (bits, needle_validity) = self.set.probe.contains(needles, ctx)?;
166-
self.finish(bits, needle_validity, needles.dtype(), options)
162+
self.result_from_bits(bits, needle_validity, needles.dtype(), options)
167163
}
168164

169165
/// Whether the constant `needle` is an element of the list, under `options`.
170166
///
171-
/// The answer is the one [`Self::contains`] gives for each row of a needle with this value. The
172-
/// probe gets one row, not a row per needle.
173-
fn contains_scalar(
167+
/// The answer is the one [`Self::contains`] gives for each row of a needle with this value, for
168+
/// example for the fill value of a sparse needle. The probe gets one row.
169+
///
170+
/// # Errors
171+
///
172+
/// Fails when the needle does not have the dtype of the list's elements.
173+
pub fn contains_scalar(
174174
&self,
175175
needle: &Scalar,
176176
options: &ListContainsOptions,
177177
ctx: &mut ExecutionCtx,
178178
) -> VortexResult<Scalar> {
179+
self.check_needle_dtype(needle.dtype())?;
179180
let needles = ConstantArray::new(needle.clone(), 1).into_array();
180181
let (bits, needle_validity) = self.set.probe.contains(&needles, ctx)?;
181-
self.finish(bits, needle_validity, needle.dtype(), options)?
182+
self.result_from_bits(bits, needle_validity, needle.dtype(), options)?
182183
.execute_scalar(0, ctx)
183184
}
184185

185-
/// Makes the result from one membership bit per needle and the validity of the needles.
186-
fn finish(
186+
/// Makes the result of [`Self::contains`] from one membership bit per needle.
187+
///
188+
/// A kernel that finds the matches of its needles without this probe, for example from bounds
189+
/// of its own, uses this to apply the null semantics of [`Self::contains`]. A set bit in `bits`
190+
/// tells that the needle equals an element. `needle_validity` is the validity of the needles,
191+
/// and `needle_dtype` is their dtype. The bit of a null needle has no effect.
192+
///
193+
/// # Errors
194+
///
195+
/// Fails when `needle_dtype` is not the dtype of the list's elements, or when
196+
/// `needle_validity` does not have one row per bit.
197+
pub fn result_from_bits(
187198
&self,
188199
bits: BitBuffer,
189200
needle_validity: Validity,
190201
needle_dtype: &DType,
191202
options: &ListContainsOptions,
192203
) -> VortexResult<ArrayRef> {
204+
self.check_needle_dtype(needle_dtype)?;
205+
if let Some(len) = needle_validity.maybe_len() {
206+
vortex_ensure!(
207+
len == bits.len(),
208+
"Needle validity has {len} rows, but there are {} membership bits",
209+
bits.len()
210+
);
211+
}
212+
193213
let nullability = options.result_nullability(&self.list_dtype(), needle_dtype);
194214

195215
let validity = if self.set.is_empty && !options.sql_null_semantics {
@@ -203,6 +223,19 @@ impl PreparedSetData {
203223

204224
Ok(BoolArray::new(bits, validity.union_nullability(nullability)).into_array())
205225
}
226+
227+
/// Fails when needles of `needle_dtype` cannot be elements of the list.
228+
fn check_needle_dtype(&self, needle_dtype: &DType) -> VortexResult<()> {
229+
let element_dtype = self.elements.dtype();
230+
if !element_dtype.eq_ignore_nullability(needle_dtype) {
231+
vortex_bail!(
232+
"Element type {} of list does not match search value {}",
233+
element_dtype,
234+
needle_dtype,
235+
);
236+
}
237+
Ok(())
238+
}
206239
}
207240

208241
impl Debug for PreparedSetData {
@@ -236,7 +269,17 @@ impl ArrayEq for PreparedSetData {
236269
impl Array<PreparedSet> {
237270
/// Prepares `elements`, the elements of a non-null list with the list nullability
238271
/// `nullability`, as a set repeated `len` times.
239-
pub(crate) fn try_new(
272+
///
273+
/// [`ListContains`] prepares a constant list itself. A kernel crate can use this to test its
274+
/// [`ListContainsElementKernel`] against a prepared set.
275+
///
276+
/// [`ListContains`]: crate::scalar_fn::fns::list_contains::ListContains
277+
/// [`ListContainsElementKernel`]: crate::scalar_fn::fns::list_contains::ListContainsElementKernel
278+
///
279+
/// # Errors
280+
///
281+
/// Fails when the elements cannot be executed into the probe.
282+
pub fn try_new(
240283
elements: ArrayRef,
241284
nullability: Nullability,
242285
len: usize,

‎vortex-array/src/scalar_fn/fns/list_contains/prepared/tests.rs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,44 @@ fn test_constant_row_needle_probes_one_row() -> VortexResult<()> {
371371
);
372372
Ok(())
373373
}
374+
375+
#[rstest]
376+
#[case::default(ListContainsOptions::default(), [Some(true), Some(false), None])]
377+
#[case::sql(ListContainsOptions { sql_null_semantics: true }, [Some(true), None, None])]
378+
fn test_result_from_bits_applies_null_semantics(
379+
#[case] options: ListContainsOptions,
380+
#[case] expected: [Option<bool>; 3],
381+
) -> VortexResult<()> {
382+
// Against `{2, null}`: a match, a non-match, and a null needle whose bit has no effect.
383+
let mut ctx = array_session().create_execution_ctx();
384+
let set = prepare(&set_with_null(), &mut ctx)?;
385+
let needle_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
386+
let bits = BitBuffer::from_iter([true, false, true]);
387+
let validity = Validity::from_iter([true, true, false]);
388+
389+
let result = set.result_from_bits(bits, validity, &needle_dtype, &options)?;
390+
assert_arrays_eq!(result, BoolArray::from_iter(expected), &mut ctx);
391+
Ok(())
392+
}
393+
394+
#[test]
395+
fn test_result_from_bits_rejects_mismatched_needles() -> VortexResult<()> {
396+
let mut ctx = array_session().create_execution_ctx();
397+
let set = prepare(&set_with_null(), &mut ctx)?;
398+
let options = ListContainsOptions::default();
399+
let bits = BitBuffer::from_iter([true, false]);
400+
401+
let short_validity = Validity::from_iter([true]);
402+
let needle_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
403+
assert!(
404+
set.result_from_bits(bits.clone(), short_validity, &needle_dtype, &options)
405+
.is_err()
406+
);
407+
408+
let wrong_dtype = DType::Primitive(PType::I64, Nullability::Nullable);
409+
assert!(
410+
set.result_from_bits(bits, Validity::AllValid, &wrong_dtype, &options)
411+
.is_err()
412+
);
413+
Ok(())
414+
}

0 commit comments

Comments
 (0)