From 5187b29318b46d95bf679866a839c96e09b8c1c5 Mon Sep 17 00:00:00 2001 From: Theekshna Kotian Date: Mon, 7 Sep 2026 11:21:30 +0530 Subject: [PATCH 1/2] fix: use ODBC 3.x temporal parameter types --- mssql_python/cursor.py | 13 +++++++++++-- tests/test_004_cursor.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 02915a952..10c082646 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -54,6 +54,11 @@ # encoding and must be rejected at detect time on both paths (see _map_sql_type). BIGINT_MIN: int = -(2**63) BIGINT_MAX: int = 2**63 - 1 +ODBC3_TEMPORAL_SQL_TYPES = { + ddbc_sql_const.SQL_DATE.value: ddbc_sql_const.SQL_TYPE_DATE.value, + ddbc_sql_const.SQL_TIME.value: ddbc_sql_const.SQL_TYPE_TIME.value, + ddbc_sql_const.SQL_TIMESTAMP.value: ddbc_sql_const.SQL_TYPE_TIMESTAMP.value, +} def _normalize_time_param(value, c_type): @@ -947,7 +952,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg ) # Naive datetime -> TIMESTAMP return ( - ddbc_sql_const.SQL_TIMESTAMP.value, + ddbc_sql_const.SQL_TYPE_TIMESTAMP.value, ddbc_sql_const.SQL_C_TYPE_TIMESTAMP.value, 26, 6, @@ -956,7 +961,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg if isinstance(param, datetime.date): return ( - ddbc_sql_const.SQL_DATE.value, + ddbc_sql_const.SQL_TYPE_DATE.value, ddbc_sql_const.SQL_C_TYPE_DATE.value, 10, 0, @@ -1180,6 +1185,8 @@ def setinputsizes(self, sizes: List[Union[int, tuple]]) -> None: f"Invalid SQL type: {sql_type}. Must be a valid SQL type constant." ) + sql_type = ODBC3_TEMPORAL_SQL_TYPES.get(sql_type, sql_type) + # Validate size and precision if not isinstance(column_size, int) or column_size < 0: raise ValueError( @@ -1203,6 +1210,8 @@ def setinputsizes(self, sizes: List[Union[int, tuple]]) -> None: f"Invalid SQL type: {sql_type}. Must be a valid SQL type constant." ) + sql_type = ODBC3_TEMPORAL_SQL_TYPES.get(sql_type, sql_type) + self._inputsizes.append((sql_type, 0, 0)) def _reset_inputsizes(self) -> None: diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index a3ddaaa1f..7972da2e2 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -2520,6 +2520,46 @@ def test_map_sql_type_none_returns_sql_unknown_type(): assert is_dae is False +def test_map_sql_type_uses_odbc3_temporal_types(): + """Python-side inference uses ODBC 3.x temporal SQL types.""" + from unittest.mock import MagicMock + + from mssql_python.constants import ConstantsDDBC as ddbc_sql_const + + cursor = MagicMock(spec=mssql_python.Cursor) + _map_sql_type = mssql_python.Cursor._map_sql_type.__get__(cursor) + + date_type = _map_sql_type(date(2025, 1, 1), [date(2025, 1, 1)], 0) + datetime_type = _map_sql_type(datetime(2025, 1, 1), [datetime(2025, 1, 1)], 0) + + assert date_type[0] == ddbc_sql_const.SQL_TYPE_DATE.value + assert datetime_type[0] == ddbc_sql_const.SQL_TYPE_TIMESTAMP.value + + +def test_setinputsizes_canonicalizes_odbc2_temporal_types(): + """Legacy temporal hints are converted before reaching SQLBindParameter.""" + from unittest.mock import MagicMock + + from mssql_python.constants import ConstantsDDBC as ddbc_sql_const + + cursor = MagicMock(spec=mssql_python.Cursor) + setinputsizes = mssql_python.Cursor.setinputsizes.__get__(cursor) + + setinputsizes( + [ + (ddbc_sql_const.SQL_DATE.value, 10, 0), + ddbc_sql_const.SQL_TIME.value, + (ddbc_sql_const.SQL_TIMESTAMP.value, 26, 6), + ] + ) + + assert cursor._inputsizes == [ + (ddbc_sql_const.SQL_TYPE_DATE.value, 10, 0), + (ddbc_sql_const.SQL_TYPE_TIME.value, 0, 0), + (ddbc_sql_const.SQL_TYPE_TIMESTAMP.value, 26, 6), + ] + + # --------------------------------------------------------- # GH-610: SQLDescribeParam cache coverage tests # --------------------------------------------------------- From 3c746ac707f41ffb2534a1cdf4bd0015923e731d Mon Sep 17 00:00:00 2001 From: Theekshna Kotian Date: Mon, 7 Sep 2026 11:35:26 +0530 Subject: [PATCH 2/2] test: expect ODBC 3.x temporal parameter types --- tests/test_023_execute_path_parity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_023_execute_path_parity.py b/tests/test_023_execute_path_parity.py index 4813bc3e7..ae02c8139 100644 --- a/tests/test_023_execute_path_parity.py +++ b/tests/test_023_execute_path_parity.py @@ -604,10 +604,10 @@ def _param_basetype(cursor, value): (b"", _c.SQL_VARBINARY, _c.SQL_C_BINARY, 1, 0, False), (b"abc", _c.SQL_VARBINARY, _c.SQL_C_BINARY, 3, 0, False), # date / datetime / time - (datetime.date(2024, 1, 1), _c.SQL_DATE, _c.SQL_C_TYPE_DATE, 10, 0, False), + (datetime.date(2024, 1, 1), _c.SQL_TYPE_DATE, _c.SQL_C_TYPE_DATE, 10, 0, False), ( datetime.datetime(2024, 1, 1, 2, 3, 4), - _c.SQL_TIMESTAMP, + _c.SQL_TYPE_TIMESTAMP, _c.SQL_C_TYPE_TIMESTAMP, 26, 6,