Skip to content

view_update_via_alter: true ignores --full-refresh when view definition is unchanged #1404

@asos-dipeshbhundia

Description

@asos-dipeshbhundia

Describe the bug

When view_update_via_alter: true is configured alongside use_materialization_v2: true, running dbt run --full-refresh on a view whose SQL definition has not changed produces a no-op — the view is silently skipped rather than replaced. The --full-refresh flag is completely ignored by the v2 materialization path.

This is problematic because --full-refresh is an explicit user instruction to force-recreate a relation, for example to pick up permission changes, ownership changes, or to recover from a corrupt/stale view state. With view_update_via_alter: true enabled globally, there is currently no way to force-recreate a view without temporarily disabling the flag.

Steps To Reproduce

  1. Enable use_materialization_v2: true in dbt_project.yml flags
  2. Set view_update_via_alter: true on a view model (globally or per-model)
  3. Run the model to create the view: dbt run -s my_view
  4. Without modifying the model SQL, run with full refresh: dbt run -s my_view --full-refresh
  5. Observe the run completes as a no-op — execute_no_op is called and no SQL is executed against Databricks

Expected behavior

--full-refresh should always force a CREATE OR REPLACE VIEW regardless of whether the view definition has changed. The changeset diff optimisation (which avoids unnecessary ALTER VIEW calls) is a valid performance improvement for normal runs, but must be bypassed entirely when --full-refresh is explicitly requested — consistent with how every other dbt materialization treats the flag.

Screenshots and log output

With debug logging (dbt --debug run -s my_view --full-refresh) the log will show:

MATERIALIZING VIEW
Using no-op for <view_name>

No SQL is executed. The expected output would be Using replace_with_view followed by a CREATE OR REPLACE VIEW statement.

System information

The output of dbt --version:

Core:
  - installed: 1.11.6
  - latest:    1.11.8

Plugins:
  - databricks: 1.11.6
  - spark:      1.10.1

The operating system you're using: Windows 11

The output of python --version: Python 3.12.12

Additional context

Root cause analysis:

The v2 materialization in dbt/include/databricks/macros/materializations/view.sql has the following decision tree when an existing view relation is found:

{% if relation_should_be_altered(existing_relation) %}
  {% set configuration_changes = get_configuration_changes(existing_relation) %}
  {% if configuration_changes and configuration_changes.changes %}
    {% if configuration_changes.requires_full_refresh %}
      {{ replace_with_view(existing_relation, target_relation) }}
    {% else %}
      {{ alter_view(target_relation, configuration_changes.changes) }}
    {% endif %}
  {% else %}
    {{ execute_no_op(target_relation) }}  {# ← BUG: fires even on --full-refresh #}
  {% endif %}
{% else %}
  {{ replace_with_view(existing_relation, target_relation) }}
{% endif %}

relation_should_be_altered() only checks whether the flag is set and the relation is a Unity Catalog view — it never checks should_full_refresh():

{% macro relation_should_be_altered(existing_relation) %}
  {% set update_via_alter = config.get('view_update_via_alter', False) | as_bool %}
  {% if existing_relation.is_view and update_via_alter %}
    {% if existing_relation.is_hive_metastore() %}
      {{ exceptions.raise_compiler_error("...") }}
    {% endif %}
    {{ return(True) }}
  {% endif %}
  {{ return(False) }}
{% endmacro %}

Because relation_should_be_altered() returns True, and no configuration changes are detected (SQL is identical), the code falls into execute_no_op — completely bypassing the --full-refresh signal.

Suggested fix:

relation_should_be_altered() should short-circuit to False when should_full_refresh() is True, which causes the outer branch to fall through to replace_with_view() unconditionally — the correct behaviour:

{% macro relation_should_be_altered(existing_relation) %}
  {% if should_full_refresh() %}
    {{ return(False) }}
  {% endif %}
  {% set update_via_alter = config.get('view_update_via_alter', False) | as_bool %}
  {% if existing_relation.is_view and update_via_alter %}
    {% if existing_relation.is_hive_metastore() %}
      {{ exceptions.raise_compiler_error("...") }}
    {% endif %}
    {{ return(True) }}
  {% endif %}
  {{ return(False) }}
{% endmacro %}

Workaround: Temporarily set view_update_via_alter: false on the specific model before running --full-refresh, then revert.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions