Skip to content

Commit 2607e28

Browse files
committed
Fix: Always recreate materialized view
1 parent f33beb6 commit 2607e28

File tree

3 files changed

+53
-19
lines changed

3 files changed

+53
-19
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,9 @@ def insert(
18761876
)
18771877
snapshot = kwargs["snapshot"]
18781878
snapshots = kwargs["snapshots"]
1879+
1880+
# We must replace a materialized view if its query has changed or if it depends on a table.
1881+
# The latter is because if the underlying table is replaced, the materialized view is invalidated in some of the engines.
18791882
if (
18801883
(
18811884
isinstance(query_or_df, exp.Expression)
@@ -1887,6 +1890,7 @@ def insert(
18871890
engine_adapter=self.adapter,
18881891
)
18891892
== query_or_df
1893+
and not model.depends_on
18901894
)
18911895
or self.adapter.HAS_VIEW_BINDING
18921896
) and self.adapter.table_exists(table_name):

tests/core/engine_adapter/integration/test_integration_bigquery.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,50 @@ def test_table_diff_table_name_matches_column_name(ctx: TestContext):
400400

401401
assert row_diff.stats["join_count"] == 1
402402
assert row_diff.full_match_count == 1
403+
404+
405+
def test_materialized_view_evaluation(ctx: TestContext, engine_adapter: BigQueryEngineAdapter):
406+
model_name = ctx.table("test_tbl")
407+
mview_name = ctx.table("test_mview")
408+
409+
sqlmesh = ctx.create_context()
410+
411+
sqlmesh.upsert_model(
412+
load_sql_based_model(
413+
d.parse(
414+
f"""
415+
MODEL (name {model_name}, kind FULL);
416+
417+
SELECT 1 AS col
418+
"""
419+
)
420+
)
421+
)
422+
423+
sqlmesh.upsert_model(
424+
load_sql_based_model(
425+
d.parse(
426+
f"""
427+
MODEL (name {mview_name}, kind VIEW (materialized true));
428+
429+
SELECT * FROM {model_name}
430+
"""
431+
)
432+
)
433+
)
434+
435+
# Case 1: Ensure that plan is successful and we can query the materialized view
436+
sqlmesh.plan(auto_apply=True, no_prompts=True)
437+
438+
df = engine_adapter.fetchdf(f"SELECT * FROM {mview_name.sql(dialect=ctx.dialect)}")
439+
assert df["col"][0] == 1
440+
441+
# Case 2: Ensure that we can change the underlying table and the materialized view is recreated
442+
sqlmesh.upsert_model(
443+
load_sql_based_model(d.parse(f"""MODEL (name {model_name}, kind FULL); SELECT 2 AS col"""))
444+
)
445+
446+
sqlmesh.plan(auto_apply=True, no_prompts=True)
447+
448+
df = engine_adapter.fetchdf(f"SELECT * FROM {mview_name.sql(dialect=ctx.dialect)}")
449+
assert df["col"][0] == 2

tests/core/test_snapshot_evaluator.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -516,25 +516,8 @@ def test_evaluate_materialized_view(
516516
snapshots={},
517517
)
518518

519-
adapter_mock.table_exists.assert_called_once_with(snapshot.table_name())
520-
521-
if view_exists:
522-
# Evaluation shouldn't take place because the rendered query hasn't changed
523-
# since the last view creation.
524-
assert not adapter_mock.create_view.called
525-
else:
526-
# If the view doesn't exist, it should be created even if the rendered query
527-
# hasn't changed since the last view creation.
528-
adapter_mock.create_view.assert_called_once_with(
529-
snapshot.table_name(),
530-
model.render_query(),
531-
model.columns_to_types,
532-
replace=True,
533-
materialized=True,
534-
view_properties={},
535-
table_description=None,
536-
column_descriptions={},
537-
)
519+
# Ensure that the materialized view is recreated even if it exists
520+
assert adapter_mock.create_view.assert_called
538521

539522

540523
def test_evaluate_materialized_view_with_partitioned_by_cluster_by(

0 commit comments

Comments
 (0)