Skip to content

Commit 76e60fe

Browse files
committed
Merge branch 'telemetry' into telemetry-testing
2 parents 67a8497 + 97df72e commit 76e60fe

File tree

9 files changed

+113
-278
lines changed

9 files changed

+113
-278
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Release History
22

3+
# 4.0.5 (2025-06-24)
4+
- Fix: Reverted change in cursor close handling which led to errors impacting users (databricks/databricks-sql-python#613 by @madhav-db)
5+
36
# 4.0.4 (2025-06-16)
47

58
- Update thrift client library after cleaning up unused fields and structs (databricks/databricks-sql-python#553 by @vikrantpuppala)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "databricks-sql-connector"
3-
version = "4.0.4"
3+
version = "4.0.5"
44
description = "Databricks SQL Connector for Python"
55
authors = ["Databricks <databricks-sql-connector-maintainers@databricks.com>"]
66
license = "Apache-2.0"

src/databricks/sql/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __repr__(self):
6868
DATE = DBAPITypeObject("date")
6969
ROWID = DBAPITypeObject()
7070

71-
__version__ = "4.0.4"
71+
__version__ = "4.0.5"
7272
USER_AGENT_NAME = "PyDatabricksSqlConnector"
7373

7474
# These two functions are pyhive legacy

src/databricks/sql/client.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,7 @@ def __enter__(self) -> "Connection":
363363
return self
364364

365365
def __exit__(self, exc_type, exc_value, traceback):
366-
try:
367-
self.close()
368-
except BaseException as e:
369-
logger.warning(f"Exception during connection close in __exit__: {e}")
370-
if exc_type is None:
371-
raise
372-
return False
366+
self.close()
373367

374368
def __del__(self):
375369
if self.open:
@@ -518,14 +512,7 @@ def __enter__(self) -> "Cursor":
518512
return self
519513

520514
def __exit__(self, exc_type, exc_value, traceback):
521-
try:
522-
logger.debug("Cursor context manager exiting, calling close()")
523-
self.close()
524-
except BaseException as e:
525-
logger.warning(f"Exception during cursor close in __exit__: {e}")
526-
if exc_type is None:
527-
raise
528-
return False
515+
self.close()
529516

530517
def __iter__(self):
531518
if self.active_result_set:
@@ -1269,21 +1256,7 @@ def cancel(self) -> None:
12691256
def close(self) -> None:
12701257
"""Close cursor"""
12711258
self.open = False
1272-
1273-
# Close active operation handle if it exists
1274-
if self.active_op_handle:
1275-
try:
1276-
self.thrift_backend.close_command(self.active_op_handle)
1277-
except RequestError as e:
1278-
if isinstance(e.args[1], CursorAlreadyClosedError):
1279-
logger.info("Operation was canceled by a prior request")
1280-
else:
1281-
logging.warning(f"Error closing operation handle: {e}")
1282-
except Exception as e:
1283-
logging.warning(f"Error closing operation handle: {e}")
1284-
finally:
1285-
self.active_op_handle = None
1286-
1259+
self.active_op_handle = None
12871260
if self.active_result_set:
12881261
self._close_and_clear_active_result_set()
12891262

tests/e2e/common/large_queries_mixin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ def test_query_with_large_narrow_result_set(self):
8383
assert row[0] == row_id
8484

8585
def test_long_running_query(self):
86-
"""Incrementally increase query size until it takes at least 5 minutes,
86+
"""Incrementally increase query size until it takes at least 3 minutes,
8787
and asserts that the query completes successfully.
8888
"""
8989
minutes = 60
90-
min_duration = 5 * minutes
90+
min_duration = 3 * minutes
9191

9292
duration = -1
9393
scale0 = 10000
@@ -113,5 +113,5 @@ def test_long_running_query(self):
113113
duration = time.time() - start
114114
current_fraction = duration / min_duration
115115
print("Took {} s with scale factor={}".format(duration, scale_factor))
116-
# Extrapolate linearly to reach 5 min and add 50% padding to push over the limit
116+
# Extrapolate linearly to reach 3 min and add 50% padding to push over the limit
117117
scale_factor = math.ceil(1.5 * scale_factor / current_fraction)

tests/e2e/common/staging_ingestion_tests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_staging_ingestion_life_cycle(self, ingestion_user):
4646
) as conn:
4747

4848
cursor = conn.cursor()
49-
query = f"PUT '{temp_path}' INTO 'stage://tmp/{ingestion_user}/tmp/11/15/file1.csv' OVERWRITE"
49+
query = f"PUT '{temp_path}' INTO 'stage://tmp/{ingestion_user}/tmp/11/16/file1.csv' OVERWRITE"
5050
cursor.execute(query)
5151

5252
# GET should succeed
@@ -57,7 +57,7 @@ def test_staging_ingestion_life_cycle(self, ingestion_user):
5757
extra_params={"staging_allowed_local_path": new_temp_path}
5858
) as conn:
5959
cursor = conn.cursor()
60-
query = f"GET 'stage://tmp/{ingestion_user}/tmp/11/15/file1.csv' TO '{new_temp_path}'"
60+
query = f"GET 'stage://tmp/{ingestion_user}/tmp/11/16/file1.csv' TO '{new_temp_path}'"
6161
cursor.execute(query)
6262

6363
with open(new_fh, "rb") as fp:
@@ -67,7 +67,7 @@ def test_staging_ingestion_life_cycle(self, ingestion_user):
6767

6868
# REMOVE should succeed
6969

70-
remove_query = f"REMOVE 'stage://tmp/{ingestion_user}/tmp/11/15/file1.csv'"
70+
remove_query = f"REMOVE 'stage://tmp/{ingestion_user}/tmp/11/16/file1.csv'"
7171

7272
with self.connection(extra_params={"staging_allowed_local_path": "/"}) as conn:
7373
cursor = conn.cursor()
@@ -79,7 +79,7 @@ def test_staging_ingestion_life_cycle(self, ingestion_user):
7979
Error, match="Staging operation over HTTP was unsuccessful: 404"
8080
):
8181
cursor = conn.cursor()
82-
query = f"GET 'stage://tmp/{ingestion_user}/tmp/11/15/file1.csv' TO '{new_temp_path}'"
82+
query = f"GET 'stage://tmp/{ingestion_user}/tmp/11/16/file1.csv' TO '{new_temp_path}'"
8383
cursor.execute(query)
8484

8585
os.remove(temp_path)

tests/e2e/test_driver.py

Lines changed: 1 addition & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
from tests.e2e.common.uc_volume_tests import PySQLUCVolumeTestSuiteMixin
5252

53-
from databricks.sql.exc import SessionAlreadyClosedError, CursorAlreadyClosedError
53+
from databricks.sql.exc import SessionAlreadyClosedError
5454

5555
log = logging.getLogger(__name__)
5656

@@ -808,146 +808,6 @@ def test_catalogs_returns_arrow_table(self):
808808
results = cursor.fetchall_arrow()
809809
assert isinstance(results, pyarrow.Table)
810810

811-
def test_close_connection_closes_cursors(self):
812-
813-
from databricks.sql.thrift_api.TCLIService import ttypes
814-
815-
with self.connection() as conn:
816-
cursor = conn.cursor()
817-
cursor.execute(
818-
"SELECT id, id `id2`, id `id3` FROM RANGE(1000000) order by RANDOM()"
819-
)
820-
ars = cursor.active_result_set
821-
822-
# We must manually run this check because thrift_backend always forces `has_been_closed_server_side` to True
823-
# Cursor op state should be open before connection is closed
824-
status_request = ttypes.TGetOperationStatusReq(
825-
operationHandle=ars.command_id, getProgressUpdate=False
826-
)
827-
op_status_at_server = ars.thrift_backend._client.GetOperationStatus(
828-
status_request
829-
)
830-
assert (
831-
op_status_at_server.operationState
832-
!= ttypes.TOperationState.CLOSED_STATE
833-
)
834-
835-
conn.close()
836-
837-
# When connection closes, any cursor operations should no longer exist at the server
838-
with pytest.raises(SessionAlreadyClosedError) as cm:
839-
op_status_at_server = ars.thrift_backend._client.GetOperationStatus(
840-
status_request
841-
)
842-
843-
def test_closing_a_closed_connection_doesnt_fail(self, caplog):
844-
caplog.set_level(logging.DEBUG)
845-
# Second .close() call is when this context manager exits
846-
with self.connection() as conn:
847-
# First .close() call is explicit here
848-
conn.close()
849-
assert "Session appears to have been closed already" in caplog.text
850-
851-
conn = None
852-
try:
853-
with pytest.raises(KeyboardInterrupt):
854-
with self.connection() as c:
855-
conn = c
856-
raise KeyboardInterrupt("Simulated interrupt")
857-
finally:
858-
if conn is not None:
859-
assert (
860-
not conn.open
861-
), "Connection should be closed after KeyboardInterrupt"
862-
863-
def test_cursor_close_properly_closes_operation(self):
864-
"""Test that Cursor.close() properly closes the active operation handle on the server."""
865-
with self.connection() as conn:
866-
cursor = conn.cursor()
867-
try:
868-
cursor.execute("SELECT 1 AS test")
869-
assert cursor.active_op_handle is not None
870-
cursor.close()
871-
assert cursor.active_op_handle is None
872-
assert not cursor.open
873-
finally:
874-
if cursor.open:
875-
cursor.close()
876-
877-
conn = None
878-
cursor = None
879-
try:
880-
with self.connection() as c:
881-
conn = c
882-
with pytest.raises(KeyboardInterrupt):
883-
with conn.cursor() as cur:
884-
cursor = cur
885-
raise KeyboardInterrupt("Simulated interrupt")
886-
finally:
887-
if cursor is not None:
888-
assert (
889-
not cursor.open
890-
), "Cursor should be closed after KeyboardInterrupt"
891-
892-
def test_nested_cursor_context_managers(self):
893-
"""Test that nested cursor context managers properly close operations on the server."""
894-
with self.connection() as conn:
895-
with conn.cursor() as cursor1:
896-
cursor1.execute("SELECT 1 AS test1")
897-
assert cursor1.active_op_handle is not None
898-
899-
with conn.cursor() as cursor2:
900-
cursor2.execute("SELECT 2 AS test2")
901-
assert cursor2.active_op_handle is not None
902-
903-
# After inner context manager exit, cursor2 should be not open
904-
assert not cursor2.open
905-
assert cursor2.active_op_handle is None
906-
907-
# After outer context manager exit, cursor1 should be not open
908-
assert not cursor1.open
909-
assert cursor1.active_op_handle is None
910-
911-
def test_cursor_error_handling(self):
912-
"""Test that cursor close handles errors properly to prevent orphaned operations."""
913-
with self.connection() as conn:
914-
cursor = conn.cursor()
915-
916-
cursor.execute("SELECT 1 AS test")
917-
918-
op_handle = cursor.active_op_handle
919-
920-
assert op_handle is not None
921-
922-
# Manually close the operation to simulate server-side closure
923-
conn.thrift_backend.close_command(op_handle)
924-
925-
cursor.close()
926-
927-
assert not cursor.open
928-
929-
def test_result_set_close(self):
930-
"""Test that ResultSet.close() properly closes operations on the server and handles state correctly."""
931-
with self.connection() as conn:
932-
cursor = conn.cursor()
933-
try:
934-
cursor.execute("SELECT * FROM RANGE(10)")
935-
936-
result_set = cursor.active_result_set
937-
assert result_set is not None
938-
939-
initial_op_state = result_set.op_state
940-
941-
result_set.close()
942-
943-
assert result_set.op_state == result_set.thrift_backend.CLOSED_OP_STATE
944-
assert result_set.op_state != initial_op_state
945-
946-
# Closing the result set again should be a no-op and not raise exceptions
947-
result_set.close()
948-
finally:
949-
cursor.close()
950-
951811

952812
# use a RetrySuite to encapsulate these tests which we'll typically want to run together; however keep
953813
# the 429/503 subsuites separate since they execute under different circumstances.

0 commit comments

Comments
 (0)