Skip to content

Commit 7bcdd83

Browse files
committed
fix: warn on contract.enforced=true for materialized_view (#1279)
Upstream dbt doesn't support column-equivalence contracts on materialized views (per https://docs.getdbt.com/docs/mesh/govern/model-contracts). Previously dbt-databricks silently accepted the flag, letting yml/SQL column drift go undetected. Now emit a user-visible warning pointing at the dbt docs. The change is strictly additive — a warning, not an error — so no existing build's pass/fail outcome changes. Model-level constraint behavior (#1343) is preserved; column-level NOT NULL enforcement on MVs is preserved and covered by a new regression-guard test. Co-authored-by: Isaac
1 parent b1047b5 commit 7bcdd83

3 files changed

Lines changed: 144 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## dbt-databricks 1.11.8 (TBD)
2+
3+
### Fixes
4+
5+
- Warn when `contract.enforced: true` is set on a `materialized_view` model — contracts are not supported for this materialization per upstream dbt ([#1279](https://github.com/databricks/dbt-databricks/issues/1279))
6+
17
## dbt-databricks 1.11.7 (Apr 17, 2026)
28

39
### Features

dbt/include/databricks/macros/relations/materialized_view/create.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,19 @@
2222

2323
{%- set columns = adapter.get_columns_in_relation(temp_relation) -%}
2424
{%- set model_columns = model.get('columns', {}) -%}
25+
{#
26+
Contracts are not supported for materialized views (see
27+
https://docs.getdbt.com/docs/mesh/govern/model-contracts). We still read
28+
`contract.enforced` here because it gates whether model-level constraints
29+
(PK/FK — informational-only on MVs) are emitted into the DDL. Column-level
30+
constraints from `model_columns` (e.g. NOT NULL) flow through
31+
unconditionally and ARE enforced by Databricks at runtime.
32+
#}
2533
{%- set contract_config = config.get('contract') -%}
2634
{%- if contract_config and contract_config.enforced -%}
35+
{%- do exceptions.warn(
36+
"model '" ~ model.name ~ "' has contract.enforced=true but contracts are not supported for materialized_view — see https://docs.getdbt.com/docs/mesh/govern/model-contracts"
37+
) -%}
2738
{%- set model_constraints = model.get('constraints', []) -%}
2839
{%- else -%}
2940
{%- set model_constraints = [] -%}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Tests for contract/constraint behavior on materialized_view models.
3+
4+
Context: https://github.com/databricks/dbt-databricks/issues/1279
5+
6+
Contracts (column-equivalence checks) are not supported for materialized views
7+
by design (see https://docs.getdbt.com/docs/mesh/govern/model-contracts).
8+
Before this fix, dbt-databricks silently accepted `contract.enforced: true` on
9+
MVs, letting yml/SQL drift go undetected. This test file verifies:
10+
11+
1. Setting `contract.enforced: true` on an MV emits a user-visible warning.
12+
2. Column-level NOT NULL constraints on MVs ARE enforced by Databricks at
13+
runtime (regression guard — the MV create macro's constraint plumbing
14+
must not be removed).
15+
"""
16+
17+
import pytest
18+
from dbt.tests import util
19+
20+
21+
MY_SEED = """
22+
id,value
23+
1,100
24+
2,200
25+
3,300
26+
""".strip()
27+
28+
29+
MV_CONTRACT_ENFORCED_SQL = """
30+
{{ config(materialized='materialized_view') }}
31+
select id, value from {{ ref('my_seed') }}
32+
"""
33+
34+
MV_CONTRACT_ENFORCED_SCHEMA = """
35+
version: 2
36+
37+
models:
38+
- name: mv_contract_enforced
39+
config:
40+
contract:
41+
enforced: true
42+
columns:
43+
- name: id
44+
data_type: bigint
45+
- name: value
46+
data_type: bigint
47+
"""
48+
49+
50+
MV_NOT_NULL_SQL = """
51+
{{ config(materialized='materialized_view') }}
52+
select
53+
case when id = 2 then cast(null as bigint) else id end as id,
54+
value
55+
from {{ ref('my_seed') }}
56+
"""
57+
58+
MV_NOT_NULL_SCHEMA = """
59+
version: 2
60+
61+
models:
62+
- name: mv_not_null
63+
columns:
64+
- name: id
65+
data_type: bigint
66+
constraints:
67+
- type: not_null
68+
- name: value
69+
data_type: bigint
70+
"""
71+
72+
73+
@pytest.mark.dlt
74+
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
75+
class TestMVContractEnforcedEmitsWarning:
76+
"""`contract.enforced: true` on a materialized_view should emit a warning
77+
pointing at the dbt docs, since the column-equivalence contract is not
78+
supported for this materialization."""
79+
80+
@pytest.fixture(scope="class")
81+
def seeds(self):
82+
return {"my_seed.csv": MY_SEED}
83+
84+
@pytest.fixture(scope="class")
85+
def models(self):
86+
return {
87+
"mv_contract_enforced.sql": MV_CONTRACT_ENFORCED_SQL,
88+
"schema.yml": MV_CONTRACT_ENFORCED_SCHEMA,
89+
}
90+
91+
def test_contract_enforced_warning(self, project):
92+
util.run_dbt(["seed"])
93+
_, log_output = util.run_dbt_and_capture(
94+
["run", "--select", "mv_contract_enforced"]
95+
)
96+
assert "contracts are not supported for materialized_view" in log_output.lower()
97+
assert "docs.getdbt.com" in log_output.lower()
98+
99+
100+
@pytest.mark.dlt
101+
@pytest.mark.skip_profile("databricks_cluster", "databricks_uc_cluster")
102+
class TestMVColumnConstraintsAreEnforced:
103+
"""Column-level NOT NULL constraints on materialized views ARE enforced by
104+
Databricks at runtime. Regression guard: the MV create macro's constraint
105+
plumbing must not be stripped on the assumption that MV constraints are
106+
informational-only (that's true only for model-level PK/FK)."""
107+
108+
@pytest.fixture(scope="class")
109+
def seeds(self):
110+
return {"my_seed.csv": MY_SEED}
111+
112+
@pytest.fixture(scope="class")
113+
def models(self):
114+
return {
115+
"mv_not_null.sql": MV_NOT_NULL_SQL,
116+
"schema.yml": MV_NOT_NULL_SCHEMA,
117+
}
118+
119+
def test_not_null_constraint_enforced_at_runtime(self, project):
120+
util.run_dbt(["seed"])
121+
results = util.run_dbt(
122+
["run", "--select", "mv_not_null"],
123+
expect_pass=False,
124+
)
125+
assert len(results) == 1
126+
assert results[0].status == "error"
127+
assert "NOT_NULL_CONSTRAINT_VIOLATED" in str(results[0].message)

0 commit comments

Comments
 (0)