From ff84fb3214543a8a10aa1f015466ccbbced2c8d3 Mon Sep 17 00:00:00 2001 From: Abanoub Doss Date: Tue, 23 Jun 2026 09:23:58 -0500 Subject: [PATCH 1/3] Fix decimal type-promotion scale check (self-comparison tautology) promote(DecimalType, ...) compared file_type.scale == file_type.scale (always True) instead of file_type.scale == read_type.scale, allowing update_column() to accept a forbidden decimal scale change. A scale decrease then makes scans crash with ArrowInvalid (table unreadable); a scale increase produces spec-violating metadata. Matches Java TypeUtil.isPromotionAllowed (scale must stay fixed, precision may widen). --- pyiceberg/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/schema.py b/pyiceberg/schema.py index fd60eb8f94..a79b3ae95f 100644 --- a/pyiceberg/schema.py +++ b/pyiceberg/schema.py @@ -1697,7 +1697,7 @@ def _(file_type: BinaryType, read_type: IcebergType) -> IcebergType: @promote.register(DecimalType) def _(file_type: DecimalType, read_type: IcebergType) -> IcebergType: if isinstance(read_type, DecimalType): - if file_type.precision <= read_type.precision and file_type.scale == file_type.scale: + if file_type.precision <= read_type.precision and file_type.scale == read_type.scale: return read_type else: raise ResolveError(f"Cannot reduce precision from {file_type} to {read_type}") From a88dbb51ccd5d8c42373e19de9724748825e74a2 Mon Sep 17 00:00:00 2001 From: Abanoub Doss Date: Tue, 23 Jun 2026 20:08:24 -0500 Subject: [PATCH 2/3] Add test for decimal scale-change promotion rejection --- tests/test_schema.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_schema.py b/tests/test_schema.py index 93ddc16202..c4d678e6ce 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1316,6 +1316,15 @@ def test_type_promote_decimal_to_fixed_scale_with_wider_precision(table_v2: Tabl assert decimal_type.scale == 1 +def test_detect_invalid_promotion_decimal_scale_change(table_v2: Table) -> None: + # Per the Iceberg spec, decimal scale is fixed; only precision may grow. + current_schema = Schema(NestedField(field_id=1, name="aCol", field_type=DecimalType(precision=20, scale=1), required=False)) + new_schema = Schema(NestedField(field_id=1, name="aCol", field_type=DecimalType(precision=22, scale=2), required=False)) + + with pytest.raises(ValidationError, match="Cannot change column type: aCol: decimal.20, 1. -> decimal.22, 2."): + _ = UpdateSchema(transaction=Transaction(table_v2), schema=current_schema).union_by_name(new_schema)._apply() + + def test_add_nested_structs(primitive_fields: NestedField, table_v2: Table) -> None: schema = Schema( NestedField( From d97dd51ce1a770fbff8d2966e8e9d4163bc7c227 Mon Sep 17 00:00:00 2001 From: Abanoub Doss Date: Tue, 23 Jun 2026 20:12:25 -0500 Subject: [PATCH 3/3] Fix same scale tautology in test helper and cover decimal scale mismatch --- tests/test_schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index c4d678e6ce..e9282c0f87 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -68,6 +68,7 @@ DoubleType(), DecimalType(10, 2), DecimalType(100, 2), + DecimalType(10, 3), StringType(), DateType(), TimeType(), @@ -878,7 +879,7 @@ def should_promote(file_type: IcebergType, read_type: IcebergType) -> bool: if isinstance(file_type, BinaryType) and isinstance(read_type, StringType): return True if isinstance(file_type, DecimalType) and isinstance(read_type, DecimalType): - return file_type.precision <= read_type.precision and file_type.scale == file_type.scale + return file_type.precision <= read_type.precision and file_type.scale == read_type.scale if isinstance(file_type, FixedType) and isinstance(read_type, UUIDType) and len(file_type) == 16: return True return False