1313from sqlglot import exp
1414from sqlglot .optimizer .normalize_identifiers import normalize_identifiers
1515
16- from sqlmesh .core .config import TableNamingConvention
16+ from sqlmesh .core .config . common import TableNamingConvention , VirtualEnvironmentMode
1717from sqlmesh .core import constants as c
1818from sqlmesh .core .audit import StandaloneAudit
1919from sqlmesh .core .environment import EnvironmentSuffixTarget
@@ -230,6 +230,7 @@ class SnapshotDataVersion(PydanticModel, frozen=True):
230230 physical_schema_ : t .Optional [str ] = Field (default = None , alias = "physical_schema" )
231231 dev_table_suffix : str
232232 table_naming_convention : TableNamingConvention = Field (default = TableNamingConvention .default )
233+ virtual_environment_mode : VirtualEnvironmentMode = Field (default = VirtualEnvironmentMode .default )
233234
234235 def snapshot_id (self , name : str ) -> SnapshotId :
235236 return SnapshotId (name = name , identifier = self .fingerprint .to_identifier ())
@@ -338,6 +339,7 @@ class SnapshotInfoMixin(ModelKindMixin):
338339 dev_table_suffix : str
339340 table_naming_convention : TableNamingConvention = Field (default = TableNamingConvention .default )
340341 forward_only : bool
342+ virtual_environment_mode : VirtualEnvironmentMode
341343
342344 @cached_property
343345 def identifier (self ) -> str :
@@ -443,6 +445,10 @@ def _table_name(self, version: str, is_deployable: bool) -> str:
443445 if self .is_external :
444446 return self .name
445447
448+ if is_deployable and self .virtual_environment_mode .is_dev_only :
449+ # Use the model name as is if the target is deployable and the virtual environment mode is set to dev-only
450+ return self .name
451+
446452 is_dev_table = not is_deployable
447453 if is_dev_table :
448454 version = self .dev_version
@@ -459,6 +465,7 @@ def _table_name(self, version: str, is_deployable: bool) -> str:
459465 fqt = self .fully_qualified_table .copy ()
460466 fqt .set ("catalog" , None )
461467 base_table_name = fqt .sql ()
468+
462469 return table_name (
463470 self .physical_schema ,
464471 base_table_name ,
@@ -499,6 +506,8 @@ class SnapshotTableInfo(PydanticModel, SnapshotInfoMixin, frozen=True):
499506 dev_table_suffix : str
500507 model_gateway : t .Optional [str ] = None
501508 forward_only : bool = False
509+ table_naming_convention : TableNamingConvention = Field (default = TableNamingConvention .default )
510+ virtual_environment_mode : VirtualEnvironmentMode = Field (default = VirtualEnvironmentMode .default )
502511
503512 def __lt__ (self , other : SnapshotTableInfo ) -> bool :
504513 return self .name < other .name
@@ -540,6 +549,7 @@ def data_version(self) -> SnapshotDataVersion:
540549 physical_schema = self .physical_schema ,
541550 dev_table_suffix = self .dev_table_suffix ,
542551 table_naming_convention = self .table_naming_convention ,
552+ virtual_environment_mode = self .virtual_environment_mode ,
543553 )
544554
545555 @property
@@ -627,6 +637,7 @@ class Snapshot(PydanticModel, SnapshotInfoMixin):
627637 default = TableNamingConvention .default , alias = "table_naming_convention"
628638 )
629639 forward_only : bool = False
640+ virtual_environment_mode : VirtualEnvironmentMode = Field (default = VirtualEnvironmentMode .default )
630641
631642 @field_validator ("ttl" )
632643 @classmethod
@@ -679,6 +690,7 @@ def from_node(
679690 version : t .Optional [str ] = None ,
680691 cache : t .Optional [t .Dict [str , SnapshotFingerprint ]] = None ,
681692 table_naming_convention : TableNamingConvention = TableNamingConvention .default ,
693+ virtual_environment_mode : VirtualEnvironmentMode = VirtualEnvironmentMode .default ,
682694 ) -> Snapshot :
683695 """Creates a new snapshot for a node.
684696
@@ -690,6 +702,7 @@ def from_node(
690702 version: The version that a snapshot is associated with. Usually set during the planning phase.
691703 cache: Cache of node name to fingerprints.
692704 table_naming_convention: Convention to follow when generating the physical table name
705+ virtual_environment_mode: Mode for handling virtual environments
693706
694707 Returns:
695708 The newly created snapshot.
@@ -722,6 +735,7 @@ def from_node(
722735 ttl = ttl ,
723736 version = version ,
724737 table_naming_convention = table_naming_convention ,
738+ virtual_environment_mode = virtual_environment_mode ,
725739 )
726740
727741 def __eq__ (self , other : t .Any ) -> bool :
@@ -876,16 +890,19 @@ def merge_intervals(self, other: t.Union[Snapshot, SnapshotIntervals]) -> None:
876890 Args:
877891 other: The target snapshot to inherit intervals from.
878892 """
879- effective_from_ts = self .normalized_effective_from_ts or 0
880- apply_effective_from = effective_from_ts > 0 and self .identifier != other .identifier
881-
882- for start , end in other .intervals :
883- # If the effective_from is set, then intervals that come after it must come from
884- # the current snapshost.
885- if apply_effective_from and start < effective_from_ts :
886- end = min (end , effective_from_ts )
887- if not apply_effective_from or end <= effective_from_ts :
888- self .add_interval (start , end )
893+ if self .is_no_rebuild or self .virtual_environment_mode .is_full or not self .is_paused :
894+ # If the virtual environment mode is not full we can only merge prod intervals if this snapshot
895+ # is currently promoted in production or if it's forward-only / metadata / indirect non-breaking.
896+ # Otherwise, we want to ignore any existing intervals and backfill this snapshot from scratch.
897+ effective_from_ts = self .normalized_effective_from_ts or 0
898+ apply_effective_from = effective_from_ts > 0 and self .identifier != other .identifier
899+ for start , end in other .intervals :
900+ # If the effective_from is set, then intervals that come after it must come from
901+ # the current snapshost.
902+ if apply_effective_from and start < effective_from_ts :
903+ end = min (end , effective_from_ts )
904+ if not apply_effective_from or end <= effective_from_ts :
905+ self .add_interval (start , end )
889906
890907 if self .dev_version == other .dev_version :
891908 # Merge dev intervals if the dev versions match which would mean
@@ -1035,7 +1052,10 @@ def categorize_as(self, category: SnapshotChangeCategory, forward_only: bool = F
10351052 SnapshotChangeCategory .INDIRECT_NON_BREAKING ,
10361053 SnapshotChangeCategory .METADATA ,
10371054 )
1038- if self .is_model and self .model .physical_version :
1055+ if self .is_model and not self .virtual_environment_mode .is_full :
1056+ # Hardcode the version if the virtual environment is not fully enabled.
1057+ self .version = "novde"
1058+ elif self .is_model and self .model .physical_version :
10391059 # If the model has a pinned version then use that.
10401060 self .version = self .model .physical_version
10411061 elif is_no_rebuild and self .previous_version :
@@ -1239,6 +1259,7 @@ def table_info(self) -> SnapshotTableInfo:
12391259 model_gateway = self .model_gateway ,
12401260 table_naming_convention = self .table_naming_convention , # type: ignore
12411261 forward_only = self .forward_only ,
1262+ virtual_environment_mode = self .virtual_environment_mode ,
12421263 )
12431264
12441265 @property
@@ -1252,6 +1273,7 @@ def data_version(self) -> SnapshotDataVersion:
12521273 physical_schema = self .physical_schema ,
12531274 dev_table_suffix = self .dev_table_suffix ,
12541275 table_naming_convention = self .table_naming_convention ,
1276+ virtual_environment_mode = self .virtual_environment_mode ,
12551277 )
12561278
12571279 @property
@@ -1535,14 +1557,20 @@ def create(
15351557 for node in dag :
15361558 if node not in snapshots :
15371559 continue
1538- # Make sure that the node is deployable according to all its parents
1539- this_deployable = all (
1540- children_deployability_mapping [p_id ]
1541- for p_id in snapshots [node ].parents
1542- if p_id in children_deployability_mapping
1543- )
1560+ snapshot = snapshots [node ]
1561+
1562+ if not snapshot .virtual_environment_mode .is_full :
1563+ # If the virtual environment is not fully enabled, then the snapshot can never be deployable
1564+ this_deployable = False
1565+ else :
1566+ # Make sure that the node is deployable according to all its parents
1567+ this_deployable = all (
1568+ children_deployability_mapping [p_id ]
1569+ for p_id in snapshots [node ].parents
1570+ if p_id in children_deployability_mapping
1571+ )
1572+
15441573 if this_deployable :
1545- snapshot = snapshots [node ]
15461574 is_forward_only_model = (
15471575 snapshot .is_model and snapshot .model .forward_only and not snapshot .is_metadata
15481576 )
0 commit comments