Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
before; users should call `cursor.setinputsizes()` to work around this.

### Fixed
- **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.
- **GH-769:** Corrected 11 `GetInfoConstants` IDs for scalar functions, outer
joins, driver handles, cursor attributes, catalog support, and parameter
descriptions. Added the ODBC name `SQL_TIMEDATE_FUNCTIONS` as an alias of
Expand Down
32 changes: 23 additions & 9 deletions mssql_python/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2179,21 +2179,35 @@ def close(self) -> None:
# Close the connection even if cursor cleanup had issues
try:
if self._conn:
if not self.autocommit:
# If autocommit is disabled, rollback any uncommitted changes
# This is important to ensure no partial transactions remain
# For autocommit True, this is not necessary as each statement is
# committed immediately
autocommit_error = None
rollback_error = None
manual_commit = False
try:
manual_commit = not self._conn.get_autocommit()
except RuntimeError as e:
autocommit_error = e
if manual_commit:
# End caller work before native close. Pooled connections are
# additionally restored to autocommit by native check-in,
# which atomically discards them if sanitation fails.
Comment thread
sumitmsft marked this conversation as resolved.
logger.debug("Rolling back uncommitted changes before closing connection.")
try:
self._conn.rollback()
except RuntimeError as e:
Comment thread
sumitmsft marked this conversation as resolved.
# Handle C++ layer RuntimeError with proper DB-API exception mapping
_raise_connection_error(e)
rollback_error = e
# TODO: Check potential race conditions in case of multithreaded scenarios
# Close the connection
self._conn.close()
self._conn = None
try:
self._conn.close(manual_commit and rollback_error is None)
except RuntimeError as e:
_raise_connection_error(e)
finally:
self._conn = None
if rollback_error is not None:
# Preserve prior DB-API error mapping after deterministic cleanup.
_raise_connection_error(rollback_error)
if autocommit_error is not None:
_raise_connection_error(autocommit_error)
except Exception as e:
logger.error(f"Error closing database connection: {e}")
# Re-raise the connection close error as it's more critical
Expand Down
Loading
Loading