Skip to content

Commit 0472626

Browse files
committed
Fix DECIMAL parameter cast size for exponent-notation and negative values
DecimalParameter.calculate_decimal_cast_string inferred DECIMAL(precision, scale) by string-splitting str(value) on ".", which miscounts two forms that str(Decimal) legitimately produces: - exponent notation (Decimal("1500").normalize() -> "1.5E+3"): the "E+3" was counted as fractional digits, giving DECIMAL(5,4) for 1500, which overflows (an error in ANSI mode, NULL in legacy mode). - a leading minus sign: counted as an extra integer digit, so a negative 38-digit value produced DECIMAL(39,0), exceeding Databricks' max DECIMAL precision of 38 and failing the cast. Derive precision/scale from Decimal.as_tuple() (digits + exponent), ignoring sign and display format. Existing cast-string tests are unchanged; added regression cases for exponent notation and negative values. Signed-off-by: Madan Kumar <winklemad@outlook.com>
1 parent 70427d7 commit 0472626

2 files changed

Lines changed: 18 additions & 15 deletions

File tree

‎src/databricks/sql/parameters/native.py‎

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -651,23 +651,20 @@ def calculate_decimal_cast_string(self, input: decimal.Decimal) -> str:
651651
Output: DECIMAL(8,4)
652652
"""
653653

654-
string_decimal = str(input)
655-
656-
if string_decimal.startswith("0."):
657-
# This decimal is less than 1
658-
overall = after = len(string_decimal) - 2
659-
elif "." not in string_decimal:
660-
# This decimal has no fractional component
661-
overall = len(string_decimal)
662-
after = 0
654+
# Derive precision/scale from the exact numeric value rather than str(),
655+
# whose sign and exponent notation (e.g. "1.5E+3", "-12.34") would be
656+
# miscounted as extra digits.
657+
_, digits, exponent = input.as_tuple()
658+
if exponent >= 0:
659+
# Integer value: `digits` followed by `exponent` trailing zeros.
660+
scale = 0
661+
precision = len(digits) + exponent
663662
else:
664-
# This decimal has both whole and fractional parts
665-
parts = string_decimal.split(".")
666-
parts_lengths = [len(i) for i in parts]
667-
before, after = parts_lengths[:2]
668-
overall = before + after
663+
scale = -exponent
664+
# A value < 1 still needs `scale` digits of precision.
665+
precision = max(len(digits), scale)
669666

670-
return self.CAST_EXPR.format(overall, after)
667+
return self.CAST_EXPR.format(precision, scale)
671668

672669

673670
def dbsql_parameter_from_int(value: int, name: Optional[str] = None):

‎tests/unit/test_parameters.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ def test_parameters_enabled(self, test_input, expected):
100100
(Decimal("123456789.123456789"), "DECIMAL(18,9)"),
101101
(Decimal("12345678912345678912345678912345678912"), "DECIMAL(38,0)"),
102102
(Decimal("1234.56"), "DECIMAL(6,2)"),
103+
# Exponent notation (e.g. from Decimal.normalize()) and negative values
104+
# must be sized from the numeric value, not str().
105+
(Decimal("1500").normalize(), "DECIMAL(4,0)"),
106+
(Decimal("1e5"), "DECIMAL(6,0)"),
107+
(Decimal("-" + "9" * 38), "DECIMAL(38,0)"),
108+
(Decimal("-12.34"), "DECIMAL(4,2)"),
103109
),
104110
)
105111
def test_calculate_decimal_cast_string(value, expected):

0 commit comments

Comments
 (0)