Skip to content

Commit b32d3eb

Browse files
ericj-dbbenc-db
andauthored
fix: Add @@ prefix to system query tag keys (#1264)
<!-- Please review our pull request review process in CONTRIBUTING.md before your proceed. --> Resolves # <!--- Include the number of the issue addressed by this PR above if applicable. Example: resolves #1234 Please review our pull request review process in CONTRIBUTING.md before your proceed. --> ### Description Add `@@` prefix to system query tag keys (i.e. reserved keys) for internal observability <img width="280" height="177" alt="Screenshot 2025-11-17 at 9 47 33 AM" src="https://github.com/user-attachments/assets/2926d2b4-8cd3-420a-b68c-8b103a3cdbd7" /> ### Checklist - [ ] I have run this code in development and it appears to resolve the stated issue - [ ] This PR includes tests, or tests are not required/relevant for this PR - [ ] I have updated the `CHANGELOG.md` and added information about my change to the "dbt-databricks next" section. --------- Co-authored-by: Ben Cassell <98852248+benc-db@users.noreply.github.com> Co-authored-by: Ben Cassell <ben.cassell@databricks.com>
1 parent 184885a commit b32d3eb

7 files changed

Lines changed: 62 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
### Under the hood
1717

1818
- Add validation for query tag value length and auto-escape special characters
19+
- Add `@@` prefix to system query tag keys
1920

2021
## dbt-databricks 1.11.0 (Oct 29, 2025)
2122

dbt/adapters/databricks/connections.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -579,16 +579,20 @@ def get_merged_query_tags(
579579

580580
# Default tags that will be applied to all queries
581581
default_tags = {
582-
"dbt_databricks_version": __version__,
583-
"dbt_core_version": version("dbt-core"),
582+
QueryTagsUtils.DBT_DATABRICKS_VERSION_QUERY_TAG_KEY: __version__,
583+
QueryTagsUtils.DBT_CORE_VERSION_QUERY_TAG_KEY: version("dbt-core"),
584584
}
585585

586586
# Default tags that will only exists for queries tied to a specific model
587587
if query_header_context:
588588
if hasattr(query_header_context, "model_name") and query_header_context.model_name:
589-
default_tags["dbt_model_name"] = query_header_context.model_name
589+
default_tags[QueryTagsUtils.DBT_MODEL_NAME_QUERY_TAG_KEY] = (
590+
query_header_context.model_name
591+
)
590592
if hasattr(query_header_context, "materialized") and query_header_context.materialized:
591-
default_tags["dbt_materialized"] = query_header_context.materialized
593+
default_tags[QueryTagsUtils.DBT_MATERIALIZED_QUERY_TAG_KEY] = (
594+
query_header_context.materialized
595+
)
592596

593597
# Parse connection tags from JSON string
594598
connection_tags = (

dbt/adapters/databricks/utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,17 @@ def is_cluster_http_path(http_path: str, cluster_id: Optional[str]) -> bool:
102102
class QueryTagsUtils:
103103
"""Utility class for handling query tags merging and validation."""
104104

105+
DBT_MODEL_NAME_QUERY_TAG_KEY = "@@dbt_model_name"
106+
DBT_CORE_VERSION_QUERY_TAG_KEY = "@@dbt_core_version"
107+
DBT_DATABRICKS_VERSION_QUERY_TAG_KEY = "@@dbt_databricks_version"
108+
DBT_MATERIALIZED_QUERY_TAG_KEY = "@@dbt_materialized"
109+
105110
# Reserved query tag keys that cannot be overridden
106111
RESERVED_KEYS = {
107-
"dbt_model_name",
108-
"dbt_core_version",
109-
"dbt_databricks_version",
110-
"dbt_materialized",
112+
DBT_MODEL_NAME_QUERY_TAG_KEY,
113+
DBT_CORE_VERSION_QUERY_TAG_KEY,
114+
DBT_DATABRICKS_VERSION_QUERY_TAG_KEY,
115+
DBT_MATERIALIZED_QUERY_TAG_KEY,
111116
}
112117

113118
# Maximum number of query tags allowed

tests/functional/adapter/aliases/test_aliases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def macros(self):
2828

2929

3030
class TestDatabricksSameAliasDifferentSchemas(BaseSameAliasDifferentSchemas):
31-
@pytest.fixture(autouse=True)
31+
@pytest.fixture(scope="class", autouse=True)
3232
def clean_up(self, project):
3333
yield
3434
with project.adapter.connection_named("__test"):
@@ -53,7 +53,7 @@ def macros(self):
5353

5454

5555
class TestDatabricksSameAliasDifferentDatabases(BaseSameAliasDifferentDatabases):
56-
@pytest.fixture(autouse=True)
56+
@pytest.fixture(scope="class", autouse=True)
5757
def clean_up(self, project):
5858
yield
5959
with project.adapter.connection_named("__test"):

tests/functional/adapter/dbt_clone/test_dbt_clone.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class CleanupMixin:
10-
@pytest.fixture(autouse=True)
10+
@pytest.fixture(scope="class", autouse=True)
1111
def clean_up(self, project):
1212
yield
1313
with project.adapter.connection_named("__test"):
Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1+
import pytest
12
from dbt.tests.adapter.incremental.test_incremental_unique_id import (
23
BaseIncrementalUniqueKey,
34
)
45

56
from tests.functional.adapter.fixtures import MaterializationV2Mixin
67

78

8-
class TestUniqueKeyDatabricks(BaseIncrementalUniqueKey):
9+
class CleanupMixin:
10+
"""Override the upstream clean_up fixture to use class scope.
11+
12+
The upstream fixture uses function scope (default), which causes it to drop
13+
the schema after each test method. This creates a race condition in parallel
14+
test execution where the schema is dropped while other test methods are still
15+
using it. Using class scope ensures cleanup only happens after all test methods
16+
in the class complete.
17+
"""
18+
19+
@pytest.fixture(scope="class", autouse=True)
20+
def clean_up(self, project):
21+
yield
22+
with project.adapter.connection_named("__test"):
23+
relation = project.adapter.Relation.create(
24+
database=project.database, schema=project.test_schema
25+
)
26+
project.adapter.drop_schema(relation)
27+
28+
29+
class TestUniqueKeyDatabricks(CleanupMixin, BaseIncrementalUniqueKey):
930
pass
1031

1132

12-
class TestUniqueKeyDatabricksV2(MaterializationV2Mixin, BaseIncrementalUniqueKey):
33+
class TestUniqueKeyDatabricksV2(CleanupMixin, MaterializationV2Mixin, BaseIncrementalUniqueKey):
1334
pass

tests/unit/test_query_tags.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_parse_query_tags_non_dict(self):
6868

6969
def test_validate_query_tags_reserved_keys(self):
7070
"""Test validation fails for reserved keys."""
71-
tags = {"dbt_model_name": "test", "team": "marketing"}
71+
tags = {"@@dbt_model_name": "test", "team": "marketing"}
7272
with pytest.raises(DbtValidationError, match="Cannot use reserved query tag keys"):
7373
QueryTagsUtils.validate_query_tags(tags)
7474

@@ -165,7 +165,7 @@ def test_merge_query_tags_reserved_key_conflict(self):
165165
assert "dbt_model_name" in result
166166

167167
# But if user tries to use a reserved key, it should fail
168-
model_tags_with_reserved = {"dbt_model_name": "override_attempt"}
168+
model_tags_with_reserved = {"@@dbt_model_name": "override_attempt"}
169169
with pytest.raises(DbtValidationError, match="Cannot use reserved query tag keys"):
170170
QueryTagsUtils.merge_query_tags(connection_tags, model_tags_with_reserved, default_tags)
171171

@@ -220,10 +220,10 @@ def test_get_merged_query_tags(self):
220220
assert result["project"] == "analytics" # From model
221221

222222
# Check default tags
223-
assert "dbt_databricks_version" in result
224-
assert "dbt_core_version" in result
225-
assert result["dbt_materialized"] == "table"
226-
assert result["dbt_model_name"] == "test_model"
223+
assert "@@dbt_databricks_version" in result
224+
assert "@@dbt_core_version" in result
225+
assert result["@@dbt_materialized"] == "table"
226+
assert result["@@dbt_model_name"] == "test_model"
227227

228228
def test_get_merged_query_tags_no_model_tags(self):
229229
"""Test getting merged query tags with no model tags."""
@@ -242,10 +242,10 @@ def test_get_merged_query_tags_no_model_tags(self):
242242
assert result["team"] == "marketing"
243243

244244
# Check default tags
245-
assert "dbt_databricks_version" in result
246-
assert "dbt_core_version" in result
247-
assert result["dbt_materialized"] == "view"
248-
assert result["dbt_model_name"] == "test_model"
245+
assert "@@dbt_databricks_version" in result
246+
assert "@@dbt_core_version" in result
247+
assert result["@@dbt_materialized"] == "view"
248+
assert result["@@dbt_model_name"] == "test_model"
249249

250250
def test_get_merged_query_tags_no_connection_tags(self):
251251
"""Test getting merged query tags with no connection tags."""
@@ -264,10 +264,10 @@ def test_get_merged_query_tags_no_connection_tags(self):
264264
assert result["project"] == "analytics"
265265

266266
# Check default tags
267-
assert "dbt_databricks_version" in result
268-
assert "dbt_core_version" in result
269-
assert result["dbt_materialized"] == "incremental"
270-
assert result["dbt_model_name"] == "test_model"
267+
assert "@@dbt_databricks_version" in result
268+
assert "@@dbt_core_version" in result
269+
assert result["@@dbt_materialized"] == "incremental"
270+
assert result["@@dbt_model_name"] == "test_model"
271271

272272
def test_get_merged_query_tags_no_tags_at_all(self):
273273
"""Test getting merged query tags with no tags from any source."""
@@ -283,10 +283,10 @@ def test_get_merged_query_tags_no_tags_at_all(self):
283283
result = QueryConfigUtils.get_merged_query_tags(query_header_context, creds)
284284

285285
# Check default tags only
286-
assert "dbt_databricks_version" in result
287-
assert "dbt_core_version" in result
288-
assert result["dbt_materialized"] == "table"
289-
assert result["dbt_model_name"] == "test_model"
286+
assert "@@dbt_databricks_version" in result
287+
assert "@@dbt_core_version" in result
288+
assert result["@@dbt_materialized"] == "table"
289+
assert result["@@dbt_model_name"] == "test_model"
290290

291291
# Should only have default tags
292292
assert len(result) == 4

0 commit comments

Comments
 (0)