11"""Tests for DatabricksDialect.is_disconnect() method."""
22import pytest
33from databricks .sqlalchemy import DatabricksDialect
4- from databricks .sql .exc import InterfaceError , DatabaseError , OperationalError
4+ from databricks .sql .exc import (
5+ InterfaceError ,
6+ DatabaseError ,
7+ OperationalError ,
8+ RequestError ,
9+ SessionAlreadyClosedError ,
10+ CursorAlreadyClosedError ,
11+ MaxRetryDurationError ,
12+ NonRecoverableNetworkError ,
13+ UnsafeToRetryError ,
14+ )
515
616
717class TestIsDisconnect :
818 @pytest .fixture
919 def dialect (self ):
1020 return DatabricksDialect ()
1121
12- def test_interface_error_is_disconnect (self , dialect ):
13- """InterfaceError (client-side) is always a disconnect."""
14- error = InterfaceError ("Cannot create cursor from closed connection" )
15- assert dialect .is_disconnect (error , None , None ) is True
22+ # --- InterfaceError: closed connection/cursor (client.py) ---
23+
24+ def test_interface_error_closed_connection (self , dialect ):
25+ """All InterfaceError messages with 'closed' are disconnects."""
26+ test_cases = [
27+ InterfaceError ("Cannot create cursor from closed connection" ),
28+ InterfaceError ("Cannot get autocommit on closed connection" ),
29+ InterfaceError ("Cannot set autocommit on closed connection" ),
30+ InterfaceError ("Cannot commit on closed connection" ),
31+ InterfaceError ("Cannot rollback on closed connection" ),
32+ InterfaceError ("Cannot get transaction isolation on closed connection" ),
33+ InterfaceError ("Cannot set transaction isolation on closed connection" ),
34+ InterfaceError ("Attempting operation on closed cursor" ),
35+ ]
36+ for error in test_cases :
37+ assert dialect .is_disconnect (error , None , None ) is True
38+
39+ def test_interface_error_without_closed_not_disconnect (self , dialect ):
40+ """InterfaceError without 'closed' is not a disconnect."""
41+ error = InterfaceError ("Some other interface error" )
42+ assert dialect .is_disconnect (error , None , None ) is False
43+
44+ # --- RequestError: transport/network-level errors ---
45+
46+ def test_request_error_is_disconnect (self , dialect ):
47+ """All RequestError instances are disconnects."""
48+ test_cases = [
49+ RequestError ("HTTP client is closing or has been closed" ),
50+ RequestError ("Connection pool not initialized" ),
51+ RequestError ("HTTP request failed: max retries exceeded" ),
52+ RequestError ("HTTP request error: connection reset" ),
53+ ]
54+ for error in test_cases :
55+ assert dialect .is_disconnect (error , None , None ) is True
56+
57+ def test_request_error_subclasses_are_disconnect (self , dialect ):
58+ """RequestError subclasses are all disconnects."""
59+ test_cases = [
60+ SessionAlreadyClosedError ("Session already closed" ),
61+ CursorAlreadyClosedError ("Cursor already closed" ),
62+ MaxRetryDurationError ("Retry duration exceeded" ),
63+ NonRecoverableNetworkError ("HTTP 501" ),
64+ UnsafeToRetryError ("Unexpected HTTP error" ),
65+ ]
66+ for error in test_cases :
67+ assert dialect .is_disconnect (error , None , None ) is True
68+
69+ # --- DatabaseError: server-side session/operation errors ---
1670
1771 def test_database_error_with_invalid_handle (self , dialect ):
1872 """DatabaseError with 'invalid handle' is a disconnect."""
@@ -25,21 +79,47 @@ def test_database_error_with_invalid_handle(self, dialect):
2579 for error in test_cases :
2680 assert dialect .is_disconnect (error , None , None ) is True
2781
28- def test_database_error_without_invalid_handle (self , dialect ):
29- """DatabaseError without 'invalid handle' is not a disconnect."""
82+ def test_database_error_unexpectedly_closed_server_side (self , dialect ):
83+ """DatabaseError for operations closed server-side is a disconnect."""
84+ test_cases = [
85+ DatabaseError ("Command abc123 unexpectedly closed server side" ),
86+ DatabaseError ("Command None unexpectedly closed server side" ),
87+ ]
88+ for error in test_cases :
89+ assert dialect .is_disconnect (error , None , None ) is True
90+
91+ def test_database_error_without_disconnect_indicators (self , dialect ):
92+ """DatabaseError without disconnect indicators is not a disconnect."""
3093 test_cases = [
3194 DatabaseError ("Syntax error in SQL" ),
3295 DatabaseError ("Table not found" ),
3396 DatabaseError ("Permission denied" ),
97+ DatabaseError ("Catalog name is required for get_schemas" ),
98+ DatabaseError ("Catalog name is required for get_columns" ),
3499 ]
35100 for error in test_cases :
36101 assert dialect .is_disconnect (error , None , None ) is False
37102
38- def test_other_errors_not_disconnect (self , dialect ):
39- """Other exception types are not disconnects."""
103+ # --- OperationalError (non-RequestError) ---
104+
105+ def test_operational_error_not_disconnect (self , dialect ):
106+ """OperationalError without disconnect indicators is not a disconnect."""
40107 test_cases = [
41108 OperationalError ("Timeout waiting for query" ),
109+ OperationalError ("Empty TColumn instance" ),
110+ OperationalError ("Unsupported TRowSet instance" ),
111+ ]
112+ for error in test_cases :
113+ assert dialect .is_disconnect (error , None , None ) is False
114+
115+ # --- Other exceptions ---
116+
117+ def test_other_errors_not_disconnect (self , dialect ):
118+ """Non-connector exception types are not disconnects."""
119+ test_cases = [
42120 Exception ("Some random error" ),
121+ ValueError ("Bad value" ),
122+ RuntimeError ("Runtime failure" ),
43123 ]
44124 for error in test_cases :
45125 assert dialect .is_disconnect (error , None , None ) is False
0 commit comments