Skip to content

Commit

Permalink
feat: add UnboundPredicate::negate()
Browse files Browse the repository at this point in the history
Issue: apache#150
  • Loading branch information
sdd committed Mar 4, 2024
1 parent 470e6f0 commit 4f9cf46
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 0 deletions.
23 changes: 23 additions & 0 deletions crates/iceberg/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,26 @@ impl Display for PredicateOperator {
}
}
}

impl PredicateOperator {

/// Returns the predicate that is the inverse of self
pub fn negate(self) -> PredicateOperator {
match self {
PredicateOperator::IsNull => PredicateOperator::NotNull,
PredicateOperator::NotNull => PredicateOperator::IsNull,
PredicateOperator::IsNan => PredicateOperator::NotNan,
PredicateOperator::NotNan => PredicateOperator::IsNan,
PredicateOperator::LessThan => PredicateOperator::GreaterThanOrEq,
PredicateOperator::LessThanOrEq => PredicateOperator::GreaterThan,
PredicateOperator::GreaterThan => PredicateOperator::LessThanOrEq,
PredicateOperator::GreaterThanOrEq => PredicateOperator::LessThan,
PredicateOperator::Eq => PredicateOperator::NotEq,
PredicateOperator::NotEq => PredicateOperator::Eq,
PredicateOperator::In => PredicateOperator::NotIn,
PredicateOperator::NotIn => PredicateOperator::In,
PredicateOperator::StartsWith => PredicateOperator::NotStartsWith,
PredicateOperator::NotStartsWith => PredicateOperator::StartsWith,
}
}
}
81 changes: 81 additions & 0 deletions crates/iceberg/src/expr/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use crate::expr::{BoundReference, PredicateOperator, UnboundReference};
use crate::spec::Datum;
use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Not;
Expand Down Expand Up @@ -101,6 +102,24 @@ pub struct SetExpression<T: Debug> {
literals: HashSet<Datum>,
}

impl<T: Debug> SetExpression<T> {
pub(crate) fn new(op: PredicateOperator, term: T, literals: HashSet<Datum>) -> Self {
Self { op, term, literals }
}
}

impl<T: Display + Debug> Display for SetExpression<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} ({})",
self.term,
self.op,
self.literals.iter().join(", ")
)
}
}

/// Unbound predicate expression before binding to a schema.
#[derive(Debug)]
pub enum UnboundPredicate {
Expand Down Expand Up @@ -195,6 +214,68 @@ impl UnboundPredicate {
pub fn or(self, other: UnboundPredicate) -> UnboundPredicate {
UnboundPredicate::Or(LogicalExpression::new([Box::new(self), Box::new(other)]))
}

/// Returns a predicate representing the negation ('NOT') of this one,
/// by using inverse predicates rather than wrapping in a `NOT`.
/// Used for `NOT` elimination.
///
/// # Example
///
/// ```rust
/// use std::ops::Bound::Unbounded;
/// use iceberg::expr::BoundPredicate::Unary;
/// use iceberg::expr::{LogicalExpression, UnboundPredicate, UnboundReference};
/// use iceberg::spec::Datum;
/// let expr1 = UnboundReference::new("a").less_than(Datum::long(10));
/// let expr2 = UnboundReference::new("b").less_than(Datum::long(5)).and(UnboundReference::new("c").less_than(Datum::long(10)));
///
/// let result = expr1.negate();
/// assert_eq!(&format!("{result}"), "a >= 10");
///
/// let result = expr2.negate();
/// assert_eq!(&format!("{result}"), "(b >= 5) OR (c >= 10)");
/// ```
/// Returns a predicate representing the negation ('NOT') of this one,
/// by using inverse predicates rather than wrapping in a `NOT`.
/// Used for `NOT` elimination.
///
/// # Example
///
/// ```rust
/// use std::ops::Bound::Unbounded;
/// use iceberg::expr::BoundPredicate::Unary;
/// use iceberg::expr::{LogicalExpression, UnboundPredicate, UnboundReference};
/// use iceberg::spec::Datum;
/// let expr1 = UnboundReference::new("a").less_than(Datum::long(10));
/// let expr2 = UnboundReference::new("b").less_than(Datum::long(5)).and(UnboundReference::new("c").less_than(Datum::long(10)));
///
/// let result = expr1.negate();
/// assert_eq!(&format!("{result}"), "a >= 10");
///
/// let result = expr2.negate();
/// assert_eq!(&format!("{result}"), "(b >= 5) OR (c >= 10)");
/// ```
pub fn negate(self) -> UnboundPredicate {
match self {
UnboundPredicate::And(expr) => UnboundPredicate::Or(LogicalExpression::new(
expr.inputs.map(|expr| Box::new(expr.negate())),
)),
UnboundPredicate::Or(expr) => UnboundPredicate::And(LogicalExpression::new(
expr.inputs.map(|expr| Box::new(expr.negate())),
)),
UnboundPredicate::Not(expr) => {
let LogicalExpression { inputs: [input_0] } = expr;
input_0.negate()
}
UnboundPredicate::Unary(expr) => UnboundPredicate::Unary(UnaryExpression::new(expr.op.negate(), expr.term)),
UnboundPredicate::Binary(expr) => {
UnboundPredicate::Binary(BinaryExpression::new(expr.op.negate(), expr.term, expr.literal))
}
UnboundPredicate::Set(expr) => {
UnboundPredicate::Set(SetExpression::new(expr.op.negate(), expr.term, expr.literals))
}
}
}
}

impl Not for UnboundPredicate {
Expand Down

0 comments on commit 4f9cf46

Please sign in to comment.