vortex_array/expr/exprs/
not.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Formatter;
5
6use vortex_dtype::DType;
7use vortex_error::{VortexResult, vortex_bail};
8
9use crate::ArrayRef;
10use crate::compute::invert;
11use crate::expr::{ChildName, ExprId, Expression, ExpressionView, VTable, VTableExt};
12
13/// Expression that logically inverts boolean values.
14pub struct Not;
15
16impl VTable for Not {
17    type Instance = ();
18
19    fn id(&self) -> ExprId {
20        ExprId::new_ref("vortex.not")
21    }
22
23    fn serialize(&self, _instance: &Self::Instance) -> VortexResult<Option<Vec<u8>>> {
24        Ok(Some(vec![]))
25    }
26
27    fn deserialize(&self, _metadata: &[u8]) -> VortexResult<Option<Self::Instance>> {
28        Ok(Some(()))
29    }
30
31    fn validate(&self, expr: &ExpressionView<Self>) -> VortexResult<()> {
32        if expr.children().len() != 1 {
33            vortex_bail!(
34                "Not expression expects exactly one child, got {}",
35                expr.children().len()
36            );
37        }
38        Ok(())
39    }
40
41    fn child_name(&self, _instance: &Self::Instance, child_idx: usize) -> ChildName {
42        match child_idx {
43            0 => ChildName::from("input"),
44            _ => unreachable!("Invalid child index {} for Not expression", child_idx),
45        }
46    }
47
48    fn fmt_sql(&self, expr: &ExpressionView<Self>, f: &mut Formatter<'_>) -> std::fmt::Result {
49        write!(f, "not(")?;
50        expr.child(0).fmt_sql(f)?;
51        write!(f, ")")
52    }
53
54    fn return_dtype(&self, expr: &ExpressionView<Self>, scope: &DType) -> VortexResult<DType> {
55        let child_dtype = expr.child(0).return_dtype(scope)?;
56        if !matches!(child_dtype, DType::Bool(_)) {
57            vortex_bail!(
58                "Not expression expects a boolean child, got: {}",
59                child_dtype
60            );
61        }
62        Ok(child_dtype)
63    }
64
65    fn evaluate(&self, expr: &ExpressionView<Self>, scope: &ArrayRef) -> VortexResult<ArrayRef> {
66        let child_result = expr.child(0).evaluate(scope)?;
67        invert(&child_result)
68    }
69}
70
71/// Creates an expression that logically inverts boolean values.
72///
73/// Returns the logical negation of the input boolean expression.
74///
75/// ```rust
76/// # use vortex_array::expr::{not, root};
77/// let expr = not(root());
78/// ```
79pub fn not(operand: Expression) -> Expression {
80    Not.new_expr((), vec![operand])
81}
82
83#[cfg(test)]
84mod tests {
85    use vortex_dtype::{DType, Nullability};
86
87    use super::not;
88    use crate::ToCanonical;
89    use crate::arrays::BoolArray;
90    use crate::expr::exprs::get_item::{col, get_item};
91    use crate::expr::exprs::root::root;
92    use crate::expr::test_harness;
93
94    #[test]
95    fn invert_booleans() {
96        let not_expr = not(root());
97        let bools = BoolArray::from_iter([false, true, false, false, true, true]);
98        assert_eq!(
99            not_expr
100                .evaluate(&bools.to_array())
101                .unwrap()
102                .to_bool()
103                .bit_buffer()
104                .iter()
105                .collect::<Vec<_>>(),
106            vec![true, false, true, true, false, false]
107        );
108    }
109
110    #[test]
111    fn test_display_order_of_operations() {
112        let a = not(get_item("a", root()));
113        let b = get_item("a", not(root()));
114        assert_ne!(a.to_string(), b.to_string());
115        assert_eq!(a.to_string(), "not($.a)");
116        assert_eq!(b.to_string(), "not($).a");
117    }
118
119    #[test]
120    fn dtype() {
121        let not_expr = not(root());
122        let dtype = DType::Bool(Nullability::NonNullable);
123        assert_eq!(
124            not_expr.return_dtype(&dtype).unwrap(),
125            DType::Bool(Nullability::NonNullable)
126        );
127
128        let dtype = test_harness::struct_dtype();
129        assert_eq!(
130            not(col("bool1")).return_dtype(&dtype).unwrap(),
131            DType::Bool(Nullability::NonNullable)
132        );
133    }
134}