diff --git a/vortex-array/src/expr/exprs/between.rs b/vortex-array/src/expr/exprs/between.rs index dc4ac6fcc37..f31249917bb 100644 --- a/vortex-array/src/expr/exprs/between.rs +++ b/vortex-array/src/expr/exprs/between.rs @@ -168,7 +168,7 @@ impl VTable for Between { let rhs = Binary.new_expr(options.upper_strict.to_operator().into(), [arr, upper]); Binary - .new_expr(Operator::And, [lhs, rhs]) + .new_expr(Operator::KleeneAnd, [lhs, rhs]) .stat_falsification(catalog) } diff --git a/vortex-array/src/expr/exprs/binary.rs b/vortex-array/src/expr/exprs/binary.rs index 54301850546..873062e59e1 100644 --- a/vortex-array/src/expr/exprs/binary.rs +++ b/vortex-array/src/expr/exprs/binary.rs @@ -13,13 +13,6 @@ use vortex_session::VortexSession; use crate::ArrayRef; use crate::compute; -use crate::compute::add; -use crate::compute::and_kleene; -use crate::compute::compare; -use crate::compute::div; -use crate::compute::mul; -use crate::compute::or_kleene; -use crate::compute::sub; use crate::expr::Arity; use crate::expr::ChildName; use crate::expr::ExecutionArgs; @@ -108,18 +101,20 @@ impl VTable for Binary { }; match op { - Operator::Eq => compare(lhs, rhs, compute::Operator::Eq), - Operator::NotEq => compare(lhs, rhs, compute::Operator::NotEq), - Operator::Lt => compare(lhs, rhs, compute::Operator::Lt), - Operator::Lte => compare(lhs, rhs, compute::Operator::Lte), - Operator::Gt => compare(lhs, rhs, compute::Operator::Gt), - Operator::Gte => compare(lhs, rhs, compute::Operator::Gte), - Operator::And => and_kleene(lhs, rhs), - Operator::Or => or_kleene(lhs, rhs), - Operator::Add => add(lhs, rhs), - Operator::Sub => sub(lhs, rhs), - Operator::Mul => mul(lhs, rhs), - Operator::Div => div(lhs, rhs), + Operator::Eq => compute::compare(lhs, rhs, compute::Operator::Eq), + Operator::NotEq => compute::compare(lhs, rhs, compute::Operator::NotEq), + Operator::Lt => compute::compare(lhs, rhs, compute::Operator::Lt), + Operator::Lte => compute::compare(lhs, rhs, compute::Operator::Lte), + Operator::Gt => compute::compare(lhs, rhs, compute::Operator::Gt), + Operator::Gte => compute::compare(lhs, rhs, compute::Operator::Gte), + Operator::And => compute::and(lhs, rhs), + Operator::Or => compute::or(lhs, rhs), + Operator::KleeneAnd => compute::and_kleene(lhs, rhs), + Operator::KleeneOr => compute::or_kleene(lhs, rhs), + Operator::Add => compute::add(lhs, rhs), + Operator::Sub => compute::sub(lhs, rhs), + Operator::Mul => compute::mul(lhs, rhs), + Operator::Div => compute::div(lhs, rhs), } } @@ -215,12 +210,12 @@ impl VTable for Binary { Some(with_nan_predicate(lhs, rhs, min_max_check, catalog)) } - Operator::And => lhs + Operator::And | Operator::KleeneAnd => lhs .stat_falsification(catalog) .into_iter() .chain(rhs.stat_falsification(catalog)) - .reduce(or), - Operator::Or => Some(and( + .reduce(or_kleene), + Operator::Or | Operator::KleeneOr => Some(and_kleene( lhs.stat_falsification(catalog)?, rhs.stat_falsification(catalog)?, )), @@ -238,8 +233,8 @@ impl VTable for Binary { Ok(match operator { // AND and OR are kleene logic. - Operator::And => None, - Operator::Or => None, + Operator::KleeneAnd => None, + Operator::KleeneOr => None, _ => { // All other binary operators are null if either side is null. Some(and(lhs, rhs)) @@ -264,6 +259,8 @@ impl VTable for Binary { | Operator::Lte | Operator::And | Operator::Or + | Operator::KleeneAnd + | Operator::KleeneOr ); !infallible @@ -436,6 +433,13 @@ pub fn or(lhs: Expression, rhs: Expression) -> Expression { .vortex_expect("Failed to create Or binary expression") } +/// Create a new [`Binary`] using the [`KleeneOr`](crate::expr::exprs::operators::Operator::KleeneOr) operator. +pub fn or_kleene(lhs: Expression, rhs: Expression) -> Expression { + Binary + .try_new_expr(Operator::KleeneOr, [lhs, rhs]) + .vortex_expect("Failed to create KleeneOr binary expression") +} + /// Collects a list of `or`ed values into a single expression using a balanced tree. /// /// This creates a balanced binary tree to avoid deep nesting that could cause @@ -447,7 +451,7 @@ where I: IntoIterator, { let exprs: Vec<_> = iter.into_iter().collect(); - balanced_reduce(exprs, or) + balanced_reduce(exprs, or_kleene) } /// Create a new [`Binary`] using the [`And`](crate::expr::exprs::operators::Operator::And) operator. @@ -472,6 +476,13 @@ pub fn and(lhs: Expression, rhs: Expression) -> Expression { .vortex_expect("Failed to create And binary expression") } +/// Create a new [`Binary`] using the [`KleeneAnd`](crate::expr::exprs::operators::Operator::KleeneAnd) operator. +pub fn and_kleene(lhs: Expression, rhs: Expression) -> Expression { + Binary + .try_new_expr(Operator::KleeneAnd, [lhs, rhs]) + .vortex_expect("Failed to create KleeneAnd binary expression") +} + /// Collects a list of `and`ed values into a single expression using a balanced tree. /// /// This creates a balanced binary tree to avoid deep nesting that could cause @@ -483,7 +494,7 @@ where I: IntoIterator, { let exprs: Vec<_> = iter.into_iter().collect(); - balanced_reduce(exprs, and) + balanced_reduce(exprs, and_kleene) } /// Helper function to reduce a list of expressions into a balanced binary tree. @@ -554,6 +565,7 @@ mod tests { use super::*; use crate::assert_arrays_eq; + use crate::compute::compare; use crate::expr::Expression; use crate::expr::exprs::get_item::col; use crate::expr::exprs::literal::lit; @@ -564,12 +576,12 @@ mod tests { let values = vec![lit(1), lit(2), lit(3), lit(4), lit(5)]; insta::assert_snapshot!(and_collect(values.into_iter()).unwrap().display_tree(), @r" - vortex.binary(and) - ├── lhs: vortex.binary(and) + vortex.binary(kleene_and) + ├── lhs: vortex.binary(kleene_and) │ ├── lhs: vortex.literal(1i32) │ └── rhs: vortex.literal(2i32) - └── rhs: vortex.binary(and) - ├── lhs: vortex.binary(and) + └── rhs: vortex.binary(kleene_and) + ├── lhs: vortex.binary(kleene_and) │ ├── lhs: vortex.literal(3i32) │ └── rhs: vortex.literal(4i32) └── rhs: vortex.literal(5i32) @@ -578,11 +590,11 @@ mod tests { // 4 elements: and(and(1, 2), and(3, 4)) - perfectly balanced let values = vec![lit(1), lit(2), lit(3), lit(4)]; insta::assert_snapshot!(and_collect(values.into_iter()).unwrap().display_tree(), @r" - vortex.binary(and) - ├── lhs: vortex.binary(and) + vortex.binary(kleene_and) + ├── lhs: vortex.binary(kleene_and) │ ├── lhs: vortex.literal(1i32) │ └── rhs: vortex.literal(2i32) - └── rhs: vortex.binary(and) + └── rhs: vortex.binary(kleene_and) ├── lhs: vortex.literal(3i32) └── rhs: vortex.literal(4i32) "); @@ -601,11 +613,11 @@ mod tests { // 4 elements: or(or(1, 2), or(3, 4)) - perfectly balanced let values = vec![lit(1), lit(2), lit(3), lit(4)]; insta::assert_snapshot!(or_collect(values.into_iter()).unwrap().display_tree(), @r" - vortex.binary(or) - ├── lhs: vortex.binary(or) + vortex.binary(kleene_or) + ├── lhs: vortex.binary(kleene_or) │ ├── lhs: vortex.literal(1i32) │ └── rhs: vortex.literal(2i32) - └── rhs: vortex.binary(or) + └── rhs: vortex.binary(kleene_or) ├── lhs: vortex.literal(3i32) └── rhs: vortex.literal(4i32) "); @@ -756,7 +768,7 @@ mod tests { .unwrap() .into_array(); - let expr = or(col("a"), col("b")); + let expr = or_kleene(col("a"), col("b")); let result = struct_arr.apply(&expr).unwrap(); assert_arrays_eq!(result, BoolArray::from_iter([Some(true)]).into_array()) diff --git a/vortex-array/src/expr/exprs/not/mod.rs b/vortex-array/src/expr/exprs/not/mod.rs index 208af4d8ee9..67d51c144f2 100644 --- a/vortex-array/src/expr/exprs/not/mod.rs +++ b/vortex-array/src/expr/exprs/not/mod.rs @@ -114,6 +114,11 @@ impl VTable for Not { } } +/// Creates an expression that logically inverts boolean values using Kleene logic. +pub fn not_kleene(operand: Expression) -> Expression { + Not.new_expr(EmptyOptions, vec![operand]) +} + /// Creates an expression that logically inverts boolean values. /// /// Returns the logical negation of the input boolean expression. @@ -123,7 +128,7 @@ impl VTable for Not { /// let expr = not(root()); /// ``` pub fn not(operand: Expression) -> Expression { - Not.new_expr(EmptyOptions, vec![operand]) + not_kleene(operand) } #[cfg(test)] @@ -132,6 +137,7 @@ mod tests { use vortex_dtype::Nullability; use super::not; + use super::not_kleene; use crate::ToCanonical; use crate::arrays::BoolArray; use crate::expr::exprs::get_item::col; @@ -141,7 +147,7 @@ mod tests { #[test] fn invert_booleans() { - let not_expr = not(root()); + let not_expr = not_kleene(root()); let bools = BoolArray::from_iter([false, true, false, false, true, true]); assert_eq!( bools @@ -167,7 +173,7 @@ mod tests { #[test] fn dtype() { - let not_expr = not(root()); + let not_expr = not_kleene(root()); let dtype = DType::Bool(Nullability::NonNullable); assert_eq!( not_expr.return_dtype(&dtype).unwrap(), diff --git a/vortex-array/src/expr/exprs/operators.rs b/vortex-array/src/expr/exprs/operators.rs index 2dee5475844..ac05f09849c 100644 --- a/vortex-array/src/expr/exprs/operators.rs +++ b/vortex-array/src/expr/exprs/operators.rs @@ -36,6 +36,10 @@ pub enum Operator { And, /// Boolean OR (∨). Or, + /// Boolean AND (∧) with Kleene logic. + KleeneAnd, + /// Boolean OR (∨) with Kleene logic. + KleeneOr, /// The sum of the arguments. /// /// Errs at runtime if the sum would overflow or underflow. @@ -70,6 +74,8 @@ impl From for BinaryOp { Operator::Lte => BinaryOp::Lte, Operator::And => BinaryOp::And, Operator::Or => BinaryOp::Or, + Operator::KleeneAnd => BinaryOp::KleeneAnd, + Operator::KleeneOr => BinaryOp::KleeneOr, Operator::Add => BinaryOp::Add, Operator::Sub => BinaryOp::Sub, Operator::Mul => BinaryOp::Mul, @@ -97,6 +103,8 @@ impl From for Operator { BinaryOp::Lte => Operator::Lte, BinaryOp::And => Operator::And, BinaryOp::Or => Operator::Or, + BinaryOp::KleeneAnd => Operator::KleeneAnd, + BinaryOp::KleeneOr => Operator::KleeneOr, BinaryOp::Add => Operator::Add, BinaryOp::Sub => Operator::Sub, BinaryOp::Mul => Operator::Mul, @@ -116,6 +124,8 @@ impl Display for Operator { Operator::Lte => "<=", Operator::And => "and", Operator::Or => "or", + Operator::KleeneAnd => "kleene_and", + Operator::KleeneOr => "kleene_or", Operator::Add => "+", Operator::Sub => "-", Operator::Mul => "*", @@ -136,6 +146,8 @@ impl Operator { Operator::Lte => Some(Operator::Gt), Operator::And | Operator::Or + | Operator::KleeneAnd + | Operator::KleeneOr | Operator::Add | Operator::Sub | Operator::Mul @@ -147,6 +159,8 @@ impl Operator { match self { Operator::And => Some(Operator::Or), Operator::Or => Some(Operator::And), + Operator::KleeneAnd => Some(Operator::KleeneOr), + Operator::KleeneOr => Some(Operator::KleeneAnd), _ => None, } } @@ -162,6 +176,8 @@ impl Operator { Operator::Lte => Some(Operator::Gte), Operator::And => Some(Operator::And), Operator::Or => Some(Operator::Or), + Operator::KleeneAnd => Some(Operator::KleeneAnd), + Operator::KleeneOr => Some(Operator::KleeneOr), Operator::Add => Some(Operator::Add), Operator::Mul => Some(Operator::Mul), Operator::Sub | Operator::Div => None, diff --git a/vortex-array/src/expr/forms/extract_conjuncts.rs b/vortex-array/src/expr/forms/extract_conjuncts.rs index 64bc04540e1..89f670ceb88 100644 --- a/vortex-array/src/expr/forms/extract_conjuncts.rs +++ b/vortex-array/src/expr/forms/extract_conjuncts.rs @@ -20,7 +20,7 @@ pub fn conjuncts(expr: &Expression) -> Vec { fn conjuncts_impl(expr: &Expression, conjuncts: &mut Vec) { if let Some(operator) = expr.as_opt::() - && *operator == Operator::And + && *operator == Operator::KleeneAnd { conjuncts_impl(expr.child(0), conjuncts); conjuncts_impl(expr.child(1), conjuncts); diff --git a/vortex-array/src/expr/mod.rs b/vortex-array/src/expr/mod.rs index 99ad896d409..ae36599fdad 100644 --- a/vortex-array/src/expr/mod.rs +++ b/vortex-array/src/expr/mod.rs @@ -76,7 +76,7 @@ pub fn split_conjunction(expr: &Expression) -> Vec { fn split_inner(expr: &Expression, exprs: &mut Vec) { match expr.as_opt::() { - Some(operator) if *operator == Operator::And => { + Some(operator) if *operator == Operator::KleeneAnd => { split_inner(expr.child(0), exprs); split_inner(expr.child(1), exprs); } diff --git a/vortex-proto/proto/expr.proto b/vortex-proto/proto/expr.proto index 4540bce0a63..3ae4fcf7af2 100644 --- a/vortex-proto/proto/expr.proto +++ b/vortex-proto/proto/expr.proto @@ -53,6 +53,8 @@ message BinaryOpts { Sub = 9; Mul = 10; Div = 11; + KleeneAnd = 12; + KleeneOr = 13; } } diff --git a/vortex-proto/src/generated/vortex.expr.rs b/vortex-proto/src/generated/vortex.expr.rs index f3b6d2cf624..ebb06d47f3b 100644 --- a/vortex-proto/src/generated/vortex.expr.rs +++ b/vortex-proto/src/generated/vortex.expr.rs @@ -64,6 +64,8 @@ pub mod binary_opts { Sub = 9, Mul = 10, Div = 11, + KleeneAnd = 12, + KleeneOr = 13, } impl BinaryOp { /// String value of the enum field names used in the ProtoBuf definition. @@ -84,6 +86,8 @@ pub mod binary_opts { Self::Sub => "Sub", Self::Mul => "Mul", Self::Div => "Div", + Self::KleeneAnd => "KleeneAnd", + Self::KleeneOr => "KleeneOr", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -101,6 +105,8 @@ pub mod binary_opts { "Sub" => Some(Self::Sub), "Mul" => Some(Self::Mul), "Div" => Some(Self::Div), + "KleeneAnd" => Some(Self::KleeneAnd), + "KleeneOr" => Some(Self::KleeneOr), _ => None, } }