-
Notifications
You must be signed in to change notification settings - Fork 15
[PECOBLR-1655] Implemented the functionality of pool_pre_ping #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
a6a3564
d2a3339
77138f3
1e47cb2
c3ae658
6936842
0145f62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -336,6 +336,46 @@ def do_rollback(self, dbapi_connection): | |
| # Databricks SQL Does not support transactions | ||
| pass | ||
|
|
||
| def is_disconnect(self, e, connection, cursor): | ||
| """Determine if an exception indicates the connection was lost. | ||
|
|
||
| This method is called by SQLAlchemy after exceptions occur during query | ||
| execution to determine if the error was due to a lost connection. If this | ||
| returns True, SQLAlchemy will invalidate the connection and create a new | ||
| one for the next operation. | ||
|
|
||
| This method is also used by SQLAlchemy's default do_ping() implementation | ||
| when pool_pre_ping=True. If do_ping() encounters an exception, it calls | ||
| is_disconnect() to classify the error and determine whether to invalidate | ||
| the connection. | ||
|
|
||
| Args: | ||
| e: The exception that was raised | ||
| connection: The connection that raised the exception (may be None) | ||
| cursor: The cursor that raised the exception (may be None) | ||
|
|
||
| Returns: | ||
| True if the error indicates a disconnect, False otherwise | ||
| """ | ||
| from databricks.sql.exc import InterfaceError, DatabaseError, Error | ||
|
|
||
| # InterfaceError: Client-side errors (e.g., connection already closed) | ||
| if isinstance(e, InterfaceError): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. InterfaceError can also be raised for programming errors like invalid params. Will this be an expected behavior then?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not the case with the current connector. All the interface errors are raised when connection is closed. |
||
| return True | ||
|
|
||
| # DatabaseError: Server-side errors with invalid handle indicate session expired | ||
| if isinstance(e, DatabaseError): | ||
| error_msg = str(e).lower() | ||
| return "invalid" in error_msg and "handle" in error_msg | ||
|
|
||
| # Base Error class: Check for closed connection errors | ||
| # This catches errors like "Cannot create cursor from closed connection" | ||
| if isinstance(e, Error): | ||
| error_msg = str(e).lower() | ||
| return "closed" in error_msg or "cannot create cursor" in error_msg | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is having closed a definite case for closed connection? There can be error message like cannot be closed etc. Seems to be error prone.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modified to be precise after checking all error occurences in the python SQL connector. Thanks |
||
|
|
||
| return False | ||
|
|
||
| @reflection.cache | ||
| def has_table( | ||
| self, connection, table_name, schema=None, catalog=None, **kwargs | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| """Tests for DatabricksDialect.is_disconnect() method.""" | ||
| import pytest | ||
| from databricks.sqlalchemy import DatabricksDialect | ||
| from databricks.sql.exc import InterfaceError, DatabaseError, OperationalError | ||
|
|
||
|
|
||
| class TestIsDisconnect: | ||
| @pytest.fixture | ||
| def dialect(self): | ||
| return DatabricksDialect() | ||
|
|
||
| def test_interface_error_is_disconnect(self, dialect): | ||
| """InterfaceError (client-side) is always a disconnect.""" | ||
| error = InterfaceError("Cannot create cursor from closed connection") | ||
| assert dialect.is_disconnect(error, None, None) is True | ||
|
|
||
| def test_database_error_with_invalid_handle(self, dialect): | ||
| """DatabaseError with 'invalid handle' is a disconnect.""" | ||
| test_cases = [ | ||
| DatabaseError("Invalid SessionHandle"), | ||
| DatabaseError("[Errno INVALID_HANDLE] Session does not exist"), | ||
| DatabaseError("INVALID HANDLE"), | ||
| DatabaseError("invalid handle"), | ||
| ] | ||
| for error in test_cases: | ||
| assert dialect.is_disconnect(error, None, None) is True | ||
|
|
||
| def test_database_error_without_invalid_handle(self, dialect): | ||
| """DatabaseError without 'invalid handle' is not a disconnect.""" | ||
| test_cases = [ | ||
| DatabaseError("Syntax error in SQL"), | ||
| DatabaseError("Table not found"), | ||
| DatabaseError("Permission denied"), | ||
| ] | ||
| for error in test_cases: | ||
| assert dialect.is_disconnect(error, None, None) is False | ||
|
|
||
| def test_other_errors_not_disconnect(self, dialect): | ||
| """Other exception types are not disconnects.""" | ||
| test_cases = [ | ||
| OperationalError("Timeout waiting for query"), | ||
| Exception("Some random error"), | ||
| ] | ||
| for error in test_cases: | ||
| assert dialect.is_disconnect(error, None, None) is False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pyproject.toml uses 2.0.8, why is this using 2.2.1 ?