Skip to content

Commit b277f4a

Browse files
aahelsd-db
andauthored
feat: catalogs.yml v2 support via bridge_v2_catalog hooks (#1440)
## Summary Adds catalogs.yml v2 support to dbt-databricks using the adapter-owned bridge architecture. ### What changed **`impl.py`** - Declares `Capability.CatalogsV2` — enables v2 path when `use_catalogs_v2: true` is set in `dbt_project.yml` - Adds `_v2_to_v1_type` hook: `unity → unity`, `hive_metastore → hive_metastore` **`catalogs/_unity.py`** - Adeed warnings if `use_uniform` is set in config properties - `location_root` must be non-blank if provided **`catalogs/_hive_metastore.py`** - Adds `file_format` enum validation in `HiveMetastoreCatalogIntegration.__init__`: must be one of `delta | parquet | hudi` ### Architecture dbt-core parses `catalogs.yml` v2 structurally, checks `Capability.CatalogsV2`, then calls `adapter.bridge_v2_catalog(catalog)`. The base adapter template method reads `catalog.config.get("databricks", {})` and calls `_v2_to_v1_type` to resolve the v1 catalog_type. Field-level validation happens in `CatalogIntegration.__init__` — same as v1, no new interface. Companion PRs: - dbt-core: dbt-labs/dbt-core#12930 - dbt-adapters: dbt-labs/dbt-adapters#1920 ## Test plan - [x] Unit tests for `UnityCatalogIntegration.__init__` validation - [x] Unit tests for `HiveMetastoreCatalogIntegration.__init__` validation - [ ] Functional tests end-to-end once dbt-core and dbt-adapters PRs are merged --------- Co-authored-by: Shubham Dhal <shubham.dhal@databricks.com>
1 parent d36904a commit b277f4a

4 files changed

Lines changed: 112 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## dbt-databricks 1.12.2 (TBD)
22

3+
### Features
4+
5+
- Add catalogs.yml v2 support (requires `use_catalogs_v2: true` in dbt-core) ([1440](https://github.com/databricks/dbt-databricks/pull/1440))
6+
37
### Under the Hood
48

59
- Raise the `dbt-adapters` upper bound to `<1.25.0` ([#1507](https://github.com/databricks/dbt-databricks/pull/1507))

dbt/adapters/databricks/catalogs/_unity.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from dbt.adapters.catalogs import CatalogIntegration, CatalogIntegrationConfig
44
from dbt.adapters.contracts.relation import RelationConfig
5+
from dbt_common.exceptions import DbtValidationError
56

67
from dbt.adapters.databricks import constants, parse_model
78
from dbt.adapters.databricks.catalogs._relation import DatabricksCatalogRelation
9+
from dbt.adapters.databricks.logging import logger
810

911

1012
class UnityCatalogIntegration(CatalogIntegration):
@@ -13,9 +15,20 @@ class UnityCatalogIntegration(CatalogIntegration):
1315

1416
def __init__(self, config: CatalogIntegrationConfig) -> None:
1517
super().__init__(config)
16-
if location_root := config.adapter_properties.get("location_root"):
18+
location_root = config.adapter_properties.get("location_root")
19+
if location_root is not None:
20+
if not str(location_root).strip():
21+
raise DbtValidationError(
22+
f"Catalog '{config.name}' unity/databricks location_root cannot be blank"
23+
)
1724
self.external_volume: Optional[str] = location_root
1825
self.file_format: Optional[str] = config.file_format
26+
if config.adapter_properties.get("use_uniform") is not None:
27+
logger.warning(
28+
f"Catalog '{config.name}': use_uniform is not yet supported by the adapter "
29+
"and has no effect. Use the use_managed_iceberg behavior flag to control "
30+
"Iceberg table creation. Support for use_uniform will be added in a future release."
31+
)
1932

2033
@property
2134
def location_root(self) -> Optional[str]:

dbt/adapters/databricks/impl.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ def get_identifier_list_string(table_names: set[str]) -> str:
236236
return _identifier
237237

238238

239+
def _adapter_capabilities() -> CapabilityDict:
240+
capabilities: dict[Capability, CapabilitySupport] = {
241+
Capability.TableLastModifiedMetadata: CapabilitySupport(support=Support.Full),
242+
Capability.SchemaMetadataByRelations: CapabilitySupport(support=Support.Full),
243+
}
244+
catalogs_v2 = getattr(Capability, "CatalogsV2", None)
245+
if catalogs_v2 is not None:
246+
capabilities[catalogs_v2] = CapabilitySupport(support=Support.Full)
247+
return CapabilityDict(capabilities)
248+
249+
239250
class DatabricksAdapter(SparkAdapter):
240251
INFORMATION_COMMENT_REGEX = re.compile(r"Comment: (.*)\n[A-Z][A-Za-z ]+:", re.DOTALL)
241252

@@ -248,17 +259,16 @@ class DatabricksAdapter(SparkAdapter):
248259

249260
AdapterSpecificConfigs = DatabricksConfig # type: ignore[assignment]
250261

251-
_capabilities = CapabilityDict(
252-
{
253-
Capability.TableLastModifiedMetadata: CapabilitySupport(support=Support.Full),
254-
Capability.SchemaMetadataByRelations: CapabilitySupport(support=Support.Full),
255-
}
256-
)
262+
_capabilities = _adapter_capabilities()
257263

258264
CATALOG_INTEGRATIONS = [
259265
HiveMetastoreCatalogIntegration,
260266
UnityCatalogIntegration,
261267
]
268+
_V2_TO_V1_TYPE: ClassVar[dict[str, str]] = {
269+
"unity": constants.UNITY_CATALOG_TYPE,
270+
"hive_metastore": constants.HIVE_METASTORE_CATALOG_TYPE,
271+
}
262272
CONSTRAINT_SUPPORT = constraints.CONSTRAINT_SUPPORT
263273

264274
get_column_behavior: GetColumnsBehavior
@@ -297,6 +307,9 @@ def _has_dbr_capability_parse(self, capability_name: str) -> bool:
297307
return False
298308
return DBRCapabilities(is_sql_warehouse=True).has_capability(capability)
299309

310+
def _v2_to_v1_type(self, catalog_type: str) -> str:
311+
return self._V2_TO_V1_TYPE.get(catalog_type, catalog_type)
312+
300313
@property
301314
def _behavior_flags(self) -> list[BehaviorFlag]:
302315
return [

tests/unit/test_catalogs_v2.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from dataclasses import dataclass, field
2+
from typing import Any, Optional
3+
4+
import pytest
5+
from dbt.adapters.capability import Capability, Support
6+
from dbt_common.exceptions import DbtValidationError
7+
8+
from dbt.adapters.databricks.catalogs import UnityCatalogIntegration
9+
from dbt.adapters.databricks.impl import DatabricksAdapter
10+
11+
12+
@dataclass
13+
class _Config:
14+
"""Minimal CatalogIntegrationConfig stub for testing __init__ validation."""
15+
16+
name: str = "test_cat"
17+
catalog_type: str = "unity"
18+
catalog_name: Optional[str] = None
19+
table_format: Optional[str] = "iceberg"
20+
external_volume: Optional[str] = None
21+
file_format: Optional[str] = None
22+
adapter_properties: dict[str, Any] = field(default_factory=dict)
23+
24+
25+
# ===== Adapter-level =====
26+
27+
28+
def test_catalogs_v2_capability_declared():
29+
catalogs_v2 = getattr(Capability, "CatalogsV2", None)
30+
if catalogs_v2 is None:
31+
pytest.skip("CatalogsV2 not available in this dbt-adapters version")
32+
cap = DatabricksAdapter._capabilities[catalogs_v2]
33+
assert cap.support == Support.Full
34+
35+
36+
def test_v2_to_v1_type_unity():
37+
adapter = object.__new__(DatabricksAdapter)
38+
assert adapter._v2_to_v1_type("unity") == "unity"
39+
40+
41+
def test_v2_to_v1_type_hive_metastore():
42+
adapter = object.__new__(DatabricksAdapter)
43+
assert adapter._v2_to_v1_type("hive_metastore") == "hive_metastore"
44+
45+
46+
def test_v2_to_v1_type_unknown_passthrough():
47+
adapter = object.__new__(DatabricksAdapter)
48+
assert adapter._v2_to_v1_type("custom_type") == "custom_type"
49+
50+
51+
# ===== UnityCatalogIntegration =====
52+
53+
54+
def test_unity_parquet_without_uniform():
55+
cfg = _Config(file_format="parquet")
56+
integration = UnityCatalogIntegration(cfg)
57+
assert integration.file_format == "parquet"
58+
59+
60+
def test_unity_with_location_root():
61+
cfg = _Config(file_format="parquet", adapter_properties={"location_root": "/mnt/data"})
62+
integration = UnityCatalogIntegration(cfg)
63+
assert integration.external_volume == "/mnt/data"
64+
65+
66+
def test_unity_blank_location_root_raises():
67+
cfg = _Config(file_format="parquet", adapter_properties={"location_root": " "})
68+
with pytest.raises(DbtValidationError, match="location_root cannot be blank"):
69+
UnityCatalogIntegration(cfg)
70+
71+
72+
def test_unity_empty_location_root_raises():
73+
cfg = _Config(file_format="parquet", adapter_properties={"location_root": ""})
74+
with pytest.raises(DbtValidationError, match="location_root cannot be blank"):
75+
UnityCatalogIntegration(cfg)

0 commit comments

Comments
 (0)