FIX: Cursor.description returns dual-compatible SqlTypeCode type codes#355
FIX: Cursor.description returns dual-compatible SqlTypeCode type codes#355dlevy-msft-sql wants to merge 1 commit intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes issue #352 by implementing two main improvements: (1) correcting cursor.description to return SQL type codes (integers) instead of Python type objects, ensuring DB-API 2.0 specification compliance, and (2) adding support for SQL Server spatial data types (geography, geometry, hierarchyid) by handling the SQL_SS_UDT type code (-151).
Key changes:
- Fixed
cursor.description[i][1]to return SQL type integer codes (e.g., 4, -9) instead of Python types (int, str) per DB-API 2.0 spec - Added SQL_SS_UDT (-151) support for SQL Server User-Defined Types including spatial data types
- Updated output converter lookup to use SQL type codes consistently throughout the codebase
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| mssql_python/cursor.py | Changed cursor.description to return SQL type codes instead of Python types; added _column_metadata storage; updated _build_converter_map to use SQL type codes; added mappings for UDT, XML, DATETIME2, SMALLDATETIME types |
| mssql_python/constants.py | Added SQL_SS_UDT = -151 constant for SQL Server User-Defined Types |
| mssql_python/pybind/ddbc_bindings.cpp | Added C++ constants for SQL_SS_UDT, SQL_DATETIME2, SQL_SMALLDATETIME; implemented UDT handling in SQLGetData_wrap, FetchBatchData, FetchMany_wrap, and FetchAll_wrap for LOB streaming |
| tests/test_004_cursor.py | Added lob_wvarchar_column to test schema; updated test_cursor_description to expect SQL type codes; added comprehensive geography type test suite (14 new tests); separated LOB and non-LOB fetch tests; fixed output converter test for UTF-16LE encoding |
| tests/test_003_connection.py | Simplified converter integration tests to use SQL type constants directly instead of dynamic type detection |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
3f52dd7 to
646ef04
Compare
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/connection.pyLines 982-995 Lines 1004-1013 Lines 1016-1024 mssql_python/cursor.pyLines 1015-1023 Lines 1357-1365 Lines 2425-2433 mssql_python/type_code.pyLines 87-95 Lines 111-116 📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.hpp: 58.8%
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.row.py: 66.2%
mssql_python.pybind.ddbc_bindings.cpp: 69.3%
mssql_python.pybind.ddbc_bindings.h: 69.7%
mssql_python.pybind.connection.connection.cpp: 75.3%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 83.4%
mssql_python.cursor.py: 84.9%🔗 Quick Links
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add SQL_SS_XML (-152) constant to Python constants.py (was incorrectly using SQL_XML = 241) - Add SQL_SS_TIME2 (-154) constant for SQL Server TIME(n) type - Update cursor type map to use SQL_SS_XML and add SQL_SS_TIME2 mapping - Add sync comment in C++ to prevent future constant drift Constants verified against Microsoft Learn ODBC documentation: - SQL_SS_TIME2: -154 (SQLNCLI.h) - SQL_SS_TIMESTAMPOFFSET: -155 (SQLNCLI.h) - SQL_SS_XML: -152 (SQL Server ODBC driver) - SQL_SS_UDT: -151 (SQL Server ODBC driver) Addresses Copilot review feedback on PR microsoft#355
f2d2c33 to
e734a2f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e734a2f to
c642f12
Compare
|
Addressed 3 of 5 comments, dismissed 2 with explanations: Fixed:
Dismissed: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8c65dde to
526035f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
mssql_python/mssql_python.pyi:169
- In the type stub,
Row’sdescriptioncurrently typestype_codeasUnion[SqlTypeCode, type], but the implementation now always populatescursor.description[i][1]with aSqlTypeCodeinstance (which is comparable totype/int). Updating the stub toSqlTypeCode(or a protocol capturing__int__+ comparisons) will better match runtime behavior and avoid type-checker false positives (e.g.,int(desc[i][1])).
description: List[
Tuple[
str,
Union[SqlTypeCode, type],
Optional[int],
Optional[int],
Optional[int],
Optional[int],
Optional[bool],
]
mssql_python/mssql_python.pyi:205
Cursor.descriptionis documented here as returningSqlTypeCode, but the stub still usesUnion[SqlTypeCode, type]for thetype_codeelement. Sincecursor._initialize_description()now always wraps the raw SQL code asSqlTypeCode(...), the stub should reflectSqlTypeCodeas the return type to align with runtime and prevent incorrect static typing downstream.
# description is a sequence of 7-item tuples:
# (name, type_code, display_size, internal_size, precision, scale, null_ok)
# type_code is SqlTypeCode which compares equal to both SQL integers and Python types
description: Optional[
List[
Tuple[
str,
Union[SqlTypeCode, type],
Optional[int],
Optional[int],
Optional[int],
Optional[int],
Optional[bool],
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
526035f to
73bd45f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I had to modify some code on my side to handle this change but now the date is properly recognized so this fixes #425 for me. |
Work Item / Issue Reference
Summary
Fixes
cursor.description[i][1]to returnSqlTypeCodeinstances instead of raw Python type objects.SqlTypeCodeis dual-compatible: it compares equal to both SQL integer type codes (DB-API 2.0) and Python types (pandas/polars compatibility), so existing code continues to work unchanged.Problem:
polars.read_database()fails withComputeError: could not append value: 2013-01-01 of type: date to the builder. The root cause is thatcursor.description[i][1]returns Python type objects (str,int,datetime.date) instead of SQL integer type codes as required by DB-API 2.0 (PEP 249). Polars uses these type codes to determine column types, and Python type objects cause type-mapping failures.Solution -- SqlTypeCode class (
type_code.py):SqlTypeCodewraps an ODBC integer type code with.type_code(raw SQL integer) and.python_type(mapped Python type) attributes__eq__matches bothintvalues and Pythontypeobjects, sodesc[1] == stranddesc[1] == -9both work__int__returns the raw SQL type code for DB-API 2.0 consumers__hash__raisesTypeErrorwith actionable guidance) because equality spans multiple hash domainsCursor internals:
_column_metadatastores per-column(col_name, sql_type, col_size, precision, nullable)tuples fromDDBCSQLDescribeCol_build_converter_maprefactored to use stored metadata instead of re-querying_initialize_descriptionbuildsSqlTypeCodefrom stored metadataConverter API:
add_output_converter,get_output_converter,remove_output_converterinconnection.pyacceptUnion[int, SqlTypeCode, type]isinstance(sqltype, SqlTypeCode)to unwrap to.type_codebefore forwarding to native layerConstants and C++ bindings:
SQL_SS_TIME2,SQL_SS_UDT,SQL_SS_XMLconstants#defineguards inddbc_bindings.cppforSQL_DATETIME2,SQL_SMALLDATETIME,SQL_SS_UDTChanges
mssql_python/type_code.pySqlTypeCodeclass extracted to own modulemssql_python/cursor.py_column_metadatatracking,_build_converter_maprefactor,SqlTypeCodeintegrationmssql_python/connection.pySqlTypeCodeviaisinstancemssql_python/constants.pySQL_SS_TIME2,SQL_SS_UDT,SQL_SS_XML)mssql_python/pybind/ddbc_bindings.cpp#defineguardsmssql_python/__init__.pySqlTypeCodemssql_python/mssql_python.pyiSqlTypeCodeCHANGELOG.mdtests/test_002_types.pySqlTypeCodeunit teststests/test_004_cursor.pytests/test_018_polars_integration.pyBreaking Changes
None. Full backward compatibility maintained:
cursor.description[i][1] == strstill works (Python type comparison)cursor.description[i][1] == -9also works (SQL type code comparison)int(cursor.description[i][1])returns the raw SQL integer type codeTesting
SqlTypeCode(equality, hashing, repr, int conversion, pandas compatibility)SqlTypeCode-aware assertionspolars.read_database())