|
8 | 8 | SQL_NUMERIC using its own precision and scale, regardless of value. Binding no longer |
9 | 9 | depends on whether the value falls in the MONEY/SMALLMONEY range, so an in-range value |
10 | 10 | compared against a smaller numeric column returns no match instead of a varchar->numeric |
11 | | -overflow (GH-740). executemany still string-binds Decimals (SQL_VARCHAR) to preserve |
12 | | -scale-38 precision (GH-503), so that path is unchanged here. |
| 11 | +overflow (GH-740). executemany string-binds Decimals to preserve scale-38 precision |
| 12 | +(GH-503); a money-range batch is additionally re-declared as SQL_NUMERIC with batch-wide |
| 13 | +scale so the server does not overflow when coercing the strings (GH-745). |
13 | 14 | """ |
14 | 15 |
|
15 | 16 | import pytest |
@@ -528,6 +529,69 @@ def test_executemany_money_smallmoney(cursor, db_connection): |
528 | 529 | db_connection.commit() |
529 | 530 |
|
530 | 531 |
|
| 532 | +class _ParamInfo: |
| 533 | + """Duck-typed stand-in for ddbc_bindings.ParamInfo (no C extension needed).""" |
| 534 | + |
| 535 | + def __init__(self, sql_type): |
| 536 | + self.paramSQLType = sql_type |
| 537 | + self.decimalDigits = 0 |
| 538 | + self.columnSize = 0 |
| 539 | + |
| 540 | + |
| 541 | +def _bare_cursor(): |
| 542 | + """A Cursor instance that skips __init__ (no DB connection required).""" |
| 543 | + from mssql_python.cursor import Cursor |
| 544 | + |
| 545 | + return Cursor.__new__(Cursor) |
| 546 | + |
| 547 | + |
| 548 | +def test_money_batch_redeclared_as_numeric(): |
| 549 | + """GH-745: a money-range Decimal batch must declare SQL_NUMERIC (not the |
| 550 | + VARCHAR shortcut), with decimalDigits = batch-wide max scale, so the server |
| 551 | + does not overflow converting the strings. Mirrors the GH-740 fix on |
| 552 | + execute(); the string binding (GH-503) is applied separately downstream.""" |
| 553 | + from mssql_python.constants import ConstantsDDBC as ddbc |
| 554 | + |
| 555 | + cur = _bare_cursor() |
| 556 | + varchar = ddbc.SQL_VARCHAR.value |
| 557 | + numeric = ddbc.SQL_NUMERIC.value |
| 558 | + |
| 559 | + # In-range batch, mixed scales -> redeclared NUMERIC with max scale 4 |
| 560 | + p = _ParamInfo(varchar) |
| 561 | + assert cur._apply_money_batch_declared_type( |
| 562 | + p, [Decimal("12345.6789"), Decimal("-0.0001"), None] |
| 563 | + ) is True |
| 564 | + assert p.paramSQLType == numeric |
| 565 | + assert p.decimalDigits == 4 |
| 566 | + |
| 567 | + # Integer-valued money -> scale 0 |
| 568 | + p = _ParamInfo(varchar) |
| 569 | + assert cur._apply_money_batch_declared_type(p, [Decimal("100"), Decimal("-2")]) is True |
| 570 | + assert p.paramSQLType == numeric |
| 571 | + assert p.decimalDigits == 0 |
| 572 | + |
| 573 | + # Above MONEY_MAX -> unchanged (existing NUMERIC path handles it) |
| 574 | + p = _ParamInfo(varchar) |
| 575 | + assert cur._apply_money_batch_declared_type(p, [Decimal("9999999999999999")]) is False |
| 576 | + assert p.paramSQLType == varchar |
| 577 | + |
| 578 | + # Mixed Decimal + str -> unchanged (not a pure Decimal column) |
| 579 | + p = _ParamInfo(varchar) |
| 580 | + assert cur._apply_money_batch_declared_type(p, [Decimal("1.5"), "2.5"]) is False |
| 581 | + assert p.paramSQLType == varchar |
| 582 | + |
| 583 | + # All-NULL column -> unchanged (nothing to type) |
| 584 | + p = _ParamInfo(varchar) |
| 585 | + assert cur._apply_money_batch_declared_type(p, [None, None]) is False |
| 586 | + assert p.paramSQLType == varchar |
| 587 | + |
| 588 | + # Already declared non-VARCHAR -> untouched |
| 589 | + p = _ParamInfo(numeric) |
| 590 | + assert cur._apply_money_batch_declared_type(p, [Decimal("1.5")]) is False |
| 591 | + assert p.paramSQLType == numeric |
| 592 | + assert p.decimalDigits == 0 |
| 593 | + |
| 594 | + |
531 | 595 | # ============================================================================= |
532 | 596 | # Invalid Input Handling |
533 | 597 | # ============================================================================= |
|
0 commit comments