Skip to content

fix: reject DecimalType with precision outside [1, 38]#3584

Open
gitcommit90 wants to merge 1 commit into
apache:mainfrom
gitcommit90:fix/issue-3583-decimal-precision-validation
Open

fix: reject DecimalType with precision outside [1, 38]#3584
gitcommit90 wants to merge 1 commit into
apache:mainfrom
gitcommit90:fix/issue-3583-decimal-precision-validation

Conversation

@gitcommit90

Copy link
Copy Markdown

The Iceberg spec states precision must be 38 or less (minimum 1). The Python library silently accepted any precision value; DecimalType(39, 0) succeeded with no error, which can corrupt downstream fixed-byte decimal encoding.

The Java reference implementation rejects it: DecimalType.of(39, 0) raises IllegalArgumentException.

Fix: Add a 2-line guard in DecimalType.__init__ that raises pyiceberg.exceptions.ValidationError when precision < 1 or precision > 38. Uses the same exception class and pattern already used throughout pyiceberg/types.py.

Tests added (4 new):

  • test_decimal_precision_above_38_raises — precision=39 raises
  • test_decimal_precision_zero_raises — precision=0 raises
  • test_decimal_precision_boundary_38_accepted — precision=38 accepted
  • test_decimal_deserialization_precision_above_38_raises — deserialization path also raises

All 292 existing tests pass; ruff clean.

closes #3583

The Iceberg spec states precision must be 38 or less [1]. The Python
implementation silently accepted any precision, including values like
decimal(39, 0) that cannot be represented in decimal fixed-byte storage.

This aligns the Python library with the Java reference implementation
which raises IllegalArgumentException for precision > 38.

Changes:
- Add a validation check in DecimalType.__init__ that raises
  ValidationError when precision < 1 or precision > 38
- Add four tests covering: precision=39 (direct), precision=0 (direct),
  precision=38 (boundary, must pass), and precision=39 via deserialization

Fixes apache#3583

[1] https://iceberg.apache.org/spec/#primitive-types
Comment thread tests/test_types.py

def test_decimal_deserialization_precision_above_38_raises() -> None:
"""Deserialization of decimal(39, 0) must also raise a ValidationError."""
with pytest.raises(Exception, match="Precision must be between 1 and 38"):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
with pytest.raises(Exception, match="Precision must be between 1 and 38"):
with pytest.raises(ValidationError, match="Precision must be between 1 and 38"):

Comment thread pyiceberg/types.py

def __init__(self, precision: int, scale: int) -> None:
if not (1 <= precision <= 38):
raise ValidationError(f"Precision must be between 1 and 38 (inclusive), got: {precision}")

@ebyhr ebyhr Jun 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

model_copy still misses this guard. The following code doesn't raise the exception:

DecimalType(10, 2).model_copy(update={"root": (39, 0)})

Could you consider using @model_validator(mode="after")?

Comment thread tests/test_types.py
Comment on lines +531 to +533
"""Precision 0 is not valid; minimum precision is 1."""
with pytest.raises(ValidationError, match="Precision must be between 1 and 38"):
DecimalType(0, 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: We could remove the code comment. It's obvious when reading the code.

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.

Decimal Should Reject precision > 38

2 participants