| /* | |
| * 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/predicate.h" | |
| #include <limits> | |
| #include <memory> | |
| #include "iceberg/expression/expressions.h" | |
| #include "iceberg/schema.h" | |
| #include "iceberg/test/matchers.h" | |
| #include "iceberg/type.h" | |
| namespace iceberg { | |
| class PredicateTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| // Create a simple test schema with various field types | |
| schema_ = std::make_shared<Schema>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired(1, "id", int64()), | |
| SchemaField::MakeOptional(2, "name", string()), | |
| SchemaField::MakeRequired(3, "age", int32()), | |
| SchemaField::MakeOptional(4, "salary", float64()), | |
| SchemaField::MakeRequired(5, "active", boolean()), | |
| SchemaField::MakeRequired(6, "age2", int32()), | |
| SchemaField::MakeOptional(7, "always_null", unknown())}, | |
| /*schema_id=*/0); | |
| } | |
| std::shared_ptr<Schema> schema_; | |
| }; | |
| TEST_F(PredicateTest, LogicalOperationsAndOr) { | |
| auto true_expr = Expressions::AlwaysTrue(); | |
| auto false_expr = Expressions::AlwaysFalse(); | |
| auto pred1 = Expressions::Equal("age", Literal::Int(25)); | |
| auto pred2 = Expressions::Equal("name", Literal::String("test")); | |
| // Test AND operations | |
| auto and_true_true = Expressions::And(true_expr, true_expr); | |
| EXPECT_EQ(and_true_true->op(), Expression::Operation::kTrue); | |
| auto and_true_pred = Expressions::And(true_expr, pred1); | |
| EXPECT_EQ(and_true_pred->op(), Expression::Operation::kEq); | |
| auto and_pred_true = Expressions::And(pred1, true_expr); | |
| EXPECT_EQ(and_pred_true->op(), Expression::Operation::kEq); | |
| auto and_false_pred = Expressions::And(false_expr, pred1); | |
| EXPECT_EQ(and_false_pred->op(), Expression::Operation::kFalse); | |
| auto and_pred_false = Expressions::And(pred1, false_expr); | |
| EXPECT_EQ(and_pred_false->op(), Expression::Operation::kFalse); | |
| // Test OR operations | |
| auto or_false_false = Expressions::Or(false_expr, false_expr); | |
| EXPECT_EQ(or_false_false->op(), Expression::Operation::kFalse); | |
| auto or_false_pred = Expressions::Or(false_expr, pred1); | |
| EXPECT_EQ(or_false_pred->op(), Expression::Operation::kEq); | |
| auto or_pred_false = Expressions::Or(pred1, false_expr); | |
| EXPECT_EQ(or_pred_false->op(), Expression::Operation::kEq); | |
| auto or_true_pred = Expressions::Or(true_expr, pred1); | |
| EXPECT_EQ(or_true_pred->op(), Expression::Operation::kTrue); | |
| auto or_pred_true = Expressions::Or(pred1, true_expr); | |
| EXPECT_EQ(or_pred_true->op(), Expression::Operation::kTrue); | |
| } | |
| TEST_F(PredicateTest, ConstantExpressions) { | |
| auto always_true = Expressions::AlwaysTrue(); | |
| auto always_false = Expressions::AlwaysFalse(); | |
| EXPECT_EQ(always_true->op(), Expression::Operation::kTrue); | |
| EXPECT_EQ(always_false->op(), Expression::Operation::kFalse); | |
| } | |
| TEST_F(PredicateTest, UnaryPredicateFactory) { | |
| auto is_null_name = Expressions::IsNull("name"); | |
| EXPECT_EQ(is_null_name->op(), Expression::Operation::kIsNull); | |
| EXPECT_EQ(is_null_name->reference()->name(), "name"); | |
| auto not_null_name = Expressions::NotNull("active"); | |
| EXPECT_EQ(not_null_name->op(), Expression::Operation::kNotNull); | |
| EXPECT_EQ(not_null_name->reference()->name(), "active"); | |
| auto is_nan_name = Expressions::IsNaN("salary"); | |
| EXPECT_EQ(is_nan_name->op(), Expression::Operation::kIsNan); | |
| EXPECT_EQ(is_nan_name->reference()->name(), "salary"); | |
| auto not_nan_name = Expressions::NotNaN("salary"); | |
| EXPECT_EQ(not_nan_name->op(), Expression::Operation::kNotNan); | |
| EXPECT_EQ(not_nan_name->reference()->name(), "salary"); | |
| } | |
| TEST_F(PredicateTest, ComparisonPredicateFactory) { | |
| auto lt_name = Expressions::LessThan("age", Literal::Int(30)); | |
| EXPECT_EQ(lt_name->op(), Expression::Operation::kLt); | |
| EXPECT_EQ(lt_name->reference()->name(), "age"); | |
| auto lte_name = Expressions::LessThanOrEqual("salary", Literal::Double(50000.0)); | |
| EXPECT_EQ(lte_name->op(), Expression::Operation::kLtEq); | |
| EXPECT_EQ(lte_name->reference()->name(), "salary"); | |
| auto gt_name = Expressions::GreaterThan("id", Literal::Long(1000)); | |
| EXPECT_EQ(gt_name->op(), Expression::Operation::kGt); | |
| EXPECT_EQ(gt_name->reference()->name(), "id"); | |
| auto gte_name = Expressions::GreaterThanOrEqual("age", Literal::Int(18)); | |
| EXPECT_EQ(gte_name->op(), Expression::Operation::kGtEq); | |
| EXPECT_EQ(gte_name->reference()->name(), "age"); | |
| auto eq_name = Expressions::Equal("name", Literal::String("test")); | |
| EXPECT_EQ(eq_name->op(), Expression::Operation::kEq); | |
| EXPECT_EQ(eq_name->reference()->name(), "name"); | |
| auto neq_name = Expressions::NotEqual("active", Literal::Boolean(false)); | |
| EXPECT_EQ(neq_name->op(), Expression::Operation::kNotEq); | |
| EXPECT_EQ(neq_name->reference()->name(), "active"); | |
| } | |
| TEST_F(PredicateTest, StringPredicateFactory) { | |
| auto starts_name = Expressions::StartsWith("name", "John"); | |
| EXPECT_EQ(starts_name->op(), Expression::Operation::kStartsWith); | |
| EXPECT_EQ(starts_name->reference()->name(), "name"); | |
| auto not_starts_name = Expressions::NotStartsWith("name", "Jane"); | |
| EXPECT_EQ(not_starts_name->op(), Expression::Operation::kNotStartsWith); | |
| EXPECT_EQ(not_starts_name->reference()->name(), "name"); | |
| } | |
| TEST_F(PredicateTest, SetPredicateFactory) { | |
| std::vector<Literal> values = {Literal::Int(10), Literal::Int(20), Literal::Int(30)}; | |
| std::initializer_list<Literal> init_values = {Literal::String("a"), | |
| Literal::String("b")}; | |
| auto in_name_vec = Expressions::In("age", values); | |
| EXPECT_EQ(in_name_vec->op(), Expression::Operation::kIn); | |
| EXPECT_EQ(in_name_vec->reference()->name(), "age"); | |
| auto in_name_init = Expressions::In("name", init_values); | |
| EXPECT_EQ(in_name_init->op(), Expression::Operation::kIn); | |
| EXPECT_EQ(in_name_init->reference()->name(), "name"); | |
| auto not_in_name_vec = Expressions::NotIn("age", values); | |
| EXPECT_EQ(not_in_name_vec->op(), Expression::Operation::kNotIn); | |
| EXPECT_EQ(not_in_name_vec->reference()->name(), "age"); | |
| auto not_in_name_init = Expressions::NotIn("name", init_values); | |
| EXPECT_EQ(not_in_name_init->op(), Expression::Operation::kNotIn); | |
| EXPECT_EQ(not_in_name_init->reference()->name(), "name"); | |
| } | |
| TEST_F(PredicateTest, GenericPredicateFactory) { | |
| auto pred_single = | |
| Expressions::Predicate(Expression::Operation::kEq, "age", Literal::Int(25)); | |
| EXPECT_EQ(pred_single->op(), Expression::Operation::kEq); | |
| EXPECT_EQ(pred_single->reference()->name(), "age"); | |
| std::vector<Literal> values = {Literal::Int(10), Literal::Int(20)}; | |
| auto pred_multi = Expressions::Predicate(Expression::Operation::kIn, "age", values); | |
| EXPECT_EQ(pred_multi->op(), Expression::Operation::kIn); | |
| EXPECT_EQ(pred_multi->reference()->name(), "age"); | |
| auto pred_unary = Expressions::Predicate(Expression::Operation::kIsNull, "name"); | |
| EXPECT_EQ(pred_unary->op(), Expression::Operation::kIsNull); | |
| EXPECT_EQ(pred_unary->reference()->name(), "name"); | |
| } | |
| TEST_F(PredicateTest, TransformFactory) { | |
| auto bucket_transform = Expressions::Bucket("id", 10); | |
| EXPECT_NE(bucket_transform, nullptr); | |
| EXPECT_EQ(bucket_transform->reference()->name(), "id"); | |
| auto year_transform = Expressions::Year("timestamp_field"); | |
| EXPECT_NE(year_transform, nullptr); | |
| EXPECT_EQ(year_transform->reference()->name(), "timestamp_field"); | |
| auto month_transform = Expressions::Month("timestamp_field"); | |
| EXPECT_NE(month_transform, nullptr); | |
| EXPECT_EQ(month_transform->reference()->name(), "timestamp_field"); | |
| auto day_transform = Expressions::Day("timestamp_field"); | |
| EXPECT_NE(day_transform, nullptr); | |
| EXPECT_EQ(day_transform->reference()->name(), "timestamp_field"); | |
| auto hour_transform = Expressions::Hour("timestamp_field"); | |
| EXPECT_NE(hour_transform, nullptr); | |
| EXPECT_EQ(hour_transform->reference()->name(), "timestamp_field"); | |
| auto truncate_transform = Expressions::Truncate("string_field", 5); | |
| EXPECT_NE(truncate_transform, nullptr); | |
| EXPECT_EQ(truncate_transform->reference()->name(), "string_field"); | |
| } | |
| TEST_F(PredicateTest, ReferenceFactory) { | |
| auto ref = Expressions::Ref("test_field"); | |
| EXPECT_EQ(ref->name(), "test_field"); | |
| EXPECT_EQ(ref->ToString(), "ref(name=\"test_field\")"); | |
| } | |
| TEST_F(PredicateTest, NamedReferenceBasics) { | |
| auto ref = Expressions::Ref("id"); | |
| EXPECT_EQ(ref->name(), "id"); | |
| EXPECT_EQ(ref->ToString(), "ref(name=\"id\")"); | |
| EXPECT_EQ(ref->reference(), ref); | |
| } | |
| TEST_F(PredicateTest, NamedReferenceBind) { | |
| auto ref = Expressions::Ref("id"); | |
| auto bound_result = ref->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_result, IsOk()); | |
| auto bound_ref = bound_result.value(); | |
| EXPECT_EQ(bound_ref->name(), "id"); | |
| EXPECT_EQ(bound_ref->field().field_id(), 1); | |
| EXPECT_EQ(bound_ref->type()->type_id(), TypeId::kLong); | |
| EXPECT_FALSE(bound_ref->MayProduceNull()); | |
| } | |
| TEST_F(PredicateTest, NamedReferenceBindNonExistentField) { | |
| auto ref = Expressions::Ref("non_existent_field"); | |
| auto bound_result = ref->Bind(*schema_, /*case_sensitive=*/true); | |
| EXPECT_THAT(bound_result, IsError(ErrorKind::kInvalidExpression)); | |
| } | |
| TEST_F(PredicateTest, BoundReferenceEquality) { | |
| auto ref1 = Expressions::Ref("id"); | |
| auto ref2 = Expressions::Ref("id"); | |
| auto ref3 = Expressions::Ref("name"); | |
| auto bound1 = ref1->Bind(*schema_, true).value(); | |
| auto bound2 = ref2->Bind(*schema_, true).value(); | |
| auto bound3 = ref3->Bind(*schema_, true).value(); | |
| // Same field should be equal | |
| EXPECT_TRUE(bound1->Equals(*bound2)); | |
| EXPECT_TRUE(bound2->Equals(*bound1)); | |
| // Different fields should not be equal | |
| EXPECT_FALSE(bound1->Equals(*bound3)); | |
| EXPECT_FALSE(bound3->Equals(*bound1)); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateCreation) { | |
| auto is_null_pred = Expressions::IsNull("name"); | |
| EXPECT_EQ(is_null_pred->op(), Expression::Operation::kIsNull); | |
| EXPECT_EQ(is_null_pred->reference()->name(), "name"); | |
| auto not_null_pred = Expressions::NotNull("name"); | |
| EXPECT_EQ(not_null_pred->op(), Expression::Operation::kNotNull); | |
| auto equal_pred = Expressions::Equal("age", Literal::Int(25)); | |
| EXPECT_EQ(equal_pred->op(), Expression::Operation::kEq); | |
| auto greater_than_pred = Expressions::GreaterThan("salary", Literal::Double(50000.0)); | |
| EXPECT_EQ(greater_than_pred->op(), Expression::Operation::kGt); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateToString) { | |
| auto equal_pred = Expressions::Equal("age", Literal::Int(25)); | |
| EXPECT_EQ(equal_pred->ToString(), "ref(name=\"age\") == 25"); | |
| auto is_null_pred = Expressions::IsNull("name"); | |
| EXPECT_EQ(is_null_pred->ToString(), "is_null(ref(name=\"name\"))"); | |
| auto in_pred = Expressions::In("age", {Literal::Int(10), Literal::Int(20)}); | |
| EXPECT_EQ(in_pred->ToString(), "ref(name=\"age\") in [10, 20]"); | |
| auto starts_with_pred = Expressions::StartsWith("name", "John"); | |
| EXPECT_EQ(starts_with_pred->ToString(), "ref(name=\"name\") startsWith \"John\""); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateEquality) { | |
| auto pred = Expressions::GreaterThan("age", Literal::Int(25)); | |
| auto same = Expressions::GreaterThan("age", Literal::Int(25)); | |
| auto different_op = Expressions::GreaterThanOrEqual("age", Literal::Int(25)); | |
| auto different_ref = Expressions::GreaterThan("id", Literal::Int(25)); | |
| auto different_literal = Expressions::GreaterThan("age", Literal::Int(26)); | |
| EXPECT_TRUE(pred->Equals(*same)); | |
| EXPECT_TRUE(same->Equals(*pred)); | |
| EXPECT_FALSE(pred->Equals(*different_op)); | |
| EXPECT_FALSE(pred->Equals(*different_ref)); | |
| EXPECT_FALSE(pred->Equals(*different_literal)); | |
| auto bucket = [](std::string name, | |
| int32_t num_buckets) -> std::shared_ptr<UnboundTerm<BoundTransform>> { | |
| return Expressions::Bucket(std::move(name), num_buckets); | |
| }; | |
| auto transformed = Expressions::Equal(bucket("id", 16), Literal::Int(3)); | |
| auto same_transformed = Expressions::Equal(bucket("id", 16), Literal::Int(3)); | |
| auto different_transform = Expressions::Equal(bucket("id", 32), Literal::Int(3)); | |
| auto different_transform_ref = Expressions::Equal(bucket("age", 16), Literal::Int(3)); | |
| EXPECT_TRUE(transformed->Equals(*same_transformed)); | |
| EXPECT_FALSE(transformed->Equals(*different_transform)); | |
| EXPECT_FALSE(transformed->Equals(*different_transform_ref)); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateNegate) { | |
| auto equal_pred = Expressions::Equal("age", Literal::Int(25)); | |
| auto negated_result = equal_pred->Negate(); | |
| ASSERT_THAT(negated_result, IsOk()); | |
| auto negated_pred = negated_result.value(); | |
| EXPECT_EQ(negated_pred->op(), Expression::Operation::kNotEq); | |
| auto is_null_pred = Expressions::IsNull("name"); | |
| auto negated_null_result = is_null_pred->Negate(); | |
| ASSERT_THAT(negated_null_result, IsOk()); | |
| auto negated_null_pred = negated_null_result.value(); | |
| EXPECT_EQ(negated_null_pred->op(), Expression::Operation::kNotNull); | |
| auto in_pred = Expressions::In("age", {Literal::Int(10), Literal::Int(20)}); | |
| auto negated_in_result = in_pred->Negate(); | |
| ASSERT_THAT(negated_in_result, IsOk()); | |
| auto negated_in_pred = negated_in_result.value(); | |
| EXPECT_EQ(negated_in_pred->op(), Expression::Operation::kNotIn); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateBindUnary) { | |
| auto is_null_pred = Expressions::IsNull("name"); | |
| auto bound_result = is_null_pred->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_result, IsOk()); | |
| auto bound_pred = bound_result.value(); | |
| EXPECT_EQ(bound_pred->op(), Expression::Operation::kIsNull); | |
| // Test NOT NULL on non-nullable field - should return AlwaysTrue | |
| auto not_null_required = Expressions::NotNull("age"); // age is required | |
| auto bound_not_null_result = not_null_required->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_not_null_result, IsOk()); | |
| auto bound_not_null = bound_not_null_result.value(); | |
| EXPECT_EQ(bound_not_null->op(), Expression::Operation::kTrue); | |
| // Test IS NULL on non-nullable field - should return AlwaysFalse | |
| auto is_null_required = Expressions::IsNull("age"); // age is required | |
| auto bound_is_null_result = is_null_required->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_is_null_result, IsOk()); | |
| auto bound_is_null = bound_is_null_result.value(); | |
| EXPECT_EQ(bound_is_null->op(), Expression::Operation::kFalse); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateBindUnaryWithUnknownType) { | |
| // Test IS NULL on unknown type field - should return AlwaysTrue | |
| auto is_null_unknown = Expressions::IsNull("always_null"); // always_null is unknown | |
| auto bound_is_null_result = is_null_unknown->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_is_null_result, IsOk()); | |
| auto bound_is_null = bound_is_null_result.value(); | |
| EXPECT_EQ(bound_is_null->op(), Expression::Operation::kTrue); | |
| // Test NOT NULL on unknown type field - should return AlwaysFalse | |
| auto not_null_unknown = Expressions::NotNull("always_null"); | |
| auto bound_not_null_result = not_null_unknown->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_not_null_result, IsOk()); | |
| auto bound_not_null = bound_not_null_result.value(); | |
| EXPECT_EQ(bound_not_null->op(), Expression::Operation::kFalse); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateBindUnaryWithNestedStructs) { | |
| // A required field nested in an optional struct can still produce null values | |
| auto nested_schema = std::make_shared<Schema>( | |
| std::vector<SchemaField>{ | |
| SchemaField::MakeOptional(1, "optional_parent", | |
| std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField::MakeRequired(2, "leaf", int32())})), | |
| SchemaField::MakeRequired(3, "required_parent", | |
| std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField::MakeRequired(4, "leaf", int32())})), | |
| }, | |
| /*schema_id=*/0); | |
| auto is_null_optional_parent = Expressions::IsNull("optional_parent.leaf"); | |
| auto bound_is_null_optional = | |
| is_null_optional_parent->Bind(*nested_schema, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_is_null_optional, IsOk()); | |
| EXPECT_EQ(bound_is_null_optional.value()->op(), Expression::Operation::kIsNull); | |
| auto not_null_optional_parent = Expressions::NotNull("optional_parent.leaf"); | |
| auto bound_not_null_optional = | |
| not_null_optional_parent->Bind(*nested_schema, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_not_null_optional, IsOk()); | |
| EXPECT_EQ(bound_not_null_optional.value()->op(), Expression::Operation::kNotNull); | |
| // A required field with required ancestors can never produce null values | |
| auto is_null_required_parent = Expressions::IsNull("required_parent.leaf"); | |
| auto bound_is_null_required = | |
| is_null_required_parent->Bind(*nested_schema, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_is_null_required, IsOk()); | |
| EXPECT_EQ(bound_is_null_required.value()->op(), Expression::Operation::kFalse); | |
| auto not_null_required_parent = Expressions::NotNull("required_parent.leaf"); | |
| auto bound_not_null_required = | |
| not_null_required_parent->Bind(*nested_schema, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_not_null_required, IsOk()); | |
| EXPECT_EQ(bound_not_null_required.value()->op(), Expression::Operation::kTrue); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateBindLiteral) { | |
| auto equal_pred = Expressions::Equal("age", Literal::Int(25)); | |
| auto bound_result = equal_pred->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_result, IsOk()); | |
| auto bound_pred = bound_result.value(); | |
| EXPECT_EQ(bound_pred->op(), Expression::Operation::kEq); | |
| // Test binding with type conversion | |
| auto equal_long_pred = | |
| Expressions::Equal("id", Literal::Int(123)); // int to long conversion | |
| auto bound_long_result = equal_long_pred->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_long_result, IsOk()); | |
| auto bound_long_pred = bound_long_result.value(); | |
| EXPECT_EQ(bound_long_pred->op(), Expression::Operation::kEq); | |
| } | |
| TEST_F(PredicateTest, UnboundPredicateBindIn) { | |
| // Test IN operation with single value (should become equality) | |
| auto in_single = Expressions::In("age", {Literal::Int(25)}); | |
| auto bound_single_result = in_single->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_single_result, IsOk()); | |
| auto bound_single = bound_single_result.value(); | |
| EXPECT_EQ(bound_single->op(), Expression::Operation::kEq); | |
| // Test NOT IN operation with single value (should become inequality) | |
| auto not_in_single = Expressions::NotIn("age", {Literal::Int(25)}); | |
| auto bound_not_single_result = not_in_single->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_not_single_result, IsOk()); | |
| auto bound_not_single = bound_not_single_result.value(); | |
| EXPECT_EQ(bound_not_single->op(), Expression::Operation::kNotEq); | |
| // Test IN operation with multiple values (should stay as IN) | |
| auto in_multi = Expressions::In("age", {Literal::Int(25), Literal::Int(30)}); | |
| auto bound_multi_result = in_multi->Bind(*schema_, true); | |
| ASSERT_THAT(bound_multi_result, IsOk()); | |
| auto bound_multi = bound_multi_result.value(); | |
| EXPECT_EQ(bound_multi->op(), Expression::Operation::kIn); | |
| } | |
| TEST_F(PredicateTest, FloatingPointNaNPredicates) { | |
| auto is_nan_float = Expressions::IsNaN("salary"); // salary is float64 | |
| auto bound_nan_result = is_nan_float->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_nan_result, IsOk()); | |
| auto bound_nan = bound_nan_result.value(); | |
| EXPECT_EQ(bound_nan->op(), Expression::Operation::kIsNan); | |
| auto is_nan_int = Expressions::IsNaN("age"); // age is int32 | |
| auto bound_nan_int_result = is_nan_int->Bind(*schema_, /*case_sensitive=*/true); | |
| EXPECT_THAT(bound_nan_int_result, IsError(ErrorKind::kInvalidExpression)); | |
| } | |
| TEST_F(PredicateTest, StringStartsWithPredicates) { | |
| auto starts_with = Expressions::StartsWith("name", "John"); // name is string | |
| auto bound_starts_result = starts_with->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_starts_result, IsOk()); | |
| auto bound_starts = bound_starts_result.value(); | |
| EXPECT_EQ(bound_starts->op(), Expression::Operation::kStartsWith); | |
| auto starts_with_int = Expressions::StartsWith("age", "test"); // age is int32 | |
| auto bound_starts_int_result = starts_with_int->Bind(*schema_, /*case_sensitive=*/true); | |
| EXPECT_THAT(bound_starts_int_result, IsError(ErrorKind::kInvalidExpression)); | |
| } | |
| TEST_F(PredicateTest, LiteralConversionEdgeCases) { | |
| auto large_value_lt = | |
| Expressions::LessThan("age", Literal::Long(std::numeric_limits<int64_t>::max())); | |
| auto bound_large_result = large_value_lt->Bind(*schema_, /*case_sensitive=*/true); | |
| ASSERT_THAT(bound_large_result, IsOk()); | |
| auto bound_large = bound_large_result.value(); | |
| EXPECT_EQ(bound_large->op(), Expression::Operation::kTrue); | |
| } | |
| TEST_F(PredicateTest, ComplexExpressionCombinations) { | |
| auto eq_pred = Expressions::Equal("age", Literal::Int(25)); | |
| auto null_pred = Expressions::IsNull("name"); | |
| auto in_pred = | |
| Expressions::In("id", {Literal::Long(1), Literal::Long(2), Literal::Long(3)}); | |
| // Test AND combinations | |
| auto and_eq_null = Expressions::And(eq_pred, null_pred); | |
| EXPECT_EQ(and_eq_null->op(), Expression::Operation::kAnd); | |
| auto and_eq_in = Expressions::And(eq_pred, in_pred); | |
| EXPECT_EQ(and_eq_in->op(), Expression::Operation::kAnd); | |
| // Test OR combinations | |
| auto or_null_in = Expressions::Or(null_pred, in_pred); | |
| EXPECT_EQ(or_null_in->op(), Expression::Operation::kOr); | |
| // Test nested combinations | |
| auto nested = Expressions::And(and_eq_null, or_null_in); | |
| EXPECT_EQ(nested->op(), Expression::Operation::kAnd); | |
| } | |
| TEST_F(PredicateTest, BoundUnaryPredicateNegate) { | |
| auto is_null_pred = Expressions::IsNull("name"); | |
| auto bound_null = is_null_pred->Bind(*schema_, /*case_sensitive=*/true).value(); | |
| auto negated_result = bound_null->Negate(); | |
| ASSERT_THAT(negated_result, IsOk()); | |
| auto negated = negated_result.value(); | |
| EXPECT_EQ(negated->op(), Expression::Operation::kNotNull); | |
| // Double negation should return the original predicate | |
| auto double_neg_result = negated->Negate(); | |
| ASSERT_THAT(double_neg_result, IsOk()); | |
| auto double_neg = double_neg_result.value(); | |
| EXPECT_EQ(double_neg->op(), Expression::Operation::kIsNull); | |
| } | |
| TEST_F(PredicateTest, BoundUnaryPredicateEquals) { | |
| auto is_null_name1 = Expressions::IsNull("name"); | |
| auto is_null_name2 = Expressions::IsNull("name"); | |
| auto is_null_age = Expressions::IsNull("age"); | |
| auto not_null_name = Expressions::NotNull("name"); | |
| auto bound_null1 = is_null_name1->Bind(*schema_, true).value(); | |
| auto bound_null2 = is_null_name2->Bind(*schema_, true).value(); | |
| auto bound_null_age = is_null_age->Bind(*schema_, true).value(); | |
| auto bound_not_null = not_null_name->Bind(*schema_, true).value(); | |
| // Same predicate should be equal | |
| EXPECT_TRUE(bound_null1->Equals(*bound_null2)); | |
| EXPECT_TRUE(bound_null2->Equals(*bound_null1)); | |
| // Different fields should not be equal | |
| EXPECT_FALSE(bound_null1->Equals(*bound_null_age)); | |
| // Different operations should not be equal | |
| EXPECT_FALSE(bound_null1->Equals(*bound_not_null)); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateNegate) { | |
| auto eq_pred = Expressions::Equal("age", Literal::Int(25)); | |
| auto bound_eq = eq_pred->Bind(*schema_, true).value(); | |
| auto negated_result = bound_eq->Negate(); | |
| ASSERT_THAT(negated_result, IsOk()); | |
| auto negated = negated_result.value(); | |
| EXPECT_EQ(negated->op(), Expression::Operation::kNotEq); | |
| // Test less than negation | |
| auto lt_pred = Expressions::LessThan("age", Literal::Int(30)); | |
| auto bound_lt = lt_pred->Bind(*schema_, true).value(); | |
| auto neg_lt_result = bound_lt->Negate(); | |
| ASSERT_THAT(neg_lt_result, IsOk()); | |
| EXPECT_EQ(neg_lt_result.value()->op(), Expression::Operation::kGtEq); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateEquals) { | |
| auto eq1 = Expressions::Equal("age", Literal::Int(25)); | |
| auto eq2 = Expressions::Equal("age", Literal::Int(25)); | |
| auto eq3 = Expressions::Equal("age", Literal::Int(30)); | |
| auto neq = Expressions::NotEqual("age", Literal::Int(25)); | |
| auto bound_eq1 = eq1->Bind(*schema_, true).value(); | |
| auto bound_eq2 = eq2->Bind(*schema_, true).value(); | |
| auto bound_eq3 = eq3->Bind(*schema_, true).value(); | |
| auto bound_neq = neq->Bind(*schema_, true).value(); | |
| // Same predicate should be equal | |
| EXPECT_TRUE(bound_eq1->Equals(*bound_eq2)); | |
| // Different literal values should not be equal | |
| EXPECT_FALSE(bound_eq1->Equals(*bound_eq3)); | |
| // Different operations should not be equal | |
| EXPECT_FALSE(bound_eq1->Equals(*bound_neq)); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateIntegerEquivalence) { | |
| // Test that < 6 is equivalent to <= 5 | |
| auto lt_6 = Expressions::LessThan("age", Literal::Int(6)); | |
| auto lte_5 = Expressions::LessThanOrEqual("age", Literal::Int(5)); | |
| auto bound_lt = lt_6->Bind(*schema_, true).value(); | |
| auto bound_lte = lte_5->Bind(*schema_, true).value(); | |
| EXPECT_TRUE(bound_lt->Equals(*bound_lte)); | |
| EXPECT_TRUE(bound_lte->Equals(*bound_lt)); | |
| // Test that > 5 is equivalent to >= 6 | |
| auto gt_5 = Expressions::GreaterThan("age", Literal::Int(5)); | |
| auto gte_6 = Expressions::GreaterThanOrEqual("age", Literal::Int(6)); | |
| auto bound_gt = gt_5->Bind(*schema_, true).value(); | |
| auto bound_gte = gte_6->Bind(*schema_, true).value(); | |
| EXPECT_TRUE(bound_gt->Equals(*bound_gte)); | |
| EXPECT_TRUE(bound_gte->Equals(*bound_gt)); | |
| // Test that < 6 is not equivalent to <= 6 | |
| auto lte_6 = Expressions::LessThanOrEqual("age", Literal::Int(6)); | |
| auto bound_lte_6 = lte_6->Bind(*schema_, true).value(); | |
| EXPECT_FALSE(bound_lt->Equals(*bound_lte_6)); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateToString) { | |
| auto in_pred = | |
| Expressions::In("age", {Literal::Int(10), Literal::Int(20), Literal::Int(30)}); | |
| auto bound_in = in_pred->Bind(*schema_, true).value(); | |
| auto str = bound_in->ToString(); | |
| // The set order might vary, but should contain the key elements | |
| // BoundReference uses field_id in ToString, so check for id=3 (age field) | |
| EXPECT_TRUE(str.find("id=3") != std::string::npos); | |
| EXPECT_TRUE(str.find("in") != std::string::npos); | |
| auto not_in_pred = | |
| Expressions::NotIn("name", {Literal::String("a"), Literal::String("b")}); | |
| auto bound_not_in = not_in_pred->Bind(*schema_, true).value(); | |
| auto not_in_str = bound_not_in->ToString(); | |
| // Check for id=2 (name field) | |
| EXPECT_TRUE(not_in_str.find("id=2") != std::string::npos); | |
| EXPECT_TRUE(not_in_str.find("not in") != std::string::npos); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateNegate) { | |
| auto in_pred = Expressions::In("age", {Literal::Int(10), Literal::Int(20)}); | |
| auto bound_in = in_pred->Bind(*schema_, true).value(); | |
| auto negated_result = bound_in->Negate(); | |
| ASSERT_THAT(negated_result, IsOk()); | |
| auto negated = negated_result.value(); | |
| EXPECT_EQ(negated->op(), Expression::Operation::kNotIn); | |
| // Test double negation | |
| auto double_neg_result = negated->Negate(); | |
| ASSERT_THAT(double_neg_result, IsOk()); | |
| EXPECT_EQ(double_neg_result.value()->op(), Expression::Operation::kIn); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateEquals) { | |
| auto in1 = Expressions::In("age", {Literal::Int(10), Literal::Int(20)}); | |
| auto in2 = | |
| Expressions::In("age", {Literal::Int(20), Literal::Int(10)}); // Different order | |
| auto in3 = | |
| Expressions::In("age", {Literal::Int(10), Literal::Int(30)}); // Different values | |
| auto in4 = Expressions::In("age2", {Literal::Int(10), Literal::Int(20)}); | |
| auto bound_in1 = in1->Bind(*schema_, /*case_sensitive=*/true).value(); | |
| auto bound_in2 = in2->Bind(*schema_, /*case_sensitive=*/true).value(); | |
| auto bound_in3 = in3->Bind(*schema_, /*case_sensitive=*/true).value(); | |
| auto bound_in4 = in4->Bind(*schema_, /*case_sensitive=*/true).value(); | |
| // Same values in different order should be equal (unordered_set) | |
| EXPECT_TRUE(bound_in1->Equals(*bound_in2)); | |
| EXPECT_TRUE(bound_in2->Equals(*bound_in1)); | |
| // Different values should not be equal | |
| EXPECT_FALSE(bound_in1->Equals(*bound_in3)); | |
| // Same values on a different term should not be equal | |
| EXPECT_FALSE(bound_in1->Equals(*bound_in4)); | |
| } | |
| namespace { | |
| std::shared_ptr<BoundPredicate> AssertAndCastToBoundPredicate( | |
| std::shared_ptr<Expression> expr) { | |
| auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(expr); | |
| EXPECT_NE(bound_pred, nullptr) << "Expected a BoundPredicate, got " << expr->ToString(); | |
| return bound_pred; | |
| } | |
| } // namespace | |
| TEST_F(PredicateTest, BoundUnaryPredicateTestIsNull) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto is_null_pred, Expressions::IsNull("name")->Bind( | |
| *schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(is_null_pred); | |
| EXPECT_THAT(bound_pred->Test(Literal::Null(string())), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("test")), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundUnaryPredicateTestNotNull) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto not_null_pred, Expressions::NotNull("name")->Bind( | |
| *schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(not_null_pred); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("test")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Null(string())), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundUnaryPredicateTestIsNaN) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto is_nan_pred, Expressions::IsNaN("salary")->Bind( | |
| *schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(is_nan_pred); | |
| // Test with NaN values | |
| EXPECT_THAT(bound_pred->Test(Literal::Float(std::numeric_limits<float>::quiet_NaN())), | |
| HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Double(std::numeric_limits<double>::quiet_NaN())), | |
| HasValue(testing::Eq(true))); | |
| // Test with regular values | |
| EXPECT_THAT(bound_pred->Test(Literal::Float(3.14f)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Double(2.718)), HasValue(testing::Eq(false))); | |
| // Test with infinity | |
| EXPECT_THAT(bound_pred->Test(Literal::Float(std::numeric_limits<float>::infinity())), | |
| HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundUnaryPredicateTestNotNaN) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto not_nan_pred, Expressions::NotNaN("salary")->Bind( | |
| *schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(not_nan_pred); | |
| // Test with regular values | |
| EXPECT_THAT(bound_pred->Test(Literal::Double(100.5)), HasValue(testing::Eq(true))); | |
| // Test with NaN | |
| EXPECT_THAT(bound_pred->Test(Literal::Double(std::numeric_limits<double>::quiet_NaN())), | |
| HasValue(testing::Eq(false))); | |
| // Test with infinity (should be true as infinity is not NaN) | |
| EXPECT_THAT(bound_pred->Test(Literal::Double(std::numeric_limits<double>::infinity())), | |
| HasValue(testing::Eq(true))); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateTestComparison) { | |
| // Test less than | |
| ICEBERG_UNWRAP_OR_FAIL(auto lt_pred, Expressions::LessThan("age", Literal::Int(30)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_lt = AssertAndCastToBoundPredicate(lt_pred); | |
| EXPECT_THAT(bound_lt->Test(Literal::Int(20)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_lt->Test(Literal::Int(30)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_lt->Test(Literal::Int(40)), HasValue(testing::Eq(false))); | |
| // Test less than or equal | |
| ICEBERG_UNWRAP_OR_FAIL(auto lte_pred, | |
| Expressions::LessThanOrEqual("age", Literal::Int(30)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_lte = AssertAndCastToBoundPredicate(lte_pred); | |
| EXPECT_THAT(bound_lte->Test(Literal::Int(20)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_lte->Test(Literal::Int(30)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_lte->Test(Literal::Int(40)), HasValue(testing::Eq(false))); | |
| // Test greater than | |
| ICEBERG_UNWRAP_OR_FAIL(auto gt_pred, Expressions::GreaterThan("age", Literal::Int(30)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_gt = AssertAndCastToBoundPredicate(gt_pred); | |
| EXPECT_THAT(bound_gt->Test(Literal::Int(20)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_gt->Test(Literal::Int(30)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_gt->Test(Literal::Int(40)), HasValue(testing::Eq(true))); | |
| // Test greater than or equal | |
| ICEBERG_UNWRAP_OR_FAIL(auto gte_pred, | |
| Expressions::GreaterThanOrEqual("age", Literal::Int(30)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_gte = AssertAndCastToBoundPredicate(gte_pred); | |
| EXPECT_THAT(bound_gte->Test(Literal::Int(20)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_gte->Test(Literal::Int(30)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_gte->Test(Literal::Int(40)), HasValue(testing::Eq(true))); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateTestEquality) { | |
| // Test equal | |
| ICEBERG_UNWRAP_OR_FAIL(auto eq_pred, Expressions::Equal("age", Literal::Int(25)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_eq = AssertAndCastToBoundPredicate(eq_pred); | |
| EXPECT_THAT(bound_eq->Test(Literal::Int(25)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_eq->Test(Literal::Int(26)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_eq->Test(Literal::Int(24)), HasValue(testing::Eq(false))); | |
| // Test not equal | |
| ICEBERG_UNWRAP_OR_FAIL(auto neq_pred, Expressions::NotEqual("age", Literal::Int(25)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_neq = AssertAndCastToBoundPredicate(neq_pred); | |
| EXPECT_THAT(bound_neq->Test(Literal::Int(25)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_neq->Test(Literal::Int(26)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_neq->Test(Literal::Int(24)), HasValue(testing::Eq(true))); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateTestWithDifferentTypes) { | |
| // Test with double | |
| ICEBERG_UNWRAP_OR_FAIL(auto gt_pred, | |
| Expressions::GreaterThan("salary", Literal::Double(50000.0)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_double = AssertAndCastToBoundPredicate(gt_pred); | |
| EXPECT_THAT(bound_double->Test(Literal::Double(60000.0)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_double->Test(Literal::Double(40000.0)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_double->Test(Literal::Double(50000.0)), HasValue(testing::Eq(false))); | |
| // Test with string | |
| ICEBERG_UNWRAP_OR_FAIL(auto str_eq_pred, | |
| Expressions::Equal("name", Literal::String("Alice")) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_string = AssertAndCastToBoundPredicate(str_eq_pred); | |
| EXPECT_THAT(bound_string->Test(Literal::String("Alice")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_string->Test(Literal::String("Bob")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_string->Test(Literal::String("alice")), | |
| HasValue(testing::Eq(false))); // Case sensitive | |
| // Test with boolean | |
| ICEBERG_UNWRAP_OR_FAIL(auto bool_eq_pred, | |
| Expressions::Equal("active", Literal::Boolean(true)) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_bool = AssertAndCastToBoundPredicate(bool_eq_pred); | |
| EXPECT_THAT(bound_bool->Test(Literal::Boolean(true)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_bool->Test(Literal::Boolean(false)), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateTestStartsWith) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto starts_with_pred, | |
| Expressions::StartsWith("name", "Jo")->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(starts_with_pred); | |
| // Test strings that start with "Jo" | |
| EXPECT_THAT(bound_pred->Test(Literal::String("John")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Joe")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Jo")), HasValue(testing::Eq(true))); | |
| // Test strings that don't start with "Jo" | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Alice")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Bob")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("")), HasValue(testing::Eq(false))); | |
| // Test empty prefix | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto empty_prefix_pred, | |
| Expressions::StartsWith("name", "")->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_empty = AssertAndCastToBoundPredicate(empty_prefix_pred); | |
| // All strings should start with empty prefix | |
| EXPECT_THAT(bound_empty->Test(Literal::String("test")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_empty->Test(Literal::String("")), HasValue(testing::Eq(true))); | |
| } | |
| TEST_F(PredicateTest, BoundLiteralPredicateTestNotStartsWith) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto not_starts_with_pred, | |
| Expressions::NotStartsWith("name", "Jo")->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(not_starts_with_pred); | |
| // Test strings that don't start with "Jo" | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Alice")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Bob")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("")), HasValue(testing::Eq(true))); | |
| // Test strings that start with "Jo" | |
| EXPECT_THAT(bound_pred->Test(Literal::String("John")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Joe")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Jo")), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateTestIn) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto in_pred, | |
| Expressions::In("age", {Literal::Int(10), Literal::Int(20), Literal::Int(30)}) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(in_pred); | |
| // Test values in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(10)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(20)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(30)), HasValue(testing::Eq(true))); | |
| // Test values not in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(15)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(40)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(0)), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateTestNotIn) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto not_in_pred, | |
| Expressions::NotIn("age", {Literal::Int(10), Literal::Int(20), Literal::Int(30)}) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(not_in_pred); | |
| // Test values not in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(15)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(40)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(0)), HasValue(testing::Eq(true))); | |
| // Test values in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(10)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(20)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Int(30)), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateTestWithStrings) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto in_pred, | |
| Expressions::In("name", {Literal::String("Alice"), Literal::String("Bob"), | |
| Literal::String("Charlie")}) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(in_pred); | |
| // Test strings in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Alice")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Bob")), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("Charlie")), HasValue(testing::Eq(true))); | |
| // Test strings not in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::String("David")), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::String("alice")), | |
| HasValue(testing::Eq(false))); // Case sensitive | |
| EXPECT_THAT(bound_pred->Test(Literal::String("")), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateTestWithLongs) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto in_pred, | |
| Expressions::In("id", {Literal::Long(100L), Literal::Long(200L), | |
| Literal::Long(300L)}) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| auto bound_pred = AssertAndCastToBoundPredicate(in_pred); | |
| // Test longs in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Long(100L)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Long(200L)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Long(300L)), HasValue(testing::Eq(true))); | |
| // Test longs not in the set | |
| EXPECT_THAT(bound_pred->Test(Literal::Long(150L)), HasValue(testing::Eq(false))); | |
| EXPECT_THAT(bound_pred->Test(Literal::Long(400L)), HasValue(testing::Eq(false))); | |
| } | |
| TEST_F(PredicateTest, BoundSetPredicateTestSingleLiteral) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto in_pred, Expressions::In("age", {Literal::Int(42)}) | |
| ->Bind(*schema_, /*case_sensitive=*/true)); | |
| // Single element IN becomes Equal | |
| EXPECT_EQ(in_pred->op(), Expression::Operation::kEq); | |
| auto bound_literal = AssertAndCastToBoundPredicate(in_pred); | |
| EXPECT_THAT(bound_literal->Test(Literal::Int(42)), HasValue(testing::Eq(true))); | |
| EXPECT_THAT(bound_literal->Test(Literal::Int(41)), HasValue(testing::Eq(false))); | |
| } | |
| } // namespace iceberg |