Skip to content

Commit eb24409

Browse files
Merge branch 'sea-migration' into sea-decouple-link-fetch
2 parents a618115 + 640cc82 commit eb24409

File tree

5 files changed

+314
-83
lines changed

5 files changed

+314
-83
lines changed

src/databricks/sql/backend/sea/backend.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@
5050

5151

5252
def _filter_session_configuration(
53-
session_configuration: Optional[Dict[str, str]]
54-
) -> Optional[Dict[str, str]]:
53+
session_configuration: Optional[Dict[str, Any]],
54+
) -> Dict[str, str]:
5555
if not session_configuration:
56-
return None
56+
return {}
5757

5858
filtered_session_configuration = {}
5959
ignored_configs: Set[str] = set()
6060

6161
for key, value in session_configuration.items():
6262
if key.upper() in ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP:
63-
filtered_session_configuration[key.lower()] = value
63+
filtered_session_configuration[key.lower()] = str(value)
6464
else:
6565
ignored_configs.add(key)
6666

@@ -190,7 +190,7 @@ def max_download_threads(self) -> int:
190190

191191
def open_session(
192192
self,
193-
session_configuration: Optional[Dict[str, str]],
193+
session_configuration: Optional[Dict[str, Any]],
194194
catalog: Optional[str],
195195
schema: Optional[str],
196196
) -> SessionId:

src/databricks/sql/backend/sea/models/responses.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,7 @@ def _parse_result(data: Dict[str, Any]) -> ResultData:
9494

9595
# Handle attachment field - decode from base64 if present
9696
attachment = result_data.get("attachment")
97-
if attachment is not None and isinstance(attachment, str):
98-
# Decode base64 string to bytes
97+
if attachment is not None:
9998
attachment = base64.b64decode(attachment)
10099

101100
return ResultData(

src/databricks/sql/backend/sea/queue.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from __future__ import annotations
22

33
from abc import ABC
4-
import threading
5-
from typing import Dict, List, Optional, Tuple, Union
4+
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
65

76
from databricks.sql.cloudfetch.download_manager import ResultFileDownloadManager
87

@@ -15,12 +14,13 @@
1514

1615
import dateutil
1716

18-
from databricks.sql.backend.sea.backend import SeaDatabricksClient
19-
from databricks.sql.backend.sea.models.base import (
20-
ExternalLink,
21-
ResultData,
22-
ResultManifest,
23-
)
17+
if TYPE_CHECKING:
18+
from databricks.sql.backend.sea.backend import SeaDatabricksClient
19+
from databricks.sql.backend.sea.models.base import (
20+
ExternalLink,
21+
ResultData,
22+
ResultManifest,
23+
)
2424
from databricks.sql.backend.sea.utils.constants import ResultFormat
2525
from databricks.sql.exc import ProgrammingError, ServerOperationError
2626
from databricks.sql.thrift_api.TCLIService.ttypes import TSparkArrowResultLink

tests/unit/test_sea_backend.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,71 @@ def test_utility_methods(self, sea_client):
624624
assert description[1][1] == "INT" # type_code
625625
assert description[1][6] is False # null_ok
626626

627+
def test_filter_session_configuration(self):
628+
"""Test that _filter_session_configuration converts all values to strings."""
629+
session_config = {
630+
"ANSI_MODE": True,
631+
"statement_timeout": 3600,
632+
"TIMEZONE": "UTC",
633+
"enable_photon": False,
634+
"MAX_FILE_PARTITION_BYTES": 128.5,
635+
"unsupported_param": "value",
636+
"ANOTHER_UNSUPPORTED": 42,
637+
}
638+
639+
result = _filter_session_configuration(session_config)
640+
641+
# Verify result is not None
642+
assert result is not None
643+
644+
# Verify all returned values are strings
645+
for key, value in result.items():
646+
assert isinstance(
647+
value, str
648+
), f"Value for key '{key}' is not a string: {type(value)}"
649+
650+
# Verify specific conversions
651+
expected_result = {
652+
"ansi_mode": "True", # boolean True -> "True", key lowercased
653+
"statement_timeout": "3600", # int -> "3600", key lowercased
654+
"timezone": "UTC", # string -> "UTC", key lowercased
655+
"enable_photon": "False", # boolean False -> "False", key lowercased
656+
"max_file_partition_bytes": "128.5", # float -> "128.5", key lowercased
657+
}
658+
659+
assert result == expected_result
660+
661+
# Test with None input
662+
assert _filter_session_configuration(None) == {}
663+
664+
# Test with only unsupported parameters
665+
unsupported_config = {
666+
"unsupported_param1": "value1",
667+
"unsupported_param2": 123,
668+
}
669+
result = _filter_session_configuration(unsupported_config)
670+
assert result == {}
671+
672+
# Test case insensitivity for keys
673+
case_insensitive_config = {
674+
"ansi_mode": "false", # lowercase key
675+
"STATEMENT_TIMEOUT": 7200, # uppercase key
676+
"TiMeZoNe": "America/New_York", # mixed case key
677+
}
678+
result = _filter_session_configuration(case_insensitive_config)
679+
expected_case_result = {
680+
"ansi_mode": "false",
681+
"statement_timeout": "7200",
682+
"timezone": "America/New_York",
683+
}
684+
assert result == expected_case_result
685+
686+
# Verify all values are strings in case insensitive test
687+
for key, value in result.items():
688+
assert isinstance(
689+
value, str
690+
), f"Value for key '{key}' is not a string: {type(value)}"
691+
627692
def test_results_message_to_execute_response_is_staging_operation(self, sea_client):
628693
"""Test that is_staging_operation is correctly set from manifest.is_volume_operation."""
629694
# Test when is_volume_operation is True
@@ -893,3 +958,67 @@ def test_get_columns(self, sea_client, sea_session_id, mock_cursor):
893958
cursor=mock_cursor,
894959
)
895960
assert "Catalog name is required for get_columns" in str(excinfo.value)
961+
962+
def test_get_chunk_links(self, sea_client, mock_http_client, sea_command_id):
963+
"""Test get_chunk_links method when links are available."""
964+
# Setup mock response
965+
mock_response = {
966+
"external_links": [
967+
{
968+
"external_link": "https://example.com/data/chunk0",
969+
"expiration": "2025-07-03T05:51:18.118009",
970+
"row_count": 100,
971+
"byte_count": 1024,
972+
"row_offset": 0,
973+
"chunk_index": 0,
974+
"next_chunk_index": 1,
975+
"http_headers": {"Authorization": "Bearer token123"},
976+
}
977+
]
978+
}
979+
mock_http_client._make_request.return_value = mock_response
980+
981+
# Call the method
982+
results = sea_client.get_chunk_links("test-statement-123", 0)
983+
984+
# Verify the HTTP client was called correctly
985+
mock_http_client._make_request.assert_called_once_with(
986+
method="GET",
987+
path=sea_client.CHUNK_PATH_WITH_ID_AND_INDEX.format(
988+
"test-statement-123", 0
989+
),
990+
)
991+
992+
# Verify the results
993+
assert isinstance(results, list)
994+
assert len(results) == 1
995+
result = results[0]
996+
assert result.external_link == "https://example.com/data/chunk0"
997+
assert result.expiration == "2025-07-03T05:51:18.118009"
998+
assert result.row_count == 100
999+
assert result.byte_count == 1024
1000+
assert result.row_offset == 0
1001+
assert result.chunk_index == 0
1002+
assert result.next_chunk_index == 1
1003+
assert result.http_headers == {"Authorization": "Bearer token123"}
1004+
1005+
def test_get_chunk_links_empty(self, sea_client, mock_http_client):
1006+
"""Test get_chunk_links when no links are returned (empty list)."""
1007+
# Setup mock response with no matching chunk
1008+
mock_response = {"external_links": []}
1009+
mock_http_client._make_request.return_value = mock_response
1010+
1011+
# Call the method
1012+
results = sea_client.get_chunk_links("test-statement-123", 0)
1013+
1014+
# Verify the HTTP client was called correctly
1015+
mock_http_client._make_request.assert_called_once_with(
1016+
method="GET",
1017+
path=sea_client.CHUNK_PATH_WITH_ID_AND_INDEX.format(
1018+
"test-statement-123", 0
1019+
),
1020+
)
1021+
1022+
# Verify the results are empty
1023+
assert isinstance(results, list)
1024+
assert results == []

0 commit comments

Comments
 (0)