Repository: TobikoData/sqlmesh
Pull request #3292 (2024-10-25 04:17:18)

Fix: Make sure that ignored models are not included in preview restatements


Patch:
diff --git a/sqlmesh/core/plan/builder.py b/sqlmesh/core/plan/builder.py
index 48eb7ede..6dcc0569 100644
--- a/sqlmesh/core/plan/builder.py
+++ b/sqlmesh/core/plan/builder.py
@@ -254,7 +254,7 @@ class PlanBuilder:
 
         models_to_backfill = self._build_models_to_backfill(filtered_dag)
         restatements = self._build_restatements(
-            dag, earliest_interval_start(filtered_snapshots.values())
+            dag, earliest_interval_start(filtered_snapshots.values()), ignored
         )
 
         interval_end_per_model = self._interval_end_per_model
@@ -323,12 +323,19 @@ class PlanBuilder:
         return filtered_dag, ignored_snapshot_ids
 
     def _build_restatements(
-        self, dag: DAG[SnapshotId], earliest_interval_start: TimeLike
+        self,
+        dag: DAG[SnapshotId],
+        earliest_interval_start: TimeLike,
+        ignored_snapshots: t.Set[SnapshotId],
     ) -> t.Dict[SnapshotId, Interval]:
         def is_restateable_snapshot(snapshot: Snapshot) -> bool:
             if not self._is_dev and snapshot.disable_restatement:
                 return False
-            return not snapshot.is_symbolic and not snapshot.is_seed
+            return (
+                not snapshot.is_symbolic
+                and not snapshot.is_seed
+                and snapshot.snapshot_id not in ignored_snapshots
+            )
 
         restate_models = self._restate_models
         if restate_models == set():
@@ -354,6 +361,7 @@ class PlanBuilder:
                     self._context_diff.directly_modified(s.name)
                     or self._context_diff.indirectly_modified(s.name)
                 )
+                and s.snapshot_id not in ignored_snapshots
             }
             is_preview = True
 


Test patch:
diff --git a/tests/core/test_plan.py b/tests/core/test_plan.py
index 6e25ee91..b8abb038 100644
--- a/tests/core/test_plan.py
+++ b/tests/core/test_plan.py
@@ -2577,3 +2577,69 @@ def test_plan_requirements():
         "dev", no_prompts=True, skip_tests=True, skip_backfill=True
     ).environment.requirements
     assert set(plan) == {"ipywidgets", "numpy", "pandas", "ruamel.yaml", "ruamel.yaml.clib"}
+
+
+def test_ignored_model_with_forward_only_preview(make_snapshot):
+    snapshot_a = make_snapshot(
+        SqlModel(
+            name="a",
+            query=parse_one("select 1, ds"),
+            kind=dict(
+                name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, forward_only=True, time_column="ds"
+            ),
+        )
+    )
+    snapshot_a.categorize_as(SnapshotChangeCategory.BREAKING)
+
+    new_snapshot_a = make_snapshot(
+        SqlModel(
+            name="a",
+            query=parse_one("select 2, ds"),
+            kind=dict(
+                name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, forward_only=True, time_column="ds"
+            ),
+        )
+    )
+    new_snapshot_a.previous_versions = snapshot_a.all_versions
+    new_snapshot_a.unpaused_ts = 1
+
+    snapshot_b = make_snapshot(
+        SqlModel(
+            name="b",
+            query=parse_one("select 1 AS key"),
+            kind=dict(name=ModelKindName.INCREMENTAL_BY_UNIQUE_KEY, unique_key="key"),
+            start="2024-01-01",
+            depends_on={"a"},
+        ),
+        nodes={new_snapshot_a.name: new_snapshot_a.model},
+    )
+
+    context_diff = ContextDiff(
+        environment="test_environment",
+        is_new_environment=True,
+        is_unfinalized_environment=False,
+        normalize_environment_name=True,
+        create_from="prod",
+        added={snapshot_b.snapshot_id},
+        removed_snapshots={},
+        snapshots={new_snapshot_a.snapshot_id: new_snapshot_a, snapshot_b.snapshot_id: snapshot_b},
+        new_snapshots={
+            new_snapshot_a.snapshot_id: new_snapshot_a,
+            snapshot_b.snapshot_id: snapshot_b,
+        },
+        modified_snapshots={snapshot_a.name: (new_snapshot_a, snapshot_a)},
+        previous_plan_id=None,
+        previously_promoted_snapshot_ids=set(),
+        previous_finalized_snapshots=None,
+    )
+
+    plan_builder = PlanBuilder(
+        context_diff,
+        DuckDBEngineAdapter.SCHEMA_DIFFER,
+        enable_preview=True,
+        is_dev=True,
+    )
+    plan = plan_builder.build()
+
+    assert plan.ignored == {snapshot_b.snapshot_id}
+    assert set(plan.restatements) == {new_snapshot_a.snapshot_id}