Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
c403899
Fix for FetchMany(number of rows) ignores batch size when table conta…
dlevy-msft-sql Nov 25, 2025
f96d14c
First round of code review fixes
dlevy-msft-sql Nov 25, 2025
e135e40
Fix test_fetchone
dlevy-msft-sql Nov 25, 2025
38966d0
Fix for issue 352
dlevy-msft-sql Dec 1, 2025
3f52dd7
Apply fixes from copilot review
dlevy-msft-sql Dec 1, 2025
38b041b
Fix for FetchMany(number of rows) ignores batch size when table conta…
dlevy-msft-sql Nov 25, 2025
139994a
First round of code review fixes
dlevy-msft-sql Nov 25, 2025
6fd823e
Fix test_fetchone
dlevy-msft-sql Nov 25, 2025
a7d9b8e
Fix for issue 352
dlevy-msft-sql Dec 1, 2025
646ef04
Apply fixes from copilot review
dlevy-msft-sql Dec 1, 2025
8018872
Merge branch 'Issue-352' of https://github.com/dlevy-msft-sql/mssql-p…
dlevy-msft-sql Jan 5, 2026
67268e5
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 5, 2026
d335ed0
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 8, 2026
2dc93f3
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 9, 2026
eee5256
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 16, 2026
d289a9b
Merge branch 'microsoft:main' into Issue-352
dlevy-msft-sql Jan 16, 2026
ced5fc2
feat: Add SQLTypeCode for dual-compatible cursor.description type codes
dlevy-msft-sql Jan 19, 2026
0568451
fix: Remove stray comment from constants.py
dlevy-msft-sql Jan 19, 2026
257f0f6
chore: Update black target-version to include py312, py313
dlevy-msft-sql Jan 19, 2026
033f7cd
style: Apply black 26.1.0 formatting and pin version in requirements.txt
dlevy-msft-sql Jan 19, 2026
63c08f5
test: Improve SQLTypeCode equality test for 100% coverage
dlevy-msft-sql Jan 19, 2026
6adeead
chore: Remove unnecessary .gitattributes
dlevy-msft-sql Jan 19, 2026
7b6da4e
chore: Keep black unpinned to always use latest
dlevy-msft-sql Jan 19, 2026
42d3e09
fix: Address Copilot review comments
dlevy-msft-sql Jan 19, 2026
31a6d13
refactor: Use DROP TABLE IF EXISTS for cleaner test cleanup
dlevy-msft-sql Jan 19, 2026
6eb43ea
refactor: Remove dead _map_data_type method
dlevy-msft-sql Jan 20, 2026
327ee42
Sync SQL Server ODBC constants between Python and C++
dlevy-msft-sql Jan 20, 2026
7a487b8
test: Add spatial type error handling and thread safety tests
dlevy-msft-sql Jan 20, 2026
839019c
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 20, 2026
466a4d5
test: Add spatial type error handling and thread safety tests
dlevy-msft-sql Jan 20, 2026
5e67a1b
test: Add spatial type error handling and thread safety tests
dlevy-msft-sql Jan 20, 2026
f484e8d
Merge remote branch, keep table name fix
dlevy-msft-sql Jan 20, 2026
c0c2117
Ran black --line-length=100 mssql_python/ tests/ to fix issue
dlevy-msft-sql Jan 20, 2026
b5460dd
docs: Add sync comment for SQL Server constants
dlevy-msft-sql Jan 20, 2026
bced470
fix: Use per-thread connections in thread safety test (MARS not enabled)
dlevy-msft-sql Jan 20, 2026
3d09f1a
style: Fix black formatting
dlevy-msft-sql Jan 20, 2026
fe2a042
Merge branch 'microsoft:main' into Issue-352
dlevy-msft-sql Jan 22, 2026
9913467
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 22, 2026
c0b619e
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 30, 2026
96b3240
Merge branch 'main' into Issue-352
dlevy-msft-sql Jan 30, 2026
aa6b071
Merge branch 'main' into Issue-352
dlevy-msft-sql Feb 1, 2026
f88b75e
fix: Address Copilot review comments - ODBC 3.x types, hash contract,…
dlevy-msft-sql Feb 1, 2026
0011cf2
fix: Address latest Copilot review comments
dlevy-msft-sql Feb 1, 2026
f41a0cb
fix: Use named constants for ODBC 3.x types and narrow test exception
dlevy-msft-sql Feb 1, 2026
fb30fc6
style: Fix black formatting
dlevy-msft-sql Feb 1, 2026
4761dda
fix: Handle SQLTypeCode in output converter methods
dlevy-msft-sql Feb 1, 2026
c5a4ae3
docs: Fix SQL_DATETIMEOFFSET comment to reference SQL_SS_TIMESTAMPOFFSET
dlevy-msft-sql Feb 1, 2026
0b3b36c
fix: Strengthen geography test assertion and update UDT log message
dlevy-msft-sql Feb 1, 2026
117b3b4
style: Fix black formatting
dlevy-msft-sql Feb 1, 2026
fb9c3e6
style: Fix black formatting
dlevy-msft-sql Feb 1, 2026
6a77ed4
fix: Address Copilot review comments
dlevy-msft-sql Feb 1, 2026
151893f
style: Apply black formatting to test_004_cursor.py
dlevy-msft-sql Feb 1, 2026
f28f94d
fix: Address Copilot review comments in ddbc_bindings.cpp
dlevy-msft-sql Feb 1, 2026
bbd4e50
style: Fix black formatting in test_004_cursor.py
dlevy-msft-sql Feb 1, 2026
85dc297
fix: Widen type stubs to Union[SQLTypeCode, type] for description typ…
dlevy-msft-sql Feb 1, 2026
98503ad
fix: Address Copilot review comments
dlevy-msft-sql Feb 1, 2026
6fc30c1
style: Fix black formatting
dlevy-msft-sql Feb 1, 2026
8e282db
fix: Update add_output_converter type hint to Union[int, SQLTypeCode]
dlevy-msft-sql Feb 2, 2026
d57ec4e
fix: Address Copilot review - clear _column_metadata in _reset_cursor…
dlevy-msft-sql Feb 2, 2026
080138a
fix: Remove line number reference from test comment
dlevy-msft-sql Feb 2, 2026
d17d9b1
fix: Remove duplicate DDBCSQLDescribeCol call in execute()
dlevy-msft-sql Feb 2, 2026
d54aea5
fix: Clear cached maps in _reset_cursor to prevent stale converters
dlevy-msft-sql Feb 2, 2026
965b1e1
fix: Add Python type fallback for output converter lookup
dlevy-msft-sql Feb 2, 2026
b2f1d5c
fix: Address Copilot review - add type to Union, use constants, add d…
dlevy-msft-sql Feb 2, 2026
dfd68fa
style: Fix black formatting in test_004_cursor.py
dlevy-msft-sql Feb 2, 2026
d656121
fix: Add SQL_WVARCHAR fallback for str/bytes and remove duplicate dat…
dlevy-msft-sql Feb 2, 2026
deb4242
style: Fix black formatting in test_004_cursor.py
dlevy-msft-sql Feb 2, 2026
2b6af43
fix: Only forward int sqltypes to native conn, use SQL_WVARCHAR constant
dlevy-msft-sql Feb 2, 2026
5ce031a
fix: Widen add/get/remove_output_converter annotations to include type
dlevy-msft-sql Feb 2, 2026
73f47ae
fix: Only forward int sqltypes to native remove_output_converter
dlevy-msft-sql Feb 2, 2026
496c3d1
docs: Fix add_output_converter docstring to reflect actual value types
dlevy-msft-sql Feb 2, 2026
0dfa606
fix: Add python_type fallback for get_output_converter, guard SQL_SS_…
dlevy-msft-sql Feb 2, 2026
dee3852
Merge branch 'main' into Issue-352
dlevy-msft-sql Feb 2, 2026
c2092a9
fix: Address PR #355 review comments
dlevy-msft-sql Feb 2, 2026
8f7516c
fix: Define python_type before conditional use to prevent NameError
dlevy-msft-sql Feb 2, 2026
f68346e
refactor: Rename SQLTypeCode to SqlTypeCode per PEP 8 naming convention
dlevy-msft-sql Feb 3, 2026
57951e6
style: Fix black formatting
dlevy-msft-sql Feb 3, 2026
7876d73
fix: Correct __hash__ type stub to mark SqlTypeCode as unhashable
dlevy-msft-sql Feb 3, 2026
cf3f530
test: Add polars integration tests for issue #352
dlevy-msft-sql Feb 3, 2026
24393a3
fix: Skip polars tests on unsupported platforms (Alpine ARM64)
dlevy-msft-sql Feb 3, 2026
8373987
refactor: Remove unused imports from polars tests
dlevy-msft-sql Feb 3, 2026
ca749a1
style: Fix black formatting
dlevy-msft-sql Feb 3, 2026
5114a5d
fix: Skip polars tests on Alpine ARM64 before import
dlevy-msft-sql Feb 4, 2026
9b99f3e
Merge branch 'main' into Issue-352
bewithgaurav Feb 5, 2026
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
3 changes: 3 additions & 0 deletions mssql_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class ConstantsDDBC(Enum):
SQL_FETCH_RELATIVE = 6
SQL_FETCH_BOOKMARK = 8
SQL_DATETIMEOFFSET = -155
SQL_SS_UDT = -151 # SQL Server User-Defined Types (geometry, geography, hierarchyid)
SQL_C_SS_TIMESTAMPOFFSET = 0x4001
SQL_SCOPE_CURROW = 0
SQL_BEST_ROWID = 1
Expand Down Expand Up @@ -502,3 +503,5 @@ def get_attribute_set_timing(attribute):
# internally.
"packetsize": "PacketSize",
}

# (Function removed; no replacement needed)
25 changes: 18 additions & 7 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import datetime
import warnings
from typing import List, Union, Any, Optional, Tuple, Sequence, TYPE_CHECKING
import xml
from mssql_python.constants import ConstantsDDBC as ddbc_sql_const, SQLTypes
from mssql_python.helpers import check_error
from mssql_python.logging import logger
Expand Down Expand Up @@ -142,6 +143,9 @@ def __init__(self, connection: "Connection", timeout: int = 0) -> None:
)
self.messages = [] # Store diagnostic messages

# Store raw column metadata for converter lookups
self._column_metadata = None

def _is_unicode_string(self, param: str) -> bool:
"""
Check if a string contains non-ASCII characters.
Expand Down Expand Up @@ -942,8 +946,12 @@ def _initialize_description(self, column_metadata: Optional[Any] = None) -> None
"""Initialize the description attribute from column metadata."""
if not column_metadata:
self.description = None
self._column_metadata = None # Clear metadata too
return

# Store raw metadata for converter map building
self._column_metadata = column_metadata

description = []
for _, col in enumerate(column_metadata):
# Get column name - lowercase it if the lowercase flag is set
Expand All @@ -957,7 +965,7 @@ def _initialize_description(self, column_metadata: Optional[Any] = None) -> None
description.append(
(
column_name, # name
self._map_data_type(col["DataType"]), # type_code
col["DataType"], # type_code (SQL type integer) - CHANGED THIS LINE
None, # display_size
col["ColumnSize"], # internal_size
col["ColumnSize"], # precision - should match ColumnSize
Expand All @@ -975,18 +983,17 @@ def _build_converter_map(self):
"""
if (
not self.description
or not self._column_metadata
or not hasattr(self.connection, "_output_converters")
or not self.connection._output_converters
):
return None

converter_map = []

for desc in self.description:
if desc is None:
converter_map.append(None)
continue
sql_type = desc[1]
for col_meta in self._column_metadata:
# Use the raw SQL type code from metadata, not the mapped Python type
sql_type = col_meta["DataType"]
converter = self.connection.get_output_converter(sql_type)
# If no converter found for the SQL type, try the WVARCHAR converter as a fallback
if converter is None:
Expand Down Expand Up @@ -1053,6 +1060,11 @@ def _map_data_type(self, sql_type):
ddbc_sql_const.SQL_VARBINARY.value: bytes,
ddbc_sql_const.SQL_LONGVARBINARY.value: bytes,
ddbc_sql_const.SQL_GUID.value: uuid.UUID,
ddbc_sql_const.SQL_SS_UDT.value: bytes, # UDTs mapped to bytes
ddbc_sql_const.SQL_XML.value: str, # XML mapped to str
ddbc_sql_const.SQL_DATETIME2.value: datetime.datetime,
ddbc_sql_const.SQL_SMALLDATETIME.value: datetime.datetime,
ddbc_sql_const.SQL_DATETIMEOFFSET.value: datetime.datetime,
# Add more mappings as needed
}
return sql_to_python_type.get(sql_type, str)
Expand Down Expand Up @@ -2572,7 +2584,6 @@ def __del__(self):
Destructor to ensure the cursor is closed when it is no longer needed.
This is a safety net to ensure resources are cleaned up
even if close() was not called explicitly.
If the cursor is already closed, it will not raise an exception during cleanup.
"""
if "closed" not in self.__dict__ or not self.closed:
try:
Expand Down
19 changes: 17 additions & 2 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#define MAX_DIGITS_IN_NUMERIC 64
#define SQL_MAX_NUMERIC_LEN 16
#define SQL_SS_XML (-152)
#define SQL_SS_UDT (-151) // SQL Server User-Defined Types (geometry, geography, hierarchyid)
#define SQL_DATETIME2 (42)
#define SQL_SMALLDATETIME (58)

#define STRINGIFY_FOR_CASE(x) \
case x: \
Expand Down Expand Up @@ -2987,6 +2990,11 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
}
break;
}
case SQL_SS_UDT: {
LOG("SQLGetData: Streaming UDT (geometry/geography) for column %d", i);
row.append(FetchLobColumnData(hStmt, i, SQL_C_BINARY, false, true));
break;
}
case SQL_SS_XML: {
LOG("SQLGetData: Streaming XML for column %d", i);
row.append(FetchLobColumnData(hStmt, i, SQL_C_WCHAR, true, false, "utf-16le"));
Expand Down Expand Up @@ -3211,6 +3219,8 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
}
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
case SQL_DATETIME2:
case SQL_SMALLDATETIME:
case SQL_DATETIME: {
SQL_TIMESTAMP_STRUCT timestampValue;
ret = SQLGetData_ptr(hStmt, i, SQL_C_TYPE_TIMESTAMP, &timestampValue,
Expand Down Expand Up @@ -3795,6 +3805,8 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
}
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
case SQL_DATETIME2:
case SQL_SMALLDATETIME:
case SQL_DATETIME: {
const SQL_TIMESTAMP_STRUCT& ts = buffers.timestampBuffers[col - 1][i];
PyObject* datetimeObj = PythonObjectCache::get_datetime_class()(
Expand Down Expand Up @@ -3974,6 +3986,9 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
case SQL_SS_TIMESTAMPOFFSET:
rowSize += sizeof(DateTimeOffset);
break;
case SQL_SS_UDT:
rowSize += columnSize; // UDT types use column size as-is
break;
default:
std::wstring columnName = columnMeta["ColumnName"].cast<std::wstring>();
std::ostringstream errorString;
Expand Down Expand Up @@ -4028,7 +4043,7 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch

if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML) &&
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
lobColumns.push_back(i + 1); // 1-based
}
Expand Down Expand Up @@ -4162,7 +4177,7 @@ SQLRETURN FetchAll_wrap(SqlHandlePtr StatementHandle, py::list& rows,

if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML) &&
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
lobColumns.push_back(i + 1); // 1-based
}
Expand Down
Loading
Loading