Skip to content

Commit 415c40e

Browse files
fix: Handle nulls in bool array min/max (#5163)
fix #5157 --------- Signed-off-by: Joe Isaacs <[email protected]> Signed-off-by: Robert Kruszewski <[email protected]> Co-authored-by: Joe Isaacs <[email protected]>
1 parent 625273e commit 415c40e

File tree

1 file changed

+78
-11
lines changed

1 file changed

+78
-11
lines changed

vortex-array/src/arrays/bool/compute/min_max.rs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,106 @@ use crate::register_kernel;
1313

1414
impl MinMaxKernel for BoolVTable {
1515
fn min_max(&self, array: &BoolArray) -> VortexResult<Option<MinMaxResult>> {
16-
let x = match array.validity_mask() {
16+
let mask = array.validity_mask();
17+
let true_non_null = match mask {
1718
Mask::AllTrue(_) => array.bit_buffer().clone(),
1819
Mask::AllFalse(_) => return Ok(None),
19-
Mask::Values(v) => array.bit_buffer().bitand(v.bit_buffer()),
20+
Mask::Values(ref v) => array.bit_buffer().bitand(v.bit_buffer()),
2021
};
2122

2223
// TODO(ngates): we should be able to bail out earlier as soon as we have one true and
2324
// one false value.
24-
let mut slices = x.set_slices();
25+
let mut true_slices = true_non_null.set_slices();
2526
// If there are no slices, then all values are false
2627
// if there is a single slice that covers the entire array, then all values are true
2728
// otherwise, we have a mix of true and false values
2829

29-
let Some(slice) = slices.next() else {
30+
let Some(slice) = true_slices.next() else {
3031
// all false
3132
return Ok(Some(MinMaxResult {
32-
min: Scalar::new(array.dtype().clone(), false.into()),
33-
max: Scalar::new(array.dtype().clone(), false.into()),
33+
min: Scalar::bool(false, array.dtype().nullability()),
34+
max: Scalar::bool(false, array.dtype().nullability()),
3435
}));
3536
};
36-
if slice.0 == 0 && slice.1 == x.len() {
37+
if slice.0 == 0 && slice.1 == array.len() {
3738
// all true
3839
return Ok(Some(MinMaxResult {
39-
min: Scalar::new(array.dtype().clone(), true.into()),
40-
max: Scalar::new(array.dtype().clone(), true.into()),
40+
min: Scalar::bool(true, array.dtype().nullability()),
41+
max: Scalar::bool(true, array.dtype().nullability()),
4142
}));
4243
};
4344

45+
// If the non null true slice doesn't cover the whole array we need to check for valid false values
46+
match mask {
47+
// if the mask is all true or all false we don't need to look for false values
48+
Mask::AllTrue(_) | Mask::AllFalse(_) => {}
49+
Mask::Values(v) => {
50+
let false_non_null = (!array.bit_buffer()).bitand(v.bit_buffer());
51+
let mut false_slices = false_non_null.set_slices();
52+
53+
let Some(_) = false_slices.next() else {
54+
// In this case we don't have any false values which means we are all true and null
55+
return Ok(Some(MinMaxResult {
56+
min: Scalar::bool(true, array.dtype().nullability()),
57+
max: Scalar::bool(true, array.dtype().nullability()),
58+
}));
59+
};
60+
}
61+
}
62+
4463
Ok(Some(MinMaxResult {
45-
min: Scalar::new(array.dtype().clone(), false.into()),
46-
max: Scalar::new(array.dtype().clone(), true.into()),
64+
min: Scalar::bool(false, array.dtype().nullability()),
65+
max: Scalar::bool(true, array.dtype().nullability()),
4766
}))
4867
}
4968
}
5069

5170
register_kernel!(MinMaxKernelAdapter(BoolVTable).lift());
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use vortex_dtype::{DType, Nullability};
75+
use vortex_scalar::Scalar;
76+
77+
use crate::arrays::BoolArray;
78+
use crate::compute::{MinMaxResult, min_max};
79+
80+
#[test]
81+
fn test_min_max_nulls() {
82+
let dtype = DType::Bool(Nullability::Nullable);
83+
assert_eq!(
84+
min_max(BoolArray::from_iter(vec![Some(true), Some(true), None, None]).as_ref())
85+
.unwrap(),
86+
Some(MinMaxResult {
87+
min: Scalar::new(dtype.clone(), true.into()),
88+
max: Scalar::new(dtype.clone(), true.into()),
89+
})
90+
);
91+
92+
assert_eq!(
93+
min_max(BoolArray::from_iter(vec![None, Some(true), Some(true)]).as_ref()).unwrap(),
94+
Some(MinMaxResult {
95+
min: Scalar::new(dtype.clone(), true.into()),
96+
max: Scalar::new(dtype.clone(), true.into()),
97+
})
98+
);
99+
100+
assert_eq!(
101+
min_max(BoolArray::from_iter(vec![None, Some(true), Some(true), None]).as_ref())
102+
.unwrap(),
103+
Some(MinMaxResult {
104+
min: Scalar::new(dtype.clone(), true.into()),
105+
max: Scalar::new(dtype.clone(), true.into()),
106+
})
107+
);
108+
109+
assert_eq!(
110+
min_max(BoolArray::from_iter(vec![Some(false), Some(false), None, None]).as_ref())
111+
.unwrap(),
112+
Some(MinMaxResult {
113+
min: Scalar::new(dtype.clone(), false.into()),
114+
max: Scalar::new(dtype, false.into()),
115+
})
116+
);
117+
}
118+
}

0 commit comments

Comments
 (0)