From c7607e325d1efa81c3cc08064caf95a2fbd4eb73 Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Thu, 5 Nov 2026 22:54:36 -0400 Subject: [PATCH] fix(snapshot): check full_history_restatement_only before depends_on_self Snapshot.depends_on_past evaluated depends_on_self first, which renders the model query and can fail to hydrate user code when computing removal intervals for restatement. Full history restatement models already depend on past intervals, so evaluate that check first and short-circuit. Only the evaluation order changes. Signed-off-by: Alex Castillo --- sqlmesh/core/snapshot/definition.py | 2 +- tests/core/test_snapshot.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/sqlmesh/core/snapshot/definition.py b/sqlmesh/core/snapshot/definition.py index 2d813a06b1..0cdb033be0 100644 --- a/sqlmesh/core/snapshot/definition.py +++ b/sqlmesh/core/snapshot/definition.py @@ -1472,7 +1472,7 @@ def depends_on_past(self) -> bool: An example of this can be an INCREMENTAL_BY_TIME_RANGE model that references previous records from itself. 2. Models that can only be restated from the beginning of history *and* their interval batches must be processed sequentially. """ - return self.depends_on_self or self.full_history_restatement_only + return self.full_history_restatement_only or self.depends_on_self @property def depends_on_self(self) -> bool: diff --git a/tests/core/test_snapshot.py b/tests/core/test_snapshot.py index 64bee7f472..d92f7dcc18 100644 --- a/tests/core/test_snapshot.py +++ b/tests/core/test_snapshot.py @@ -919,6 +919,31 @@ def test_get_removal_intervals_full_history_restatement_model(make_snapshot): assert interval == (to_timestamp("2023-01-01"), execution_time) +def test_get_removal_interval_full_history_does_not_render_model( + make_snapshot: t.Callable[..., Snapshot], mocker: MockerFixture +): + # Full history restatement models are already known to depend on past intervals, so computing + # the removal interval must not render the model query (which may fail to hydrate user code). + model = SqlModel( + name="name", + kind=IncrementalUnmanagedKind(), + query=parse_one("SELECT 1"), + ) + snapshot = make_snapshot(model) + snapshot.add_interval("2020-01-01", "2024-01-01") + # Drop the cached dependencies, as for a snapshot loaded from the state sync. + snapshot.model._full_depends_on = None + + render_query = mocker.patch.object(SqlModel, "render_query", side_effect=ModuleNotFoundError) + + execution_time = to_timestamp("2024-01-02") + interval = snapshot.get_removal_interval( + "2023-01-01", "2023-01-01", execution_time=execution_time + ) + assert interval == (to_timestamp("2020-01-01"), execution_time) + render_query.assert_not_called() + + def test_get_removal_intervals_warns_when_requested_range_automatically_widened( make_snapshot: t.Callable[..., Snapshot], mocker: MockerFixture ):