Skip to main content

vortex_bytebool/
compute.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use num_traits::AsPrimitive;
5use vortex_array::ArrayRef;
6use vortex_array::ArrayView;
7use vortex_array::ExecutionCtx;
8use vortex_array::IntoArray;
9use vortex_array::arrays::Constant;
10use vortex_array::arrays::PrimitiveArray;
11use vortex_array::arrays::dict::TakeExecute;
12use vortex_array::buffer::BufferHandle;
13use vortex_array::dtype::DType;
14use vortex_array::match_each_integer_ptype;
15use vortex_array::scalar_fn::fns::binary::BooleanKernel;
16use vortex_array::scalar_fn::fns::binary::kleene_boolean_buffer_scalar;
17use vortex_array::scalar_fn::fns::binary::kleene_boolean_buffers;
18use vortex_array::scalar_fn::fns::cast::CastKernel;
19use vortex_array::scalar_fn::fns::cast::CastReduce;
20use vortex_array::scalar_fn::fns::mask::MaskReduce;
21use vortex_array::scalar_fn::fns::operators::Operator;
22use vortex_array::validity::Validity;
23use vortex_buffer::BitBuffer;
24use vortex_buffer::ByteBuffer;
25use vortex_error::VortexResult;
26use vortex_error::vortex_err;
27
28use super::ByteBool;
29
30impl CastReduce for ByteBool {
31    fn cast(array: ArrayView<'_, Self>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
32        // ByteBool is essentially a bool array stored as bytes
33        // The main difference from BoolArray is the storage format
34        // For casting, we can decode to canonical (BoolArray) and let it handle the cast
35        // If just changing nullability, we can optimize
36        if !dtype.is_boolean() {
37            return Ok(None);
38        }
39
40        let Some(new_validity) = array
41            .validity()?
42            .trivially_cast_nullability(dtype.nullability(), array.len())?
43        else {
44            return Ok(None);
45        };
46
47        Ok(Some(
48            ByteBool::new(array.buffer().clone(), new_validity).into_array(),
49        ))
50    }
51}
52
53impl CastKernel for ByteBool {
54    fn cast(
55        array: ArrayView<'_, Self>,
56        dtype: &DType,
57        ctx: &mut ExecutionCtx,
58    ) -> VortexResult<Option<ArrayRef>> {
59        // Only handle nullability changes here; non-bool targets fall through to canonicalization.
60        if !dtype.is_boolean() {
61            return Ok(None);
62        }
63
64        let new_validity =
65            array
66                .validity()?
67                .cast_nullability(dtype.nullability(), array.len(), ctx)?;
68
69        Ok(Some(
70            ByteBool::new(array.buffer().clone(), new_validity).into_array(),
71        ))
72    }
73}
74
75impl MaskReduce for ByteBool {
76    fn mask(array: ArrayView<'_, Self>, mask: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
77        Ok(Some(
78            ByteBool::new(
79                array.buffer().clone(),
80                array.validity()?.and(Validity::Array(mask.clone()))?,
81            )
82            .into_array(),
83        ))
84    }
85}
86
87impl TakeExecute for ByteBool {
88    fn take(
89        array: ArrayView<'_, Self>,
90        indices: &ArrayRef,
91        ctx: &mut ExecutionCtx,
92    ) -> VortexResult<Option<ArrayRef>> {
93        let indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
94        let values = array.truthy_bytes();
95
96        // This handles combining validity from both source array and nullable indices
97        let validity = array.validity()?.take(&indices.clone().into_array())?;
98
99        let taken = match_each_integer_ptype!(indices.ptype(), |I| {
100            indices
101                .as_slice::<I>()
102                .iter()
103                .map(|&idx| {
104                    let idx: usize = idx.as_();
105                    values[idx]
106                })
107                .collect::<ByteBuffer>()
108        });
109
110        Ok(Some(
111            ByteBool::new(BufferHandle::new_host(taken), validity).into_array(),
112        ))
113    }
114}
115
116impl BooleanKernel for ByteBool {
117    fn boolean(
118        lhs: ArrayView<'_, Self>,
119        rhs: &ArrayRef,
120        operator: Operator,
121        ctx: &mut ExecutionCtx,
122    ) -> VortexResult<Option<ArrayRef>> {
123        let nullability = lhs.dtype().nullability() | rhs.dtype().nullability();
124        let lhs_values = truthy_bit_buffer(lhs);
125
126        if let Some(rhs) = rhs.as_opt::<Constant>() {
127            let rhs = rhs
128                .scalar()
129                .as_bool_opt()
130                .ok_or_else(|| vortex_err!("expected boolean scalar"))?;
131            return kleene_boolean_buffer_scalar(
132                lhs_values,
133                lhs.validity()?,
134                &rhs,
135                operator,
136                nullability,
137                ctx,
138            )
139            .map(Some);
140        }
141
142        let Some(rhs) = rhs.as_opt::<ByteBool>() else {
143            return Ok(None);
144        };
145
146        kleene_boolean_buffers(
147            lhs_values,
148            lhs.validity()?,
149            truthy_bit_buffer(rhs),
150            rhs.validity()?,
151            operator,
152            nullability,
153            ctx,
154        )
155        .map(Some)
156    }
157}
158
159fn truthy_bit_buffer(array: ArrayView<'_, ByteBool>) -> BitBuffer {
160    let bytes = array.truthy_bytes();
161    BitBuffer::collect_bool(bytes.len(), |idx| bytes[idx] != 0)
162}
163
164#[cfg(test)]
165mod tests {
166    use std::sync::LazyLock;
167
168    use rstest::rstest;
169    use vortex_array::VortexSessionExecute;
170    use vortex_array::array_session;
171    use vortex_array::arrays::BoolArray;
172    use vortex_array::assert_arrays_eq;
173    use vortex_array::builtins::ArrayBuiltins;
174    use vortex_array::compute::conformance::cast::test_cast_conformance;
175    use vortex_array::compute::conformance::consistency::test_array_consistency;
176    use vortex_array::compute::conformance::filter::test_filter_conformance;
177    use vortex_array::compute::conformance::mask::test_mask_conformance;
178    use vortex_array::compute::conformance::take::test_take_conformance;
179    use vortex_array::dtype::DType;
180    use vortex_array::dtype::Nullability;
181    use vortex_array::scalar_fn::fns::operators::Operator;
182    use vortex_error::vortex_err;
183    use vortex_session::VortexSession;
184
185    use super::*;
186    use crate::ByteBoolArray;
187
188    static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
189        let session = array_session();
190        crate::initialize(&session);
191        session
192    });
193
194    fn bb(v: Vec<bool>) -> ByteBoolArray {
195        ByteBool::from_vec(v, Validity::AllValid)
196    }
197
198    fn bb_opt(v: Vec<Option<bool>>) -> ByteBoolArray {
199        ByteBool::from_option_vec(v)
200    }
201
202    #[test]
203    fn test_slice() {
204        let original = vec![Some(true), Some(true), None, Some(false), None];
205        let vortex_arr = bb_opt(original);
206
207        let sliced_arr = vortex_arr.slice(1..4).unwrap();
208
209        let expected = bb_opt(vec![Some(true), None, Some(false)]);
210        assert_arrays_eq!(
211            sliced_arr,
212            expected.into_array(),
213            &mut SESSION.create_execution_ctx()
214        );
215    }
216
217    #[test]
218    fn test_compare_all_equal() {
219        let lhs = bb(vec![true; 5]);
220        let rhs = bb(vec![true; 5]);
221
222        let arr = lhs
223            .into_array()
224            .binary(rhs.into_array(), Operator::Eq)
225            .unwrap();
226
227        let expected = bb(vec![true; 5]);
228        assert_arrays_eq!(
229            arr,
230            expected.into_array(),
231            &mut SESSION.create_execution_ctx()
232        );
233    }
234
235    #[test]
236    fn test_compare_all_different() {
237        let lhs = bb(vec![false; 5]);
238        let rhs = bb(vec![true; 5]);
239
240        let arr = lhs
241            .into_array()
242            .binary(rhs.into_array(), Operator::Eq)
243            .unwrap();
244
245        let expected = bb(vec![false; 5]);
246        assert_arrays_eq!(
247            arr,
248            expected.into_array(),
249            &mut SESSION.create_execution_ctx()
250        );
251    }
252
253    #[test]
254    fn test_compare_with_nulls() {
255        let lhs = bb(vec![true; 5]);
256        let rhs = bb_opt(vec![Some(true), Some(true), Some(true), Some(false), None]);
257
258        let arr = lhs
259            .into_array()
260            .binary(rhs.into_array(), Operator::Eq)
261            .unwrap();
262
263        let expected = bb_opt(vec![Some(true), Some(true), Some(true), Some(false), None]);
264        assert_arrays_eq!(
265            arr,
266            expected.into_array(),
267            &mut SESSION.create_execution_ctx()
268        );
269    }
270
271    #[test]
272    fn test_boolean_kernel_kleene() -> VortexResult<()> {
273        let lhs = bb_opt(vec![Some(false), Some(true), None, Some(false), None]);
274        let rhs = bb_opt(vec![None, None, Some(true), Some(false), None]).into_array();
275        let mut ctx = SESSION.create_execution_ctx();
276
277        let and_result =
278            <ByteBool as BooleanKernel>::boolean(lhs.as_view(), &rhs, Operator::And, &mut ctx)?
279                .ok_or_else(|| vortex_err!("ByteBool should handle ByteBool boolean AND"))?;
280        assert_arrays_eq!(
281            and_result,
282            BoolArray::from_iter([Some(false), None, None, Some(false), None]),
283            &mut ctx
284        );
285
286        let or_result =
287            <ByteBool as BooleanKernel>::boolean(lhs.as_view(), &rhs, Operator::Or, &mut ctx)?
288                .ok_or_else(|| vortex_err!("ByteBool should handle ByteBool boolean OR"))?;
289        assert_arrays_eq!(
290            or_result,
291            BoolArray::from_iter([None, Some(true), Some(true), Some(false), None]),
292            &mut ctx
293        );
294
295        Ok(())
296    }
297
298    #[test]
299    fn test_mask_byte_bool() {
300        test_mask_conformance(&bb(vec![true, false, true, true, false]).into_array());
301        test_mask_conformance(
302            &bb_opt(vec![Some(true), Some(true), None, Some(false), None]).into_array(),
303        );
304    }
305
306    #[test]
307    fn test_filter_byte_bool() {
308        test_filter_conformance(&bb(vec![true, false, true, true, false]).into_array());
309        test_filter_conformance(
310            &bb_opt(vec![Some(true), Some(true), None, Some(false), None]).into_array(),
311        );
312    }
313
314    #[rstest]
315    #[case(bb(vec![true, false, true, true, false]))]
316    #[case(bb_opt(vec![Some(true), Some(true), None, Some(false), None]))]
317    #[case(bb(vec![true, false]))]
318    #[case(bb(vec![true]))]
319    fn test_take_byte_bool_conformance(#[case] array: ByteBoolArray) {
320        test_take_conformance(&array.into_array());
321    }
322
323    #[test]
324    fn test_cast_bytebool_to_nullable() {
325        let array = bb(vec![true, false, true, false]);
326        let casted = array
327            .into_array()
328            .cast(DType::Bool(Nullability::Nullable))
329            .unwrap();
330        assert_eq!(casted.dtype(), &DType::Bool(Nullability::Nullable));
331        assert_eq!(casted.len(), 4);
332    }
333
334    #[rstest]
335    #[case(bb(vec![true, false, true, true, false]))]
336    #[case(bb_opt(vec![Some(true), Some(false), None, Some(true), None]))]
337    #[case(bb(vec![false]))]
338    #[case(bb(vec![true]))]
339    #[case(bb_opt(vec![Some(true), None]))]
340    fn test_cast_bytebool_conformance(#[case] array: ByteBoolArray) {
341        test_cast_conformance(&array.into_array());
342    }
343
344    #[rstest]
345    #[case::non_nullable(bb(vec![true, false, true, true, false]))]
346    #[case::nullable(bb_opt(vec![Some(true), Some(false), None, Some(true), None]))]
347    #[case::all_true(bb(vec![true, true, true, true]))]
348    #[case::all_false(bb(vec![false, false, false, false]))]
349    #[case::single_true(bb(vec![true]))]
350    #[case::single_false(bb(vec![false]))]
351    #[case::single_null(bb_opt(vec![None]))]
352    #[case::mixed_with_nulls(bb_opt(vec![Some(true), None, Some(false), None, Some(true)]))]
353    fn test_bytebool_consistency(#[case] array: ByteBoolArray) {
354        let ctx = &mut array_session().create_execution_ctx();
355        test_array_consistency(&array.into_array(), ctx);
356    }
357}