Repository navigation
Use ODBC 3.x temporal types for parameter binding #757
Description
Describe the bug
mssql-python declares itself as an ODBC 3.8 application by setting SQL_ATTR_ODBC_VERSION to SQL_OV_ODBC3_80, but some parameter-binding paths still pass ODBC 2.x temporal SQL type identifiers to SQLBindParameter:
SQL_DATE(9) instead ofSQL_TYPE_DATE(91)SQL_TIMESTAMP(11) instead ofSQL_TYPE_TIMESTAMP(93)
This currently works with drivers that retain ODBC 2.x application compatibility, but it is not behavior an ODBC 3.x application should rely on. mssql-odbc targets ODBC 3.x only and intentionally does not implement ODBC 2.x application behavior, so it rejects SQL_DATE as an ambiguous ParameterType with HY004 (Invalid SQL data type). It will not add ODBC 2.x application support in the future.
This causes mssql-python compatibility tests to fail when run against mssql-odbc, including:
tests.test_004_cursor::test_executemany_mixed_null_and_typed_valuestests.test_004_cursor::test_setinputsizes_with_null_values
The optimized detector in mssql_python/pybind/param_detect.hpp already uses the ODBC 3.x identifiers. The inconsistency is in the Python fallback used by executemany() and setinputsizes():
Cursor._map_sql_type()returnsSQL_DATEfordatetime.dateandSQL_TIMESTAMPfor naivedatetime.datetime.Cursor.setinputsizes()accepts legacy temporal identifiers and stores them unchanged.ddbc_bindings.cppforwardsParamInfo.paramSQLTypedirectly toSQLBindParameterwithout canonicalization.
To reproduce
import os
import mssql_python
connection = mssql_python.connect(os.environ["DB_CONNECTION_STRING"])
cursor = connection.cursor()
cursor.execute("CREATE TABLE #odbc3_date_repro (value DATE)")
cursor.setinputsizes([(mssql_python.SQL_DATE, 0, 0)])
cursor.execute("INSERT INTO #odbc3_date_repro VALUES (?)", None)Against mssql-odbc, the bind receives ParameterType = 9 and fails with HY004. Using mssql_python.SQL_TYPE_DATE (91) succeeds.
The executemany() path can reach the same legacy mapping without an explicit setinputsizes() call because its Python-side type inference returns SQL_DATE/SQL_TIMESTAMP.
Expected behavior
Because mssql-python declares ODBC 3.8, every SQL temporal ParameterType sent to the driver should use the ODBC 3.x concise identifiers:
SQL_TYPE_DATE(91)SQL_TYPE_TIME(92)SQL_TYPE_TIMESTAMP(93)
Suggested implementation:
- Change Python-side automatic inference to return
SQL_TYPE_DATEandSQL_TYPE_TIMESTAMP. - Canonicalize legacy
SQL_DATE,SQL_TIME, andSQL_TIMESTAMPvalues supplied throughsetinputsizes()to theirSQL_TYPE_*equivalents before storing/binding them. - Keep the exported legacy constants at their standard numeric values for API compatibility; do not redefine
SQL_DATEfrom9to91.
Acceptance criteria
datetime.dateinference binds withSQL_TYPE_DATE(91).- Naive
datetime.datetimeinference binds withSQL_TYPE_TIMESTAMP(93). - Legacy temporal hints passed to
setinputsizes()are canonicalized beforeSQLBindParameter. - The two compatibility tests listed above pass against
mssql-odbc. - Existing execution against msodbcsql remains passing.
Further technical details
Python version: all supported versions
SQL Server version: not server-version-specific
Operating system: cross-platform
Additional context
Related mssql-rs tracking item: AB#47874
Activity
github-actions commentedon Sep 7, 2026
Hi ttk (@Theekshna), thank you for opening this issue!
Our team will review it shortly. We aim to triage all new issues within 24-48 hours and get back to you.
If you have additional information to share, please feel free to update the issue.
Thank you for your patience!
FIX: Use ODBC 3.x parameter types instead of ODBC 2.x (microsoft#758)