blob: 6b990462a4b8f6a990abb8065f305d15103f170d [file]
/*
* 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())},
/*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, 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