Skip to content

Commit 9760ae0

Browse files
Chore: Validate start and end dates during plan build time (#5100)
1 parent d177625 commit 9760ae0

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

sqlmesh/core/plan/builder.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def __init__(
155155

156156
self._backfill_models = backfill_models
157157
self._end = end or default_end
158+
self._default_start = default_start
158159
self._apply = apply
159160
self._console = console or get_console()
160161
self._choices: t.Dict[SnapshotId, SnapshotChangeCategory] = {}
@@ -802,6 +803,25 @@ def _ensure_valid_date_range(self) -> None:
802803
f"Plan end date: '{time_like_to_str(end)}' cannot be in the future (execution time: '{time_like_to_str(self.execution_time)}')"
803804
)
804805

806+
# Validate model-specific start/end dates
807+
if (start := self.start or self._default_start) and (end := self.end):
808+
start_ts = to_datetime(start)
809+
end_ts = to_datetime(end)
810+
if start_ts > end_ts:
811+
models_to_check: t.Set[str] = (
812+
set(self._backfill_models or [])
813+
| set(self._context_diff.modified_snapshots.keys())
814+
| {s.name for s in self._context_diff.added}
815+
| set((self._end_override_per_model or {}).keys())
816+
)
817+
for model_name in models_to_check:
818+
if snapshot := self._model_fqn_to_snapshot.get(model_name):
819+
if snapshot.node.start is None or to_datetime(snapshot.node.start) > end_ts:
820+
raise PlanError(
821+
f"Model '{model_name}': Start date / time '({time_like_to_str(start_ts)})' can't be greater than end date / time '({time_like_to_str(end_ts)})'.\n"
822+
f"Set the `start` attribute in your project config model defaults to avoid this issue."
823+
)
824+
805825
def _ensure_no_broken_references(self) -> None:
806826
for snapshot in self._context_diff.snapshots.values():
807827
broken_references = {

tests/core/test_context.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,3 +3007,59 @@ def test_uppercase_gateway_external_models(tmp_path):
30073007
assert len(uppercase_in_yaml_models) == 1, (
30083008
f"External model with uppercase gateway in YAML should be found. Found {len(uppercase_in_yaml_models)} models"
30093009
)
3010+
3011+
3012+
def test_plan_no_start_configured():
3013+
context = Context(config=Config())
3014+
context.upsert_model(
3015+
load_sql_based_model(
3016+
parse(
3017+
"""
3018+
MODEL(
3019+
name db.xvg,
3020+
kind INCREMENTAL_BY_TIME_RANGE (
3021+
time_column ds
3022+
),
3023+
cron '@daily'
3024+
);
3025+
3026+
SELECT id, ds FROM (VALUES
3027+
('1', '2020-01-01'),
3028+
) data(id, ds)
3029+
WHERE ds BETWEEN @start_ds AND @end_ds
3030+
"""
3031+
)
3032+
)
3033+
)
3034+
3035+
prod_plan = context.plan(auto_apply=True)
3036+
assert len(prod_plan.new_snapshots) == 1
3037+
3038+
context.upsert_model(
3039+
load_sql_based_model(
3040+
parse(
3041+
"""
3042+
MODEL(
3043+
name db.xvg,
3044+
kind INCREMENTAL_BY_TIME_RANGE (
3045+
time_column ds
3046+
),
3047+
cron '@daily',
3048+
physical_properties ('some_prop' = 1),
3049+
);
3050+
3051+
SELECT id, ds FROM (VALUES
3052+
('1', '2020-01-01'),
3053+
) data(id, ds)
3054+
WHERE ds BETWEEN @start_ds AND @end_ds
3055+
"""
3056+
)
3057+
)
3058+
)
3059+
3060+
# This should raise an error because the model has no start configured and the end time is less than the start time which will be calculated from the intervals
3061+
with pytest.raises(
3062+
PlanError,
3063+
match=r"Model '.*xvg.*': Start date / time .* can't be greater than end date / time .*\.\nSet the `start` attribute in your project config model defaults to avoid this issue",
3064+
):
3065+
context.plan("dev", execution_time="1999-01-05")

0 commit comments

Comments
 (0)