This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new f2a79a1  feat: add `or` expression (#120)
f2a79a1 is described below

commit f2a79a14a4ef5d8200d0240d1122307be2a06e3f
Author: Ying Cai <ying.cai...@gmail.com>
AuthorDate: Wed Jun 25 23:03:00 2025 +0800

    feat: add `or` expression (#120)
    
    - or expression
     - change negate return type form Result to Expression
     - add some tests
---
 src/iceberg/expression/expression.cc |  38 +++++++++---
 src/iceberg/expression/expression.h  |  48 ++++++++++++---
 test/expression_test.cc              | 114 +++++++++++++++++++++++++++++++----
 3 files changed, 174 insertions(+), 26 deletions(-)

diff --git a/src/iceberg/expression/expression.cc 
b/src/iceberg/expression/expression.cc
index 77f341e..c6fa940 100644
--- a/src/iceberg/expression/expression.cc
+++ b/src/iceberg/expression/expression.cc
@@ -21,8 +21,6 @@
 
 #include <format>
 
-#include "iceberg/result.h"
-
 namespace iceberg {
 
 // True implementation
@@ -31,7 +29,7 @@ const std::shared_ptr<True>& True::Instance() {
   return instance;
 }
 
-Result<std::shared_ptr<Expression>> True::Negate() const { return 
False::Instance(); }
+std::shared_ptr<Expression> True::Negate() const { return False::Instance(); }
 
 // False implementation
 const std::shared_ptr<False>& False::Instance() {
@@ -39,7 +37,7 @@ const std::shared_ptr<False>& False::Instance() {
   return instance;
 }
 
-Result<std::shared_ptr<Expression>> False::Negate() const { return 
True::Instance(); }
+std::shared_ptr<Expression> False::Negate() const { return True::Instance(); }
 
 // And implementation
 And::And(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right)
@@ -49,9 +47,11 @@ std::string And::ToString() const {
   return std::format("({} and {})", left_->ToString(), right_->ToString());
 }
 
-Result<std::shared_ptr<Expression>> And::Negate() const {
-  // TODO(yingcai-cy): Implement Or expression
-  return InvalidExpression("And negation not yet implemented");
+std::shared_ptr<Expression> And::Negate() const {
+  // De Morgan's law: not(A and B) = (not A) or (not B)
+  auto left_negated = left_->Negate();
+  auto right_negated = right_->Negate();
+  return std::make_shared<Or>(left_negated, right_negated);
 }
 
 bool And::Equals(const Expression& expr) const {
@@ -63,4 +63,28 @@ bool And::Equals(const Expression& expr) const {
   return false;
 }
 
+// Or implementation
+Or::Or(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right)
+    : left_(std::move(left)), right_(std::move(right)) {}
+
+std::string Or::ToString() const {
+  return std::format("({} or {})", left_->ToString(), right_->ToString());
+}
+
+std::shared_ptr<Expression> Or::Negate() const {
+  // De Morgan's law: not(A or B) = (not A) and (not B)
+  auto left_negated = left_->Negate();
+  auto right_negated = right_->Negate();
+  return std::make_shared<And>(left_negated, right_negated);
+}
+
+bool Or::Equals(const Expression& expr) const {
+  if (expr.op() == Operation::kOr) {
+    const auto& other = static_cast<const Or&>(expr);
+    return (left_->Equals(*other.left()) && right_->Equals(*other.right())) ||
+           (left_->Equals(*other.right()) && right_->Equals(*other.left()));
+  }
+  return false;
+}
+
 }  // namespace iceberg
diff --git a/src/iceberg/expression/expression.h 
b/src/iceberg/expression/expression.h
index 342122f..9ceae1c 100644
--- a/src/iceberg/expression/expression.h
+++ b/src/iceberg/expression/expression.h
@@ -25,9 +25,8 @@
 #include <memory>
 #include <string>
 
-#include "iceberg/expected.h"
+#include "iceberg/exception.h"
 #include "iceberg/iceberg_export.h"
-#include "iceberg/result.h"
 
 namespace iceberg {
 
@@ -67,8 +66,8 @@ class ICEBERG_EXPORT Expression {
   virtual Operation op() const = 0;
 
   /// \brief Returns the negation of this expression, equivalent to not(this).
-  virtual Result<std::shared_ptr<Expression>> Negate() const {
-    return InvalidExpression("Expression cannot be negated");
+  virtual std::shared_ptr<Expression> Negate() const {
+    throw IcebergError("Expression cannot be negated");
   }
 
   /// \brief Returns whether this expression will accept the same values as 
another.
@@ -94,7 +93,7 @@ class ICEBERG_EXPORT True : public Expression {
 
   std::string ToString() const override { return "true"; }
 
-  Result<std::shared_ptr<Expression>> Negate() const override;
+  std::shared_ptr<Expression> Negate() const override;
 
   bool Equals(const Expression& other) const override {
     return other.op() == Operation::kTrue;
@@ -114,7 +113,7 @@ class ICEBERG_EXPORT False : public Expression {
 
   std::string ToString() const override { return "false"; }
 
-  Result<std::shared_ptr<Expression>> Negate() const override;
+  std::shared_ptr<Expression> Negate() const override;
 
   bool Equals(const Expression& other) const override {
     return other.op() == Operation::kFalse;
@@ -150,7 +149,42 @@ class ICEBERG_EXPORT And : public Expression {
 
   std::string ToString() const override;
 
-  Result<std::shared_ptr<Expression>> Negate() const override;
+  std::shared_ptr<Expression> Negate() const override;
+
+  bool Equals(const Expression& other) const override;
+
+ private:
+  std::shared_ptr<Expression> left_;
+  std::shared_ptr<Expression> right_;
+};
+
+/// \brief An Expression that represents a logical OR operation between two 
expressions.
+///
+/// This expression evaluates to true if at least one of its child expressions
+/// evaluates to true.
+class ICEBERG_EXPORT Or : public Expression {
+ public:
+  /// \brief Constructs an Or expression from two sub-expressions.
+  ///
+  /// \param left The left operand of the OR expression
+  /// \param right The right operand of the OR expression
+  Or(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right);
+
+  /// \brief Returns the left operand of the OR expression.
+  ///
+  /// \return The left operand of the OR expression
+  const std::shared_ptr<Expression>& left() const { return left_; }
+
+  /// \brief Returns the right operand of the OR expression.
+  ///
+  /// \return The right operand of the OR expression
+  const std::shared_ptr<Expression>& right() const { return right_; }
+
+  Operation op() const override { return Operation::kOr; }
+
+  std::string ToString() const override;
+
+  std::shared_ptr<Expression> Negate() const override;
 
   bool Equals(const Expression& other) const override;
 
diff --git a/test/expression_test.cc b/test/expression_test.cc
index f722d62..c14c7d9 100644
--- a/test/expression_test.cc
+++ b/test/expression_test.cc
@@ -30,25 +30,17 @@ TEST(TrueFalseTest, Basic) {
   auto false_instance = False::Instance();
   auto negated = false_instance->Negate();
 
-  EXPECT_TRUE(negated.has_value());
-
   // Check that negated expression is True
-  auto true_expr = negated.value();
-  EXPECT_EQ(true_expr->op(), Expression::Operation::kTrue);
-
-  EXPECT_EQ(true_expr->ToString(), "true");
+  EXPECT_EQ(negated->op(), Expression::Operation::kTrue);
+  EXPECT_EQ(negated->ToString(), "true");
 
   // Test negation of True returns false
   auto true_instance = True::Instance();
   negated = true_instance->Negate();
 
-  EXPECT_TRUE(negated.has_value());
-
   // Check that negated expression is False
-  auto false_expr = negated.value();
-  EXPECT_EQ(false_expr->op(), Expression::Operation::kFalse);
-
-  EXPECT_EQ(false_expr->ToString(), "false");
+  EXPECT_EQ(negated->op(), Expression::Operation::kFalse);
+  EXPECT_EQ(negated->ToString(), "false");
 }
 
 TEST(ANDTest, Basic) {
@@ -64,4 +56,102 @@ TEST(ANDTest, Basic) {
   EXPECT_EQ(and_expr->left()->op(), Expression::Operation::kTrue);
   EXPECT_EQ(and_expr->right()->op(), Expression::Operation::kTrue);
 }
+
+TEST(ORTest, Basic) {
+  // Create True and False expressions
+  auto true_expr = True::Instance();
+  auto false_expr = False::Instance();
+
+  // Create an OR expression
+  auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+
+  EXPECT_EQ(or_expr->op(), Expression::Operation::kOr);
+  EXPECT_EQ(or_expr->ToString(), "(true or false)");
+  EXPECT_EQ(or_expr->left()->op(), Expression::Operation::kTrue);
+  EXPECT_EQ(or_expr->right()->op(), Expression::Operation::kFalse);
+}
+
+TEST(ORTest, Negation) {
+  // Test De Morgan's law: not(A or B) = (not A) and (not B)
+  auto true_expr = True::Instance();
+  auto false_expr = False::Instance();
+
+  auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+  auto negated_or = or_expr->Negate();
+
+  // Should become AND expression
+  EXPECT_EQ(negated_or->op(), Expression::Operation::kAnd);
+  EXPECT_EQ(negated_or->ToString(), "(false and true)");
+}
+
+TEST(ORTest, Equals) {
+  auto true_expr = True::Instance();
+  auto false_expr = False::Instance();
+
+  // Test basic equality
+  auto or_expr1 = std::make_shared<Or>(true_expr, false_expr);
+  auto or_expr2 = std::make_shared<Or>(true_expr, false_expr);
+  EXPECT_TRUE(or_expr1->Equals(*or_expr2));
+
+  // Test commutativity: (A or B) equals (B or A)
+  auto or_expr3 = std::make_shared<Or>(false_expr, true_expr);
+  EXPECT_TRUE(or_expr1->Equals(*or_expr3));
+
+  // Test inequality with different expressions
+  auto or_expr4 = std::make_shared<Or>(true_expr, true_expr);
+  EXPECT_FALSE(or_expr1->Equals(*or_expr4));
+
+  // Test inequality with different operation types
+  auto and_expr = std::make_shared<And>(true_expr, false_expr);
+  EXPECT_FALSE(or_expr1->Equals(*and_expr));
+}
+
+TEST(ANDTest, Negation) {
+  // Test De Morgan's law: not(A and B) = (not A) or (not B)
+  auto true_expr = True::Instance();
+  auto false_expr = False::Instance();
+
+  auto and_expr = std::make_shared<And>(true_expr, false_expr);
+  auto negated_and = and_expr->Negate();
+
+  // Should become OR expression
+  EXPECT_EQ(negated_and->op(), Expression::Operation::kOr);
+  EXPECT_EQ(negated_and->ToString(), "(false or true)");
+}
+
+TEST(ANDTest, Equals) {
+  auto true_expr = True::Instance();
+  auto false_expr = False::Instance();
+
+  // Test basic equality
+  auto and_expr1 = std::make_shared<And>(true_expr, false_expr);
+  auto and_expr2 = std::make_shared<And>(true_expr, false_expr);
+  EXPECT_TRUE(and_expr1->Equals(*and_expr2));
+
+  // Test commutativity: (A and B) equals (B and A)
+  auto and_expr3 = std::make_shared<And>(false_expr, true_expr);
+  EXPECT_TRUE(and_expr1->Equals(*and_expr3));
+
+  // Test inequality with different expressions
+  auto and_expr4 = std::make_shared<And>(true_expr, true_expr);
+  EXPECT_FALSE(and_expr1->Equals(*and_expr4));
+
+  // Test inequality with different operation types
+  auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+  EXPECT_FALSE(and_expr1->Equals(*or_expr));
+}
+
+TEST(ExpressionTest, BaseClassNegateThrowsException) {
+  // Create a mock expression that doesn't override Negate()
+  class MockExpression : public Expression {
+   public:
+    Operation op() const override { return Operation::kTrue; }
+    // Deliberately not overriding Negate() to test base class behavior
+  };
+
+  auto mock_expr = std::make_shared<MockExpression>();
+
+  // Should throw IcebergError when calling Negate() on base class
+  EXPECT_THROW(mock_expr->Negate(), IcebergError);
+}
 }  // namespace iceberg

Reply via email to