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


Reply via email to