From 99336ad28835f2377f0350858c251cc63f919188 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 19 Dec 2025 15:18:25 +0530 Subject: [PATCH 1/2] RELEASE: v1.1.0 --- PyPI_Description.md | 25 ++++++-- mssql_python/__init__.py | 48 +------------- setup.py | 132 ++++++++++++++++++++++----------------- 3 files changed, 95 insertions(+), 110 deletions(-) diff --git a/PyPI_Description.md b/PyPI_Description.md index afe8cd8f6..d183545b1 100644 --- a/PyPI_Description.md +++ b/PyPI_Description.md @@ -35,12 +35,29 @@ PyBind11 provides: - Memory-safe bindings - Clean and Pythonic API, while performance-critical logic remains in robust, maintainable C++. -## What's new in v1.0.0 +## What's new in v1.1.0 -### New Features +### Enhancements -- *Python 3.14 support* - ensuring compatibility with the latest Python ecosystem. -- *GA stability* - hardened release engineering, expanded test coverage, and compliance checks for enterprise readiness. +- **Thread-Safe Encoding/Decoding** - Mutex-based lock protection prevents race conditions in multi-threaded applications with concurrent connections. Strict validation for SQL_WCHAR types and security measures against encoding-based attacks ensure data integrity and application security. + +### Critical Bug Fixes + +- **Linux Stability** - Fixed critical double-free issue causing segmentation faults during Python garbage collection, significantly improving stability on Linux platforms. + +- **Connection Pooling Isolation Level** - Transaction isolation level now explicitly resets to READ COMMITTED when pooled connections are reused, preventing unexpected transaction behavior and isolation level conflicts. + +- **Connection String Escaping** - Fixed parser and builder to correctly handle ODBC curly brace escaping rules, enabling proper handling of special characters in passwords and connection values. + +- **UTF-16 String Decoding** - Enhanced `connection.getinfo()` to properly decode UTF-16LE strings from SQL Server, eliminating data corruption when retrieving metadata with non-ASCII characters. + +- **NULL Parameter Arrays** - Added support for arrays containing only NULL values in `executemany()`, improving batch operation reliability. + +- **Query Timeout Consistency** - Refactored timeout handling to set query timeout during cursor initialization, reducing overhead and ensuring consistent timeout application. + +- **IntegrityError Detection** - Fixed error handling in `fetchall()` for INSERT statements with OUTPUT clause and multiple VALUES entries. + +- **Sensitive Parameter Filtering** - Corrected authentication parameter filtering to properly exclude Trusted_Connection while preserving encryption settings. For more information, please visit the project link on Github: https://github.com/microsoft/mssql-python diff --git a/mssql_python/__init__.py b/mssql_python/__init__.py index 7420c7ea4..9be0c7e3d 100644 --- a/mssql_python/__init__.py +++ b/mssql_python/__init__.py @@ -4,18 +4,15 @@ This module initializes the mssql_python package. """ -import atexit import sys -import threading import types -import weakref from typing import Dict # Import settings from helpers to avoid circular imports from .helpers import Settings, get_settings, _settings, _settings_lock # Driver version -__version__ = "1.0.0" +__version__ = "1.1.0" # Exceptions # https://www.python.org/dev/peps/pep-0249/#exceptions @@ -70,49 +67,6 @@ # Pooling from .pooling import PoolingManager -# Global registry for tracking active connections (using weak references) -_active_connections = weakref.WeakSet() -_connections_lock = threading.Lock() - - -def _register_connection(conn): - """Register a connection for cleanup before shutdown.""" - with _connections_lock: - _active_connections.add(conn) - - -def _cleanup_connections(): - """ - Cleanup function called by atexit to close all active connections. - - This prevents resource leaks during interpreter shutdown by ensuring - all ODBC handles are freed in the correct order before Python finalizes. - """ - # Make a copy of the connections to avoid modification during iteration - with _connections_lock: - connections_to_close = list(_active_connections) - - for conn in connections_to_close: - try: - # Check if connection is still valid and not closed - if hasattr(conn, "_closed") and not conn._closed: - # Close will handle both cursors and the connection - conn.close() - except Exception as e: - # Log errors during shutdown cleanup for debugging - # We're prioritizing crash prevention over error propagation - try: - driver_logger.error( - f"Error during connection cleanup at shutdown: {type(e).__name__}: {e}" - ) - except Exception: - # If logging fails during shutdown, silently ignore - pass - - -# Register cleanup function to run before Python exits -atexit.register(_cleanup_connections) - # GLOBALS # Read-Only apilevel: str = "2.0" diff --git a/setup.py b/setup.py index a2d2c3406..170245015 100644 --- a/setup.py +++ b/setup.py @@ -4,60 +4,67 @@ from setuptools.dist import Distribution from wheel.bdist_wheel import bdist_wheel + # Custom distribution to force platform-specific wheel class BinaryDistribution(Distribution): def has_ext_modules(self): return True + def get_platform_info(): """Get platform-specific architecture and platform tag information.""" - if sys.platform.startswith('win'): + if sys.platform.startswith("win"): # Get architecture from environment variable or default to x64 - arch = os.environ.get('ARCHITECTURE', 'x64') + arch = os.environ.get("ARCHITECTURE", "x64") # Strip quotes if present if isinstance(arch, str): - arch = arch.strip('"\'') + arch = arch.strip("\"'") # Normalize architecture values - if arch in ['x86', 'win32']: - return 'x86', 'win32' - elif arch == 'arm64': - return 'arm64', 'win_arm64' + if arch in ["x86", "win32"]: + return "x86", "win32" + elif arch == "arm64": + return "arm64", "win_arm64" else: # Default to x64/amd64 - return 'x64', 'win_amd64' - - elif sys.platform.startswith('darwin'): + return "x64", "win_amd64" + + elif sys.platform.startswith("darwin"): # macOS platform - always use universal2 - return 'universal2', 'macosx_15_0_universal2' - - elif sys.platform.startswith('linux'): + return "universal2", "macosx_15_0_universal2" + + elif sys.platform.startswith("linux"): # Linux platform - use musllinux or manylinux tags based on architecture # Get target architecture from environment variable or default to platform machine type import platform - target_arch = os.environ.get('targetArch', platform.machine()) + + target_arch = os.environ.get("targetArch", platform.machine()) # Detect libc type libc_name, _ = platform.libc_ver() - is_musl = libc_name == '' or 'musl' in libc_name.lower() - - if target_arch == 'x86_64': - return 'x86_64', 'musllinux_1_2_x86_64' if is_musl else 'manylinux_2_28_x86_64' - elif target_arch in ['aarch64', 'arm64']: - return 'aarch64', 'musllinux_1_2_aarch64' if is_musl else 'manylinux_2_28_aarch64' + is_musl = libc_name == "" or "musl" in libc_name.lower() + + if target_arch == "x86_64": + return "x86_64", "musllinux_1_2_x86_64" if is_musl else "manylinux_2_28_x86_64" + elif target_arch in ["aarch64", "arm64"]: + return "aarch64", "musllinux_1_2_aarch64" if is_musl else "manylinux_2_28_aarch64" else: - raise OSError(f"Unsupported architecture '{target_arch}' for Linux; expected 'x86_64' or 'aarch64'.") + raise OSError( + f"Unsupported architecture '{target_arch}' for Linux; expected 'x86_64' or 'aarch64'." + ) + # Custom bdist_wheel command to override platform tag class CustomBdistWheel(bdist_wheel): def finalize_options(self): # Call the original finalize_options first to initialize self.bdist_dir bdist_wheel.finalize_options(self) - + # Get platform info using consolidated function arch, platform_tag = get_platform_info() self.plat_name = platform_tag print(f"Setting wheel platform tag to: {self.plat_name} (arch: {arch})") + # Find all packages in the current directory packages = find_packages() @@ -66,64 +73,71 @@ def finalize_options(self): print(f"Detected architecture: {arch} (platform tag: {platform_tag})") # Add platform-specific packages -if sys.platform.startswith('win'): - packages.extend([ - f'mssql_python.libs.windows.{arch}', - f'mssql_python.libs.windows.{arch}.1033', - f'mssql_python.libs.windows.{arch}.vcredist' - ]) -elif sys.platform.startswith('darwin'): - packages.extend([ - f'mssql_python.libs.macos', - ]) -elif sys.platform.startswith('linux'): - packages.extend([ - f'mssql_python.libs.linux', - ]) +if sys.platform.startswith("win"): + packages.extend( + [ + f"mssql_python.libs.windows.{arch}", + f"mssql_python.libs.windows.{arch}.1033", + f"mssql_python.libs.windows.{arch}.vcredist", + ] + ) +elif sys.platform.startswith("darwin"): + packages.extend( + [ + f"mssql_python.libs.macos", + ] + ) +elif sys.platform.startswith("linux"): + packages.extend( + [ + f"mssql_python.libs.linux", + ] + ) setup( - name='mssql-python', - version='1.0.0', - description='A Python library for interacting with Microsoft SQL Server', - long_description=open('PyPI_Description.md', encoding='utf-8').read(), - long_description_content_type='text/markdown', - author='Microsoft Corporation', - author_email='mssql-python@microsoft.com', - url='https://github.com/microsoft/mssql-python', + name="mssql-python", + version="1.1.0", + description="A Python library for interacting with Microsoft SQL Server", + long_description=open("PyPI_Description.md", encoding="utf-8").read(), + long_description_content_type="text/markdown", + author="Microsoft Corporation", + author_email="mssql-python@microsoft.com", + url="https://github.com/microsoft/mssql-python", packages=packages, package_data={ # Include PYD and DLL files inside mssql_python, exclude YML files - 'mssql_python': [ - 'ddbc_bindings.cp*.pyd', # Include all PYD files - 'ddbc_bindings.cp*.so', # Include all SO files - 'libs/*', - 'libs/**/*', - '*.dll' + "mssql_python": [ + "ddbc_bindings.cp*.pyd", # Include all PYD files + "ddbc_bindings.cp*.so", # Include all SO files + "libs/*", + "libs/**/*", + "*.dll", ] }, include_package_data=True, # Requires >= Python 3.10 - python_requires='>=3.10', + python_requires=">=3.10", # Add dependencies install_requires=[ - 'azure-identity>=1.12.0', # Azure authentication library + "azure-identity>=1.12.0", # Azure authentication library ], classifiers=[ - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS', - 'Operating System :: POSIX :: Linux', + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", ], zip_safe=False, # Force binary distribution distclass=BinaryDistribution, exclude_package_data={ - '': ['*.yml', '*.yaml'], # Exclude YML files - 'mssql_python': [ - 'libs/*/vcredist/*', 'libs/*/vcredist/**/*', # Exclude vcredist directories, added here since `'libs/*' is already included` + "": ["*.yml", "*.yaml"], # Exclude YML files + "mssql_python": [ + "libs/*/vcredist/*", + "libs/*/vcredist/**/*", # Exclude vcredist directories, added here since `'libs/*' is already included` ], }, # Register custom commands cmdclass={ - 'bdist_wheel': CustomBdistWheel, + "bdist_wheel": CustomBdistWheel, }, ) From b3ac493a66ba2440b8921711b8c655d56fa35daf Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 19 Dec 2025 15:21:50 +0530 Subject: [PATCH 2/2] RELEASE: v1.1.0 --- mssql_python/__init__.py | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/mssql_python/__init__.py b/mssql_python/__init__.py index 9be0c7e3d..5fdf6b3f0 100644 --- a/mssql_python/__init__.py +++ b/mssql_python/__init__.py @@ -4,8 +4,11 @@ This module initializes the mssql_python package. """ +import atexit import sys +import threading import types +import weakref from typing import Dict # Import settings from helpers to avoid circular imports @@ -67,6 +70,49 @@ # Pooling from .pooling import PoolingManager +# Global registry for tracking active connections (using weak references) +_active_connections = weakref.WeakSet() +_connections_lock = threading.Lock() + + +def _register_connection(conn): + """Register a connection for cleanup before shutdown.""" + with _connections_lock: + _active_connections.add(conn) + + +def _cleanup_connections(): + """ + Cleanup function called by atexit to close all active connections. + + This prevents resource leaks during interpreter shutdown by ensuring + all ODBC handles are freed in the correct order before Python finalizes. + """ + # Make a copy of the connections to avoid modification during iteration + with _connections_lock: + connections_to_close = list(_active_connections) + + for conn in connections_to_close: + try: + # Check if connection is still valid and not closed + if hasattr(conn, "_closed") and not conn._closed: + # Close will handle both cursors and the connection + conn.close() + except Exception as e: + # Log errors during shutdown cleanup for debugging + # We're prioritizing crash prevention over error propagation + try: + driver_logger.error( + f"Error during connection cleanup at shutdown: {type(e).__name__}: {e}" + ) + except Exception: + # If logging fails during shutdown, silently ignore + pass + + +# Register cleanup function to run before Python exits +atexit.register(_cleanup_connections) + # GLOBALS # Read-Only apilevel: str = "2.0"