Skip to main content

vortex_array/expr/exprs/not/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4mod kernel;
5
6use std::fmt::Formatter;
7
8pub use kernel::*;
9use vortex_dtype::DType;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_session::VortexSession;
14
15use crate::Array;
16use crate::ArrayRef;
17use crate::IntoArray;
18use crate::arrays::BoolArray;
19use crate::arrays::BoolVTable;
20use crate::arrays::ConstantArray;
21use crate::builtins::ArrayBuiltins;
22use crate::expr::Arity;
23use crate::expr::ChildName;
24use crate::expr::EmptyOptions;
25use crate::expr::ExecutionArgs;
26use crate::expr::ExprId;
27use crate::expr::Expression;
28use crate::expr::VTable;
29use crate::expr::VTableExt;
30use crate::scalar::Scalar;
31
32/// Expression that logically inverts boolean values.
33pub struct Not;
34
35impl VTable for Not {
36    type Options = EmptyOptions;
37
38    fn id(&self) -> ExprId {
39        ExprId::from("vortex.not")
40    }
41
42    fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
43        Ok(Some(vec![]))
44    }
45
46    fn deserialize(
47        &self,
48        _metadata: &[u8],
49        _session: &VortexSession,
50    ) -> VortexResult<Self::Options> {
51        Ok(EmptyOptions)
52    }
53
54    fn arity(&self, _options: &Self::Options) -> Arity {
55        Arity::Exact(1)
56    }
57
58    fn child_name(&self, _options: &Self::Options, child_idx: usize) -> ChildName {
59        match child_idx {
60            0 => ChildName::from("input"),
61            _ => unreachable!("Invalid child index {} for Not expression", child_idx),
62        }
63    }
64
65    fn fmt_sql(
66        &self,
67        _options: &Self::Options,
68        expr: &Expression,
69        f: &mut Formatter<'_>,
70    ) -> std::fmt::Result {
71        write!(f, "not(")?;
72        expr.child(0).fmt_sql(f)?;
73        write!(f, ")")
74    }
75
76    fn return_dtype(&self, _options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
77        let child_dtype = &arg_dtypes[0];
78        if !matches!(child_dtype, DType::Bool(_)) {
79            vortex_bail!(
80                "Not expression expects a boolean child, got: {}",
81                child_dtype
82            );
83        }
84        Ok(child_dtype.clone())
85    }
86
87    fn execute(&self, _data: &Self::Options, mut args: ExecutionArgs) -> VortexResult<ArrayRef> {
88        let child = args.inputs.pop().vortex_expect("Missing input child");
89
90        // For constant boolean
91        if let Some(scalar) = child.as_constant() {
92            let value = match scalar.as_bool().value() {
93                Some(b) => Scalar::bool(!b, child.dtype().nullability()),
94                None => Scalar::null(child.dtype().clone()),
95            };
96            return Ok(ConstantArray::new(value, args.row_count).into_array());
97        }
98
99        // For boolean array
100        if let Some(bool) = child.as_opt::<BoolVTable>() {
101            return Ok(BoolArray::new(!bool.to_bit_buffer(), bool.validity()?).into_array());
102        }
103
104        // Otherwise, execute and try again
105        child.execute::<ArrayRef>(args.ctx)?.not()
106    }
107
108    fn is_null_sensitive(&self, _options: &Self::Options) -> bool {
109        false
110    }
111
112    fn is_fallible(&self, _options: &Self::Options) -> bool {
113        false
114    }
115}
116
117/// Creates an expression that logically inverts boolean values.
118///
119/// Returns the logical negation of the input boolean expression.
120///
121/// ```rust
122/// # use vortex_array::expr::{not, root};
123/// let expr = not(root());
124/// ```
125pub fn not(operand: Expression) -> Expression {
126    Not.new_expr(EmptyOptions, vec![operand])
127}
128
129#[cfg(test)]
130mod tests {
131    use vortex_dtype::DType;
132    use vortex_dtype::Nullability;
133
134    use super::not;
135    use crate::ToCanonical;
136    use crate::arrays::BoolArray;
137    use crate::expr::exprs::get_item::col;
138    use crate::expr::exprs::get_item::get_item;
139    use crate::expr::exprs::root::root;
140    use crate::expr::test_harness;
141
142    #[test]
143    fn invert_booleans() {
144        let not_expr = not(root());
145        let bools = BoolArray::from_iter([false, true, false, false, true, true]);
146        assert_eq!(
147            bools
148                .to_array()
149                .apply(&not_expr)
150                .unwrap()
151                .to_bool()
152                .to_bit_buffer()
153                .iter()
154                .collect::<Vec<_>>(),
155            vec![true, false, true, true, false, false]
156        );
157    }
158
159    #[test]
160    fn test_display_order_of_operations() {
161        let a = not(get_item("a", root()));
162        let b = get_item("a", not(root()));
163        assert_ne!(a.to_string(), b.to_string());
164        assert_eq!(a.to_string(), "not($.a)");
165        assert_eq!(b.to_string(), "not($).a");
166    }
167
168    #[test]
169    fn dtype() {
170        let not_expr = not(root());
171        let dtype = DType::Bool(Nullability::NonNullable);
172        assert_eq!(
173            not_expr.return_dtype(&dtype).unwrap(),
174            DType::Bool(Nullability::NonNullable)
175        );
176
177        let dtype = test_harness::struct_dtype();
178        assert_eq!(
179            not(col("bool1")).return_dtype(&dtype).unwrap(),
180            DType::Bool(Nullability::NonNullable)
181        );
182    }
183}