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

vortex-data / vortex / 16935267080

13 Aug 2025 11:00AM UTC coverage: 24.312% (-63.3%) from 87.658%
16935267080

Pull #4226

github

web-flow
Merge 81b48c7fb into baa6ea202
Pull Request #4226: Support converting TimestampTZ to and from duckdb

0 of 2 new or added lines in 1 file covered. (0.0%)

20666 existing lines in 469 files now uncovered.

8726 of 35892 relevant lines covered (24.31%)

147.74 hits per line

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

0.0
/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

UNCOV
18
static BOOLEAN_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
×
UNCOV
19
    let compute = ComputeFn::new("boolean".into(), ArcRef::new_ref(&Boolean));
×
UNCOV
20
    for kernel in inventory::iter::<BooleanKernelRef> {
×
UNCOV
21
        compute.register_kernel(kernel.0.clone());
×
UNCOV
22
    }
×
UNCOV
23
    compute
×
UNCOV
24
});
×
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]
UNCOV
32
pub fn and(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
×
UNCOV
33
    boolean(lhs, rhs, BooleanOperator::And)
×
UNCOV
34
}
×
35

36
/// Point-wise Kleene logical _and_ between two Boolean arrays.
37
///
38
/// See also [BooleanOperator::AndKleene]
UNCOV
39
pub fn and_kleene(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
×
UNCOV
40
    boolean(lhs, rhs, BooleanOperator::AndKleene)
×
UNCOV
41
}
×
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]
UNCOV
49
pub fn or(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
×
UNCOV
50
    boolean(lhs, rhs, BooleanOperator::Or)
×
UNCOV
51
}
×
52

53
/// Point-wise Kleene logical _or_ between two Boolean arrays.
54
///
55
/// See also [BooleanOperator::OrKleene]
UNCOV
56
pub fn or_kleene(lhs: &dyn Array, rhs: &dyn Array) -> VortexResult<ArrayRef> {
×
UNCOV
57
    boolean(lhs, rhs, BooleanOperator::OrKleene)
×
UNCOV
58
}
×
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".
UNCOV
64
pub fn boolean(lhs: &dyn Array, rhs: &dyn Array, op: BooleanOperator) -> VortexResult<ArrayRef> {
×
UNCOV
65
    BOOLEAN_FN
×
UNCOV
66
        .invoke(&InvocationArgs {
×
UNCOV
67
            inputs: &[lhs.into(), rhs.into()],
×
UNCOV
68
            options: &op,
×
UNCOV
69
        })?
×
UNCOV
70
        .unwrap_array()
×
UNCOV
71
}
×
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> {
89
    pub const fn lift(&'static self) -> BooleanKernelRef {
×
90
        BooleanKernelRef(ArcRef::new_ref(self))
×
91
    }
×
92
}
93

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

104
struct Boolean;
105

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

UNCOV
114
        let rhs_is_constant = rhs.is_constant();
×
115

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

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

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

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

UNCOV
149
        log::debug!(
×
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.
UNCOV
157
        Ok(arrow_boolean(lhs.to_array(), rhs.to_array(), operator)?.into())
×
UNCOV
158
    }
×
159

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

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

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

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

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

UNCOV
190
        Ok(lhs.len())
×
UNCOV
191
    }
×
192

UNCOV
193
    fn is_elementwise(&self) -> bool {
×
UNCOV
194
        true
×
UNCOV
195
    }
×
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 {
UNCOV
245
    fn as_any(&self) -> &dyn Any {
×
UNCOV
246
        self
×
UNCOV
247
    }
×
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

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

UNCOV
275
        Ok(BooleanArgs {
×
UNCOV
276
            lhs,
×
UNCOV
277
            rhs,
×
UNCOV
278
            operator: *operator,
×
UNCOV
279
        })
×
UNCOV
280
    }
×
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.
UNCOV
287
pub(crate) fn arrow_boolean(
×
UNCOV
288
    lhs: ArrayRef,
×
UNCOV
289
    rhs: ArrayRef,
×
UNCOV
290
    operator: BooleanOperator,
×
UNCOV
291
) -> VortexResult<ArrayRef> {
×
UNCOV
292
    let nullable = lhs.dtype().is_nullable() || rhs.dtype().is_nullable();
×
293

UNCOV
294
    let lhs = lhs.into_arrow(&DataType::Boolean)?.as_boolean().clone();
×
UNCOV
295
    let rhs = rhs.into_arrow(&DataType::Boolean)?.as_boolean().clone();
×
296

UNCOV
297
    let array = match operator {
×
UNCOV
298
        BooleanOperator::And => arrow_arith::boolean::and(&lhs, &rhs)?,
×
UNCOV
299
        BooleanOperator::AndKleene => arrow_arith::boolean::and_kleene(&lhs, &rhs)?,
×
UNCOV
300
        BooleanOperator::Or => arrow_arith::boolean::or(&lhs, &rhs)?,
×
UNCOV
301
        BooleanOperator::OrKleene => arrow_arith::boolean::or_kleene(&lhs, &rhs)?,
×
302
    };
303

UNCOV
304
    Ok(ArrayRef::from_arrow(&array, nullable))
×
UNCOV
305
}
×
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