Skip to content

Commit 81300e6

Browse files
committed
feat(expression): cast integer literals to decimal type
1 parent 0825f56 commit 81300e6

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

src/iceberg/expression/literal.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ class LiteralCaster {
8585
/// Cast from Fixed type to target type.
8686
static Result<Literal> CastFromFixed(const Literal& literal,
8787
const std::shared_ptr<PrimitiveType>& target_type);
88+
89+
/// Cast an integer value (from Int or Long) to a decimal target type.
90+
static Result<Literal> CastIntegerToDecimal(
91+
int64_t value, const std::shared_ptr<PrimitiveType>& target_type);
8892
};
8993

9094
Literal LiteralCaster::BelowMinLiteral(std::shared_ptr<PrimitiveType> type) {
@@ -95,6 +99,20 @@ Literal LiteralCaster::AboveMaxLiteral(std::shared_ptr<PrimitiveType> type) {
9599
return Literal(Literal::AboveMax{}, std::move(type));
96100
}
97101

102+
Result<Literal> LiteralCaster::CastIntegerToDecimal(
103+
int64_t value, const std::shared_ptr<PrimitiveType>& target_type) {
104+
const auto& decimal_type = internal::checked_cast<const DecimalType&>(*target_type);
105+
// An integer has scale 0, so scaling it to the target scale multiplies the unscaled
106+
// value by 10^scale; Rescale rejects a target scale that would overflow the value.
107+
ICEBERG_ASSIGN_OR_RAISE(auto decimal, Decimal(value).Rescale(0, decimal_type.scale()));
108+
if (!decimal.FitsInPrecision(decimal_type.precision())) {
109+
return InvalidArgument("Cannot cast {} as a {} value", value,
110+
target_type->ToString());
111+
}
112+
return Literal::Decimal(decimal.value(), decimal_type.precision(),
113+
decimal_type.scale());
114+
}
115+
98116
Result<Literal> LiteralCaster::CastFromInt(
99117
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
100118
auto int_val = std::get<int32_t>(literal.value_);
@@ -109,6 +127,8 @@ Result<Literal> LiteralCaster::CastFromInt(
109127
return Literal::Double(static_cast<double>(int_val));
110128
case TypeId::kDate:
111129
return Literal::Date(int_val);
130+
case TypeId::kDecimal:
131+
return CastIntegerToDecimal(static_cast<int64_t>(int_val), target_type);
112132
default:
113133
return NotSupported("Cast from Int to {} is not implemented",
114134
target_type->ToString());
@@ -153,6 +173,8 @@ Result<Literal> LiteralCaster::CastFromLong(
153173
return Literal::TimestampNs(long_val);
154174
case TypeId::kTimestampTzNs:
155175
return Literal::TimestampTzNs(long_val);
176+
case TypeId::kDecimal:
177+
return CastIntegerToDecimal(long_val, target_type);
156178
default:
157179
return NotSupported("Cast from Long to {} is not supported",
158180
target_type->ToString());

src/iceberg/test/literal_test.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ TEST(LiteralTest, DoubleCastToOverflow) {
150150
EXPECT_TRUE(min_result->IsBelowMin());
151151
}
152152

153+
TEST(LiteralTest, IntegerCastToDecimal) {
154+
// An integer default is scaled up to the target decimal scale: 12 -> 12.00 keeps the
155+
// unscaled value 1200 at precision/scale (9, 2).
156+
auto int_result = Literal::Int(12).CastTo(decimal(9, 2));
157+
ASSERT_THAT(int_result, IsOk());
158+
EXPECT_EQ(*int_result, Literal::Decimal(1200, 9, 2));
159+
160+
auto long_result = Literal::Long(int64_t{12}).CastTo(decimal(18, 3));
161+
ASSERT_THAT(long_result, IsOk());
162+
EXPECT_EQ(*long_result, Literal::Decimal(12000, 18, 3));
163+
164+
// A value whose scaled form needs more digits than the target precision is rejected.
165+
EXPECT_THAT(Literal::Int(12345).CastTo(decimal(4, 0)),
166+
IsError(ErrorKind::kInvalidArgument));
167+
EXPECT_THAT(Literal::Long(int64_t{100}).CastTo(decimal(4, 2)),
168+
IsError(ErrorKind::kInvalidArgument));
169+
}
170+
153171
// Error cases for casts
154172
TEST(LiteralTest, CastToError) {
155173
std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04};

0 commit comments

Comments
 (0)