Skip to content

Commit 57c96fc

Browse files
committed
feat: align geospatial support configuration
Signed-off-by: Cathleen Yan <58714163+cathleeny@users.noreply.github.com>
1 parent 2b56603 commit 57c96fc

8 files changed

Lines changed: 56 additions & 40 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Release History
22

33
# Unreleased
4-
- Add the kernel-only `geospatial_as_string` connection option. GEOMETRY / GEOGRAPHY results are exposed as EWKT strings when true or `{"srid": int, "wkb": bytes}` values when false.
4+
- Add the default-enabled, kernel-only `enable_geospatial_support` connection option. GEOMETRY / GEOGRAPHY results are exposed as `{"srid": int, "wkb": bytes}` values when true or WKT / EWKT strings when false.
55

66
# 4.6.0 (2026-09-24)
77
- Upgrade Databricks SQL Kernel to 1.1.0; the kernel dependency is now stable and no longer experimental.

‎CONNECTION_PARAMETERS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ to change without notice.
153153
| `max_download_threads` | `int` | ✅ | ❌ | `10` | Worker threads for cloud-fetch downloads. Not forwarded to the kernel. |
154154
| `enable_query_result_lz4_compression` | `bool` | ✅ | ❌ | `True` | LZ4-compress result payloads. Not forwarded; the kernel handles compression internally. |
155155
| `_disable_pandas` | `bool` | ✅ | ✅ | `False` | Skip the pandas-based Arrow→row deserialization and materialize rows directly with PyArrow. This is a **Python-side** result-conversion toggle, not a wire option: the kernel returns results as Arrow (`RecordBatch`es) and the connector runs the *same* `_convert_arrow_table` for both backends, so the flag is honored on the kernel path too. Affects only row fetches (`fetchone`/`fetchmany`/`fetchall`); the `fetch*_arrow` methods return the Arrow table unchanged regardless of this flag. |
156-
| `geospatial_as_string` | `bool \| None` | ❌ | ✅ | `None` (kernel default: `True`) | Return GEOMETRY / GEOGRAPHY as EWKT strings when `True`, or as `{"srid": int, "wkb": bytes}` values when `False`. `None` leaves the kernel default in force. This is a local result conversion and is never forwarded to SEA. |
156+
| `enable_geospatial_support` | `bool` | ❌ | ✅ | `True` | Return GEOMETRY / GEOGRAPHY as `{"srid": int, "wkb": bytes}` values when `True`, or as WKT / EWKT strings when `False`. This is a local result conversion and is never forwarded to SEA. |
157157
| `_use_arrow_native_complex_types` | `bool` | ✅ | ✅ | `True` | Return `ARRAY`/`MAP`/`STRUCT` as native Arrow types instead of JSON strings. Forwarded to the kernel. |
158158
| `_use_arrow_native_decimals` | `bool` | ✅ | ❌ | `True` | Thrift wire encoding for `DECIMAL`: `True` → native Arrow `decimal128`, `False` → Arrow string. **No value-level effect**, though: the connector unconditionally re-casts the column back to `decimal128` (`convert_decimals_in_arrow_table`, `thrift_backend.py`), so both `fetchall()` and `fetchall_arrow()` yield `Decimal` / `decimal128(p,s)` either way (verified live). Not forwarded to the kernel, which always returns native Arrow decimals. |
159159
| `_use_arrow_native_timestamps` | `bool` | ✅ | ❌ | `True` | Thrift wire encoding for `TIMESTAMP`: `True` → native Arrow timestamp (→ Python `datetime`), `False` → Arrow string (→ Python **`str`**). **Unlike decimals there is no re-cast**, so `False` genuinely surfaces strings — and `cursor.description` still reports the type code as `'timestamp'`, a mismatch to watch for (verified live). Note the connector always also sends the `spark.thriftserver.arrowBasedRowSet.timestampAsString=false` conf, but the `timestampAsArrow=False` flag wins. Not forwarded to the kernel, which always returns native Arrow timestamps. |

‎KERNEL_REV‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ad3bc6993bca95b810839feade77ccd0ab98ece5
1+
b7e9310b27be16a6c42490e58c4b5c4a525320bc

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -169,27 +169,25 @@ def _kernel_session_accepts_kwarg(name: str) -> bool:
169169
return name in params
170170

171171

172-
def _kernel_geospatial_kwargs(value: Optional[bool]) -> Dict[str, bool]:
173-
"""Build the optional geospatial result-representation kwarg.
172+
def _kernel_geospatial_kwargs(value: bool) -> Dict[str, bool]:
173+
"""Build the default-enabled native geospatial-support kwarg.
174174
175-
``None`` deliberately omits the option so the installed kernel owns its
176-
default. An explicit value must never be silently ignored: older kernel
177-
wheels do not declare ``geospatial_as_string`` and would otherwise return a
178-
different public value shape than the caller requested.
175+
The value is always passed explicitly so the driver and kernel contracts
176+
cannot drift. Older kernel wheels do not declare
177+
``enable_geospatial_support`` and must fail clearly rather than silently
178+
return a different public value shape.
179179
"""
180-
if value is None:
181-
return {}
182180
if not isinstance(value, bool):
183181
raise ValueError(
184-
"geospatial_as_string must be a bool or None; "
182+
"enable_geospatial_support must be a bool; "
185183
f"got {type(value).__name__}"
186184
)
187-
if not _kernel_session_accepts_kwarg("geospatial_as_string"):
185+
if not _kernel_session_accepts_kwarg("enable_geospatial_support"):
188186
raise NotSupportedError(
189-
"geospatial_as_string requires a newer databricks-sql-kernel "
187+
"enable_geospatial_support requires a newer databricks-sql-kernel "
190188
"wheel that exposes geospatial result representation support."
191189
)
192-
return {"geospatial_as_string": value}
190+
return {"enable_geospatial_support": value}
193191

194192

195193
def _kernel_telemetry_kwargs(options: Dict[str, Any]) -> Dict[str, Any]:
@@ -285,12 +283,14 @@ def __init__(
285283
# The kernel binding owns type and range validation.
286284
self._request_timeout_secs = kwargs.get("request_timeout_secs")
287285
self._max_connections = kwargs.get("max_connections")
288-
# Client-side result representation for GEOMETRY / GEOGRAPHY. None
289-
# leaves the kernel default in force (EWKT strings); False requests the
290-
# canonical Arrow struct and surfaces as ``{"srid": int, "wkb":
291-
# bytes}`` through pyarrow. This is intentionally separate from
292-
# ``session_configuration``: it is never forwarded to SEA.
293-
self._geospatial_as_string = kwargs.get("geospatial_as_string")
286+
# Default-enabled native GEOMETRY / GEOGRAPHY support. True requests
287+
# the canonical Arrow struct and surfaces as ``{"srid": int, "wkb":
288+
# bytes}`` through pyarrow; False requests WKT / EWKT strings. This is
289+
# intentionally separate from ``session_configuration``: it is never
290+
# forwarded to SEA.
291+
self._enable_geospatial_support = kwargs.get(
292+
"enable_geospatial_support", True
293+
)
294294
# Kernel telemetry phase 7 adds binding/runtime identity and
295295
# telemetry config kwargs directly to ``databricks_sql_kernel.Session``.
296296
self._telemetry_options = kwargs.get("telemetry_options") or {}
@@ -408,7 +408,9 @@ def open_session(
408408
# kernel's ``retry_*`` kwargs. Empty when at defaults.
409409
retry_kwargs = _kernel_retry_kwargs(self._retry_options)
410410
telemetry_kwargs = _kernel_telemetry_kwargs(self._telemetry_options)
411-
geospatial_kwargs = _kernel_geospatial_kwargs(self._geospatial_as_string)
411+
geospatial_kwargs = _kernel_geospatial_kwargs(
412+
self._enable_geospatial_support
413+
)
412414
max_connections_kwargs: Dict[str, Any] = {}
413415
if _kernel_session_accepts_kwarg("max_connections"):
414416
max_connections_kwargs["max_connections"] = self._max_connections

‎src/databricks/sql/client.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,12 @@ def __init__(
194194
decision. This is an intentional divergence from the
195195
Thrift/SEA paths, where an explicit ``True`` can still
196196
be suppressed by the feature flag.
197-
:param geospatial_as_string: `bool | None`, optional (default is None)
197+
:param enable_geospatial_support: `bool`, optional (default is True)
198198
Kernel backend only. Controls the public representation of
199199
``GEOMETRY`` and ``GEOGRAPHY`` result values. ``True`` returns
200-
EWKT strings (for example ``"SRID=4326;POINT(1 2)"``);
201-
``False`` returns ``{"srid": int, "wkb": bytes}``; and
202-
``None`` uses the kernel default (currently EWKT strings).
203-
The conversion is local to the kernel/driver and this option is
200+
``{"srid": int, "wkb": bytes}``; ``False`` returns WKT / EWKT
201+
strings (for example ``"SRID=4326;POINT(1 2)"``). The
202+
conversion is local to the kernel/driver and this option is
204203
never sent to the SQL Execution API.
205204
:param use_hybrid_disposition: `bool`, optional (default is False)
206205
Use the hybrid disposition instead of the inline disposition.

‎src/databricks/sql/session.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,9 @@ def _create_backend(
314314
retry_options=kernel_retry_options,
315315
request_timeout_secs=kwargs.get("_socket_timeout"),
316316
max_connections=kwargs.get("_pool_maxsize") or None,
317-
geospatial_as_string=kwargs.get("geospatial_as_string"),
317+
enable_geospatial_support=kwargs.get(
318+
"enable_geospatial_support", True
319+
),
318320
telemetry_options=kernel_telemetry_options,
319321
)
320322

‎tests/unit/test_kernel_client.py‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -392,14 +392,14 @@ def fake_session(**kw):
392392
assert captured["max_connections"] == max_connections
393393

394394

395-
@pytest.mark.parametrize("as_string", [True, False])
395+
@pytest.mark.parametrize("enabled", [True, False])
396396
def test_open_session_passes_geospatial_representation_to_kernel(
397-
monkeypatch, as_string
397+
monkeypatch, enabled
398398
):
399399
captured = {}
400400

401-
def fake_session(*, geospatial_as_string=None, **kw):
402-
captured["geospatial_as_string"] = geospatial_as_string
401+
def fake_session(*, enable_geospatial_support=True, **kw):
402+
captured["enable_geospatial_support"] = enable_geospatial_support
403403
sess = MagicMock()
404404
sess.session_id = "sess-id"
405405
return sess
@@ -410,15 +410,15 @@ def fake_session(*, geospatial_as_string=None, **kw):
410410
http_path="/sql/1.0/warehouses/abc",
411411
auth_provider=AccessTokenAuthProvider("dapi-test"),
412412
ssl_options=None,
413-
geospatial_as_string=as_string,
413+
enable_geospatial_support=enabled,
414414
)
415415

416416
c.open_session(session_configuration=None, catalog=None, schema=None)
417417

418-
assert captured["geospatial_as_string"] is as_string
418+
assert captured["enable_geospatial_support"] is enabled
419419

420420

421-
def test_open_session_omits_unset_geospatial_representation(monkeypatch):
421+
def test_open_session_enables_geospatial_support_by_default(monkeypatch):
422422
captured = {}
423423

424424
def fake_session(**kw):
@@ -437,7 +437,7 @@ def fake_session(**kw):
437437

438438
c.open_session(session_configuration=None, catalog=None, schema=None)
439439

440-
assert "geospatial_as_string" not in captured
440+
assert captured["enable_geospatial_support"] is True
441441

442442

443443
def test_open_session_rejects_explicit_geospatial_option_with_old_kernel(
@@ -468,15 +468,15 @@ def fake_session_without_geospatial(
468468
http_path="/sql/1.0/warehouses/abc",
469469
auth_provider=AccessTokenAuthProvider("dapi-test"),
470470
ssl_options=None,
471-
geospatial_as_string=False,
471+
enable_geospatial_support=False,
472472
)
473473

474474
with pytest.raises(NotSupportedError, match="newer databricks-sql-kernel"):
475475
c.open_session(session_configuration=None, catalog=None, schema=None)
476476

477477

478478
def test_geospatial_option_rejects_non_bool():
479-
with pytest.raises(ValueError, match="must be a bool or None"):
479+
with pytest.raises(ValueError, match="must be a bool"):
480480
kernel_client._kernel_geospatial_kwargs("false")
481481

482482

@@ -578,6 +578,7 @@ def fake_session_without_optional_kwargs(
578578
catalog=None,
579579
schema=None,
580580
session_conf=None,
581+
enable_geospatial_support=True,
581582
complex_types_as_json=False,
582583
intervals_as_string=False,
583584
request_timeout_secs=None,

‎tests/unit/test_session.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ def test_retry_and_socket_timeout_threaded_into_kernel_client(self):
476476
_retry_stop_after_attempts_duration=600.0,
477477
_socket_timeout=12.5,
478478
_pool_maxsize=41,
479-
geospatial_as_string=False,
479+
enable_geospatial_support=False,
480480
)
481481
try:
482482
_, kwargs = mock_kernel_client.call_args
@@ -487,7 +487,7 @@ def test_retry_and_socket_timeout_threaded_into_kernel_client(self):
487487
assert opts["retry_stop_after_attempts_duration"] == 600.0
488488
assert kwargs["request_timeout_secs"] == 12.5
489489
assert kwargs["max_connections"] == 41
490-
assert kwargs["geospatial_as_string"] is False
490+
assert kwargs["enable_geospatial_support"] is False
491491
finally:
492492
conn.close()
493493

@@ -793,6 +793,18 @@ def test_connect_use_kernel_instantiates_real_kernel_backend(self):
793793
finally:
794794
conn.close()
795795

796+
def test_enable_geospatial_support_matches_real_kernel_signature(self):
797+
self._real_kernel_or_skip()
798+
799+
from databricks.sql.backend.kernel.client import _kernel_geospatial_kwargs
800+
801+
assert _kernel_geospatial_kwargs(True) == {
802+
"enable_geospatial_support": True
803+
}
804+
assert _kernel_geospatial_kwargs(False) == {
805+
"enable_geospatial_support": False
806+
}
807+
796808

797809
class TestReydenThriftFallback:
798810
"""Transparent auto-recovery from a Reyden / Real-Time warehouse rejecting

0 commit comments

Comments
 (0)