Skip to content

Commit a479509

Browse files
authored
Fix: Only check destructive changes for directly modified models at plan time (#2733)
1 parent 9c5b985 commit a479509

File tree

2 files changed

+35
-72
lines changed

2 files changed

+35
-72
lines changed

sqlmesh/core/plan/builder.py

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def build(self) -> Plan:
206206
dag = self._build_dag()
207207
directly_modified, indirectly_modified = self._build_directly_and_indirectly_modified(dag)
208208

209-
self._check_destructive_changes(dag, directly_modified)
209+
self._check_destructive_changes(directly_modified)
210210
self._categorize_snapshots(dag, directly_modified, indirectly_modified)
211211
self._adjust_new_snapshot_intervals()
212212

@@ -436,63 +436,40 @@ def _adjust_new_snapshot_intervals(self) -> None:
436436
if new.is_forward_only:
437437
new.dev_intervals = new.intervals.copy()
438438

439-
def _check_destructive_changes(
440-
self, dag: DAG[SnapshotId], directly_modified: t.Set[SnapshotId]
441-
) -> None:
442-
def _raise_or_warn(snapshot: Snapshot) -> None:
443-
warning_msg = f"Plan results in a destructive change to forward-only model '{snapshot.name}'s schema."
444-
if snapshot.model.on_destructive_change.is_warn:
445-
logger.warning(warning_msg)
446-
return
447-
raise PlanError(
448-
f"{warning_msg} To allow this, change the model's `on_destructive_change` setting to `warn` or `allow` or include it in the plan's `--allow-destructive-model` option."
449-
)
439+
def _check_destructive_changes(self, directly_modified: t.Set[SnapshotId]) -> None:
440+
for s_id in sorted(directly_modified):
441+
snapshot = self._context_diff.snapshots[s_id]
442+
# should we raise/warn if this snapshot has/inherits a destructive change?
443+
should_raise_or_warn = (
444+
self._is_forward_only_change(s_id) or self._forward_only
445+
) and snapshot.needs_destructive_check(self._allow_destructive_models)
450446

451-
snapshot_is_destructive = {}
452-
for s_id in dag:
453-
# Only process snapshots that are modified by the plan
454-
if s_id.name in self._context_diff.modified_snapshots:
455-
snapshot = self._context_diff.snapshots[s_id]
456-
457-
if snapshot and snapshot.is_model:
458-
# should we raise/warn if this snapshot has/inherits a destructive change?
459-
should_raise_or_warn = (
460-
snapshot.model.forward_only or self._forward_only
461-
) and snapshot.needs_destructive_check(self._allow_destructive_models)
462-
463-
snapshot_is_destructive[s_id] = False
464-
465-
# get parent classifications
466-
for parent_id in snapshot.parents:
467-
if snapshot_is_destructive.get(parent_id):
468-
snapshot_is_destructive[s_id] = True
469-
if should_raise_or_warn:
470-
_raise_or_warn(snapshot)
471-
break
472-
473-
# examine directly modified snapshots unless we already know the snapshot is destructive
474-
if s_id in directly_modified and not snapshot_is_destructive[s_id]:
475-
new, old = self._context_diff.modified_snapshots[snapshot.name]
476-
477-
# we must know all columns_to_types to determine whether a change is destructive
478-
old_columns_to_types = old.model.columns_to_types or {}
479-
new_columns_to_types = new.model.columns_to_types or {}
480-
481-
if not columns_to_types_all_known(
482-
old_columns_to_types
483-
) or not columns_to_types_all_known(new_columns_to_types):
484-
snapshot_is_destructive[s_id] = False
485-
else:
486-
schema_diff = self._engine_schema_differ.compare_columns(
487-
new.name,
488-
old_columns_to_types,
489-
new_columns_to_types,
490-
)
447+
if not should_raise_or_warn or not snapshot.is_model:
448+
continue
449+
450+
new, old = self._context_diff.modified_snapshots[snapshot.name]
451+
452+
# we must know all columns_to_types to determine whether a change is destructive
453+
old_columns_to_types = old.model.columns_to_types or {}
454+
new_columns_to_types = new.model.columns_to_types or {}
455+
456+
if columns_to_types_all_known(old_columns_to_types) and columns_to_types_all_known(
457+
new_columns_to_types
458+
):
459+
schema_diff = self._engine_schema_differ.compare_columns(
460+
new.name,
461+
old_columns_to_types,
462+
new_columns_to_types,
463+
)
491464

492-
has_drop = has_drop_alteration(schema_diff)
493-
snapshot_is_destructive[s_id] = has_drop
494-
if has_drop and should_raise_or_warn:
495-
_raise_or_warn(snapshot)
465+
if has_drop_alteration(schema_diff):
466+
warning_msg = f"Plan results in a destructive change to forward-only model '{snapshot.name}'s schema"
467+
if snapshot.model.on_destructive_change.is_warn:
468+
logger.warning(warning_msg)
469+
else:
470+
raise PlanError(
471+
f"{warning_msg}. To allow this, change the model's `on_destructive_change` setting to `warn` or `allow` or include it in the plan's `--allow-destructive-model` option."
472+
)
496473

497474
def _categorize_snapshots(
498475
self,

tests/core/test_plan.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,6 @@ def test_forward_only_model_on_destructive_change(
355355
},
356356
snapshots={
357357
snapshot_a.snapshot_id: snapshot_a,
358-
snapshot_a_old.snapshot_id: snapshot_a_old,
359358
},
360359
new_snapshots={
361360
snapshot_a.snapshot_id: snapshot_a,
@@ -409,9 +408,7 @@ def test_forward_only_model_on_destructive_change(
409408
},
410409
snapshots={
411410
snapshot_a2.snapshot_id: snapshot_a2,
412-
snapshot_a_old2.snapshot_id: snapshot_a_old2,
413411
snapshot_b2.snapshot_id: snapshot_b2,
414-
snapshot_b_old2.snapshot_id: snapshot_b_old2,
415412
},
416413
new_snapshots={
417414
snapshot_a2.snapshot_id: snapshot_a2,
@@ -422,11 +419,7 @@ def test_forward_only_model_on_destructive_change(
422419
previous_finalized_snapshots=None,
423420
)
424421

425-
with pytest.raises(
426-
PlanError,
427-
match="""Plan results in a destructive change to forward-only model '"b"'s schema.""",
428-
):
429-
PlanBuilder(context_diff_2, schema_differ).build()
422+
PlanBuilder(context_diff_2, schema_differ).build()
430423

431424
# allow A and B, indirect change to C
432425
snapshot_a_old3, snapshot_a3 = make_snapshot_on_destructive_change(
@@ -493,11 +486,8 @@ def test_forward_only_model_on_destructive_change(
493486
},
494487
snapshots={
495488
snapshot_a3.snapshot_id: snapshot_a3,
496-
snapshot_a_old3.snapshot_id: snapshot_a_old3,
497489
snapshot_b3.snapshot_id: snapshot_b3,
498-
snapshot_b_old3.snapshot_id: snapshot_b_old3,
499490
snapshot_c3.snapshot_id: snapshot_c3,
500-
snapshot_c_old3.snapshot_id: snapshot_c_old3,
501491
},
502492
new_snapshots={
503493
snapshot_a3.snapshot_id: snapshot_a3,
@@ -509,11 +499,7 @@ def test_forward_only_model_on_destructive_change(
509499
previous_finalized_snapshots=None,
510500
)
511501

512-
with pytest.raises(
513-
PlanError,
514-
match="""Plan results in a destructive change to forward-only model '"c"'s schema.""",
515-
):
516-
PlanBuilder(context_diff_3, schema_differ).build()
502+
PlanBuilder(context_diff_3, schema_differ).build()
517503

518504

519505
def test_forward_only_model_on_destructive_change_no_column_types(

0 commit comments

Comments
 (0)