Skip to content

Commit 6e69ce6

Browse files
Fix: Exclude seed models from default plan end date calculation (#5720)
1 parent cea8418 commit 6e69ce6

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

sqlmesh/core/context.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,10 +3043,17 @@ def _get_plan_default_start_end(
30433043
modified_model_names: t.Set[str],
30443044
execution_time: t.Optional[TimeLike] = None,
30453045
) -> t.Tuple[t.Optional[int], t.Optional[int]]:
3046-
if not max_interval_end_per_model:
3046+
# exclude seeds so their stale interval ends does not become the default plan end date
3047+
# when they're the only ones that contain intervals in this plan
3048+
non_seed_interval_ends = {
3049+
model_fqn: end
3050+
for model_fqn, end in max_interval_end_per_model.items()
3051+
if model_fqn not in snapshots or not snapshots[model_fqn].is_seed
3052+
}
3053+
if not non_seed_interval_ends:
30473054
return None, None
30483055

3049-
default_end = to_timestamp(max(max_interval_end_per_model.values()))
3056+
default_end = to_timestamp(max(non_seed_interval_ends.values()))
30503057
default_start: t.Optional[int] = None
30513058
# Infer the default start by finding the smallest interval start that corresponds to the default end.
30523059
for model_name in backfill_models or modified_model_names or max_interval_end_per_model:

tests/core/test_context.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,72 @@ def test_plan_start_ahead_of_end(copy_to_temp_path):
11571157
context.close()
11581158

11591159

1160+
@pytest.mark.slow
1161+
def test_plan_seed_model_excluded_from_default_end(copy_to_temp_path: t.Callable):
1162+
path = copy_to_temp_path("examples/sushi")
1163+
with time_machine.travel("2024-06-01 00:00:00 UTC"):
1164+
context = Context(paths=path, gateway="duckdb_persistent")
1165+
context.plan("prod", no_prompts=True, auto_apply=True)
1166+
max_ends = context.state_sync.max_interval_end_per_model("prod")
1167+
seed_fqns = [k for k in max_ends if "waiter_names" in k]
1168+
assert len(seed_fqns) == 1
1169+
assert max_ends[seed_fqns[0]] == to_timestamp("2024-06-01")
1170+
context.close()
1171+
1172+
with time_machine.travel("2026-03-01 00:00:00 UTC"):
1173+
context = Context(paths=path, gateway="duckdb_persistent")
1174+
1175+
# a model that depends on this seed but has no interval in prod yet so only the seed would contribute to max_interval_end_per_model
1176+
context.upsert_model(
1177+
load_sql_based_model(
1178+
parse(
1179+
"""
1180+
MODEL(
1181+
name sushi.waiter_summary,
1182+
kind INCREMENTAL_BY_TIME_RANGE (
1183+
time_column ds
1184+
),
1185+
start '2025-01-01',
1186+
cron '@daily'
1187+
);
1188+
1189+
SELECT
1190+
id,
1191+
name,
1192+
@start_ds AS ds
1193+
FROM
1194+
sushi.waiter_names
1195+
WHERE
1196+
@start_ds BETWEEN @start_ds AND @end_ds
1197+
"""
1198+
),
1199+
default_catalog=context.default_catalog,
1200+
)
1201+
)
1202+
1203+
# the seed's interval end would still be 2024-06-01
1204+
max_ends = context.state_sync.max_interval_end_per_model("prod")
1205+
seed_fqns = [k for k in max_ends if "waiter_names" in k]
1206+
assert len(seed_fqns) == 1
1207+
assert max_ends[seed_fqns[0]] == to_timestamp("2024-06-01")
1208+
1209+
# the plan start date 2025-01-01 is after the seeds end date but shouldnt cause the plan to fail
1210+
plan = context.plan(
1211+
"dev", start="2025-01-01", no_prompts=True, select_models=["*waiter_summary"]
1212+
)
1213+
1214+
# the end should fall back to execution_time rather than seeds end
1215+
assert plan.models_to_backfill == {
1216+
'"duckdb"."sushi"."waiter_names"',
1217+
'"duckdb"."sushi"."waiter_summary"',
1218+
}
1219+
assert plan.provided_end is None
1220+
assert plan.provided_start == "2025-01-01"
1221+
assert to_timestamp(plan.end) == to_timestamp("2026-03-01")
1222+
assert to_timestamp(plan.start) == to_timestamp("2025-01-01")
1223+
context.close()
1224+
1225+
11601226
@pytest.mark.slow
11611227
def test_schema_error_no_default(sushi_context_pre_scheduling) -> None:
11621228
context = sushi_context_pre_scheduling

0 commit comments

Comments
 (0)