diff --git a/CHANGELOG.md b/CHANGELOG.md index df104bda0..dd87bc70f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Add support for Python UDFs ([#1336](https://github.com/databricks/dbt-databricks/pull/1336)) - Add support for key-only `databricks_tags` for table and column tagging. This can now be configured by setting tag values to empty strings `""` or `None`. ([#1339](https://github.com/databricks/dbt-databricks/pull/1339)) +### Under the Hood + +- **BREAKING:** `databricks_tags` defined at different hierarchy levels (e.g. project-level and model-level) now merge additively instead of the child config completely replacing the parent. + ## dbt-databricks 1.11.7 (Apr 17, 2026) ### Features diff --git a/dbt/adapters/databricks/impl.py b/dbt/adapters/databricks/impl.py index b7234e278..5b461967c 100644 --- a/dbt/adapters/databricks/impl.py +++ b/dbt/adapters/databricks/impl.py @@ -5,7 +5,7 @@ from collections.abc import Iterable, Iterator from concurrent.futures import Future from contextlib import contextmanager -from dataclasses import dataclass +from dataclasses import dataclass, field from importlib import metadata from multiprocessing.context import SpawnContext from typing import TYPE_CHECKING, Any, ClassVar, Generic, NamedTuple, Optional, Union, cast @@ -29,7 +29,7 @@ SparkAdapter, ) from dbt_common.behavior_flags import BehaviorFlag -from dbt_common.contracts.config.base import BaseConfig +from dbt_common.contracts.config.base import BaseConfig, MergeBehavior from dbt_common.exceptions import DbtConfigError, DbtInternalError from dbt_common.utils import executor from dbt_common.utils.dict import AttrDict @@ -176,7 +176,9 @@ class DatabricksConfig(AdapterConfig): options: Optional[dict[str, str]] = None merge_update_columns: Optional[str] = None merge_exclude_columns: Optional[str] = None - databricks_tags: Optional[dict[str, str]] = None + databricks_tags: Optional[dict[str, str]] = field( + default=None, metadata=MergeBehavior.Update.meta() + ) query_tags: Optional[str] = None tblproperties: Optional[dict[str, str]] = None zorder: Optional[Union[list[str], str]] = None diff --git a/tests/functional/adapter/tags/fixtures.py b/tests/functional/adapter/tags/fixtures.py index 3dad09a82..6044876cc 100644 --- a/tests/functional/adapter/tags/fixtures.py +++ b/tests/functional/adapter/tags/fixtures.py @@ -16,6 +16,15 @@ select cast(1 as bigint) as id, 'hello' as msg, 'blue' as color """ +tags_merged_sql = """ +{{ config( + materialized = 'table', + databricks_tags = {'c': 'd', 'k': ''}, +) }} + +select cast(1 as bigint) as id, 'hello' as msg, 'blue' as color +""" + streaming_table_tags_sql = """ {{ config( materialized='streaming_table', diff --git a/tests/functional/adapter/tags/test_databricks_tags.py b/tests/functional/adapter/tags/test_databricks_tags.py index e28d11510..aad562bb9 100644 --- a/tests/functional/adapter/tags/test_databricks_tags.py +++ b/tests/functional/adapter/tags/test_databricks_tags.py @@ -62,6 +62,24 @@ def test_updated_tags(self, project): assert actual_tags == expected_tags +@pytest.mark.skip_profile("databricks_cluster") +class TestTableTagsMerged(BaseTestTags): + @pytest.fixture(scope="class") + def models(self): + return {"tags.sql": fixtures.tags_merged_sql} + + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "models": { + "+databricks_tags": { + "a": "b", + "c": "TO_BE_REPLACED_AT_MODEL_LEVEL", + } + } + } + + @pytest.mark.skip_profile("databricks_cluster") class TestTableTagsUpdateViaAlter(BaseTestTagsUpdateViaAlter): pass