Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pyiceberg/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,11 @@ def _(primitive_type: DecimalType, value: Decimal) -> bytes:
bytes: The byte representation of `value`.
"""
_, digits, exponent = value.as_tuple()
exponent = abs(int(exponent))
if exponent != primitive_type.scale:
raise ValueError(f"Cannot serialize value, scale of value does not match type {primitive_type}: {exponent}")
# A Decimal carries the negated scale as its exponent, so a value with a negative
# scale (1E+2) must not be read as if it had the matching positive one
value_scale = -int(exponent)
if value_scale != primitive_type.scale:
raise ValueError(f"Cannot serialize value, scale of value does not match type {primitive_type}: {value_scale}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: I don't love this error message (decimal scale isn't always a term that people are immediately familiar with), but I think it'll be fine.

elif len(digits) > primitive_type.precision:
raise ValueError(
f"Cannot serialize value, precision of value is greater than precision of type {primitive_type}: {len(digits)}"
Expand Down
4 changes: 2 additions & 2 deletions pyiceberg/expressions/literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def to(self, type_var: IcebergType) -> Literal: # type: ignore

@to.register(DecimalType)
def _(self, type_var: DecimalType) -> Literal[Decimal]:
if type_var.scale == abs(int(self.value.as_tuple().exponent)):
if type_var.scale == -int(self.value.as_tuple().exponent):
return self
raise ValueError(f"Could not convert {self.value} into a {type_var}")

Expand Down Expand Up @@ -628,7 +628,7 @@ def _(self, _: UUIDType) -> Literal[bytes]:
@to.register(DecimalType)
def _(self, type_var: DecimalType) -> Literal[Decimal]:
dec = Decimal(self.value)
scale = abs(int(dec.as_tuple().exponent))
scale = -int(dec.as_tuple().exponent)
if type_var.scale == scale:
return DecimalLiteral(dec)
else:
Expand Down
6 changes: 6 additions & 0 deletions tests/expressions/test_literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ def test_decimal_to_decimal_conversion() -> None:
with pytest.raises(ValueError) as e:
_ = lit.to(DecimalType(9, 3))
assert "Could not convert 34.11 into a decimal(9, 3)" in str(e.value)
with pytest.raises(ValueError) as e:
_ = literal(Decimal("1E+2")).to(DecimalType(9, 2))
assert "Could not convert 1E+2 into a decimal(9, 2)" in str(e.value)


def test_timestamp_to_date() -> None:
Expand Down Expand Up @@ -947,6 +950,9 @@ def test_string_to_decimal_type_invalid_value() -> None:
with pytest.raises(ValueError) as e:
_ = literal("18.15").to(DecimalType(10, 0))
assert "Could not convert 18.15 into a decimal(10, 0), scales differ 0 <> 2" in str(e.value)
with pytest.raises(ValueError) as e:
_ = literal("1E+2").to(DecimalType(10, 2))
assert "Could not convert 1E+2 into a decimal(10, 2), scales differ 2 <> -2" in str(e.value)


def test_decimal_literal_increment() -> None:
Expand Down
7 changes: 7 additions & 0 deletions tests/test_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,13 @@ def __repr__(self) -> str:
"primitive_type, value, expected_error_message",
[
(DecimalType(7, 3), Decimal("123.4567"), "Cannot serialize value, scale of value does not match type decimal(7, 3): 4"),
# A Decimal with a negative scale must not pass as its positive counterpart
(DecimalType(10, 2), Decimal("1E+2"), "Cannot serialize value, scale of value does not match type decimal(10, 2): -2"),
(
DecimalType(10, 2),
Decimal("100").normalize(),
"Cannot serialize value, scale of value does not match type decimal(10, 2): -2",
),
(
DecimalType(18, 8),
Decimal("123456789.123456789"),
Expand Down
Loading