Skip to content

Commit a197d58

Browse files
Simplify native transfer setup and focused coverage
Signed-off-by: Aakash Saravanan <aakash.saravanan@databricks.com>
1 parent 85fe971 commit a197d58

2 files changed

Lines changed: 48 additions & 118 deletions

File tree

‎src/databricks/sql/backend/kernel/client.py‎

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -578,28 +578,21 @@ def execute_command(
578578
if staging_verb in ("PUT", "GET")
579579
else None
580580
)
581-
set_paths = getattr(stmt, "set_staging_allowed_local_paths", None)
582-
if callable(set_paths):
583-
if allowed_paths is not None and not isinstance(
584-
allowed_paths, (str, list)
585-
):
586-
raise ProgrammingError(_STAGING_PATHS_TYPE_ERROR)
587-
if isinstance(allowed_paths, str):
588-
allowed_paths = [allowed_paths]
589-
try:
590-
normalized_paths = [
591-
os.fspath(path) for path in allowed_paths or []
592-
]
593-
if any(not isinstance(path, str) for path in normalized_paths):
594-
raise TypeError("Local paths must resolve to strings")
595-
except (TypeError, ValueError) as exc:
596-
raise ProgrammingError(_STAGING_PATHS_TYPE_ERROR) from exc
597-
set_paths(normalized_paths, input_stream_token="__input_stream__")
598-
elif staging_verb is not None:
599-
raise NotSupportedError(
600-
"Volume transfers require a databricks-sql-kernel wheel "
601-
"with staging_allowed_local_paths support."
602-
)
581+
if allowed_paths is not None and not isinstance(
582+
allowed_paths, (str, list)
583+
):
584+
raise ProgrammingError(_STAGING_PATHS_TYPE_ERROR)
585+
if isinstance(allowed_paths, str):
586+
allowed_paths = [allowed_paths]
587+
try:
588+
normalized_paths = [os.fspath(path) for path in allowed_paths or []]
589+
if any(not isinstance(path, str) for path in normalized_paths):
590+
raise TypeError("Local paths must resolve to strings")
591+
except (TypeError, ValueError) as exc:
592+
raise ProgrammingError(_STAGING_PATHS_TYPE_ERROR) from exc
593+
stmt.set_staging_allowed_local_paths(
594+
normalized_paths, input_stream_token="__input_stream__"
595+
)
603596
stmt.set_row_limit(row_limit)
604597
if query_tags:
605598
# Per-statement query tags. The kernel serialises the

‎tests/unit/test_kernel_client.py‎

Lines changed: 33 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import types
2020
from pathlib import Path
2121
from typing import Optional
22-
from unittest.mock import MagicMock, create_autospec
22+
from unittest.mock import MagicMock
2323

2424
import pytest
2525

@@ -809,32 +809,30 @@ def test_execute_command_forwards_row_limit(row_limit):
809809

810810

811811
@pytest.mark.parametrize(
812-
"allowed, expected",
812+
"operation, allowed, expected, async_op",
813813
[
814-
(None, []),
815-
("/tmp/files", ["/tmp/files"]),
816-
([Path("/tmp/a"), Path("/tmp/b")], ["/tmp/a", "/tmp/b"]),
817-
(["/tmp/a", "/tmp/b"], ["/tmp/a", "/tmp/b"]),
818-
],
819-
)
820-
@pytest.mark.parametrize("async_op", [False, True])
821-
@pytest.mark.parametrize(
822-
"operation, uses_paths",
823-
[
824-
("PUT '/tmp/files/input' INTO '/Volumes/c/s/v/input'", True),
825-
("PUT '__INPUT_STREAM__' INTO '/Volumes/c/s/v/input'", True),
826-
("GET '/Volumes/c/s/v/input' TO '/tmp/files/output'", True),
827-
("/* download */ GET '/Volumes/c/s/v/input' TO '/tmp/files/output'", True),
814+
("PUT 'input' INTO '/Volumes/c/s/v/file'", None, [], False),
815+
("PUT 'input' INTO '/Volumes/c/s/v/file'", "/tmp/files", ["/tmp/files"], False),
816+
("GET '/Volumes/c/s/v/file' TO 'output'", [], [], False),
817+
(
818+
"GET '/Volumes/c/s/v/file' TO 'output'",
819+
[Path("/tmp/a"), Path("/tmp/b")],
820+
["/tmp/a", "/tmp/b"],
821+
False,
822+
),
828823
(
829-
"/* outer /* inner */ outer */ GET '/Volumes/c/s/v/input' TO '/tmp/files/output'",
824+
"/* outer /* inner */ outer */ GET '/Volumes/c/s/v/file' TO 'output'",
825+
["/tmp/a", "/tmp/b"],
826+
["/tmp/a", "/tmp/b"],
830827
True,
831828
),
832-
("REMOVE '/Volumes/c/s/v/input'", False),
833-
("SELECT 1", False),
829+
("PUT 'input' INTO '/Volumes/c/s/v/file'", "/tmp/files", ["/tmp/files"], True),
830+
("REMOVE '/Volumes/c/s/v/file'", object(), [], False),
831+
("SELECT 1", object(), [], False),
834832
],
835833
)
836834
def test_execute_command_forwards_allowed_local_paths(
837-
allowed, expected, async_op, operation, uses_paths
835+
operation, allowed, expected, async_op
838836
):
839837
c = _make_client()
840838
c._kernel_session = MagicMock()
@@ -844,12 +842,6 @@ def test_execute_command_forwards_allowed_local_paths(
844842
cursor.buffer_size_bytes = 1024
845843
stmt = c._kernel_session.statement.return_value
846844

847-
def set_staging_allowed_local_paths(paths=None, *, input_stream_token=None):
848-
pass
849-
850-
stmt.set_staging_allowed_local_paths = create_autospec(
851-
set_staging_allowed_local_paths
852-
)
853845
executed = MagicMock(
854846
statement_id="00000000-0000-0000-0000-000000000001",
855847
arrow_schema=MagicMock(return_value=pa.schema([("x", pa.int64())])),
@@ -869,7 +861,7 @@ def set_staging_allowed_local_paths(paths=None, *, input_stream_token=None):
869861
enforce_embedded_schema_correctness=False,
870862
)
871863
stmt.set_staging_allowed_local_paths.assert_called_once_with(
872-
expected if uses_paths else [], input_stream_token="__input_stream__"
864+
expected, input_stream_token="__input_stream__"
873865
)
874866
(stmt.submit if async_op else stmt.execute).assert_called_once()
875867

@@ -879,24 +871,12 @@ def set_staging_allowed_local_paths(paths=None, *, input_stream_token=None):
879871
[
880872
123,
881873
[object()],
882-
[b"/tmp/files"],
883874
["/tmp/files", b"/tmp/other"],
884875
Path("/tmp/files"),
885876
("/tmp/files",),
886-
(),
887-
{"/tmp/files": True},
888-
],
889-
)
890-
@pytest.mark.parametrize(
891-
"operation",
892-
[
893-
"SELECT 1",
894-
"PUT 'input' INTO '/Volumes/c/s/v/file'",
895-
"GET '/Volumes/c/s/v/file' TO 'output'",
896-
"REMOVE '/Volumes/c/s/v/file'",
897877
],
898878
)
899-
def test_malformed_local_paths_only_reject_transfers(allowed, operation):
879+
def test_execute_command_rejects_malformed_local_paths(allowed):
900880
c = _make_client()
901881
c._kernel_session = MagicMock()
902882
cursor = MagicMock()
@@ -907,22 +887,21 @@ def test_malformed_local_paths_only_reject_transfers(allowed, operation):
907887
arrow_schema=MagicMock(return_value=pa.schema([("x", pa.int64())])),
908888
)
909889

910-
def execute():
890+
with pytest.raises(ProgrammingError, match="staging_allowed_local_path"):
911891
c.execute_command(
912-
operation, MagicMock(), 1, 1, False, cursor, False, [], False, False
913-
)
914-
915-
if operation.startswith(("PUT", "GET")):
916-
with pytest.raises(ProgrammingError, match="staging_allowed_local_path"):
917-
execute()
918-
stmt.execute.assert_not_called()
919-
stmt.set_staging_allowed_local_paths.assert_not_called()
920-
else:
921-
execute()
922-
stmt.set_staging_allowed_local_paths.assert_called_once_with(
923-
[], input_stream_token="__input_stream__"
892+
"PUT 'input' INTO '/Volumes/c/s/v/file'",
893+
MagicMock(),
894+
1,
895+
1,
896+
False,
897+
cursor,
898+
False,
899+
[],
900+
False,
901+
False,
924902
)
925-
stmt.execute.assert_called_once()
903+
stmt.execute.assert_not_called()
904+
stmt.set_staging_allowed_local_paths.assert_not_called()
926905

927906

928907
@pytest.mark.parametrize(
@@ -987,48 +966,6 @@ def test_kernel_rejects_decoded_streaming_put():
987966
stmt.close.assert_called_once()
988967

989968

990-
@pytest.mark.parametrize(
991-
"operation",
992-
[
993-
"SELECT 1",
994-
"PUT 'a' INTO 'b'",
995-
"-- comment\nGET 'a' TO 'b'",
996-
"/* comment */ REMOVE 'a'",
997-
"GET'/Volumes/c/s/v/file' TO '/tmp/file'",
998-
"/* comment */ REMOVE'/Volumes/c/s/v/file'",
999-
],
1000-
)
1001-
def test_execute_command_with_older_kernel(operation):
1002-
c = _make_client()
1003-
c._kernel_session = MagicMock()
1004-
stmt = c._kernel_session.statement.return_value
1005-
del stmt.set_staging_allowed_local_paths
1006-
stmt.execute.return_value = MagicMock(
1007-
statement_id="00000000-0000-0000-0000-000000000001",
1008-
arrow_schema=MagicMock(return_value=pa.schema([("x", pa.int64())])),
1009-
)
1010-
kwargs = dict(
1011-
operation=operation,
1012-
session_id=MagicMock(),
1013-
max_rows=1,
1014-
max_bytes=1,
1015-
lz4_compression=False,
1016-
cursor=MagicMock(),
1017-
use_cloud_fetch=False,
1018-
parameters=[],
1019-
async_op=False,
1020-
enforce_embedded_schema_correctness=False,
1021-
)
1022-
if operation == "SELECT 1":
1023-
c.execute_command(**kwargs)
1024-
stmt.execute.assert_called_once()
1025-
else:
1026-
with pytest.raises(NotSupportedError, match="Volume transfers require"):
1027-
c.execute_command(**kwargs)
1028-
stmt.execute.assert_not_called()
1029-
stmt.close.assert_called_once()
1030-
1031-
1032969
# ---------------------------------------------------------------------------
1033970
# Sync cancel wiring (cursor.cancel() during a blocking execute())
1034971
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)