@@ -640,13 +640,36 @@ def __init__(
640640 self .name = name
641641 self .scale = scale
642642 self .precision = precision
643+ self ._legacy_swapped = False
643644
644645 if not self .valid_scale_and_precision ():
645646 raise ValueError (
646647 "DecimalParameter requires both or none of scale and precision to be set, "
647648 "as integers"
648649 )
649650
651+ if self .scale is not None and self .precision is not None :
652+ # Range-check the raw arguments *before* the legacy swap below, so a call
653+ # that is simply invalid (rather than written for the legacy convention)
654+ # gets an error naming the argument the caller actually passed, and no
655+ # misleading migration warning.
656+ if self .scale < 0 :
657+ raise ValueError (
658+ "DecimalParameter scale must be >= 0, got {}" .format (self .scale )
659+ )
660+ if self .precision < 0 or self .precision > self .MAX_PRECISION :
661+ raise ValueError (
662+ "DecimalParameter precision must be between 1 and {}, got {}" .format (
663+ self .MAX_PRECISION , self .precision
664+ )
665+ )
666+ if self .scale > self .MAX_PRECISION :
667+ raise ValueError (
668+ "DecimalParameter scale must be between 0 and {}, got {}" .format (
669+ self .MAX_PRECISION , self .scale
670+ )
671+ )
672+
650673 if (
651674 self .scale is not None
652675 and self .precision is not None
@@ -663,7 +686,10 @@ def __init__(
663686 "`precision` is the total number of digits and `scale` is the number of "
664687 "digits after the decimal point, as in SQL DECIMAL(precision, scale). "
665688 "Swap your arguments; this will raise ValueError in a future "
666- "release." .format (
689+ "release. Legacy-convention calls keep the previous behaviour (the "
690+ "server rounds the value to the declared scale); with correctly "
691+ "ordered arguments the bound scale is widened to fit the value's "
692+ "fractional digits instead." .format (
667693 self .scale , self .precision , self .scale , self .precision
668694 ),
669695 # FutureWarning (not DeprecationWarning) so end users see it by
@@ -672,6 +698,9 @@ def __init__(
672698 stacklevel = 2 ,
673699 )
674700 self .scale , self .precision = self .precision , self .scale
701+ # Preserve the old bound type exactly (no widening) for these callers,
702+ # so the only change they see is the warning.
703+ self ._legacy_swapped = True
675704
676705 if self .scale is not None and self .precision is not None :
677706 if self .scale < 0 :
@@ -700,6 +729,10 @@ def _is_int(x):
700729
701730 def _cast_expr (self ):
702731 if self .scale is not None and self .precision is not None :
732+ if self ._legacy_swapped :
733+ # Legacy-convention call: bind the declared type unchanged, as
734+ # earlier releases did, so existing rounding is preserved.
735+ return self .CAST_EXPR .format (self .precision , self .scale )
703736 precision , scale = self ._effective_precision_and_scale (
704737 self .precision , self .scale
705738 )
0 commit comments