Skip to content

Commit ad6ee9d

Browse files
authored
Reduce lazy masks into arrays with all-valid metadata (#10016)
## Summary Allows lazy masks to attach directly as validity when an encoding can inspect validity through metadata and the input is `AllValid` or `NonNullable`. Array-backed validity and encodings that have not opted in retain the existing lazy-mask fallback. The reduction rule, in pseudocode, is: ```text Mask(Array(values, validity = AllValid), m) -> Array(values, validity = m) Mask(Array(values, validity = NonNullable), m) -> Array(values, validity = m) ``` This applies when the input encoding opts into `VALIDITY_IS_METADATA_ONLY` and supports mask reduction. `m` is a non-nullable Boolean array with the same length as the input, and it may be lazy. The reduction reuses the values and attaches `m` as validity without executing it. The output dtype is nullable. The identity is `AllValid AND m = m`, so no intermediate validity array is needed. ## Changes Adds `MaskReduce::VALIDITY_IS_METADATA_ONLY`, defaulting to false, and enables it for Boolean, primitive, decimal, string, list, struct, map, and ByteBool encodings. String, decimal, and list reducers preserve existing buffers and children when rebuilding. List-view masking also preserves its metadata without revalidating the zero-copy flag. Two focused tests cover direct attachment to an all-valid primitive array and the fallback for array-backed validity, including an all-true bitmap. Existing constant-mask tests remain unchanged, and all tests stay inline. The Boolean device-buffer fix landed in #10018. Validation before the rebase: 168 focused comparison, mask, and RowFn tests passed on the combined stack through #9979, along with `cargo clippy -p vortex-array --all-targets --all-features -- -D warnings`. The rebase preserves this PR's patches. Tests, formatting, and benchmarks were not rerun locally after the rebase. --------- Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 2ab66d7 commit ad6ee9d

12 files changed

Lines changed: 142 additions & 45 deletions

File tree

‎encodings/bytebool/src/compute.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ impl CastKernel for ByteBool {
7373
}
7474

7575
impl MaskReduce for ByteBool {
76+
const VALIDITY_IS_METADATA_ONLY: bool = true;
77+
7678
fn mask(array: ArrayView<'_, Self>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
7779
Ok(Some(
7880
ByteBool::new(

‎vortex-array/src/arrays/bool/compute/mask.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1212
use crate::validity::Validity;
1313

1414
impl MaskReduce for Bool {
15+
const VALIDITY_IS_METADATA_ONLY: bool = true;
16+
1517
fn mask(array: ArrayView<'_, Bool>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
1618
Ok(Some(
1719
BoolArray::try_new_from_handle(

‎vortex-array/src/arrays/decimal/compute/mask.rs‎

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,22 @@ use crate::IntoArray;
88
use crate::array::ArrayView;
99
use crate::arrays::Decimal;
1010
use crate::arrays::DecimalArray;
11-
use crate::match_each_decimal_value_type;
1211
use crate::scalar_fn::fns::mask::MaskReduce;
1312
use crate::validity::Validity;
1413

1514
impl MaskReduce for Decimal {
15+
const VALIDITY_IS_METADATA_ONLY: bool = true;
16+
1617
fn mask(array: ArrayView<'_, Decimal>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
17-
Ok(Some(match_each_decimal_value_type!(
18-
array.values_type(),
19-
|D| {
20-
// SAFETY: masking the validity does not affect the invariants
21-
unsafe {
22-
DecimalArray::new_unchecked(
23-
array.buffer::<D>(),
24-
array.decimal_dtype(),
25-
array.validity()?.and(Validity::Array(mask.clone()))?,
26-
)
27-
}
28-
.into_array()
29-
}
30-
)))
18+
// SAFETY: the values and decimal type are unchanged, and masking only removes valid rows.
19+
Ok(Some(unsafe {
20+
DecimalArray::new_unchecked_handle(
21+
array.buffer_handle().clone(),
22+
array.values_type(),
23+
array.decimal_dtype(),
24+
array.validity()?.and(Validity::Array(mask.clone()))?,
25+
)
26+
.into_array()
27+
}))
3128
}
3229
}

‎vortex-array/src/arrays/fixed_size_list/compute/mask.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1414
use crate::validity::Validity;
1515

1616
impl MaskReduce for FixedSizeList {
17+
const VALIDITY_IS_METADATA_ONLY: bool = true;
18+
1719
fn mask(
1820
array: ArrayView<'_, FixedSizeList>,
1921
mask: &ArrayRef,

‎vortex-array/src/arrays/list/compute/mask.rs‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1313
use crate::validity::Validity;
1414

1515
impl MaskReduce for List {
16+
const VALIDITY_IS_METADATA_ONLY: bool = true;
17+
1618
fn mask(array: ArrayView<'_, List>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
17-
ListArray::try_new(
18-
array.elements().clone(),
19-
array.offsets().clone(),
20-
array.validity()?.and(Validity::Array(mask.clone()))?,
21-
)
22-
.map(|a| Some(a.into_array()))
19+
// SAFETY: elements and offsets are unchanged, and masking only removes valid rows.
20+
Ok(Some(unsafe {
21+
ListArray::new_unchecked(
22+
array.elements().clone(),
23+
array.offsets().clone(),
24+
array.validity()?.and(Validity::Array(mask.clone()))?,
25+
)
26+
.into_array()
27+
}))
2328
}
2429
}

‎vortex-array/src/arrays/listview/compute/mask.rs‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,39 @@ use vortex_error::VortexResult;
55

66
use crate::ArrayRef;
77
use crate::IntoArray;
8+
use crate::array::ArrayParts;
89
use crate::array::ArrayView;
910
use crate::arrays::ListView;
1011
use crate::arrays::ListViewArray;
1112
use crate::arrays::listview::ListViewArraySlotsExt;
13+
use crate::arrays::listview::ListViewData;
1214
use crate::scalar_fn::fns::mask::MaskReduce;
1315
use crate::validity::Validity;
1416

1517
impl MaskReduce for ListView {
18+
const VALIDITY_IS_METADATA_ONLY: bool = true;
19+
1620
fn mask(array: ArrayView<'_, ListView>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
17-
// SAFETY: masking the validity does not affect the invariants
21+
let validity = array.validity()?.and(Validity::Array(mask.clone()))?;
22+
let slots = ListViewData::make_slots(
23+
array.elements(),
24+
array.offsets(),
25+
array.sizes(),
26+
&validity,
27+
array.len(),
28+
);
29+
let parts = ArrayParts::new(
30+
ListView,
31+
array.dtype().as_nullable(),
32+
array.len(),
33+
array.data().clone(),
34+
)
35+
.with_slots(slots);
36+
37+
// SAFETY: elements, offsets, sizes, and their metadata are unchanged. Masking only removes
38+
// valid rows, so the existing zero-copy-to-list guarantee still holds.
1839
Ok(Some(
19-
unsafe {
20-
ListViewArray::new_unchecked(
21-
array.elements().clone(),
22-
array.offsets().clone(),
23-
array.sizes().clone(),
24-
array.validity()?.and(Validity::Array(mask.clone()))?,
25-
)
26-
.with_zero_copy_to_list(array.is_zero_copy_to_list())
27-
}
28-
.into_array(),
40+
unsafe { ListViewArray::from_parts_unchecked(parts) }.into_array(),
2941
))
3042
}
3143
}

‎vortex-array/src/arrays/map/compute/mask.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use crate::scalar_fn::fns::mask::MaskKernel;
1515
use crate::scalar_fn::fns::mask::MaskReduce;
1616

1717
impl MaskReduce for Map {
18+
const VALIDITY_IS_METADATA_ONLY: bool = true;
19+
1820
fn mask(array: ArrayView<'_, Self>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
1921
let Some(entries) =
2022
<ListView as MaskReduce>::mask(array.entries().as_::<ListView>(), mask)?

‎vortex-array/src/arrays/primitive/compute/mask.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1212
use crate::validity::Validity;
1313

1414
impl MaskReduce for Primitive {
15+
const VALIDITY_IS_METADATA_ONLY: bool = true;
16+
1517
fn mask(array: ArrayView<'_, Primitive>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
1618
// SAFETY: validity and data buffer still have same length
1719
Ok(Some(unsafe {

‎vortex-array/src/arrays/struct_/compute/mask.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1313
use crate::validity::Validity;
1414

1515
impl MaskReduce for Struct {
16+
const VALIDITY_IS_METADATA_ONLY: bool = true;
17+
1618
fn mask(array: ArrayView<'_, Struct>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
1719
StructArray::try_new_with_dtype(
1820
array.iter_unmasked_fields().cloned(),

‎vortex-array/src/arrays/varbin/compute/mask.rs‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ use crate::scalar_fn::fns::mask::MaskReduce;
1313
use crate::validity::Validity;
1414

1515
impl MaskReduce for VarBin {
16+
const VALIDITY_IS_METADATA_ONLY: bool = true;
17+
1618
fn mask(array: ArrayView<'_, VarBin>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
17-
Ok(Some(
18-
VarBinArray::try_new(
19+
// SAFETY: offsets and bytes are unchanged, and masking only removes valid rows.
20+
Ok(Some(unsafe {
21+
VarBinArray::new_unchecked_from_handle(
1922
array.offsets().clone(),
20-
array.bytes().clone(),
23+
array.bytes_handle().clone(),
2124
array.dtype().as_nullable(),
2225
array.validity()?.and(Validity::Array(mask.clone()))?,
23-
)?
24-
.into_array(),
25-
))
26+
)
27+
.into_array()
28+
}))
2629
}
2730
}
2831

0 commit comments

Comments
 (0)