Skip to content

Commit 5cbf3df

Browse files
authored
feat: add not expression (#279)
1 parent 6a8aeeb commit 5cbf3df

File tree

5 files changed

+109
-0
lines changed

5 files changed

+109
-0
lines changed

src/iceberg/expression/expression.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <format>
2323
#include <utility>
2424

25+
#include "iceberg/util/checked_cast.h"
2526
#include "iceberg/util/formatter_internal.h"
2627
#include "iceberg/util/macros.h"
2728

@@ -91,6 +92,18 @@ bool Or::Equals(const Expression& expr) const {
9192
return false;
9293
}
9394

95+
// Not implementation
96+
Not::Not(std::shared_ptr<Expression> child) : child_(std::move(child)) {}
97+
98+
std::string Not::ToString() const { return std::format("not({})", child_->ToString()); }
99+
100+
Result<std::shared_ptr<Expression>> Not::Negate() const { return child_; }
101+
102+
bool Not::Equals(const Expression& other) const {
103+
return other.op() == Operation::kNot &&
104+
internal::checked_cast<const Not&>(other).child_->Equals(*child_);
105+
}
106+
94107
std::string_view ToString(Expression::Operation op) {
95108
switch (op) {
96109
case Expression::Operation::kAnd:

src/iceberg/expression/expression.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,33 @@ class ICEBERG_EXPORT Or : public Expression {
194194
std::shared_ptr<Expression> right_;
195195
};
196196

197+
/// \brief An Expression that represents logical NOT operation.
198+
///
199+
/// This expression negates its child expression.
200+
class ICEBERG_EXPORT Not : public Expression {
201+
public:
202+
/// \brief Constructs a Not expression from a child expression.
203+
///
204+
/// \param child The expression to negate
205+
explicit Not(std::shared_ptr<Expression> child);
206+
207+
/// \brief Returns the child expression.
208+
///
209+
/// \return The child expression being negated
210+
const std::shared_ptr<Expression>& child() const { return child_; }
211+
212+
Operation op() const override { return Operation::kNot; }
213+
214+
std::string ToString() const override;
215+
216+
Result<std::shared_ptr<Expression>> Negate() const override;
217+
218+
bool Equals(const Expression& other) const override;
219+
220+
private:
221+
std::shared_ptr<Expression> child_;
222+
};
223+
197224
/// \brief Returns a string representation of an expression operation.
198225
ICEBERG_EXPORT std::string_view ToString(Expression::Operation op);
199226

src/iceberg/expression/expressions.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@
2525

2626
namespace iceberg {
2727

28+
// Logical NOT operation
29+
std::shared_ptr<Expression> Expressions::Not(std::shared_ptr<Expression> child) {
30+
if (child->op() == Expression::Operation::kTrue) {
31+
return AlwaysFalse();
32+
}
33+
34+
if (child->op() == Expression::Operation::kFalse) {
35+
return AlwaysTrue();
36+
}
37+
38+
// not(not(x)) = x
39+
if (child->op() == Expression::Operation::kNot) {
40+
const auto& not_expr = static_cast<const ::iceberg::Not&>(*child);
41+
return not_expr.child();
42+
}
43+
44+
return std::make_shared<::iceberg::Not>(std::move(child));
45+
}
46+
2847
// Transform functions
2948

3049
std::shared_ptr<UnboundTransform> Expressions::Bucket(std::string name,

src/iceberg/expression/expressions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ class ICEBERG_EXPORT Expressions {
9292
}
9393
}
9494

95+
/// \brief Create a NOT expression.
96+
///
97+
/// \param child The expression to negate
98+
/// \return A negated expression with optimizations applied:
99+
/// - not(true) returns false
100+
/// - not(false) returns true
101+
/// - not(not(x)) returns x
102+
static std::shared_ptr<Expression> Not(std::shared_ptr<Expression> child);
103+
95104
// Transform functions
96105

97106
/// \brief Create a bucket transform term.

src/iceberg/test/expression_test.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,45 @@ TEST(ExpressionTest, BaseClassNegateErrorOut) {
165165
auto negate_result = mock_expr->Negate();
166166
EXPECT_THAT(negate_result, IsError(ErrorKind::kNotSupported));
167167
}
168+
169+
TEST(NotTest, Basic) {
170+
auto true_expr = True::Instance();
171+
auto not_expr = std::make_shared<Not>(true_expr);
172+
173+
EXPECT_EQ(not_expr->op(), Expression::Operation::kNot);
174+
EXPECT_EQ(not_expr->ToString(), "not(true)");
175+
EXPECT_EQ(not_expr->child()->op(), Expression::Operation::kTrue);
176+
}
177+
178+
TEST(NotTest, Negation) {
179+
// Test that not(not(x)) = x
180+
auto true_expr = True::Instance();
181+
auto not_expr = std::make_shared<Not>(true_expr);
182+
183+
auto negated_result = not_expr->Negate();
184+
ASSERT_THAT(negated_result, IsOk());
185+
auto negated = negated_result.value();
186+
187+
// Should return the original true expression
188+
EXPECT_EQ(negated->op(), Expression::Operation::kTrue);
189+
}
190+
191+
TEST(NotTest, Equals) {
192+
auto true_expr = True::Instance();
193+
auto false_expr = False::Instance();
194+
195+
// Test basic equality
196+
auto not_expr1 = std::make_shared<Not>(true_expr);
197+
auto not_expr2 = std::make_shared<Not>(true_expr);
198+
EXPECT_TRUE(not_expr1->Equals(*not_expr2));
199+
200+
// Test inequality with different child expressions
201+
auto not_expr3 = std::make_shared<Not>(false_expr);
202+
EXPECT_FALSE(not_expr1->Equals(*not_expr3));
203+
204+
// Test inequality with different operation types
205+
auto and_expr = std::make_shared<And>(true_expr, false_expr);
206+
EXPECT_FALSE(not_expr1->Equals(*and_expr));
207+
}
208+
168209
} // namespace iceberg

0 commit comments

Comments
 (0)