blob: 88f4b5740445f2dbe81fb9dd3dae316bec777f7e [file]
/*
* 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 <array>
#include <charconv>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <format>
#include <string>
#include <string_view>
#include <system_error>
#include <vector>
#include "iceberg/result.h"
#include "iceberg/type.h"
#include "iceberg/util/checked_cast.h"
#include "iceberg/util/conversions.h"
#include "iceberg/util/decimal.h"
#include "iceberg/util/macros.h"
#include "iceberg/util/string_util.h"
#include "iceberg/util/temporal_util.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<Literal> CastTo(const Literal& literal,
const std::shared_ptr<PrimitiveType>& target_type);
/// Create a literal representing a value below the minimum for the given type.
static Literal BelowMinLiteral(std::shared_ptr<PrimitiveType> type);
/// Create a literal representing a value above the maximum for the given type.
static Literal AboveMaxLiteral(std::shared_ptr<PrimitiveType> type);
private:
/// Cast from Int type to target type.
static Result<Literal> CastFromInt(const Literal& literal,
const std::shared_ptr<PrimitiveType>& target_type);
/// Cast from Long type to target type.
static Result<Literal> CastFromLong(const Literal& literal,
const std::shared_ptr<PrimitiveType>& target_type);
/// 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);
/// Cast an integer value (from Int or Long) to a decimal target type.
static Result<Literal> CastIntegerToDecimal(
int64_t value, const std::shared_ptr<PrimitiveType>& target_type);
/// Cast a floating-point value (from Float or Double) to a decimal target type.
static Result<Literal> CastRealToDecimal(
double value, const std::shared_ptr<PrimitiveType>& target_type);
};
Literal LiteralCaster::BelowMinLiteral(std::shared_ptr<PrimitiveType> type) {
return Literal(Literal::BelowMin{}, std::move(type));
}
Literal LiteralCaster::AboveMaxLiteral(std::shared_ptr<PrimitiveType> type) {
return Literal(Literal::AboveMax{}, std::move(type));
}
namespace {
Status ValidateDecimalScale(int32_t scale) {
if (scale < -Decimal::kMaxScale || scale > Decimal::kMaxScale) {
return InvalidArgument("decimal scale must be in range [-{}, {}], was {}",
Decimal::kMaxScale, Decimal::kMaxScale, scale);
}
return {};
}
// Rescale `unscaled` (interpreted at `from_scale`) to `to_scale` using HALF_UP rounding
// (round half away from zero), matching Java's BigDecimal.setScale(scale, HALF_UP).
// Unlike Decimal::Rescale, which only truncates and rejects any dropped remainder, this
// rounds, and it supports the full negative..positive scale range Iceberg decimals allow.
Result<Decimal> RescaleHalfUp(const Decimal& unscaled, int32_t from_scale,
int32_t to_scale, bool negative) {
const int32_t delta = to_scale - from_scale;
if (delta == 0) {
return unscaled;
}
if (delta > 0) {
// Growing the scale multiplies the coefficient by 10^delta. Report an overflow as an
// invalid argument (consistent with the other rejections here) rather than leaking
// Rescale's "data loss" error to callers.
if (delta > Decimal::kMaxScale) {
return InvalidArgument("scale change {} exceeds the maximum {}", delta,
Decimal::kMaxScale);
}
auto rescaled = unscaled.Rescale(from_scale, to_scale);
if (!rescaled.has_value()) {
return InvalidArgument("value does not fit the target decimal scale");
}
return rescaled.value();
}
// Shrinking the scale drops `drop` digits with HALF_UP rounding. A drop larger than the
// digits any decimal can hold rounds everything away, so the result is zero (e.g.
// 1e-100 to decimal(9, 2)); this also keeps the divisor within the powers-of-ten table.
const int32_t drop = -delta;
if (drop > Decimal::kMaxScale) {
return Decimal(0);
}
ICEBERG_ASSIGN_OR_RAISE(auto divisor, Decimal(1).Rescale(0, drop));
ICEBERG_ASSIGN_OR_RAISE(auto divmod, unscaled.Divide(divisor));
Decimal quotient = divmod.first;
Decimal remainder = Decimal::Abs(divmod.second);
// Compare against divisor/2 rather than remainder*2: for drop near kMaxScale,
// remainder*2 can overflow int128 and flip the HALF_UP decision. Powers of ten
// with drop >= 1 are always even, so the two comparisons are equivalent.
if (remainder >= divisor / Decimal(2)) {
quotient += negative ? Decimal(-1) : Decimal(1);
}
return quotient;
}
// Parse a `std::to_chars` double rendering (`[-]digits[.digits][e[+-]exp]`) into an
// integer coefficient and the scale at which it is interpreted (value == coefficient *
// 10^-scale). The coefficient is just the significant digits, so it never overflows
// int128; the exponent is folded into the returned scale rather than expanded, leaving
// RescaleHalfUp to combine it with the target scale.
Result<Decimal> ParseRealCoefficient(std::string_view str, int32_t* scale) {
bool negative = !str.empty() && str.front() == '-';
if (negative) {
str.remove_prefix(1);
}
int32_t exponent = 0;
if (auto e = str.find_first_of("eE"); e != std::string_view::npos) {
auto exp_str = str.substr(e + 1);
// std::to_chars writes a leading '+' for positive exponents, which from_chars
// rejects.
if (!exp_str.empty() && exp_str.front() == '+') {
exp_str.remove_prefix(1);
}
if (std::from_chars(exp_str.data(), exp_str.data() + exp_str.size(), exponent).ec !=
std::errc{}) {
return InvalidArgument("Invalid real value '{}'", str);
}
str = str.substr(0, e);
}
int32_t frac_digits = 0;
std::string digits;
digits.reserve(str.size());
if (auto dot = str.find('.'); dot != std::string_view::npos) {
frac_digits = static_cast<int32_t>(str.size() - dot - 1);
digits.append(str.substr(0, dot));
digits.append(str.substr(dot + 1));
} else {
digits.append(str);
}
if (digits.empty() || digits.find_first_not_of("0123456789") != std::string::npos) {
return InvalidArgument("Invalid real value '{}'", str);
}
// Coefficient has scale `frac_digits`; a positive base-10 exponent lowers that scale.
*scale = frac_digits - exponent;
ICEBERG_ASSIGN_OR_RAISE(auto coefficient, Decimal::FromString(digits));
if (negative) {
coefficient.Negate();
}
return coefficient;
}
} // namespace
Result<Literal> LiteralCaster::CastIntegerToDecimal(
int64_t value, const std::shared_ptr<PrimitiveType>& target_type) {
const auto& decimal_type = internal::checked_cast<const DecimalType&>(*target_type);
ICEBERG_RETURN_UNEXPECTED(ValidateDecimalScale(decimal_type.scale()));
// An integer has scale 0; rescale it to the target scale, rounding HALF_UP when the
// target scale is negative (matching Java's numeric-to-decimal default handling).
ICEBERG_ASSIGN_OR_RAISE(auto unscaled, RescaleHalfUp(Decimal(value), /*from_scale=*/0,
decimal_type.scale(), value < 0));
if (!unscaled.FitsInPrecision(decimal_type.precision())) {
return InvalidArgument("Cannot cast {} as a {} value", value,
target_type->ToString());
}
return Literal::Decimal(unscaled.value(), decimal_type.precision(),
decimal_type.scale());
}
Result<Literal> LiteralCaster::CastRealToDecimal(
double value, const std::shared_ptr<PrimitiveType>& target_type) {
const auto& decimal_type = internal::checked_cast<const DecimalType&>(*target_type);
ICEBERG_RETURN_UNEXPECTED(ValidateDecimalScale(decimal_type.scale()));
if (!std::isfinite(value)) {
return InvalidArgument("Cannot cast {} as a {} value", value,
target_type->ToString());
}
// Convert via the shortest round-tripping decimal string (std::to_chars without a
// format specifier), then round to the target scale. Float callers widen to double
// first, so both float and double sources share this path.
std::array<char, 64> buf{};
auto [ptr, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), value);
if (ec != std::errc{}) {
return InvalidArgument("Cannot cast {} as a {} value", value,
target_type->ToString());
}
// Parse the coefficient and its scale directly instead of via Decimal::FromString,
// which normalizes a negative scale by multiplying the coefficient by 10^-scale and can
// overflow int128 for large magnitudes (e.g. 4e38). The coefficient itself always fits,
// and RescaleHalfUp handles the exponent against the target scale (and rejects
// overflow).
int32_t coeff_scale = 0;
ICEBERG_ASSIGN_OR_RAISE(
auto coefficient,
ParseRealCoefficient(std::string_view(buf.data(), ptr), &coeff_scale));
ICEBERG_ASSIGN_OR_RAISE(auto unscaled, RescaleHalfUp(coefficient, coeff_scale,
decimal_type.scale(), value < 0));
if (!unscaled.FitsInPrecision(decimal_type.precision())) {
return InvalidArgument("Cannot cast {} as a {} value", value,
target_type->ToString());
}
return Literal::Decimal(unscaled.value(), decimal_type.precision(),
decimal_type.scale());
}
Result<Literal> LiteralCaster::CastFromInt(
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
auto int_val = std::get<int32_t>(literal.value_);
auto target_type_id = target_type->type_id();
switch (target_type_id) {
case TypeId::kLong:
return Literal::Long(static_cast<int64_t>(int_val));
case TypeId::kFloat:
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);
case TypeId::kDecimal:
return CastIntegerToDecimal(static_cast<int64_t>(int_val), target_type);
default:
return NotSupported("Cast from Int to {} is not implemented",
target_type->ToString());
}
}
Result<Literal> LiteralCaster::CastFromLong(
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
auto long_val = std::get<int64_t>(literal.value_);
switch (target_type->type_id()) {
case TypeId::kInt: {
// Check for overflow
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::Int(static_cast<int32_t>(long_val));
}
case TypeId::kFloat:
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);
case TypeId::kTimestampNs:
return Literal::TimestampNs(long_val);
case TypeId::kTimestampTzNs:
return Literal::TimestampTzNs(long_val);
case TypeId::kDecimal:
return CastIntegerToDecimal(long_val, target_type);
default:
return NotSupported("Cast from Long to {} is not supported",
target_type->ToString());
}
}
Result<Literal> LiteralCaster::CastFromFloat(
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
auto float_val = std::get<float>(literal.value_);
switch (target_type->type_id()) {
case TypeId::kDouble:
return Literal::Double(static_cast<double>(float_val));
case TypeId::kDecimal:
return CastRealToDecimal(static_cast<double>(float_val), target_type);
default:
return NotSupported("Cast from Float to {} is not supported",
target_type->ToString());
}
}
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));
}
case TypeId::kDecimal:
return CastRealToDecimal(double_val, target_type);
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::kUuid: {
ICEBERG_ASSIGN_OR_RAISE(auto uuid, Uuid::FromString(str_val));
return Literal::UUID(uuid);
}
case TypeId::kDate: {
ICEBERG_ASSIGN_OR_RAISE(auto days, TemporalUtils::ParseDay(str_val));
return Literal::Date(days);
}
case TypeId::kTime: {
ICEBERG_ASSIGN_OR_RAISE(auto micros, TemporalUtils::ParseTime(str_val));
return Literal::Time(micros);
}
case TypeId::kTimestamp: {
ICEBERG_ASSIGN_OR_RAISE(auto micros, TemporalUtils::ParseTimestamp(str_val));
return Literal::Timestamp(micros);
}
case TypeId::kTimestampTz: {
ICEBERG_ASSIGN_OR_RAISE(auto micros,
TemporalUtils::ParseTimestampWithZone(str_val));
return Literal::TimestampTz(micros);
}
case TypeId::kTimestampNs: {
ICEBERG_ASSIGN_OR_RAISE(auto nanos, TemporalUtils::ParseTimestampNs(str_val));
return Literal::TimestampNs(nanos);
}
case TypeId::kTimestampTzNs: {
ICEBERG_ASSIGN_OR_RAISE(auto nanos,
TemporalUtils::ParseTimestampNsWithZone(str_val));
return Literal::TimestampTzNs(nanos);
}
case TypeId::kBinary: {
ICEBERG_ASSIGN_OR_RAISE(auto bytes, StringUtils::HexStringToBytes(str_val));
return Literal::Binary(std::move(bytes));
}
case TypeId::kFixed: {
const auto& fixed_type = internal::checked_cast<const FixedType&>(*target_type);
if (str_val.size() != static_cast<size_t>(fixed_type.length()) * 2) {
return InvalidArgument("Cannot cast string to {}: expected {} hex chars, got {}",
target_type->ToString(), fixed_type.length() * 2,
str_val.size());
}
ICEBERG_ASSIGN_OR_RAISE(auto bytes, StringUtils::HexStringToBytes(str_val));
return Literal::Fixed(std::move(bytes));
}
case TypeId::kDecimal: {
const auto& dec_type = internal::checked_cast<const DecimalType&>(*target_type);
int32_t parsed_precision = 0;
int32_t parsed_scale = 0;
ICEBERG_ASSIGN_OR_RAISE(
auto dec, Decimal::FromString(str_val, &parsed_precision, &parsed_scale));
if (parsed_precision > dec_type.precision() || parsed_scale != dec_type.scale()) {
return InvalidArgument("Cannot cast {} as a {} value", str_val,
target_type->ToString());
}
return Literal::Decimal(dec.value(), dec_type.precision(), dec_type.scale());
}
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_);
const auto& source_timestamp =
internal::checked_cast<const TimestampBase&>(*literal.type());
const bool source_is_nanos = source_timestamp.time_unit() == TimeUnit::kNanosecond;
switch (target_type->type_id()) {
case TypeId::kDate: {
ICEBERG_ASSIGN_OR_RAISE(auto days, TemporalUtils::ExtractDay(literal));
return Literal::Date(std::get<int32_t>(days.value()));
}
case TypeId::kTimestamp:
return source_is_nanos
? Literal::Timestamp(TemporalUtils::NanosToMicros(timestamp_val))
: Literal::Timestamp(timestamp_val);
case TypeId::kTimestampTz:
return source_is_nanos
? Literal::TimestampTz(TemporalUtils::NanosToMicros(timestamp_val))
: Literal::TimestampTz(timestamp_val);
case TypeId::kTimestampNs: {
if (source_is_nanos) {
return Literal::TimestampNs(timestamp_val);
}
ICEBERG_ASSIGN_OR_RAISE(auto nanos, TemporalUtils::MicrosToNanos(timestamp_val));
return Literal::TimestampNs(nanos);
}
case TypeId::kTimestampTzNs: {
if (source_is_nanos) {
return Literal::TimestampTzNs(timestamp_val);
}
ICEBERG_ASSIGN_OR_RAISE(auto nanos, TemporalUtils::MicrosToNanos(timestamp_val));
return Literal::TimestampTzNs(nanos);
}
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 timestamp_val = std::get<int64_t>(literal.value_);
const auto& source_timestamp =
internal::checked_cast<const TimestampBase&>(*literal.type());
const bool source_is_nanos = source_timestamp.time_unit() == TimeUnit::kNanosecond;
switch (target_type->type_id()) {
case TypeId::kDate: {
ICEBERG_ASSIGN_OR_RAISE(auto days, TemporalUtils::ExtractDay(literal));
return Literal::Date(std::get<int32_t>(days.value()));
}
case TypeId::kTimestampTz:
return source_is_nanos
? Literal::TimestampTz(TemporalUtils::NanosToMicros(timestamp_val))
: Literal::TimestampTz(timestamp_val);
case TypeId::kTimestamp:
return source_is_nanos
? Literal::Timestamp(TemporalUtils::NanosToMicros(timestamp_val))
: Literal::Timestamp(timestamp_val);
case TypeId::kTimestampNs: {
if (source_is_nanos) {
return Literal::TimestampNs(timestamp_val);
}
ICEBERG_ASSIGN_OR_RAISE(auto nanos, TemporalUtils::MicrosToNanos(timestamp_val));
return Literal::TimestampNs(nanos);
}
case TypeId::kTimestampTzNs: {
if (source_is_nanos) {
return Literal::TimestampTzNs(timestamp_val);
}
ICEBERG_ASSIGN_OR_RAISE(auto nanos, TemporalUtils::MicrosToNanos(timestamp_val));
return Literal::TimestampTzNs(nanos);
}
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)) {}
// Factory methods
Literal Literal::Boolean(bool value) { return {Value{value}, boolean()}; }
Literal Literal::Int(int32_t value) { return {Value{value}, int32()}; }
Literal Literal::Date(int32_t value) { return {Value{value}, date()}; }
Literal Literal::Long(int64_t value) { return {Value{value}, int64()}; }
Literal Literal::Time(int64_t value) { return {Value{value}, time()}; }
Literal Literal::Timestamp(int64_t value) { return {Value{value}, timestamp()}; }
Literal Literal::TimestampTz(int64_t value) { return {Value{value}, timestamp_tz()}; }
Literal Literal::TimestampNs(int64_t value) { return {Value{value}, timestamp_ns()}; }
Literal Literal::TimestampTzNs(int64_t value) { return {Value{value}, timestamptz_ns()}; }
Literal Literal::Float(float value) { return {Value{value}, float32()}; }
Literal Literal::Double(double value) { return {Value{value}, float64()}; }
Literal Literal::String(std::string value) { return {Value{std::move(value)}, string()}; }
Literal Literal::UUID(Uuid value) { return {Value{std::move(value)}, uuid()}; }
Literal Literal::Binary(std::vector<uint8_t> value) {
return {Value{std::move(value)}, binary()};
}
Literal Literal::Fixed(std::vector<uint8_t> value) {
const auto size = value.size();
return {Value{std::move(value)}, fixed(size)};
}
Literal Literal::Decimal(int128_t value, int32_t precision, int32_t scale) {
return {Value{::iceberg::Decimal(value)}, decimal(precision, scale)};
}
Result<Literal> Literal::Deserialize(std::span<const uint8_t> data,
std::shared_ptr<PrimitiveType> type) {
return Conversions::FromBytes(std::move(type), data);
}
Result<std::vector<uint8_t>> Literal::Serialize() const {
return Conversions::ToBytes(*this);
}
// Getters
const std::shared_ptr<PrimitiveType>& Literal::type() const { return type_; }
// Cast method
Result<Literal> Literal::CastTo(const std::shared_ptr<PrimitiveType>& target_type) const {
return LiteralCaster::CastTo(*this, target_type);
}
// Template function for floating point comparison following the Iceberg total
// ordering: -NaN < -Infinity < ... < +Infinity < +NaN. std::strong_order
// implements the IEEE 754 totalOrder predicate on IEC 559 types, which matches
// this requirement (and orders -0 below +0).
template <std::floating_point T>
std::strong_ordering CompareFloat(T lhs, T rhs) {
return std::strong_order(lhs, rhs);
}
namespace {
bool Comparable(TypeId lhs, TypeId rhs) {
if (lhs == rhs) {
return true;
}
if ((lhs == TypeId::kInt || lhs == TypeId::kDate) &&
(rhs == TypeId::kInt || rhs == TypeId::kDate)) {
return true;
}
// Java allows zoned/non-zoned timestamp comparisons; keep C++ stricter for now.
return false;
}
} // namespace
bool Literal::operator==(const Literal& other) const { return (*this <=> other) == 0; }
// Three-way comparison operator
std::partial_ordering Literal::operator<=>(const Literal& other) const {
// Allow date/int comparisons for transformed date bounds. Otherwise, comparisons
// are limited to identical logical values.
if (!Comparable(type_->type_id(), other.type_->type_id())) {
return std::partial_ordering::unordered;
}
// If either value is AboveMax, BelowMin or null, comparison is unordered
if (IsAboveMax() || IsBelowMin() || other.IsAboveMax() || other.IsBelowMin() ||
IsNull() || other.IsNull()) {
return std::partial_ordering::unordered;
}
// Same type comparison for normal values
switch (type_->type_id()) {
case TypeId::kBoolean: {
auto this_val = std::get<bool>(value_);
auto other_val = std::get<bool>(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:
case TypeId::kDate: {
auto this_val = std::get<int32_t>(value_);
auto other_val = std::get<int32_t>(other.value_);
return this_val <=> other_val;
}
case TypeId::kLong:
case TypeId::kTime:
case TypeId::kTimestamp:
case TypeId::kTimestampTz:
case TypeId::kTimestampNs:
case TypeId::kTimestampTzNs: {
auto this_val = std::get<int64_t>(value_);
auto other_val = std::get<int64_t>(other.value_);
return this_val <=> other_val;
}
case TypeId::kFloat: {
auto this_val = std::get<float>(value_);
auto other_val = std::get<float>(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<double>(value_);
auto other_val = std::get<double>(other.value_);
// Use strong_ordering for floating point as spec requests
return CompareFloat(this_val, other_val);
}
case TypeId::kDecimal: {
auto& this_val = std::get<::iceberg::Decimal>(value_);
auto& other_val = std::get<::iceberg::Decimal>(other.value_);
const auto& this_decimal_type = internal::checked_cast<DecimalType&>(*type_);
const auto& other_decimal_type = internal::checked_cast<DecimalType&>(*other.type_);
return ::iceberg::Decimal::Compare(this_val, other_val, this_decimal_type.scale(),
other_decimal_type.scale());
}
case TypeId::kString: {
auto& this_val = std::get<std::string>(value_);
auto& other_val = std::get<std::string>(other.value_);
return this_val <=> other_val;
}
case TypeId::kUuid: {
auto& this_val = std::get<Uuid>(value_);
auto& other_val = std::get<Uuid>(other.value_);
if (this_val == other_val) {
return std::partial_ordering::equivalent;
}
return std::partial_ordering::unordered;
}
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_);
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<BelowMin>(value_)) {
return "belowMin";
}
if (std::holds_alternative<AboveMax>(value_)) {
return "aboveMax";
}
if (std::holds_alternative<std::monostate>(value_)) {
return "null";
}
switch (type_->type_id()) {
case TypeId::kBoolean: {
return std::get<bool>(value_) ? "true" : "false";
}
case TypeId::kInt: {
return std::to_string(std::get<int32_t>(value_));
}
case TypeId::kLong: {
return std::to_string(std::get<int64_t>(value_));
}
case TypeId::kFloat: {
return std::to_string(std::get<float>(value_));
}
case TypeId::kDouble: {
return std::to_string(std::get<double>(value_));
}
case TypeId::kDecimal: {
const auto& decimal_type = internal::checked_cast<DecimalType&>(*type_);
const auto& decimal = std::get<::iceberg::Decimal>(value_);
return decimal.ToString(decimal_type.scale())
.value_or("invalid literal of type decimal");
}
case TypeId::kString: {
return "\"" + std::get<std::string>(value_) + "\"";
}
case TypeId::kUuid: {
return std::get<Uuid>(value_).ToString();
}
case TypeId::kBinary:
case TypeId::kFixed: {
const auto& binary_data = std::get<std::vector<uint8_t>>(value_);
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::kTime:
case TypeId::kTimestamp:
case TypeId::kTimestampTz:
case TypeId::kTimestampNs:
case TypeId::kTimestampTzNs: {
return std::to_string(std::get<int64_t>(value_));
}
case TypeId::kDate: {
return std::to_string(std::get<int32_t>(value_));
}
default: {
return std::format("invalid literal of type {}", type_->ToString());
}
}
}
bool Literal::IsBelowMin() const { return std::holds_alternative<BelowMin>(value_); }
bool Literal::IsAboveMax() const { return std::holds_alternative<AboveMax>(value_); }
bool Literal::IsNull() const { return std::holds_alternative<std::monostate>(value_); }
bool Literal::IsNaN() const {
return std::holds_alternative<float>(value_) && std::isnan(std::get<float>(value_)) ||
std::holds_alternative<double>(value_) && std::isnan(std::get<double>(value_));
}
// LiteralCaster implementation
Result<Literal> LiteralCaster::CastTo(const Literal& literal,
const std::shared_ptr<PrimitiveType>& 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::BelowMin>(literal.value_) ||
std::holds_alternative<Literal::AboveMax>(literal.value_) ||
std::holds_alternative<std::monostate>(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::kBoolean:
// No casts defined for Boolean, other than to itself.
break;
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:
return CastFromDouble(literal, target_type);
case TypeId::kString:
return CastFromString(literal, target_type);
case TypeId::kBinary:
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);
case TypeId::kTimestampNs:
return CastFromTimestamp(literal, target_type);
case TypeId::kTimestampTzNs:
return CastFromTimestampTz(literal, target_type);
default:
break;
}
return NotSupported("Cast from {} to {} is not supported", literal.type_->ToString(),
target_type->ToString());
}
// LiteralValueHash implementation
std::size_t LiteralValueHash::operator()(const Literal::Value& value) const noexcept {
return std::visit(
[](const auto& v) -> std::size_t {
using T = std::remove_cvref_t<decltype(v)>;
constexpr size_t kHashPrime = 0x9e3779b9;
if constexpr (std::is_same_v<T, std::monostate>) {
return 0;
} else if constexpr (std::is_same_v<T, Literal::BelowMin>) {
return std::numeric_limits<std::size_t>::min();
} else if constexpr (std::is_same_v<T, Literal::AboveMax>) {
return std::numeric_limits<std::size_t>::max();
} else if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, int32_t> ||
std::is_same_v<T, int64_t> || std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>) {
return std::hash<T>{}(v);
} else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
std::size_t hash = 0;
for (size_t i = 0; i < v.size(); ++i) {
hash ^= std::hash<uint8_t>{}(v[i]) + kHashPrime + (hash << 6) + (hash >> 2);
}
return hash;
} else if constexpr (std::is_same_v<T, Decimal>) {
const int128_t& val = v.value();
std::size_t hash = std::hash<uint64_t>{}(static_cast<uint64_t>(val >> 64));
hash ^= std::hash<uint64_t>{}(static_cast<uint64_t>(val)) + kHashPrime +
(hash << 6) + (hash >> 2);
return hash;
} else if constexpr (std::is_same_v<T, Uuid>) {
std::size_t hash = 0;
const auto& bytes = v.bytes();
for (size_t i = 0; i < bytes.size(); ++i) {
hash ^=
std::hash<uint8_t>{}(bytes[i]) + kHashPrime + (hash << 6) + (hash >> 2);
}
return hash;
} else {
static_assert(sizeof(T) == 0, "Unhandled variant type in LiteralValueHash");
return 0;
}
},
value);
}
} // namespace iceberg