diff --git a/vortex-array/src/arrays/interleave/execute/decimal.rs b/vortex-array/src/arrays/interleave/execute/decimal.rs new file mode 100644 index 00000000000..6e6fbd3e492 --- /dev/null +++ b/vortex-array/src/arrays/interleave/execute/decimal.rs @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Execution for decimal [`Interleave`] values. +//! +//! Sources can use different stored widths for the same logical dtype. Decimal execution +//! normalizes them to the logical canonical width before gathering rows. + +use vortex_buffer::Buffer; +use vortex_error::VortexResult; +use vortex_error::vortex_err; + +use super::super::Interleave; +use super::super::InterleaveArrayExt; +use super::super::InterleaveSlots; +use super::fixed_width::Source; +use super::fixed_width::gather; +use crate::AnyColumnar; +use crate::ArrayRef; +use crate::array::Array; +use crate::arrays::Constant; +use crate::arrays::Decimal; +use crate::arrays::DecimalArray; +use crate::arrays::Primitive; +use crate::arrays::decimal::DecimalArrayExt; +use crate::arrays::decimal::copy_to_canonical_width; +use crate::dtype::DecimalDType; +use crate::dtype::NativeDecimalType; +use crate::executor::ExecutionCtx; +use crate::executor::ExecutionResult; +use crate::match_each_decimal_value_type; +use crate::require_child; + +pub(super) fn execute( + mut array: Array, + ctx: &mut ExecutionCtx, +) -> VortexResult { + let num_values = array.num_values(); + array = + require_child!(array, array.array_indices(), InterleaveSlots::ARRAY_INDICES => Primitive); + array = require_child!(array, array.row_indices(), InterleaveSlots::ROW_INDICES => Primitive); + for i in 0..num_values { + array = require_child!(array, array.value(i), InterleaveSlots::VALUES_OFFSET + i => AnyColumnar); + } + + let validity = array.as_ref().validity()?; + let dtype = DecimalDType::try_from(array.dtype())?; + let output = match_each_decimal_value_type!(dtype.canonical_values_type(), |T| { + DecimalArray::new(gather_values::(&array, dtype, ctx)?, dtype, validity) + }); + + Ok(ExecutionResult::done(output)) +} + +fn gather_values( + array: &Array, + dtype: DecimalDType, + ctx: &mut ExecutionCtx, +) -> VortexResult> { + let sources = (0..array.num_values()) + .map(|i| prepare_source::(array.value(i), dtype, ctx)) + .collect::>>()?; + + gather(array, &sources, ctx.allocator()) +} + +/// Normalizes one decimal source while retaining a single payload for a constant. +fn prepare_source( + value: &ArrayRef, + dtype: DecimalDType, + ctx: &mut ExecutionCtx, +) -> VortexResult> { + if let Some(constant) = value.as_opt::() { + let payload = match constant.scalar().as_decimal().decimal_value() { + Some(value) => value.cast::().ok_or_else(|| { + vortex_err!("interleave decimal constant must fit {dtype}, got {value}") + })?, + None => T::default(), + }; + + return Ok(Source::constant(payload, value.len(), ctx.allocator())); + } + + let decimal = value.as_::(); + let data = if decimal.values_type() == T::DECIMAL_TYPE { + decimal.buffer::() + } else { + // TODO(connor): Remove this branch once every `DecimalArray` stores its values at the + // canonical width of its dtype (#10701). Then all sources share the canonical width `T`, + // because they share the dtype up to nullability. Until then, a source can store its values + // at another width. The copy widens such values, or checks their precision and narrows them + // with zeros in the null slots. + let mask = decimal.validity()?.execute_mask(value.len(), ctx)?; + let copy = match_each_decimal_value_type!(decimal.values_type(), |S| { + copy_to_canonical_width(decimal.buffer::().as_slice(), &mask, dtype)? + }); + Buffer::::from_byte_buffer(copy.unwrap_host()) + }; + + Ok(Source::decoded(data)) +} + +#[cfg(test)] +mod tests { + use std::sync::LazyLock; + + use rstest::rstest; + use vortex_buffer::Buffer; + use vortex_buffer::buffer; + use vortex_error::VortexExpect; + use vortex_error::VortexResult; + use vortex_session::VortexSession; + + use crate::ArrayRef; + use crate::IntoArray; + use crate::VortexSessionExecute; + use crate::arrays::ConstantArray; + use crate::arrays::DecimalArray; + use crate::arrays::InterleaveArray; + use crate::arrays::PrimitiveArray; + use crate::arrays::decimal::DecimalArrayExt; + use crate::assert_arrays_eq; + use crate::dtype::BigCast; + use crate::dtype::DType; + use crate::dtype::DecimalDType; + use crate::dtype::NativeDecimalType; + use crate::dtype::Nullability; + use crate::dtype::i256; + use crate::match_each_decimal_value_type; + use crate::scalar::DecimalValue; + use crate::scalar::Scalar; + use crate::validity::Validity; + + static SESSION: LazyLock = LazyLock::new(VortexSession::empty); + + /// Interleaves `values` with one `(array_index, row_index)` pair for each output row, and + /// checks that the result has the canonical width of its dtype and equals `expected`. + #[track_caller] + fn assert_interleaved( + values: Vec, + indices: &[(u8, u8)], + expected: DecimalArray, + ) -> VortexResult<()> { + let branches = PrimitiveArray::from_iter(indices.iter().map(|&(branch, _)| branch)); + let rows = PrimitiveArray::from_iter(indices.iter().map(|&(_, row)| row)); + let array = InterleaveArray::try_new(values, branches.into_array(), rows.into_array())?; + let mut ctx = SESSION.create_execution_ctx(); + + let output = array.into_array().execute::(&mut ctx)?; + assert_eq!( + output.values_type(), + expected.decimal_dtype().canonical_values_type() + ); + assert_eq!(output.dtype(), expected.dtype()); + assert_arrays_eq!(output.into_array(), expected.into_array(), &mut ctx); + + Ok(()) + } + + /// Gathers rows out of order, with repeats and nulls, at each canonical width. The null slot of + /// the nullable source holds `T::MIN`, which can be outside the precision. + #[rstest] + #[case::i8(2)] + #[case::i16(4)] + #[case::i32(9)] + #[case::i64(18)] + #[case::i128(38)] + #[case::i256(76)] + fn test_decimal_interleave(#[case] precision: u8) -> VortexResult<()> { + let dtype = DecimalDType::new(precision, 2); + match_each_decimal_value_type!(dtype.canonical_values_type(), |T| { + let min = T::MIN_BY_PRECISION[usize::from(precision)]; + let max = T::MAX_BY_PRECISION[usize::from(precision)]; + let zero = T::default(); + let nullable = DecimalArray::new( + Buffer::copy_from([min, T::MIN, max]), + dtype, + Validity::from_iter([true, false, true]), + ); + let non_nullable = DecimalArray::new( + Buffer::copy_from([max, min, zero]), + dtype, + Validity::NonNullable, + ); + let expected = DecimalArray::new( + Buffer::copy_from([zero, min, zero, max, max, min]), + dtype, + Validity::from_iter([true, true, false, true, true, true]), + ); + + assert_interleaved( + vec![nullable.into_array(), non_nullable.into_array()], + &[(1, 2), (0, 0), (0, 1), (1, 0), (0, 2), (0, 0)], + expected, + ) + }) + } + + /// A constant keeps one payload at the canonical width, also when its scalar stores the value + /// at another width. + #[rstest] + #[case::narrower_scalar(76, Some(DecimalValue::I8(-7)))] + #[case::wider_scalar(2, Some(DecimalValue::I256(i256::from_i128(-7))))] + #[case::null(9, None)] + fn test_decimal_interleave_constant( + #[case] precision: u8, + #[case] payload: Option, + ) -> VortexResult<()> { + let dtype = DecimalDType::new(precision, 2); + let (scalar, validity) = match payload { + Some(payload) => ( + Scalar::decimal(payload, dtype, Nullability::NonNullable), + Validity::NonNullable, + ), + None => ( + Scalar::null(DType::Decimal(dtype, Nullability::Nullable)), + Validity::from_iter([false, true, false, false]), + ), + }; + let constant = ConstantArray::new(scalar, 3).into_array(); + match_each_decimal_value_type!(dtype.canonical_values_type(), |T| { + let seven = ::from(7i8).vortex_expect("7 fits every decimal type"); + let minus_seven = + ::from(-7i8).vortex_expect("-7 fits every decimal type"); + let column = DecimalArray::new(buffer![seven], dtype, Validity::NonNullable); + let expected = DecimalArray::new( + buffer![minus_seven, seven, minus_seven, minus_seven], + dtype, + validity, + ); + + assert_interleaved( + vec![constant, column.into_array()], + &[(0, 2), (1, 0), (0, 1), (0, 2)], + expected, + ) + }) + } + + /// Sources that store their values at another width are copied to the canonical width first. + /// The `i8` source widens, and the `i256` source narrows with garbage in its null slots. + #[test] + fn test_decimal_interleave_mixed_stored_widths() -> VortexResult<()> { + let dtype = DecimalDType::new(9, 0); + let narrow = DecimalArray::new(buffer![1i8, -2], dtype, Validity::NonNullable); + let wide = DecimalArray::new( + buffer![i256::from_i128(3), i256::MIN, i256::MAX], + dtype, + Validity::from_iter([true, false, false]), + ); + let expected = DecimalArray::new( + buffer![3i32, -2, 0, 1, 0], + dtype, + Validity::from_iter([true, true, false, true, false]), + ); + + assert_interleaved( + vec![narrow.into_array(), wide.into_array()], + &[(1, 0), (0, 1), (1, 1), (0, 0), (1, 2)], + expected, + ) + } + + #[test] + fn test_decimal_interleave_empty() -> VortexResult<()> { + let dtype = DecimalDType::new(9, 0); + let empty = DecimalArray::new(Buffer::::empty(), dtype, Validity::NonNullable); + let expected = DecimalArray::new(Buffer::::empty(), dtype, Validity::NonNullable); + + assert_interleaved( + vec![empty.clone().into_array(), empty.into_array()], + &[], + expected, + ) + } + + #[test] + fn test_decimal_interleave_rejects_values_outside_precision() -> VortexResult<()> { + let source = DecimalArray::new( + buffer![100i16], + DecimalDType::new(2, 0), + Validity::NonNullable, + ) + .into_array(); + let array = InterleaveArray::try_new( + vec![source.clone(), source], + PrimitiveArray::from_iter([0u8]).into_array(), + PrimitiveArray::from_iter([0u8]).into_array(), + )? + .into_array(); + + let error = array + .execute::(&mut SESSION.create_execution_ctx()) + .expect_err("the narrowing copy to `i8` rejects 100 at precision 2"); + assert!(error.to_string().contains("precision"), "{error}"); + + Ok(()) + } +} diff --git a/vortex-array/src/arrays/interleave/execute/fixed_width.rs b/vortex-array/src/arrays/interleave/execute/fixed_width.rs new file mode 100644 index 00000000000..dfd59fd87c7 --- /dev/null +++ b/vortex-array/src/arrays/interleave/execute/fixed_width.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Shared gather kernel for fixed-width [`Interleave`] values. +//! +//! Primitive and decimal execution turn each value into a [`Source`] and call [`gather`]. They +//! differ only in how they read a constant payload or a decoded buffer, and in the array that they +//! build from the gathered payloads. + +use num_traits::AsPrimitive; +use vortex_buffer::Buffer; +use vortex_buffer::BufferAllocatorRef; +use vortex_buffer::BufferMut; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; +use vortex_error::vortex_ensure; +use vortex_error::vortex_ensure_eq; + +use super::super::Interleave; +use super::super::InterleaveArrayExt; +use crate::array::Array; +use crate::arrays::Primitive; +use crate::match_each_unsigned_integer_ptype; + +/// Physical storage for one fixed-width value of an [`Interleave`] array. +/// +/// A constant keeps one payload instead of one payload for each row. `row_mask` maps each row to +/// its payload, so one gather loop reads constants and decoded buffers without a branch. +pub(super) struct Source { + /// One payload for each row, or a single payload for every row of a constant. + data: Buffer, + /// The number of logical rows. A constant can have more rows than `data` has payloads. + len: usize, + /// `usize::MAX` for a decoded buffer and zero for a constant, so that `row & row_mask` is the + /// position of the payload of `row` in `data`. + row_mask: usize, +} + +impl Source { + /// Returns a source of `len` rows that all hold `payload`. + pub(super) fn constant(payload: T, len: usize, allocator: &BufferAllocatorRef) -> Self { + Self { + data: Buffer::full_in(payload, 1, allocator.clone()), + len, + row_mask: 0, + } + } + + /// Returns a source with one row for each payload in `data`. + pub(super) fn decoded(data: Buffer) -> Self { + Self { + len: data.len(), + data, + row_mask: usize::MAX, + } + } +} + +/// Gathers the payload of each output row of `array` from `sources`. +/// +/// `sources` holds one [`Source`] for each value of `array`, in order. Both selectors of `array` +/// **must** be [`Primitive`] arrays, which the kernels ensure with `require_child!`. +/// +/// # Errors +/// +/// Returns an error if the selectors have different lengths, or if a selector names a value or a +/// row that does not exist. +pub(super) fn gather( + array: &Array, + sources: &[Source], + allocator: &BufferAllocatorRef, +) -> VortexResult> { + let branches = array.array_indices().as_::(); + let rows = array.row_indices().as_::(); + + match_each_unsigned_integer_ptype!(branches.ptype(), |A| { + match_each_unsigned_integer_ptype!(rows.ptype(), |R| { + gather_rows( + sources, + branches.as_slice::(), + rows.as_slice::(), + allocator, + ) + }) + }) +} + +fn gather_rows( + sources: &[Source], + branches: &[A], + rows: &[R], + allocator: &BufferAllocatorRef, +) -> VortexResult> +where + T: Copy, + A: AsPrimitive, + R: AsPrimitive, +{ + // `zip` truncates to the shorter input. + vortex_ensure_eq!( + rows.len(), + branches.len(), + "interleave row_indices length does not match array_indices length", + ); + + // Write rows through a `&mut` slice. Yielding `Result` per row splits each `[u8; W]` copy + // into narrow moves (the `Ok` payload is misaligned), and pushing through `BufferMut` reloads + // its length on every row. + let len = branches.len(); + let mut output = BufferMut::with_capacity_in(len, allocator.clone()); + let slots = &mut output.spare_capacity_mut()[..len]; + for ((slot, branch), row) in slots.iter_mut().zip(branches).zip(rows) { + let Some(source) = sources.get((*branch).as_()) else { + vortex_bail!("interleave array index out of bounds"); + }; + let row = (*row).as_(); + vortex_ensure!(row < source.len, "interleave row index out of bounds"); + slot.write(source.data[row & source.row_mask]); + } + // SAFETY: the loop returns on any error, so reaching here means it wrote all `len` slots: + // `slots`, `branches`, and `rows` all have length `len`. + unsafe { output.set_len(len) }; + Ok(output.freeze()) +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + use vortex_buffer::buffer; + use vortex_error::VortexResult; + + use super::Source; + use super::gather_rows; + use crate::memory::BufferAllocatorRef; + + #[rstest] + #[case::array_index(2, 0, "array index out of bounds")] + #[case::constant_row_index(0, 1, "row index out of bounds")] + #[case::decoded_row_index(1, 1, "row index out of bounds")] + fn rejects_out_of_bounds_selectors( + #[case] branch: u8, + #[case] row: u8, + #[case] message: &str, + ) -> VortexResult<()> { + let allocator = BufferAllocatorRef::statically_allocated(); + let sources = [ + Source::constant(1u32, 1, &allocator), + Source::decoded(buffer![2u32]), + ]; + + let error = gather_rows(&sources, &[branch], &[row], &allocator) + .expect_err("every case names a value or a row that does not exist"); + assert!(error.to_string().contains(message), "{error}"); + + Ok(()) + } +} diff --git a/vortex-array/src/arrays/interleave/execute/mod.rs b/vortex-array/src/arrays/interleave/execute/mod.rs index 558db9e3309..20133f64cfc 100644 --- a/vortex-array/src/arrays/interleave/execute/mod.rs +++ b/vortex-array/src/arrays/interleave/execute/mod.rs @@ -10,6 +10,8 @@ //! [`Interleave::check`]: super::Interleave::check mod bool; +mod decimal; +mod fixed_width; mod primitive; use vortex_error::VortexResult; @@ -27,6 +29,8 @@ pub(super) fn execute( ) -> VortexResult { if array.dtype().is_boolean() { bool::execute(array, ctx) + } else if array.dtype().is_decimal() { + decimal::execute(array, ctx) } else if array.dtype().is_primitive() { primitive::execute(array, ctx) } else { diff --git a/vortex-array/src/arrays/interleave/execute/primitive.rs b/vortex-array/src/arrays/interleave/execute/primitive.rs index f1595045383..c758d49b47b 100644 --- a/vortex-array/src/arrays/interleave/execute/primitive.rs +++ b/vortex-array/src/arrays/interleave/execute/primitive.rs @@ -3,19 +3,16 @@ //! Execution for primitive [`Interleave`] values. -use num_traits::AsPrimitive; use vortex_buffer::Alignment; use vortex_buffer::Buffer; use vortex_buffer::BufferAllocatorRef; -use vortex_buffer::BufferMut; use vortex_error::VortexResult; -use vortex_error::vortex_bail; -use vortex_error::vortex_ensure; -use vortex_error::vortex_ensure_eq; use super::super::Interleave; use super::super::InterleaveArrayExt; use super::super::InterleaveSlots; +use super::fixed_width::Source; +use super::fixed_width::gather; use crate::AnyColumnar; use crate::array::Array; use crate::arrays::Constant; @@ -26,7 +23,6 @@ use crate::dtype::ToBytes; use crate::executor::ExecutionCtx; use crate::executor::ExecutionResult; use crate::match_each_ptype_width; -use crate::match_each_unsigned_integer_ptype; use crate::require_child; pub(super) fn execute( @@ -57,22 +53,13 @@ pub(super) fn execute( Ok(ExecutionResult::done(output)) } -/// Physical storage for one primitive source array. -struct PrimitiveSource { - data: Buffer, - len: usize, - // Maps logical rows to `data`: zero for constants and identity for decoded buffers. - row_mask: usize, -} - fn gather_values( array: &Array, allocator: &BufferAllocatorRef, ) -> VortexResult> { - let values = (0..array.num_values()) + let sources = (0..array.num_values()) .map(|i| { let value = array.value(i); - let len = value.len(); if let Some(constant) = value.as_opt::() { // Validity carries nullness; a null constant's payload is never observed. let payload = constant @@ -85,98 +72,14 @@ fn gather_values( bytes }) .unwrap_or([0; W]); - PrimitiveSource { - data: Buffer::full_in(payload, 1, allocator.clone()), - len, - row_mask: 0, - } + Source::constant(payload, value.len(), allocator) } else { - PrimitiveSource { - data: Buffer::<[u8; W]>::from_byte_buffer( - value.as_::().buffer_handle().to_host_sync(), - ), - len, - row_mask: usize::MAX, - } + Source::decoded(Buffer::<[u8; W]>::from_byte_buffer( + value.as_::().buffer_handle().to_host_sync(), + )) } }) .collect::>(); - let branches = array.array_indices().as_::(); - let rows = array.row_indices().as_::(); - - match_each_unsigned_integer_ptype!(branches.ptype(), |A| { - match_each_unsigned_integer_ptype!(rows.ptype(), |R| { - gather( - &values, - branches.as_slice::(), - rows.as_slice::(), - allocator, - ) - }) - }) -} - -fn gather( - values: &[PrimitiveSource], - branches: &[A], - rows: &[R], - allocator: &BufferAllocatorRef, -) -> VortexResult> -where - T: Copy, - A: AsPrimitive, - R: AsPrimitive, -{ - // `zip` truncates to the shorter input. - vortex_ensure_eq!( - rows.len(), - branches.len(), - "interleave row_indices length does not match array_indices length", - ); - - // Write rows through a `&mut` slice. Yielding `Result` per row splits each `[u8; W]` copy - // into narrow moves (the `Ok` payload is misaligned), and pushing through `BufferMut` reloads - // its length on every row. - let len = branches.len(); - let mut output = BufferMut::with_capacity_in(len, allocator.clone()); - let slots = &mut output.spare_capacity_mut()[..len]; - for ((slot, branch), row) in slots.iter_mut().zip(branches).zip(rows) { - let Some(source) = values.get((*branch).as_()) else { - vortex_bail!("interleave array index out of bounds"); - }; - let row = (*row).as_(); - vortex_ensure!(row < source.len, "interleave row index out of bounds"); - slot.write(source.data[row & source.row_mask]); - } - // SAFETY: the loop returns on any error, so reaching here means it wrote all `len` slots: - // `slots`, `branches`, and `rows` all have length `len`. - unsafe { output.set_len(len) }; - Ok(output.freeze()) -} -#[cfg(test)] -mod tests { - use super::*; - use crate::memory::BufferAllocatorRef; - - #[test] - fn rejects_out_of_bounds_selectors() { - let values = [PrimitiveSource { - data: Buffer::full_in(1u32, 1, BufferAllocatorRef::statically_allocated()), - len: 1, - row_mask: 0, - }]; - - let allocator = BufferAllocatorRef::statically_allocated(); - let error = gather(&values, &[1u8], &[0u8], &allocator).unwrap_err(); - assert!( - error.to_string().contains("array index out of bounds"), - "{error}" - ); - let error = gather(&values, &[0u8], &[1u8], &allocator).unwrap_err(); - assert!( - error.to_string().contains("row index out of bounds"), - "{error}" - ); - } + gather(array, &sources, allocator) } diff --git a/vortex-array/src/arrays/interleave/mod.rs b/vortex-array/src/arrays/interleave/mod.rs index 28c40c8d804..b9c5c7374ee 100644 --- a/vortex-array/src/arrays/interleave/mod.rs +++ b/vortex-array/src/arrays/interleave/mod.rs @@ -322,10 +322,12 @@ mod tests { use crate::VortexSessionExecute; use crate::arrays::Bool; use crate::arrays::BoolArray; + use crate::arrays::DecimalArray; use crate::arrays::Primitive; use crate::arrays::PrimitiveArray; use crate::arrays::SliceArray; use crate::assert_arrays_eq; + use crate::dtype::DecimalDType; use crate::dtype::PType; static SESSION: LazyLock = LazyLock::new(VortexSession::empty); @@ -337,16 +339,25 @@ mod tests { ThirdValue, } + /// The value type, which picks the execution kernel. + #[derive(Clone, Copy)] + enum ValueKind { + Bool, + Primitive, + Decimal, + } + #[rstest] #[case::array_selector(LazyChild::ArraySelector)] #[case::row_selector(LazyChild::RowSelector)] #[case::third_value(LazyChild::ThirdValue)] fn executes_lazy_children( #[case] lazy_child: LazyChild, - #[values(false, true)] boolean: bool, + #[values(ValueKind::Bool, ValueKind::Primitive, ValueKind::Decimal)] value_kind: ValueKind, ) -> VortexResult<()> { - let (mut values, expected) = if boolean { - ( + let decimal_dtype = DecimalDType::new(9, 2); + let (mut values, expected) = match value_kind { + ValueKind::Bool => ( vec![ BoolArray::from_iter([true, false]).into_array(), BoolArray::from_iter([false, true]).into_array(), @@ -354,9 +365,8 @@ mod tests { ], BoolArray::from_iter([None, Some(true), Some(true), Some(false), Some(false)]) .into_array(), - ) - } else { - ( + ), + ValueKind::Primitive => ( vec![ PrimitiveArray::from_iter([10i32, 11]).into_array(), PrimitiveArray::from_iter([20i32, 21]).into_array(), @@ -364,7 +374,19 @@ mod tests { ], PrimitiveArray::from_option_iter([None, Some(10i32), Some(21), Some(30), Some(11)]) .into_array(), - ) + ), + ValueKind::Decimal => ( + vec![ + DecimalArray::from_iter([10i32, 11], decimal_dtype).into_array(), + DecimalArray::from_iter([20i32, 21], decimal_dtype).into_array(), + DecimalArray::from_option_iter([Some(30i32), None], decimal_dtype).into_array(), + ], + DecimalArray::from_option_iter( + [None, Some(10i32), Some(21), Some(30), Some(11)], + decimal_dtype, + ) + .into_array(), + ), }; let mut array_indices = PrimitiveArray::from_iter([2u8, 0, 1, 2, 0]).into_array(); let mut row_indices = PrimitiveArray::from_iter([1u32, 0, 1, 0, 1]).into_array(); @@ -384,10 +406,9 @@ mod tests { !matches!(lazy_child, LazyChild::RowSelector) ); for (i, value) in values.iter().enumerate() { - let matches = if boolean { - value.is::() - } else { - value.is::() + let matches = match value_kind { + ValueKind::Bool => value.is::(), + ValueKind::Primitive | ValueKind::Decimal => value.is::(), }; assert_eq!( matches,