This is an automated email from the ASF dual-hosted git repository.
gangwu 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 5cbf3df feat: add not expression (#279)
5cbf3df is described below
commit 5cbf3df681c0b4032e55bbe8f1a8c73509041668
Author: Gang Wu <[email protected]>
AuthorDate: Tue Oct 28 22:52:26 2025 +0800
feat: add not expression (#279)
---
src/iceberg/expression/expression.cc | 13 +++++++++++
src/iceberg/expression/expression.h | 27 +++++++++++++++++++++++
src/iceberg/expression/expressions.cc | 19 ++++++++++++++++
src/iceberg/expression/expressions.h | 9 ++++++++
src/iceberg/test/expression_test.cc | 41 +++++++++++++++++++++++++++++++++++
5 files changed, 109 insertions(+)
diff --git a/src/iceberg/expression/expression.cc
b/src/iceberg/expression/expression.cc
index f6f6d0f..070d5fe 100644
--- a/src/iceberg/expression/expression.cc
+++ b/src/iceberg/expression/expression.cc
@@ -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"
@@ -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:
diff --git a/src/iceberg/expression/expression.h
b/src/iceberg/expression/expression.h
index e0708c4..9a80522 100644
--- a/src/iceberg/expression/expression.h
+++ b/src/iceberg/expression/expression.h
@@ -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);
diff --git a/src/iceberg/expression/expressions.cc
b/src/iceberg/expression/expressions.cc
index a775c99..686a55d 100644
--- a/src/iceberg/expression/expressions.cc
+++ b/src/iceberg/expression/expressions.cc
@@ -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,
diff --git a/src/iceberg/expression/expressions.h
b/src/iceberg/expression/expressions.h
index 7d9f9a1..66083da 100644
--- a/src/iceberg/expression/expressions.h
+++ b/src/iceberg/expression/expressions.h
@@ -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.
diff --git a/src/iceberg/test/expression_test.cc
b/src/iceberg/test/expression_test.cc
index cce7c53..326dadc 100644
--- a/src/iceberg/test/expression_test.cc
+++ b/src/iceberg/test/expression_test.cc
@@ -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