Skip to content

Commit f5aaabb

Browse files
rahuls-dbIsaac
andcommitted
refactor: scope KP001 Reyden detection to OpenSession only
_check_response_for_error runs for every Thrift RPC, so mapping KP001 to the recoverable marker there gave it a wider blast radius than the recovery logic (which only wraps session open): a stray KP001 on any other RPC would have surfaced as ReydenThriftUnsupportedError with no handler. Gate the mapping behind a detect_reyden flag that make_request sets only for the OpenSession method (mirroring the existing method.__name__ discrimination). Every other RPC now surfaces a KP001 as a generic DatabaseError, unchanged from before. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Rahul Singhal <rahul.singhal@databricks.com>
1 parent df4fba5 commit f5aaabb

2 files changed

Lines changed: 70 additions & 19 deletions

File tree

src/databricks/sql/backend/thrift_backend.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,17 +284,22 @@ def _initialize_retry_args(self, kwargs):
284284
)
285285

286286
@staticmethod
287-
def _check_response_for_error(response, host_url=None):
287+
def _check_response_for_error(response, host_url=None, detect_reyden=False):
288288
if response.status and response.status.statusCode in [
289289
ttypes.TStatusCode.ERROR_STATUS,
290290
ttypes.TStatusCode.INVALID_HANDLE_STATUS,
291291
]:
292292
# A Reyden / Real-Time warehouse rejects the legacy Thrift protocol
293-
# with SQLSTATE KP001. Surface a distinct marker so the connection
294-
# layer can transparently re-open on the kernel backend. host_url is
295-
# deliberately omitted: this is a recoverable signal, not a terminal
296-
# failure, so it must not emit a failure-telemetry event here.
297-
if response.status.sqlState == ReydenThriftUnsupportedError.SQL_STATE:
293+
# with SQLSTATE KP001, but only at OpenSession. `detect_reyden` gates
294+
# the marker to that call so a stray KP001 on any other RPC surfaces
295+
# as a normal DatabaseError (the connection-layer recovery only wraps
296+
# session open). host_url is deliberately omitted on the marker: it is
297+
# a recoverable signal, not a terminal failure, so it must not emit a
298+
# failure-telemetry event here.
299+
if (
300+
detect_reyden
301+
and response.status.sqlState == ReydenThriftUnsupportedError.SQL_STATE
302+
):
298303
raise ReydenThriftUnsupportedError(response.status.errorMessage)
299304
raise DatabaseError(
300305
response.status.errorMessage,
@@ -527,7 +532,14 @@ def attempt_request(attempt):
527532
if not isinstance(response_or_error_info, RequestErrorInfo):
528533
# log nothing here, presume that main request logging covers
529534
response = response_or_error_info
530-
ThriftDatabricksClient._check_response_for_error(response, self._host)
535+
# Only OpenSession opts into KP001→Reyden-marker detection (the
536+
# rejection is stamped only there). Mirrors the method.__name__
537+
# discrimination already used above for GetOperationStatus.
538+
ThriftDatabricksClient._check_response_for_error(
539+
response,
540+
self._host,
541+
detect_reyden=getattr(method, "__name__", None) == "OpenSession",
542+
)
531543
return response
532544

533545
error_info = response_or_error_info

tests/unit/test_thrift_backend.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,47 @@ def test_make_request_checks_status_code(self):
595595
thrift_backend.make_request(lambda _: mock_response, Mock())
596596

597597
def test_reyden_sqlstate_raises_distinct_marker(self):
598+
reyden_resp = Mock()
599+
reyden_resp.status = ttypes.TStatus(
600+
statusCode=ttypes.TStatusCode.ERROR_STATUS,
601+
sqlState=ReydenThriftUnsupportedError.SQL_STATE,
602+
errorMessage="Lakehouse/RT is not supported for Thrift protocol",
603+
)
604+
605+
# KP001 on an ERROR_STATUS → the recoverable Reyden marker, but ONLY when
606+
# detection is enabled (i.e. the OpenSession path).
607+
with self.assertRaises(ReydenThriftUnsupportedError):
608+
ThriftDatabricksClient._check_response_for_error(
609+
reyden_resp, detect_reyden=True
610+
)
611+
612+
# The same KP001 on any other RPC (detect_reyden=False, the default) is a
613+
# generic DatabaseError — the marker is scoped to OpenSession so recovery
614+
# never has to handle it elsewhere.
615+
with self.assertRaises(DatabaseError) as cm:
616+
ThriftDatabricksClient._check_response_for_error(
617+
reyden_resp, detect_reyden=False
618+
)
619+
self.assertNotIsInstance(cm.exception, ReydenThriftUnsupportedError)
620+
621+
# Any other sqlState on an ERROR_STATUS is never the marker, even on the
622+
# OpenSession path.
623+
other_resp = Mock()
624+
other_resp.status = ttypes.TStatus(
625+
statusCode=ttypes.TStatusCode.ERROR_STATUS,
626+
sqlState="42000",
627+
errorMessage="a syntax error",
628+
)
629+
with self.assertRaises(DatabaseError) as cm:
630+
ThriftDatabricksClient._check_response_for_error(
631+
other_resp, detect_reyden=True
632+
)
633+
self.assertNotIsInstance(cm.exception, ReydenThriftUnsupportedError)
634+
635+
def test_reyden_detection_wired_only_for_open_session(self):
636+
# make_request enables Reyden detection based on the RPC method name, so
637+
# a KP001 from OpenSession maps to the marker while the same status from
638+
# any other RPC stays a generic DatabaseError.
598639
thrift_backend = ThriftDatabricksClient(
599640
"foobar",
600641
443,
@@ -604,27 +645,25 @@ def test_reyden_sqlstate_raises_distinct_marker(self):
604645
ssl_options=SSLOptions(),
605646
http_client=MagicMock(),
606647
)
607-
608-
# KP001 on an ERROR_STATUS → the recoverable Reyden marker.
609648
reyden_resp = Mock()
610649
reyden_resp.status = ttypes.TStatus(
611650
statusCode=ttypes.TStatusCode.ERROR_STATUS,
612651
sqlState=ReydenThriftUnsupportedError.SQL_STATE,
613652
errorMessage="Lakehouse/RT is not supported for Thrift protocol",
614653
)
654+
655+
# make_request keys detection off method.__name__.
656+
def OpenSession(_):
657+
return reyden_resp
658+
659+
def ExecuteStatement(_):
660+
return reyden_resp
661+
615662
with self.assertRaises(ReydenThriftUnsupportedError):
616-
thrift_backend.make_request(lambda _: reyden_resp, Mock())
663+
thrift_backend.make_request(OpenSession, Mock())
617664

618-
# Any other sqlState on an ERROR_STATUS → the generic DatabaseError, and
619-
# explicitly NOT the Reyden marker.
620-
other_resp = Mock()
621-
other_resp.status = ttypes.TStatus(
622-
statusCode=ttypes.TStatusCode.ERROR_STATUS,
623-
sqlState="42000",
624-
errorMessage="a syntax error",
625-
)
626665
with self.assertRaises(DatabaseError) as cm:
627-
thrift_backend.make_request(lambda _: other_resp, Mock())
666+
thrift_backend.make_request(ExecuteStatement, Mock())
628667
self.assertNotIsInstance(cm.exception, ReydenThriftUnsupportedError)
629668

630669
def test_handle_execute_response_checks_operation_state_in_direct_results(self):

0 commit comments

Comments
 (0)