All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- New feature: Support for macOS and Linux.
- Documentation: Added API documentation in the Wiki.
- New
token_provider=parameter onconnect()/Connectionfor Microsoft Entra ID authentication with a custom credential object. Accepts any object exposing a.get_token(scope)method (e.g. anyazure-identitycredential such asDefaultAzureCredential,AzureCliCredential,ManagedIdentityCredential). Mutually exclusive withAuthentication=in the connection string and withattrs_before[SQL_COPT_SS_ACCESS_TOKEN]. Bulk copy re-acquires a fresh token from the provider on each operation. The token scope is fixed to the Azure commercial cloud; sovereign clouds are out of scope (supply a pre-acquired token viaattrs_beforeinstead). - GH-570: New
Cursor.bulkcopy_arrow(table_name, source)method for high-performance bulk loading directly from Apache Arrow data. Accepts apyarrow.Table,RecordBatch, orRecordBatchReader, any object exposing the Arrow C Data Interface (__arrow_c_stream__/__arrow_c_array__— e.g. polars, pandas 2.2+, DuckDB, ADBC results), or an iterable of record batches. Data is streamed to the server through the Arrow C Data Interface without materializing intermediate Python row objects, and the GIL is released for the duration of the network transfer. When the source data already originates as Arrow, this avoids the Arrow→tuple conversion the classicbulkcopy()path requires (measured ~1.4x–2.7x faster end-to-end for such sources).bulkcopy()now raisesTypeErrorsteering Arrow inputs to this method. Requiresmssql-py-core0.1.5+. - Bulk copy now supports
Authentication=ActiveDirectoryServicePrincipalvia anentra_id_token_factorycallback registered on the mssql-py-core connection. The callback is invoked by mssql-tds mid-handshake (FedAuth workflow 0x02) so the tenant id can be resolved from the server-supplied STS URL. Requiresmssql-py-core0.1.5+. Partial fix for #534. - Standalone
mssql-python-odbcpackage (PRs #663, #664): the Microsoft ODBC Driver 18 for SQL Server binaries are now also published as a separate, platform-specificmssql-python-odbcpackage. When it is installed, the native driver loader resolves the driver from it; when it is absent or incomplete, mssql-python transparently falls back to its own bundledlibs/. This is a non-breaking step toward decoupling driver-binary updates from mssql-python releases; a future major version will make the dependency explicit and drop the bundled binaries. - Opt-in/opt-out native provider selection: a new
mssql_python.native_providermodule property (andMSSQL_PYTHON_NATIVE_PROVIDERenvironment variable, which takes precedence) lets a caller select which native ODBC provider is loaded: the default"msodbcsql18"(Microsoft ODBC Driver 18, unchanged behavior) or the opt-in"mssql-odbc"(a Rust-based driver, shipped inside themssql-python-rspackage alongside the Rust TDS core). The selection must be made before the first connection; it resolves and freezes then, and a later change is ignored with aRuntimeWarning; a conflicting property assignment also warns when the environment variable takes precedence. Callmssql_python.get_native_provider_info()for diagnostics (selected id, package, version, driver path, source, and whether it's frozen). This PR does not change the default provider or ship any Rust driver binaries.
- Fetches reuse owned native metadata for stable columns within a result set;
fetchmany()avoids the Python metadata-dictionary roundtrip. Re-execution, result transitions and statement/connection cleanup invalidate this metadata. Public descriptions stay fresh, name-validation timing is preserved, andsql_variantcolumns retain per-row descriptions and per-value probes. Fetch buffers, decoding settings and converted values are not cached. - DATE, TIME, and TIMESTAMP fetch conversion uses checked CPython constructors for the standard datetime types, while preserving cached substitute constructors, their positional arguments and exceptions, and fractional-second truncation. DATETIMEOFFSET, UUID, and Decimal conversion are unchanged.
mssql-pythonnow depends onmssql-python-rs==0.1.0formssql_py_coreinstead of embedding files owned by that separately published distribution.- GH-769 deprecation policy: The misplaced
GetInfoConstantsmembersSQL_TXN_ISOLATION_LEVEL,SQL_CONCURRENCY,SQL_ROWSET_SIZE,SQL_ROW_NUMBER,SQL_IC_UPPER,SQL_IC_LOWER,SQL_IC_SENSITIVE,SQL_IC_MIXED, andSQL_SQL92_ENTRY_SQL,SQL_SQL92_INTERMEDIATE_SQL,SQL_SQL92_FULL_SQLremain available with their original values throughout 1.x. Existing enum attribute access, module-level imports, andget_info_constants()dictionary lookups remain available. Removal is deferred to 2.0 or later, only after maintainer approval and an explicit migration notice; this is not a scheduled removal. Deprecation is documented rather than emitting runtime warnings. These names are not information-type names, and passing their integers togetinfo()still requests unrelated information. They cannot be rejected by value without also rejecting legitimate information types with the same IDs. For migration, useSQL_ATTR_TXN_ISOLATIONfor the connection attribute; useConstantsDDBCor module-level names for legacy statement options andSQL_IC_*response values, not asgetinfo()requests. To interpretSQL_SQL_CONFORMANCE, useSQL_SC_SQL92_ENTRY(1),SQL_SC_FIPS127_2_TRANSITIONAL(2),SQL_SC_SQL92_INTERMEDIATE(4), andSQL_SC_SQL92_FULL(8). The deprecatedSQL_SQL92_*_SQLnames retain 127/128/129 solely for compatibility; they are not conformance flags. For information types whose IDs are corrected, previously persisted value-based enum pickles and raw IDs cannot identify their original meaning; rebuild them from the intended information-type names. Name-based enum pickles resolve retained names to their corrected values. - Connection strings and string connection parameters that contain a NUL
(
\x00) character are now rejected up front withInterfaceErrorinstead of being silently truncated at the NUL by the underlying driver. - Improved error handling in the connection module.
- GH-627 behavioral change:
NULLparameters forVARBINARY/BINARYcolumns on physical tables now succeed silently (previously raisedProgrammingErrorwhen a non-NULL parameter was bound first). For temp tables whereSQLDescribeParamcannot determine column metadata, the fallback toSQL_VARCHARstill produces the sameProgrammingErroras before; users should callcursor.setinputsizes()to work around this.
- GH-754: Pooled connections are now rolled back and restored to autocommit
mode before being parked. This prevents an empty transaction from remaining
visible on an idle SQL Server session after
Connection.close(). Abandoned native connections also roll back pending work before disconnecting during normal object destruction. Statement-handle allocation and cleanup are synchronized with disconnect, including cleanup invoked by cursor finalizers. - Bounded text fetched as UTF-16 now preserves leading U+FEFF and U+FFFE as
payload rather than treating them as byte-order markers. This corrects
row-wise
fetchone(),fetchmany(), andfetchall()results, including bounded columns fetched alongside a MAX column, as well as bounded batch decoding on Linux/macOS. Existing platform-specific decode-error fallbacks remain unchanged. Actual MAX/LOB text decoding is unchanged; its existing BOM and trailing-NUL loss is not fixed here. - GH-745:
executemanyauto-detect binds money-rangeDecimalvalues asSQL_NUMERICwith a batch-wide precision/scale (still viaSQL_C_CHARstring values), so a comparison against a smaller numeric column no longer overflows. SQL precision/scale stay oncolumnSize/decimalDigits; the CHAR array stride uses a separatebufferSizesized from the longest fixed-point encoding (e.g.Decimal("1E-38")), so near-max precision values are not rejected by the array buffer. ThesetinputsizesDECIMAL/NUMERIC string path uses the same buffer-width split; buffer width is derived from text produced by the protected conversion path so failed conversions still raise a sanitizedValueErrorwith row/column details (no raw MemoryError/RuntimeError leakage). - GH-769: Corrected 11
GetInfoConstantsIDs for scalar functions, outer joins, driver handles, cursor attributes, catalog support, and parameter descriptions. Added the ODBC nameSQL_TIMEDATE_FUNCTIONSas an alias ofSQL_DATETIME_FUNCTIONSat 52, preserving the existing canonical.name. For registered information types,getinfo()now uses ODBC return types instead of guessing from the bytes: numeric information is decoded as unsigned integers (includingSQL_SQL_CONFORMANCE,SQL_CURSOR_SENSITIVITY, andSQL_MAX_IDENTIFIER_LEN), and character results use the Unicode path. An immutable return-type registry enforces the exact ODBC numeric width, including pointer-sized handles. Wrong-width or truncated numeric payloads raiseDatabaseErrorrather than returning a guessed value. Legacy non-byte numeric payloads are not truncated or coerced from bool to int; convertible decimal strings still return integers. Also corrects decoding for the unlisted standard IDsSQL_DBMS_NAME(17),SQL_DBMS_VER(18),SQL_XOPEN_CLI_YEAR(10000),SQL_ASYNC_MODE(10021), andSQL_CREATE_ASSERTION(127). Other unlisted raw IDs retain their existing behavior. Driver Manager-only handle queries can still be unsupported by the native provider; correcting their IDs does not add Driver Manager support. Native retrieval errors, including timeout and connection-loss errors, continue to be logged and returnNone. Providers may return cached metadata after connection loss:getinfo()is not a connection-health check. - GH-740: A Python
Decimalwhose value falls in the SQL Server MONEY / SMALLMONEY range is now bound asSQL_NUMERICwith its own precision and scale on bothexecute()paths (native detection, and the legacy path reached whensetinputsizes()covers fewer positions than parameters). Previously it was bound as a formattedVARCHARbased on the value alone, so comparing it against a smallernumeric/decimalcolumn (WHERE v = ?) made SQL Server convertvarchar→numericand raise an arithmetic overflow instead of simply not matching. Also fixes a latent binder bug the shortcut was masking: the numeric APD descriptor record number was hardcoded to1, so a numeric parameter in any position other than the first corrupted the parameter bound at position 1. Behavioral change on the wire: money-range Decimals now arrive asnumericrather thanvarchar— a bareSELECT ?returnsDecimalinstead ofstr,sql_variantstores them asnumeric, and becausenumericoutranksmoney/varcharin data-type precedence,WHERE money_or_varchar_col = ?can add aCONVERT_IMPLICITon the column side that turns an index seek into a scan. Theexecutemanyauto-detect path is covered separately by GH-745. - GH-725: The
timeoutparameter ofconnect()/Connection(...)now correctly sets the login (connection-attempt) timeout (SQL_ATTR_LOGIN_TIMEOUT), matching pyodbc and its own docstring. Previously it was silently applied as the per-statement query timeout, soconnect(timeout=N)did not bound the connection attempt and instead aborted long-running queries. Behavioral change: the constructortimeoutno longer affectsConnection.timeout(the query timeout, still settable via the property, default0); an explicitattrs_before[SQL_ATTR_LOGIN_TIMEOUT]takes precedence over thetimeoutkwarg. Timeout values are now validated consistently at both entry points: the constructortimeoutand theConnection.timeoutsetter reject negative, non-integer, andboolvalues (previouslyConnection.timeout = True/Falsewas accepted as1/0). - GH-627: Fixed
SQLDescribeParamordinal remapping bug that causedVARBINARY/BINARYNULLbindings to fail when a non-NULL parameter was bound first. The driver now pre-resolves unknown NULL parameter types before anySQLBindParametercalls, avoiding ODBC ordinal confusion. - GH-726: The extension loader now derives the Windows architecture from
sysconfig.get_platform()(the interpreter build) instead ofplatform.machine()(the host CPU). An x64 interpreter on a Windows ARM64 machine previously looked forddbc_bindings.cpXY-arm64.pyd, missed, and took the fallback path on every import; it now resolves theamd64binary thewin_amd64wheel ships. The fallback notice is now emitted as aRuntimeWarninginstead of being printed to stdout.
- Initial release of the mssql-python driver for SQL Server.
- N/A
- N/A