Skip to content

Commit 72cdd60

Browse files
ai: apply changes for #1006 (3 review threads)
Addresses: - #4418346548 at src/databricks/sql/parameters/native.py:696 - #4418346556 at src/databricks/sql/parameters/native.py:697 - #4418346563 at src/databricks/sql/parameters/native.py:655 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
1 parent d578b17 commit 72cdd60

3 files changed

Lines changed: 34 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Release History
22

33
# Unreleased
4-
- **Behaviour change:** `DecimalParameter` now binds `DECIMAL(precision, scale)`, matching SQL: `precision` is the total number of digits and `scale` the number of fractional digits (earlier docs described them the other way round). Calls written for the old convention (`scale > precision`, e.g. `scale=10, precision=2`) still bind `DECIMAL(10,2)` but emit a `DeprecationWarning`; swap your arguments, as this will raise `ValueError` in a future release. A declared `scale` equal to or less than `precision` is now interpreted with the SQL meaning, and a declared `scale` smaller than the value's own scale is widened so fractional digits are not lost.
4+
- **Behaviour change:** `DecimalParameter` now binds `DECIMAL(precision, scale)`, matching SQL: `precision` is the total number of digits and `scale` the number of fractional digits (earlier docs described them the other way round). Calls written for the old convention (`scale > precision`, e.g. `scale=10, precision=2`) still bind `DECIMAL(10,2)` but emit a `DeprecationWarning`; swap your arguments, as this will raise `ValueError` in a future release. A declared `scale` equal to or less than `precision` is now interpreted with the SQL meaning, and a declared `scale` smaller than the value's own scale is widened so fractional digits are not lost (up to the 38-digit DECIMAL maximum; a value needing more digits than that still has its excess fractional digits rounded).
55
- Preserve the originating session when re-fetching or concurrently polling async statements on the kernel backend.
66

77
# 4.8.0 (2026-10-24)

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,9 @@ def __init__(
623623
The `name` argument to this function would be `my_param`.
624624
:scale:
625625
The number of digits to the right of the decimal point. If the value itself
626-
has more fractional digits, the value's scale is used so no digits are lost.
626+
has more fractional digits, the value's scale is used so no digits are lost
627+
(unless the value needs more than 38 digits in total; the excess fractional
628+
digits are then rounded by the server).
627629
:precision:
628630
The minimum precision (total number of digits) of the number, between 1 and 38.
629631
If the value has more integer digits than `precision - scale` allows, or its
@@ -652,7 +654,9 @@ def __init__(
652654
"digits after the decimal point, as in SQL DECIMAL(precision, scale). "
653655
"Swap your arguments; this will raise ValueError in a future "
654656
"release.".format(self.scale, self.precision, self.scale, self.precision),
655-
DeprecationWarning,
657+
# FutureWarning (not DeprecationWarning) so end users see it by
658+
# default, not only when the call originates from __main__.
659+
FutureWarning,
656660
stacklevel=2,
657661
)
658662
self.scale, self.precision = self.precision, self.scale
@@ -676,9 +680,13 @@ def _cast_expr(self):
676680
MAX_PRECISION = 38
677681

678682
def _effective_precision_and_scale(self, precision: int, scale: int):
679-
"""Widen a declared DECIMAL(precision, scale) so the bound value never loses
683+
"""Widen a declared DECIMAL(precision, scale) so the bound value does not lose
680684
fractional digits: the value's own scale is authoritative over an
681685
under-declared target scale. Integer-digit room from the declared type is kept.
686+
687+
Exception: the total is capped at MAX_PRECISION (38) digits. If the value
688+
itself needs more than 38 digits (e.g. a scale over 38), the cast is clamped
689+
and the server rounds away the fractional digits that do not fit.
682690
"""
683691
try:
684692
sign, digits, exponent = self.value.as_tuple()
@@ -691,11 +699,22 @@ def _effective_precision_and_scale(self, precision: int, scale: int):
691699
value_scale = max(-exponent, 0)
692700
value_int_digits = max(len(digits) + exponent, 0)
693701

694-
effective_scale = max(scale, value_scale)
695-
int_digits = max(precision - scale, value_int_digits)
696-
effective_precision = min(int_digits + effective_scale, self.MAX_PRECISION)
697-
effective_scale = min(effective_scale, effective_precision)
698-
return effective_precision, effective_scale
702+
desired_scale = max(scale, value_scale)
703+
desired_int_digits = max(precision - scale, value_int_digits)
704+
705+
# Allocate the (at most MAX_PRECISION) digits by priority so that, when the
706+
# cap is hit, the value's own digits win over extra declared room
707+
# (similar to Spark's decimal precision adjustment):
708+
# 1. the value's integer digits, 2. the value's fractional digits,
709+
# 3. extra declared integer room, 4. extra declared fractional room.
710+
int_digits = min(value_int_digits, self.MAX_PRECISION)
711+
effective_scale = min(value_scale, self.MAX_PRECISION - int_digits)
712+
remaining = self.MAX_PRECISION - int_digits - effective_scale
713+
extra_int = min(desired_int_digits - int_digits, remaining)
714+
int_digits += extra_int
715+
remaining -= extra_int
716+
effective_scale += min(desired_scale - effective_scale, remaining)
717+
return int_digits + effective_scale, effective_scale
699718

700719
def calculate_decimal_cast_string(self, input: decimal.Decimal) -> str:
701720
"""Returns the smallest SQL cast argument that can contain the passed decimal

‎tests/unit/test_parameters.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ def test_calculate_decimal_cast_string(value, expected):
122122
(Decimal("0.123456789012345678901234567890123456"), 2, 10, "DECIMAL(38,36)"),
123123
# Widened scale is capped at the (capped) precision
124124
(Decimal("0.1234567890123456789012345678901234567890"), 2, 10, "DECIMAL(38,38)"),
125+
# When the cap is hit, the value's integer digits win and extra declared
126+
# scale shrinks (31 int + 1 frac digits fit losslessly in DECIMAL(38,7))
127+
(Decimal("1234567890123456789012345678901.5"), 8, 10, "DECIMAL(38,7)"),
128+
# Value fills all 38 digits: declared extra scale is dropped entirely
129+
(Decimal("1234567890123456789012345678901234567.8"), 8, 10, "DECIMAL(38,1)"),
125130
# NaN / Infinity leave the declared type untouched
126131
(Decimal("NaN"), 2, 10, "DECIMAL(10,2)"),
127132
(Decimal("Infinity"), 2, 10, "DECIMAL(10,2)"),
@@ -137,7 +142,7 @@ def test_decimal_explicit_precision_scale_cast_string(value, scale, precision, e
137142

138143
def test_decimal_scale_greater_than_precision_legacy_swapped():
139144
"""Callers using the old (swapped) convention keep working, with a deprecation warning."""
140-
with pytest.warns(DeprecationWarning, match="legacy"):
145+
with pytest.warns(FutureWarning, match="legacy"):
141146
p = DecimalParameter(Decimal("123.45"), scale=10, precision=2)
142147
assert p.precision == 10
143148
assert p.scale == 2

0 commit comments

Comments
 (0)