diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt index 09b419e7..851ddcaf 100644 --- a/src/iceberg/CMakeLists.txt +++ b/src/iceberg/CMakeLists.txt @@ -22,6 +22,7 @@ set(ICEBERG_SOURCES catalog/in_memory_catalog.cc demo.cc expression/expression.cc + expression/literal.cc file_reader.cc json_internal.cc manifest_entry.cc diff --git a/src/iceberg/expression/expression.h b/src/iceberg/expression/expression.h index 258c9ee2..342122f4 100644 --- a/src/iceberg/expression/expression.h +++ b/src/iceberg/expression/expression.h @@ -19,7 +19,7 @@ #pragma once -/// \file iceberg/expression.h +/// \file iceberg/expression/expression.h /// Expression interface for Iceberg table operations. #include diff --git a/src/iceberg/expression/literal.cc b/src/iceberg/expression/literal.cc new file mode 100644 index 00000000..f0f5b9dd --- /dev/null +++ b/src/iceberg/expression/literal.cc @@ -0,0 +1,357 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/expression/literal.h" + +#include +#include +#include + +#include "iceberg/exception.h" + +namespace iceberg { + +/// \brief LiteralCaster handles type casting operations for Literal. +/// This is an internal implementation class. +class LiteralCaster { + public: + /// Cast a Literal to the target type. + static Result CastTo(const Literal& literal, + const std::shared_ptr& target_type); + + /// Create a literal representing a value below the minimum for the given type. + static Literal BelowMinLiteral(std::shared_ptr type); + + /// Create a literal representing a value above the maximum for the given type. + static Literal AboveMaxLiteral(std::shared_ptr type); + + private: + /// Cast from Int type to target type. + static Result CastFromInt(const Literal& literal, + const std::shared_ptr& target_type); + + /// Cast from Long type to target type. + static Result CastFromLong(const Literal& literal, + const std::shared_ptr& target_type); + + /// Cast from Float type to target type. + static Result CastFromFloat(const Literal& literal, + const std::shared_ptr& target_type); +}; + +Literal LiteralCaster::BelowMinLiteral(std::shared_ptr type) { + return Literal(Literal::BelowMin{}, std::move(type)); +} + +Literal LiteralCaster::AboveMaxLiteral(std::shared_ptr type) { + return Literal(Literal::AboveMax{}, std::move(type)); +} + +Result LiteralCaster::CastFromInt( + const Literal& literal, const std::shared_ptr& target_type) { + auto int_val = std::get(literal.value_); + auto target_type_id = target_type->type_id(); + + switch (target_type_id) { + case TypeId::kLong: + return Literal::Long(static_cast(int_val)); + case TypeId::kFloat: + return Literal::Float(static_cast(int_val)); + case TypeId::kDouble: + return Literal::Double(static_cast(int_val)); + default: + return NotSupported("Cast from Int to {} is not implemented", + target_type->ToString()); + } +} + +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) { + case TypeId::kInt: { + // Check for overflow + if (long_val >= std::numeric_limits::max()) { + return AboveMaxLiteral(target_type); + } + if (long_val <= std::numeric_limits::min()) { + return BelowMinLiteral(target_type); + } + return Literal::Int(static_cast(long_val)); + } + case TypeId::kFloat: + return Literal::Float(static_cast(long_val)); + case TypeId::kDouble: + return Literal::Double(static_cast(long_val)); + default: + return NotSupported("Cast from Long to {} is not supported", + target_type->ToString()); + } +} + +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) { + case TypeId::kDouble: + return Literal::Double(static_cast(float_val)); + default: + return NotSupported("Cast from Float to {} is not supported", + target_type->ToString()); + } +} + +// Constructor +Literal::Literal(Value value, std::shared_ptr type) + : value_(std::move(value)), type_(std::move(type)) {} + +// Factory methods +Literal Literal::Boolean(bool value) { + return {Value{value}, std::make_shared()}; +} + +Literal Literal::Int(int32_t value) { + return {Value{value}, std::make_shared()}; +} + +Literal Literal::Long(int64_t value) { + return {Value{value}, std::make_shared()}; +} + +Literal Literal::Float(float value) { + return {Value{value}, std::make_shared()}; +} + +Literal Literal::Double(double value) { + return {Value{value}, std::make_shared()}; +} + +Literal Literal::String(std::string value) { + return {Value{std::move(value)}, std::make_shared()}; +} + +Literal Literal::Binary(std::vector value) { + return {Value{std::move(value)}, std::make_shared()}; +} + +Result Literal::Deserialize(std::span data, + std::shared_ptr type) { + return NotImplemented("Deserialization of Literal is not implemented yet"); +} + +Result> Literal::Serialize() const { + return NotImplemented("Serialization of Literal is not implemented yet"); +} + +// Getters + +const std::shared_ptr& Literal::type() const { return type_; } + +// Cast method +Result Literal::CastTo(const std::shared_ptr& target_type) const { + return LiteralCaster::CastTo(*this, target_type); +} + +// Template function for floating point comparison following Iceberg rules: +// -NaN < NaN, but all NaN values (qNaN, sNaN) are treated as equivalent within their sign +template +std::strong_ordering CompareFloat(T lhs, T rhs) { + // If both are NaN, check their signs + bool lhs_is_nan = std::isnan(lhs); + bool rhs_is_nan = std::isnan(rhs); + if (lhs_is_nan && rhs_is_nan) { + bool lhs_is_negative = std::signbit(lhs); + bool rhs_is_negative = std::signbit(rhs); + + if (lhs_is_negative == rhs_is_negative) { + // Same sign NaN values are equivalent (no qNaN vs sNaN distinction) + return std::strong_ordering::equivalent; + } + // -NaN < NaN + return lhs_is_negative ? std::strong_ordering::less : std::strong_ordering::greater; + } + + // For non-NaN values, use standard strong ordering + return std::strong_order(lhs, rhs); +} + +// Three-way comparison operator +std::partial_ordering Literal::operator<=>(const Literal& other) const { + // If types are different, comparison is unordered + if (type_->type_id() != other.type_->type_id()) { + return std::partial_ordering::unordered; + } + + // If either value is AboveMax or BelowMin, comparison is unordered + if (IsAboveMax() || IsBelowMin() || other.IsAboveMax() || other.IsBelowMin()) { + return std::partial_ordering::unordered; + } + + // Same type comparison for normal values + switch (type_->type_id()) { + case TypeId::kBoolean: { + auto this_val = std::get(value_); + auto other_val = std::get(other.value_); + if (this_val == other_val) return std::partial_ordering::equivalent; + return this_val ? std::partial_ordering::greater : std::partial_ordering::less; + } + + case TypeId::kInt: { + auto this_val = std::get(value_); + auto other_val = std::get(other.value_); + return this_val <=> other_val; + } + + case TypeId::kLong: { + auto this_val = std::get(value_); + auto other_val = std::get(other.value_); + return this_val <=> other_val; + } + + case TypeId::kFloat: { + auto this_val = std::get(value_); + auto other_val = std::get(other.value_); + // Use strong_ordering for floating point as spec requests + return CompareFloat(this_val, other_val); + } + + case TypeId::kDouble: { + auto this_val = std::get(value_); + auto other_val = std::get(other.value_); + // Use strong_ordering for floating point as spec requests + return CompareFloat(this_val, other_val); + } + + case TypeId::kString: { + auto& this_val = std::get(value_); + auto& other_val = std::get(other.value_); + return this_val <=> other_val; + } + + case TypeId::kBinary: { + auto& this_val = std::get>(value_); + auto& other_val = std::get>(other.value_); + return this_val <=> other_val; + } + + default: + // For unsupported types, return unordered + return std::partial_ordering::unordered; + } +} + +std::string Literal::ToString() const { + if (std::holds_alternative(value_)) { + return "belowMin"; + } + if (std::holds_alternative(value_)) { + return "aboveMax"; + } + + switch (type_->type_id()) { + case TypeId::kBoolean: { + return std::get(value_) ? "true" : "false"; + } + case TypeId::kInt: { + return std::to_string(std::get(value_)); + } + case TypeId::kLong: { + return std::to_string(std::get(value_)); + } + case TypeId::kFloat: { + return std::to_string(std::get(value_)); + } + case TypeId::kDouble: { + return std::to_string(std::get(value_)); + } + case TypeId::kString: { + return std::get(value_); + } + case TypeId::kBinary: { + const auto& binary_data = std::get>(value_); + std::string result; + result.reserve(binary_data.size() * 2); // 2 chars per byte + for (const auto& byte : binary_data) { + std::format_to(std::back_inserter(result), "{:02X}", byte); + } + return result; + } + case TypeId::kDecimal: + case TypeId::kUuid: + case TypeId::kFixed: + case TypeId::kDate: + case TypeId::kTime: + case TypeId::kTimestamp: + case TypeId::kTimestampTz: { + throw IcebergError("Not implemented: ToString for " + type_->ToString()); + } + default: { + throw IcebergError("Unknown type: " + type_->ToString()); + } + } +} + +bool Literal::IsBelowMin() const { return std::holds_alternative(value_); } + +bool Literal::IsAboveMax() const { return std::holds_alternative(value_); } + +// LiteralCaster implementation + +Result LiteralCaster::CastTo(const Literal& literal, + const std::shared_ptr& target_type) { + if (*literal.type_ == *target_type) { + // If types are the same, return a copy of the current literal + return Literal(literal.value_, target_type); + } + + // Handle special values + if (std::holds_alternative(literal.value_) || + std::holds_alternative(literal.value_)) { + // Cannot cast type for special values + return NotSupported("Cannot cast type for {}", literal.ToString()); + } + + auto source_type_id = literal.type_->type_id(); + + // Delegate to specific cast functions based on source type + switch (source_type_id) { + case TypeId::kInt: + return CastFromInt(literal, target_type); + case TypeId::kLong: + return CastFromLong(literal, target_type); + case TypeId::kFloat: + return CastFromFloat(literal, target_type); + case TypeId::kDouble: + case TypeId::kBoolean: + case TypeId::kString: + case TypeId::kBinary: + break; + default: + break; + } + + return NotSupported("Cast from {} to {} is not implemented", literal.type_->ToString(), + target_type->ToString()); +} + +} // namespace iceberg diff --git a/src/iceberg/expression/literal.h b/src/iceberg/expression/literal.h new file mode 100644 index 00000000..17752c48 --- /dev/null +++ b/src/iceberg/expression/literal.h @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "iceberg/result.h" +#include "iceberg/type.h" + +namespace iceberg { + +/// \brief Literal is a literal value that is associated with a primitive type. +class ICEBERG_EXPORT Literal { + private: + /// \brief Sentinel value to indicate that the literal value is below the valid range + /// of a specific primitive type. It can happen when casting a literal to a narrower + /// primitive type. + struct BelowMin { + bool operator==(const BelowMin&) const = default; + std::strong_ordering operator<=>(const BelowMin&) const = default; + }; + + /// \brief Sentinel value to indicate that the literal value is above the valid range + /// of a specific primitive type. It can happen when casting a literal to a narrower + /// primitive type. + struct AboveMax { + bool operator==(const AboveMax&) const = default; + std::strong_ordering operator<=>(const AboveMax&) const = default; + }; + + using Value = std::variant, // for binary, fixed + std::array, // for uuid and decimal + BelowMin, AboveMax>; + + public: + /// \brief Factory methods for primitive types + static Literal Boolean(bool value); + static Literal Int(int32_t value); + static Literal Long(int64_t value); + static Literal Float(float value); + static Literal Double(double value); + static Literal String(std::string value); + static Literal Binary(std::vector value); + + /// \brief Restore a literal from single-value serialization. + /// + /// See [this spec](https://iceberg.apache.org/spec/#binary-single-value-serialization) + /// for reference. + static Result Deserialize(std::span data, + std::shared_ptr type); + + /// \brief Perform single-value serialization. + /// + /// See [this spec](https://iceberg.apache.org/spec/#binary-single-value-serialization) + /// for reference. + Result> Serialize() const; + + /// \brief Get the literal type. + const std::shared_ptr& type() const; + + /// \brief Converts this literal to a literal of the given type. + /// + /// When a predicate is bound to a concrete data column, literals are converted to match + /// the bound column's type. This conversion process is more narrow than a cast and is + /// only intended for cases where substituting one type is a common mistake (e.g. 34 + /// instead of 34L) or where this API avoids requiring a concrete class (e.g., dates). + /// + /// If conversion to a target type is not supported, this method returns an error. + /// + /// This method may return BelowMin or AboveMax when the target type is not as wide as + /// the original type. These values indicate that the containing predicate can be + /// simplified. For example, std::numeric_limits::max()+1 converted to an int will + /// result in AboveMax and can simplify a < std::numeric_limits::max()+1 to always + /// true. + /// + /// \param target_type A primitive PrimitiveType + /// \return A Result containing a literal of the given type or an error if conversion + /// was not valid + Result CastTo(const std::shared_ptr& target_type) const; + + /// \brief Compare two PrimitiveLiterals. Both literals must have the same type + /// and should not be AboveMax or BelowMin. + std::partial_ordering operator<=>(const Literal& other) const; + + /// Check if this literal represents a value above the maximum allowed value + /// for its type. This occurs when casting from a wider type to a narrower type + /// and the value exceeds the target type's maximum. + /// \return true if this literal represents an AboveMax value, false otherwise + bool IsAboveMax() const; + + /// Check if this literal represents a value below the minimum allowed value + /// for its type. This occurs when casting from a wider type to a narrower type + /// and the value is less than the target type's minimum. + /// \return true if this literal represents a BelowMin value, false otherwise + bool IsBelowMin() const; + + std::string ToString() const; + + private: + Literal(Value value, std::shared_ptr type); + + friend class LiteralCaster; + + private: + Value value_; + std::shared_ptr type_; +}; + +} // namespace iceberg diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 863bb09e..64f8e03f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -50,7 +50,7 @@ target_link_libraries(catalog_test PRIVATE iceberg_static GTest::gtest_main GTes add_test(NAME catalog_test COMMAND catalog_test) add_executable(expression_test) -target_sources(expression_test PRIVATE expression_test.cc) +target_sources(expression_test PRIVATE expression_test.cc literal_test.cc) target_link_libraries(expression_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock) add_test(NAME expression_test COMMAND expression_test) diff --git a/test/literal_test.cc b/test/literal_test.cc new file mode 100644 index 00000000..74c87ba6 --- /dev/null +++ b/test/literal_test.cc @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/expression/literal.h" + +#include +#include +#include + +#include + +#include "iceberg/type.h" +#include "matchers.h" + +namespace iceberg { + +// Boolean type tests +TEST(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, 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); + + EXPECT_EQ(int_literal.ToString(), "42"); + EXPECT_EQ(negative_int.ToString(), "-123"); +} + +TEST(PrimitiveLiteralTest, IntComparison) { + auto int1 = Literal::Int(10); + auto int2 = Literal::Int(20); + auto int3 = Literal::Int(10); + + EXPECT_EQ(int1 <=> int3, std::partial_ordering::equivalent); + EXPECT_EQ(int1 <=> int2, std::partial_ordering::less); + EXPECT_EQ(int2 <=> int1, std::partial_ordering::greater); +} + +TEST(PrimitiveLiteralTest, IntCastTo) { + auto int_literal = Literal::Int(42); + + // Cast to Long + auto long_result = int_literal.CastTo(std::make_shared()); + ASSERT_THAT(long_result, IsOk()); + EXPECT_EQ(long_result->type()->type_id(), TypeId::kLong); + EXPECT_EQ(long_result->ToString(), "42"); + + // Cast to Float + auto float_result = int_literal.CastTo(std::make_shared()); + ASSERT_THAT(float_result, IsOk()); + EXPECT_EQ(float_result->type()->type_id(), TypeId::kFloat); + + // Cast to Double + auto double_result = int_literal.CastTo(std::make_shared()); + ASSERT_THAT(double_result, IsOk()); + EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble); +} + +// Long type tests +TEST(PrimitiveLiteralTest, 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); + + EXPECT_EQ(long_literal.ToString(), "1234567890"); + EXPECT_EQ(negative_long.ToString(), "-9876543210"); +} + +TEST(PrimitiveLiteralTest, LongComparison) { + auto long1 = Literal::Long(100L); + auto long2 = Literal::Long(200L); + auto long3 = Literal::Long(100L); + + EXPECT_EQ(long1 <=> long3, std::partial_ordering::equivalent); + EXPECT_EQ(long1 <=> long2, std::partial_ordering::less); + EXPECT_EQ(long2 <=> long1, std::partial_ordering::greater); +} + +TEST(PrimitiveLiteralTest, LongCastTo) { + auto long_literal = Literal::Long(42L); + + // Cast to Int (within range) + auto int_result = long_literal.CastTo(std::make_shared()); + 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(std::make_shared()); + ASSERT_THAT(float_result, IsOk()); + EXPECT_EQ(float_result->type()->type_id(), TypeId::kFloat); + + // Cast to Double + auto double_result = long_literal.CastTo(std::make_shared()); + ASSERT_THAT(double_result, IsOk()); + EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble); +} + +TEST(PrimitiveLiteralTest, LongCastToIntOverflow) { + // 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(std::make_shared()); + ASSERT_THAT(max_result, IsOk()); + EXPECT_TRUE(max_result->IsAboveMax()); + + auto min_result = min_long.CastTo(std::make_shared()); + ASSERT_THAT(min_result, IsOk()); + EXPECT_TRUE(min_result->IsBelowMin()); +} + +// Float type tests +TEST(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, FloatCastTo) { + auto float_literal = Literal::Float(3.14f); + + // Cast to Double + auto double_result = float_literal.CastTo(std::make_shared()); + ASSERT_THAT(double_result, IsOk()); + EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble); +} + +// Double type tests +TEST(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, 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(PrimitiveLiteralTest, 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); + + EXPECT_EQ(string_literal.ToString(), "hello world"); + EXPECT_EQ(empty_string.ToString(), ""); +} + +TEST(PrimitiveLiteralTest, StringComparison) { + auto string1 = Literal::String("apple"); + auto string2 = Literal::String("banana"); + auto string3 = Literal::String("apple"); + + EXPECT_EQ(string1 <=> string3, std::partial_ordering::equivalent); + EXPECT_EQ(string1 <=> string2, std::partial_ordering::less); + EXPECT_EQ(string2 <=> string1, std::partial_ordering::greater); +} + +// Binary type tests +TEST(PrimitiveLiteralTest, BinaryBasics) { + std::vector data = {0x01, 0x02, 0x03, 0xFF}; + 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(PrimitiveLiteralTest, BinaryComparison) { + std::vector data1 = {0x01, 0x02}; + std::vector data2 = {0x01, 0x03}; + std::vector data3 = {0x01, 0x02}; + + auto binary1 = Literal::Binary(data1); + auto binary2 = Literal::Binary(data2); + auto binary3 = Literal::Binary(data3); + + 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(PrimitiveLiteralTest, 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); +} + +// Special value tests +TEST(PrimitiveLiteralTest, SpecialValues) { + auto int_literal = Literal::Int(42); + + EXPECT_FALSE(int_literal.IsAboveMax()); + EXPECT_FALSE(int_literal.IsBelowMin()); +} + +// Same type cast test +TEST(PrimitiveLiteralTest, SameTypeCast) { + auto int_literal = Literal::Int(42); + + auto same_type_result = int_literal.CastTo(std::make_shared()); + 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(PrimitiveLiteralTest, FloatSpecialValuesComparison) { + // Create special float values + auto neg_nan = Literal::Float(-std::numeric_limits::quiet_NaN()); + auto neg_inf = Literal::Float(-std::numeric_limits::infinity()); + auto neg_value = Literal::Float(-1.5f); + auto neg_zero = Literal::Float(-0.0f); + auto pos_zero = Literal::Float(0.0f); + auto pos_value = Literal::Float(1.5f); + auto pos_inf = Literal::Float(std::numeric_limits::infinity()); + auto pos_nan = Literal::Float(std::numeric_limits::quiet_NaN()); + + // Test the ordering: -NaN < -Infinity < -value < -0 < 0 < value < Infinity < NaN + EXPECT_EQ(neg_nan <=> neg_inf, std::partial_ordering::less); + EXPECT_EQ(neg_inf <=> neg_value, std::partial_ordering::less); + EXPECT_EQ(neg_value <=> neg_zero, std::partial_ordering::less); + EXPECT_EQ(neg_zero <=> pos_zero, std::partial_ordering::less); + EXPECT_EQ(pos_zero <=> pos_value, std::partial_ordering::less); + EXPECT_EQ(pos_value <=> pos_inf, std::partial_ordering::less); + EXPECT_EQ(pos_inf <=> pos_nan, std::partial_ordering::less); +} + +TEST(PrimitiveLiteralTest, FloatNaNComparison) { + auto nan1 = Literal::Float(std::numeric_limits::quiet_NaN()); + auto nan2 = Literal::Float(std::numeric_limits::quiet_NaN()); + auto signaling_nan = Literal::Float(std::numeric_limits::signaling_NaN()); + + // NaN should be equal to itself in strong ordering + EXPECT_EQ(nan1 <=> nan2, std::partial_ordering::equivalent); + EXPECT_EQ(nan1 <=> signaling_nan, std::partial_ordering::equivalent); +} + +TEST(PrimitiveLiteralTest, FloatInfinityComparison) { + auto neg_inf = Literal::Float(-std::numeric_limits::infinity()); + auto pos_inf = Literal::Float(std::numeric_limits::infinity()); + auto max_value = Literal::Float(std::numeric_limits::max()); + auto min_value = Literal::Float(std::numeric_limits::lowest()); + + EXPECT_EQ(neg_inf <=> min_value, std::partial_ordering::less); + EXPECT_EQ(max_value <=> pos_inf, std::partial_ordering::less); + EXPECT_EQ(neg_inf <=> pos_inf, std::partial_ordering::less); +} + +TEST(PrimitiveLiteralTest, FloatZeroComparison) { + auto neg_zero = Literal::Float(-0.0f); + auto pos_zero = Literal::Float(0.0f); + + // -0 should be less than +0 + EXPECT_EQ(neg_zero <=> pos_zero, std::partial_ordering::less); +} + +// Double special values tests +TEST(PrimitiveLiteralTest, DoubleSpecialValuesComparison) { + // Create special double values + auto neg_nan = Literal::Double(-std::numeric_limits::quiet_NaN()); + auto neg_inf = Literal::Double(-std::numeric_limits::infinity()); + auto neg_value = Literal::Double(-1.5); + auto neg_zero = Literal::Double(-0.0); + auto pos_zero = Literal::Double(0.0); + auto pos_value = Literal::Double(1.5); + auto pos_inf = Literal::Double(std::numeric_limits::infinity()); + auto pos_nan = Literal::Double(std::numeric_limits::quiet_NaN()); + + // Test the ordering: -NaN < -Infinity < -value < -0 < 0 < value < Infinity < NaN + EXPECT_EQ(neg_nan <=> neg_inf, std::partial_ordering::less); + EXPECT_EQ(neg_inf <=> neg_value, std::partial_ordering::less); + EXPECT_EQ(neg_value <=> neg_zero, std::partial_ordering::less); + EXPECT_EQ(neg_zero <=> pos_zero, std::partial_ordering::less); + EXPECT_EQ(pos_zero <=> pos_value, std::partial_ordering::less); + EXPECT_EQ(pos_value <=> pos_inf, std::partial_ordering::less); + EXPECT_EQ(pos_inf <=> pos_nan, std::partial_ordering::less); +} + +TEST(PrimitiveLiteralTest, DoubleNaNComparison) { + auto nan1 = Literal::Double(std::numeric_limits::quiet_NaN()); + auto nan2 = Literal::Double(std::numeric_limits::quiet_NaN()); + auto signaling_nan = Literal::Double(std::numeric_limits::signaling_NaN()); + + // NaN should be equal to itself in strong ordering + EXPECT_EQ(nan1 <=> nan2, std::partial_ordering::equivalent); + EXPECT_EQ(nan1 <=> signaling_nan, std::partial_ordering::equivalent); +} + +TEST(PrimitiveLiteralTest, DoubleInfinityComparison) { + auto neg_inf = Literal::Double(-std::numeric_limits::infinity()); + auto pos_inf = Literal::Double(std::numeric_limits::infinity()); + auto max_value = Literal::Double(std::numeric_limits::max()); + auto min_value = Literal::Double(std::numeric_limits::lowest()); + + EXPECT_EQ(neg_inf <=> min_value, std::partial_ordering::less); + EXPECT_EQ(max_value <=> pos_inf, std::partial_ordering::less); + EXPECT_EQ(neg_inf <=> pos_inf, std::partial_ordering::less); +} + +TEST(PrimitiveLiteralTest, 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); +} + +} // namespace iceberg