Skip to content

Commit 8c793ab

Browse files
authored
Fix: Direct breaking category should only impact direct children snapshot (#3975)
1 parent db507bc commit 8c793ab

3 files changed

Lines changed: 62 additions & 8 deletions

File tree

sqlmesh/core/plan/builder.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -578,21 +578,23 @@ def _categorize_snapshot(
578578
if mode == AutoCategorizationMode.FULL:
579579
snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
580580
elif self._context_diff.indirectly_modified(snapshot.name):
581-
categories = []
581+
all_upstream_categories = set()
582+
direct_parent_categories = set()
582583

583584
for p_id in dag.upstream(s_id):
584585
parent = self._context_diff.snapshots.get(p_id)
585586

586587
if parent and self._is_new_snapshot(parent):
587-
categories.append(parent.change_category)
588+
all_upstream_categories.add(parent.change_category)
589+
if p_id in snapshot.parents:
590+
direct_parent_categories.add(parent.change_category)
588591

589-
if not categories or any(
590-
category.is_breaking or category.is_indirect_breaking
591-
for category in categories
592-
if category
592+
if not direct_parent_categories or direct_parent_categories.intersection(
593+
{SnapshotChangeCategory.BREAKING, SnapshotChangeCategory.INDIRECT_BREAKING}
593594
):
594595
snapshot.categorize_as(SnapshotChangeCategory.INDIRECT_BREAKING)
595-
elif any(category.is_forward_only for category in categories if category):
596+
elif SnapshotChangeCategory.FORWARD_ONLY in all_upstream_categories:
597+
# FORWARD_ONLY must take precedence over INDIRECT_NON_BREAKING
596598
snapshot.categorize_as(SnapshotChangeCategory.FORWARD_ONLY)
597599
else:
598600
snapshot.categorize_as(SnapshotChangeCategory.INDIRECT_NON_BREAKING)

tests/core/test_integration.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,58 @@ def test_forward_only_precedence_over_indirect_non_breaking(init_and_plan_contex
14281428
)
14291429

14301430

1431+
@time_machine.travel("2023-01-08 15:00:00 UTC")
1432+
def test_breaking_only_impacts_immediate_children(init_and_plan_context: t.Callable):
1433+
context, plan = init_and_plan_context("examples/sushi")
1434+
context.apply(plan)
1435+
1436+
breaking_model = context.get_model("sushi.orders")
1437+
breaking_model = breaking_model.copy(update={"stamp": "force new version"})
1438+
context.upsert_model(breaking_model)
1439+
breaking_snapshot = context.get_snapshot(breaking_model, raise_if_missing=True)
1440+
1441+
non_breaking_model = context.get_model("sushi.waiter_revenue_by_day")
1442+
context.upsert_model(add_projection_to_model(t.cast(SqlModel, non_breaking_model)))
1443+
non_breaking_snapshot = context.get_snapshot(non_breaking_model, raise_if_missing=True)
1444+
top_waiter_snapshot = context.get_snapshot("sushi.top_waiters", raise_if_missing=True)
1445+
1446+
plan_builder = context.plan_builder("dev", skip_tests=True, enable_preview=False)
1447+
plan_builder.set_choice(breaking_snapshot, SnapshotChangeCategory.BREAKING)
1448+
plan = plan_builder.build()
1449+
assert (
1450+
plan.context_diff.snapshots[breaking_snapshot.snapshot_id].change_category
1451+
== SnapshotChangeCategory.BREAKING
1452+
)
1453+
assert (
1454+
plan.context_diff.snapshots[non_breaking_snapshot.snapshot_id].change_category
1455+
== SnapshotChangeCategory.NON_BREAKING
1456+
)
1457+
assert (
1458+
plan.context_diff.snapshots[top_waiter_snapshot.snapshot_id].change_category
1459+
== SnapshotChangeCategory.INDIRECT_NON_BREAKING
1460+
)
1461+
assert plan.start == to_timestamp("2023-01-01")
1462+
assert not any(i.snapshot_id == top_waiter_snapshot.snapshot_id for i in plan.missing_intervals)
1463+
1464+
context.apply(plan)
1465+
assert (
1466+
not context.plan_builder("dev", skip_tests=True, enable_preview=False)
1467+
.build()
1468+
.requires_backfill
1469+
)
1470+
1471+
# Deploy everything to prod.
1472+
plan = context.plan_builder("prod", skip_tests=True).build()
1473+
assert not plan.missing_intervals
1474+
1475+
context.apply(plan)
1476+
assert (
1477+
not context.plan_builder("prod", skip_tests=True, enable_preview=False)
1478+
.build()
1479+
.requires_backfill
1480+
)
1481+
1482+
14311483
@time_machine.travel("2023-01-08 15:00:00 UTC")
14321484
def test_run_with_select_models(
14331485
init_and_plan_context: t.Callable,

tests/core/test_plan.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,7 @@ def test_indirectly_modified_forward_only_model(make_snapshot, mocker: MockerFix
17461746

17471747
assert updated_snapshot_a.change_category == SnapshotChangeCategory.BREAKING
17481748
assert updated_snapshot_b.change_category == SnapshotChangeCategory.FORWARD_ONLY
1749-
assert updated_snapshot_c.change_category == SnapshotChangeCategory.INDIRECT_BREAKING
1749+
assert updated_snapshot_c.change_category == SnapshotChangeCategory.FORWARD_ONLY
17501750
assert updated_snapshot_d.change_category == SnapshotChangeCategory.INDIRECT_BREAKING
17511751

17521752
deployability_index = DeployabilityIndex.create(

0 commit comments

Comments
 (0)