Skip to content

Commit a4899cb

Browse files
Add ignore_transactions config to disable transaction operations (#711)
Introduces a new `ignore_transactions` configuration parameter (default: True) to control transaction-related behavior in the Connection class. When ignore_transactions=True (default): - commit(): no-op, returns immediately - rollback(): raises NotSupportedError with message "Transactions are not supported on Databricks" - autocommit setter: no-op, returns immediately When ignore_transactions=False: - All transaction methods execute normally Changes: - Added ignore_transactions parameter to Connection.__init__() with default value True - Modified commit(), rollback(), and autocommit setter to check ignore_transactions flag - Updated unit tests to pass ignore_transactions=False when testing transaction functionality - Updated e2e transaction tests to pass ignore_transactions=False - Added three new unit tests to verify ignore_transactions
1 parent cca421b commit a4899cb

File tree

3 files changed

+131
-5
lines changed

3 files changed

+131
-5
lines changed

src/databricks/sql/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def __init__(
104104
catalog: Optional[str] = None,
105105
schema: Optional[str] = None,
106106
_use_arrow_native_complex_types: Optional[bool] = True,
107+
ignore_transactions: bool = True,
107108
**kwargs,
108109
) -> None:
109110
"""
@@ -217,6 +218,12 @@ def read(self) -> Optional[OAuthToken]:
217218
using SET AUTOCOMMIT instead of returning cached value.
218219
Set to True if autocommit might be changed by external means (e.g., external SQL commands).
219220
When False (default), uses cached state for better performance.
221+
:param ignore_transactions: `bool`, optional (default is True)
222+
When True, transaction-related operations behave as follows:
223+
- commit(): no-op (does nothing)
224+
- rollback(): raises NotSupportedError
225+
- autocommit setter: no-op (does nothing)
226+
When False, transaction operations execute normally.
220227
"""
221228

222229
# Internal arguments in **kwargs:
@@ -318,6 +325,7 @@ def read(self) -> Optional[OAuthToken]:
318325
self._fetch_autocommit_from_server = kwargs.get(
319326
"fetch_autocommit_from_server", False
320327
)
328+
self.ignore_transactions = ignore_transactions
321329

322330
self.force_enable_telemetry = kwargs.get("force_enable_telemetry", False)
323331
self.enable_telemetry = kwargs.get("enable_telemetry", False)
@@ -556,10 +564,17 @@ def autocommit(self, value: bool) -> None:
556564
Args:
557565
value: True to enable auto-commit, False to disable
558566
567+
When ignore_transactions is True:
568+
- This method is a no-op (does nothing)
569+
559570
Raises:
560571
InterfaceError: If connection is closed
561572
TransactionError: If server rejects the change
562573
"""
574+
# No-op when ignore_transactions is True
575+
if self.ignore_transactions:
576+
return
577+
563578
if not self.open:
564579
raise InterfaceError(
565580
"Cannot set autocommit on closed connection",
@@ -651,10 +666,17 @@ def commit(self) -> None:
651666
When autocommit is True:
652667
- Server may throw error if no active transaction
653668
669+
When ignore_transactions is True:
670+
- This method is a no-op (does nothing)
671+
654672
Raises:
655673
InterfaceError: If connection is closed
656674
TransactionError: If commit fails (e.g., no active transaction)
657675
"""
676+
# No-op when ignore_transactions is True
677+
if self.ignore_transactions:
678+
return
679+
658680
if not self.open:
659681
raise InterfaceError(
660682
"Cannot commit on closed connection",
@@ -689,12 +711,23 @@ def rollback(self) -> None:
689711
When autocommit is True:
690712
- ROLLBACK is forgiving (no-op, doesn't throw exception)
691713
714+
When ignore_transactions is True:
715+
- Raises NotSupportedError
716+
692717
Note: ROLLBACK is safe to call even without active transaction.
693718
694719
Raises:
695720
InterfaceError: If connection is closed
721+
NotSupportedError: If ignore_transactions is True
696722
TransactionError: If rollback fails
697723
"""
724+
# Raise NotSupportedError when ignore_transactions is True
725+
if self.ignore_transactions:
726+
raise NotSupportedError(
727+
"Transactions are not supported on Databricks",
728+
session_id_hex=self.get_session_id_hex(),
729+
)
730+
698731
if not self.open:
699732
raise InterfaceError(
700733
"Cannot rollback on closed connection",

tests/e2e/test_transactions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def setup_and_teardown(self, connection_details):
4848
"server_hostname": connection_details["host"],
4949
"http_path": connection_details["http_path"],
5050
"access_token": connection_details.get("access_token"),
51+
"ignore_transactions": False, # Enable actual transaction functionality for these tests
5152
}
5253

5354
# Get catalog and schema from environment or use defaults

tests/unit/test_client.py

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -655,8 +655,10 @@ def _create_mock_connection(self, mock_session_class):
655655
mock_session.get_autocommit.return_value = True
656656
mock_session_class.return_value = mock_session
657657

658-
# Create connection
659-
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)
658+
# Create connection with ignore_transactions=False to test actual transaction functionality
659+
conn = client.Connection(
660+
ignore_transactions=False, **self.DUMMY_CONNECTION_ARGS
661+
)
660662
return conn
661663

662664
@patch("%s.client.Session" % PACKAGE_NAME)
@@ -928,7 +930,9 @@ def test_fetch_autocommit_from_server_queries_server(self, mock_session_class):
928930
mock_session_class.return_value = mock_session
929931

930932
conn = client.Connection(
931-
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
933+
fetch_autocommit_from_server=True,
934+
ignore_transactions=False,
935+
**self.DUMMY_CONNECTION_ARGS,
932936
)
933937

934938
mock_cursor = Mock()
@@ -958,7 +962,9 @@ def test_fetch_autocommit_from_server_handles_false_value(self, mock_session_cla
958962
mock_session_class.return_value = mock_session
959963

960964
conn = client.Connection(
961-
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
965+
fetch_autocommit_from_server=True,
966+
ignore_transactions=False,
967+
**self.DUMMY_CONNECTION_ARGS,
962968
)
963969

964970
mock_cursor = Mock()
@@ -983,7 +989,9 @@ def test_fetch_autocommit_from_server_raises_on_no_result(self, mock_session_cla
983989
mock_session_class.return_value = mock_session
984990

985991
conn = client.Connection(
986-
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
992+
fetch_autocommit_from_server=True,
993+
ignore_transactions=False,
994+
**self.DUMMY_CONNECTION_ARGS,
987995
)
988996

989997
mock_cursor = Mock()
@@ -998,6 +1006,90 @@ def test_fetch_autocommit_from_server_raises_on_no_result(self, mock_session_cla
9981006

9991007
conn.close()
10001008

1009+
# ==================== IGNORE_TRANSACTIONS TESTS ====================
1010+
1011+
@patch("%s.client.Session" % PACKAGE_NAME)
1012+
def test_commit_is_noop_when_ignore_transactions_true(self, mock_session_class):
1013+
"""Test that commit() is a no-op when ignore_transactions=True."""
1014+
1015+
mock_session = Mock()
1016+
mock_session.is_open = True
1017+
mock_session.guid_hex = "test-session-id"
1018+
mock_session_class.return_value = mock_session
1019+
1020+
# Create connection with ignore_transactions=True (default)
1021+
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)
1022+
1023+
# Verify ignore_transactions is True by default
1024+
self.assertTrue(conn.ignore_transactions)
1025+
1026+
mock_cursor = Mock()
1027+
with patch.object(conn, "cursor", return_value=mock_cursor):
1028+
# Call commit - should be no-op
1029+
conn.commit()
1030+
1031+
# Verify that execute was NOT called (no-op)
1032+
mock_cursor.execute.assert_not_called()
1033+
mock_cursor.close.assert_not_called()
1034+
1035+
conn.close()
1036+
1037+
@patch("%s.client.Session" % PACKAGE_NAME)
1038+
def test_rollback_raises_not_supported_when_ignore_transactions_true(
1039+
self, mock_session_class
1040+
):
1041+
"""Test that rollback() raises NotSupportedError when ignore_transactions=True."""
1042+
1043+
mock_session = Mock()
1044+
mock_session.is_open = True
1045+
mock_session.guid_hex = "test-session-id"
1046+
mock_session_class.return_value = mock_session
1047+
1048+
# Create connection with ignore_transactions=True (default)
1049+
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)
1050+
1051+
# Verify ignore_transactions is True by default
1052+
self.assertTrue(conn.ignore_transactions)
1053+
1054+
# Call rollback - should raise NotSupportedError
1055+
with self.assertRaises(NotSupportedError) as ctx:
1056+
conn.rollback()
1057+
1058+
self.assertIn("Transactions are not supported", str(ctx.exception))
1059+
1060+
conn.close()
1061+
1062+
@patch("%s.client.Session" % PACKAGE_NAME)
1063+
def test_autocommit_setter_is_noop_when_ignore_transactions_true(
1064+
self, mock_session_class
1065+
):
1066+
"""Test that autocommit setter is a no-op when ignore_transactions=True."""
1067+
1068+
mock_session = Mock()
1069+
mock_session.is_open = True
1070+
mock_session.guid_hex = "test-session-id"
1071+
mock_session_class.return_value = mock_session
1072+
1073+
# Create connection with ignore_transactions=True (default)
1074+
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)
1075+
1076+
# Verify ignore_transactions is True by default
1077+
self.assertTrue(conn.ignore_transactions)
1078+
1079+
mock_cursor = Mock()
1080+
with patch.object(conn, "cursor", return_value=mock_cursor):
1081+
# Set autocommit - should be no-op
1082+
conn.autocommit = False
1083+
1084+
# Verify that execute was NOT called (no-op)
1085+
mock_cursor.execute.assert_not_called()
1086+
mock_cursor.close.assert_not_called()
1087+
1088+
# Session set_autocommit should also not be called
1089+
conn.session.set_autocommit.assert_not_called()
1090+
1091+
conn.close()
1092+
10011093

10021094
if __name__ == "__main__":
10031095
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])

0 commit comments

Comments
 (0)