Skip to content
Merged
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
33 changes: 33 additions & 0 deletions src/databricks/sql/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def __init__(
catalog: Optional[str] = None,
schema: Optional[str] = None,
_use_arrow_native_complex_types: Optional[bool] = True,
ignore_transactions: bool = True,
**kwargs,
) -> None:
"""
Expand Down Expand Up @@ -217,6 +218,12 @@ def read(self) -> Optional[OAuthToken]:
using SET AUTOCOMMIT instead of returning cached value.
Set to True if autocommit might be changed by external means (e.g., external SQL commands).
When False (default), uses cached state for better performance.
:param ignore_transactions: `bool`, optional (default is True)
When True, transaction-related operations behave as follows:
- commit(): no-op (does nothing)
- rollback(): raises NotSupportedError
- autocommit setter: no-op (does nothing)
When False, transaction operations execute normally.
"""

# Internal arguments in **kwargs:
Expand Down Expand Up @@ -318,6 +325,7 @@ def read(self) -> Optional[OAuthToken]:
self._fetch_autocommit_from_server = kwargs.get(
"fetch_autocommit_from_server", False
)
self.ignore_transactions = ignore_transactions

self.force_enable_telemetry = kwargs.get("force_enable_telemetry", False)
self.enable_telemetry = kwargs.get("enable_telemetry", False)
Expand Down Expand Up @@ -556,10 +564,17 @@ def autocommit(self, value: bool) -> None:
Args:
value: True to enable auto-commit, False to disable

When ignore_transactions is True:
- This method is a no-op (does nothing)

Raises:
InterfaceError: If connection is closed
TransactionError: If server rejects the change
"""
# No-op when ignore_transactions is True
if self.ignore_transactions:
return

if not self.open:
raise InterfaceError(
"Cannot set autocommit on closed connection",
Expand Down Expand Up @@ -651,10 +666,17 @@ def commit(self) -> None:
When autocommit is True:
- Server may throw error if no active transaction

When ignore_transactions is True:
- This method is a no-op (does nothing)

Raises:
InterfaceError: If connection is closed
TransactionError: If commit fails (e.g., no active transaction)
"""
# No-op when ignore_transactions is True
if self.ignore_transactions:
return

if not self.open:
raise InterfaceError(
"Cannot commit on closed connection",
Expand Down Expand Up @@ -689,12 +711,23 @@ def rollback(self) -> None:
When autocommit is True:
- ROLLBACK is forgiving (no-op, doesn't throw exception)

When ignore_transactions is True:
- Raises NotSupportedError

Note: ROLLBACK is safe to call even without active transaction.

Raises:
InterfaceError: If connection is closed
NotSupportedError: If ignore_transactions is True
TransactionError: If rollback fails
"""
# Raise NotSupportedError when ignore_transactions is True
if self.ignore_transactions:
raise NotSupportedError(
"Transactions are not supported on Databricks",
session_id_hex=self.get_session_id_hex(),
)

if not self.open:
raise InterfaceError(
"Cannot rollback on closed connection",
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/test_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def setup_and_teardown(self, connection_details):
"server_hostname": connection_details["host"],
"http_path": connection_details["http_path"],
"access_token": connection_details.get("access_token"),
"ignore_transactions": False, # Enable actual transaction functionality for these tests
}

# Get catalog and schema from environment or use defaults
Expand Down
102 changes: 97 additions & 5 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,10 @@ def _create_mock_connection(self, mock_session_class):
mock_session.get_autocommit.return_value = True
mock_session_class.return_value = mock_session

# Create connection
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)
# Create connection with ignore_transactions=False to test actual transaction functionality
conn = client.Connection(
ignore_transactions=False, **self.DUMMY_CONNECTION_ARGS
)
return conn

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

conn = client.Connection(
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
fetch_autocommit_from_server=True,
ignore_transactions=False,
**self.DUMMY_CONNECTION_ARGS,
)

mock_cursor = Mock()
Expand Down Expand Up @@ -958,7 +962,9 @@ def test_fetch_autocommit_from_server_handles_false_value(self, mock_session_cla
mock_session_class.return_value = mock_session

conn = client.Connection(
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
fetch_autocommit_from_server=True,
ignore_transactions=False,
**self.DUMMY_CONNECTION_ARGS,
)

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

conn = client.Connection(
fetch_autocommit_from_server=True, **self.DUMMY_CONNECTION_ARGS
fetch_autocommit_from_server=True,
ignore_transactions=False,
**self.DUMMY_CONNECTION_ARGS,
)

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

conn.close()

# ==================== IGNORE_TRANSACTIONS TESTS ====================

@patch("%s.client.Session" % PACKAGE_NAME)
def test_commit_is_noop_when_ignore_transactions_true(self, mock_session_class):
"""Test that commit() is a no-op when ignore_transactions=True."""

mock_session = Mock()
mock_session.is_open = True
mock_session.guid_hex = "test-session-id"
mock_session_class.return_value = mock_session

# Create connection with ignore_transactions=True (default)
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)

# Verify ignore_transactions is True by default
self.assertTrue(conn.ignore_transactions)

mock_cursor = Mock()
with patch.object(conn, "cursor", return_value=mock_cursor):
# Call commit - should be no-op
conn.commit()

# Verify that execute was NOT called (no-op)
mock_cursor.execute.assert_not_called()
mock_cursor.close.assert_not_called()

conn.close()

@patch("%s.client.Session" % PACKAGE_NAME)
def test_rollback_raises_not_supported_when_ignore_transactions_true(
self, mock_session_class
):
"""Test that rollback() raises NotSupportedError when ignore_transactions=True."""

mock_session = Mock()
mock_session.is_open = True
mock_session.guid_hex = "test-session-id"
mock_session_class.return_value = mock_session

# Create connection with ignore_transactions=True (default)
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)

# Verify ignore_transactions is True by default
self.assertTrue(conn.ignore_transactions)

# Call rollback - should raise NotSupportedError
with self.assertRaises(NotSupportedError) as ctx:
conn.rollback()

self.assertIn("Transactions are not supported", str(ctx.exception))

conn.close()

@patch("%s.client.Session" % PACKAGE_NAME)
def test_autocommit_setter_is_noop_when_ignore_transactions_true(
self, mock_session_class
):
"""Test that autocommit setter is a no-op when ignore_transactions=True."""

mock_session = Mock()
mock_session.is_open = True
mock_session.guid_hex = "test-session-id"
mock_session_class.return_value = mock_session

# Create connection with ignore_transactions=True (default)
conn = client.Connection(**self.DUMMY_CONNECTION_ARGS)

# Verify ignore_transactions is True by default
self.assertTrue(conn.ignore_transactions)

mock_cursor = Mock()
with patch.object(conn, "cursor", return_value=mock_cursor):
# Set autocommit - should be no-op
conn.autocommit = False

# Verify that execute was NOT called (no-op)
mock_cursor.execute.assert_not_called()
mock_cursor.close.assert_not_called()

# Session set_autocommit should also not be called
conn.session.set_autocommit.assert_not_called()

conn.close()


if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
Expand Down
Loading