sqlmesh 0.192.3.dev1__py3-none-any.whl → 0.192.3.dev2__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.
- sqlmesh/_version.py +2 -2
- sqlmesh/core/plan/__init__.py +0 -1
- sqlmesh/core/plan/evaluator.py +193 -362
- sqlmesh/core/plan/explainer.py +28 -12
- sqlmesh/core/plan/stages.py +328 -43
- sqlmesh/core/scheduler.py +17 -16
- sqlmesh/core/state_sync/base.py +11 -11
- sqlmesh/utils/__init__.py +6 -0
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/METADATA +1 -1
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/RECORD +14 -14
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/WHEEL +0 -0
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.192.3.dev1.dist-info → sqlmesh-0.192.3.dev2.dist-info}/top_level.txt +0 -0
sqlmesh/core/plan/evaluator.py
CHANGED
|
@@ -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
|
|
39
|
-
from sqlmesh.core.state_sync import StateSync
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
208
|
-
self,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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=
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
|
342
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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=
|
|
329
|
+
snapshots=stage.all_snapshots,
|
|
397
330
|
)
|
|
398
|
-
if
|
|
331
|
+
if stage.demoted_environment_naming_info:
|
|
399
332
|
self._demote_snapshots(
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
476
|
+
if snapshots_intervals:
|
|
477
|
+
self.state_sync.add_snapshots_intervals(snapshots_intervals)
|