@@ -239,8 +239,6 @@ def set_choice(self, snapshot: Snapshot, choice: SnapshotChangeCategory) -> Plan
239239 snapshot: The target snapshot.
240240 choice: The user decision on how to version the target snapshot and its children.
241241 """
242- if self ._forward_only :
243- raise PlanError ("Choice setting is not supported by a forward-only plan." )
244242 if not self ._is_new_snapshot (snapshot ):
245243 raise PlanError (
246244 f"A choice can't be changed for the existing version of { snapshot .name } ."
@@ -250,8 +248,6 @@ def set_choice(self, snapshot: Snapshot, choice: SnapshotChangeCategory) -> Plan
250248 and snapshot .snapshot_id not in self ._context_diff .added
251249 ):
252250 raise PlanError (f"Only directly modified models can be categorized ({ snapshot .name } )." )
253- if snapshot .is_model and snapshot .model .forward_only :
254- raise PlanError (f"Forward-only model { snapshot .name } cannot be categorized manually." )
255251
256252 self ._choices [snapshot .snapshot_id ] = choice
257253 self ._latest_plan = None
@@ -369,8 +365,10 @@ def _build_restatements(
369365 restate_models = {
370366 s .name
371367 for s in self ._context_diff .new_snapshots .values ()
372- if s .is_materialized
373- and (self ._forward_only or s .model .forward_only )
368+ if s .is_model
369+ and not s .is_symbolic
370+ and (s .is_forward_only or s .model .forward_only )
371+ and not s .is_no_preview
374372 and (
375373 # Metadata changes should not be previewed.
376374 self ._context_diff .directly_modified (s .name )
@@ -395,6 +393,9 @@ def _build_restatements(
395393 for s_id in dag :
396394 snapshot = self ._context_diff .snapshots [s_id ]
397395
396+ if is_preview and snapshot .is_no_preview :
397+ continue
398+
398399 # Since we are traversing the graph in topological order and the largest interval range is pushed down
399400 # the graph we just have to check our immediate parents in the graph and not the whole upstream graph.
400401 restating_parents = [
@@ -583,38 +584,35 @@ def _categorize_snapshots(
583584 if not snapshot or not self ._is_new_snapshot (snapshot ):
584585 continue
585586
587+ forward_only = self ._is_forward_only_change (s_id ) or self ._forward_only
588+
586589 if s_id in self ._choices :
587- snapshot .categorize_as (self ._choices [s_id ])
590+ snapshot .categorize_as (self ._choices [s_id ], forward_only )
588591 continue
589592
590593 if s_id in self ._context_diff .added :
591- snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
592- elif self ._is_forward_only_change (s_id ) or self ._forward_only :
593- # In case of the forward only plan any modifications result in reuse of the
594- # previous version for non-seed models.
595- # New snapshots of seed models are considered non-breaking ones.
596- category = (
597- SnapshotChangeCategory .NON_BREAKING
598- if snapshot .is_seed
599- else SnapshotChangeCategory .FORWARD_ONLY
600- )
601- # If the model kind changes mark as breaking
602- if snapshot .is_model and snapshot .name in self ._context_diff .modified_snapshots :
603- _ , old = self ._context_diff .modified_snapshots [snapshot .name ]
604- if _is_breaking_kind_change (old , snapshot ):
605- category = SnapshotChangeCategory .BREAKING
606-
607- snapshot .categorize_as (category )
594+ snapshot .categorize_as (SnapshotChangeCategory .BREAKING , forward_only )
608595 elif s_id .name in self ._context_diff .modified_snapshots :
609- self ._categorize_snapshot (snapshot , dag , indirectly_modified )
596+ self ._categorize_snapshot (snapshot , forward_only , dag , indirectly_modified )
610597
611598 def _categorize_snapshot (
612- self , snapshot : Snapshot , dag : DAG [SnapshotId ], indirectly_modified : SnapshotMapping
599+ self ,
600+ snapshot : Snapshot ,
601+ forward_only : bool ,
602+ dag : DAG [SnapshotId ],
603+ indirectly_modified : SnapshotMapping ,
613604 ) -> None :
614605 s_id = snapshot .snapshot_id
615606
616607 if self ._context_diff .directly_modified (s_id .name ):
617- if self ._auto_categorization_enabled :
608+ new , old = self ._context_diff .modified_snapshots [s_id .name ]
609+ if _is_breaking_kind_change (old , new ):
610+ snapshot .categorize_as (SnapshotChangeCategory .BREAKING , False )
611+ elif self ._auto_categorization_enabled :
612+ if snapshot .is_seed :
613+ # Seed changes can't be forward-only.
614+ forward_only = False
615+
618616 s_id_with_missing_columns : t .Optional [SnapshotId ] = None
619617 this_sid_with_downstream = indirectly_modified .get (s_id , set ()) | {s_id }
620618 for downstream_s_id in this_sid_with_downstream :
@@ -626,18 +624,18 @@ def _categorize_snapshot(
626624 s_id_with_missing_columns = downstream_s_id
627625 break
628626
629- new , old = self ._context_diff .modified_snapshots [s_id .name ]
630627 if s_id_with_missing_columns is None :
631628 change_category = categorize_change (new , old , config = self ._categorizer_config )
632629 if change_category is not None :
633- snapshot .categorize_as (change_category )
630+ snapshot .categorize_as (change_category , forward_only )
634631 else :
635632 mode = self ._categorizer_config .dict ().get (
636633 new .model .source_type , AutoCategorizationMode .OFF
637634 )
638635 if mode == AutoCategorizationMode .FULL :
639- snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
636+ snapshot .categorize_as (SnapshotChangeCategory .BREAKING , forward_only )
640637 elif self ._context_diff .indirectly_modified (snapshot .name ):
638+ all_upstream_forward_only = set ()
641639 all_upstream_categories = set ()
642640 direct_parent_categories = set ()
643641
@@ -646,27 +644,30 @@ def _categorize_snapshot(
646644
647645 if parent and self ._is_new_snapshot (parent ):
648646 all_upstream_categories .add (parent .change_category )
647+ all_upstream_forward_only .add (parent .is_forward_only )
649648 if p_id in snapshot .parents :
650649 direct_parent_categories .add (parent .change_category )
651650
652- if snapshot .is_model and snapshot .model .forward_only :
653- snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
654- elif direct_parent_categories .intersection (
651+ if all_upstream_forward_only == {True } or (
652+ snapshot .is_model and snapshot .model .forward_only
653+ ):
654+ forward_only = True
655+
656+ if direct_parent_categories .intersection (
655657 {SnapshotChangeCategory .BREAKING , SnapshotChangeCategory .INDIRECT_BREAKING }
656658 ):
657- snapshot .categorize_as (SnapshotChangeCategory .INDIRECT_BREAKING )
659+ snapshot .categorize_as (SnapshotChangeCategory .INDIRECT_BREAKING , forward_only )
658660 elif not direct_parent_categories :
659- snapshot .categorize_as (self ._get_orphaned_indirect_change_category (snapshot ))
660- elif SnapshotChangeCategory .FORWARD_ONLY in all_upstream_categories :
661- # FORWARD_ONLY must take precedence over INDIRECT_NON_BREAKING
662- snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
661+ snapshot .categorize_as (
662+ self ._get_orphaned_indirect_change_category (snapshot ), forward_only
663+ )
663664 elif all_upstream_categories == {SnapshotChangeCategory .METADATA }:
664- snapshot .categorize_as (SnapshotChangeCategory .METADATA )
665+ snapshot .categorize_as (SnapshotChangeCategory .METADATA , forward_only )
665666 else :
666- snapshot .categorize_as (SnapshotChangeCategory .INDIRECT_NON_BREAKING )
667+ snapshot .categorize_as (SnapshotChangeCategory .INDIRECT_NON_BREAKING , forward_only )
667668 else :
668669 # Metadata updated.
669- snapshot .categorize_as (SnapshotChangeCategory .METADATA )
670+ snapshot .categorize_as (SnapshotChangeCategory .METADATA , forward_only )
670671
671672 def _get_orphaned_indirect_change_category (
672673 self , indirect_snapshot : Snapshot
@@ -769,10 +770,7 @@ def _is_forward_only_change(self, s_id: SnapshotId) -> bool:
769770 if snapshot .is_model and _is_breaking_kind_change (old , snapshot ):
770771 return False
771772 return (
772- snapshot .is_model
773- and snapshot .model .forward_only
774- and not snapshot .change_category
775- and bool (snapshot .previous_versions )
773+ snapshot .is_model and snapshot .model .forward_only and bool (snapshot .previous_versions )
776774 )
777775
778776 def _is_new_snapshot (self , snapshot : Snapshot ) -> bool :
@@ -811,7 +809,7 @@ def _ensure_no_forward_only_revert(self) -> None:
811809 and not candidate .model .forward_only
812810 and promoted .is_forward_only
813811 and not promoted .is_paused
814- and not candidate .reuses_previous_version
812+ and not candidate .is_no_rebuild
815813 and promoted .version == candidate .version
816814 ):
817815 raise PlanError (
0 commit comments