Skip to content

Conversation

HeartLinked
Copy link
Contributor

  • Implements the complete type casting logic for iceberg::Literal in the LiteralCaster class to align with the Java reference implementation. This is critical for expression evaluation and predicate pushdown.
  • Add basic implementation for fixed type.
  • Updated ToString() to match Java's output format for better consistency (e.g., X'...' for binary).
  • Added comprehensive unit tests to validate all new casting logic and ToString() formatting.

@HeartLinked HeartLinked force-pushed the feat/literal2 branch 2 times, most recently from cf43748 to 406a3f2 Compare September 10, 2025 06:26
Comment on lines 575 to 507
result.reserve(2 + binary_data.size() * 2 +
1); // 2 chars per byte and 2 + 1 for prefix and suffix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result.reserve(2 + binary_data.size() * 2 +
1); // 2 chars per byte and 2 + 1 for prefix and suffix
result.reserve(/*prefix*/2 + binary_data.size() * 2 + /*suffix*/1);

Comment on lines 78 to 81

/// \brief Factory methods for decimal type
static Literal Decimal(const Decimal& value, int32_t precision, int32_t scale);
static Literal Decimal(int128_t value, int32_t precision, int32_t scale);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// \brief Factory methods for decimal type
static Literal Decimal(const Decimal& value, int32_t precision, int32_t scale);
static Literal Decimal(int128_t value, int32_t precision, int32_t scale);
static Literal Decimal(int128_t value, int32_t precision, int32_t scale);
  • Decimal type is also a primitive type so let's put them together without blank line.
  • I think int128_t is enough and it duplicates with Decimal

case TypeId::kDate:
return Literal::Date(int_val);
case TypeId::kDecimal: {
auto decimal_type = std::static_pointer_cast<DecimalType>(target_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto decimal_type = std::static_pointer_cast<DecimalType>(target_type);
auto decimal_type = internal::checked_pointer_cast<DecimalType>(target_type);

case TypeId::kDouble:
return Literal::Double(static_cast<double>(float_val));
case TypeId::kDecimal: {
auto decimal_type = std::static_pointer_cast<DecimalType>(target_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

case TypeId::kTimestampTz:
return Literal::TimestampTz(long_val);
case TypeId::kDecimal: {
auto decimal_type = std::static_pointer_cast<DecimalType>(target_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto decimal_type = std::static_pointer_cast<DecimalType>(target_type);
auto decimal_type = internal::checked_pointer_cast<DecimalType>(target_type);

Comment on lines 208 to 214
iceberg::Decimal parsed_val;
int32_t parsed_scale = 0;
ICEBERG_ASSIGN_OR_RAISE(
parsed_val, iceberg::Decimal::FromString(double_str, nullptr, &parsed_scale));
iceberg::Decimal rescaled_val;
ICEBERG_ASSIGN_OR_RAISE(rescaled_val,
parsed_val.Rescale(parsed_scale, decimal_type->scale()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
iceberg::Decimal parsed_val;
int32_t parsed_scale = 0;
ICEBERG_ASSIGN_OR_RAISE(
parsed_val, iceberg::Decimal::FromString(double_str, nullptr, &parsed_scale));
iceberg::Decimal rescaled_val;
ICEBERG_ASSIGN_OR_RAISE(rescaled_val,
parsed_val.Rescale(parsed_scale, decimal_type->scale()));
int32_t parsed_scale = 0;
ICEBERG_ASSIGN_OR_RAISE(
auto parsed_val, iceberg::Decimal::FromString(double_str, /*precision=*/nullptr, &parsed_scale));
iceberg::Decimal rescaled_val;
ICEBERG_ASSIGN_OR_RAISE(rescaled_val,
parsed_val.Rescale(parsed_scale, decimal_type->scale()));

Comment on lines 239 to 240
iceberg::Decimal dec_val;
ICEBERG_ASSIGN_OR_RAISE(dec_val, iceberg::Decimal::FromString(str_val));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
iceberg::Decimal dec_val;
ICEBERG_ASSIGN_OR_RAISE(dec_val, iceberg::Decimal::FromString(str_val));
ICEBERG_ASSIGN_OR_RAISE(auto dec_val, iceberg::Decimal::FromString(str_val));

auto binary_val = std::get<std::vector<uint8_t>>(literal.value_);
switch (target_type->type_id()) {
case TypeId::kFixed: {
auto target_fixed_type = std::static_pointer_cast<FixedType>(target_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto target_fixed_type = std::static_pointer_cast<FixedType>(target_type);
auto target_fixed_type = internal::checked_pointer_cast<FixedType>(target_type);

Comment on lines 468 to 410
auto unsupported_error = [this]() {
return std::format("ToString not supported for type: {}", type_->ToString());
};
auto invalid_argument = [this]() {
return std::format("Invalid argument for type: {}", type_->ToString());
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto unsupported_error = [this]() {
return std::format("ToString not supported for type: {}", type_->ToString());
};
auto invalid_argument = [this]() {
return std::format("Invalid argument for type: {}", type_->ToString());
};
auto invalid_literal = [this]() {
return std::format("Invalid literal of type: {}", type_->ToString());
};

This function will be called in printing an expression so it is better to print a consistent short message.

Comment on lines 531 to 534
if (str_res.has_value()) {
return str_res.value();
}
return invalid_argument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use std::expected.value_or to simplify this.

// Special value tests
TEST(LiteralTest, SpecialValues) {
// Same type cast tests
TEST(LiteralTest, SameTypeCast) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, please refactor this file to use parameterized test to use common code for testing different data types.

case TypeId::kTimestamp:
case TypeId::kTimestampTz: {
throw IcebergError("Not implemented: ToString for " + type_->ToString());
return std::to_string(std::get<int64_t>(value_));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these switch cases can be easily rewritten by return std::to_string(std::get<typename LiteralTraits<type_id>::ValueType>(value_)); once #185 is merged.

Comment on lines +405 to +408
auto unsupported_error = [this]() {
return std::format("ToString not supported for type: {}", type_->ToString());
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto unsupported_error = [this]() {
return std::format("ToString not supported for type: {}", type_->ToString());
};

}
default: {
throw IcebergError("Unknown type: " + type_->ToString());
return unsupported_error();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return unsupported_error();
return std::format("invalid literal of type {}", type_->ToString());

Comment on lines +30 to +31
#include "iceberg/util/decimal.h"
#include "iceberg/util/int128.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert these related lines?

std::string, // for string
std::vector<uint8_t>, // for binary, fixed
std::array<uint8_t, 16>, // for uuid and decimal
std::array<uint8_t, 16>, // for uuid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

ASSERT_THAT(min_result, IsOk());
EXPECT_TRUE(min_result->IsBelowMin());

max_result = max_long.CastTo(iceberg::date());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_result = max_long.CastTo(iceberg::date());
max_result = max_long.CastTo(date());

ASSERT_THAT(double_result, IsOk());
EXPECT_EQ(double_result->type()->type_id(), TypeId::kDouble);
AssertCastSucceeds(float_literal.CastTo(iceberg::float64()), TypeId::kDouble,
static_cast<double>(2.0f));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static_cast<double>(2.0f));
2.0);

auto min_double =
Literal::Double(-static_cast<double>(std::numeric_limits<float>::max()) * 2);

auto max_result = max_double.CastTo(iceberg::float32());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto max_result = max_double.CastTo(iceberg::float32());
auto max_result = max_double.CastTo(float32());

auto binary_literal = Literal::Binary(data4);

// Cast to Fixed with matching length
AssertCastSucceeds(binary_literal.CastTo(iceberg::fixed(4)), TypeId::kFixed, data4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AssertCastSucceeds(binary_literal.CastTo(iceberg::fixed(4)), TypeId::kFixed, data4);
AssertCastSucceeds(binary_literal.CastTo(fixed(4)), TypeId::kFixed, data4);

AssertCastSucceeds(binary_literal.CastTo(iceberg::fixed(4)), TypeId::kFixed, data4);

// Cast to Fixed with different length should fail
EXPECT_THAT(binary_literal.CastTo(iceberg::fixed(5)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EXPECT_THAT(binary_literal.CastTo(iceberg::fixed(5)),
EXPECT_THAT(binary_literal.CastTo(fixed(5)),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants