Skip to content

Commit c60e4f2

Browse files
committed
adding option for alter behavior
1 parent 8453a68 commit c60e4f2

File tree

11 files changed

+558
-65
lines changed

11 files changed

+558
-65
lines changed

dbt/adapters/databricks/impl.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,10 +1172,6 @@ def _describe_relation(
11721172
) -> RelationResults:
11731173
results = {}
11741174
kwargs = {"relation": relation}
1175-
# Metric views are stored as views in information_schema but have different properties
1176-
results["information_schema.views"] = get_first_row(
1177-
adapter.execute_macro("get_view_description", kwargs=kwargs)
1178-
)
11791175
results["information_schema.tags"] = adapter.execute_macro("fetch_tags", kwargs=kwargs)
11801176
results["show_tblproperties"] = adapter.execute_macro("fetch_tbl_properties", kwargs=kwargs)
11811177
kwargs = {"table_name": relation}

dbt/adapters/databricks/relation.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def render(self) -> str:
4848
"""Return the type formatted for SQL statements (replace underscores with spaces)"""
4949
return self.value.replace("_", " ").upper()
5050

51+
def render_for_alter(self) -> str:
52+
"""Return the type formatted for ALTER statements.
53+
54+
Metric views use ALTER VIEW (not ALTER METRIC VIEW) syntax.
55+
"""
56+
if self == DatabricksRelationType.MetricView:
57+
return "VIEW"
58+
return self.render()
59+
5160

5261
class DatabricksTableType(StrEnum):
5362
External = "external"

dbt/adapters/databricks/relation_configs/metric_view.py

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,101 @@
1-
from typing import Optional
1+
from typing import ClassVar, Optional
22

3-
from typing_extensions import Self
3+
from dbt.adapters.contracts.relation import RelationConfig
4+
from dbt.adapters.relation_configs.config_base import RelationResults
5+
from dbt_common.exceptions import DbtRuntimeError
46

5-
from dbt.adapters.databricks.logging import logger
67
from dbt.adapters.databricks.relation_configs.base import (
7-
DatabricksRelationChangeSet,
8+
DatabricksComponentConfig,
9+
DatabricksComponentProcessor,
810
DatabricksRelationConfigBase,
911
)
10-
from dbt.adapters.databricks.relation_configs.column_comments import ColumnCommentsProcessor
11-
from dbt.adapters.databricks.relation_configs.column_tags import ColumnTagsProcessor
12-
from dbt.adapters.databricks.relation_configs.comment import CommentProcessor
13-
from dbt.adapters.databricks.relation_configs.query import QueryProcessor
1412
from dbt.adapters.databricks.relation_configs.tags import TagsProcessor
1513
from dbt.adapters.databricks.relation_configs.tblproperties import TblPropertiesProcessor
1614

1715

16+
class MetricViewQueryConfig(DatabricksComponentConfig):
17+
"""Component encapsulating the YAML definition of a metric view."""
18+
19+
query: str
20+
21+
def get_diff(self, other: "MetricViewQueryConfig") -> Optional["MetricViewQueryConfig"]:
22+
# Normalize whitespace for comparison
23+
self_normalized = " ".join(self.query.split())
24+
other_normalized = " ".join(other.query.split())
25+
if self_normalized != other_normalized:
26+
return self
27+
return None
28+
29+
30+
class MetricViewQueryProcessor(DatabricksComponentProcessor[MetricViewQueryConfig]):
31+
"""Processor for metric view YAML definitions.
32+
33+
Metric views store their YAML definitions in information_schema.views, but wrapped
34+
in $$ delimiters. This processor extracts and compares the YAML content.
35+
"""
36+
37+
name: ClassVar[str] = "query"
38+
39+
@classmethod
40+
def from_relation_results(cls, result: RelationResults) -> MetricViewQueryConfig:
41+
from dbt.adapters.databricks.logging import logger
42+
43+
# Get the view text from DESCRIBE EXTENDED output
44+
describe_extended = result.get("describe_extended")
45+
if not describe_extended:
46+
raise DbtRuntimeError(
47+
f"Cannot find metric view description. Result keys: {list(result.keys())}"
48+
)
49+
50+
# Find the "View Text" row in DESCRIBE EXTENDED output
51+
view_definition = None
52+
for row in describe_extended:
53+
if row[0] == "View Text":
54+
view_definition = row[1]
55+
break
56+
57+
logger.debug(
58+
f"MetricViewQueryProcessor: view_definition = "
59+
f"{view_definition[:200] if view_definition else 'None'}"
60+
)
61+
62+
if not view_definition:
63+
raise DbtRuntimeError("Metric view has no 'View Text' in DESCRIBE EXTENDED output")
64+
65+
view_definition = view_definition.strip()
66+
67+
# Extract YAML content from $$ delimiters if present
68+
# Format: $$ yaml_content $$
69+
if "$$" in view_definition:
70+
parts = view_definition.split("$$")
71+
if len(parts) >= 2:
72+
# The YAML is between the first and second $$ markers
73+
view_definition = parts[1].strip()
74+
75+
return MetricViewQueryConfig(query=view_definition)
76+
77+
@classmethod
78+
def from_relation_config(cls, relation_config: RelationConfig) -> MetricViewQueryConfig:
79+
query = relation_config.compiled_code
80+
81+
if query:
82+
return MetricViewQueryConfig(query=query.strip())
83+
else:
84+
raise DbtRuntimeError(
85+
f"Cannot compile metric view {relation_config.identifier} with no YAML definition"
86+
)
87+
88+
1889
class MetricViewConfig(DatabricksRelationConfigBase):
90+
"""Config for metric views.
91+
92+
Metric views use YAML definitions stored in information_schema.views wrapped in $$ delimiters.
93+
Changes to the YAML definition can be applied via ALTER VIEW AS.
94+
Tags and tblproperties can also be altered incrementally.
95+
"""
96+
1997
config_components = [
2098
TagsProcessor,
2199
TblPropertiesProcessor,
22-
QueryProcessor,
23-
CommentProcessor,
24-
ColumnCommentsProcessor,
25-
ColumnTagsProcessor,
100+
MetricViewQueryProcessor,
26101
]
27-
28-
def get_changeset(self, existing: Self) -> Optional[DatabricksRelationChangeSet]:
29-
changeset = super().get_changeset(existing)
30-
if changeset:
31-
# Metric views: query changes require full refresh (can't ALTER YAML definition)
32-
if "query" in changeset.changes:
33-
logger.debug(
34-
"Metric view YAML definition changed, requiring replace, as there is"
35-
" no API to update the YAML specification via ALTER."
36-
)
37-
changeset.requires_full_refresh = True
38-
# Comment changes also require full refresh for metric views
39-
if "comment" in changeset.changes:
40-
logger.debug(
41-
"Metric view description changed, requiring replace, as there is"
42-
" no API yet to update comments."
43-
)
44-
changeset.requires_full_refresh = True
45-
# Column comment changes require full refresh for metric views
46-
if "column_comments" in changeset.changes:
47-
logger.debug(
48-
"Metric view column comments changed, requiring replace, as there is"
49-
" no API to update column comments for metric views."
50-
)
51-
changeset.requires_full_refresh = True
52-
return changeset

dbt/include/databricks/macros/materializations/metric_view.sql

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,24 @@
88
{{ run_pre_hooks() }}
99

1010
{% if existing_relation %}
11-
{#- Metric views always use CREATE OR REPLACE - no alter path for now -#}
12-
{{ log('Using replace_with_metric_view (metric views always use replace)') }}
13-
{{ replace_with_metric_view(existing_relation, target_relation) }}
11+
{#- Only use alter path if existing relation is actually a metric_view -#}
12+
{% if existing_relation.is_metric_view and relation_should_be_altered(existing_relation) %}
13+
{% set configuration_changes = get_metric_view_configuration_changes(existing_relation) %}
14+
{% if configuration_changes and configuration_changes.changes %}
15+
{% if configuration_changes.requires_full_refresh %}
16+
{{ replace_with_metric_view(existing_relation, target_relation) }}
17+
{% else %}
18+
{{ alter_metric_view(target_relation, configuration_changes.changes) }}
19+
{% endif %}
20+
{% else %}
21+
{# No changes detected - run a no-op statement for dbt tracking #}
22+
{% call statement('main') %}
23+
select 1
24+
{% endcall %}
25+
{% endif %}
26+
{% else %}
27+
{{ replace_with_metric_view(existing_relation, target_relation) }}
28+
{% endif %}
1429
{% else %}
1530
{% call statement('main') -%}
1631
{{ get_create_metric_view_as_sql(target_relation, sql) }}
@@ -28,4 +43,4 @@
2843
{{ run_post_hooks() }}
2944

3045
{{ return({'relations': [target_relation]}) }}
31-
{%- endmaterialization %}
46+
{%- endmaterialization %}

dbt/include/databricks/macros/relations/components/query.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{% endmacro %}
99

1010
{% macro get_alter_query_sql(target_relation, query) -%}
11-
ALTER {{ target_relation.type.render() }} {{ target_relation.render() }} AS (
11+
ALTER {{ target_relation.type.render_for_alter() }} {{ target_relation.render() }} AS (
1212
{{ query }}
1313
)
14-
{%- endmacro %}
14+
{%- endmacro %}

dbt/include/databricks/macros/relations/config.sql

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
{% do return(configuration_changes) %}
66
{%- endmacro -%}
77

8-
{#- Metric view specific config changes - needed because metric views are stored as VIEWs in DB -#}
98
{%- macro get_metric_view_configuration_changes(existing_relation) -%}
10-
{#- existing_relation should already be typed as metric_view via incorporate() -#}
119
{%- set existing_config = adapter.get_relation_config(existing_relation) -%}
1210
{%- set model_config = adapter.get_config_from_model(config.model) -%}
1311
{%- set configuration_changes = model_config.get_changeset(existing_config) -%}
1412
{% do return(configuration_changes) %}
15-
{%- endmacro -%}
13+
{%- endmacro -%}

dbt/include/databricks/macros/relations/metric_view/alter.sql

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,50 @@
77
{% set tags = changes.get("tags") %}
88
{% set tblproperties = changes.get("tblproperties") %}
99
{% set query = changes.get("query") %}
10-
{% set column_comments = changes.get("column_comments") %}
1110

12-
{# For metric views, only tags and tblproperties can be altered without recreating #}
11+
{# Handle YAML definition changes via ALTER VIEW AS #}
12+
{% if query %}
13+
{% call statement('main') %}
14+
{{ get_alter_metric_view_as_sql(target_relation, query.query) }}
15+
{% endcall %}
16+
{% else %}
17+
{# Ensure statement('main') is called for dbt to track the run #}
18+
{% call statement('main') %}
19+
select 1
20+
{% endcall %}
21+
{% endif %}
22+
1323
{% if tags %}
1424
{{ apply_tags(target_relation, tags.set_tags) }}
1525
{% endif %}
1626
{% if tblproperties %}
1727
{{ apply_tblproperties(target_relation, tblproperties.tblproperties) }}
1828
{% endif %}
29+
{% endmacro %}
1930

20-
{# Query changes and column comment changes require full refresh for metric views #}
21-
{% if query or column_comments %}
22-
{{ exceptions.warn("Metric view YAML definition or column comment changes detected that cannot be applied via ALTER. These changes will require CREATE OR REPLACE on next run.") }}
23-
{% endif %}
31+
{% macro get_alter_metric_view_as_sql(relation, yaml_content) -%}
32+
{{ adapter.dispatch('get_alter_metric_view_as_sql', 'dbt')(relation, yaml_content) }}
33+
{%- endmacro %}
34+
35+
{% macro databricks__get_alter_metric_view_as_sql(relation, yaml_content) %}
36+
alter view {{ relation.render() }} as $$
37+
{{ yaml_content }}
38+
$$
2439
{% endmacro %}
2540

2641
{% macro replace_with_metric_view(existing_relation, target_relation) %}
2742
{% set sql = adapter.clean_sql(sql) %}
2843
{% set tags = config.get('databricks_tags') %}
44+
{% set tblproperties = config.get('tblproperties') %}
2945
{{ execute_multiple_statements(get_replace_sql(existing_relation, target_relation, sql)) }}
3046
{%- do apply_tags(target_relation, tags) -%}
3147

48+
{% if tblproperties %}
49+
{{ apply_tblproperties(target_relation, tblproperties) }}
50+
{% endif %}
51+
3252
{% set column_tags = adapter.get_column_tags_from_model(config.model) %}
3353
{% if column_tags and column_tags.set_column_tags %}
3454
{{ apply_column_tags(target_relation, column_tags) }}
3555
{% endif %}
36-
{% endmacro %}
56+
{% endmacro %}

dbt/include/databricks/macros/relations/tags.sql

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@
2929
{%- endmacro -%}
3030

3131
{% macro alter_set_tags(relation, tags) -%}
32-
{#- Metric views use ALTER VIEW syntax, not ALTER METRIC VIEW -#}
33-
{%- if relation.is_metric_view -%}
34-
ALTER VIEW {{ relation.render() }} SET TAGS (
35-
{%- else -%}
36-
ALTER {{ relation.type.render() }} {{ relation.render() }} SET TAGS (
37-
{%- endif -%}
32+
ALTER {{ relation.type.render_for_alter() }} {{ relation.render() }} SET TAGS (
3833
{% for tag in tags -%}
3934
'{{ tag }}' = '{{ tags[tag] }}' {%- if not loop.last %}, {% endif -%}
4035
{%- endfor %}

dbt/include/databricks/macros/relations/tblproperties.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
{% set tblproperty_statment = databricks__tblproperties_clause(tblproperties) %}
2222
{% if tblproperty_statment %}
2323
{%- call statement('main') -%}
24-
ALTER {{ relation.type.render() }} {{ relation.render() }} SET {{ tblproperty_statment}}
24+
ALTER {{ relation.type.render_for_alter() }} {{ relation.render() }} SET {{ tblproperty_statment}}
2525
{%- endcall -%}
2626
{% endif %}
2727
{%- endmacro -%}

0 commit comments

Comments
 (0)