Skip to content

fix: recreate materialized views on query schema drift - #1621

Merged
sd-db merged 7 commits into
1.13.latestfrom
sd-db/fix/issue-1359-mv-schema-drift
Aug 10, 2026
Merged

fix: recreate materialized views on query schema drift#1621
sd-db merged 7 commits into
1.13.latestfrom
sd-db/fix/issue-1359-mv-schema-drift

Conversation

@sd-db

@sd-db sd-db commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Detect when a materialized view's create-time column list no longer matches the query-inferred schema (e.g. upstream select * gains a column) and recreate via replace instead of issuing REFRESH, which Databricks rejects with an incompatible user-specified schema error.
  • Names-only comparison by design (type-label variance across DESCRIBE paths would cause spurious recreates). Streaming tables (dbt streaming_table fails on schema evolution while native streaming table works #1303) are intentionally out of scope — they need a different approach around locking columns on CREATE.

Resolves #1359

Test plan

  • Unit: tests/unit/test_column_schemas_differ.py
  • Functional (SQL warehouse): tests/functional/adapter/materialized_view_tests/test_mv_schema_evolution.py
  • Smoke: existing test_mv_alter_no_rebuild still passes
  • CI integration tests

@sd-db
sd-db requested a review from jprakash-db as a code owner July 30, 2026 11:52
sd-db added a commit that referenced this pull request Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Coverage report

This PR does not seem to contain any modification to coverable code.

sd-db added 6 commits August 6, 2026 20:09
When CREATE embeds a user-specified column list, Databricks rejects
REFRESH if the query-inferred schema changes (e.g. upstream select *
gains a column). Detect drift and replace instead of refresh.
Replace the bespoke temp-view + `LIMIT 10` schema inference with the base
adapter's `get_columns_in_query`, which emits
`select * from (<sql>) as __dbt_sbq where false limit 0` -- no DDL and no data
scan. Subquery wrapping also makes the check safe for model SQL ending in its
own `LIMIT`, which the string-concatenated variant was not.

The comparison collapses into the macro, so the `column_schemas_differ` adapter
method is no longer needed. `get_columns_in_query` is part of the shared adapter
contract (already used by this adapter in relations/view/create.sql and
snapshot_helpers.sql), so no Databricks-specific method needs porting to Fusion.

Also log the drifted column names, since a schema-driven replace was otherwise
silent, and record why get_configuration_changes cannot catch this case: the
model's query text is unchanged, only the upstream schema moved.

Unit coverage moves from tests/unit/test_column_schemas_differ.py to a macro
test exercising the same cases through the macro, plus empty-list cases.

Co-authored-by: Isaac
The list is only populated via `returned.append` handed to the Jinja context as
a callback, so mypy has no assignment to infer the element type from.
Schema drift short-circuited to a replace ahead of the on_configuration_change
dispatch, so `fail` and `continue` were silently overridden: a drifted
materialized view was rebuilt regardless of the setting.

Drift is a configuration change the config components cannot observe -- the
model's query text is unchanged, only the upstream schema moved -- so compute it
alongside get_configuration_changes and let the existing dispatch handle it.
`apply` replaces (REFRESH cannot reconcile a drifted schema), while `fail` and
`continue` need no new branches; they fall out of the dispatch already there.

Functional coverage grows from one test to four: the issue's own sequence of
adding the column to the properties YAML after the upstream model gains it, plus
the fail and continue paths. Both new dispatch tests were confirmed to fail
against the previous ordering.
Drift previously called get_replace_sql directly, bypassing the alter macro's
special handling for partition_by, which needs DROP + CREATE because
CREATE OR REPLACE cannot change partitioning.

Instead mark the changeset requires_full_refresh and let
get_alter_materialized_view_as_sql pick the right rebuild. Drift with no
component changes still replaces directly, since there is no changeset to carry
the flag. model_copy keeps this a new object rather than mutating a shared one,
and works under dbt's Jinja sandbox where __setattr__ is blocked.
@sd-db
sd-db force-pushed the sd-db/fix/issue-1359-mv-schema-drift branch from 8f1a2bd to 9f26f22 Compare August 6, 2026 21:36
@sd-db
sd-db changed the base branch from main to 1.13.latest August 7, 2026 04:55
@sd-db
sd-db merged commit a36f297 into 1.13.latest Aug 10, 2026
8 of 9 checks passed
@sd-db
sd-db deleted the sd-db/fix/issue-1359-mv-schema-drift branch August 10, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding column to materialized view properties YAML failed to trigger full refresh

2 participants