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
11 changes: 6 additions & 5 deletions eng/pipelines/pr-validation-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,12 @@ jobs:
distroName: 'Ubuntu-SQL2025'
sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest'
useAzureSQL: 'false'
Ubuntu_AzureSQL:
dockerImage: 'ubuntu:22.04'
distroName: 'Ubuntu-AzureSQL'
sqlServerImage: ''
useAzureSQL: 'true'
${{ if ne(variables['AZURE_CONNECTION_STRING'], '') }}:
Ubuntu_AzureSQL:
dockerImage: 'ubuntu:22.04'
distroName: 'Ubuntu-AzureSQL'
sqlServerImage: ''
useAzureSQL: 'true'
Debian:
dockerImage: 'debian:12'
distroName: 'Debian'
Expand Down
19 changes: 1 addition & 18 deletions mssql_python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Licensed under the MIT license.
This module initializes the mssql_python package.
"""

import sys
import types
from typing import Dict
Expand Down Expand Up @@ -174,7 +173,6 @@ def pooling(max_size: int = 100, idle_timeout: int = 600, enabled: bool = True)
else:
PoolingManager.enable(max_size, idle_timeout)


_original_module_setattr = sys.modules[__name__].__setattr__

# Export SQL constants at module level
Expand Down Expand Up @@ -251,22 +249,8 @@ def get_info_constants() -> Dict[str, int]:
"""
return {name: member.value for name, member in GetInfoConstants.__members__.items()}


# Create a custom module class that uses properties instead of __setattr__
class _MSSQLModule(types.ModuleType):
@property
def native_uuid(self) -> bool:
"""Get the native UUID setting."""
return _settings.native_uuid

@native_uuid.setter
def native_uuid(self, value: bool) -> None:
"""Set the native UUID setting."""
if not isinstance(value, bool):
raise ValueError("native_uuid must be a boolean value")
with _settings_lock:
_settings.native_uuid = value

@property
def lowercase(self) -> bool:
"""Get the lowercase setting."""
Expand Down Expand Up @@ -297,5 +281,4 @@ def lowercase(self, value: bool) -> None:
sys.modules[__name__] = new_module

# Initialize property values
lowercase: bool = _settings.lowercase
native_uuid: bool = _settings.native_uuid
lowercase: bool = _settings.lowercase
40 changes: 13 additions & 27 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,8 +1125,6 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
# After successful execution, initialize description if there are results
column_metadata = []
try:
# ODBC specification guarantees that column metadata is available immediately after
# a successful SQLExecute/SQLExecDirect for the first result set
ddbc_bindings.DDBCSQLDescribeCol(self.hstmt, column_metadata)
self._initialize_description(column_metadata)
except Exception as e: # pylint: disable=broad-exception-caught
Expand All @@ -1135,30 +1133,21 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state

# Reset rownumber for new result set (only for SELECT statements)
if self.description: # If we have column descriptions, it's likely a SELECT
# Capture settings snapshot for this result set
settings = get_settings()
self._settings_snapshot = { # pylint: disable=attribute-defined-outside-init
"lowercase": settings.lowercase,
"native_uuid": settings.native_uuid,
}
# Identify UUID columns based on Python type in description[1]
# This relies on _map_data_type correctly mapping SQL_GUID to uuid.UUID
self._uuid_indices = [] # pylint: disable=attribute-defined-outside-init
for i, desc in enumerate(self.description):
if desc and desc[1] == uuid.UUID: # Column type code at index 1
self._uuid_indices.append(i)
# Verify we have complete description tuples (7 items per PEP-249)
elif desc and len(desc) != 7:
log(
"warning",
f"Column description at index {i} has incorrect tuple length: {len(desc)}",
)
self.rowcount = -1
self._reset_rownumber()
else:
self.rowcount = ddbc_bindings.DDBCSQLRowCount(self.hstmt)
self._clear_rownumber()

# After successful execution, initialize description if there are results
column_metadata = []
try:
ddbc_bindings.DDBCSQLDescribeCol(self.hstmt, column_metadata)
self._initialize_description(column_metadata)
except Exception as e:
# If describe fails, it's likely there are no results (e.g., for INSERT)
self.description = None

self._reset_inputsizes() # Reset input sizes after execution
# Return self for method chaining
return self
Expand Down Expand Up @@ -1971,8 +1960,7 @@ def fetchone(self) -> Union[None, Row]:

# Create and return a Row object, passing column name map if available
column_map = getattr(self, "_column_name_map", None)
settings_snapshot = getattr(self, "_settings_snapshot", None)
return Row(self, self.description, row_data, column_map, settings_snapshot)
return Row(self, self.description, row_data, column_map)
except Exception as e: # pylint: disable=broad-exception-caught
# On error, don't increment rownumber - rethrow the error
raise e
Expand Down Expand Up @@ -2019,9 +2007,8 @@ def fetchmany(self, size: Optional[int] = None) -> List[Row]:

# Convert raw data to Row objects
column_map = getattr(self, "_column_name_map", None)
settings_snapshot = getattr(self, "_settings_snapshot", None)
return [
Row(self, self.description, row_data, column_map, settings_snapshot)
Row(self, self.description, row_data, column_map)
for row_data in rows_data
]
except Exception as e: # pylint: disable=broad-exception-caught
Expand Down Expand Up @@ -2060,9 +2047,8 @@ def fetchall(self) -> List[Row]:

# Convert raw data to Row objects
column_map = getattr(self, "_column_name_map", None)
settings_snapshot = getattr(self, "_settings_snapshot", None)
return [
Row(self, self.description, row_data, column_map, settings_snapshot)
Row(self, self.description, row_data, column_map)
for row_data in rows_data
]
except Exception as e: # pylint: disable=broad-exception-caught
Expand Down Expand Up @@ -2471,4 +2457,4 @@ def setoutputsize(self, size: int, column: Optional[int] = None) -> None:
This method is a no-op in this implementation as buffer sizes
are managed automatically by the underlying driver.
"""
# This is a no-op - buffer sizes are managed automatically
# This is a no-op - buffer sizes are managed automatically
6 changes: 2 additions & 4 deletions mssql_python/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,12 @@ class Settings:
Settings class for mssql_python package configuration.

This class holds global settings that affect the behavior of the package,
including lowercase column names, decimal separator, and native UUID handling.
including lowercase column names, decimal separator.
"""
def __init__(self) -> None:
self.lowercase: bool = False
# Use the pre-determined separator - no locale access here
self.decimal_separator: str = _default_decimal_separator
self.native_uuid: bool = False # Default to False for backwards compatibility


# Global settings instance
_settings: Settings = Settings()
Expand All @@ -345,4 +343,4 @@ def __init__(self) -> None:
def get_settings() -> Settings:
"""Return the global settings object"""
with _settings_lock:
return _settings
return _settings
Loading