• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16595455362

29 Jul 2025 12:00PM UTC coverage: 82.182% (+0.4%) from 81.796%
16595455362

Pull #4036

github

web-flow
Merge 28b120788 into 261aabd6a
Pull Request #4036: varbinview builder buffer deduplication

147 of 155 new or added lines in 2 files covered. (94.84%)

404 existing lines in 34 files now uncovered.

44415 of 54045 relevant lines covered (82.18%)

167869.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.05
/vortex-array/src/compute/boolean.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::any::Any;
5
use std::sync::LazyLock;
6

7
use arcref::ArcRef;
8
use arrow_array::cast::AsArray;
9
use arrow_schema::DataType;
10
use vortex_dtype::DType;
11
use vortex_error::{VortexError, VortexExpect, VortexResult, vortex_bail, vortex_err};
12

13
use crate::arrow::{FromArrowArray, IntoArrowArray};
14
use crate::compute::{ComputeFn, ComputeFnVTable, InvocationArgs, Kernel, Options, Output};
15
use crate::vtable::VTable;
16
use crate::{Array, ArrayRef};
17

18
static BOOLEAN_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
701✔
19
    let compute = ComputeFn::new("boolean".into(), ArcRef::new_ref(&Boolean));
701✔
20
    for kernel in inventory::iter::<BooleanKernelRef> {
1,402✔
21
        compute.register_kernel(kernel.0.clone());
701✔
22
    }
701✔
23
    compute
701✔
24
});
701✔
25

26
/// Point-wise logical _and_ between two Boolean arrays.
27
///
28
/// This method uses Arrow-style null propagation rather than the Kleene logic semantics. This
29
/// semantics is also known as "Bochvar logic" and "weak Kleene logic".
30
///
31
/// See also [BooleanOperator::And]
32
pub fn and(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
308✔
33
    boolean(lhs, rhs, BooleanOperator::And)
308✔
34
}
308✔
35

36
/// Point-wise Kleene logical _and_ between two Boolean arrays.
37
///
38
/// See also [BooleanOperator::AndKleene]
39
pub fn and_kleene(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
1,090✔
40
    boolean(lhs, rhs, BooleanOperator::AndKleene)
1,090✔
41
}
1,090✔
42

43
/// Point-wise logical _or_ between two Boolean arrays.
44
///
45
/// This method uses Arrow-style null propagation rather than the Kleene logic semantics. This
46
/// semantics is also known as "Bochvar logic" and "weak Kleene logic".
47
///
48
/// See also [BooleanOperator::Or]
49
pub fn or(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
1,540✔
50
    boolean(lhs, rhs, BooleanOperator::Or)
1,540✔
51
}
1,540✔
52

53
/// Point-wise Kleene logical _or_ between two Boolean arrays.
54
///
55
/// See also [BooleanOperator::OrKleene]
56
pub fn or_kleene(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
1,918✔
57
    boolean(lhs, rhs, BooleanOperator::OrKleene)
1,918✔
58
}
1,918✔
59

60
/// Point-wise logical operator between two Boolean arrays.
61
///
62
/// This method uses Arrow-style null propagation rather than the Kleene logic semantics. This
63
/// semantics is also known as "Bochvar logic" and "weak Kleene logic".
64
pub fn boolean(lhs: &dyn Array, rhs: &dyn Array, op: BooleanOperator) -> VortexResult<ArrayRef> {
5,032✔
65
    BOOLEAN_FN
5,032✔
66
        .invoke(&InvocationArgs {
5,032✔
67
            inputs: &[lhs.into(), rhs.into()],
5,032✔
68
            options: &op,
5,032✔
69
        })?
5,032✔
70
        .unwrap_array()
5,032✔
71
}
5,032✔
72

73
pub struct BooleanKernelRef(ArcRef<dyn Kernel>);
74
inventory::collect!(BooleanKernelRef);
75

76
pub trait BooleanKernel: VTable {
77
    fn boolean(
78
        &self,
79
        array: &Self::Array,
80
        other: &dyn Array,
81
        op: BooleanOperator,
82
    ) -> VortexResult<Option<ArrayRef>>;
83
}
84

85
#[derive(Debug)]
86
pub struct BooleanKernelAdapter<V: VTable>(pub V);
87

88
impl<V: VTable + BooleanKernel> BooleanKernelAdapter<V> {
UNCOV
89
    pub const fn lift(&'static self) -> BooleanKernelRef {
×
UNCOV
90
        BooleanKernelRef(ArcRef::new_ref(self))
×
UNCOV
91
    }
×
92
}
93

94
impl<V: VTable + BooleanKernel> Kernel for BooleanKernelAdapter<V> {
95
    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
3,249✔
96
        let inputs = BooleanArgs::try_from(args)?;
3,249✔
97
        let Some(array) = inputs.lhs.as_opt::<V>() else {
3,249✔
98
            return Ok(None);
55✔
99
        };
100
        Ok(V::boolean(&self.0, array, inputs.rhs, inputs.operator)?.map(|array| array.into()))
3,194✔
101
    }
3,249✔
102
}
103

104
struct Boolean;
105

106
impl ComputeFnVTable for Boolean {
107
    fn invoke(
5,035✔
108
        &self,
5,035✔
109
        args: &InvocationArgs,
5,035✔
110
        kernels: &[ArcRef<dyn Kernel>],
5,035✔
111
    ) -> VortexResult<Output> {
5,035✔
112
        let BooleanArgs { lhs, rhs, operator } = BooleanArgs::try_from(args)?;
5,035✔
113

114
        let rhs_is_constant = rhs.is_constant();
5,035✔
115

116
        // If LHS is constant, then we make sure it's on the RHS.
117
        if lhs.is_constant() && !rhs_is_constant {
5,035✔
118
            return Ok(boolean(rhs, lhs, operator)?.into());
24✔
119
        }
5,011✔
120

121
        // If the RHS is constant and the LHS is Arrow, we can't do any better than arrow_compare.
122
        if lhs.is_arrow() && (rhs.is_arrow() || rhs_is_constant) {
5,011✔
123
            return Ok(arrow_boolean(lhs.to_array(), rhs.to_array(), operator)?.into());
1,807✔
124
        }
3,204✔
125

126
        // Check if either LHS or RHS supports the operation directly.
127
        for kernel in kernels {
3,250✔
128
            if let Some(output) = kernel.invoke(args)? {
3,204✔
129
                return Ok(output);
3,158✔
130
            }
46✔
131
        }
132
        if let Some(output) = lhs.invoke(&BOOLEAN_FN, args)? {
46✔
133
            return Ok(output);
1✔
134
        }
45✔
135

136
        let inverse_args = InvocationArgs {
45✔
137
            inputs: &[rhs.into(), lhs.into()],
45✔
138
            options: &operator,
45✔
139
        };
45✔
140
        for kernel in kernels {
54✔
141
            if let Some(output) = kernel.invoke(&inverse_args)? {
45✔
142
                return Ok(output);
36✔
143
            }
9✔
144
        }
145
        if let Some(output) = rhs.invoke(&BOOLEAN_FN, &inverse_args)? {
9✔
146
            return Ok(output);
1✔
147
        }
8✔
148

149
        log::debug!(
8✔
150
            "No boolean implementation found for LHS {}, RHS {}, and operator {:?} (or inverse)",
×
151
            rhs.encoding_id(),
×
152
            lhs.encoding_id(),
×
153
            operator,
154
        );
155

156
        // If neither side implements the trait, then we delegate to Arrow compute.
157
        Ok(arrow_boolean(lhs.to_array(), rhs.to_array(), operator)?.into())
8✔
158
    }
5,035✔
159

160
    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
5,037✔
161
        let BooleanArgs { lhs, rhs, .. } = BooleanArgs::try_from(args)?;
5,037✔
162

163
        if !lhs.dtype().is_boolean()
5,037✔
164
            || !rhs.dtype().is_boolean()
5,037✔
165
            || !lhs.dtype().eq_ignore_nullability(rhs.dtype())
5,037✔
166
        {
167
            vortex_bail!(
×
168
                "Boolean operations are only supported on boolean arrays: {} and {}",
×
169
                lhs.dtype(),
×
170
                rhs.dtype()
×
171
            )
172
        }
5,037✔
173

174
        Ok(DType::Bool(
175
            (lhs.dtype().is_nullable() || rhs.dtype().is_nullable()).into(),
5,037✔
176
        ))
177
    }
5,037✔
178

179
    fn return_len(&self, args: &InvocationArgs) -> VortexResult<usize> {
5,035✔
180
        let BooleanArgs { lhs, rhs, .. } = BooleanArgs::try_from(args)?;
5,035✔
181

182
        if lhs.len() != rhs.len() {
5,035✔
183
            vortex_bail!(
×
184
                "Boolean operations aren't supported on arrays of different lengths: {} and {}",
×
185
                lhs.len(),
×
186
                rhs.len()
×
187
            )
188
        }
5,035✔
189

190
        Ok(lhs.len())
5,035✔
191
    }
5,035✔
192

193
    fn is_elementwise(&self) -> bool {
5,039✔
194
        true
5,039✔
195
    }
5,039✔
196
}
197

198
/// Operations over the nullable Boolean values.
199
///
200
/// All three operators accept and produce values from the set {true, false, and null}.
201
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202
pub enum BooleanOperator {
203
    /// Logical and, unless either value is null, in which case the result is null.
204
    ///
205
    /// | A ∧ B |       | **B** |       |       |
206
    /// |:-----:|:-----:|:-----:|:-----:|:-----:|
207
    /// |       |       | **F** | **U** | **T** |
208
    /// | **A** | **F** | F     | U     | F     |
209
    /// |       | **U** | U     | U     | U     |
210
    /// |       | **T** | F     | U     | T     |
211
    And,
212
    /// [Kleene (three-valued) logical and](https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics).
213
    ///
214
    /// | A ∧ B |       | **B** |       |       |
215
    /// |:-----:|:-----:|:-----:|:-----:|:-----:|
216
    /// |       |       | **F** | **U** | **T** |
217
    /// | **A** | **F** | F     | F     | F     |
218
    /// |       | **U** | F     | U     | U     |
219
    /// |       | **T** | F     | U     | T     |
220
    AndKleene,
221
    /// Logical or, unless either value is null, in which case the result is null.
222
    ///
223
    /// | A ∨ B |       | **B** |       |       |
224
    /// |:-----:|:-----:|:-----:|:-----:|:-----:|
225
    /// |       |       | **F** | **U** | **T** |
226
    /// | **A** | **F** | F     | U     | T     |
227
    /// |       | **U** | U     | U     | U     |
228
    /// |       | **T** | T     | U     | T     |
229
    Or,
230
    /// [Kleene (three-valued) logical or](https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics).
231
    ///
232
    /// | A ∨ B |       | **B** |       |       |
233
    /// |:-----:|:-----:|:-----:|:-----:|:-----:|
234
    /// |       |       | **F** | **U** | **T** |
235
    /// | **A** | **F** | F     | U     | T     |
236
    /// |       | **U** | U     | U     | T     |
237
    /// |       | **T** | T     | T     | T     |
238
    OrKleene,
239
    // AndNot,
240
    // AndNotKleene,
241
    // Xor,
242
}
243

244
impl Options for BooleanOperator {
245
    fn as_any(&self) -> &dyn Any {
18,356✔
246
        self
18,356✔
247
    }
18,356✔
248
}
249

250
struct BooleanArgs<'a> {
251
    lhs: &'a dyn Array,
252
    rhs: &'a dyn Array,
253
    operator: BooleanOperator,
254
}
255

256
impl<'a> TryFrom<&InvocationArgs<'a>> for BooleanArgs<'a> {
257
    type Error = VortexError;
258

259
    fn try_from(value: &InvocationArgs<'a>) -> VortexResult<Self> {
18,356✔
260
        if value.inputs.len() != 2 {
18,356✔
261
            vortex_bail!("Expected 2 inputs, found {}", value.inputs.len());
×
262
        }
18,356✔
263
        let lhs = value.inputs[0]
18,356✔
264
            .array()
18,356✔
265
            .ok_or_else(|| vortex_err!("Expected input 0 to be an array"))?;
18,356✔
266
        let rhs = value.inputs[1]
18,356✔
267
            .array()
18,356✔
268
            .ok_or_else(|| vortex_err!("Expected input 1 to be an array"))?;
18,356✔
269
        let operator = value
18,356✔
270
            .options
18,356✔
271
            .as_any()
18,356✔
272
            .downcast_ref::<BooleanOperator>()
18,356✔
273
            .vortex_expect("Expected options to be an operator");
18,356✔
274

275
        Ok(BooleanArgs {
18,356✔
276
            lhs,
18,356✔
277
            rhs,
18,356✔
278
            operator: *operator,
18,356✔
279
        })
18,356✔
280
    }
18,356✔
281
}
282

283
/// Implementation of `BinaryBooleanFn` using the Arrow crate.
284
///
285
/// Note that other encodings should handle a constant RHS value, so we can assume here that
286
/// the RHS is not constant and expand to a full array.
287
pub(crate) fn arrow_boolean(
1,815✔
288
    lhs: ArrayRef,
1,815✔
289
    rhs: ArrayRef,
1,815✔
290
    operator: BooleanOperator,
1,815✔
291
) -> VortexResult<ArrayRef> {
1,815✔
292
    let nullable = lhs.dtype().is_nullable() || rhs.dtype().is_nullable();
1,815✔
293

294
    let lhs = lhs.into_arrow(&DataType::Boolean)?.as_boolean().clone();
1,815✔
295
    let rhs = rhs.into_arrow(&DataType::Boolean)?.as_boolean().clone();
1,815✔
296

297
    let array = match operator {
1,815✔
298
        BooleanOperator::And => arrow_arith::boolean::and(&lhs, &rhs)?,
155✔
299
        BooleanOperator::AndKleene => arrow_arith::boolean::and_kleene(&lhs, &rhs)?,
144✔
300
        BooleanOperator::Or => arrow_arith::boolean::or(&lhs, &rhs)?,
1,086✔
301
        BooleanOperator::OrKleene => arrow_arith::boolean::or_kleene(&lhs, &rhs)?,
430✔
302
    };
303

304
    Ok(ArrayRef::from_arrow(&array, nullable))
1,815✔
305
}
1,815✔
306

307
#[cfg(test)]
308
mod tests {
309
    use rstest::rstest;
310

311
    use super::*;
312
    use crate::IntoArray;
313
    use crate::arrays::BoolArray;
314
    use crate::canonical::ToCanonical;
315
    #[rstest]
316
    #[case(BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)].into_iter())
317
    .into_array(), BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)].into_iter())
318
    .into_array())]
319
    #[case(BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)].into_iter()).into_array(),
320
        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)].into_iter()).into_array())]
321
    fn test_or(#[case] lhs: ArrayRef, #[case] rhs: ArrayRef) {
322
        let r = or(&lhs, &rhs).unwrap();
323

324
        let r = r.to_bool().unwrap().into_array();
325

326
        let v0 = r.scalar_at(0).unwrap().as_bool().value();
327
        let v1 = r.scalar_at(1).unwrap().as_bool().value();
328
        let v2 = r.scalar_at(2).unwrap().as_bool().value();
329
        let v3 = r.scalar_at(3).unwrap().as_bool().value();
330

331
        assert!(v0.unwrap());
332
        assert!(v1.unwrap());
333
        assert!(v2.unwrap());
334
        assert!(!v3.unwrap());
335
    }
336

337
    #[rstest]
338
    #[case(BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)].into_iter())
339
    .into_array(), BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)].into_iter())
340
    .into_array())]
341
    #[case(BoolArray::from_iter([Some(true), Some(false), Some(true), Some(false)].into_iter()).into_array(),
342
        BoolArray::from_iter([Some(true), Some(true), Some(false), Some(false)].into_iter()).into_array())]
343
    fn test_and(#[case] lhs: ArrayRef, #[case] rhs: ArrayRef) {
344
        let r = and(&lhs, &rhs).unwrap().to_bool().unwrap().into_array();
345

346
        let v0 = r.scalar_at(0).unwrap().as_bool().value();
347
        let v1 = r.scalar_at(1).unwrap().as_bool().value();
348
        let v2 = r.scalar_at(2).unwrap().as_bool().value();
349
        let v3 = r.scalar_at(3).unwrap().as_bool().value();
350

351
        assert!(v0.unwrap());
352
        assert!(!v1.unwrap());
353
        assert!(!v2.unwrap());
354
        assert!(!v3.unwrap());
355
    }
356
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc