Skip to content

FIX: Cursor.description returns dual-compatible SqlTypeCode type codes#355

Draft
dlevy-msft-sql wants to merge 1 commit intomicrosoft:mainfrom
dlevy-msft-sql:Issue-352
Draft

FIX: Cursor.description returns dual-compatible SqlTypeCode type codes#355
dlevy-msft-sql wants to merge 1 commit intomicrosoft:mainfrom
dlevy-msft-sql:Issue-352

Conversation

@dlevy-msft-sql
Copy link
Contributor

@dlevy-msft-sql dlevy-msft-sql commented Dec 1, 2025

Work Item / Issue Reference

GitHub Issue: #352


Summary

Fixes cursor.description to return SqlTypeCode instances as type codes instead of raw Python types. SqlTypeCode is 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.

The original issue was reported when polars pl.read_database() failed with:

ComputeError: could not append value: 2013-01-01 of type: date to the builder

Core change — SqlTypeCode class:

  • SqlTypeCode wraps an ODBC integer type code with .name and .python_type attributes
  • __eq__ matches both int values and Python type objects, so desc[1] == str and desc[1] == -9 both work
  • __int__ returns the raw SQL type code for DB-API 2.0 consumers
  • Intentionally unhashable (__hash__ raises TypeError) because equality spans multiple hash domains — error message guides users to int(type_code) for dict keys

Cursor internals:

  • _column_metadata stores per-column (col_name, sql_type, col_size, precision, nullable) tuples from DDBCSQLDescribeCol
  • _build_converter_map refactored to use stored metadata instead of re-querying
  • _initialize_description builds SqlTypeCode from stored metadata

Constants and C++ bindings:

  • Added SQL_SS_TIME2, SQL_SS_UDT, SQL_SS_XML constants
  • Added #define guards in ddbc_bindings.cpp for SQL_DATETIME2 and SQL_SMALLDATETIME

Converter API:

  • add_output_converter, get_output_converter, remove_output_converter in connection.py updated to accept SqlTypeCode via hasattr(sql_type, 'type_code') duck-typing (isinstance not possible due to circular import guard)

Testing:

  • 18 new SqlTypeCode unit tests (equality, hashing, repr, pandas compatibility)
  • Cursor description tests rewritten with SqlTypeCode-aware assertions
  • New tests: datetime type verification, metadata thread safety, sequential isolation
  • New test_018_polars_integration.py with 5 integration tests (basic types, dates, nulls, large results, all common types)
  • All 1383 tests pass

Files Changed

File Changes
mssql_python/cursor.py SqlTypeCode class, _column_metadata tracking, _build_converter_map refactor
mssql_python/connection.py Converter methods accept SqlTypeCode via duck-typing
mssql_python/constants.py +3 constants (SQL_SS_TIME2, SQL_SS_UDT, SQL_SS_XML)
mssql_python/pybind/ddbc_bindings.cpp +3 #define guards
mssql_python/__init__.py Export SqlTypeCode
mssql_python/mssql_python.pyi Type stub for SqlTypeCode
CHANGELOG.md #352 entries
tests/test_002_types.py 18 new SqlTypeCode tests
tests/test_004_cursor.py Rewritten description tests, new metadata tests
tests/test_018_polars_integration.py NEW: 5 polars integration tests

Breaking Changes

None. Full backward compatibility maintained:

  • cursor.description[i][1] == str still works (Python type comparison)
  • cursor.description[i][1] == -9 also works (SQL type code comparison)
  • int(cursor.description[i][1]) returns the raw SQL integer type code

Copilot AI review requested due to automatic review settings December 1, 2025 17:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dlevy-msft-sql dlevy-msft-sql added bug Something isn't working pr-size: medium Moderate update size labels Dec 1, 2025
@github-actions
Copy link

github-actions bot commented Jan 5, 2026

📊 Code Coverage Report

🔥 Diff Coverage

92%


🎯 Overall Coverage

76%


📈 Total Lines Covered: 5537 out of 7194
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/init.py (100%)
  • mssql_python/connection.py (94.1%): Missing lines 1007
  • mssql_python/constants.py (100%)
  • mssql_python/cursor.py (87.0%): Missing lines 1019,1366,2434
  • mssql_python/type_code.py (93.9%): Missing lines 91,115

Summary

  • Total: 77 lines
  • Missing: 6 lines
  • Coverage: 92%

mssql_python/connection.py

Lines 1003-1011


mssql_python/cursor.py

Lines 1015-1023


Lines 1362-1370


Lines 2430-2438


mssql_python/type_code.py

Lines 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.pybind.ddbc_bindings.cpp: 69.3%
mssql_python.pybind.ddbc_bindings.h: 69.7%
mssql_python.pybind.connection.connection.cpp: 75.3%
mssql_python.row.py: 79.5%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 84.2%
mssql_python.cursor.py: 84.9%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

@dlevy-msft-sql dlevy-msft-sql added pr-size: small Minimal code update pr-size: medium Moderate update size and removed pr-size: medium Moderate update size labels Jan 16, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dlevy-msft-sql dlevy-msft-sql linked an issue Jan 19, 2026 that may be closed by this pull request
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

dlevy-msft-sql added a commit to dlevy-msft-sql/mssql-python that referenced this pull request Jan 20, 2026
- 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
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

tests/test_004_cursor.py:1

  • This change corrects the exception type from a generic Exception to mssql_python.Error, which is more specific and accurate for database errors. This is already fixed in the updated code.
"""

tests/test_004_cursor.py:13063

  • Simplified cleanup by removing unnecessary try-except block around DROP TABLE statement. This is already fixed in the updated code.
        cursor.execute(f"DROP TABLE IF EXISTS {table_name}")

tests/test_004_cursor.py:1

  • Added proper encoding handling for string comparison in output converter test. The driver passes string values as UTF-16LE encoded bytes to output converters, so the test now correctly encodes the comparison value. This is already fixed in the updated code.
"""

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@saurabh500
Copy link
Contributor

SqlTypeCode should be used instead of SQLTypeCode. I am assuming this is a public type and the naming would matter more. PascalCase for Python classes is recommended and applicable to acronyms also.

@dlevy-msft-sql
Copy link
Contributor Author

SqlTypeCode should be used instead of SQLTypeCode. I am assuming this is a public type and the naming would matter more. PascalCase for Python classes is recommended and applicable to acronyms also.

Fixed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dlevy-msft-sql dlevy-msft-sql marked this pull request as ready for review February 3, 2026 02:45
saurabh500
saurabh500 previously approved these changes Feb 4, 2026
@dlevy-msft-sql dlevy-msft-sql changed the title FIX: Cursor.describe invalid data fix: cursor.description returns dual-compatible SqlTypeCode type codes Feb 7, 2026
@dlevy-msft-sql dlevy-msft-sql marked this pull request as draft February 7, 2026 19:46
@dlevy-msft-sql dlevy-msft-sql changed the title fix: cursor.description returns dual-compatible SqlTypeCode type codes FIX: cursor.description returns dual-compatible SqlTypeCode type codes Feb 7, 2026
@dlevy-msft-sql dlevy-msft-sql force-pushed the Issue-352 branch 2 times, most recently from 48a5042 to a61d87e Compare February 7, 2026 20:04
@dlevy-msft-sql dlevy-msft-sql changed the title FIX: cursor.description returns dual-compatible SqlTypeCode type codes FIX: Cursor.description returns dual-compatible SqlTypeCode type codes Feb 7, 2026
@dlevy-msft-sql dlevy-msft-sql force-pushed the Issue-352 branch 3 times, most recently from 6002dd0 to f2d2c33 Compare February 7, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working pr-size: medium Moderate update size

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cursor.description returning incorrect values

3 participants