You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by fo...@apache.org on 2026/07/18 20:23:51 UTC

(iceberg-python) branch main updated: fix: raise `ValidationError` for decimal precision outside range (#3585)

This is an automated email from the ASF dual-hosted git repository.

Fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git


The following commit(s) were added to refs/heads/main by this push:
     new c2093328c fix: raise `ValidationError` for decimal precision outside range (#3585)
c2093328c is described below

commit c2093328cf9fdf0d7d1da485e9f3b0681cc2ed05
Author: Platon G. Gimp <pl...@gmail.com>
AuthorDate: Sun Jul 19 01:23:46 2026 +0500

    fix: raise `ValidationError` for decimal precision outside range (#3585)
    
    Closes #3583
    
    # Rationale for this change
    According to the Iceberg specification, decimal precision must be
    between 1 and 38. Previously, `pyiceberg` silently accepted invalid
    values (such as `precision=39` or `0`), which could lead to data
    corruption or crashes downstream when encoding/decoding fixed-byte
    decimals (matching the Java implementation's
    `IllegalArgumentException`).
    
    This PR adds a defensive validation guard into `DecimalType.__init__` to
    raise a `ValidationError` when the precision is out of bounds `[1, 38]`.
    
    Additionally, fixed an existing invalid type definition
    (`DecimalType(100, 2)`) inside `tests/test_schema.py` which was
    violating the specification bounds and causing the new validation guard
    to trip during the integration test suite.
    
    ## Are these changes tested?
    Yes, added dedicated unit tests in `tests/test_types.py`:
    - `test_decimal_precision_validation` covers upper bounds
    (`precision=39`), lower bounds (`precision=0`, `precision=-5`), and
    ensures valid boundary values (`precision=38`) work perfectly.
    
    Also updated existing test assertions in `tests/test_schema.py` to use a
    compliant precision value (`38`).
    
    ## Are there any user-facing changes?
    Yes, creating a `DecimalType` with an invalid precision will now
    explicitly raise a `pyiceberg.exceptions.ValidationError` instead of
    failing silently at runtime.
---
 pyiceberg/types.py   |  8 ++++++++
 tests/test_schema.py |  2 +-
 tests/test_types.py  | 15 +++++++++++++++
 3 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/pyiceberg/types.py b/pyiceberg/types.py
index 3c9821536..fd1d8fc0a 100644
--- a/pyiceberg/types.py
+++ b/pyiceberg/types.py
@@ -326,6 +326,14 @@ class DecimalType(PrimitiveType):
     def __init__(self, precision: int, scale: int) -> None:
         super().__init__(root=(precision, scale))
 
+    @model_validator(mode="after")
+    def check_precision(self) -> DecimalType:
+        precision = getattr(self, "precision", None) or self.root[0]
+
+        if not (1 <= precision <= 38):
+            raise ValidationError(f"Decimal precision must be between 1 and 38 (inclusive), got: {precision}")
+        return self
+
     @model_serializer
     def ser_model(self) -> str:
         """Serialize the model to a string."""
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 93ddc1620..aacdbc7d5 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -67,7 +67,7 @@ TEST_PRIMITIVE_TYPES = [
     FloatType(),
     DoubleType(),
     DecimalType(10, 2),
-    DecimalType(100, 2),
+    DecimalType(38, 2),
     StringType(),
     DateType(),
     TimeType(),
diff --git a/tests/test_types.py b/tests/test_types.py
index 5e95687ba..375fb9cdb 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -920,3 +920,18 @@ def test_nested_field_geography_with_params_as_string() -> None:
     assert isinstance(field.field_type, GeographyType)
     assert field.field_type.crs == "EPSG:4326"
     assert field.field_type.algorithm == "planar"
+
+
+def test_decimal_precision_validation() -> None:
+    """Test that DecimalType rejects precision outside the [1, 38] range."""
+    decimal_type = DecimalType(38, 2)
+    assert decimal_type.precision == 38
+
+    with pytest.raises(ValidationError, match="Decimal precision must be between 1 and 38"):
+        DecimalType(39, 2)
+
+    with pytest.raises(ValidationError, match="Decimal precision must be between 1 and 38"):
+        DecimalType(0, 2)
+
+    with pytest.raises(ValidationError, match="Decimal precision must be between 1 and 38"):
+        DecimalType(-5, 2)