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 811fd1d commit a02ed28
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 1 deletion.
35 changes: 34 additions & 1 deletion crates/iceberg/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub use predicate::*;
/// The discriminant of this enum is used for determining the type of the operator, see
/// [`PredicateOperator::is_unary`], [`PredicateOperator::is_binary`], [`PredicateOperator::is_set`]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u16)]
pub enum PredicateOperator {
// Unary operators
Expand Down Expand Up @@ -112,6 +112,39 @@ impl PredicateOperator {
pub fn is_set(self) -> bool {
(self as u16) > (PredicateOperator::NotStartsWith as u16)
}

/// Returns the predicate that is the inverse of self
///
/// # Example
///
/// ```rust
/// use iceberg::expr::PredicateOperator;
/// assert!(PredicateOperator::IsNull.negate() == PredicateOperator::NotNull);
/// assert!(PredicateOperator::IsNan.negate() == PredicateOperator::NotNan);
/// assert!(PredicateOperator::LessThan.negate() == PredicateOperator::GreaterThanOrEq);
/// assert!(PredicateOperator::GreaterThan.negate() == PredicateOperator::LessThanOrEq);
/// assert!(PredicateOperator::Eq.negate() == PredicateOperator::NotEq);
/// assert!(PredicateOperator::In.negate() == PredicateOperator::NotIn);
/// assert!(PredicateOperator::StartsWith.negate() == PredicateOperator::NotStartsWith);
/// ```
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,
}
}
}

#[cfg(test)]
Expand Down
70 changes: 70 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, Reference};
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 @@ -136,6 +137,24 @@ impl<T: Debug> Debug for SetExpression<T> {
}
}

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 Predicate {
Expand Down Expand Up @@ -230,6 +249,57 @@ impl Predicate {
pub fn or(self, other: Predicate) -> Predicate {
Predicate::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, Predicate, Reference};
/// use iceberg::spec::Datum;
/// let expr1 = Reference::new("a").less_than(Datum::long(10));
/// let expr2 = Reference::new("b").less_than(Datum::long(5)).and(Reference::new("c").less_than(Datum::long(10)));
/// let expr3 = Reference::new("b").less_than(Datum::long(5)).or(Reference::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)");
///
/// assert_eq!(&format!("{result}"), "(b >= 5) AND (c >= 10)");
/// ```
pub fn negate(self) -> Predicate {
match self {
Predicate::And(expr) => Predicate::Or(LogicalExpression::new(
expr.inputs.map(|expr| Box::new(expr.negate())),
)),
Predicate::Or(expr) => Predicate::And(LogicalExpression::new(
expr.inputs.map(|expr| Box::new(expr.negate())),
)),
Predicate::Not(expr) => {
let LogicalExpression { inputs: [input_0] } = expr;
input_0
}
Predicate::Unary(expr) => {
Predicate::Unary(UnaryExpression::new(expr.op.negate(), expr.term))
}
Predicate::Binary(expr) => Predicate::Binary(BinaryExpression::new(
expr.op.negate(),
expr.term,
expr.literal,
)),
Predicate::Set(expr) => Predicate::Set(SetExpression::new(
expr.op.negate(),
expr.term,
expr.literals,
)),
}
}
}

impl Not for Predicate {
Expand Down

0 comments on commit a02ed28

Please sign in to comment.