This is an automated email from the ASF dual-hosted git repository.
fokko 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 4ebf729 feat: Implement Type Casting and toString for Literals (#206)
4ebf729 is described below
commit 4ebf729a0b0cac0cde893169b3dcd13f96bdf7a9
Author: Li Feiyang <[email protected]>
AuthorDate: Tue Oct 14 01:59:36 2025 +0800
feat: Implement Type Casting and toString for Literals (#206)
- Implements the complete type casting logic for `iceberg::Literal` in
the `LiteralCaster` class to align with the Java reference
implementation. This is critical for expression evaluation and predicate
pushdown.
- Add basic implementation for fixed type.
- Updated `ToString()` to match Java's output format for better
consistency (e.g., `X'...'` for binary).
- Added comprehensive unit tests to validate all new casting logic and
`ToString()` formatting.
---
src/iceberg/expression/literal.cc | 209 ++++++++++++---
src/iceberg/expression/predicate.cc | 7 +-
src/iceberg/test/literal_test.cc | 499 +++++++++++++++++++++---------------
3 files changed, 464 insertions(+), 251 deletions(-)
diff --git a/src/iceberg/expression/literal.cc
b/src/iceberg/expression/literal.cc
index bd76d9c..18a46c6 100644
--- a/src/iceberg/expression/literal.cc
+++ b/src/iceberg/expression/literal.cc
@@ -21,10 +21,12 @@
#include <cmath>
#include <concepts>
+#include <cstdint>
+#include <string>
-#include "iceberg/exception.h"
+#include "iceberg/type_fwd.h"
+#include "iceberg/util/checked_cast.h"
#include "iceberg/util/conversions.h"
-#include "iceberg/util/macros.h"
namespace iceberg {
@@ -54,6 +56,30 @@ class LiteralCaster {
/// Cast from Float type to target type.
static Result<Literal> CastFromFloat(const Literal& literal,
const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from Double type to target type.
+ static Result<Literal> CastFromDouble(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from String type to target type.
+ static Result<Literal> CastFromString(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from Timestamp type to target type.
+ static Result<Literal> CastFromTimestamp(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from TimestampTz type to target type.
+ static Result<Literal> CastFromTimestampTz(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from Binary type to target type.
+ static Result<Literal> CastFromBinary(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>&
target_type);
+
+ /// Cast from Fixed type to target type.
+ static Result<Literal> CastFromFixed(const Literal& literal,
+ const std::shared_ptr<PrimitiveType>&
target_type);
};
Literal LiteralCaster::BelowMinLiteral(std::shared_ptr<PrimitiveType> type) {
@@ -76,6 +102,8 @@ Result<Literal> LiteralCaster::CastFromInt(
return Literal::Float(static_cast<float>(int_val));
case TypeId::kDouble:
return Literal::Double(static_cast<double>(int_val));
+ case TypeId::kDate:
+ return Literal::Date(int_val);
default:
return NotSupported("Cast from Int to {} is not implemented",
target_type->ToString());
@@ -85,15 +113,14 @@ Result<Literal> LiteralCaster::CastFromInt(
Result<Literal> LiteralCaster::CastFromLong(
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
auto long_val = std::get<int64_t>(literal.value_);
- auto target_type_id = target_type->type_id();
- switch (target_type_id) {
+ switch (target_type->type_id()) {
case TypeId::kInt: {
// Check for overflow
- if (long_val >= std::numeric_limits<int32_t>::max()) {
+ if (long_val > std::numeric_limits<int32_t>::max()) {
return AboveMaxLiteral(target_type);
}
- if (long_val <= std::numeric_limits<int32_t>::min()) {
+ if (long_val < std::numeric_limits<int32_t>::min()) {
return BelowMinLiteral(target_type);
}
return Literal::Int(static_cast<int32_t>(long_val));
@@ -102,6 +129,21 @@ Result<Literal> LiteralCaster::CastFromLong(
return Literal::Float(static_cast<float>(long_val));
case TypeId::kDouble:
return Literal::Double(static_cast<double>(long_val));
+ case TypeId::kDate: {
+ if (long_val > std::numeric_limits<int32_t>::max()) {
+ return AboveMaxLiteral(target_type);
+ }
+ if (long_val < std::numeric_limits<int32_t>::min()) {
+ return BelowMinLiteral(target_type);
+ }
+ return Literal::Date(static_cast<int32_t>(long_val));
+ }
+ case TypeId::kTime:
+ return Literal::Time(long_val);
+ case TypeId::kTimestamp:
+ return Literal::Timestamp(long_val);
+ case TypeId::kTimestampTz:
+ return Literal::TimestampTz(long_val);
default:
return NotSupported("Cast from Long to {} is not supported",
target_type->ToString());
@@ -111,9 +153,8 @@ Result<Literal> LiteralCaster::CastFromLong(
Result<Literal> LiteralCaster::CastFromFloat(
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
auto float_val = std::get<float>(literal.value_);
- auto target_type_id = target_type->type_id();
- switch (target_type_id) {
+ switch (target_type->type_id()) {
case TypeId::kDouble:
return Literal::Double(static_cast<double>(float_val));
default:
@@ -122,6 +163,103 @@ Result<Literal> LiteralCaster::CastFromFloat(
}
}
+Result<Literal> LiteralCaster::CastFromDouble(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ auto double_val = std::get<double>(literal.value_);
+
+ switch (target_type->type_id()) {
+ case TypeId::kFloat: {
+ if (double_val > static_cast<double>(std::numeric_limits<float>::max()))
{
+ return AboveMaxLiteral(target_type);
+ }
+ if (double_val <
static_cast<double>(std::numeric_limits<float>::lowest())) {
+ return BelowMinLiteral(target_type);
+ }
+ return Literal::Float(static_cast<float>(double_val));
+ }
+ default:
+ return NotSupported("Cast from Double to {} is not supported",
+ target_type->ToString());
+ }
+}
+
+Result<Literal> LiteralCaster::CastFromString(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ const auto& str_val = std::get<std::string>(literal.value_);
+
+ switch (target_type->type_id()) {
+ case TypeId::kDate:
+ case TypeId::kTime:
+ case TypeId::kTimestamp:
+ case TypeId::kTimestampTz:
+ case TypeId::kUuid:
+ return NotImplemented("Cast from String to {} is not implemented yet",
+ target_type->ToString());
+ default:
+ return NotSupported("Cast from String to {} is not supported",
+ target_type->ToString());
+ }
+}
+
+Result<Literal> LiteralCaster::CastFromTimestamp(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ auto timestamp_val = std::get<int64_t>(literal.value_);
+
+ switch (target_type->type_id()) {
+ case TypeId::kDate:
+ return NotImplemented("Cast from Timestamp to Date is not implemented
yet");
+ case TypeId::kTimestampTz:
+ return Literal::TimestampTz(timestamp_val);
+ default:
+ return NotSupported("Cast from Timestamp to {} is not supported",
+ target_type->ToString());
+ }
+}
+
+Result<Literal> LiteralCaster::CastFromTimestampTz(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ auto micros = std::get<int64_t>(literal.value_);
+
+ switch (target_type->type_id()) {
+ case TypeId::kDate:
+ return NotImplemented("Cast from TimestampTz to Date is not implemented
yet");
+ case TypeId::kTimestamp:
+ return Literal::Timestamp(micros);
+ default:
+ return NotSupported("Cast from TimestampTz to {} is not supported",
+ target_type->ToString());
+ }
+}
+
+Result<Literal> LiteralCaster::CastFromBinary(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ auto binary_val = std::get<std::vector<uint8_t>>(literal.value_);
+ switch (target_type->type_id()) {
+ case TypeId::kFixed: {
+ auto target_fixed_type =
internal::checked_pointer_cast<FixedType>(target_type);
+ if (binary_val.size() == target_fixed_type->length()) {
+ return Literal::Fixed(std::move(binary_val));
+ }
+ return InvalidArgument("Failed to cast Binary with length {} to
Fixed({})",
+ binary_val.size(), target_fixed_type->length());
+ }
+ default:
+ return NotSupported("Cast from Binary to {} is not supported",
+ target_type->ToString());
+ }
+}
+
+Result<Literal> LiteralCaster::CastFromFixed(
+ const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type)
{
+ switch (target_type->type_id()) {
+ case TypeId::kBinary:
+ return Literal::Binary(std::get<std::vector<uint8_t>>(literal.value_));
+ default:
+ return NotSupported("Cast from Fixed to {} is not supported",
+ target_type->ToString());
+ }
+}
+
// Constructor
Literal::Literal(Value value, std::shared_ptr<PrimitiveType> type)
: value_(std::move(value)), type_(std::move(type)) {}
@@ -154,8 +292,8 @@ Literal Literal::Binary(std::vector<uint8_t> value) {
}
Literal Literal::Fixed(std::vector<uint8_t> value) {
- auto length = static_cast<int32_t>(value.size());
- return {Value{std::move(value)}, fixed(length)};
+ const auto size = value.size();
+ return {Value{std::move(value)}, fixed(size)};
}
Result<Literal> Literal::Deserialize(std::span<const uint8_t> data,
@@ -262,12 +400,7 @@ std::partial_ordering Literal::operator<=>(const Literal&
other) const {
return std::partial_ordering::unordered;
}
- case TypeId::kBinary: {
- auto& this_val = std::get<std::vector<uint8_t>>(value_);
- auto& other_val = std::get<std::vector<uint8_t>>(other.value_);
- return this_val <=> other_val;
- }
-
+ case TypeId::kBinary:
case TypeId::kFixed: {
auto& this_val = std::get<std::vector<uint8_t>>(value_);
auto& other_val = std::get<std::vector<uint8_t>>(other.value_);
@@ -308,38 +441,32 @@ std::string Literal::ToString() const {
return std::to_string(std::get<double>(value_));
}
case TypeId::kString: {
- return std::get<std::string>(value_);
+ return "\"" + std::get<std::string>(value_) + "\"";
}
case TypeId::kUuid: {
return std::get<Uuid>(value_).ToString();
}
- case TypeId::kBinary: {
+ case TypeId::kBinary:
+ case TypeId::kFixed: {
const auto& binary_data = std::get<std::vector<uint8_t>>(value_);
- std::string result;
- result.reserve(binary_data.size() * 2); // 2 chars per byte
+ std::string result = "X'";
+ result.reserve(/*prefix*/ 2 + /*suffix*/ 1 + /*data*/ binary_data.size()
* 2);
for (const auto& byte : binary_data) {
std::format_to(std::back_inserter(result), "{:02X}", byte);
}
+ result.push_back('\'');
return result;
}
- case TypeId::kFixed: {
- const auto& fixed_data = std::get<std::vector<uint8_t>>(value_);
- std::string result;
- result.reserve(fixed_data.size() * 2); // 2 chars per byte
- for (const auto& byte : fixed_data) {
- std::format_to(std::back_inserter(result), "{:02X}", byte);
- }
- return result;
- }
- case TypeId::kDecimal:
- case TypeId::kDate:
case TypeId::kTime:
case TypeId::kTimestamp:
case TypeId::kTimestampTz: {
- throw IcebergError("Not implemented: ToString for " + type_->ToString());
+ return std::to_string(std::get<int64_t>(value_));
+ }
+ case TypeId::kDate: {
+ return std::to_string(std::get<int32_t>(value_));
}
default: {
- throw IcebergError("Unknown type: " + type_->ToString());
+ return std::format("invalid literal of type {}", type_->ToString());
}
}
}
@@ -371,6 +498,9 @@ Result<Literal> LiteralCaster::CastTo(const Literal&
literal,
// Delegate to specific cast functions based on source type
switch (source_type_id) {
+ case TypeId::kBoolean:
+ // No casts defined for Boolean, other than to itself.
+ break;
case TypeId::kInt:
return CastFromInt(literal, target_type);
case TypeId::kLong:
@@ -378,15 +508,22 @@ Result<Literal> LiteralCaster::CastTo(const Literal&
literal,
case TypeId::kFloat:
return CastFromFloat(literal, target_type);
case TypeId::kDouble:
- case TypeId::kBoolean:
+ return CastFromDouble(literal, target_type);
case TypeId::kString:
+ return CastFromString(literal, target_type);
case TypeId::kBinary:
- break;
+ return CastFromBinary(literal, target_type);
+ case TypeId::kFixed:
+ return CastFromFixed(literal, target_type);
+ case TypeId::kTimestamp:
+ return CastFromTimestamp(literal, target_type);
+ case TypeId::kTimestampTz:
+ return CastFromTimestampTz(literal, target_type);
default:
break;
}
- return NotSupported("Cast from {} to {} is not implemented",
literal.type_->ToString(),
+ return NotSupported("Cast from {} to {} is not supported",
literal.type_->ToString(),
target_type->ToString());
}
diff --git a/src/iceberg/expression/predicate.cc
b/src/iceberg/expression/predicate.cc
index 144ef2b..2ce04a1 100644
--- a/src/iceberg/expression/predicate.cc
+++ b/src/iceberg/expression/predicate.cc
@@ -100,12 +100,11 @@ std::string UnboundPredicate<B>::ToString() const {
return values_.size() == 1 ? std::format("{} != {}", term, values_[0])
: invalid_predicate_string(op);
case Expression::Operation::kStartsWith:
- return values_.size() == 1 ? std::format("{} startsWith \"{}\"", term,
values_[0])
+ return values_.size() == 1 ? std::format("{} startsWith {}", term,
values_[0])
: invalid_predicate_string(op);
case Expression::Operation::kNotStartsWith:
- return values_.size() == 1
- ? std::format("{} notStartsWith \"{}\"", term, values_[0])
- : invalid_predicate_string(op);
+ return values_.size() == 1 ? std::format("{} notStartsWith {}", term,
values_[0])
+ : invalid_predicate_string(op);
case Expression::Operation::kIn:
return std::format("{} in {}", term, values_);
case Expression::Operation::kNotIn:
diff --git a/src/iceberg/test/literal_test.cc b/src/iceberg/test/literal_test.cc
index 58cc906..6e4b2aa 100644
--- a/src/iceberg/test/literal_test.cc
+++ b/src/iceberg/test/literal_test.cc
@@ -30,265 +30,131 @@
namespace iceberg {
-// Boolean type tests
-TEST(LiteralTest, BooleanBasics) {
- auto true_literal = Literal::Boolean(true);
- auto false_literal = Literal::Boolean(false);
-
- EXPECT_EQ(true_literal.type()->type_id(), TypeId::kBoolean);
- EXPECT_EQ(false_literal.type()->type_id(), TypeId::kBoolean);
-
- EXPECT_EQ(true_literal.ToString(), "true");
- EXPECT_EQ(false_literal.ToString(), "false");
-}
-
-TEST(LiteralTest, BooleanComparison) {
- auto true_literal = Literal::Boolean(true);
- auto false_literal = Literal::Boolean(false);
- auto another_true = Literal::Boolean(true);
-
- EXPECT_EQ(true_literal <=> another_true, std::partial_ordering::equivalent);
- EXPECT_EQ(true_literal <=> false_literal, std::partial_ordering::greater);
- EXPECT_EQ(false_literal <=> true_literal, std::partial_ordering::less);
-}
-
-// Int type tests
-TEST(LiteralTest, IntBasics) {
- auto int_literal = Literal::Int(42);
- auto negative_int = Literal::Int(-123);
-
- EXPECT_EQ(int_literal.type()->type_id(), TypeId::kInt);
- EXPECT_EQ(negative_int.type()->type_id(), TypeId::kInt);
+// Parameter struct for basic literal tests
+struct BasicLiteralTestParam {
+ std::string test_name;
+ Literal literal;
+ TypeId expected_type_id;
+ std::string expected_string;
+};
- EXPECT_EQ(int_literal.ToString(), "42");
- EXPECT_EQ(negative_int.ToString(), "-123");
-}
+class BasicLiteralTest : public
::testing::TestWithParam<BasicLiteralTestParam> {};
-TEST(LiteralTest, IntComparison) {
- auto int1 = Literal::Int(10);
- auto int2 = Literal::Int(20);
- auto int3 = Literal::Int(10);
+TEST_P(BasicLiteralTest, BasicsTest) {
+ const auto& param = GetParam();
- EXPECT_EQ(int1 <=> int3, std::partial_ordering::equivalent);
- EXPECT_EQ(int1 <=> int2, std::partial_ordering::less);
- EXPECT_EQ(int2 <=> int1, std::partial_ordering::greater);
+ EXPECT_EQ(param.literal.type()->type_id(), param.expected_type_id);
+ EXPECT_EQ(param.literal.ToString(), param.expected_string);
}
-TEST(LiteralTest, IntCastTo) {
- auto int_literal = Literal::Int(42);
+// Parameter struct for comparison tests
+struct ComparisonLiteralTestParam {
+ std::string test_name;
+ Literal small_literal;
+ Literal large_literal;
+ Literal equal_literal; // same as small_literal
+};
- // Cast to Long
- auto long_result = int_literal.CastTo(iceberg::int64());
- ASSERT_THAT(long_result, IsOk());
- EXPECT_EQ(long_result->type()->type_id(), TypeId::kLong);
- EXPECT_EQ(std::get<int64_t>(long_result->value()), 42L);
+class ComparisonLiteralTest
+ : public ::testing::TestWithParam<ComparisonLiteralTestParam> {};
- // Cast to Float
- auto float_result = int_literal.CastTo(iceberg::float32());
- ASSERT_THAT(float_result, IsOk());
- EXPECT_EQ(float_result->type()->type_id(), TypeId::kFloat);
+TEST_P(ComparisonLiteralTest, ComparisonTest) {
+ const auto& param = GetParam();
- // Cast to Double
- auto double_result = int_literal.CastTo(iceberg::float64());
- ASSERT_THAT(double_result, IsOk());
- EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble);
+ EXPECT_EQ(param.small_literal <=> param.equal_literal,
+ std::partial_ordering::equivalent);
+ EXPECT_EQ(param.small_literal <=> param.large_literal,
std::partial_ordering::less);
+ EXPECT_EQ(param.large_literal <=> param.small_literal,
std::partial_ordering::greater);
}
-// Long type tests
-TEST(LiteralTest, LongBasics) {
- auto long_literal = Literal::Long(1234567890L);
- auto negative_long = Literal::Long(-9876543210L);
-
- EXPECT_EQ(long_literal.type()->type_id(), TypeId::kLong);
- EXPECT_EQ(negative_long.type()->type_id(), TypeId::kLong);
+// Parameter struct for cast tests
+struct CastLiteralTestParam {
+ std::string test_name;
+ Literal source_literal;
+ std::shared_ptr<PrimitiveType> target_type;
+ Literal expected_literal;
+};
- EXPECT_EQ(long_literal.ToString(), "1234567890");
- EXPECT_EQ(negative_long.ToString(), "-9876543210");
-}
+class CastLiteralTest : public ::testing::TestWithParam<CastLiteralTestParam>
{};
-TEST(LiteralTest, LongComparison) {
- auto long1 = Literal::Long(100L);
- auto long2 = Literal::Long(200L);
- auto long3 = Literal::Long(100L);
+TEST_P(CastLiteralTest, CastTest) {
+ const auto& param = GetParam();
+ auto result = param.source_literal.CastTo(param.target_type);
- EXPECT_EQ(long1 <=> long3, std::partial_ordering::equivalent);
- EXPECT_EQ(long1 <=> long2, std::partial_ordering::less);
- EXPECT_EQ(long2 <=> long1, std::partial_ordering::greater);
+ ASSERT_THAT(result, IsOk());
+ EXPECT_EQ(*result, param.expected_literal);
}
-TEST(LiteralTest, LongCastTo) {
- auto long_literal = Literal::Long(42L);
-
- // Cast to Int (within range)
- auto int_result = long_literal.CastTo(iceberg::int32());
- ASSERT_THAT(int_result, IsOk());
- EXPECT_EQ(int_result->type()->type_id(), TypeId::kInt);
- EXPECT_EQ(int_result->ToString(), "42");
-
- // Cast to Float
- auto float_result = long_literal.CastTo(iceberg::float32());
- ASSERT_THAT(float_result, IsOk());
- EXPECT_EQ(float_result->type()->type_id(), TypeId::kFloat);
+// Cross-type comparison tests
+TEST(LiteralTest, CrossTypeComparison) {
+ auto int_literal = Literal::Int(42);
+ auto string_literal = Literal::String("42");
- // Cast to Double
- auto double_result = long_literal.CastTo(iceberg::float64());
- ASSERT_THAT(double_result, IsOk());
- EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble);
+ // Different types should return unordered
+ EXPECT_EQ(int_literal <=> string_literal, std::partial_ordering::unordered);
}
-TEST(LiteralTest, LongCastToIntOverflow) {
+// Overflow tests
+TEST(LiteralTest, LongCastToOverflow) {
+ // Test overflow cases
auto max_long =
Literal::Long(static_cast<int64_t>(std::numeric_limits<int32_t>::max())
+ 1);
auto min_long =
Literal::Long(static_cast<int64_t>(std::numeric_limits<int32_t>::min())
- 1);
- auto max_result = max_long.CastTo(iceberg::int32());
+ auto max_result = max_long.CastTo(int32());
ASSERT_THAT(max_result, IsOk());
EXPECT_TRUE(max_result->IsAboveMax());
- auto min_result = min_long.CastTo(iceberg::int32());
+ auto min_result = min_long.CastTo(int32());
ASSERT_THAT(min_result, IsOk());
EXPECT_TRUE(min_result->IsBelowMin());
-}
-
-// Float type tests
-TEST(LiteralTest, FloatBasics) {
- auto float_literal = Literal::Float(3.14f);
- auto negative_float = Literal::Float(-2.71f);
-
- EXPECT_EQ(float_literal.type()->type_id(), TypeId::kFloat);
- EXPECT_EQ(negative_float.type()->type_id(), TypeId::kFloat);
-
- EXPECT_EQ(float_literal.ToString(), "3.140000");
- EXPECT_EQ(negative_float.ToString(), "-2.710000");
-}
-
-TEST(LiteralTest, FloatComparison) {
- auto float1 = Literal::Float(1.5f);
- auto float2 = Literal::Float(2.5f);
- auto float3 = Literal::Float(1.5f);
-
- EXPECT_EQ(float1 <=> float3, std::partial_ordering::equivalent);
- EXPECT_EQ(float1 <=> float2, std::partial_ordering::less);
- EXPECT_EQ(float2 <=> float1, std::partial_ordering::greater);
-}
-
-TEST(LiteralTest, FloatCastTo) {
- auto float_literal = Literal::Float(3.14f);
-
- // Cast to Double
- auto double_result = float_literal.CastTo(iceberg::float64());
- ASSERT_THAT(double_result, IsOk());
- EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble);
-}
-
-// Double type tests
-TEST(LiteralTest, DoubleBasics) {
- auto double_literal = Literal::Double(std::numbers::pi);
- auto negative_double = Literal::Double(-std::numbers::e);
-
- EXPECT_EQ(double_literal.type()->type_id(), TypeId::kDouble);
- EXPECT_EQ(negative_double.type()->type_id(), TypeId::kDouble);
-
- EXPECT_EQ(double_literal.ToString(), "3.141593");
- EXPECT_EQ(negative_double.ToString(), "-2.718282");
-}
-TEST(LiteralTest, DoubleComparison) {
- auto double1 = Literal::Double(1.5);
- auto double2 = Literal::Double(2.5);
- auto double3 = Literal::Double(1.5);
-
- EXPECT_EQ(double1 <=> double3, std::partial_ordering::equivalent);
- EXPECT_EQ(double1 <=> double2, std::partial_ordering::less);
- EXPECT_EQ(double2 <=> double1, std::partial_ordering::greater);
-}
-
-// String type tests
-TEST(LiteralTest, StringBasics) {
- auto string_literal = Literal::String("hello world");
- auto empty_string = Literal::String("");
-
- EXPECT_EQ(string_literal.type()->type_id(), TypeId::kString);
- EXPECT_EQ(empty_string.type()->type_id(), TypeId::kString);
+ max_result = max_long.CastTo(date());
+ ASSERT_THAT(max_result, IsOk());
+ EXPECT_TRUE(max_result->IsAboveMax());
- EXPECT_EQ(string_literal.ToString(), "hello world");
- EXPECT_EQ(empty_string.ToString(), "");
+ min_result = min_long.CastTo(date());
+ ASSERT_THAT(min_result, IsOk());
+ EXPECT_TRUE(min_result->IsBelowMin());
}
-// Uuid type tests
-TEST(LiteralTest, UuidBasics) {
- auto uuid = Uuid::FromString("123e4567-e89b-12d3-a456-426614174000").value();
- auto uuid_literal = Literal::UUID(uuid);
-
- EXPECT_EQ(uuid_literal.type()->type_id(), TypeId::kUuid);
- EXPECT_EQ(uuid_literal.ToString(), "123e4567-e89b-12d3-a456-426614174000");
-}
+TEST(LiteralTest, DoubleCastToOverflow) {
+ // Test overflow cases for Double to Float
+ auto max_double = Literal::Double(double{std::numeric_limits<float>::max()}
* 2);
+ auto min_double = Literal::Double(-double{std::numeric_limits<float>::max()}
* 2);
-TEST(LiteralTest, StringComparison) {
- auto string1 = Literal::String("apple");
- auto string2 = Literal::String("banana");
- auto string3 = Literal::String("apple");
+ auto max_result = max_double.CastTo(float32());
+ ASSERT_THAT(max_result, IsOk());
+ EXPECT_TRUE(max_result->IsAboveMax());
- EXPECT_EQ(string1 <=> string3, std::partial_ordering::equivalent);
- EXPECT_EQ(string1 <=> string2, std::partial_ordering::less);
- EXPECT_EQ(string2 <=> string1, std::partial_ordering::greater);
+ auto min_result = min_double.CastTo(float32());
+ ASSERT_THAT(min_result, IsOk());
+ EXPECT_TRUE(min_result->IsBelowMin());
}
-// Binary type tests
-TEST(LiteralTest, BinaryBasics) {
- std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0xFF};
+// Error cases for casts
+TEST(LiteralTest, CastToError) {
+ std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04};
auto binary_literal = Literal::Binary(data);
- auto empty_binary = Literal::Binary({});
-
- EXPECT_EQ(binary_literal.type()->type_id(), TypeId::kBinary);
- EXPECT_EQ(empty_binary.type()->type_id(), TypeId::kBinary);
-
- EXPECT_EQ(binary_literal.ToString(), "010203FF");
- EXPECT_EQ(empty_binary.ToString(), "");
-}
-TEST(LiteralTest, BinaryComparison) {
- std::vector<uint8_t> data1 = {0x01, 0x02};
- std::vector<uint8_t> data2 = {0x01, 0x03};
- std::vector<uint8_t> data3 = {0x01, 0x02};
+ // Cast to Fixed with different length should fail
+ EXPECT_THAT(binary_literal.CastTo(fixed(5)),
IsError(ErrorKind::kInvalidArgument));
- auto binary1 = Literal::Binary(data1);
- auto binary2 = Literal::Binary(data2);
- auto binary3 = Literal::Binary(data3);
+ data = {0x01, 0x02, 0x03, 0x04};
+ auto fixed_literal = Literal::Fixed(data);
- EXPECT_EQ(binary1 <=> binary3, std::partial_ordering::equivalent);
- EXPECT_EQ(binary1 <=> binary2, std::partial_ordering::less);
- EXPECT_EQ(binary2 <=> binary1, std::partial_ordering::greater);
-}
-
-// Cross-type comparison tests
-TEST(LiteralTest, CrossTypeComparison) {
- auto int_literal = Literal::Int(42);
- auto string_literal = Literal::String("42");
-
- // Different types should return unordered
- EXPECT_EQ(int_literal <=> string_literal, std::partial_ordering::unordered);
+ // Cast to Fixed with different length should fail
+ EXPECT_THAT(fixed_literal.CastTo(fixed(5)),
IsError(ErrorKind::kNotSupported));
}
// Special value tests
TEST(LiteralTest, SpecialValues) {
auto int_literal = Literal::Int(42);
-
EXPECT_FALSE(int_literal.IsAboveMax());
EXPECT_FALSE(int_literal.IsBelowMin());
}
-// Same type cast test
-TEST(LiteralTest, SameTypeCast) {
- auto int_literal = Literal::Int(42);
-
- auto same_type_result = int_literal.CastTo(iceberg::int32());
- ASSERT_THAT(same_type_result, IsOk());
- EXPECT_EQ(same_type_result->type()->type_id(), TypeId::kInt);
- EXPECT_EQ(same_type_result->ToString(), "42");
-}
-
// Float special values tests
TEST(LiteralTest, FloatSpecialValuesComparison) {
// Create special float values
@@ -387,10 +253,10 @@ TEST(LiteralTest, DoubleZeroComparison) {
auto neg_zero = Literal::Double(-0.0);
auto pos_zero = Literal::Double(0.0);
- // -0 should be less than +0
EXPECT_EQ(neg_zero <=> pos_zero, std::partial_ordering::less);
}
+// Parameter struct for literal serialization and deserialization tests
struct LiteralParam {
std::string test_name;
std::vector<uint8_t> serialized;
@@ -603,5 +469,216 @@ TEST(LiteralSerDeTest, TypePromotion) {
EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble);
EXPECT_DOUBLE_EQ(std::get<double>(double_result->value()), 1.0);
}
+// Instantiate parameterized tests
+
+INSTANTIATE_TEST_SUITE_P(
+ BasicLiteralTestCases, BasicLiteralTest,
+ ::testing::Values(
+ BasicLiteralTestParam{.test_name = "BooleanTrue",
+ .literal = Literal::Boolean(true),
+ .expected_type_id = TypeId::kBoolean,
+ .expected_string = "true"},
+ BasicLiteralTestParam{.test_name = "BooleanFalse",
+ .literal = Literal::Boolean(false),
+ .expected_type_id = TypeId::kBoolean,
+ .expected_string = "false"},
+ BasicLiteralTestParam{.test_name = "IntPositive",
+ .literal = Literal::Int(42),
+ .expected_type_id = TypeId::kInt,
+ .expected_string = "42"},
+ BasicLiteralTestParam{.test_name = "IntNegative",
+ .literal = Literal::Int(-123),
+ .expected_type_id = TypeId::kInt,
+ .expected_string = "-123"},
+ BasicLiteralTestParam{.test_name = "LongPositive",
+ .literal = Literal::Long(1234567890L),
+ .expected_type_id = TypeId::kLong,
+ .expected_string = "1234567890"},
+ BasicLiteralTestParam{.test_name = "LongNegative",
+ .literal = Literal::Long(-9876543210L),
+ .expected_type_id = TypeId::kLong,
+ .expected_string = "-9876543210"},
+ BasicLiteralTestParam{.test_name = "Float",
+ .literal = Literal::Float(3.14f),
+ .expected_type_id = TypeId::kFloat,
+ .expected_string = "3.140000"},
+ BasicLiteralTestParam{.test_name = "Double",
+ .literal = Literal::Double(std::numbers::pi),
+ .expected_type_id = TypeId::kDouble,
+ .expected_string = "3.141593"},
+ BasicLiteralTestParam{.test_name = "String",
+ .literal = Literal::String("hello world"),
+ .expected_type_id = TypeId::kString,
+ .expected_string = "\"hello world\""},
+ BasicLiteralTestParam{
+ .test_name = "Binary",
+ .literal = Literal::Binary(std::vector<uint8_t>{0x01, 0x02, 0x03,
0xFF}),
+ .expected_type_id = TypeId::kBinary,
+ .expected_string = "X'010203FF'"},
+ BasicLiteralTestParam{
+ .test_name = "Fixed",
+ .literal = Literal::Fixed(std::vector<uint8_t>{0x01, 0x02, 0x03,
0xFF}),
+ .expected_type_id = TypeId::kFixed,
+ .expected_string = "X'010203FF'"},
+ BasicLiteralTestParam{.test_name = "Date",
+ .literal = Literal::Date(19489),
+ .expected_type_id = TypeId::kDate,
+ .expected_string = "19489"},
+ BasicLiteralTestParam{.test_name = "Time",
+ .literal = Literal::Time(43200000000LL),
+ .expected_type_id = TypeId::kTime,
+ .expected_string = "43200000000"},
+ BasicLiteralTestParam{.test_name = "Timestamp",
+ .literal =
Literal::Timestamp(1684137600000000LL),
+ .expected_type_id = TypeId::kTimestamp,
+ .expected_string = "1684137600000000"},
+ BasicLiteralTestParam{.test_name = "TimestampTz",
+ .literal =
Literal::TimestampTz(1684137600000000LL),
+ .expected_type_id = TypeId::kTimestampTz,
+ .expected_string = "1684137600000000"}),
+ [](const ::testing::TestParamInfo<BasicLiteralTestParam>& info) {
+ return info.param.test_name;
+ });
+
+INSTANTIATE_TEST_SUITE_P(
+ ComparisonLiteralTestCases, ComparisonLiteralTest,
+ ::testing::Values(
+ ComparisonLiteralTestParam{.test_name = "Boolean",
+ .small_literal = Literal::Boolean(false),
+ .large_literal = Literal::Boolean(true),
+ .equal_literal = Literal::Boolean(false)},
+ ComparisonLiteralTestParam{.test_name = "Int",
+ .small_literal = Literal::Int(10),
+ .large_literal = Literal::Int(20),
+ .equal_literal = Literal::Int(10)},
+ ComparisonLiteralTestParam{.test_name = "Long",
+ .small_literal = Literal::Long(100L),
+ .large_literal = Literal::Long(200L),
+ .equal_literal = Literal::Long(100L)},
+ ComparisonLiteralTestParam{.test_name = "Float",
+ .small_literal = Literal::Float(1.5f),
+ .large_literal = Literal::Float(2.5f),
+ .equal_literal = Literal::Float(1.5f)},
+ ComparisonLiteralTestParam{.test_name = "Double",
+ .small_literal = Literal::Double(1.5),
+ .large_literal = Literal::Double(2.5),
+ .equal_literal = Literal::Double(1.5)},
+ ComparisonLiteralTestParam{.test_name = "String",
+ .small_literal = Literal::String("apple"),
+ .large_literal = Literal::String("banana"),
+ .equal_literal = Literal::String("apple")},
+ ComparisonLiteralTestParam{
+ .test_name = "Binary",
+ .small_literal = Literal::Binary(std::vector<uint8_t>{0x01, 0x02}),
+ .large_literal = Literal::Binary(std::vector<uint8_t>{0x01, 0x03}),
+ .equal_literal = Literal::Binary(std::vector<uint8_t>{0x01,
0x02})},
+ ComparisonLiteralTestParam{
+ .test_name = "Fixed",
+ .small_literal = Literal::Fixed(std::vector<uint8_t>{0x01, 0x02}),
+ .large_literal = Literal::Fixed(std::vector<uint8_t>{0x01, 0x03}),
+ .equal_literal = Literal::Fixed(std::vector<uint8_t>{0x01, 0x02})},
+ ComparisonLiteralTestParam{.test_name = "Date",
+ .small_literal = Literal::Date(100),
+ .large_literal = Literal::Date(200),
+ .equal_literal = Literal::Date(100)},
+ ComparisonLiteralTestParam{.test_name = "Time",
+ .small_literal =
Literal::Time(43200000000LL),
+ .large_literal =
Literal::Time(86400000000LL),
+ .equal_literal =
Literal::Time(43200000000LL)},
+ ComparisonLiteralTestParam{.test_name = "Timestamp",
+ .small_literal =
Literal::Timestamp(1000000LL),
+ .large_literal =
Literal::Timestamp(2000000LL),
+ .equal_literal =
Literal::Timestamp(1000000LL)},
+ ComparisonLiteralTestParam{.test_name = "TimestampTz",
+ .small_literal =
Literal::TimestampTz(1000000LL),
+ .large_literal =
Literal::TimestampTz(2000000LL),
+ .equal_literal =
Literal::TimestampTz(1000000LL)}),
+ [](const ::testing::TestParamInfo<ComparisonLiteralTestParam>& info) {
+ return info.param.test_name;
+ });
+
+INSTANTIATE_TEST_SUITE_P(
+ CastLiteralTestCases, CastLiteralTest,
+ ::testing::Values(
+ // Int cast tests
+ CastLiteralTestParam{.test_name = "IntToLong",
+ .source_literal = Literal::Int(42),
+ .target_type = int64(),
+ .expected_literal = Literal::Long(42L)},
+ CastLiteralTestParam{.test_name = "IntToFloat",
+ .source_literal = Literal::Int(42),
+ .target_type = float32(),
+ .expected_literal = Literal::Float(42.0f)},
+ CastLiteralTestParam{.test_name = "IntToDouble",
+ .source_literal = Literal::Int(42),
+ .target_type = float64(),
+ .expected_literal = Literal::Double(42.0)},
+ CastLiteralTestParam{.test_name = "IntToDate",
+ .source_literal = Literal::Int(42),
+ .target_type = date(),
+ .expected_literal = Literal::Date(42)},
+ // Long cast tests
+ CastLiteralTestParam{.test_name = "LongToInt",
+ .source_literal = Literal::Long(42L),
+ .target_type = int32(),
+ .expected_literal = Literal::Int(42)},
+ CastLiteralTestParam{.test_name = "LongToFloat",
+ .source_literal = Literal::Long(42L),
+ .target_type = float32(),
+ .expected_literal = Literal::Float(42.0f)},
+ CastLiteralTestParam{.test_name = "LongToDouble",
+ .source_literal = Literal::Long(42L),
+ .target_type = float64(),
+ .expected_literal = Literal::Double(42.0)},
+ CastLiteralTestParam{.test_name = "LongToTime",
+ .source_literal = Literal::Long(42L),
+ .target_type = time(),
+ .expected_literal = Literal::Time(42L)},
+ CastLiteralTestParam{.test_name = "LongToTimestamp",
+ .source_literal = Literal::Long(42L),
+ .target_type = timestamp(),
+ .expected_literal = Literal::Timestamp(42L)},
+ CastLiteralTestParam{.test_name = "LongToTimestampTz",
+ .source_literal = Literal::Long(42L),
+ .target_type = timestamp_tz(),
+ .expected_literal = Literal::TimestampTz(42L)},
+ // Float cast tests
+ CastLiteralTestParam{.test_name = "FloatToDouble",
+ .source_literal = Literal::Float(2.0f),
+ .target_type = float64(),
+ .expected_literal =
Literal::Double(double{2.0f})},
+ // Double cast tests
+ CastLiteralTestParam{.test_name = "DoubleToFloat",
+ .source_literal = Literal::Double(2.0),
+ .target_type = float32(),
+ .expected_literal = Literal::Float(2.0f)},
+ // Binary cast tests
+ CastLiteralTestParam{.test_name = "BinaryToFixed",
+ .source_literal =
Literal::Binary(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04}),
+ .target_type = fixed(4),
+ .expected_literal =
Literal::Fixed(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04})},
+ // Fixed cast tests
+ CastLiteralTestParam{.test_name = "FixedToBinary",
+ .source_literal =
Literal::Fixed(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04}),
+ .target_type = binary(),
+ .expected_literal =
Literal::Binary(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04})},
+ CastLiteralTestParam{.test_name = "FixedToFixed",
+ .source_literal =
Literal::Fixed(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04}),
+ .target_type = fixed(4),
+ .expected_literal =
Literal::Fixed(std::vector<uint8_t>{
+ 0x01, 0x02, 0x03, 0x04})},
+ // Same type cast test
+ CastLiteralTestParam{.test_name = "IntToInt",
+ .source_literal = Literal::Int(42),
+ .target_type = int32(),
+ .expected_literal = Literal::Int(42)}),
+ [](const ::testing::TestParamInfo<CastLiteralTestParam>& info) {
+ return info.param.test_name;
+ });
} // namespace iceberg