This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new e684be28 feat: add strict projection to transform (#387)
e684be28 is described below
commit e684be286509257ce00ad9d96382ac5a41f1c3a9
Author: Junwang Zhao <[email protected]>
AuthorDate: Fri Dec 5 10:35:39 2025 +0800
feat: add strict projection to transform (#387)
---
src/iceberg/test/predicate_test.cc | 87 ++--
src/iceberg/test/transform_test.cc | 771 ++++++++++++++++++++++++----
src/iceberg/transform.cc | 60 +++
src/iceberg/transform.h | 12 +
src/iceberg/util/projection_util_internal.h | 655 ++++++++++++++---------
5 files changed, 1196 insertions(+), 389 deletions(-)
diff --git a/src/iceberg/test/predicate_test.cc
b/src/iceberg/test/predicate_test.cc
index 532e908b..fab0b561 100644
--- a/src/iceberg/test/predicate_test.cc
+++ b/src/iceberg/test/predicate_test.cc
@@ -26,7 +26,6 @@
#include "iceberg/schema.h"
#include "iceberg/test/matchers.h"
#include "iceberg/type.h"
-#include "iceberg/util/macros.h"
namespace iceberg {
@@ -607,24 +606,24 @@ std::shared_ptr<BoundPredicate>
AssertAndCastToBoundPredicate(
} // namespace
TEST_F(PredicateTest, BoundUnaryPredicateTestIsNull) {
- ICEBERG_ASSIGN_OR_THROW(auto is_null_pred, Expressions::IsNull("name")->Bind(
- *schema_,
/*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto not_null_pred,
Expressions::NotNull("name")->Bind(
- *schema_,
/*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto is_nan_pred, Expressions::IsNaN("salary")->Bind(
- *schema_,
/*case_sensitive=*/true));
+ 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
@@ -643,8 +642,8 @@ TEST_F(PredicateTest, BoundUnaryPredicateTestIsNaN) {
}
TEST_F(PredicateTest, BoundUnaryPredicateTestNotNaN) {
- ICEBERG_ASSIGN_OR_THROW(auto not_nan_pred,
Expressions::NotNaN("salary")->Bind(
- *schema_,
/*case_sensitive=*/true));
+ 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
@@ -661,34 +660,34 @@ TEST_F(PredicateTest, BoundUnaryPredicateTestNotNaN) {
TEST_F(PredicateTest, BoundLiteralPredicateTestComparison) {
// Test less than
- ICEBERG_ASSIGN_OR_THROW(auto lt_pred, Expressions::LessThan("age",
Literal::Int(30))
- ->Bind(*schema_,
/*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto lte_pred,
- Expressions::LessThanOrEqual("age", Literal::Int(30))
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto gt_pred, Expressions::GreaterThan("age",
Literal::Int(30))
- ->Bind(*schema_,
/*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto gte_pred,
- Expressions::GreaterThanOrEqual("age",
Literal::Int(30))
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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)));
@@ -697,16 +696,16 @@ TEST_F(PredicateTest,
BoundLiteralPredicateTestComparison) {
TEST_F(PredicateTest, BoundLiteralPredicateTestEquality) {
// Test equal
- ICEBERG_ASSIGN_OR_THROW(auto eq_pred, Expressions::Equal("age",
Literal::Int(25))
- ->Bind(*schema_,
/*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto neq_pred, Expressions::NotEqual("age",
Literal::Int(25))
- ->Bind(*schema_,
/*case_sensitive=*/true));
+ 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)));
@@ -715,18 +714,18 @@ TEST_F(PredicateTest, BoundLiteralPredicateTestEquality) {
TEST_F(PredicateTest, BoundLiteralPredicateTestWithDifferentTypes) {
// Test with double
- ICEBERG_ASSIGN_OR_THROW(auto gt_pred,
- Expressions::GreaterThan("salary",
Literal::Double(50000.0))
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(auto str_eq_pred,
- Expressions::Equal("name", Literal::String("Alice"))
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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)));
@@ -734,16 +733,16 @@ TEST_F(PredicateTest,
BoundLiteralPredicateTestWithDifferentTypes) {
HasValue(testing::Eq(false))); // Case sensitive
// Test with boolean
- ICEBERG_ASSIGN_OR_THROW(auto bool_eq_pred,
- Expressions::Equal("active", Literal::Boolean(true))
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto starts_with_pred,
Expressions::StartsWith("name", "Jo")->Bind(*schema_,
/*case_sensitive=*/true));
auto bound_pred = AssertAndCastToBoundPredicate(starts_with_pred);
@@ -759,7 +758,7 @@ TEST_F(PredicateTest, BoundLiteralPredicateTestStartsWith) {
EXPECT_THAT(bound_pred->Test(Literal::String("")),
HasValue(testing::Eq(false)));
// Test empty prefix
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto empty_prefix_pred,
Expressions::StartsWith("name", "")->Bind(*schema_,
/*case_sensitive=*/true));
auto bound_empty = AssertAndCastToBoundPredicate(empty_prefix_pred);
@@ -770,7 +769,7 @@ TEST_F(PredicateTest, BoundLiteralPredicateTestStartsWith) {
}
TEST_F(PredicateTest, BoundLiteralPredicateTestNotStartsWith) {
- ICEBERG_ASSIGN_OR_THROW(
+ 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);
@@ -787,7 +786,7 @@ TEST_F(PredicateTest,
BoundLiteralPredicateTestNotStartsWith) {
}
TEST_F(PredicateTest, BoundSetPredicateTestIn) {
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto in_pred,
Expressions::In("age", {Literal::Int(10), Literal::Int(20),
Literal::Int(30)})
->Bind(*schema_, /*case_sensitive=*/true));
@@ -805,7 +804,7 @@ TEST_F(PredicateTest, BoundSetPredicateTestIn) {
}
TEST_F(PredicateTest, BoundSetPredicateTestNotIn) {
- ICEBERG_ASSIGN_OR_THROW(
+ 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));
@@ -823,7 +822,7 @@ TEST_F(PredicateTest, BoundSetPredicateTestNotIn) {
}
TEST_F(PredicateTest, BoundSetPredicateTestWithStrings) {
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto in_pred,
Expressions::In("name", {Literal::String("Alice"),
Literal::String("Bob"),
Literal::String("Charlie")})
@@ -843,10 +842,10 @@ TEST_F(PredicateTest, BoundSetPredicateTestWithStrings) {
}
TEST_F(PredicateTest, BoundSetPredicateTestWithLongs) {
- ICEBERG_ASSIGN_OR_THROW(auto in_pred,
- Expressions::In("id", {Literal::Long(100L),
Literal::Long(200L),
- Literal::Long(300L)})
- ->Bind(*schema_, /*case_sensitive=*/true));
+ 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
@@ -860,8 +859,8 @@ TEST_F(PredicateTest, BoundSetPredicateTestWithLongs) {
}
TEST_F(PredicateTest, BoundSetPredicateTestSingleLiteral) {
- ICEBERG_ASSIGN_OR_THROW(auto in_pred, Expressions::In("age",
{Literal::Int(42)})
- ->Bind(*schema_,
/*case_sensitive=*/true));
+ 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);
diff --git a/src/iceberg/test/transform_test.cc
b/src/iceberg/test/transform_test.cc
index 821edac5..7f0514df 100644
--- a/src/iceberg/test/transform_test.cc
+++ b/src/iceberg/test/transform_test.cc
@@ -36,7 +36,6 @@
#include "iceberg/type.h"
#include "iceberg/util/checked_cast.h"
#include "iceberg/util/formatter.h" // IWYU pragma: keep
-#include "iceberg/util/macros.h"
namespace iceberg {
@@ -954,12 +953,12 @@ TEST_F(TransformProjectTest, IdentityProjectEquality) {
// Test equality predicate
auto unbound = Expressions::Equal("value", Literal::Int(100));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
@@ -977,23 +976,23 @@ TEST_F(TransformProjectTest, IdentityProjectComparison) {
// Test less than predicate
auto unbound_lt = Expressions::LessThan("value", Literal::Int(50));
- ICEBERG_ASSIGN_OR_THROW(auto bound_lt,
- unbound_lt->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_lt,
+ unbound_lt->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_lt = std::dynamic_pointer_cast<BoundPredicate>(bound_lt);
ASSERT_NE(bound_pred_lt, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_lt, transform->Project("part",
bound_pred_lt));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_lt, transform->Project("part",
bound_pred_lt));
ASSERT_NE(projected_lt, nullptr);
EXPECT_EQ(projected_lt->op(), Expression::Operation::kLt);
// Test greater than or equal predicate
auto unbound_gte = Expressions::GreaterThanOrEqual("value",
Literal::Int(100));
- ICEBERG_ASSIGN_OR_THROW(auto bound_gte,
- unbound_gte->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_gte,
+ unbound_gte->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_gte = std::dynamic_pointer_cast<BoundPredicate>(bound_gte);
ASSERT_NE(bound_pred_gte, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_gte, transform->Project("part",
bound_pred_gte));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_gte, transform->Project("part",
bound_pred_gte));
ASSERT_NE(projected_gte, nullptr);
EXPECT_EQ(projected_gte->op(), Expression::Operation::kGtEq);
}
@@ -1003,13 +1002,13 @@ TEST_F(TransformProjectTest, IdentityProjectUnary) {
// Test IsNull predicate
auto unbound_null = Expressions::IsNull("value");
- ICEBERG_ASSIGN_OR_THROW(auto bound_null,
- unbound_null->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_null,
+ unbound_null->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_null = std::dynamic_pointer_cast<BoundPredicate>(bound_null);
ASSERT_NE(bound_pred_null, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_null,
- transform->Project("part", bound_pred_null));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_null,
+ transform->Project("part", bound_pred_null));
ASSERT_NE(projected_null, nullptr);
EXPECT_EQ(projected_null->op(), Expression::Operation::kIsNull);
}
@@ -1020,12 +1019,12 @@ TEST_F(TransformProjectTest, IdentityProjectSet) {
// Test IN predicate
auto unbound_in =
Expressions::In("value", {Literal::Int(1), Literal::Int(2),
Literal::Int(3)});
- ICEBERG_ASSIGN_OR_THROW(auto bound_in,
- unbound_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_in,
+ unbound_in->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_in = std::dynamic_pointer_cast<BoundPredicate>(bound_in);
ASSERT_NE(bound_pred_in, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_in, transform->Project("part",
bound_pred_in));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_in, transform->Project("part",
bound_pred_in));
ASSERT_NE(projected_in, nullptr);
EXPECT_EQ(projected_in->op(), Expression::Operation::kIn);
auto unbound_projected =
@@ -1046,12 +1045,12 @@ TEST_F(TransformProjectTest, BucketProjectEquality) {
// Bucket can project equality predicates
auto unbound = Expressions::Equal("value", Literal::Int(34));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
@@ -1070,8 +1069,8 @@ TEST_F(TransformProjectTest,
BucketProjectWithMatchingTransformedChild) {
// Create a predicate like: bucket(value, 16) = 5
auto bucket_term = Expressions::Bucket("value", 16);
auto unbound = Expressions::Equal<BoundTransform>(bucket_term,
Literal::Int(5));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
@@ -1080,8 +1079,8 @@ TEST_F(TransformProjectTest,
BucketProjectWithMatchingTransformedChild) {
// When the transform matches, Project should use RemoveTransform and return
the
// predicate
- ICEBERG_ASSIGN_OR_THROW(auto projected,
- partition_transform->Project("part", bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected,
+ partition_transform->Project("part", bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
auto unbound_projected =
@@ -1098,12 +1097,12 @@ TEST_F(TransformProjectTest,
BucketProjectComparisonReturnsNull) {
// Bucket cannot project comparison predicates (they return null)
auto unbound_lt = Expressions::LessThan("value", Literal::Int(50));
- ICEBERG_ASSIGN_OR_THROW(auto bound_lt,
- unbound_lt->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_lt,
+ unbound_lt->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_lt = std::dynamic_pointer_cast<BoundPredicate>(bound_lt);
ASSERT_NE(bound_pred_lt, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_lt, transform->Project("part",
bound_pred_lt));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_lt, transform->Project("part",
bound_pred_lt));
EXPECT_EQ(projected_lt, nullptr);
}
@@ -1113,12 +1112,12 @@ TEST_F(TransformProjectTest, BucketProjectInSet) {
// Bucket can project IN predicates
auto unbound_in =
Expressions::In("value", {Literal::Int(1), Literal::Int(2),
Literal::Int(3)});
- ICEBERG_ASSIGN_OR_THROW(auto bound_in,
- unbound_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_in,
+ unbound_in->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_in = std::dynamic_pointer_cast<BoundPredicate>(bound_in);
ASSERT_NE(bound_pred_in, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_in, transform->Project("part",
bound_pred_in));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_in, transform->Project("part",
bound_pred_in));
ASSERT_NE(projected_in, nullptr);
EXPECT_EQ(projected_in->op(), Expression::Operation::kIn);
}
@@ -1129,13 +1128,13 @@ TEST_F(TransformProjectTest,
BucketProjectNotInReturnsNull) {
// Bucket cannot project NOT IN predicates
auto unbound_not_in =
Expressions::NotIn("value", {Literal::Int(1), Literal::Int(2),
Literal::Int(3)});
- ICEBERG_ASSIGN_OR_THROW(auto bound_not_in,
- unbound_not_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_not_in,
+ unbound_not_in->Bind(*int_schema_,
/*case_sensitive=*/true));
auto bound_pred_not_in =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_in);
ASSERT_NE(bound_pred_not_in, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_not_in,
- transform->Project("part", bound_pred_not_in));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_in,
+ transform->Project("part", bound_pred_not_in));
EXPECT_EQ(projected_not_in, nullptr);
}
@@ -1144,12 +1143,12 @@ TEST_F(TransformProjectTest,
TruncateProjectIntEquality) {
// Truncate can project equality predicates
auto unbound = Expressions::Equal("value", Literal::Int(123));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
@@ -1167,12 +1166,12 @@ TEST_F(TransformProjectTest,
TruncateProjectIntLessThan) {
// Truncate projects LT as LTE
auto unbound = Expressions::LessThan("value", Literal::Int(25));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kLtEq);
}
@@ -1182,12 +1181,12 @@ TEST_F(TransformProjectTest,
TruncateProjectIntGreaterThan) {
// Truncate projects GT as GTE
auto unbound = Expressions::GreaterThan("value", Literal::Int(25));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kGtEq);
@@ -1204,12 +1203,12 @@ TEST_F(TransformProjectTest,
TruncateProjectStringEquality) {
auto transform = Transform::Truncate(5);
auto unbound = Expressions::Equal("value", Literal::String("Hello, World!"));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*string_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*string_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
@@ -1228,13 +1227,13 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWith) {
// StartsWith with shorter string than width
auto unbound_short = Expressions::StartsWith("value", "Hi");
- ICEBERG_ASSIGN_OR_THROW(auto bound_short,
- unbound_short->Bind(*string_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_short,
+ unbound_short->Bind(*string_schema_,
/*case_sensitive=*/true));
auto bound_pred_short =
std::dynamic_pointer_cast<BoundPredicate>(bound_short);
ASSERT_NE(bound_pred_short, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_short,
- transform->Project("part", bound_pred_short));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_short,
+ transform->Project("part", bound_pred_short));
ASSERT_NE(projected_short, nullptr);
EXPECT_EQ(projected_short->op(), Expression::Operation::kStartsWith);
@@ -1249,13 +1248,13 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWith) {
// StartsWith with string equal to width
auto unbound_equal = Expressions::StartsWith("value", "Hello");
- ICEBERG_ASSIGN_OR_THROW(auto bound_equal,
- unbound_equal->Bind(*string_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_equal,
+ unbound_equal->Bind(*string_schema_,
/*case_sensitive=*/true));
auto bound_pred_equal =
std::dynamic_pointer_cast<BoundPredicate>(bound_equal);
ASSERT_NE(bound_pred_equal, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_equal,
- transform->Project("part", bound_pred_equal));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_equal,
+ transform->Project("part", bound_pred_equal));
ASSERT_NE(projected_equal, nullptr);
EXPECT_EQ(projected_equal->op(), Expression::Operation::kEq);
@@ -1275,15 +1274,15 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWithCodePointCountLessTh
// Code point count < width (multi-byte UTF-8 characters)
// "ππ§" has 2 code points, width is 5
auto unbound_emoji_short = Expressions::StartsWith("value", "ππ§");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_emoji_short,
unbound_emoji_short->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_emoji_short =
std::dynamic_pointer_cast<BoundPredicate>(bound_emoji_short);
ASSERT_NE(bound_pred_emoji_short, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_emoji_short,
- transform->Project("part", bound_pred_emoji_short));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_emoji_short,
+ transform->Project("part", bound_pred_emoji_short));
ASSERT_NE(projected_emoji_short, nullptr);
EXPECT_EQ(projected_emoji_short->op(), Expression::Operation::kStartsWith);
@@ -1304,15 +1303,15 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWithCodePointCountEqualT
// Code point count == width (exactly 5 code points)
// "ππ§π€π€ͺπ₯³" has exactly 5 code points
auto unbound_emoji_equal = Expressions::StartsWith("value", "ππ§π€π€ͺπ₯³");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_emoji_equal,
unbound_emoji_equal->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_emoji_equal =
std::dynamic_pointer_cast<BoundPredicate>(bound_emoji_equal);
ASSERT_NE(bound_pred_emoji_equal, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_emoji_equal,
- transform->Project("part", bound_pred_emoji_equal));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_emoji_equal,
+ transform->Project("part", bound_pred_emoji_equal));
ASSERT_NE(projected_emoji_equal, nullptr);
EXPECT_EQ(projected_emoji_equal->op(), Expression::Operation::kEq);
@@ -1335,15 +1334,15 @@ TEST_F(TransformProjectTest,
// "ππ§π€π€ͺπ₯³π΅βπ«π" has 7 code points, should truncate to 5
auto unbound_emoji_long =
Expressions::StartsWith("value", "ππ§π€π€ͺπ₯³π΅βπ«π");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_emoji_long,
unbound_emoji_long->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_emoji_long =
std::dynamic_pointer_cast<BoundPredicate>(bound_emoji_long);
ASSERT_NE(bound_pred_emoji_long, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_emoji_long,
- transform->Project("part", bound_pred_emoji_long));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_emoji_long,
+ transform->Project("part", bound_pred_emoji_long));
ASSERT_NE(projected_emoji_long, nullptr);
EXPECT_EQ(projected_emoji_long->op(), Expression::Operation::kStartsWith);
@@ -1364,15 +1363,15 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWithMixedAsciiAndMultiBy
// Mixed ASCII and multi-byte UTF-8 characters
// "aπbπ§c" has 5 code points (3 ASCII + 2 emojis)
auto unbound_mixed_equal = Expressions::StartsWith("value", "aπbπ§c");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_mixed_equal,
unbound_mixed_equal->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_mixed_equal =
std::dynamic_pointer_cast<BoundPredicate>(bound_mixed_equal);
ASSERT_NE(bound_pred_mixed_equal, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_mixed_equal,
- transform->Project("part", bound_pred_mixed_equal));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_mixed_equal,
+ transform->Project("part", bound_pred_mixed_equal));
ASSERT_NE(projected_mixed_equal, nullptr);
EXPECT_EQ(projected_mixed_equal->op(), Expression::Operation::kEq);
@@ -1393,15 +1392,15 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWithChineseCharactersSho
// Chinese characters (3-byte UTF-8)
// "δ½ ε₯½δΈη" has 4 code points, width is 5
auto unbound_chinese_short = Expressions::StartsWith("value", "δ½ ε₯½δΈη");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_chinese_short,
unbound_chinese_short->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_chinese_short =
std::dynamic_pointer_cast<BoundPredicate>(bound_chinese_short);
ASSERT_NE(bound_pred_chinese_short, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_chinese_short,
- transform->Project("part",
bound_pred_chinese_short));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_chinese_short,
+ transform->Project("part", bound_pred_chinese_short));
ASSERT_NE(projected_chinese_short, nullptr);
EXPECT_EQ(projected_chinese_short->op(), Expression::Operation::kStartsWith);
@@ -1422,15 +1421,15 @@ TEST_F(TransformProjectTest,
TruncateProjectStringStartsWithChineseCharactersEqu
// Chinese characters exactly matching width
// "δ½ ε₯½δΈηε₯½" has exactly 5 code points
auto unbound_chinese_equal = Expressions::StartsWith("value", "δ½ ε₯½δΈηε₯½");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_chinese_equal,
unbound_chinese_equal->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_chinese_equal =
std::dynamic_pointer_cast<BoundPredicate>(bound_chinese_equal);
ASSERT_NE(bound_pred_chinese_equal, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_chinese_equal,
- transform->Project("part",
bound_pred_chinese_equal));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_chinese_equal,
+ transform->Project("part", bound_pred_chinese_equal));
ASSERT_NE(projected_chinese_equal, nullptr);
EXPECT_EQ(projected_chinese_equal->op(), Expression::Operation::kEq);
@@ -1452,15 +1451,15 @@ TEST_F(TransformProjectTest,
// NotStartsWith with code point count == width
// Should convert to NotEq
auto unbound_not_starts_equal = Expressions::NotStartsWith("value", "ππ§π€π€ͺπ₯³");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_not_starts_equal,
unbound_not_starts_equal->Bind(*string_schema_,
/*case_sensitive=*/true));
auto bound_pred_not_starts_equal =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_starts_equal);
ASSERT_NE(bound_pred_not_starts_equal, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_not_starts_equal,
- transform->Project("part",
bound_pred_not_starts_equal));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_starts_equal,
+ transform->Project("part",
bound_pred_not_starts_equal));
ASSERT_NE(projected_not_starts_equal, nullptr);
EXPECT_EQ(projected_not_starts_equal->op(), Expression::Operation::kNotEq);
@@ -1482,15 +1481,15 @@ TEST_F(TransformProjectTest,
// NotStartsWith with code point count < width
// Should remain NotStartsWith
auto unbound_not_starts_short = Expressions::NotStartsWith("value", "ππ§");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_not_starts_short,
unbound_not_starts_short->Bind(*string_schema_,
/*case_sensitive=*/true));
auto bound_pred_not_starts_short =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_starts_short);
ASSERT_NE(bound_pred_not_starts_short, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_not_starts_short,
- transform->Project("part",
bound_pred_not_starts_short));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_starts_short,
+ transform->Project("part",
bound_pred_not_starts_short));
ASSERT_NE(projected_not_starts_short, nullptr);
EXPECT_EQ(projected_not_starts_short->op(),
Expression::Operation::kNotStartsWith);
@@ -1514,15 +1513,15 @@ TEST_F(TransformProjectTest,
// Should return nullptr (cannot project)
auto unbound_not_starts_long =
Expressions::NotStartsWith("value", "ππ§π€π€ͺπ₯³π΅βπ«π");
- ICEBERG_ASSIGN_OR_THROW(
+ ICEBERG_UNWRAP_OR_FAIL(
auto bound_not_starts_long,
unbound_not_starts_long->Bind(*string_schema_, /*case_sensitive=*/true));
auto bound_pred_not_starts_long =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_starts_long);
ASSERT_NE(bound_pred_not_starts_long, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_not_starts_long,
- transform->Project("part",
bound_pred_not_starts_long));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_starts_long,
+ transform->Project("part",
bound_pred_not_starts_long));
EXPECT_EQ(projected_not_starts_long, nullptr);
}
@@ -1533,12 +1532,12 @@ TEST_F(TransformProjectTest, YearProjectEquality) {
int32_t date_value =
TemporalTestHelper::CreateDate({.year = 2021, .month = 6, .day = 1});
auto unbound = Expressions::Equal("value", Literal::Date(date_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
}
@@ -1551,23 +1550,23 @@ TEST_F(TransformProjectTest, YearProjectComparison) {
// LT projects to LTE
auto unbound_lt = Expressions::LessThan("value", Literal::Date(date_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound_lt,
- unbound_lt->Bind(*date_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_lt,
+ unbound_lt->Bind(*date_schema_,
/*case_sensitive=*/true));
auto bound_pred_lt = std::dynamic_pointer_cast<BoundPredicate>(bound_lt);
ASSERT_NE(bound_pred_lt, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_lt, transform->Project("part",
bound_pred_lt));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_lt, transform->Project("part",
bound_pred_lt));
ASSERT_NE(projected_lt, nullptr);
EXPECT_EQ(projected_lt->op(), Expression::Operation::kLtEq);
// GT projects to GTE
auto unbound_gt = Expressions::GreaterThan("value",
Literal::Date(date_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound_gt,
- unbound_gt->Bind(*date_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_gt,
+ unbound_gt->Bind(*date_schema_,
/*case_sensitive=*/true));
auto bound_pred_gt = std::dynamic_pointer_cast<BoundPredicate>(bound_gt);
ASSERT_NE(bound_pred_gt, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_gt, transform->Project("part",
bound_pred_gt));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_gt, transform->Project("part",
bound_pred_gt));
ASSERT_NE(projected_gt, nullptr);
EXPECT_EQ(projected_gt->op(), Expression::Operation::kGtEq);
}
@@ -1578,12 +1577,12 @@ TEST_F(TransformProjectTest, MonthProjectEquality) {
int64_t ts_value =
TemporalTestHelper::CreateTimestamp({.year = 2021, .month = 6, .day =
1});
auto unbound = Expressions::Equal("value", Literal::Timestamp(ts_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
}
@@ -1594,12 +1593,12 @@ TEST_F(TransformProjectTest, DayProjectEquality) {
int32_t date_value =
TemporalTestHelper::CreateDate({.year = 2021, .month = 6, .day = 15});
auto unbound = Expressions::Equal("value", Literal::Date(date_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
}
@@ -1610,12 +1609,12 @@ TEST_F(TransformProjectTest, HourProjectEquality) {
int64_t ts_value = TemporalTestHelper::CreateTimestamp(
{.year = 2021, .month = 6, .day = 1, .hour = 14, .minute = 30});
auto unbound = Expressions::Equal("value", Literal::Timestamp(ts_value));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
EXPECT_EQ(projected->op(), Expression::Operation::kEq);
}
@@ -1624,13 +1623,13 @@ TEST_F(TransformProjectTest, VoidProjectReturnsNull) {
auto transform = Transform::Void();
auto unbound = Expressions::Equal("value", Literal::Int(100));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*int_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
// Void transform always returns null (no projection possible)
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
EXPECT_EQ(projected, nullptr);
}
@@ -1643,12 +1642,12 @@ TEST_F(TransformProjectTest, TemporalProjectInSet) {
auto unbound_in = Expressions::In(
"value", {Literal::Date(date1), Literal::Date(date2),
Literal::Date(date3)});
- ICEBERG_ASSIGN_OR_THROW(auto bound_in,
- unbound_in->Bind(*date_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_in,
+ unbound_in->Bind(*date_schema_,
/*case_sensitive=*/true));
auto bound_pred_in = std::dynamic_pointer_cast<BoundPredicate>(bound_in);
ASSERT_NE(bound_pred_in, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected_in, transform->Project("part",
bound_pred_in));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_in, transform->Project("part",
bound_pred_in));
ASSERT_NE(projected_in, nullptr);
EXPECT_EQ(projected_in->op(), Expression::Operation::kIn);
}
@@ -1663,12 +1662,12 @@ TEST_F(TransformProjectTest, DayTimestampProjectionFix)
{
// If we fix (for buggy writers), we project to day <= 0.
auto unbound = Expressions::LessThan("value", Literal::Timestamp(0));
- ICEBERG_ASSIGN_OR_THROW(auto bound,
- unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
ASSERT_NE(bound_pred, nullptr);
- ICEBERG_ASSIGN_OR_THROW(auto projected, transform->Project("part",
bound_pred));
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->Project("part",
bound_pred));
ASSERT_NE(projected, nullptr);
auto unbound_projected =
@@ -1680,4 +1679,560 @@ TEST_F(TransformProjectTest, DayTimestampProjectionFix)
{
EXPECT_EQ(val, 0) << "Expected projected value to be 0 (fix applied), but
got " << val;
}
+// Test fixture for Transform::ProjectStrict tests
+class TransformProjectStrictTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Create test schemas for different source types
+ int_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
int32())},
+ /*schema_id=*/0);
+ long_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
int64())},
+ /*schema_id=*/0);
+ string_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
string())},
+ /*schema_id=*/0);
+ date_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
date())},
+ /*schema_id=*/0);
+ timestamp_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
timestamp())},
+ /*schema_id=*/0);
+ decimal_schema_ = std::make_shared<Schema>(
+ std::vector<SchemaField>{SchemaField::MakeRequired(1, "value",
decimal(9, 2))},
+ /*schema_id=*/0);
+ }
+
+ std::shared_ptr<Schema> int_schema_;
+ std::shared_ptr<Schema> long_schema_;
+ std::shared_ptr<Schema> string_schema_;
+ std::shared_ptr<Schema> date_schema_;
+ std::shared_ptr<Schema> timestamp_schema_;
+ std::shared_ptr<Schema> decimal_schema_;
+};
+
+TEST_F(TransformProjectStrictTest, IdentityStrictProjection) {
+ auto transform = Transform::Identity();
+
+ // Identity strict projection should behave the same as inclusive projection
+ auto unbound = Expressions::Equal("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kEq);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kEq);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
100);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictEqualityReturnsFalse) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection: equality should return FALSE (cannot guarantee
equality)
+ auto unbound = Expressions::Equal("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ EXPECT_EQ(projected, nullptr);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictNotEqual) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection: notEqual can be projected
+ auto unbound = Expressions::NotEqual("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kNotEq);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kNotEq);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ // bucket(100, 10) = 6
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
6);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictComparisonReturnsNull) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection: comparison predicates return null
+ auto unbound_lt = Expressions::LessThan("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_lt,
+ unbound_lt->Bind(*int_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_lt = std::dynamic_pointer_cast<BoundPredicate>(bound_lt);
+ ASSERT_NE(bound_pred_lt, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_lt,
+ transform->ProjectStrict("part", bound_pred_lt));
+ EXPECT_EQ(projected_lt, nullptr);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictNotIn) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection: NOT_IN can be projected
+ auto unbound_not_in = Expressions::NotIn(
+ "value", {Literal::Int(99), Literal::Int(100), Literal::Int(101)});
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_not_in,
+ unbound_not_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_not_in =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_in);
+ ASSERT_NE(bound_pred_not_in, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_in,
+ transform->ProjectStrict("part", bound_pred_not_in));
+ ASSERT_NE(projected_not_in, nullptr);
+ EXPECT_EQ(projected_not_in->op(), Expression::Operation::kNotIn);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictInReturnsNull) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection: IN returns null (cannot guarantee)
+ auto unbound_in =
+ Expressions::In("value", {Literal::Int(99), Literal::Int(100),
Literal::Int(101)});
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_in,
+ unbound_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_in = std::dynamic_pointer_cast<BoundPredicate>(bound_in);
+ ASSERT_NE(bound_pred_in, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_in,
+ transform->ProjectStrict("part", bound_pred_in));
+ EXPECT_EQ(projected_in, nullptr);
+}
+
+TEST_F(TransformProjectStrictTest, BucketStrictString) {
+ auto transform = Transform::Bucket(10);
+
+ // Bucket strict projection for string
+ auto unbound_not_eq = Expressions::NotEqual("value",
Literal::String("abcdefg"));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_not_eq,
+ unbound_not_eq->Bind(*string_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_not_eq =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_eq);
+ ASSERT_NE(bound_pred_not_eq, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_eq,
+ transform->ProjectStrict("part", bound_pred_not_eq));
+ ASSERT_NE(projected_not_eq, nullptr);
+ EXPECT_EQ(projected_not_eq->op(), Expression::Operation::kNotEq);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntEqualityReturnsNull) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: equality returns null (cannot guarantee)
+ auto unbound = Expressions::Equal("value", Literal::Int(123));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ EXPECT_EQ(projected, nullptr);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntLessThan) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: LT projects to LT
+ auto unbound = Expressions::LessThan("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kLt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
100);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntLessThanOrEqual) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: LTE projects to LT
+ auto unbound = Expressions::LessThanOrEqual("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kLt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
100);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntGreaterThan) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: GT projects to GT
+ auto unbound = Expressions::GreaterThan("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kGt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kGt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
100);
+}
+
+TEST_F(TransformProjectStrictTest,
TruncateStrictIntGreaterThanOrEqualLowerBound) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: GTE projects to GT (lower bound, value = 100)
+ auto unbound = Expressions::GreaterThanOrEqual("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kGt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kGt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ // For GTE with value 100 and width 10, truncate(100) = 100, so GT should be
90
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
90);
+}
+
+TEST_F(TransformProjectStrictTest,
TruncateStrictIntGreaterThanOrEqualUpperBound) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: GTE projects to GT (upper bound, value = 99)
+ auto unbound = Expressions::GreaterThanOrEqual("value", Literal::Int(99));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kGt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kGt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ // For GTE with value 99 and width 10, truncate(99) = 90, so GT should be 90
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
90);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntNotEqual) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: notEqual can be projected
+ auto unbound = Expressions::NotEqual("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kNotEq);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kNotEq);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
100);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictIntNotIn) {
+ auto transform = Transform::Truncate(10);
+
+ // Truncate strict projection: NOT_IN can be projected
+ auto unbound_not_in = Expressions::NotIn(
+ "value", {Literal::Int(99), Literal::Int(100), Literal::Int(101)});
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_not_in,
+ unbound_not_in->Bind(*int_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_not_in =
std::dynamic_pointer_cast<BoundPredicate>(bound_not_in);
+ ASSERT_NE(bound_pred_not_in, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_not_in,
+ transform->ProjectStrict("part", bound_pred_not_in));
+ ASSERT_NE(projected_not_in, nullptr);
+ EXPECT_EQ(projected_not_in->op(), Expression::Operation::kNotIn);
+}
+
+TEST_F(TransformProjectStrictTest, TruncateStrictString) {
+ auto transform = Transform::Truncate(5);
+
+ // Truncate strict projection for string
+ auto unbound_lt = Expressions::LessThan("value", Literal::String("abcdefg"));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound_lt,
+ unbound_lt->Bind(*string_schema_,
/*case_sensitive=*/true));
+ auto bound_pred_lt = std::dynamic_pointer_cast<BoundPredicate>(bound_lt);
+ ASSERT_NE(bound_pred_lt, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected_lt,
+ transform->ProjectStrict("part", bound_pred_lt));
+ ASSERT_NE(projected_lt, nullptr);
+ EXPECT_EQ(projected_lt->op(), Expression::Operation::kLt);
+
+ auto unbound_projected_lt =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected_lt));
+ EXPECT_EQ(unbound_projected_lt->op(), Expression::Operation::kLt);
+ EXPECT_EQ(unbound_projected_lt->literals().size(), 1);
+
EXPECT_EQ(std::get<std::string>(unbound_projected_lt->literals().front().value()),
+ "abcde");
+}
+
+TEST_F(TransformProjectStrictTest, YearStrictEqualityReturnsNull) {
+ auto transform = Transform::Year();
+
+ // Year strict projection: equality returns null (cannot guarantee)
+ int32_t date_value =
+ TemporalTestHelper::CreateDate({.year = 2021, .month = 6, .day = 1});
+ auto unbound = Expressions::Equal("value", Literal::Date(date_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ EXPECT_EQ(projected, nullptr);
+}
+
+TEST_F(TransformProjectStrictTest, YearStrictLessThan) {
+ auto transform = Transform::Year();
+
+ // Year strict projection: LT projects to LT
+ int32_t date_value =
+ TemporalTestHelper::CreateDate({.year = 2021, .month = 1, .day = 1});
+ auto unbound = Expressions::LessThan("value", Literal::Date(date_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kLt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
2021);
+}
+
+TEST_F(TransformProjectStrictTest, YearStrictGreaterThanOrEqual) {
+ auto transform = Transform::Year();
+
+ // Year strict projection: GTE projects to GT (lower bound)
+ int32_t date_value =
+ TemporalTestHelper::CreateDate({.year = 2021, .month = 1, .day = 1});
+ auto unbound = Expressions::GreaterThanOrEqual("value",
Literal::Date(date_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kGt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kGt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
2020);
+}
+
+TEST_F(TransformProjectStrictTest, YearStrictNotEqual) {
+ auto transform = Transform::Year();
+
+ // Year strict projection: notEqual can be projected
+ int32_t date_value =
+ TemporalTestHelper::CreateDate({.year = 2021, .month = 1, .day = 1});
+ auto unbound = Expressions::NotEqual("value", Literal::Date(date_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kNotEq);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kNotEq);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
2021);
+}
+
+TEST_F(TransformProjectStrictTest, MonthStrictLessThan) {
+ auto transform = Transform::Month();
+
+ // Month strict projection: LT projects to LT
+ int64_t ts_value =
+ TemporalTestHelper::CreateTimestamp({.year = 2017, .month = 12, .day =
1});
+ auto unbound = Expressions::LessThan("value", Literal::Timestamp(ts_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+}
+
+TEST_F(TransformProjectStrictTest, DayStrictLessThan) {
+ auto transform = Transform::Day();
+
+ // Day strict projection: LT projects to LT
+ int64_t ts_value =
+ TemporalTestHelper::CreateTimestamp({.year = 2017, .month = 12, .day =
1});
+ auto unbound = Expressions::LessThan("value", Literal::Timestamp(ts_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+}
+
+TEST_F(TransformProjectStrictTest, HourStrictLessThan) {
+ auto transform = Transform::Hour();
+
+ // Hour strict projection: LT projects to LT
+ int64_t ts_value = TemporalTestHelper::CreateTimestamp(
+ {.year = 2017, .month = 12, .day = 1, .hour = 10, .minute = 0});
+ auto unbound = Expressions::LessThan("value", Literal::Timestamp(ts_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+}
+
+TEST_F(TransformProjectStrictTest, DayStrictEpoch) {
+ auto transform = Transform::Day();
+
+ // Day strict projection at epoch: LT projects to LT
+ auto unbound = Expressions::LessThan("value", Literal::Timestamp(0));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+}
+
+TEST_F(TransformProjectStrictTest, MonthStrictNotEqualNegative) {
+ auto transform = Transform::Month();
+
+ // Month strict projection: notEqual with negative dates may convert to
NOT_IN
+ int64_t ts_value =
+ TemporalTestHelper::CreateTimestamp({.year = 1969, .month = 1, .day =
1});
+ auto unbound = Expressions::NotEqual("value", Literal::Timestamp(ts_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*timestamp_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ // For negative dates, NOT_EQ may convert to NOT_IN
+ EXPECT_TRUE(projected->op() == Expression::Operation::kNotEq ||
+ projected->op() == Expression::Operation::kNotIn);
+}
+
+TEST_F(TransformProjectStrictTest, YearStrictUpperBound) {
+ auto transform = Transform::Year();
+
+ // Year strict projection: upper bound (end of year)
+ int32_t date_value =
+ TemporalTestHelper::CreateDate({.year = 2017, .month = 12, .day = 31});
+ auto unbound = Expressions::LessThanOrEqual("value",
Literal::Date(date_value));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*date_schema_,
/*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ ASSERT_NE(projected, nullptr);
+ EXPECT_EQ(projected->op(), Expression::Operation::kLt);
+
+ auto unbound_projected =
+ internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected));
+ EXPECT_EQ(unbound_projected->op(), Expression::Operation::kLt);
+ EXPECT_EQ(unbound_projected->literals().size(), 1);
+ EXPECT_EQ(std::get<int32_t>(unbound_projected->literals().front().value()),
2018);
+}
+
+TEST_F(TransformProjectStrictTest, VoidStrictReturnsNull) {
+ auto transform = Transform::Void();
+
+ // Void transform always returns null for strict projection
+ auto unbound = Expressions::Equal("value", Literal::Int(100));
+ ICEBERG_UNWRAP_OR_FAIL(auto bound,
+ unbound->Bind(*int_schema_, /*case_sensitive=*/true));
+ auto bound_pred = std::dynamic_pointer_cast<BoundPredicate>(bound);
+ ASSERT_NE(bound_pred, nullptr);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto projected, transform->ProjectStrict("part",
bound_pred));
+ EXPECT_EQ(projected, nullptr);
+}
+
} // namespace iceberg
diff --git a/src/iceberg/transform.cc b/src/iceberg/transform.cc
index f8d2f065..61448971 100644
--- a/src/iceberg/transform.cc
+++ b/src/iceberg/transform.cc
@@ -306,6 +306,66 @@ Result<std::unique_ptr<UnboundPredicate>>
Transform::Project(
std::unreachable();
}
+Result<std::unique_ptr<UnboundPredicate>> Transform::ProjectStrict(
+ std::string_view name, const std::shared_ptr<BoundPredicate>& predicate) {
+ switch (transform_type_) {
+ case TransformType::kIdentity:
+ return ProjectionUtil::IdentityProject(name, predicate);
+ case TransformType::kBucket: {
+ // If the predicate has a transformed child that matches the given
transform, return
+ // a predicate.
+ if (predicate->term()->kind() == Term::Kind::kTransform) {
+ const auto boundTransform =
+ internal::checked_pointer_cast<BoundTransform>(predicate->term());
+ if (*this == *boundTransform->transform()) {
+ return ProjectionUtil::RemoveTransform(name, predicate);
+ } else {
+ return nullptr;
+ }
+ }
+ ICEBERG_ASSIGN_OR_RAISE(auto func, Bind(predicate->term()->type()));
+ return ProjectionUtil::BucketProjectStrict(name, predicate, func);
+ }
+ case TransformType::kTruncate: {
+ // If the predicate has a transformed child that matches the given
transform, return
+ // a predicate.
+ if (predicate->term()->kind() == Term::Kind::kTransform) {
+ const auto boundTransform =
+ internal::checked_pointer_cast<BoundTransform>(predicate->term());
+ if (*this == *boundTransform->transform()) {
+ return ProjectionUtil::RemoveTransform(name, predicate);
+ } else {
+ return nullptr;
+ }
+ }
+ ICEBERG_ASSIGN_OR_RAISE(auto func, Bind(predicate->term()->type()));
+ return ProjectionUtil::TruncateProjectStrict(name, predicate, func);
+ }
+ case TransformType::kYear:
+ case TransformType::kMonth:
+ case TransformType::kDay:
+ case TransformType::kHour: {
+ // If the predicate has a transformed child that matches the given
transform, return
+ // a predicate.
+ if (predicate->term()->kind() == Term::Kind::kTransform) {
+ const auto boundTransform =
+ internal::checked_pointer_cast<BoundTransform>(predicate->term());
+ if (*this == *boundTransform->transform()) {
+ return ProjectionUtil::RemoveTransform(name, predicate);
+ } else {
+ return nullptr;
+ }
+ }
+ ICEBERG_ASSIGN_OR_RAISE(auto func, Bind(predicate->term()->type()));
+ return ProjectionUtil::TemporalProjectStrict(name, predicate, func);
+ }
+ case TransformType::kUnknown:
+ case TransformType::kVoid:
+ return nullptr;
+ }
+ std::unreachable();
+}
+
bool TransformFunction::Equals(const TransformFunction& other) const {
return transform_type_ == other.transform_type_ && *source_type_ ==
*other.source_type_;
}
diff --git a/src/iceberg/transform.h b/src/iceberg/transform.h
index 64b85072..53993b4e 100644
--- a/src/iceberg/transform.h
+++ b/src/iceberg/transform.h
@@ -182,6 +182,18 @@ class ICEBERG_EXPORT Transform : public util::Formattable {
Result<std::unique_ptr<UnboundPredicate>> Project(
std::string_view name, const std::shared_ptr<BoundPredicate>& predicate);
+ /// \brief Transforms a BoundPredicate to a strict predicate on the
partition values
+ /// produced by the transform.
+ ///
+ /// This strict transform guarantees that if Projected(transform(value)) is
true, then
+ /// predicate->Test(value) is also true.
+ /// \param name The name of the partition column.
+ /// \param predicate The predicate to project.
+ /// \return A Result containing either a unique pointer to the projected
predicate,
+ /// nullptr if the projection cannot be performed, or an Error if the
projection fails.
+ Result<std::unique_ptr<UnboundPredicate>> ProjectStrict(
+ std::string_view name, const std::shared_ptr<BoundPredicate>& predicate);
+
/// \brief Returns a string representation of this transform (e.g.,
"bucket[16]").
std::string ToString() const override;
diff --git a/src/iceberg/util/projection_util_internal.h
b/src/iceberg/util/projection_util_internal.h
index 3ce2dbf8..df4fe978 100644
--- a/src/iceberg/util/projection_util_internal.h
+++ b/src/iceberg/util/projection_util_internal.h
@@ -24,10 +24,12 @@
#include <ranges>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <utility>
#include "iceberg/expression/literal.h"
#include "iceberg/expression/predicate.h"
+#include "iceberg/expression/term.h"
#include "iceberg/result.h"
#include "iceberg/transform.h"
#include "iceberg/transform_function.h"
@@ -40,248 +42,230 @@ namespace iceberg {
class ProjectionUtil {
private:
+ static Result<Literal> AdjustLiteral(const Literal& literal, int adjustment)
{
+ switch (literal.type()->type_id()) {
+ case TypeId::kInt:
+ return Literal::Int(std::get<int32_t>(literal.value()) + adjustment);
+ case TypeId::kLong:
+ return Literal::Long(std::get<int64_t>(literal.value()) + adjustment);
+ case TypeId::kDate:
+ return Literal::Date(std::get<int32_t>(literal.value()) + adjustment);
+ case TypeId::kTimestamp:
+ return Literal::Timestamp(std::get<int64_t>(literal.value()) +
adjustment);
+ case TypeId::kTimestampTz:
+ return Literal::TimestampTz(std::get<int64_t>(literal.value()) +
adjustment);
+ case TypeId::kDecimal: {
+ const auto& decimal_type =
+ internal::checked_cast<const DecimalType&>(*literal.type());
+ Decimal adjusted = std::get<Decimal>(literal.value()) +
Decimal(adjustment);
+ return Literal::Decimal(adjusted.value(), decimal_type.precision(),
+ decimal_type.scale());
+ }
+ default:
+ return NotSupported("{} is not a valid literal type for value
adjustment",
+ literal.type()->ToString());
+ }
+ }
+
+ static Result<Literal> PlusOne(const Literal& literal) {
+ return AdjustLiteral(literal, /*adjustment=*/+1);
+ }
+
+ static Result<Literal> MinusOne(const Literal& literal) {
+ return AdjustLiteral(literal, /*adjustment=*/-1);
+ }
+
+ static Result<std::unique_ptr<UnboundPredicate>> MakePredicate(
+ Expression::Operation op, std::string_view name,
+ const std::shared_ptr<TransformFunction>& func, const Literal& literal) {
+ ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ ICEBERG_ASSIGN_OR_RAISE(auto lit, func->Transform(literal));
+ return UnboundPredicateImpl<BoundReference>::Make(op, std::move(ref),
std::move(lit));
+ }
+
static Result<std::unique_ptr<UnboundPredicate>> TransformSet(
- std::string_view name, const std::shared_ptr<BoundSetPredicate>&
predicate,
+ std::string_view name, const std::shared_ptr<BoundSetPredicate>& pred,
const std::shared_ptr<TransformFunction>& func) {
std::vector<Literal> transformed;
- transformed.reserve(predicate->literal_set().size());
- for (const auto& lit : predicate->literal_set()) {
+ transformed.reserve(pred->literal_set().size());
+ for (const auto& lit : pred->literal_set()) {
ICEBERG_ASSIGN_OR_RAISE(auto transformed_lit, func->Transform(lit));
transformed.push_back(std::move(transformed_lit));
}
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
std::move(ref),
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref),
std::move(transformed));
}
- // General transform for all literal predicates. This is used as a fallback
for special
- // cases that are not handled by the other transform functions.
- static Result<std::unique_ptr<UnboundPredicate>> GenericTransform(
- std::unique_ptr<NamedReference> ref,
- const std::shared_ptr<BoundLiteralPredicate>& predicate,
+ static Result<std::unique_ptr<UnboundPredicate>> TruncateByteArray(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
func->Transform(predicate->literal()));
- switch (predicate->op()) {
+ switch (pred->op()) {
case Expression::Operation::kLt:
- case Expression::Operation::kLtEq: {
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
+ case Expression::Operation::kLtEq:
+ return MakePredicate(Expression::Operation::kLtEq, name, func,
pred->literal());
case Expression::Operation::kGt:
- case Expression::Operation::kGtEq: {
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- }
- case Expression::Operation::kEq: {
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kEq, std::move(ref),
std::move(transformed));
- }
+ case Expression::Operation::kGtEq:
+ return MakePredicate(Expression::Operation::kGtEq, name, func,
pred->literal());
+ case Expression::Operation::kEq:
+ case Expression::Operation::kStartsWith:
+ return MakePredicate(pred->op(), name, func, pred->literal());
default:
return nullptr;
}
}
- static Result<std::unique_ptr<UnboundPredicate>> TruncateByteArray(
- std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
predicate,
+ static Result<std::unique_ptr<UnboundPredicate>> TruncateByteArrayStrict(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- switch (predicate->op()) {
- case Expression::Operation::kStartsWith: {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
func->Transform(predicate->literal()));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kStartsWith, std::move(ref),
std::move(transformed));
- }
+ switch (pred->op()) {
+ case Expression::Operation::kLt:
+ case Expression::Operation::kLtEq:
+ return MakePredicate(Expression::Operation::kLt, name, func,
pred->literal());
+ case Expression::Operation::kGt:
+ case Expression::Operation::kGtEq:
+ return MakePredicate(Expression::Operation::kGt, name, func,
pred->literal());
+ case Expression::Operation::kNotEq:
+ return MakePredicate(Expression::Operation::kNotEq, name, func,
pred->literal());
default:
- return GenericTransform(std::move(ref), predicate, func);
+ return nullptr;
}
}
- template <typename T>
- requires std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>
- static Result<std::unique_ptr<UnboundPredicate>> TruncateInteger(
- std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
predicate,
+ // Apply to int32, int64, decimal, and temporal types
+ static Result<std::unique_ptr<UnboundPredicate>> TransformNumeric(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- const Literal& literal = predicate->literal();
- ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ switch (func->source_type()->type_id()) {
+ case TypeId::kInt:
+ case TypeId::kLong:
+ case TypeId::kDecimal:
+ case TypeId::kDate:
+ case TypeId::kTimestamp:
+ case TypeId::kTimestampTz:
+ break;
+ default:
+ return NotSupported("{} is not a valid input type for numeric
transform",
+ func->source_type()->ToString());
+ }
- switch (predicate->op()) {
+ switch (pred->op()) {
case Expression::Operation::kLt: {
// adjust closed and then transform ltEq
- if constexpr (std::is_same_v<T, int32_t>) {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
- func->Transform(Literal::Int(std::get<int32_t>(literal.value())
- 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- } else {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
- func->Transform(Literal::Long(std::get<int64_t>(literal.value())
- 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
+ ICEBERG_ASSIGN_OR_RAISE(auto adjusted, MinusOne(pred->literal()));
+ return MakePredicate(Expression::Operation::kLtEq, name, func,
adjusted);
}
case Expression::Operation::kGt: {
// adjust closed and then transform gtEq
- if constexpr (std::is_same_v<T, int32_t>) {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
- func->Transform(Literal::Int(std::get<int32_t>(literal.value())
+ 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- } else {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
- func->Transform(Literal::Long(std::get<int64_t>(literal.value())
+ 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- }
+ ICEBERG_ASSIGN_OR_RAISE(auto adjusted, PlusOne(pred->literal()));
+ return MakePredicate(Expression::Operation::kGtEq, name, func,
adjusted);
}
+ case Expression::Operation::kLtEq:
+ case Expression::Operation::kGtEq:
+ case Expression::Operation::kEq:
+ return MakePredicate(pred->op(), name, func, pred->literal());
default:
- return GenericTransform(std::move(ref), predicate, func);
+ return nullptr;
}
}
- static Result<std::unique_ptr<UnboundPredicate>> TransformTemporal(
- std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
predicate,
+ static Result<std::unique_ptr<UnboundPredicate>> TransformNumericStrict(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- const Literal& literal = predicate->literal();
- ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
-
switch (func->source_type()->type_id()) {
- case TypeId::kDate: {
- switch (predicate->op()) {
- case Expression::Operation::kLt: {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
-
func->Transform(Literal::Date(std::get<int32_t>(literal.value()) - 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
- case Expression::Operation::kGt: {
- ICEBERG_ASSIGN_OR_RAISE(
- auto transformed,
-
func->Transform(Literal::Date(std::get<int32_t>(literal.value()) + 1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- }
- default:
- return GenericTransform(std::move(ref), predicate, func);
- }
- }
- case TypeId::kTimestamp: {
- switch (predicate->op()) {
- case Expression::Operation::kLt: {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(Literal::Timestamp(
- std::get<int64_t>(literal.value()) -
1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
- case Expression::Operation::kGt: {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(Literal::Timestamp(
- std::get<int64_t>(literal.value()) +
1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- }
- default:
- return GenericTransform(std::move(ref), predicate, func);
- }
+ case TypeId::kInt:
+ case TypeId::kLong:
+ case TypeId::kDecimal:
+ case TypeId::kDate:
+ case TypeId::kTimestamp:
+ case TypeId::kTimestampTz:
+ break;
+ default:
+ return NotSupported("{} is not a valid input type for numeric
transform",
+ func->source_type()->ToString());
+ }
+
+ switch (pred->op()) {
+ case Expression::Operation::kLtEq: {
+ ICEBERG_ASSIGN_OR_RAISE(auto adjusted, PlusOne(pred->literal()));
+ return MakePredicate(Expression::Operation::kLt, name, func, adjusted);
}
- case TypeId::kTimestampTz: {
- switch (predicate->op()) {
- case Expression::Operation::kLt: {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(Literal::TimestampTz(
- std::get<int64_t>(literal.value()) -
1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
- case Expression::Operation::kGt: {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(Literal::TimestampTz(
- std::get<int64_t>(literal.value()) +
1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
- }
- default:
- return GenericTransform(std::move(ref), predicate, func);
- }
+ case Expression::Operation::kGtEq: {
+ ICEBERG_ASSIGN_OR_RAISE(auto adjusted, MinusOne(pred->literal()));
+ return MakePredicate(Expression::Operation::kGt, name, func, adjusted);
}
+ case Expression::Operation::kLt:
+ case Expression::Operation::kGt:
+ case Expression::Operation::kNotEq:
+ return MakePredicate(pred->op(), name, func, pred->literal());
default:
- return NotSupported("{} is not a valid input type for temporal
transform",
- func->source_type()->ToString());
+ return nullptr;
}
}
- static Result<std::unique_ptr<UnboundPredicate>> TruncateDecimal(
- std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
predicate,
+ static Result<std::unique_ptr<UnboundPredicate>> TruncateStringLiteral(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- const Literal& boundary = predicate->literal();
- ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ const auto op = pred->op();
+ if (op != Expression::Operation::kStartsWith &&
+ op != Expression::Operation::kNotStartsWith) {
+ return TruncateByteArray(name, pred, func);
+ }
- // For boundary adjustments, extract type info once
- auto make_adjusted_literal = [&boundary](int adjustment) {
- const auto& type =
internal::checked_pointer_cast<DecimalType>(boundary.type());
- Decimal adjusted = std::get<Decimal>(boundary.value()) +
Decimal(adjustment);
- return Literal::Decimal(adjusted.value(), type->precision(),
type->scale());
- };
+ const auto& literal = pred->literal();
+ const auto length =
+ StringUtils::CodePointCount(std::get<std::string>(literal.value()));
+ const auto width = static_cast<size_t>(
+ internal::checked_pointer_cast<TruncateTransform>(func)->width());
- switch (predicate->op()) {
- case Expression::Operation::kLt: {
- // adjust closed and then transform ltEq
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(make_adjusted_literal(-1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kLtEq, std::move(ref),
std::move(transformed));
- }
- case Expression::Operation::kGt: {
- // adjust closed and then transform gtEq
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
- func->Transform(make_adjusted_literal(1)));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kGtEq, std::move(ref),
std::move(transformed));
+ if (length < width) {
+ return MakePredicate(op, name, func, literal);
+ }
+
+ if (length == width) {
+ if (op == Expression::Operation::kStartsWith) {
+ return MakePredicate(Expression::Operation::kEq, name, func, literal);
+ } else {
+ return MakePredicate(Expression::Operation::kNotEq, name, func,
literal);
}
- default:
- return GenericTransform(std::move(ref), predicate, func);
}
+
+ if (op == Expression::Operation::kStartsWith) {
+ return TruncateByteArray(name, pred, func);
+ }
+
+ return nullptr;
}
- static Result<std::unique_ptr<UnboundPredicate>> TruncateStringLiteral(
- std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
predicate,
+ static Result<std::unique_ptr<UnboundPredicate>> TruncateStringLiteralStrict(
+ std::string_view name, const std::shared_ptr<BoundLiteralPredicate>&
pred,
const std::shared_ptr<TransformFunction>& func) {
- const auto op = predicate->op();
+ const auto op = pred->op();
if (op != Expression::Operation::kStartsWith &&
op != Expression::Operation::kNotStartsWith) {
- return TruncateByteArray(name, predicate, func);
+ return TruncateByteArrayStrict(name, pred, func);
}
- const auto& truncate_transform =
- internal::checked_pointer_cast<TruncateTransform>(func);
- const auto& str_value =
std::get<std::string>(predicate->literal().value());
- const auto width = truncate_transform->width();
- ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ const auto& literal = pred->literal();
+ const auto length =
+ StringUtils::CodePointCount(std::get<std::string>(literal.value()));
+ const auto width = static_cast<size_t>(
+ internal::checked_pointer_cast<TruncateTransform>(func)->width());
- if (StringUtils::CodePointCount(str_value) < width) {
- return UnboundPredicateImpl<BoundReference>::Make(op, std::move(ref),
- predicate->literal());
+ if (length < width) {
+ return MakePredicate(op, name, func, literal);
}
- if (StringUtils::CodePointCount(str_value) == width) {
+ if (length == width) {
if (op == Expression::Operation::kStartsWith) {
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kEq, std::move(ref), predicate->literal());
+ return MakePredicate(Expression::Operation::kEq, name, func, literal);
} else {
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kNotEq, std::move(ref),
predicate->literal());
+ return MakePredicate(Expression::Operation::kNotEq, name, func,
literal);
}
}
- if (op == Expression::Operation::kStartsWith) {
- ICEBERG_ASSIGN_OR_RAISE(auto transformed,
func->Transform(predicate->literal()));
- return UnboundPredicateImpl<BoundReference>::Make(
- Expression::Operation::kStartsWith, std::move(ref),
std::move(transformed));
+ if (op == Expression::Operation::kNotStartsWith) {
+ return MakePredicate(Expression::Operation::kNotStartsWith, name, func,
literal);
}
return nullptr;
@@ -304,14 +288,13 @@ class ProjectionUtil {
const auto& literal = projected->literals().front();
ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
"Expected int32_t");
- auto value = std::get<int32_t>(literal.value());
- if (value < 0) {
+ if (auto value = std::get<int32_t>(literal.value()); value < 0) {
return
UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kLt,
std::move(projected->term()),
Literal::Int(value
+ 1));
}
- return std::move(projected);
+ return projected;
}
case Expression::Operation::kLtEq: {
@@ -319,34 +302,33 @@ class ProjectionUtil {
const auto& literal = projected->literals().front();
ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
"Expected int32_t");
- auto value = std::get<int32_t>(literal.value());
- if (value < 0) {
+
+ if (auto value = std::get<int32_t>(literal.value()); value < 0) {
return
UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kLtEq,
std::move(projected->term()),
Literal::Int(value
+ 1));
}
- return std::move(projected);
+ return projected;
}
case Expression::Operation::kGt:
case Expression::Operation::kGtEq:
// incorrect projected values are already greater than the bound for
GT, GT_EQ
- return std::move(projected);
+ return projected;
case Expression::Operation::kEq: {
ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one
literal");
const auto& literal = projected->literals().front();
ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
"Expected int32_t");
- auto value = std::get<int32_t>(literal.value());
- if (value < 0) {
+ if (auto value = std::get<int32_t>(literal.value()); value < 0) {
// match either the incorrect value (projectedValue + 1) or the
correct value
// (projectedValue)
return UnboundPredicateImpl<BoundReference>::Make(
Expression::Operation::kIn, std::move(projected->term()),
{literal, Literal::Int(value + 1)});
}
- return std::move(projected);
+ return projected;
}
case Expression::Operation::kIn: {
@@ -377,7 +359,7 @@ class ProjectionUtil {
std::move(projected->term()),
std::move(values));
}
- return std::move(projected);
+ return projected;
}
case Expression::Operation::kNotIn:
@@ -386,30 +368,128 @@ class ProjectionUtil {
return nullptr;
default:
- return std::move(projected);
+ return projected;
+ }
+ }
+
+ // Fixes a strict projection to account for incorrectly transformed values.
+ // align with Java implementation:
+ //
https://github.com/apache/iceberg/blob/1.10.x/api/src/main/java/org/apache/iceberg/transforms/ProjectionUtil.java#L347
+ static Result<std::unique_ptr<UnboundPredicate>> FixStrictTimeProjection(
+ std::unique_ptr<UnboundPredicateImpl<BoundReference>> projected) {
+ if (projected == nullptr) {
+ return nullptr;
+ }
+
+ switch (projected->op()) {
+ case Expression::Operation::kLt:
+ case Expression::Operation::kLtEq:
+ // the correct bound is a correct strict projection for the incorrectly
+ // transformed values.
+ return projected;
+
+ case Expression::Operation::kGt: {
+ // GT and GT_EQ need to be adjusted because values that do not match
the predicate
+ // may have been transformed into partition values that match the
projected
+ // predicate.
+ ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one
literal");
+ const auto& literal = projected->literals().front();
+ ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
+ "Expected int32_t");
+ if (auto value = std::get<int32_t>(literal.value()); value <= 0) {
+ return
UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kGt,
+
std::move(projected->term()),
+ Literal::Int(value
+ 1));
+ }
+ return projected;
+ }
+
+ case Expression::Operation::kGtEq: {
+ ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one
literal");
+ const auto& literal = projected->literals().front();
+ ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
+ "Expected int32_t");
+ if (auto value = std::get<int32_t>(literal.value()); value <= 0) {
+ return
UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kGtEq,
+
std::move(projected->term()),
+ Literal::Int(value
+ 1));
+ }
+ return projected;
+ }
+
+ case Expression::Operation::kEq:
+ case Expression::Operation::kIn:
+ // there is no strict projection for EQ and IN
+ return nullptr;
+
+ case Expression::Operation::kNotEq: {
+ ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one
literal");
+ const auto& literal = projected->literals().front();
+ ICEBERG_DCHECK(std::holds_alternative<int32_t>(literal.value()),
+ "Expected int32_t");
+ if (auto value = std::get<int32_t>(literal.value()); value < 0) {
+ return UnboundPredicateImpl<BoundReference>::Make(
+ Expression::Operation::kNotIn, std::move(projected->term()),
+ {literal, Literal::Int(value + 1)});
+ }
+ return projected;
+ }
+
+ case Expression::Operation::kNotIn: {
+ ICEBERG_DCHECK(!projected->literals().empty(), "Expected at least one
literal");
+ const auto& literals = projected->literals();
+ ICEBERG_DCHECK(
+ std::ranges::all_of(literals,
+ [](const auto& lit) {
+ return
std::holds_alternative<int32_t>(lit.value());
+ }),
+ "Expected int32_t");
+ std::unordered_set<int32_t> value_set;
+ bool has_negative_value = false;
+ for (const auto& lit : literals) {
+ auto value = std::get<int32_t>(lit.value());
+ value_set.insert(value);
+ if (value < 0) {
+ value_set.insert(value + 1);
+ has_negative_value = true;
+ }
+ }
+ if (has_negative_value) {
+ auto values =
+ std::views::transform(value_set,
+ [](int32_t value) { return
Literal::Int(value); }) |
+ std::ranges::to<std::vector>();
+ return
UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kNotIn,
+
std::move(projected->term()),
+ std::move(values));
+ }
+ return projected;
+ }
+
+ default:
+ return nullptr;
}
}
public:
static Result<std::unique_ptr<UnboundPredicate>> IdentityProject(
- std::string_view name, const std::shared_ptr<BoundPredicate>& predicate)
{
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred) {
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- switch (predicate->kind()) {
+ switch (pred->kind()) {
case BoundPredicate::Kind::kUnary: {
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
- std::move(ref));
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
}
case BoundPredicate::Kind::kLiteral: {
const auto& literalPredicate =
- internal::checked_pointer_cast<BoundLiteralPredicate>(predicate);
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
std::move(ref),
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref),
literalPredicate->literal());
}
case BoundPredicate::Kind::kSet: {
const auto& setPredicate =
- internal::checked_pointer_cast<BoundSetPredicate>(predicate);
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
return UnboundPredicateImpl<BoundReference>::Make(
- predicate->op(), std::move(ref),
+ pred->op(), std::move(ref),
std::vector<Literal>(setPredicate->literal_set().begin(),
setPredicate->literal_set().end()));
}
@@ -418,30 +498,29 @@ class ProjectionUtil {
}
static Result<std::unique_ptr<UnboundPredicate>> BucketProject(
- std::string_view name, const std::shared_ptr<BoundPredicate>& predicate,
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
const std::shared_ptr<TransformFunction>& func) {
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- switch (predicate->kind()) {
+ switch (pred->kind()) {
case BoundPredicate::Kind::kUnary: {
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
- std::move(ref));
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
}
case BoundPredicate::Kind::kLiteral: {
- if (predicate->op() == Expression::Operation::kEq) {
+ if (pred->op() == Expression::Operation::kEq) {
const auto& literalPredicate =
- internal::checked_pointer_cast<BoundLiteralPredicate>(predicate);
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
ICEBERG_ASSIGN_OR_RAISE(auto transformed,
func->Transform(literalPredicate->literal()));
- return UnboundPredicateImpl<BoundReference>::Make(
- predicate->op(), std::move(ref), std::move(transformed));
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref),
+
std::move(transformed));
}
break;
}
case BoundPredicate::Kind::kSet: {
// notIn can't be projected
- if (predicate->op() == Expression::Operation::kIn) {
+ if (pred->op() == Expression::Operation::kIn) {
const auto& setPredicate =
- internal::checked_pointer_cast<BoundSetPredicate>(predicate);
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
return TransformSet(name, setPredicate, func);
}
break;
@@ -455,19 +534,19 @@ class ProjectionUtil {
}
static Result<std::unique_ptr<UnboundPredicate>> TruncateProject(
- std::string_view name, const std::shared_ptr<BoundPredicate>& predicate,
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
const std::shared_ptr<TransformFunction>& func) {
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
// Handle unary predicates uniformly for all types
- if (predicate->kind() == BoundPredicate::Kind::kUnary) {
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
std::move(ref));
+ if (pred->kind() == BoundPredicate::Kind::kUnary) {
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
}
// Handle set predicates (kIn) uniformly for all types
- if (predicate->kind() == BoundPredicate::Kind::kSet) {
- if (predicate->op() == Expression::Operation::kIn) {
+ if (pred->kind() == BoundPredicate::Kind::kSet) {
+ if (pred->op() == Expression::Operation::kIn) {
const auto& setPredicate =
- internal::checked_pointer_cast<BoundSetPredicate>(predicate);
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
return TransformSet(name, setPredicate, func);
}
return nullptr;
@@ -475,15 +554,13 @@ class ProjectionUtil {
// Handle literal predicates based on source type
const auto& literalPredicate =
- internal::checked_pointer_cast<BoundLiteralPredicate>(predicate);
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
switch (func->source_type()->type_id()) {
case TypeId::kInt:
- return TruncateInteger<int32_t>(name, literalPredicate, func);
case TypeId::kLong:
- return TruncateInteger<int64_t>(name, literalPredicate, func);
case TypeId::kDecimal:
- return TruncateDecimal(name, literalPredicate, func);
+ return TransformNumeric(name, literalPredicate, func);
case TypeId::kString:
return TruncateStringLiteral(name, literalPredicate, func);
case TypeId::kBinary:
@@ -495,16 +572,16 @@ class ProjectionUtil {
}
static Result<std::unique_ptr<UnboundPredicate>> TemporalProject(
- std::string_view name, const std::shared_ptr<BoundPredicate>& predicate,
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
const std::shared_ptr<TransformFunction>& func) {
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- if (predicate->kind() == BoundPredicate::Kind::kUnary) {
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
std::move(ref));
- } else if (predicate->kind() == BoundPredicate::Kind::kLiteral) {
+ if (pred->kind() == BoundPredicate::Kind::kUnary) {
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
+ } else if (pred->kind() == BoundPredicate::Kind::kLiteral) {
const auto& literalPredicate =
- internal::checked_pointer_cast<BoundLiteralPredicate>(predicate);
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
ICEBERG_ASSIGN_OR_RAISE(auto projected,
- TransformTemporal(name, literalPredicate, func));
+ TransformNumeric(name, literalPredicate, func));
if (func->transform_type() != TransformType::kDay ||
func->source_type()->type_id() != TypeId::kDate) {
return FixInclusiveTimeProjection(
@@ -512,10 +589,9 @@ class ProjectionUtil {
std::move(projected)));
}
return projected;
- } else if (predicate->kind() == BoundPredicate::Kind::kSet &&
- predicate->op() == Expression::Operation::kIn) {
- const auto& setPredicate =
- internal::checked_pointer_cast<BoundSetPredicate>(predicate);
+ } else if (pred->kind() == BoundPredicate::Kind::kSet &&
+ pred->op() == Expression::Operation::kIn) {
+ const auto& setPredicate =
internal::checked_pointer_cast<BoundSetPredicate>(pred);
ICEBERG_ASSIGN_OR_RAISE(auto projected, TransformSet(name, setPredicate,
func));
if (func->transform_type() != TransformType::kDay ||
func->source_type()->type_id() != TypeId::kDate) {
@@ -530,30 +606,135 @@ class ProjectionUtil {
}
static Result<std::unique_ptr<UnboundPredicate>> RemoveTransform(
- std::string_view name, const std::shared_ptr<BoundPredicate>& predicate)
{
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred) {
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
- switch (predicate->kind()) {
+ switch (pred->kind()) {
case BoundPredicate::Kind::kUnary: {
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
- std::move(ref));
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
}
case BoundPredicate::Kind::kLiteral: {
const auto& literalPredicate =
- internal::checked_pointer_cast<BoundLiteralPredicate>(predicate);
- return UnboundPredicateImpl<BoundReference>::Make(predicate->op(),
std::move(ref),
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref),
literalPredicate->literal());
}
case BoundPredicate::Kind::kSet: {
const auto& setPredicate =
- internal::checked_pointer_cast<BoundSetPredicate>(predicate);
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
return UnboundPredicateImpl<BoundReference>::Make(
- predicate->op(), std::move(ref),
+ pred->op(), std::move(ref),
std::vector<Literal>(setPredicate->literal_set().begin(),
setPredicate->literal_set().end()));
}
}
std::unreachable();
}
+
+ static Result<std::unique_ptr<UnboundPredicate>> BucketProjectStrict(
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
+ const std::shared_ptr<TransformFunction>& func) {
+ ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ switch (pred->kind()) {
+ case BoundPredicate::Kind::kUnary: {
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
+ }
+ case BoundPredicate::Kind::kLiteral: {
+ if (pred->op() == Expression::Operation::kNotEq) {
+ const auto& literalPredicate =
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
+ ICEBERG_ASSIGN_OR_RAISE(auto transformed,
+
func->Transform(literalPredicate->literal()));
+ // TODO(anyone): need to translate not(eq(...)) into notEq in
expressions
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref),
+
std::move(transformed));
+ }
+ break;
+ }
+ case BoundPredicate::Kind::kSet: {
+ if (pred->op() == Expression::Operation::kNotIn) {
+ const auto& setPredicate =
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
+ return TransformSet(name, setPredicate, func);
+ }
+ break;
+ }
+ }
+
+ // no strict projection for comparison or equality
+ return nullptr;
+ }
+
+ static Result<std::unique_ptr<UnboundPredicate>> TruncateProjectStrict(
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
+ const std::shared_ptr<TransformFunction>& func) {
+ ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ // Handle unary predicates uniformly for all types
+ if (pred->kind() == BoundPredicate::Kind::kUnary) {
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
+ }
+
+ // Handle set predicates (kNotIn) uniformly for all types
+ if (pred->kind() == BoundPredicate::Kind::kSet) {
+ if (pred->op() == Expression::Operation::kNotIn) {
+ const auto& setPredicate =
+ internal::checked_pointer_cast<BoundSetPredicate>(pred);
+ return TransformSet(name, setPredicate, func);
+ }
+ return nullptr;
+ }
+
+ // Handle literal predicates based on source type
+ const auto& literalPredicate =
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
+
+ switch (func->source_type()->type_id()) {
+ case TypeId::kInt:
+ case TypeId::kLong:
+ case TypeId::kDecimal:
+ return TransformNumericStrict(name, literalPredicate, func);
+ case TypeId::kString:
+ return TruncateStringLiteralStrict(name, literalPredicate, func);
+ case TypeId::kBinary:
+ return TruncateByteArrayStrict(name, literalPredicate, func);
+ default:
+ return NotSupported("{} is not a valid input type for truncate
transform",
+ func->source_type()->ToString());
+ }
+ }
+
+ static Result<std::unique_ptr<UnboundPredicate>> TemporalProjectStrict(
+ std::string_view name, const std::shared_ptr<BoundPredicate>& pred,
+ const std::shared_ptr<TransformFunction>& func) {
+ ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(std::string(name)));
+ if (pred->kind() == BoundPredicate::Kind::kUnary) {
+ return UnboundPredicateImpl<BoundReference>::Make(pred->op(),
std::move(ref));
+ } else if (pred->kind() == BoundPredicate::Kind::kLiteral) {
+ const auto& literalPredicate =
+ internal::checked_pointer_cast<BoundLiteralPredicate>(pred);
+ ICEBERG_ASSIGN_OR_RAISE(auto projected,
+ TransformNumericStrict(name, literalPredicate,
func));
+ if (func->transform_type() != TransformType::kDay ||
+ func->source_type()->type_id() != TypeId::kDate) {
+ return FixStrictTimeProjection(
+
internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected)));
+ }
+ return projected;
+ } else if (pred->kind() == BoundPredicate::Kind::kSet &&
+ pred->op() == Expression::Operation::kNotIn) {
+ const auto& setPredicate =
internal::checked_pointer_cast<BoundSetPredicate>(pred);
+ ICEBERG_ASSIGN_OR_RAISE(auto projected, TransformSet(name, setPredicate,
func));
+ if (func->transform_type() != TransformType::kDay ||
+ func->source_type()->type_id() != TypeId::kDate) {
+ return FixStrictTimeProjection(
+
internal::checked_pointer_cast<UnboundPredicateImpl<BoundReference>>(
+ std::move(projected)));
+ }
+ return projected;
+ }
+
+ return nullptr;
+ }
};
} // namespace iceberg