This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-3997-e3fbd9440ad0bb25c45848797ff9ec5ad7758624 in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
commit 16cbef094012efef4ce990675664f25ad5bb24db Author: Rodrigo Stachlewski Palma <[email protected]> AuthorDate: Thu Oct 22 21:08:01 2026 +0000 fix(conversions): reject a Decimal whose scale is negative (#3997) to_bytes compared abs(exponent) against the type scale, so a Decimal carrying a negative scale passed as if it had the matching positive one. The exponent is then dropped by decimal_to_unscaled, and the value is written four orders of magnitude off without an error: Decimal('1E+2') stored in a decimal(10, 2) reads back as 0.01. Decimal('100').normalize() produces exactly that form, and to_bytes writes the lower and upper bounds of a data file, so a wrong bound prunes files that do hold matching rows. Compare the signed scale instead. --- pyiceberg/conversions.py | 8 +++++--- tests/test_conversions.py | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyiceberg/conversions.py b/pyiceberg/conversions.py index 6dd964f43..fde188544 100644 --- a/pyiceberg/conversions.py +++ b/pyiceberg/conversions.py @@ -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}") 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)}" diff --git a/tests/test_conversions.py b/tests/test_conversions.py index e786ae068..322e58a9b 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -511,6 +511,13 @@ def test_raise_on_unregistered_type() -> None: "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"),
