Skip to content

Commit d6998af

Browse files
authored
Merge branch '1.12.latest' into sd-db/fix/issue-1331-warehouse-only-parse-stub
2 parents a49ad02 + 3caad33 commit d6998af

15 files changed

Lines changed: 2596 additions & 45 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
- Add support for row filters ([#1294](https://github.com/databricks/dbt-databricks/pull/1294))
77
- Add support for Python UDFs ([#1336](https://github.com/databricks/dbt-databricks/pull/1336))
88
- 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))
9+
- Fetch relation metadata like constraints, column masks, row filters, etc with a single `DESCRIBE TABLE EXTENDED ... AS JSON` call, replacing multiple `information_schema` queries. Falls back to `information_schema` on older runtimes. Gated behind `use_describe_as_json_for_relation_metadata` behavior flag, off by default. ([#1432](https://github.com/databricks/dbt-databricks/pull/1432))
910
- Support `SCHEDULE EVERY` and `TRIGGER ON UPDATE` refresh modes for materialized views and streaming tables, with parser and diff coverage so relations whose actual refresh is not CRON no longer crash on subsequent runs ([#1293](https://github.com/databricks/dbt-databricks/issues/1293))
1011

1112
### Fixes
1213

14+
- Fix `metric_view` failing with `METRIC_VIEW_INVALID_VIEW_DEFINITION` when models use bare `{{ ref(...) }}` for the `source:` field ([#1361](https://github.com/databricks/dbt-databricks/issues/1361))
1315
- Fix `RefreshConfig.__eq__` self/other typo where two configs with the same `cron` but different `time_zone_value` compared equal
1416
- Fix streaming-table DROP-SCHEDULE path that was silently filtered out of the changeset
1517
- Fix `metric_view` failing with `METRIC_VIEW_INVALID_VIEW_DEFINITION` when models use bare `{{ ref(...) }}` for the `source:` field ([#1361](https://github.com/databricks/dbt-databricks/issues/1361))

dbt/adapters/databricks/dbr_capabilities.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
class DBRCapability(Enum):
1414
"""Named capabilities that depend on DBR version."""
1515

16-
TIMESTAMPDIFF = "timestampdiff"
17-
ICEBERG = "iceberg"
1816
COMMENT_ON_COLUMN = "comment_on_column"
19-
JSON_COLUMN_METADATA = "json_column_metadata"
20-
STREAMING_TABLE_JSON_METADATA = "streaming_table_json_metadata"
17+
DESCRIBE_TABLE_EXTENDED_AS_JSON = "describe_table_extended_as_json"
18+
ICEBERG = "iceberg"
2119
INSERT_BY_NAME = "insert_by_name"
20+
JSON_COLUMN_METADATA = "json_column_metadata"
2221
REPLACE_ON = "replace_on"
22+
STREAMING_TABLE_JSON_METADATA = "streaming_table_json_metadata"
23+
TIMESTAMPDIFF = "timestampdiff"
2324

2425

2526
@dataclass
@@ -61,6 +62,9 @@ class DBRCapabilities:
6162
DBRCapability.REPLACE_ON: CapabilitySpec(
6263
min_version=(17, 1),
6364
),
65+
DBRCapability.DESCRIBE_TABLE_EXTENDED_AS_JSON: CapabilitySpec(
66+
min_version=(17, 3),
67+
),
6468
}
6569

6670
def __init__(

dbt/adapters/databricks/impl.py

Lines changed: 528 additions & 22 deletions
Large diffs are not rendered by default.

dbt/adapters/databricks/relation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ def is_metric_view(self) -> bool:
150150
def is_streaming_table(self) -> bool:
151151
return self.type == DatabricksRelationType.StreamingTable
152152

153+
@property
154+
def is_foreign_table(self) -> bool:
155+
return self.type == DatabricksRelationType.Foreign
156+
153157
@property
154158
def is_external_table(self) -> bool:
155159
return self.databricks_table_type == DatabricksTableType.External

dbt/include/databricks/macros/adapters/metadata.sql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,17 @@ SELECT
129129
NULL
130130
) AS databricks_table_type
131131
FROM `system`.`information_schema`.`tables`
132-
WHERE table_catalog = '{{ relation.database|lower }}'
132+
WHERE table_catalog = '{{ relation.database|lower }}'
133133
AND table_schema = '{{ relation.schema|lower }}'
134134
{%- if relation.identifier %}
135135
AND table_name = '{{ relation.identifier|lower }}'
136136
{% endif %}
137137
{% endmacro %}
138+
139+
{% macro describe_table_extended_as_json(relation) %}
140+
{{ return(run_query_as(describe_table_extended_as_json_sql(relation), 'describe_table_extended_as_json')) }}
141+
{% endmacro %}
142+
143+
{% macro describe_table_extended_as_json_sql(relation) %}
144+
DESCRIBE TABLE EXTENDED {{ relation.render() }} AS JSON
145+
{% endmacro %}

tests/functional/adapter/fixtures.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import pytest
2+
from dbt.tests import util
3+
4+
from dbt.adapters.databricks.dbr_capabilities import DBRCapability
25

36
fail_if_tag_fetch_called_macros = """
47
{% macro fetch_tags(relation) %}
@@ -33,3 +36,13 @@ class ManagedIcebergMixin:
3336
@pytest.fixture(scope="class")
3437
def project_config_update(self):
3538
return {"flags": {"use_managed_iceberg": True}}
39+
40+
41+
class RequiresDescribeAsJsonCapabilityMixin:
42+
"""Skip the test class if the connected compute lacks DESCRIBE TABLE EXTENDED AS JSON."""
43+
44+
@pytest.fixture(scope="class", autouse=True)
45+
def require_describe_as_json_capability(self, project):
46+
with util.get_connection(project.adapter):
47+
if not project.adapter.has_capability(DBRCapability.DESCRIBE_TABLE_EXTENDED_AS_JSON):
48+
pytest.skip("DESCRIBE TABLE EXTENDED AS JSON not supported on this compute")

tests/functional/adapter/incremental/test_incremental_column_masks.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import pytest
22
from dbt.tests import util
33

4-
from tests.functional.adapter.fixtures import MaterializationV2Mixin
4+
from tests.functional.adapter.fixtures import (
5+
MaterializationV2Mixin,
6+
RequiresDescribeAsJsonCapabilityMixin,
7+
)
58
from tests.functional.adapter.incremental import fixtures
69

710

@@ -61,3 +64,17 @@ def test_changing_column_masks(self, project):
6164
assert result[0][1] == "hello" # name (unmasked)
6265
assert result[0][2] == "********@example.com" # email (partially masked)
6366
assert result[0][3] == "*****" # password (masked)
67+
68+
69+
@pytest.mark.skip_profile("databricks_cluster")
70+
class TestIncrementalColumnMasksDescribeJsonOn(
71+
RequiresDescribeAsJsonCapabilityMixin, TestIncrementalColumnMasks
72+
):
73+
@pytest.fixture(scope="class")
74+
def project_config_update(self):
75+
return {
76+
"flags": {
77+
"use_materialization_v2": True,
78+
"use_describe_as_json_for_relation_metadata": True,
79+
}
80+
}

tests/functional/adapter/incremental/test_incremental_constraints.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from dbt.contracts.results import RunStatus
33
from dbt.tests import util
44

5+
from tests.functional.adapter.fixtures import RequiresDescribeAsJsonCapabilityMixin
56
from tests.functional.adapter.incremental import fixtures
67

78

@@ -30,6 +31,20 @@ def test_add_non_null_constraint(self, project):
3031
assert "DELTA_NOT_NULL_CONSTRAINT_VIOLATED" in results.results[0].message
3132

3233

34+
@pytest.mark.skip_profile("databricks_cluster")
35+
class TestIncrementalSetNonNullConstraintDescribeJsonOn(
36+
RequiresDescribeAsJsonCapabilityMixin, TestIncrementalSetNonNullConstraint
37+
):
38+
@pytest.fixture(scope="class")
39+
def project_config_update(self):
40+
return {
41+
"flags": {
42+
"use_materialization_v2": True,
43+
"use_describe_as_json_for_relation_metadata": True,
44+
}
45+
}
46+
47+
3348
@pytest.mark.skip_profile("databricks_cluster")
3449
class TestIncrementalUnsetNonNullConstraint:
3550
@pytest.fixture(scope="class")
@@ -175,6 +190,20 @@ def test_update_primary_key_constraint(self, project):
175190
assert not any(constraint[0] == "pk_model" for constraint in primary_key_constraints)
176191

177192

193+
@pytest.mark.skip_profile("databricks_cluster")
194+
class TestIncrementalUpdatePrimaryKeyConstraintDescribeJsonOn(
195+
RequiresDescribeAsJsonCapabilityMixin, TestIncrementalUpdatePrimaryKeyConstraint
196+
):
197+
@pytest.fixture(scope="class")
198+
def project_config_update(self):
199+
return {
200+
"flags": {
201+
"use_materialization_v2": True,
202+
"use_describe_as_json_for_relation_metadata": True,
203+
}
204+
}
205+
206+
178207
@pytest.mark.skip_profile("databricks_cluster")
179208
class TestCascadingConstraintDrop:
180209
@pytest.fixture(scope="class")

tests/functional/adapter/materialized_view_tests/test_changes.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
MaterializedViewChangesContinueMixin,
1010
MaterializedViewChangesFailMixin,
1111
)
12+
from dbt_common.contracts.config.materialization import OnConfigurationChangeOption
1213

1314
from dbt.adapters.databricks.relation_configs.materialized_view import (
1415
MaterializedViewConfig,
1516
)
1617
from dbt.adapters.databricks.relation_configs.tblproperties import TblPropertiesConfig
18+
from tests.functional.adapter.fixtures import RequiresDescribeAsJsonCapabilityMixin
1719
from tests.functional.adapter.materialized_view_tests import fixtures
1820

1921

@@ -86,6 +88,29 @@ class TestMaterializedViewApplyChanges(
8688
pass
8789

8890

91+
@pytest.mark.dlt
92+
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
93+
class TestMaterializedViewApplyChangesDescribeJsonOn(
94+
RequiresDescribeAsJsonCapabilityMixin, TestMaterializedViewApplyChanges
95+
):
96+
@pytest.fixture(scope="class")
97+
def project_config_update(self):
98+
return {
99+
"models": {"on_configuration_change": OnConfigurationChangeOption.Apply.value},
100+
"flags": {"use_describe_as_json_for_relation_metadata": True},
101+
}
102+
103+
@pytest.mark.skip(reason="Full-refresh bypasses get_configuration_changes(); JSON path unused")
104+
def test_full_refresh_occurs_with_changes(self):
105+
pass
106+
107+
@pytest.mark.skip(
108+
reason="Alter test only mutates refresh schedule. view_text is parsed but not asserted on"
109+
)
110+
def test_change_is_applied_via_alter(self):
111+
pass
112+
113+
89114
@pytest.mark.dlt
90115
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
91116
class TestMaterializedViewContinueOnChanges(

tests/functional/adapter/row_filters/test_row_filter.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import pytest
22
from dbt.tests.util import run_dbt, write_file
33

4-
from tests.functional.adapter.fixtures import MaterializationV1Mixin, MaterializationV2Mixin
4+
from tests.functional.adapter.fixtures import (
5+
MaterializationV1Mixin,
6+
MaterializationV2Mixin,
7+
RequiresDescribeAsJsonCapabilityMixin,
8+
)
59
from tests.functional.adapter.row_filters.fixtures import (
610
base_model_mv,
711
base_model_sql,
@@ -142,6 +146,20 @@ def test_incremental_row_filter_lifecycle(self, project):
142146
assert len(filters) == 0
143147

144148

149+
@pytest.mark.skip_profile("databricks_cluster")
150+
class TestIncrementalRowFilterDescribeJsonOn(
151+
RequiresDescribeAsJsonCapabilityMixin, TestIncrementalRowFilter
152+
):
153+
@pytest.fixture(scope="class")
154+
def project_config_update(self):
155+
return {
156+
"flags": {
157+
"use_materialization_v2": True,
158+
"use_describe_as_json_for_relation_metadata": True,
159+
}
160+
}
161+
162+
145163
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
146164
class TestMaterializedViewRowFilter(RowFilterMixin):
147165
"""Test row filters on materialized view models."""
@@ -177,6 +195,20 @@ def test_mv_row_filter_lifecycle(self, project):
177195
assert len(filters) == 0
178196

179197

198+
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
199+
class TestMaterializedViewRowFilterDescribeJsonOn(
200+
RequiresDescribeAsJsonCapabilityMixin, TestMaterializedViewRowFilter
201+
):
202+
@pytest.fixture(scope="class")
203+
def project_config_update(self):
204+
return {
205+
"flags": {
206+
"use_materialization_v2": True,
207+
"use_describe_as_json_for_relation_metadata": True,
208+
}
209+
}
210+
211+
180212
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
181213
class TestStreamingTableRowFilter(RowFilterMixin):
182214
"""Test row filters on streaming table models."""
@@ -228,6 +260,20 @@ def test_streaming_table_row_filter_lifecycle(self, project):
228260
assert len(filters) == 0
229261

230262

263+
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
264+
class TestStreamingTableRowFilterDescribeJsonOn(
265+
RequiresDescribeAsJsonCapabilityMixin, TestStreamingTableRowFilter
266+
):
267+
@pytest.fixture(scope="class")
268+
def project_config_update(self):
269+
return {
270+
"flags": {
271+
"use_materialization_v2": True,
272+
"use_describe_as_json_for_relation_metadata": True,
273+
}
274+
}
275+
276+
231277
@pytest.mark.skip_profile("databricks_cluster")
232278
class TestViewRowFilterFailure(MaterializationV2Mixin):
233279
"""Test that row filters on regular views fail with clear error."""

0 commit comments

Comments
 (0)