Skip to content

Commit b0643b3

Browse files
committed
fix(kernel): honor use_cloud_fetch
Signed-off-by: Vu Anh Phung <vu.phung@databricks.com>
1 parent 8f4daee commit b0643b3

5 files changed

Lines changed: 121 additions & 9 deletions

File tree

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ def __init__(
230230
self._use_arrow_native_complex_types = kwargs.get(
231231
"_use_arrow_native_complex_types", True
232232
)
233+
# This is a connection option: the kernel fixes the SEA result
234+
# disposition policy for the lifetime of its session.
235+
self._use_cloud_fetch = bool(kwargs.get("use_cloud_fetch", True))
233236
# NB: don't call ``kernel_auth_kwargs`` here. That call
234237
# materialises the bearer token in-process; keeping a
235238
# cleartext copy on a long-lived connector object that may
@@ -293,12 +296,18 @@ def open_session(
293296
) -> SessionId:
294297
if self._kernel_session is not None:
295298
raise InterfaceError("KernelDatabricksClient already has an open session.")
296-
# ``session_configuration`` flows through to the kernel's
297-
# ``session_conf`` map verbatim; the SEA endpoint enforces
298-
# its own allow-list and rejects unknown keys.
299-
session_conf: Optional[Dict[str, str]] = None
300-
if session_configuration:
301-
session_conf = {k: str(v) for k, v in session_configuration.items()}
299+
# Convert server session confs to strings, then add the kernel's
300+
# client-side CloudFetch knob to the same boundary map.
301+
session_conf = (
302+
{k: str(v) for k, v in session_configuration.items()}
303+
if session_configuration
304+
else {}
305+
)
306+
# The kernel consumes this before filtering the server confs and
307+
# selects INLINE when CloudFetch is disabled.
308+
session_conf["cloudfetch_enabled"] = (
309+
"true" if self._use_cloud_fetch else "false"
310+
)
302311
# The kwarg builds run INSIDE the try so the ``finally`` scrub
303312
# below always fires — including when ``kernel_auth_kwargs``
304313
# itself raises mid-build (e.g. an OAuth token-exchange failure

src/databricks/sql/session.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def _create_backend(
204204
http_client=self.http_client,
205205
catalog=kwargs.get("catalog"),
206206
schema=kwargs.get("schema"),
207+
use_cloud_fetch=kwargs.get("use_cloud_fetch", True),
207208
_use_arrow_native_complex_types=_use_arrow_native_complex_types,
208209
auth_options=kernel_auth_options,
209210
retry_options=kernel_retry_options,

tests/e2e/test_kernel_backend.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from __future__ import annotations
2323

24+
import logging
2425
import sys
2526
from uuid import uuid4
2627

@@ -163,6 +164,30 @@ def test_drain_large_range_to_arrow(conn):
163164
assert len(rows) == 10000
164165

165166

167+
@pytest.mark.realkernel
168+
def test_use_cloud_fetch_false_uses_inline_results(kernel_conn_params, caplog):
169+
"""The real wheel consumes the client knob and selects inline results."""
170+
params = dict(kernel_conn_params)
171+
params["use_cloud_fetch"] = False
172+
173+
with caplog.at_level(logging.INFO, logger="databricks.sql.kernel"):
174+
with sql.connect(**params) as c:
175+
with c.cursor() as cur:
176+
# Large enough to exercise multi-chunk inline delivery.
177+
cur.execute("SELECT * FROM range(5000000)")
178+
assert cur.fetchmany(1)[0][0] == 0
179+
180+
messages = [
181+
record.getMessage()
182+
for record in caplog.records
183+
if record.name.startswith("databricks.sql.kernel")
184+
]
185+
assert any("Using inline" in message for message in messages), messages
186+
assert not any(
187+
"Using CloudFetch reader" in message for message in messages
188+
), messages
189+
190+
166191
def test_fetchmany_pacing(conn):
167192
"""fetchmany honours the requested size and stops cleanly at
168193
end-of-stream — covers the buffer-slicing logic in
@@ -194,9 +219,6 @@ def test_fetchall_arrow(conn):
194219
# `databricks.sql.kernel.pyo3`. If the kernel's tracing target or the
195220
# pyo3-log wiring ever drifts, these fail.
196221

197-
import logging
198-
199-
200222
def test_kernel_logs_reach_python_logging(kernel_conn_params, caplog):
201223
"""A query at DEBUG produces records on the `databricks.sql.kernel`
202224
logger — proving the tracing -> log -> pyo3-log -> logging chain."""

tests/unit/test_kernel_client.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,48 @@ def fake_session(**kw):
344344
assert captured.get("complex_types_as_json") is expected_flag
345345

346346

347+
@pytest.mark.parametrize(
348+
"client_kwargs, expected",
349+
[
350+
({}, "true"),
351+
({"use_cloud_fetch": True}, "true"),
352+
({"use_cloud_fetch": False}, "false"),
353+
({"use_cloud_fetch": None}, "false"),
354+
({"use_cloud_fetch": "false"}, "true"),
355+
],
356+
)
357+
def test_open_session_passes_cloud_fetch_setting_to_kernel(
358+
monkeypatch, client_kwargs, expected
359+
):
360+
captured = {}
361+
362+
def fake_session(**kw):
363+
captured.update(kw)
364+
sess = MagicMock()
365+
sess.session_id = "sess-id"
366+
return sess
367+
368+
monkeypatch.setattr(kernel_client._kernel, "Session", fake_session)
369+
370+
c = kernel_client.KernelDatabricksClient(
371+
server_hostname="example.cloud.databricks.com",
372+
http_path="/sql/1.0/warehouses/abc",
373+
auth_provider=AccessTokenAuthProvider("dapi-test"),
374+
ssl_options=None,
375+
**client_kwargs,
376+
)
377+
c.open_session(
378+
session_configuration={"ANSI_MODE": "false"},
379+
catalog=None,
380+
schema=None,
381+
)
382+
383+
assert captured["session_conf"] == {
384+
"ANSI_MODE": "false",
385+
"cloudfetch_enabled": expected,
386+
}
387+
388+
347389
def test_execute_command_forwards_parameters_to_bind_param():
348390
"""``execute_command(parameters=[...])`` routes each parameter
349391
through ``bind_tspark_params`` onto the kernel statement before

tests/unit/test_session.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,44 @@ def test_retry_kwargs_threaded_into_kernel_client(self):
478478
conn.close()
479479

480480

481+
class TestKernelCloudFetchThreading:
482+
def test_use_cloud_fetch_threaded_into_kernel_client(self):
483+
import sys
484+
import types
485+
486+
pytest.importorskip(
487+
"pyarrow",
488+
reason="kernel client module imports pyarrow at load",
489+
)
490+
491+
fake = types.ModuleType("databricks_sql_kernel")
492+
fake.KernelError = type("KernelError", (Exception,), {})
493+
fake.Session = MagicMock()
494+
495+
with patch.dict(sys.modules, {"databricks_sql_kernel": fake}), patch(
496+
"databricks.sql.backend.kernel.client.KernelDatabricksClient"
497+
) as mock_kernel_client, patch(
498+
"databricks.sql.session.get_python_sql_connector_auth_provider"
499+
):
500+
instance = mock_kernel_client.return_value
501+
instance.open_session.return_value = SessionId(
502+
BackendType.SEA, "sess-id", None
503+
)
504+
505+
conn = databricks.sql.connect(
506+
server_hostname="foo",
507+
http_path="/sql/1.0/warehouses/abc",
508+
use_kernel=True,
509+
use_cloud_fetch=False,
510+
access_token="dapi-xyz",
511+
enable_telemetry=False,
512+
)
513+
try:
514+
assert mock_kernel_client.call_args.kwargs["use_cloud_fetch"] is False
515+
finally:
516+
conn.close()
517+
518+
481519
class TestKernelUserAgentForwarding:
482520
"""user_agent_entry must reach the kernel on the use_kernel path —
483521
session.py folds it into the composed User-Agent and includes it in

0 commit comments

Comments
 (0)