diff --git a/src/iceberg/expression/literal.cc b/src/iceberg/expression/literal.cc index bd76d9c28..18a46c636 100644 --- a/src/iceberg/expression/literal.cc +++ b/src/iceberg/expression/literal.cc @@ -21,10 +21,12 @@ #include #include +#include +#include -#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 CastFromFloat(const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from Double type to target type. + static Result CastFromDouble( + const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from String type to target type. + static Result CastFromString( + const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from Timestamp type to target type. + static Result CastFromTimestamp( + const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from TimestampTz type to target type. + static Result CastFromTimestampTz( + const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from Binary type to target type. + static Result CastFromBinary( + const Literal& literal, const std::shared_ptr& target_type); + + /// Cast from Fixed type to target type. + static Result CastFromFixed(const Literal& literal, + const std::shared_ptr& target_type); }; Literal LiteralCaster::BelowMinLiteral(std::shared_ptr type) { @@ -76,6 +102,8 @@ Result LiteralCaster::CastFromInt( return Literal::Float(static_cast(int_val)); case TypeId::kDouble: return Literal::Double(static_cast(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 LiteralCaster::CastFromInt( Result LiteralCaster::CastFromLong( const Literal& literal, const std::shared_ptr& target_type) { auto long_val = std::get(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::max()) { + if (long_val > std::numeric_limits::max()) { return AboveMaxLiteral(target_type); } - if (long_val <= std::numeric_limits::min()) { + if (long_val < std::numeric_limits::min()) { return BelowMinLiteral(target_type); } return Literal::Int(static_cast(long_val)); @@ -102,6 +129,21 @@ Result LiteralCaster::CastFromLong( return Literal::Float(static_cast(long_val)); case TypeId::kDouble: return Literal::Double(static_cast(long_val)); + case TypeId::kDate: { + if (long_val > std::numeric_limits::max()) { + return AboveMaxLiteral(target_type); + } + if (long_val < std::numeric_limits::min()) { + return BelowMinLiteral(target_type); + } + return Literal::Date(static_cast(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 LiteralCaster::CastFromLong( Result LiteralCaster::CastFromFloat( const Literal& literal, const std::shared_ptr& target_type) { auto float_val = std::get(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(float_val)); default: @@ -122,6 +163,103 @@ Result LiteralCaster::CastFromFloat( } } +Result LiteralCaster::CastFromDouble( + const Literal& literal, const std::shared_ptr& target_type) { + auto double_val = std::get(literal.value_); + + switch (target_type->type_id()) { + case TypeId::kFloat: { + if (double_val > static_cast(std::numeric_limits::max())) { + return AboveMaxLiteral(target_type); + } + if (double_val < static_cast(std::numeric_limits::lowest())) { + return BelowMinLiteral(target_type); + } + return Literal::Float(static_cast(double_val)); + } + default: + return NotSupported("Cast from Double to {} is not supported", + target_type->ToString()); + } +} + +Result LiteralCaster::CastFromString( + const Literal& literal, const std::shared_ptr& target_type) { + const auto& str_val = std::get(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 LiteralCaster::CastFromTimestamp( + const Literal& literal, const std::shared_ptr& target_type) { + auto timestamp_val = std::get(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 LiteralCaster::CastFromTimestampTz( + const Literal& literal, const std::shared_ptr& target_type) { + auto micros = std::get(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 LiteralCaster::CastFromBinary( + const Literal& literal, const std::shared_ptr& target_type) { + auto binary_val = std::get>(literal.value_); + switch (target_type->type_id()) { + case TypeId::kFixed: { + auto target_fixed_type = internal::checked_pointer_cast(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 LiteralCaster::CastFromFixed( + const Literal& literal, const std::shared_ptr& target_type) { + switch (target_type->type_id()) { + case TypeId::kBinary: + return Literal::Binary(std::get>(literal.value_)); + default: + return NotSupported("Cast from Fixed to {} is not supported", + target_type->ToString()); + } +} + // Constructor Literal::Literal(Value value, std::shared_ptr type) : value_(std::move(value)), type_(std::move(type)) {} @@ -154,8 +292,8 @@ Literal Literal::Binary(std::vector value) { } Literal Literal::Fixed(std::vector value) { - auto length = static_cast(value.size()); - return {Value{std::move(value)}, fixed(length)}; + const auto size = value.size(); + return {Value{std::move(value)}, fixed(size)}; } Result Literal::Deserialize(std::span 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>(value_); - auto& other_val = std::get>(other.value_); - return this_val <=> other_val; - } - + case TypeId::kBinary: case TypeId::kFixed: { auto& this_val = std::get>(value_); auto& other_val = std::get>(other.value_); @@ -308,38 +441,32 @@ std::string Literal::ToString() const { return std::to_string(std::get(value_)); } case TypeId::kString: { - return std::get(value_); + return "\"" + std::get(value_) + "\""; } case TypeId::kUuid: { return std::get(value_).ToString(); } - case TypeId::kBinary: { + case TypeId::kBinary: + case TypeId::kFixed: { const auto& binary_data = std::get>(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>(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(value_)); + } + case TypeId::kDate: { + return std::to_string(std::get(value_)); } default: { - throw IcebergError("Unknown type: " + type_->ToString()); + return std::format("invalid literal of type {}", type_->ToString()); } } } @@ -371,6 +498,9 @@ Result 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 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 144ef2b02..2ce04a161 100644 --- a/src/iceberg/expression/predicate.cc +++ b/src/iceberg/expression/predicate.cc @@ -100,12 +100,11 @@ std::string UnboundPredicate::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 58cc906da..6e4b2aa3b 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 {}; -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(long_result->value()), 42L); +class ComparisonLiteralTest + : public ::testing::TestWithParam {}; - // 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 target_type; + Literal expected_literal; +}; - EXPECT_EQ(long_literal.ToString(), "1234567890"); - EXPECT_EQ(negative_long.ToString(), "-9876543210"); -} +class CastLiteralTest : public ::testing::TestWithParam {}; -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(std::numeric_limits::max()) + 1); auto min_long = Literal::Long(static_cast(std::numeric_limits::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::max()} * 2); + auto min_double = Literal::Double(-double{std::numeric_limits::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 data = {0x01, 0x02, 0x03, 0xFF}; +// Error cases for casts +TEST(LiteralTest, CastToError) { + std::vector 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 data1 = {0x01, 0x02}; - std::vector data2 = {0x01, 0x03}; - std::vector 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 serialized; @@ -603,5 +469,216 @@ TEST(LiteralSerDeTest, TypePromotion) { EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble); EXPECT_DOUBLE_EQ(std::get(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{0x01, 0x02, 0x03, 0xFF}), + .expected_type_id = TypeId::kBinary, + .expected_string = "X'010203FF'"}, + BasicLiteralTestParam{ + .test_name = "Fixed", + .literal = Literal::Fixed(std::vector{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& 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{0x01, 0x02}), + .large_literal = Literal::Binary(std::vector{0x01, 0x03}), + .equal_literal = Literal::Binary(std::vector{0x01, 0x02})}, + ComparisonLiteralTestParam{ + .test_name = "Fixed", + .small_literal = Literal::Fixed(std::vector{0x01, 0x02}), + .large_literal = Literal::Fixed(std::vector{0x01, 0x03}), + .equal_literal = Literal::Fixed(std::vector{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& 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{ + 0x01, 0x02, 0x03, 0x04}), + .target_type = fixed(4), + .expected_literal = Literal::Fixed(std::vector{ + 0x01, 0x02, 0x03, 0x04})}, + // Fixed cast tests + CastLiteralTestParam{.test_name = "FixedToBinary", + .source_literal = Literal::Fixed(std::vector{ + 0x01, 0x02, 0x03, 0x04}), + .target_type = binary(), + .expected_literal = Literal::Binary(std::vector{ + 0x01, 0x02, 0x03, 0x04})}, + CastLiteralTestParam{.test_name = "FixedToFixed", + .source_literal = Literal::Fixed(std::vector{ + 0x01, 0x02, 0x03, 0x04}), + .target_type = fixed(4), + .expected_literal = Literal::Fixed(std::vector{ + 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& info) { + return info.param.test_name; + }); } // namespace iceberg