Skip to content

Commit 2f3ada6

Browse files
authored
FIX: Setting autocommit as False by default & add rollback on connection close (#158)
1 parent 6077554 commit 2f3ada6

File tree

4 files changed

+60
-5
lines changed

4 files changed

+60
-5
lines changed

mssql_python/connection.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def autocommit(self, value: bool) -> None:
147147
self.setautocommit(value)
148148
log('info', "Autocommit mode set to %s.", value)
149149

150-
def setautocommit(self, value: bool = True) -> None:
150+
def setautocommit(self, value: bool = False) -> None:
151151
"""
152152
Set the autocommit mode of the connection.
153153
Args:
@@ -259,6 +259,13 @@ def close(self) -> None:
259259
# Close the connection even if cursor cleanup had issues
260260
try:
261261
if self._conn:
262+
if not self.autocommit:
263+
# If autocommit is disabled, rollback any uncommitted changes
264+
# This is important to ensure no partial transactions remain
265+
# For autocommit True, this is not necessary as each statement is committed immediately
266+
self._conn.rollback()
267+
# TODO: Check potential race conditions in case of multithreaded scenarios
268+
# Close the connection
262269
self._conn.close()
263270
self._conn = None
264271
except Exception as e:

mssql_python/db_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66
from mssql_python.connection import Connection
77

8-
def connect(connection_str: str = "", autocommit: bool = True, attrs_before: dict = None, **kwargs) -> Connection:
8+
def connect(connection_str: str = "", autocommit: bool = False, attrs_before: dict = None, **kwargs) -> Connection:
99
"""
1010
Constructor for creating a connection to the database.
1111

mssql_python/pybind/connection/connection.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,14 @@ void Connection::setAutocommit(bool enable) {
132132
ThrowStdException("Connection handle not allocated");
133133
}
134134
SQLINTEGER value = enable ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
135-
LOG("Set SQL Connection Attribute");
135+
LOG("Setting SQL Connection Attribute");
136136
SQLRETURN ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(value)), 0);
137137
checkError(ret);
138+
if(value == SQL_AUTOCOMMIT_ON) {
139+
LOG("SQL Autocommit set to True");
140+
} else {
141+
LOG("SQL Autocommit set to False");
142+
}
138143
_autocommit = enable;
139144
}
140145

tests/test_003_connection.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
- test_commit: Make a transaction and commit.
88
- test_rollback: Make a transaction and rollback.
99
- test_invalid_connection_string: Check if initializing with an invalid connection string raises an exception.
10-
Note: The cursor function is not yet implemented, so related tests are commented out.
10+
- test_connection_pooling_speed: Test connection pooling speed.
11+
- test_connection_pooling_basic: Test basic connection pooling functionality.
12+
- test_autocommit_default: Check if autocommit is False by default.
13+
- test_autocommit_setter: Test setting autocommit mode and its effect on transactions.
14+
- test_set_autocommit: Test the setautocommit method.
15+
- test_construct_connection_string: Check if the connection string is constructed correctly with kwargs.
16+
- test_connection_string_with_attrs_before: Check if the connection string is constructed correctly with attrs_before.
17+
- test_connection_string_with_odbc_param: Check if the connection string is constructed correctly with ODBC parameters.
18+
- test_rollback_on_close: Test that rollback occurs on connection close if autocommit is False.
1119
"""
1220

1321
from mssql_python.exceptions import InterfaceError
@@ -73,7 +81,7 @@ def test_connection_string_with_odbc_param(db_connection):
7381
assert "Driver={ODBC Driver 18 for SQL Server};;APP=MSSQL-Python;Server=localhost;Uid=me;Pwd=mypwd;Database=mydb;Encrypt=yes;TrustServerCertificate=yes;" == conn_str, "Connection string is incorrect"
7482

7583
def test_autocommit_default(db_connection):
76-
assert db_connection.autocommit is True, "Autocommit should be True by default"
84+
assert db_connection.autocommit is False, "Autocommit should be False by default"
7785

7886
def test_autocommit_setter(db_connection):
7987
db_connection.autocommit = True
@@ -140,6 +148,41 @@ def test_commit(db_connection):
140148
cursor.execute("DROP TABLE #pytest_test_commit;")
141149
db_connection.commit()
142150

151+
def test_rollback_on_close(conn_str, db_connection):
152+
# Test that rollback occurs on connection close if autocommit is False
153+
# Using a permanent table to ensure rollback is tested correctly
154+
cursor = db_connection.cursor()
155+
drop_table_if_exists(cursor, "pytest_test_rollback_on_close")
156+
try:
157+
# Create a permanent table for testing
158+
cursor.execute("CREATE TABLE pytest_test_rollback_on_close (id INT PRIMARY KEY, value VARCHAR(50));")
159+
db_connection.commit()
160+
161+
# This simulates a scenario where the connection is closed without committing
162+
# and checks if the rollback occurs
163+
temp_conn = connect(conn_str)
164+
temp_cursor = temp_conn.cursor()
165+
temp_cursor.execute("INSERT INTO pytest_test_rollback_on_close (id, value) VALUES (1, 'test');")
166+
167+
# Verify data is visible within the same transaction
168+
temp_cursor.execute("SELECT * FROM pytest_test_rollback_on_close WHERE id = 1;")
169+
result = temp_cursor.fetchone()
170+
assert result is not None, "Rollback on close failed: No data found before close"
171+
assert result[1] == 'test', "Rollback on close failed: Incorrect data before close"
172+
173+
# Close the temporary connection without committing
174+
temp_conn.close()
175+
176+
# Now check if the data is rolled back
177+
cursor.execute("SELECT * FROM pytest_test_rollback_on_close WHERE id = 1;")
178+
result = cursor.fetchone()
179+
assert result is None, "Rollback on close failed: Data found after rollback"
180+
except Exception as e:
181+
pytest.fail(f"Rollback on close failed: {e}")
182+
finally:
183+
drop_table_if_exists(cursor, "pytest_test_rollback_on_close")
184+
db_connection.commit()
185+
143186
def test_rollback(db_connection):
144187
# Make a transaction and rollback
145188
cursor = db_connection.cursor()

0 commit comments

Comments
 (0)