Skip to content

Commit 3d3d5ab

Browse files
authored
Fix: Skip preview for metadata-only changes (#2741)
1 parent f720c14 commit 3d3d5ab

File tree

3 files changed

+106
-7
lines changed

3 files changed

+106
-7
lines changed

sqlmesh/core/plan/builder.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,13 @@ def is_restateable_snapshot(snapshot: Snapshot) -> bool:
321321
restate_models = {
322322
s.name
323323
for s in self._context_diff.new_snapshots.values()
324-
if s.is_materialized and (self._forward_only or s.model.forward_only)
324+
if s.is_materialized
325+
and (self._forward_only or s.model.forward_only)
326+
and (
327+
# Metadata changes should not be previewed.
328+
self._context_diff.directly_modified(s.name)
329+
or self._context_diff.indirectly_modified(s.name)
330+
)
325331
}
326332
is_preview = True
327333

tests/core/test_integration.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,34 @@ def test_full_history_restatement_model_regular_plan_preview_enabled(
395395
context.apply(plan)
396396

397397

398+
@freeze_time("2023-01-08 15:00:00")
399+
def test_metadata_changed_regular_plan_preview_enabled(init_and_plan_context: t.Callable):
400+
context, plan = init_and_plan_context("examples/sushi")
401+
context.apply(plan)
402+
403+
model_name = "sushi.waiter_revenue_by_day"
404+
405+
model = context.get_model(model_name)
406+
model = model.copy(update={"owner": "new_owner"})
407+
408+
context.upsert_model(model)
409+
snapshot = context.get_snapshot(model, raise_if_missing=True)
410+
top_waiters_snapshot = context.get_snapshot("sushi.top_waiters", raise_if_missing=True)
411+
412+
plan = context.plan("dev", no_prompts=True, skip_tests=True, enable_preview=True)
413+
assert len(plan.new_snapshots) == 2
414+
assert (
415+
plan.context_diff.snapshots[snapshot.snapshot_id].change_category
416+
== SnapshotChangeCategory.FORWARD_ONLY
417+
)
418+
assert (
419+
plan.context_diff.snapshots[top_waiters_snapshot.snapshot_id].change_category
420+
== SnapshotChangeCategory.FORWARD_ONLY
421+
)
422+
assert not plan.missing_intervals
423+
assert not plan.restatements
424+
425+
398426
@freeze_time("2023-01-08 15:00:00")
399427
def test_hourly_model_with_lookback_no_backfill_in_dev(init_and_plan_context: t.Callable):
400428
context, plan = init_and_plan_context("examples/sushi")

tests/core/test_plan.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
SeedKind,
1919
SeedModel,
2020
SqlModel,
21+
ModelKindName,
2122
)
2223
from sqlmesh.core.model.kind import OnDestructiveChange
2324
from sqlmesh.core.model.seed import Seed
@@ -91,12 +92,22 @@ def test_forward_only_plan_sets_version(make_snapshot, mocker: MockerFixture):
9192

9293

9394
def test_forward_only_dev(make_snapshot, mocker: MockerFixture):
94-
snapshot_a = make_snapshot(
95+
snapshot = make_snapshot(
9596
SqlModel(
9697
name="a",
9798
dialect="duckdb",
9899
query=parse_one("select 1, ds"),
99-
kind=IncrementalByTimeRangeKind(time_column="ds"),
100+
kind=dict(name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, time_column="ds"),
101+
)
102+
)
103+
104+
# Simulate a direct change.
105+
updated_snapshot = make_snapshot(
106+
SqlModel(
107+
**{
108+
**snapshot.model.dict(),
109+
"query": parse_one("select 2, ds"),
110+
}
100111
)
101112
)
102113

@@ -111,9 +122,9 @@ def test_forward_only_dev(make_snapshot, mocker: MockerFixture):
111122
create_from="prod",
112123
added=set(),
113124
removed_snapshots={},
114-
modified_snapshots={snapshot_a.name: (snapshot_a, snapshot_a)},
115-
snapshots={snapshot_a.snapshot_id: snapshot_a},
116-
new_snapshots={snapshot_a.snapshot_id: snapshot_a},
125+
modified_snapshots={snapshot.name: (updated_snapshot, snapshot)},
126+
snapshots={updated_snapshot.snapshot_id: updated_snapshot},
127+
new_snapshots={updated_snapshot.snapshot_id: updated_snapshot},
117128
previous_plan_id=None,
118129
previously_promoted_snapshot_ids=set(),
119130
previous_finalized_snapshots=None,
@@ -133,12 +144,66 @@ def test_forward_only_dev(make_snapshot, mocker: MockerFixture):
133144
).build()
134145

135146
assert plan.restatements == {
136-
snapshot_a.snapshot_id: (to_timestamp(expected_start), expected_interval_end)
147+
updated_snapshot.snapshot_id: (to_timestamp(expected_start), expected_interval_end)
137148
}
138149
assert plan.start == to_date(expected_start)
139150
assert plan.end == expected_end
140151

141152

153+
def test_forward_only_metadata_change_dev(make_snapshot, mocker: MockerFixture):
154+
snapshot = make_snapshot(
155+
SqlModel(
156+
name="a",
157+
dialect="duckdb",
158+
query=parse_one("select 1, ds"),
159+
kind=dict(name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, time_column="ds"),
160+
)
161+
)
162+
163+
# Simulate a metadata change.
164+
updated_snapshot = make_snapshot(
165+
SqlModel(
166+
**{
167+
**snapshot.model.dict(),
168+
"owner": "new_owner",
169+
}
170+
)
171+
)
172+
173+
expected_start = to_date("2022-01-02")
174+
expected_end = to_date("2022-01-03")
175+
176+
context_diff = ContextDiff(
177+
environment="test_environment",
178+
is_new_environment=True,
179+
is_unfinalized_environment=False,
180+
create_from="prod",
181+
added=set(),
182+
removed_snapshots={},
183+
modified_snapshots={updated_snapshot.name: (updated_snapshot, snapshot)},
184+
snapshots={updated_snapshot.snapshot_id: snapshot},
185+
new_snapshots={updated_snapshot.snapshot_id: snapshot},
186+
previous_plan_id=None,
187+
previously_promoted_snapshot_ids=set(),
188+
previous_finalized_snapshots=None,
189+
)
190+
191+
yesterday_ds_mock = mocker.patch("sqlmesh.core.plan.builder.yesterday_ds")
192+
yesterday_ds_mock.return_value = expected_start
193+
194+
now_mock = mocker.patch("sqlmesh.core.snapshot.definition.now")
195+
now_mock.return_value = expected_end
196+
197+
mocker.patch("sqlmesh.core.plan.builder.now").return_value = expected_end
198+
mocker.patch("sqlmesh.core.plan.definition.now").return_value = expected_end
199+
200+
plan = PlanBuilder(
201+
context_diff, DuckDBEngineAdapter.SCHEMA_DIFFER, forward_only=True, is_dev=True
202+
).build()
203+
204+
assert not plan.restatements
205+
206+
142207
def test_forward_only_plan_added_models(make_snapshot, mocker: MockerFixture):
143208
snapshot_a = make_snapshot(
144209
SqlModel(name="a", query=parse_one("select 1 as a, ds"), dialect="duckdb")

0 commit comments

Comments
 (0)