Skip to content

Commit 5e09830

Browse files
ai: apply changes for #1006 (3 review threads)
Addresses: - #4418237463 at src/databricks/sql/parameters/native.py:642 - #4418237473 at src/databricks/sql/parameters/native.py:650 - #4418237477 at tests/unit/test_parameters.py:113 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
1 parent 8c170bf commit 5e09830

4 files changed

Lines changed: 48 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +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). Passing `scale > precision` now raises `ValueError`, and a declared `scale` smaller than the value's own scale is widened so fractional digits are not lost.
45
- Preserve the originating session when re-fetching or concurrently polling async statements on the kernel backend.
56

67
# 4.8.0 (2026-10-24)

‎examples/parameters.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,11 @@
107107
# will infer the Decimal to be a DECIMAL and will preserve its current precision and scale.
108108
ex4_p3 = Decimal("12.3456")
109109

110-
# For this parameter value, we bind a decimal with custom scale and precision
111-
# that will result in the decimal being truncated.
112-
ex4_p4 = DecimalParameter(value=ex4_p3, scale=4, precision=2)
110+
# For this parameter value, we bind a decimal with custom precision and scale,
111+
# i.e. DECIMAL(precision, scale). The declared scale is a minimum: if the value has
112+
# more fractional digits, the bound type is widened so no digits are lost. Here
113+
# DECIMAL(10,2) is widened to DECIMAL(12,4) and 12.3456 round-trips unchanged.
114+
ex4_p4 = DecimalParameter(value=ex4_p3, precision=10, scale=2)
113115

114116
EX4_QUERY = "SELECT ? `p1`, ? `p2`, ? `p3`, ? `p4`"
115117
EX4_PARAMS = [ex4_p1, ex4_p2, ex4_p3, ex4_p4]

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,12 @@ def __init__(
600600
If set, `scale` and `precision` must both be set. If neither is set, the value
601601
will be casted to the smallest possible DECIMAL type that can contain it.
602602
603+
When set, `precision` and `scale` declare a *minimum* DECIMAL(precision, scale)
604+
type: if the value needs more fractional or integer digits than declared, the
605+
bound type is widened to fit it (capped at precision 38) so no digits are lost.
606+
The value is never rounded or truncated to a narrower declared type; round the
607+
`Decimal` yourself (e.g. with `Decimal.quantize`) before binding if you need that.
608+
603609
:value:
604610
The value to bind for this parameter. This will be casted to a DECIMAL.
605611
:name:
@@ -618,7 +624,9 @@ def __init__(
618624
The number of digits to the right of the decimal point. If the value itself
619625
has more fractional digits, the value's scale is used so no digits are lost.
620626
:precision:
621-
The maximum precision (total number of digits) of the number between 1 and 38.
627+
The minimum precision (total number of digits) of the number, between 1 and 38.
628+
If the value has more integer digits than `precision - scale` allows, or its
629+
scale is widened, the precision grows to fit it (up to 38).
622630
"""
623631
self.value: decimal.Decimal = value
624632
self.name = name
@@ -630,6 +638,18 @@ def __init__(
630638
"DecimalParameter requires both or none of scale and precision to be set"
631639
)
632640

641+
if self.scale is not None and self.precision is not None and self.scale > self.precision:
642+
# Earlier releases documented these arguments the other way round
643+
# (scale = total digits, precision = fractional digits). Fail loudly
644+
# instead of silently reinterpreting a call written that way.
645+
raise ValueError(
646+
"DecimalParameter scale ({}) must not exceed precision ({}). `precision` is "
647+
"the total number of digits and `scale` is the number of digits after the "
648+
"decimal point, as in SQL DECIMAL(precision, scale).".format(
649+
self.scale, self.precision
650+
)
651+
)
652+
633653
def valid_scale_and_precision(self):
634654
if (self.scale is None and self.precision is None) or (
635655
isinstance(self.scale, int) and isinstance(self.precision, int)

‎tests/unit/test_parameters.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ def test_calculate_decimal_cast_string(value, expected):
112112
(
113113
(Decimal("123.45"), 2, 10, "DECIMAL(10,2)"),
114114
(Decimal("123"), 0, 10, "DECIMAL(10,0)"),
115+
# Value scale exceeds declared scale: scale widens, declared int room (8) is kept
116+
(Decimal("123.4567"), 2, 10, "DECIMAL(12,4)"),
117+
# Value has more integer digits than declared precision - scale allows
118+
(Decimal("123456789012.5"), 2, 10, "DECIMAL(14,2)"),
119+
# Positive exponent: 1E+3 needs 4 integer digits, no fractional digits
120+
(Decimal("1E+3"), 0, 2, "DECIMAL(4,0)"),
121+
# Widened precision is capped at 38
122+
(Decimal("0.123456789012345678901234567890123456"), 2, 10, "DECIMAL(38,36)"),
123+
# Widened scale is capped at the (capped) precision
124+
(Decimal("0.1234567890123456789012345678901234567890"), 2, 10, "DECIMAL(38,38)"),
125+
# NaN / Infinity leave the declared type untouched
126+
(Decimal("NaN"), 2, 10, "DECIMAL(10,2)"),
127+
(Decimal("Infinity"), 2, 10, "DECIMAL(10,2)"),
128+
# Non-Decimal values (no as_tuple) fall back to the declared type
129+
(1.5, 2, 10, "DECIMAL(10,2)"),
115130
),
116131
)
117132
def test_decimal_explicit_precision_scale_cast_string(value, scale, precision, expected):
@@ -120,6 +135,12 @@ def test_decimal_explicit_precision_scale_cast_string(value, scale, precision, e
120135
assert p._cast_expr() == expected
121136

122137

138+
def test_decimal_scale_greater_than_precision_rejected():
139+
"""Callers using the old (swapped) argument convention must fail loudly."""
140+
with pytest.raises(ValueError, match="must not exceed precision"):
141+
DecimalParameter(Decimal("12.3456"), scale=4, precision=2)
142+
143+
123144
class Primitive(Enum):
124145
"""These are the inferrable types. This Enum is used for parametrized tests."""
125146

0 commit comments

Comments
 (0)