Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion vortex-array/src/expr/exprs/between.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
86 changes: 49 additions & 37 deletions vortex-array/src/expr/exprs/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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),
Copy link
Contributor

@joseph-isaacs joseph-isaacs Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does or map to or kleene here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, since or() now defaults to strict null handling, we have to use or_kleene() here to keep the current behavior. Plus, Kleene is actually better for stats since it's more aggressive with pruning when there are nulls

Operator::Or | Operator::KleeneOr => Some(and_kleene(
lhs.stat_falsification(catalog)?,
rhs.stat_falsification(catalog)?,
)),
Expand All @@ -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))
Expand All @@ -264,6 +259,8 @@ impl VTable for Binary {
| Operator::Lte
| Operator::And
| Operator::Or
| Operator::KleeneAnd
| Operator::KleeneOr
);

!infallible
Expand Down Expand Up @@ -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
Expand All @@ -447,7 +451,7 @@ where
I: IntoIterator<Item = Expression>,
{
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.
Expand All @@ -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
Expand All @@ -483,7 +494,7 @@ where
I: IntoIterator<Item = Expression>,
{
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.
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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)
");
Expand All @@ -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)
");
Expand Down Expand Up @@ -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())
Expand Down
12 changes: 9 additions & 3 deletions vortex-array/src/expr/exprs/not/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
Comment on lines +118 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not correct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aparently this is redundant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why include it?


/// Creates an expression that logically inverts boolean values.
///
/// Returns the logical negation of the input boolean expression.
Expand All @@ -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)]
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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(),
Expand Down
16 changes: 16 additions & 0 deletions vortex-array/src/expr/exprs/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -70,6 +74,8 @@ impl From<Operator> 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,
Expand Down Expand Up @@ -97,6 +103,8 @@ impl From<BinaryOp> 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,
Expand All @@ -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 => "*",
Expand All @@ -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
Expand All @@ -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,
}
}
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion vortex-array/src/expr/forms/extract_conjuncts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub fn conjuncts(expr: &Expression) -> Vec<Expression> {

fn conjuncts_impl(expr: &Expression, conjuncts: &mut Vec<Expression>) {
if let Some(operator) = expr.as_opt::<Binary>()
&& *operator == Operator::And
&& *operator == Operator::KleeneAnd
{
conjuncts_impl(expr.child(0), conjuncts);
conjuncts_impl(expr.child(1), conjuncts);
Expand Down
2 changes: 1 addition & 1 deletion vortex-array/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn split_conjunction(expr: &Expression) -> Vec<Expression> {

fn split_inner(expr: &Expression, exprs: &mut Vec<Expression>) {
match expr.as_opt::<Binary>() {
Some(operator) if *operator == Operator::And => {
Some(operator) if *operator == Operator::KleeneAnd => {
Copy link
Contributor

@joseph-isaacs joseph-isaacs Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for changing this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that Operator::And now represents Standard logic , but Vortex’s query engine was built assuming Kleene logic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about regular and?

split_inner(expr.child(0), exprs);
split_inner(expr.child(1), exprs);
}
Expand Down
2 changes: 2 additions & 0 deletions vortex-proto/proto/expr.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ message BinaryOpts {
Sub = 9;
Mul = 10;
Div = 11;
KleeneAnd = 12;
KleeneOr = 13;
}
}

Expand Down
6 changes: 6 additions & 0 deletions vortex-proto/src/generated/vortex.expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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,
}
}
Expand Down
Loading