diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt index 65a4f2e2b..cd7276e69 100644 --- a/src/iceberg/CMakeLists.txt +++ b/src/iceberg/CMakeLists.txt @@ -34,6 +34,8 @@ set(ICEBERG_SOURCES transform.cc transform_function.cc type.cc + snapshot.cc + expression.cc util/murmurhash3_internal.cc) set(ICEBERG_STATIC_BUILD_INTERFACE_LIBS) diff --git a/src/iceberg/expression.cc b/src/iceberg/expression.cc new file mode 100644 index 000000000..d53cef3cc --- /dev/null +++ b/src/iceberg/expression.cc @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "expression.h" + +#include + +namespace iceberg { + +// True implementation +const std::shared_ptr& True::Instance() { + static const std::shared_ptr instance{new True()}; + return instance; +} + +Result> True::Negate() const { return False::Instance(); } + +// False implementation +const std::shared_ptr& False::Instance() { + static const std::shared_ptr instance = std::shared_ptr(new False()); + return instance; +} + +Result> False::Negate() const { return True::Instance(); } + +// And implementation +And::And(std::shared_ptr left, std::shared_ptr right) + : left_(std::move(left)), right_(std::move(right)) {} + +std::string And::ToString() const { + return std::format("({} and {})", left_->ToString(), right_->ToString()); +} + +Result> And::Negate() const { + // TODO(yingcai-cy): Implement Or expression + return unexpected( + Error(ErrorKind::kInvalidExpression, "And negation not yet implemented")); +} + +bool And::Equals(const Expression& expr) const { + if (expr.op() == Operation::kAnd) { + const auto& other = static_cast(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.h b/src/iceberg/expression.h new file mode 100644 index 000000000..3113fb8bd --- /dev/null +++ b/src/iceberg/expression.h @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +/// \file iceberg/expression.h +/// Expression interface for Iceberg table operations. + +#include +#include + +#include "iceberg/expected.h" +#include "iceberg/iceberg_export.h" +#include "iceberg/result.h" + +namespace iceberg { + +/// \brief Represents a boolean expression tree. +class ICEBERG_EXPORT Expression { + public: + /// Operation types for expressions + enum class Operation { + kTrue, + kFalse, + kIsNull, + kNotNull, + kIsNan, + kNotNan, + kLt, + kLtEq, + kGt, + kGtEq, + kEq, + kNotEq, + kIn, + kNotIn, + kNot, + kAnd, + kOr, + kStartsWith, + kNotStartsWith, + kCount, + kCountStar, + kMax, + kMin + }; + + virtual ~Expression() = default; + + /// \brief Returns the operation for an expression node. + virtual Operation op() const = 0; + + /// \brief Returns the negation of this expression, equivalent to not(this). + virtual Result> Negate() const { + return unexpected( + Error(ErrorKind::kInvalidExpression, "Expression cannot be negated")); + } + + /// \brief Returns whether this expression will accept the same values as another. + /// \param other another expression + /// \return true if the expressions are equivalent + virtual bool Equals(const Expression& other) const { + // only bound predicates can be equivalent + return false; + } + + virtual std::string ToString() const { return "Expression"; } +}; + +/// \brief An Expression that is always true. +/// +/// Represents a boolean predicate that always evaluates to true. +class ICEBERG_EXPORT True : public Expression { + public: + /// \brief Returns the singleton instance + static const std::shared_ptr& Instance(); + + Operation op() const override { return Operation::kTrue; } + + std::string ToString() const override { return "true"; } + + Result> Negate() const override; + + bool Equals(const Expression& other) const override { + return other.op() == Operation::kTrue; + } + + private: + constexpr True() = default; +}; + +/// \brief An expression that is always false. +class ICEBERG_EXPORT False : public Expression { + public: + /// \brief Returns the singleton instance + static const std::shared_ptr& Instance(); + + Operation op() const override { return Operation::kFalse; } + + std::string ToString() const override { return "false"; } + + Result> Negate() const override; + + bool Equals(const Expression& other) const override { + return other.op() == Operation::kFalse; + } + + private: + constexpr False() = default; +}; + +/// \brief An Expression that represents a logical AND operation between two expressions. +/// +/// This expression evaluates to true if and only if both of its child expressions +/// evaluate to true. +class ICEBERG_EXPORT And : public Expression { + public: + /// \brief Constructs an And expression from two sub-expressions. + /// + /// \param left The left operand of the AND expression + /// \param right The right operand of the AND expression + And(std::shared_ptr left, std::shared_ptr right); + + /// \brief Returns the left operand of the AND expression. + /// + /// \return The left operand of the AND expression + const std::shared_ptr& left() const { return left_; } + + /// \brief Returns the right operand of the AND expression. + /// + /// \return The right operand of the AND expression + const std::shared_ptr& right() const { return right_; } + + Operation op() const override { return Operation::kAnd; } + + std::string ToString() const override; + + Result> Negate() const override; + + bool Equals(const Expression& other) const override; + + private: + std::shared_ptr left_; + std::shared_ptr right_; +}; + +} // namespace iceberg diff --git a/src/iceberg/result.h b/src/iceberg/result.h index 7d12f367d..a70383d43 100644 --- a/src/iceberg/result.h +++ b/src/iceberg/result.h @@ -39,6 +39,7 @@ enum class ErrorKind { kNotImplemented, kUnknownError, kNotSupported, + kInvalidExpression, kJsonParseError, }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4c41364d0..b9dc6c2a7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,6 +44,12 @@ target_sources(expected_test PRIVATE expected_test.cc) target_link_libraries(expected_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock) add_test(NAME expected_test COMMAND expected_test) +add_executable(expression_test) +target_sources(expression_test PRIVATE expression_test.cc) +target_link_libraries(expression_test PRIVATE iceberg_static GTest::gtest_main + GTest::gmock) +add_test(NAME expression_test COMMAND expression_test) + if(ICEBERG_BUILD_BUNDLE) add_executable(avro_test) target_sources(avro_test PRIVATE avro_test.cc) diff --git a/test/expression_test.cc b/test/expression_test.cc new file mode 100644 index 000000000..1ca50cb5f --- /dev/null +++ b/test/expression_test.cc @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/expression.h" + +#include + +#include + +namespace iceberg { + +TEST(TrueFalseTest, Basic) { + // Test negation of False returns True + 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"); + + // 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"); +} + +TEST(ANDTest, Basic) { + // Create two True expressions + auto true_expr1 = True::Instance(); + auto true_expr2 = True::Instance(); + + // Create an AND expression + auto and_expr = std::make_shared(true_expr1, true_expr2); + + EXPECT_EQ(and_expr->op(), Expression::Operation::kAnd); + EXPECT_EQ(and_expr->ToString(), "(true and true)"); + EXPECT_EQ(and_expr->left()->op(), Expression::Operation::kTrue); + EXPECT_EQ(and_expr->right()->op(), Expression::Operation::kTrue); +} +} // namespace iceberg