Skip to content

Commit 18ea497

Browse files
axellpadillaAxell Padilla
authored andcommitted
Fix: Handle view alterations and existing table replacements
This commit addresses the following: - Modifies the `create view` macro to use `alter view` when the view already exists, preventing errors during re-materialization. - Updates the view materialization logic to correctly handle scenarios where a table with the same name exists. It now renames the existing table to a backup before creating the view. - Adds a test case to verify that materializing an existing view updates its definition correctly, including column changes.
1 parent bcf4ac9 commit 18ea497

File tree

3 files changed

+74
-11
lines changed

3 files changed

+74
-11
lines changed

dbt/include/sqlserver/macros/materializations/models/view/view.sql

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,23 @@
3636
-- `BEGIN` happens here:
3737
{{ run_hooks(pre_hooks, inside_transaction=True) }}
3838

39-
-- build model
40-
{% call statement('main') -%}
41-
{{ get_create_view_as_sql(intermediate_relation, sql) }}
42-
{%- endcall %}
43-
44-
-- cleanup
45-
-- move the existing view out of the way
46-
{% if existing_relation is not none %}
39+
-- move the existing table if not view out of the way
40+
{% if existing_relation is not none and existing_relation.type != 'view' %}
4741
/* Do the equivalent of rename_if_exists. 'existing_relation' could have been dropped
4842
since the variable was first set. */
4943
{% set existing_relation = load_cached_relation(existing_relation) %}
5044
{% if existing_relation is not none %}
5145
{{ adapter.rename_relation(existing_relation, backup_relation) }}
5246
{% endif %}
5347
{% endif %}
54-
{{ adapter.rename_relation(intermediate_relation, target_relation) }}
48+
49+
-- build model
50+
{% call statement('main') -%}
51+
{# The underlying macro sqlserver__create_view_as handles CREATE vs ALTER logic #}
52+
{{ get_create_view_as_sql(target_relation, sql) }}
53+
{%- endcall %}
54+
55+
-- cleanup
5556

5657
{% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}
5758
{% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}

dbt/include/sqlserver/macros/relations/views/create.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
{{ get_assert_columns_equivalent(sql) }}
77
{%- endif %}
88

9+
{% set existing_relation = load_cached_relation(relation) %}
10+
911
{% set query %}
10-
create view {{ relation.include(database=False) }} as {{ sql }};
12+
{% if existing_relation is not none %}
13+
alter view {{ relation.include(database=False) }} as {{ sql }};
14+
{% else %}
15+
create view {{ relation.include(database=False) }} as {{ sql }};
16+
{% endif %}
1117
{% endset %}
1218

1319
{% set tst %}

tests/functional/adapter/dbt/test_basic.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
import pytest
24
from dbt.tests.adapter.basic.test_adapter_methods import BaseAdapterMethod
35
from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations
@@ -9,10 +11,64 @@
911
from dbt.tests.adapter.basic.test_singular_tests_ephemeral import BaseSingularTestsEphemeral
1012
from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols
1113
from dbt.tests.adapter.basic.test_snapshot_timestamp import BaseSnapshotTimestamp
14+
from dbt.tests.util import run_dbt
1215

1316

1417
class TestSimpleMaterializations(BaseSimpleMaterializations):
15-
pass
18+
19+
def test_existing_view_materialization(self, project, models):
20+
"""Test that materializing an existing view works correctly."""
21+
# Create a temporary model file directly in the project
22+
model_path = os.path.join(project.project_root, "models", "view_model_exists.sql")
23+
24+
# Write the initial model without the value column
25+
with open(model_path, "w") as f:
26+
f.write(
27+
"""
28+
{{ config(materialized='view') }}
29+
select
30+
1 as id
31+
{% if var('include_value_column', false) %}
32+
, 2 as value
33+
{% endif %}
34+
"""
35+
)
36+
37+
# First run to create the view without the extra column
38+
results = run_dbt(["run", "-m", "view_model_exists"])
39+
assert len(results) == 1
40+
41+
# Generate catalog to get column information
42+
catalog = run_dbt(["docs", "generate"])
43+
44+
# Check columns in the catalog
45+
node_id = "model.base.view_model_exists"
46+
assert node_id in catalog.nodes
47+
48+
# Get columns from the catalog
49+
columns = catalog.nodes[node_id].columns
50+
column_names = [name.lower() for name in columns.keys()]
51+
52+
# Verify only the id column exists
53+
assert "id" in column_names
54+
assert "value" not in column_names
55+
56+
# Second run with a variable to include the extra column
57+
results = run_dbt(
58+
["run", "-m", "view_model_exists", "--vars", '{"include_value_column": true}']
59+
)
60+
assert len(results) == 1
61+
62+
# Generate catalog again to get updated column information
63+
catalog = run_dbt(["docs", "generate"])
64+
65+
# Get updated columns from the catalog
66+
columns = catalog.nodes[node_id].columns
67+
column_names = [name.lower() for name in columns.keys()]
68+
69+
# Verify both columns exist now
70+
assert "id" in column_names
71+
assert "value" in column_names
1672

1773

1874
class TestSingularTests(BaseSingularTests):

0 commit comments

Comments
 (0)