sqlmesh 0.192.3.dev1__py3-none-any.whl → 0.192.3.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ from sqlmesh.core.console import Console, get_console
23
23
  from sqlmesh.core.environment import EnvironmentNamingInfo, execute_environment_statements
24
24
  from sqlmesh.core.macros import RuntimeStage
25
25
  from sqlmesh.core.snapshot.definition import Interval, to_view_mapping
26
+ from sqlmesh.core.plan import stages
26
27
  from sqlmesh.core.plan.definition import EvaluatablePlan
27
28
  from sqlmesh.core.scheduler import Scheduler
28
29
  from sqlmesh.core.snapshot import (
@@ -35,11 +36,10 @@ from sqlmesh.core.snapshot import (
35
36
  SnapshotTableInfo,
36
37
  SnapshotCreationFailedError,
37
38
  )
38
- from sqlmesh.utils import CompletionStatus
39
- from sqlmesh.core.state_sync import StateSync, StateReader
40
- from sqlmesh.core.state_sync.base import PromotionResult
39
+ from sqlmesh.utils import to_snake_case
40
+ from sqlmesh.core.state_sync import StateSync
41
41
  from sqlmesh.utils.concurrency import NodeExecutionFailedError
42
- from sqlmesh.utils.errors import PlanError
42
+ from sqlmesh.utils.errors import PlanError, SQLMeshError
43
43
  from sqlmesh.utils.dag import DAG
44
44
  from sqlmesh.utils.date import now
45
45
 
@@ -77,12 +77,14 @@ class BuiltInPlanEvaluator(PlanEvaluator):
77
77
  self.create_scheduler = create_scheduler
78
78
  self.default_catalog = default_catalog
79
79
  self.console = console or get_console()
80
+ self._circuit_breaker: t.Optional[t.Callable[[], bool]] = None
80
81
 
81
82
  def evaluate(
82
83
  self,
83
84
  plan: EvaluatablePlan,
84
85
  circuit_breaker: t.Optional[t.Callable[[], bool]] = None,
85
86
  ) -> None:
87
+ self._circuit_breaker = circuit_breaker
86
88
  self.console.start_plan_evaluation(plan)
87
89
  analytics.collector.on_plan_apply_start(
88
90
  plan=plan,
@@ -92,110 +94,8 @@ class BuiltInPlanEvaluator(PlanEvaluator):
92
94
  )
93
95
 
94
96
  try:
95
- new_snapshots = {s.snapshot_id: s for s in plan.new_snapshots}
96
- stored_snapshots = self.state_sync.get_snapshots(plan.environment.snapshots)
97
- snapshots = {**new_snapshots, **stored_snapshots}
98
- snapshots_by_name = {s.name: s for s in snapshots.values()}
99
-
100
- all_names = {name for name in snapshots_by_name if plan.is_selected_for_backfill(name)}
101
-
102
- deployability_index_for_evaluation = DeployabilityIndex.create(
103
- snapshots, start=plan.start
104
- )
105
- deployability_index_for_creation = deployability_index_for_evaluation
106
- if plan.is_dev:
107
- before_promote_snapshots = all_names
108
- after_promote_snapshots = set()
109
- else:
110
- before_promote_snapshots = {
111
- s.name
112
- for s in snapshots.values()
113
- if deployability_index_for_evaluation.is_representative(s)
114
- and plan.is_selected_for_backfill(s.name)
115
- }
116
- after_promote_snapshots = all_names - before_promote_snapshots
117
- deployability_index_for_evaluation = DeployabilityIndex.all_deployable()
118
-
119
- # Step 1: Run before_all environment statements before doing anything else
120
- execute_environment_statements(
121
- adapter=self.snapshot_evaluator.adapter,
122
- environment_statements=plan.environment_statements or [],
123
- runtime_stage=RuntimeStage.BEFORE_ALL,
124
- environment_naming_info=plan.environment.naming_info,
125
- default_catalog=self.default_catalog,
126
- snapshots=snapshots_by_name,
127
- start=plan.start,
128
- end=plan.end,
129
- execution_time=plan.execution_time,
130
- )
131
-
132
- # Step 2:Store new snapshot records in the state and create physical tables for them
133
- push_completion_status = self._push(plan, snapshots, deployability_index_for_creation)
134
- if push_completion_status.is_nothing_to_do:
135
- self.console.log_success(
136
- "" if plan.restatements else "\nSKIP: No physical layer updates to perform"
137
- )
138
-
139
- # Step 3: Update the intervals for the new forward-only snapshots
140
- update_intervals_for_new_snapshots(plan.new_snapshots, self.state_sync)
141
-
142
- # Step 4: Run audits without evaluations for snapshots that capture audit metadata changes
143
- self._run_audits_for_metadata_snapshots(plan, new_snapshots)
144
-
145
- # Step 5: Remove intervals for snapshots that need to be restated
146
- self._restate(plan, snapshots_by_name)
147
-
148
- # Step 6: Backfill missing intervals for snapshots that can be backfilled before updating
149
- # the schema of production tables
150
- first_bf_completion_status = self._backfill(
151
- plan,
152
- snapshots_by_name,
153
- before_promote_snapshots,
154
- deployability_index_for_evaluation,
155
- circuit_breaker=circuit_breaker,
156
- )
157
-
158
- # Step 7: Update the target environment record in the state and migrate table schemas for forward-only
159
- # snapshots if deploying to production
160
- promotion_result = self._promote(
161
- plan, snapshots, before_promote_snapshots, deployability_index_for_creation
162
- )
163
-
164
- # Step 8: Backfill missing intervals for snapshots that can be backfilled only after updating
165
- # the schema of production tables
166
- second_bf_completion_status = self._backfill(
167
- plan,
168
- snapshots_by_name,
169
- after_promote_snapshots,
170
- deployability_index_for_evaluation,
171
- circuit_breaker=circuit_breaker,
172
- )
173
-
174
- if (
175
- first_bf_completion_status.is_nothing_to_do
176
- and second_bf_completion_status.is_nothing_to_do
177
- ):
178
- self.console.log_success("SKIP: No model batches to execute")
179
-
180
- # Step 9: Update environment views to point at new physical tables and finalize the environment
181
- # record in the state
182
- self._update_views(
183
- plan, snapshots, promotion_result, deployability_index_for_evaluation
184
- )
185
-
186
- # Step 10: Run after_all environment statements
187
- execute_environment_statements(
188
- adapter=self.snapshot_evaluator.adapter,
189
- environment_statements=plan.environment_statements or [],
190
- runtime_stage=RuntimeStage.AFTER_ALL,
191
- environment_naming_info=plan.environment.naming_info,
192
- default_catalog=self.default_catalog,
193
- snapshots=snapshots_by_name,
194
- start=plan.start,
195
- end=plan.end,
196
- execution_time=plan.execution_time,
197
- )
198
-
97
+ plan_stages = stages.build_plan_stages(plan, self.state_sync, self.default_catalog)
98
+ self._evaluate_stages(plan_stages, plan)
199
99
  except Exception as e:
200
100
  analytics.collector.on_plan_apply_end(plan_id=plan.plan_id, error=e)
201
101
  raise
@@ -204,101 +104,80 @@ class BuiltInPlanEvaluator(PlanEvaluator):
204
104
  finally:
205
105
  self.console.stop_plan_evaluation()
206
106
 
207
- def _backfill(
208
- self,
209
- plan: EvaluatablePlan,
210
- snapshots_by_name: t.Dict[str, Snapshot],
211
- selected_snapshots: t.Set[str],
212
- deployability_index: DeployabilityIndex,
213
- circuit_breaker: t.Optional[t.Callable[[], bool]] = None,
214
- ) -> CompletionStatus:
215
- """Backfill missing intervals for snapshots that are part of the given plan.
216
-
217
- Args:
218
- plan: The plan to source snapshots from.
219
- selected_snapshots: The snapshots to backfill.
220
- """
221
- if plan.empty_backfill:
222
- intervals_to_add = []
223
- for snapshot in snapshots_by_name.values():
224
- if not snapshot.evaluatable or not plan.is_selected_for_backfill(snapshot.name):
225
- # Skip snapshots that are not evaluatable or not selected for backfill.
226
- continue
227
- intervals = [
228
- snapshot.inclusive_exclusive(plan.start, plan.end, strict=False, expand=False)
229
- ]
230
- is_deployable = deployability_index.is_deployable(snapshot)
231
- intervals_to_add.append(
232
- SnapshotIntervals(
233
- name=snapshot.name,
234
- identifier=snapshot.identifier,
235
- version=snapshot.version,
236
- dev_version=snapshot.dev_version,
237
- intervals=intervals if is_deployable else [],
238
- dev_intervals=intervals if not is_deployable else [],
239
- )
240
- )
241
- self.state_sync.add_snapshots_intervals(intervals_to_add)
242
- return CompletionStatus.NOTHING_TO_DO
243
-
244
- if not plan.requires_backfill or not selected_snapshots:
245
- return CompletionStatus.NOTHING_TO_DO
246
-
247
- scheduler = self.create_scheduler(snapshots_by_name.values())
248
- completion_status = scheduler.run(
249
- plan.environment,
250
- plan.start,
251
- plan.end,
107
+ def _evaluate_stages(
108
+ self, plan_stages: t.List[stages.PlanStage], plan: EvaluatablePlan
109
+ ) -> None:
110
+ for stage in plan_stages:
111
+ stage_name = stage.__class__.__name__
112
+ handler_name = f"visit_{to_snake_case(stage_name)}"
113
+ if not hasattr(self, handler_name):
114
+ raise SQLMeshError(f"Unexpected plan stage: {stage_name}")
115
+ logger.info("Evaluating plan stage %s", stage_name)
116
+ handler = getattr(self, handler_name)
117
+ handler(stage, plan)
118
+
119
+ def visit_before_all_stage(self, stage: stages.BeforeAllStage, plan: EvaluatablePlan) -> None:
120
+ execute_environment_statements(
121
+ adapter=self.snapshot_evaluator.adapter,
122
+ environment_statements=stage.statements,
123
+ runtime_stage=RuntimeStage.BEFORE_ALL,
124
+ environment_naming_info=plan.environment.naming_info,
125
+ default_catalog=self.default_catalog,
126
+ snapshots=stage.all_snapshots,
127
+ start=plan.start,
128
+ end=plan.end,
252
129
  execution_time=plan.execution_time,
253
- restatements={
254
- snapshots_by_name[name].snapshot_id: interval
255
- for name, interval in plan.restatements.items()
256
- },
257
- selected_snapshots=selected_snapshots,
258
- deployability_index=deployability_index,
259
- circuit_breaker=circuit_breaker,
260
- end_bounded=plan.end_bounded,
261
- interval_end_per_model=plan.interval_end_per_model,
262
130
  )
263
- if completion_status.is_failure:
264
- raise PlanError("Plan application failed.")
265
131
 
266
- return completion_status
267
-
268
- def _push(
269
- self,
270
- plan: EvaluatablePlan,
271
- snapshots: t.Dict[SnapshotId, Snapshot],
272
- deployability_index: t.Optional[DeployabilityIndex] = None,
273
- ) -> CompletionStatus:
274
- """Push the snapshots to the state sync.
275
-
276
- As a part of plan pushing, snapshot tables are created.
132
+ def visit_after_all_stage(self, stage: stages.AfterAllStage, plan: EvaluatablePlan) -> None:
133
+ execute_environment_statements(
134
+ adapter=self.snapshot_evaluator.adapter,
135
+ environment_statements=stage.statements,
136
+ runtime_stage=RuntimeStage.AFTER_ALL,
137
+ environment_naming_info=plan.environment.naming_info,
138
+ default_catalog=self.default_catalog,
139
+ snapshots=stage.all_snapshots,
140
+ start=plan.start,
141
+ end=plan.end,
142
+ execution_time=plan.execution_time,
143
+ )
277
144
 
278
- Args:
279
- plan: The plan to source snapshots from.
280
- deployability_index: Indicates which snapshots are deployable in the context of this creation.
281
- """
282
- self.state_sync.push_snapshots(plan.new_snapshots)
145
+ def visit_create_snapshot_records_stage(
146
+ self, stage: stages.CreateSnapshotRecordsStage, plan: EvaluatablePlan
147
+ ) -> None:
148
+ self.state_sync.push_snapshots(stage.snapshots)
283
149
  analytics.collector.on_snapshots_created(
284
- new_snapshots=plan.new_snapshots, plan_id=plan.plan_id
150
+ new_snapshots=stage.snapshots, plan_id=plan.plan_id
285
151
  )
152
+ # Update the intervals for the new forward-only snapshots
153
+ self._update_intervals_for_new_snapshots(stage.snapshots)
154
+
155
+ def visit_physical_layer_update_stage(
156
+ self, stage: stages.PhysicalLayerUpdateStage, plan: EvaluatablePlan
157
+ ) -> None:
158
+ skip_message = "" if plan.restatements else "\nSKIP: No physical layer updates to perform"
286
159
 
287
- snapshots_to_create = get_snapshots_to_create(plan, snapshots)
160
+ snapshots_to_create = stage.snapshots
161
+ if not snapshots_to_create:
162
+ self.console.log_success(skip_message)
163
+ return
288
164
 
289
165
  completion_status = None
290
166
  progress_stopped = False
291
167
  try:
292
168
  completion_status = self.snapshot_evaluator.create(
293
169
  snapshots_to_create,
294
- snapshots,
170
+ stage.all_snapshots,
295
171
  allow_destructive_snapshots=plan.allow_destructive_models,
296
- deployability_index=deployability_index,
172
+ deployability_index=stage.deployability_index,
297
173
  on_start=lambda x: self.console.start_creation_progress(
298
174
  x, plan.environment, self.default_catalog
299
175
  ),
300
176
  on_complete=self.console.update_creation_progress,
301
177
  )
178
+ if completion_status.is_nothing_to_do:
179
+ self.console.log_success(skip_message)
180
+ return
302
181
  except SnapshotCreationFailedError as ex:
303
182
  self.console.stop_creation_progress(success=False)
304
183
  progress_stopped = True
@@ -316,71 +195,125 @@ class BuiltInPlanEvaluator(PlanEvaluator):
316
195
  success=completion_status is not None and completion_status.is_success
317
196
  )
318
197
 
319
- return completion_status
198
+ def visit_backfill_stage(self, stage: stages.BackfillStage, plan: EvaluatablePlan) -> None:
199
+ if plan.empty_backfill:
200
+ intervals_to_add = []
201
+ for snapshot in stage.all_snapshots.values():
202
+ if not snapshot.evaluatable or not plan.is_selected_for_backfill(snapshot.name):
203
+ # Skip snapshots that are not evaluatable or not selected for backfill.
204
+ continue
205
+ intervals = [
206
+ snapshot.inclusive_exclusive(plan.start, plan.end, strict=False, expand=False)
207
+ ]
208
+ is_deployable = stage.deployability_index.is_deployable(snapshot)
209
+ intervals_to_add.append(
210
+ SnapshotIntervals(
211
+ name=snapshot.name,
212
+ identifier=snapshot.identifier,
213
+ version=snapshot.version,
214
+ dev_version=snapshot.dev_version,
215
+ intervals=intervals if is_deployable else [],
216
+ dev_intervals=intervals if not is_deployable else [],
217
+ )
218
+ )
219
+ self.state_sync.add_snapshots_intervals(intervals_to_add)
220
+ self.console.log_success("SKIP: No model batches to execute")
221
+ return
320
222
 
321
- def _promote(
322
- self,
323
- plan: EvaluatablePlan,
324
- snapshots: t.Dict[SnapshotId, Snapshot],
325
- no_gaps_snapshot_names: t.Optional[t.Set[str]] = None,
326
- deployability_index: t.Optional[DeployabilityIndex] = None,
327
- ) -> PromotionResult:
328
- """Promote a plan.
223
+ if not stage.snapshot_to_intervals:
224
+ self.console.log_success("SKIP: No model batches to execute")
225
+ return
329
226
 
330
- Args:
331
- plan: The plan to promote.
332
- no_gaps_snapshot_names: The names of snapshots to check for gaps if the no gaps check is enabled in the plan.
333
- If not provided, all snapshots are checked.
334
- """
335
- promotion_result = self.state_sync.promote(
227
+ scheduler = self.create_scheduler(stage.all_snapshots.values())
228
+ errors, _ = scheduler.run_merged_intervals(
229
+ merged_intervals=stage.snapshot_to_intervals,
230
+ deployability_index=stage.deployability_index,
231
+ environment_naming_info=plan.environment.naming_info,
232
+ execution_time=plan.execution_time,
233
+ circuit_breaker=self._circuit_breaker,
234
+ start=plan.start,
235
+ end=plan.end,
236
+ )
237
+ if errors:
238
+ raise PlanError("Plan application failed.")
239
+
240
+ def visit_audit_only_run_stage(
241
+ self, stage: stages.AuditOnlyRunStage, plan: EvaluatablePlan
242
+ ) -> None:
243
+ audit_snapshots = stage.snapshots
244
+ if not audit_snapshots:
245
+ return
246
+
247
+ # If there are any snapshots to be audited, we'll reuse the scheduler's internals to audit them
248
+ scheduler = self.create_scheduler(audit_snapshots)
249
+ completion_status = scheduler.audit(
336
250
  plan.environment,
337
- no_gaps_snapshot_names=no_gaps_snapshot_names if plan.no_gaps else set(),
338
- environment_statements=plan.environment_statements,
251
+ plan.start,
252
+ plan.end,
253
+ execution_time=plan.execution_time,
254
+ end_bounded=plan.end_bounded,
255
+ interval_end_per_model=plan.interval_end_per_model,
339
256
  )
340
257
 
341
- if not plan.is_dev:
342
- try:
343
- self.snapshot_evaluator.migrate(
344
- [s for s in snapshots.values() if s.is_paused],
345
- snapshots,
346
- allow_destructive_snapshots=plan.allow_destructive_models,
347
- deployability_index=deployability_index,
348
- )
349
- except NodeExecutionFailedError as ex:
350
- raise PlanError(str(ex.__cause__) if ex.__cause__ else str(ex))
258
+ if completion_status.is_failure:
259
+ raise PlanError("Plan application failed.")
351
260
 
352
- if not plan.ensure_finalized_snapshots:
353
- # Only unpause at this point if we don't have to use the finalized snapshots
354
- # for subsequent plan applications. Otherwise, unpause right before finalizing
355
- # the environment.
356
- self.state_sync.unpause_snapshots(promotion_result.added, plan.end)
261
+ def visit_restatement_stage(
262
+ self, stage: stages.RestatementStage, plan: EvaluatablePlan
263
+ ) -> None:
264
+ snapshot_intervals_to_restate = {(s, i) for s, i in stage.snapshot_intervals.items()}
357
265
 
358
- return promotion_result
266
+ # Restating intervals on prod plans should mean that the intervals are cleared across
267
+ # all environments, not just the version currently in prod
268
+ # This ensures that work done in dev environments can still be promoted to prod
269
+ # by forcing dev environments to re-run intervals that changed in prod
270
+ #
271
+ # Without this rule, its possible that promoting a dev table to prod will introduce old data to prod
272
+ snapshot_intervals_to_restate.update(
273
+ self._restatement_intervals_across_all_environments(
274
+ prod_restatements=plan.restatements,
275
+ disable_restatement_models=plan.disabled_restatement_models,
276
+ loaded_snapshots={s.snapshot_id: s for s in stage.all_snapshots.values()},
277
+ )
278
+ )
359
279
 
360
- def _update_views(
361
- self,
362
- plan: EvaluatablePlan,
363
- snapshots: t.Dict[SnapshotId, Snapshot],
364
- promotion_result: PromotionResult,
365
- deployability_index: t.Optional[DeployabilityIndex] = None,
280
+ self.state_sync.remove_intervals(
281
+ snapshot_intervals=list(snapshot_intervals_to_restate),
282
+ remove_shared_versions=plan.is_prod,
283
+ )
284
+
285
+ def visit_environment_record_update_stage(
286
+ self, stage: stages.EnvironmentRecordUpdateStage, plan: EvaluatablePlan
366
287
  ) -> None:
367
- """Update environment views.
288
+ self.state_sync.promote(
289
+ plan.environment,
290
+ no_gaps_snapshot_names=stage.no_gaps_snapshot_names if plan.no_gaps else set(),
291
+ environment_statements=plan.environment_statements,
292
+ )
368
293
 
369
- Args:
370
- plan: The plan to promote.
371
- promotion_result: The result of the promotion.
372
- deployability_index: Indicates which snapshots are deployable in the context of this promotion.
373
- """
374
- if not plan.is_dev and plan.ensure_finalized_snapshots:
375
- # Unpause right before finalizing the environment in case when
376
- # we need to use the finalized snapshots for subsequent plan applications.
377
- # Otherwise, unpause right after updatig the environment record.
378
- self.state_sync.unpause_snapshots(promotion_result.added, plan.end)
294
+ def visit_migrate_schemas_stage(
295
+ self, stage: stages.MigrateSchemasStage, plan: EvaluatablePlan
296
+ ) -> None:
297
+ try:
298
+ self.snapshot_evaluator.migrate(
299
+ stage.snapshots,
300
+ stage.all_snapshots,
301
+ allow_destructive_snapshots=plan.allow_destructive_models,
302
+ deployability_index=stage.deployability_index,
303
+ )
304
+ except NodeExecutionFailedError as ex:
305
+ raise PlanError(str(ex.__cause__) if ex.__cause__ else str(ex))
306
+
307
+ def visit_unpause_stage(self, stage: stages.UnpauseStage, plan: EvaluatablePlan) -> None:
308
+ self.state_sync.unpause_snapshots(stage.promoted_snapshots, plan.end)
379
309
 
310
+ def visit_virtual_layer_update_stage(
311
+ self, stage: stages.VirtualLayerUpdateStage, plan: EvaluatablePlan
312
+ ) -> None:
380
313
  environment = plan.environment
381
314
 
382
315
  self.console.start_promotion_progress(
383
- promotion_result.added + promotion_result.removed,
316
+ list(stage.promoted_snapshots) + list(stage.demoted_snapshots),
384
317
  environment.naming_info,
385
318
  self.default_catalog,
386
319
  )
@@ -389,25 +322,28 @@ class BuiltInPlanEvaluator(PlanEvaluator):
389
322
  try:
390
323
  self._promote_snapshots(
391
324
  plan,
392
- [snapshots[s.snapshot_id] for s in promotion_result.added],
325
+ [stage.all_snapshots[s.snapshot_id] for s in stage.promoted_snapshots],
393
326
  environment.naming_info,
394
- deployability_index=deployability_index,
327
+ deployability_index=stage.deployability_index,
395
328
  on_complete=lambda s: self.console.update_promotion_progress(s, True),
396
- snapshots=snapshots,
329
+ snapshots=stage.all_snapshots,
397
330
  )
398
- if promotion_result.removed_environment_naming_info:
331
+ if stage.demoted_environment_naming_info:
399
332
  self._demote_snapshots(
400
- plan,
401
- promotion_result.removed,
402
- promotion_result.removed_environment_naming_info,
333
+ stage.demoted_snapshots,
334
+ stage.demoted_environment_naming_info,
403
335
  on_complete=lambda s: self.console.update_promotion_progress(s, False),
404
336
  )
405
337
 
406
- self.state_sync.finalize(environment)
407
338
  completed = True
408
339
  finally:
409
340
  self.console.stop_promotion_progress(success=completed)
410
341
 
342
+ def visit_finalize_environment_stage(
343
+ self, stage: stages.FinalizeEnvironmentStage, plan: EvaluatablePlan
344
+ ) -> None:
345
+ self.state_sync.finalize(plan.environment)
346
+
411
347
  def _promote_snapshots(
412
348
  self,
413
349
  plan: EvaluatablePlan,
@@ -436,7 +372,6 @@ class BuiltInPlanEvaluator(PlanEvaluator):
436
372
 
437
373
  def _demote_snapshots(
438
374
  self,
439
- plan: EvaluatablePlan,
440
375
  target_snapshots: t.Iterable[SnapshotTableInfo],
441
376
  environment_naming_info: EnvironmentNamingInfo,
442
377
  on_complete: t.Optional[t.Callable[[SnapshotInfoLike], None]] = None,
@@ -445,34 +380,6 @@ class BuiltInPlanEvaluator(PlanEvaluator):
445
380
  target_snapshots, environment_naming_info, on_complete=on_complete
446
381
  )
447
382
 
448
- def _restate(self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]) -> None:
449
- if not plan.restatements or plan.is_dev:
450
- return
451
-
452
- snapshot_intervals_to_restate = {
453
- (snapshots_by_name[name].table_info, intervals)
454
- for name, intervals in plan.restatements.items()
455
- }
456
-
457
- # Restating intervals on prod plans should mean that the intervals are cleared across
458
- # all environments, not just the version currently in prod
459
- # This ensures that work done in dev environments can still be promoted to prod
460
- # by forcing dev environments to re-run intervals that changed in prod
461
- #
462
- # Without this rule, its possible that promoting a dev table to prod will introduce old data to prod
463
- snapshot_intervals_to_restate.update(
464
- self._restatement_intervals_across_all_environments(
465
- prod_restatements=plan.restatements,
466
- disable_restatement_models=plan.disabled_restatement_models,
467
- loaded_snapshots={s.snapshot_id: s for s in snapshots_by_name.values()},
468
- )
469
- )
470
-
471
- self.state_sync.remove_intervals(
472
- snapshot_intervals=list(snapshot_intervals_to_restate),
473
- remove_shared_versions=plan.is_prod,
474
- )
475
-
476
383
  def _restatement_intervals_across_all_environments(
477
384
  self,
478
385
  prod_restatements: t.Dict[str, Interval],
@@ -552,95 +459,19 @@ class BuiltInPlanEvaluator(PlanEvaluator):
552
459
 
553
460
  return set(snapshots_to_restate.values())
554
461
 
555
- def _run_audits_for_metadata_snapshots(
556
- self,
557
- plan: EvaluatablePlan,
558
- new_snapshots: t.Dict[SnapshotId, Snapshot],
559
- ) -> None:
560
- audit_snapshots = get_audit_only_snapshots(new_snapshots, self.state_sync)
561
- if not audit_snapshots:
562
- return
563
-
564
- # If there are any snapshots to be audited, we'll reuse the scheduler's internals to audit them
565
- scheduler = self.create_scheduler(audit_snapshots.values())
566
- completion_status = scheduler.audit(
567
- plan.environment,
568
- plan.start,
569
- plan.end,
570
- execution_time=plan.execution_time,
571
- end_bounded=plan.end_bounded,
572
- interval_end_per_model=plan.interval_end_per_model,
573
- )
574
-
575
- if completion_status.is_failure:
576
- raise PlanError("Plan application failed.")
577
-
578
-
579
- def update_intervals_for_new_snapshots(
580
- snapshots: t.Collection[Snapshot], state_sync: StateSync
581
- ) -> None:
582
- snapshots_intervals: t.List[SnapshotIntervals] = []
583
- for snapshot in state_sync.refresh_snapshot_intervals(snapshots):
584
- if snapshot.is_forward_only:
585
- snapshot.dev_intervals = snapshot.intervals.copy()
586
- snapshots_intervals.append(
587
- SnapshotIntervals(
588
- name=snapshot.name,
589
- identifier=snapshot.identifier,
590
- version=snapshot.version,
591
- dev_version=snapshot.dev_version,
592
- dev_intervals=snapshot.dev_intervals,
462
+ def _update_intervals_for_new_snapshots(self, snapshots: t.Collection[Snapshot]) -> None:
463
+ snapshots_intervals: t.List[SnapshotIntervals] = []
464
+ for snapshot in snapshots:
465
+ if snapshot.is_forward_only:
466
+ snapshots_intervals.append(
467
+ SnapshotIntervals(
468
+ name=snapshot.name,
469
+ identifier=snapshot.identifier,
470
+ version=snapshot.version,
471
+ dev_version=snapshot.dev_version,
472
+ dev_intervals=snapshot.dev_intervals,
473
+ )
593
474
  )
594
- )
595
-
596
- if snapshots_intervals:
597
- state_sync.add_snapshots_intervals(snapshots_intervals)
598
-
599
-
600
- def get_audit_only_snapshots(
601
- new_snapshots: t.Dict[SnapshotId, Snapshot], state_reader: StateReader
602
- ) -> t.Dict[SnapshotId, Snapshot]:
603
- metadata_snapshots = []
604
- for snapshot in new_snapshots.values():
605
- if not snapshot.is_metadata or not snapshot.is_model or not snapshot.evaluatable:
606
- continue
607
-
608
- metadata_snapshots.append(snapshot)
609
-
610
- # Bulk load all the previous snapshots
611
- previous_snapshots = state_reader.get_snapshots(
612
- [s.previous_version.snapshot_id(s.name) for s in metadata_snapshots if s.previous_version]
613
- ).values()
614
-
615
- # Check if any of the snapshots have modifications to the audits field by comparing the hashes
616
- audit_snapshots = {}
617
- for snapshot, previous_snapshot in zip(metadata_snapshots, previous_snapshots):
618
- new_audits_hash = snapshot.model.audit_metadata_hash()
619
- previous_audit_hash = previous_snapshot.model.audit_metadata_hash()
620
-
621
- if snapshot.model.audits and previous_audit_hash != new_audits_hash:
622
- audit_snapshots[snapshot.snapshot_id] = snapshot
623
-
624
- return audit_snapshots
625
-
626
-
627
- def get_snapshots_to_create(
628
- plan: EvaluatablePlan, snapshots: t.Dict[SnapshotId, Snapshot]
629
- ) -> t.List[Snapshot]:
630
- promoted_snapshot_ids = (
631
- set(plan.environment.promoted_snapshot_ids)
632
- if plan.environment.promoted_snapshot_ids is not None
633
- else None
634
- )
635
-
636
- def _should_create(s: Snapshot) -> bool:
637
- if not s.is_model or s.is_symbolic:
638
- return False
639
- # Only create tables for snapshots that we're planning to promote or that were selected for backfill
640
- return (
641
- plan.is_selected_for_backfill(s.name)
642
- or promoted_snapshot_ids is None
643
- or s.snapshot_id in promoted_snapshot_ids
644
- )
645
475
 
646
- return [s for s in snapshots.values() if _should_create(s)]
476
+ if snapshots_intervals:
477
+ self.state_sync.add_snapshots_intervals(snapshots_intervals)