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_compute::logical::LogicalNot;
7use vortex_dtype::DType;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_vector::Datum;
12
13use crate::ArrayRef;
14use crate::compute::invert;
15use crate::expr::Arity;
16use crate::expr::ChildName;
17use crate::expr::EmptyOptions;
18use crate::expr::ExecutionArgs;
19use crate::expr::ExprId;
20use crate::expr::Expression;
21use crate::expr::VTable;
22use crate::expr::VTableExt;
23
24/// Expression that logically inverts boolean values.
25pub struct Not;
26
27impl VTable for Not {
28    type Options = EmptyOptions;
29
30    fn id(&self) -> ExprId {
31        ExprId::from("vortex.not")
32    }
33
34    fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
35        Ok(Some(vec![]))
36    }
37
38    fn deserialize(&self, _metadata: &[u8]) -> VortexResult<Self::Options> {
39        Ok(EmptyOptions)
40    }
41
42    fn arity(&self, _options: &Self::Options) -> Arity {
43        Arity::Exact(1)
44    }
45
46    fn child_name(&self, _options: &Self::Options, child_idx: usize) -> ChildName {
47        match child_idx {
48            0 => ChildName::from("input"),
49            _ => unreachable!("Invalid child index {} for Not expression", child_idx),
50        }
51    }
52
53    fn fmt_sql(
54        &self,
55        _options: &Self::Options,
56        expr: &Expression,
57        f: &mut Formatter<'_>,
58    ) -> std::fmt::Result {
59        write!(f, "not(")?;
60        expr.child(0).fmt_sql(f)?;
61        write!(f, ")")
62    }
63
64    fn return_dtype(&self, _options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
65        let child_dtype = &arg_dtypes[0];
66        if !matches!(child_dtype, DType::Bool(_)) {
67            vortex_bail!(
68                "Not expression expects a boolean child, got: {}",
69                child_dtype
70            );
71        }
72        Ok(child_dtype.clone())
73    }
74
75    fn evaluate(
76        &self,
77        _options: &Self::Options,
78        expr: &Expression,
79        scope: &ArrayRef,
80    ) -> VortexResult<ArrayRef> {
81        let child_result = expr.child(0).evaluate(scope)?;
82        invert(&child_result)
83    }
84
85    fn execute(&self, _data: &Self::Options, mut args: ExecutionArgs) -> VortexResult<Datum> {
86        let child = args.datums.pop().vortex_expect("Missing input child");
87        Ok(child.into_bool().not().into())
88    }
89
90    fn is_null_sensitive(&self, _options: &Self::Options) -> bool {
91        false
92    }
93
94    fn is_fallible(&self, _options: &Self::Options) -> bool {
95        false
96    }
97}
98
99/// Creates an expression that logically inverts boolean values.
100///
101/// Returns the logical negation of the input boolean expression.
102///
103/// ```rust
104/// # use vortex_array::expr::{not, root};
105/// let expr = not(root());
106/// ```
107pub fn not(operand: Expression) -> Expression {
108    Not.new_expr(EmptyOptions, vec![operand])
109}
110
111#[cfg(test)]
112mod tests {
113    use vortex_dtype::DType;
114    use vortex_dtype::Nullability;
115
116    use super::not;
117    use crate::ToCanonical;
118    use crate::arrays::BoolArray;
119    use crate::expr::exprs::get_item::col;
120    use crate::expr::exprs::get_item::get_item;
121    use crate::expr::exprs::root::root;
122    use crate::expr::test_harness;
123
124    #[test]
125    fn invert_booleans() {
126        let not_expr = not(root());
127        let bools = BoolArray::from_iter([false, true, false, false, true, true]);
128        assert_eq!(
129            not_expr
130                .evaluate(&bools.to_array())
131                .unwrap()
132                .to_bool()
133                .bit_buffer()
134                .iter()
135                .collect::<Vec<_>>(),
136            vec![true, false, true, true, false, false]
137        );
138    }
139
140    #[test]
141    fn test_display_order_of_operations() {
142        let a = not(get_item("a", root()));
143        let b = get_item("a", not(root()));
144        assert_ne!(a.to_string(), b.to_string());
145        assert_eq!(a.to_string(), "not($.a)");
146        assert_eq!(b.to_string(), "not($).a");
147    }
148
149    #[test]
150    fn dtype() {
151        let not_expr = not(root());
152        let dtype = DType::Bool(Nullability::NonNullable);
153        assert_eq!(
154            not_expr.return_dtype(&dtype).unwrap(),
155            DType::Bool(Nullability::NonNullable)
156        );
157
158        let dtype = test_harness::struct_dtype();
159        assert_eq!(
160            not(col("bool1")).return_dtype(&dtype).unwrap(),
161            DType::Bool(Nullability::NonNullable)
162        );
163    }
164}