blob: 97d70d7a84b667052bdc6d1eb34a2ccffd6f9948 [file] [log] [blame]
/*
* 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 <gtest/gtest.h>
#include "iceberg/expression/binder.h"
#include "iceberg/expression/expressions.h"
#include "iceberg/expression/rewrite_not.h"
#include "iceberg/result.h"
#include "iceberg/schema.h"
#include "iceberg/test/matchers.h"
#include "iceberg/type.h"
namespace iceberg {
class ExpressionVisitorTest : public ::testing::Test {
protected:
void SetUp() override {
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())},
/*schema_id=*/0);
}
Result<std::shared_ptr<Expression>> Bind(const std::shared_ptr<Expression>& expr,
bool case_sensitive = true) {
return Binder::Bind(*schema_, expr, case_sensitive);
}
std::shared_ptr<Schema> schema_;
};
class BinderTest : public ExpressionVisitorTest {};
TEST_F(BinderTest, UnaryPredicates) {
// Test IsNull
auto unbound_is_null = Expressions::IsNull("name");
ICEBERG_UNWRAP_OR_FAIL(auto bound_is_null, Bind(unbound_is_null));
EXPECT_EQ(bound_is_null->op(), Expression::Operation::kIsNull);
EXPECT_TRUE(bound_is_null->is_bound_predicate());
EXPECT_EQ(bound_is_null->ToString(), "is_null(ref(id=2, type=string))");
// Test NotNull
auto unbound_not_null = Expressions::NotNull("name");
ICEBERG_UNWRAP_OR_FAIL(auto bound_not_null, Bind(unbound_not_null));
EXPECT_EQ(bound_not_null->op(), Expression::Operation::kNotNull);
EXPECT_TRUE(bound_not_null->is_bound_predicate());
EXPECT_EQ(bound_not_null->ToString(), "not_null(ref(id=2, type=string))");
// Test IsNaN
auto unbound_is_nan = Expressions::IsNaN("salary");
ICEBERG_UNWRAP_OR_FAIL(auto bound_is_nan, Bind(unbound_is_nan));
EXPECT_EQ(bound_is_nan->op(), Expression::Operation::kIsNan);
EXPECT_TRUE(bound_is_nan->is_bound_predicate());
EXPECT_EQ(bound_is_nan->ToString(), "is_nan(ref(id=4, type=double))");
// Test NotNaN
auto unbound_not_nan = Expressions::NotNaN("salary");
ICEBERG_UNWRAP_OR_FAIL(auto bound_not_nan, Bind(unbound_not_nan));
EXPECT_EQ(bound_not_nan->op(), Expression::Operation::kNotNan);
EXPECT_TRUE(bound_not_nan->is_bound_predicate());
EXPECT_EQ(bound_not_nan->ToString(), "not_nan(ref(id=4, type=double))");
}
TEST_F(BinderTest, ComparisonPredicates) {
// Test LessThan
auto unbound_lt = Expressions::LessThan("age", Literal::Int(30));
ICEBERG_UNWRAP_OR_FAIL(auto bound_lt, Bind(unbound_lt));
EXPECT_EQ(bound_lt->op(), Expression::Operation::kLt);
EXPECT_TRUE(bound_lt->is_bound_predicate());
EXPECT_EQ(bound_lt->ToString(), "ref(id=3, type=int) < 30");
// Test LessThanOrEqual
auto unbound_lte = Expressions::LessThanOrEqual("age", Literal::Int(30));
ICEBERG_UNWRAP_OR_FAIL(auto bound_lte, Bind(unbound_lte));
EXPECT_EQ(bound_lte->op(), Expression::Operation::kLtEq);
EXPECT_TRUE(bound_lte->is_bound_predicate());
EXPECT_EQ(bound_lte->ToString(), "ref(id=3, type=int) <= 30");
// Test GreaterThan
auto unbound_gt = Expressions::GreaterThan("salary", Literal::Double(50000.0));
ICEBERG_UNWRAP_OR_FAIL(auto bound_gt, Bind(unbound_gt));
EXPECT_EQ(bound_gt->op(), Expression::Operation::kGt);
EXPECT_TRUE(bound_gt->is_bound_predicate());
EXPECT_EQ(bound_gt->ToString(), "ref(id=4, type=double) > 50000.000000");
// Test GreaterThanOrEqual
auto unbound_gte = Expressions::GreaterThanOrEqual("salary", Literal::Double(50000.0));
ICEBERG_UNWRAP_OR_FAIL(auto bound_gte, Bind(unbound_gte));
EXPECT_EQ(bound_gte->op(), Expression::Operation::kGtEq);
EXPECT_TRUE(bound_gte->is_bound_predicate());
EXPECT_EQ(bound_gte->ToString(), "ref(id=4, type=double) >= 50000.000000");
// Test Equal
auto unbound_eq = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_eq, Bind(unbound_eq));
EXPECT_EQ(bound_eq->op(), Expression::Operation::kEq);
EXPECT_TRUE(bound_eq->is_bound_predicate());
EXPECT_EQ(bound_eq->ToString(), "ref(id=2, type=string) == \"Alice\"");
// Test NotEqual
auto unbound_neq = Expressions::NotEqual("name", Literal::String("Bob"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_neq, Bind(unbound_neq));
EXPECT_EQ(bound_neq->op(), Expression::Operation::kNotEq);
EXPECT_TRUE(bound_neq->is_bound_predicate());
EXPECT_EQ(bound_neq->ToString(), "ref(id=2, type=string) != \"Bob\"");
}
TEST_F(BinderTest, StringPredicates) {
// Test StartsWith
auto unbound_starts = Expressions::StartsWith("name", "Al");
ICEBERG_UNWRAP_OR_FAIL(auto bound_starts, Bind(unbound_starts));
EXPECT_EQ(bound_starts->op(), Expression::Operation::kStartsWith);
EXPECT_TRUE(bound_starts->is_bound_predicate());
EXPECT_EQ(bound_starts->ToString(), "ref(id=2, type=string) startsWith \"\"Al\"\"");
// Test NotStartsWith
auto unbound_not_starts = Expressions::NotStartsWith("name", "Bo");
ICEBERG_UNWRAP_OR_FAIL(auto bound_not_starts, Bind(unbound_not_starts));
EXPECT_EQ(bound_not_starts->op(), Expression::Operation::kNotStartsWith);
EXPECT_TRUE(bound_not_starts->is_bound_predicate());
EXPECT_EQ(bound_not_starts->ToString(),
"ref(id=2, type=string) notStartsWith \"\"Bo\"\"");
}
TEST_F(BinderTest, SetPredicates) {
// Test In
auto unbound_in =
Expressions::In("age", {Literal::Int(25), Literal::Int(30), Literal::Int(35)});
ICEBERG_UNWRAP_OR_FAIL(auto bound_in, Bind(unbound_in));
EXPECT_EQ(bound_in->op(), Expression::Operation::kIn);
EXPECT_TRUE(bound_in->is_bound_predicate());
EXPECT_THAT(bound_in->ToString(), testing::HasSubstr("ref(id=3, type=int) in ("));
// Test NotIn
auto unbound_not_in = Expressions::NotIn("age", {Literal::Int(40), Literal::Int(45)});
ICEBERG_UNWRAP_OR_FAIL(auto bound_not_in, Bind(unbound_not_in));
EXPECT_EQ(bound_not_in->op(), Expression::Operation::kNotIn);
EXPECT_TRUE(bound_not_in->is_bound_predicate());
EXPECT_THAT(bound_not_in->ToString(),
testing::HasSubstr("ref(id=3, type=int) not in ("));
}
TEST_F(BinderTest, Constants) {
// Test AlwaysTrue
auto true_expr = Expressions::AlwaysTrue();
ICEBERG_UNWRAP_OR_FAIL(auto bound_true, Bind(true_expr));
EXPECT_EQ(bound_true->op(), Expression::Operation::kTrue);
// Test AlwaysFalse
auto false_expr = Expressions::AlwaysFalse();
ICEBERG_UNWRAP_OR_FAIL(auto bound_false, Bind(false_expr));
EXPECT_EQ(bound_false->op(), Expression::Operation::kFalse);
}
TEST_F(BinderTest, AndExpression) {
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto unbound_and = Expressions::And(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_and, Bind(unbound_and));
EXPECT_EQ(bound_and->op(), Expression::Operation::kAnd);
EXPECT_EQ(bound_and->ToString(),
"(ref(id=2, type=string) == \"Alice\" and ref(id=3, type=int) > 25)");
// Verify both children are bound
auto result = IsBoundVisitor::IsBound(bound_and);
ASSERT_THAT(result, IsOk());
EXPECT_TRUE(result.value());
}
TEST_F(BinderTest, OrExpression) {
auto pred1 = Expressions::IsNull("name");
auto pred2 = Expressions::LessThan("salary", Literal::Double(30000.0));
auto unbound_or = Expressions::Or(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_or, Bind(unbound_or));
EXPECT_EQ(bound_or->op(), Expression::Operation::kOr);
EXPECT_EQ(bound_or->ToString(),
"(is_null(ref(id=2, type=string)) or ref(id=4, type=double) < 30000.000000)");
// Verify both children are bound
auto result = IsBoundVisitor::IsBound(bound_or);
ASSERT_THAT(result, IsOk());
EXPECT_TRUE(result.value());
}
TEST_F(BinderTest, NotExpression) {
auto pred = Expressions::Equal("active", Literal::Boolean(true));
auto unbound_not = Expressions::Not(pred);
ICEBERG_UNWRAP_OR_FAIL(auto bound_not, Bind(unbound_not));
EXPECT_EQ(bound_not->op(), Expression::Operation::kNot);
EXPECT_EQ(bound_not->ToString(), "not(ref(id=5, type=boolean) == true)");
// Verify child is bound
auto result = IsBoundVisitor::IsBound(bound_not);
ASSERT_THAT(result, IsOk());
EXPECT_TRUE(result.value());
}
TEST_F(BinderTest, ComplexNestedExpression) {
// (name = 'Alice' AND age > 25) OR (salary < 30000 AND active = true)
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto pred3 = Expressions::LessThan("salary", Literal::Double(30000.0));
auto pred4 = Expressions::Equal("active", Literal::Boolean(true));
auto and1 = Expressions::And(pred1, pred2);
auto and2 = Expressions::And(pred3, pred4);
auto complex_or = Expressions::Or(and1, and2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_complex, Bind(complex_or));
EXPECT_EQ(bound_complex->op(), Expression::Operation::kOr);
// Verify entire tree is bound
auto result = IsBoundVisitor::IsBound(bound_complex);
ASSERT_THAT(result, IsOk());
EXPECT_TRUE(result.value());
}
TEST_F(BinderTest, CaseSensitive) {
// Create predicate with exact field name
auto pred_exact = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_exact, Bind(pred_exact, true));
EXPECT_EQ(bound_exact->op(), Expression::Operation::kEq);
EXPECT_TRUE(bound_exact->is_bound_predicate());
// Create predicate with different case - should fail with case-sensitive binding
auto pred_wrong_case = Expressions::Equal("NAME", Literal::String("Alice"));
auto result_case_sensitive = Bind(pred_wrong_case, true);
EXPECT_THAT(result_case_sensitive, HasErrorMessage("NAME"));
}
TEST_F(BinderTest, CaseInsensitive) {
// Create predicate with different case
auto pred_upper = Expressions::Equal("NAME", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_upper, Bind(pred_upper, false));
EXPECT_EQ(bound_upper->op(), Expression::Operation::kEq);
EXPECT_TRUE(bound_upper->is_bound_predicate());
// Create predicate with mixed case
auto pred_mixed = Expressions::Equal("NaMe", Literal::String("Bob"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_mixed, Bind(pred_mixed, false));
EXPECT_EQ(bound_mixed->op(), Expression::Operation::kEq);
EXPECT_TRUE(bound_mixed->is_bound_predicate());
}
TEST_F(BinderTest, ErrorFieldNotFound) {
// Try to bind with non-existent field
auto pred_nonexistent =
Expressions::Equal("nonexistent_field", Literal::String("value"));
auto result = Bind(pred_nonexistent);
EXPECT_THAT(result, HasErrorMessage("Cannot find field 'nonexistent_field'"));
}
TEST_F(BinderTest, ErrorAlreadyBound) {
// First bind the predicate
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
// Try to bind it again - should fail
auto result = Bind(bound_pred);
EXPECT_THAT(result, HasErrorMessage("already bound"));
}
TEST_F(BinderTest, ErrorNestedUnboundField) {
// Create complex expression with one invalid field
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::Equal("invalid_field", Literal::String("value"));
auto complex_and = Expressions::And(pred1, pred2);
auto result = Bind(complex_and);
EXPECT_THAT(result, HasErrorMessage("invalid_field"));
}
class IsBoundVisitorTest : public ExpressionVisitorTest {};
TEST_F(IsBoundVisitorTest, Constants) {
// True and False should error out
auto true_expr = Expressions::AlwaysTrue();
auto result_true = IsBoundVisitor::IsBound(true_expr);
EXPECT_THAT(result_true, IsError(ErrorKind::kInvalidExpression));
auto false_expr = Expressions::AlwaysFalse();
auto result_false = IsBoundVisitor::IsBound(false_expr);
EXPECT_THAT(result_false, IsError(ErrorKind::kInvalidExpression));
}
TEST_F(IsBoundVisitorTest, UnboundPredicate) {
// Unbound predicates should return false
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(unbound_pred));
EXPECT_FALSE(is_bound);
}
TEST_F(IsBoundVisitorTest, BoundPredicate) {
// Bound predicates should return true
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(bound_pred));
EXPECT_TRUE(is_bound);
}
TEST_F(IsBoundVisitorTest, AndWithBoundChildren) {
// AND with all bound children should return true
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto unbound_and = Expressions::And(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_and, Bind(unbound_and));
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(bound_and));
EXPECT_TRUE(is_bound);
}
TEST_F(IsBoundVisitorTest, AndWithUnboundChild) {
// AND with any unbound child should return false
auto bound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto pred1, Bind(bound_pred));
auto pred2 = Expressions::Equal("age", Literal::Int(25)); // unbound
auto mixed_and = Expressions::And(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(mixed_and));
EXPECT_FALSE(is_bound);
}
TEST_F(IsBoundVisitorTest, OrWithBoundChildren) {
// OR with all bound children should return true
auto pred1 = Expressions::IsNull("name");
auto pred2 = Expressions::LessThan("salary", Literal::Double(30000.0));
auto unbound_or = Expressions::Or(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_or, Bind(unbound_or));
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(bound_or));
EXPECT_TRUE(is_bound);
}
TEST_F(IsBoundVisitorTest, OrWithUnboundChild) {
// OR with any unbound child should return false
auto pred1 = Expressions::IsNull("name"); // unbound
auto bound_pred2 = Expressions::Equal("age", Literal::Int(25));
ICEBERG_UNWRAP_OR_FAIL(auto pred2, Bind(bound_pred2));
auto mixed_or = Expressions::Or(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(mixed_or));
EXPECT_FALSE(is_bound);
}
TEST_F(IsBoundVisitorTest, NotWithBoundChild) {
// NOT with bound child should return true
auto unbound_pred = Expressions::Equal("active", Literal::Boolean(true));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
auto not_expr = Expressions::Not(bound_pred);
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(not_expr));
EXPECT_TRUE(is_bound);
}
TEST_F(IsBoundVisitorTest, NotWithUnboundChild) {
// NOT with unbound child should return false
auto unbound_pred = Expressions::Equal("active", Literal::Boolean(true));
auto not_expr = Expressions::Not(unbound_pred);
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(not_expr));
EXPECT_FALSE(is_bound);
}
TEST_F(IsBoundVisitorTest, ComplexExpression) {
// Complex expression: all bound should return true
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto pred3 = Expressions::LessThan("salary", Literal::Double(30000.0));
auto and_expr = Expressions::And(pred1, pred2);
auto complex_or = Expressions::Or(and_expr, pred3);
ICEBERG_UNWRAP_OR_FAIL(auto bound_complex, Bind(complex_or));
ICEBERG_UNWRAP_OR_FAIL(auto is_bound, IsBoundVisitor::IsBound(bound_complex));
EXPECT_TRUE(is_bound);
// Complex expression: one unbound should return false
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred2, Bind(pred2));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred3, Bind(pred3));
auto mixed_and = Expressions::And(unbound_pred, bound_pred2);
auto mixed_complex = Expressions::Or(mixed_and, bound_pred3);
ICEBERG_UNWRAP_OR_FAIL(auto is_bound_mixed, IsBoundVisitor::IsBound(mixed_complex));
EXPECT_FALSE(is_bound_mixed);
}
class RewriteNotTest : public ExpressionVisitorTest {};
TEST_F(RewriteNotTest, Constants) {
// True remains True
auto true_expr = Expressions::AlwaysTrue();
ICEBERG_UNWRAP_OR_FAIL(auto rewritten_true, RewriteNot::Visit(true_expr));
EXPECT_EQ(rewritten_true->op(), Expression::Operation::kTrue);
EXPECT_TRUE(rewritten_true->Equals(*True::Instance()));
// False remains False
auto false_expr = Expressions::AlwaysFalse();
ICEBERG_UNWRAP_OR_FAIL(auto rewritten_false, RewriteNot::Visit(false_expr));
EXPECT_EQ(rewritten_false->op(), Expression::Operation::kFalse);
EXPECT_TRUE(rewritten_false->Equals(*False::Instance()));
}
TEST_F(RewriteNotTest, Predicates) {
// Bound predicates pass through unchanged
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(bound_pred));
EXPECT_EQ(rewritten->op(), Expression::Operation::kEq);
EXPECT_TRUE(rewritten->is_bound_predicate());
// Unbound predicates pass through unchanged
auto unbound_pred2 = Expressions::IsNull("salary");
ICEBERG_UNWRAP_OR_FAIL(auto rewritten_unbound, RewriteNot::Visit(unbound_pred2));
EXPECT_EQ(rewritten_unbound->op(), Expression::Operation::kIsNull);
EXPECT_TRUE(rewritten_unbound->is_unbound_predicate());
}
TEST_F(RewriteNotTest, NotExpression) {
// NOT(predicate) should be rewritten to negated predicate
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
auto not_expr = Expressions::Not(bound_pred);
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(not_expr));
// Equal should be negated to NotEqual
EXPECT_EQ(rewritten->op(), Expression::Operation::kNotEq);
EXPECT_TRUE(rewritten->is_bound_predicate());
EXPECT_EQ(rewritten->ToString(), "ref(id=2, type=string) != \"Alice\"");
}
TEST_F(RewriteNotTest, DoubleNegation) {
// NOT(NOT(predicate)) should be rewritten back to predicate
auto unbound_pred = Expressions::Equal("age", Literal::Int(25));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
auto not_expr = Expressions::Not(bound_pred);
auto double_not = Expressions::Not(not_expr);
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(double_not));
// Should be back to Equal
EXPECT_EQ(rewritten->op(), Expression::Operation::kEq);
EXPECT_TRUE(rewritten->is_bound_predicate());
EXPECT_EQ(rewritten->ToString(), "ref(id=3, type=int) == 25");
}
TEST_F(RewriteNotTest, AndExpression) {
// AND expressions pass through (children are processed)
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred1, Bind(pred1));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred2, Bind(pred2));
auto and_expr = Expressions::And(bound_pred1, bound_pred2);
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(and_expr));
EXPECT_EQ(rewritten->op(), Expression::Operation::kAnd);
}
TEST_F(RewriteNotTest, OrExpression) {
// OR expressions pass through (children are processed)
auto pred1 = Expressions::IsNull("name");
auto pred2 = Expressions::LessThan("salary", Literal::Double(30000.0));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred1, Bind(pred1));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred2, Bind(pred2));
auto or_expr = Expressions::Or(bound_pred1, bound_pred2);
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(or_expr));
EXPECT_EQ(rewritten->op(), Expression::Operation::kOr);
}
TEST_F(RewriteNotTest, ComplexExpression) {
// Complex: NOT(pred1 AND NOT(pred2))
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred1, Bind(pred1));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred2, Bind(pred2));
auto not_pred2 = Expressions::Not(bound_pred2);
auto and_expr = Expressions::And(bound_pred1, not_pred2);
auto not_and = Expressions::Not(and_expr);
ICEBERG_UNWRAP_OR_FAIL(auto rewritten, RewriteNot::Visit(not_and));
// The outer NOT should push down via negation
// NOT(pred1 AND NOT(pred2)) becomes NOT(pred1) OR pred2
EXPECT_EQ(rewritten->op(), Expression::Operation::kOr);
}
class ReferenceVisitorTest : public ExpressionVisitorTest {};
TEST_F(ReferenceVisitorTest, Constants) {
// Constants should have no referenced fields
auto true_expr = Expressions::AlwaysTrue();
ICEBERG_UNWRAP_OR_FAIL(auto refs_true,
ReferenceVisitor::GetReferencedFieldIds(true_expr));
EXPECT_TRUE(refs_true.empty());
auto false_expr = Expressions::AlwaysFalse();
ICEBERG_UNWRAP_OR_FAIL(auto refs_false,
ReferenceVisitor::GetReferencedFieldIds(false_expr));
EXPECT_TRUE(refs_false.empty());
}
TEST_F(ReferenceVisitorTest, UnboundPredicate) {
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
auto result = ReferenceVisitor::GetReferencedFieldIds(unbound_pred);
EXPECT_THAT(result, IsError(ErrorKind::kInvalidExpression));
EXPECT_THAT(result,
HasErrorMessage("Cannot get referenced field IDs from unbound predicate"));
}
TEST_F(ReferenceVisitorTest, BoundPredicate) {
// Bound predicate should return the field ID
auto unbound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto bound_pred, Bind(unbound_pred));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_pred));
EXPECT_EQ(refs.size(), 1);
EXPECT_EQ(refs.count(2), 1); // name field has id=2
}
TEST_F(ReferenceVisitorTest, MultiplePredicates) {
// Test various predicates with different fields
auto pred_age = Expressions::GreaterThan("age", Literal::Int(25));
ICEBERG_UNWRAP_OR_FAIL(auto bound_age, Bind(pred_age));
ICEBERG_UNWRAP_OR_FAIL(auto refs_age,
ReferenceVisitor::GetReferencedFieldIds(bound_age));
EXPECT_EQ(refs_age.size(), 1);
EXPECT_EQ(refs_age.count(3), 1); // age field has id=3
auto pred_salary = Expressions::LessThan("salary", Literal::Double(50000.0));
ICEBERG_UNWRAP_OR_FAIL(auto bound_salary, Bind(pred_salary));
ICEBERG_UNWRAP_OR_FAIL(auto refs_salary,
ReferenceVisitor::GetReferencedFieldIds(bound_salary));
EXPECT_EQ(refs_salary.size(), 1);
EXPECT_EQ(refs_salary.count(4), 1); // salary field has id=4
}
TEST_F(ReferenceVisitorTest, UnaryPredicates) {
// Test unary predicates
auto pred_is_null = Expressions::IsNull("name");
ICEBERG_UNWRAP_OR_FAIL(auto bound_is_null, Bind(pred_is_null));
ICEBERG_UNWRAP_OR_FAIL(auto refs,
ReferenceVisitor::GetReferencedFieldIds(bound_is_null));
EXPECT_EQ(refs.size(), 1);
EXPECT_EQ(refs.count(2), 1);
auto pred_is_nan = Expressions::IsNaN("salary");
ICEBERG_UNWRAP_OR_FAIL(auto bound_is_nan, Bind(pred_is_nan));
ICEBERG_UNWRAP_OR_FAIL(auto refs_nan,
ReferenceVisitor::GetReferencedFieldIds(bound_is_nan));
EXPECT_EQ(refs_nan.size(), 1);
EXPECT_EQ(refs_nan.count(4), 1);
}
TEST_F(ReferenceVisitorTest, AndExpression) {
// AND expression should return union of field IDs from both sides
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto and_expr = Expressions::And(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_and, Bind(and_expr));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_and));
EXPECT_EQ(refs.size(), 2);
EXPECT_EQ(refs.count(2), 1); // name field
EXPECT_EQ(refs.count(3), 1); // age field
}
TEST_F(ReferenceVisitorTest, OrExpression) {
// OR expression should return union of field IDs from both sides
auto pred1 = Expressions::IsNull("salary");
auto pred2 = Expressions::Equal("active", Literal::Boolean(true));
auto or_expr = Expressions::Or(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_or, Bind(or_expr));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_or));
EXPECT_EQ(refs.size(), 2);
EXPECT_EQ(refs.count(4), 1); // salary field
EXPECT_EQ(refs.count(5), 1); // active field
}
TEST_F(ReferenceVisitorTest, NotExpression) {
// NOT expression should return field IDs from its child
auto pred = Expressions::Equal("name", Literal::String("Alice"));
auto not_expr = Expressions::Not(pred);
ICEBERG_UNWRAP_OR_FAIL(auto bound_not, Bind(not_expr));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_not));
EXPECT_EQ(refs.size(), 1);
EXPECT_EQ(refs.count(2), 1); // name field
}
TEST_F(ReferenceVisitorTest, ComplexNestedExpression) {
// (name = 'Alice' AND age > 25) OR (salary < 30000 AND active = true)
// Should reference fields: name(2), age(3), salary(4), active(5)
auto pred1 = Expressions::Equal("name", Literal::String("Alice"));
auto pred2 = Expressions::GreaterThan("age", Literal::Int(25));
auto pred3 = Expressions::LessThan("salary", Literal::Double(30000.0));
auto pred4 = Expressions::Equal("active", Literal::Boolean(true));
auto and1 = Expressions::And(pred1, pred2);
auto and2 = Expressions::And(pred3, pred4);
auto complex_or = Expressions::Or(and1, and2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_complex, Bind(complex_or));
ICEBERG_UNWRAP_OR_FAIL(auto refs,
ReferenceVisitor::GetReferencedFieldIds(bound_complex));
EXPECT_EQ(refs.size(), 4);
EXPECT_EQ(refs.count(2), 1); // name field
EXPECT_EQ(refs.count(3), 1); // age field
EXPECT_EQ(refs.count(4), 1); // salary field
EXPECT_EQ(refs.count(5), 1); // active field
}
TEST_F(ReferenceVisitorTest, DuplicateFieldReferences) {
// Multiple predicates referencing the same field
// age > 25 AND age < 50
auto pred1 = Expressions::GreaterThan("age", Literal::Int(25));
auto pred2 = Expressions::LessThan("age", Literal::Int(50));
auto and_expr = Expressions::And(pred1, pred2);
ICEBERG_UNWRAP_OR_FAIL(auto bound_and, Bind(and_expr));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_and));
// Should only contain the field ID once (set semantics)
EXPECT_EQ(refs.size(), 1);
EXPECT_EQ(refs.count(3), 1); // age field
}
TEST_F(ReferenceVisitorTest, SetPredicates) {
// Test In predicate
auto pred_in =
Expressions::In("age", {Literal::Int(25), Literal::Int(30), Literal::Int(35)});
ICEBERG_UNWRAP_OR_FAIL(auto bound_in, Bind(pred_in));
ICEBERG_UNWRAP_OR_FAIL(auto refs_in, ReferenceVisitor::GetReferencedFieldIds(bound_in));
EXPECT_EQ(refs_in.size(), 1);
EXPECT_EQ(refs_in.count(3), 1); // age field
// Test NotIn predicate
auto pred_not_in =
Expressions::NotIn("name", {Literal::String("Alice"), Literal::String("Bob")});
ICEBERG_UNWRAP_OR_FAIL(auto bound_not_in, Bind(pred_not_in));
ICEBERG_UNWRAP_OR_FAIL(auto refs_not_in,
ReferenceVisitor::GetReferencedFieldIds(bound_not_in));
EXPECT_EQ(refs_not_in.size(), 1);
EXPECT_EQ(refs_not_in.count(2), 1); // name field
}
TEST_F(ReferenceVisitorTest, MixedBoundAndUnbound) {
auto bound_pred = Expressions::Equal("name", Literal::String("Alice"));
ICEBERG_UNWRAP_OR_FAIL(auto pred1, Bind(bound_pred));
auto unbound_pred = Expressions::GreaterThan("age", Literal::Int(25));
auto mixed_and = Expressions::And(pred1, unbound_pred);
auto result = ReferenceVisitor::GetReferencedFieldIds(mixed_and);
EXPECT_THAT(result, IsError(ErrorKind::kInvalidExpression));
EXPECT_THAT(result,
HasErrorMessage("Cannot get referenced field IDs from unbound predicate"));
}
TEST_F(ReferenceVisitorTest, AllFields) {
// Create expression referencing all fields in the schema
auto pred1 = Expressions::NotNull("id");
auto pred2 = Expressions::Equal("name", Literal::String("Test"));
auto pred3 = Expressions::GreaterThan("age", Literal::Int(0));
auto pred4 = Expressions::LessThan("salary", Literal::Double(100000.0));
auto pred5 = Expressions::Equal("active", Literal::Boolean(true));
auto and1 = Expressions::And(pred1, pred2);
auto and2 = Expressions::And(pred3, pred4);
auto and3 = Expressions::And(and1, and2);
auto all_fields = Expressions::And(and3, pred5);
ICEBERG_UNWRAP_OR_FAIL(auto bound_all, Bind(all_fields));
ICEBERG_UNWRAP_OR_FAIL(auto refs, ReferenceVisitor::GetReferencedFieldIds(bound_all));
// Should reference all 5 fields
EXPECT_EQ(refs.size(), 4);
EXPECT_EQ(refs.count(1), 0); // id field is optimized out
EXPECT_EQ(refs.count(2), 1); // name field
EXPECT_EQ(refs.count(3), 1); // age field
EXPECT_EQ(refs.count(4), 1); // salary field
EXPECT_EQ(refs.count(5), 1); // active field
}
} // namespace iceberg