Skip to content
Merged
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
13 changes: 13 additions & 0 deletions src/iceberg/expression/expression.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <format>
#include <utility>

#include "iceberg/util/checked_cast.h"
#include "iceberg/util/formatter_internal.h"
#include "iceberg/util/macros.h"

Expand Down Expand Up @@ -91,6 +92,18 @@ bool Or::Equals(const Expression& expr) const {
return false;
}

// Not implementation
Not::Not(std::shared_ptr<Expression> child) : child_(std::move(child)) {}

std::string Not::ToString() const { return std::format("not({})", child_->ToString()); }

Result<std::shared_ptr<Expression>> Not::Negate() const { return child_; }

bool Not::Equals(const Expression& other) const {
return other.op() == Operation::kNot &&
internal::checked_cast<const Not&>(other).child_->Equals(*child_);
}

std::string_view ToString(Expression::Operation op) {
switch (op) {
case Expression::Operation::kAnd:
Expand Down
27 changes: 27 additions & 0 deletions src/iceberg/expression/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,33 @@ class ICEBERG_EXPORT Or : public Expression {
std::shared_ptr<Expression> right_;
};

/// \brief An Expression that represents logical NOT operation.
///
/// This expression negates its child expression.
class ICEBERG_EXPORT Not : public Expression {
public:
/// \brief Constructs a Not expression from a child expression.
///
/// \param child The expression to negate
explicit Not(std::shared_ptr<Expression> child);

/// \brief Returns the child expression.
///
/// \return The child expression being negated
const std::shared_ptr<Expression>& child() const { return child_; }

Operation op() const override { return Operation::kNot; }

std::string ToString() const override;

Result<std::shared_ptr<Expression>> Negate() const override;

bool Equals(const Expression& other) const override;

private:
std::shared_ptr<Expression> child_;
};

/// \brief Returns a string representation of an expression operation.
ICEBERG_EXPORT std::string_view ToString(Expression::Operation op);

Expand Down
19 changes: 19 additions & 0 deletions src/iceberg/expression/expressions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@

namespace iceberg {

// Logical NOT operation
std::shared_ptr<Expression> Expressions::Not(std::shared_ptr<Expression> child) {
if (child->op() == Expression::Operation::kTrue) {
return AlwaysFalse();
}

if (child->op() == Expression::Operation::kFalse) {
return AlwaysTrue();
}

// not(not(x)) = x
if (child->op() == Expression::Operation::kNot) {
const auto& not_expr = static_cast<const ::iceberg::Not&>(*child);
return not_expr.child();
}

return std::make_shared<::iceberg::Not>(std::move(child));
}

// Transform functions

std::shared_ptr<UnboundTransform> Expressions::Bucket(std::string name,
Expand Down
9 changes: 9 additions & 0 deletions src/iceberg/expression/expressions.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ class ICEBERG_EXPORT Expressions {
}
}

/// \brief Create a NOT expression.
///
/// \param child The expression to negate
/// \return A negated expression with optimizations applied:
/// - not(true) returns false
/// - not(false) returns true
/// - not(not(x)) returns x
static std::shared_ptr<Expression> Not(std::shared_ptr<Expression> child);

// Transform functions

/// \brief Create a bucket transform term.
Expand Down
41 changes: 41 additions & 0 deletions src/iceberg/test/expression_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,45 @@ TEST(ExpressionTest, BaseClassNegateErrorOut) {
auto negate_result = mock_expr->Negate();
EXPECT_THAT(negate_result, IsError(ErrorKind::kNotSupported));
}

TEST(NotTest, Basic) {
auto true_expr = True::Instance();
auto not_expr = std::make_shared<Not>(true_expr);

EXPECT_EQ(not_expr->op(), Expression::Operation::kNot);
EXPECT_EQ(not_expr->ToString(), "not(true)");
EXPECT_EQ(not_expr->child()->op(), Expression::Operation::kTrue);
}

TEST(NotTest, Negation) {
// Test that not(not(x)) = x
auto true_expr = True::Instance();
auto not_expr = std::make_shared<Not>(true_expr);

auto negated_result = not_expr->Negate();
ASSERT_THAT(negated_result, IsOk());
auto negated = negated_result.value();

// Should return the original true expression
EXPECT_EQ(negated->op(), Expression::Operation::kTrue);
}

TEST(NotTest, Equals) {
auto true_expr = True::Instance();
auto false_expr = False::Instance();

// Test basic equality
auto not_expr1 = std::make_shared<Not>(true_expr);
auto not_expr2 = std::make_shared<Not>(true_expr);
EXPECT_TRUE(not_expr1->Equals(*not_expr2));

// Test inequality with different child expressions
auto not_expr3 = std::make_shared<Not>(false_expr);
EXPECT_FALSE(not_expr1->Equals(*not_expr3));

// Test inequality with different operation types
auto and_expr = std::make_shared<And>(true_expr, false_expr);
EXPECT_FALSE(not_expr1->Equals(*and_expr));
}

} // namespace iceberg
Loading