Skip to content

Commit 234e349

Browse files
committed
fix: prevent _cache_dbr_capabilities from caching None-version entries (#1398)
When _query_dbr_version() returns None after a successful connection (e.g. transient failure on `SET spark.databricks.clusterUsageTags.sparkVersion`), _cache_dbr_capabilities would write DBRCapabilities(dbr_version=None) to the class-level cache, and the idempotency guard at the top of the method would block every subsequent re-query — silently disabling every version-gated feature for the life of the process. Apply the same None-guard already present in _try_cache_dbr_capabilities, and switch the downstream read in open() to .get() with a default DBRCapabilities() so the now-possibly-missing entry no longer KeyErrors.
1 parent b1047b5 commit 234e349

3 files changed

Lines changed: 81 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## dbt-databricks next (TBD)
2+
3+
### Fixes
4+
5+
- Fix DBR capability cache being permanently poisoned by a transient version-query failure ([#1398](https://github.com/databricks/dbt-databricks/issues/1398))
6+
17
## dbt-databricks 1.11.7 (Apr 17, 2026)
28

39
### Features

dbt/adapters/databricks/connections.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,11 @@ def _cache_dbr_capabilities(cls, creds: DatabricksCredentials, http_path: str) -
202202
if http_path not in cls._dbr_capabilities_cache:
203203
is_cluster = is_cluster_http_path(http_path, creds.cluster_id)
204204
dbr_version = cls._query_dbr_version(creds, http_path)
205-
206-
cls._dbr_capabilities_cache[http_path] = DBRCapabilities(
207-
dbr_version=dbr_version,
208-
is_sql_warehouse=not is_cluster,
209-
)
205+
if dbr_version is not None:
206+
cls._dbr_capabilities_cache[http_path] = DBRCapabilities(
207+
dbr_version=dbr_version,
208+
is_sql_warehouse=not is_cluster,
209+
)
210210

211211
@classmethod
212212
def _try_cache_dbr_capabilities(cls, creds: DatabricksCredentials, http_path: str) -> None:
@@ -506,9 +506,9 @@ def connect() -> DatabricksHandle:
506506
if conn:
507507
databricks_connection.session_id = conn.session_id
508508
cls._cache_dbr_capabilities(creds, databricks_connection.http_path)
509-
databricks_connection.capabilities = cls._dbr_capabilities_cache[
510-
databricks_connection.http_path
511-
]
509+
databricks_connection.capabilities = cls._dbr_capabilities_cache.get(
510+
databricks_connection.http_path, DBRCapabilities()
511+
)
512512
return conn
513513
else:
514514
raise DbtDatabaseError("Failed to create connection")

tests/unit/test_connection_manager.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,70 @@ def test_skips_write_when_already_cached(self, mock_query):
148148

149149
mock_query.assert_not_called()
150150
assert DatabricksConnectionManager._dbr_capabilities_cache[self.HTTP_PATH] is existing
151+
152+
153+
class TestCacheDbr:
154+
"""Unit tests for _cache_dbr_capabilities."""
155+
156+
HTTP_PATH = "sql/protocolv1/o/1234567890123456/cluster-abc"
157+
158+
@pytest.fixture(autouse=True)
159+
def clear_cache(self):
160+
DatabricksConnectionManager._dbr_capabilities_cache = {}
161+
yield
162+
DatabricksConnectionManager._dbr_capabilities_cache = {}
163+
164+
@patch.object(DatabricksConnectionManager, "_query_dbr_version", return_value=None)
165+
def test_does_not_write_to_cache_when_version_is_none(self, mock_query):
166+
"""Regression for #1398: a None version query result must not poison the cache."""
167+
creds = Mock(spec=DatabricksCredentials)
168+
creds.cluster_id = None
169+
170+
DatabricksConnectionManager._cache_dbr_capabilities(creds, self.HTTP_PATH)
171+
172+
assert self.HTTP_PATH not in DatabricksConnectionManager._dbr_capabilities_cache
173+
mock_query.assert_called_once_with(creds, self.HTTP_PATH)
174+
175+
@patch.object(DatabricksConnectionManager, "_query_dbr_version", return_value=(15, 4))
176+
def test_writes_to_cache_when_version_is_known(self, mock_query):
177+
"""When the version query succeeds, capabilities are cached correctly."""
178+
creds = Mock(spec=DatabricksCredentials)
179+
creds.cluster_id = None
180+
181+
DatabricksConnectionManager._cache_dbr_capabilities(creds, self.HTTP_PATH)
182+
183+
mock_query.assert_called_once_with(creds, self.HTTP_PATH)
184+
caps = DatabricksConnectionManager._dbr_capabilities_cache.get(self.HTTP_PATH)
185+
assert caps is not None
186+
assert caps.dbr_version == (15, 4)
187+
assert not caps.is_sql_warehouse
188+
assert caps.has_capability(DBRCapability.ICEBERG)
189+
190+
@patch.object(DatabricksConnectionManager, "_query_dbr_version", return_value=(15, 4))
191+
def test_skips_write_when_already_cached(self, mock_query):
192+
"""If the path is already in cache, the version query is never made."""
193+
creds = Mock(spec=DatabricksCredentials)
194+
creds.cluster_id = None
195+
existing = DBRCapabilities(dbr_version=(14, 3), is_sql_warehouse=False)
196+
DatabricksConnectionManager._dbr_capabilities_cache[self.HTTP_PATH] = existing
197+
198+
DatabricksConnectionManager._cache_dbr_capabilities(creds, self.HTTP_PATH)
199+
200+
mock_query.assert_not_called()
201+
assert DatabricksConnectionManager._dbr_capabilities_cache[self.HTTP_PATH] is existing
202+
203+
def test_retry_succeeds_after_transient_failure(self):
204+
"""Regression for #1398: after a transient None result, the next call must re-query."""
205+
creds = Mock(spec=DatabricksCredentials)
206+
creds.cluster_id = None
207+
208+
with patch.object(DatabricksConnectionManager, "_query_dbr_version", return_value=None):
209+
DatabricksConnectionManager._cache_dbr_capabilities(creds, self.HTTP_PATH)
210+
211+
with patch.object(DatabricksConnectionManager, "_query_dbr_version", return_value=(16, 2)):
212+
DatabricksConnectionManager._cache_dbr_capabilities(creds, self.HTTP_PATH)
213+
214+
caps = DatabricksConnectionManager._dbr_capabilities_cache.get(self.HTTP_PATH)
215+
assert caps is not None
216+
assert caps.dbr_version == (16, 2)
217+
assert caps.has_capability(DBRCapability.ICEBERG)

0 commit comments

Comments
 (0)