Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -1210,6 +1217,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, self._get_c_type_for_sql_type(sql_type), 0, 0)
)
Expand Down
52 changes: 52 additions & 0 deletions tests/test_004_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2520,6 +2520,58 @@ 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 mssql_python.constants import ConstantsDDBC as ddbc_sql_const

cursor = object.__new__(mssql_python.Cursor)

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,
ddbc_sql_const.SQL_C_TYPE_DATE.value,
10,
0,
),
(
ddbc_sql_const.SQL_TYPE_TIME.value,
ddbc_sql_const.SQL_C_TYPE_TIME.value,
0,
0,
),
(
ddbc_sql_const.SQL_TYPE_TIMESTAMP.value,
ddbc_sql_const.SQL_C_TYPE_TIMESTAMP.value,
26,
6,
),
]


# ---------------------------------------------------------
# GH-610: SQLDescribeParam cache coverage tests
# ---------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions tests/test_023_execute_path_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,10 +664,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,
Expand Down
Loading