sqlmesh 0.192.2.dev6__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.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/METADATA +1 -1
- {sqlmesh-0.192.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/RECORD +14 -14
- {sqlmesh-0.192.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/WHEEL +0 -0
- {sqlmesh-0.192.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.192.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.192.2.dev6.dist-info → sqlmesh-0.192.3.dev2.dist-info}/top_level.txt +0 -0
sqlmesh/core/plan/explainer.py
CHANGED
|
@@ -17,7 +17,7 @@ from sqlmesh.core.state_sync import StateReader
|
|
|
17
17
|
from sqlmesh.core.snapshot.definition import (
|
|
18
18
|
SnapshotInfoMixin,
|
|
19
19
|
)
|
|
20
|
-
from sqlmesh.utils import Verbosity, rich as srich
|
|
20
|
+
from sqlmesh.utils import Verbosity, rich as srich, to_snake_case
|
|
21
21
|
from sqlmesh.utils.date import to_ts
|
|
22
22
|
from sqlmesh.utils.errors import SQLMeshError
|
|
23
23
|
|
|
@@ -73,7 +73,7 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
73
73
|
def explain(self, stages: t.List[stages.PlanStage]) -> None:
|
|
74
74
|
tree = Tree("[bold]Explained plan[/bold]")
|
|
75
75
|
for stage in stages:
|
|
76
|
-
handler_name = f"visit_{
|
|
76
|
+
handler_name = f"visit_{to_snake_case(stage.__class__.__name__)}"
|
|
77
77
|
if not hasattr(self, handler_name):
|
|
78
78
|
logger.error("Unexpected stage: %s", stage.__class__.__name__)
|
|
79
79
|
continue
|
|
@@ -97,6 +97,9 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
97
97
|
"[bold]Validate SQL and create physical layer tables and views if they do not exist[/bold]"
|
|
98
98
|
)
|
|
99
99
|
for snapshot in stage.snapshots:
|
|
100
|
+
if snapshot.snapshot_id not in stage.snapshots_with_missing_intervals:
|
|
101
|
+
continue
|
|
102
|
+
|
|
100
103
|
is_deployable = (
|
|
101
104
|
stage.deployability_index.is_deployable(snapshot)
|
|
102
105
|
if self.environment_naming_info.name != c.PROD
|
|
@@ -114,7 +117,9 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
114
117
|
|
|
115
118
|
if snapshot.is_view:
|
|
116
119
|
create_tree = Tree("Create view if it doesn't exist")
|
|
117
|
-
elif
|
|
120
|
+
elif (
|
|
121
|
+
snapshot.is_forward_only and snapshot.previous_versions and not snapshot.is_managed
|
|
122
|
+
):
|
|
118
123
|
prod_table = snapshot.table_name(True)
|
|
119
124
|
create_tree = Tree(
|
|
120
125
|
f"Clone {prod_table} into {table_name} and then update its schema if it doesn't exist"
|
|
@@ -224,7 +229,7 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
224
229
|
"[bold]Delete views in the virtual layer for models that were removed[/bold]"
|
|
225
230
|
)
|
|
226
231
|
for snapshot in stage.demoted_snapshots:
|
|
227
|
-
display_name = self._display_name(snapshot)
|
|
232
|
+
display_name = self._display_name(snapshot, stage.demoted_environment_naming_info)
|
|
228
233
|
demote_tree.add(display_name)
|
|
229
234
|
|
|
230
235
|
if stage.promoted_snapshots:
|
|
@@ -233,14 +238,31 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
233
238
|
tree.add(self._limit_tree(demote_tree))
|
|
234
239
|
return tree
|
|
235
240
|
|
|
241
|
+
def visit_create_snapshot_records_stage(
|
|
242
|
+
self, stage: stages.CreateSnapshotRecordsStage
|
|
243
|
+
) -> t.Optional[Tree]:
|
|
244
|
+
return None
|
|
245
|
+
|
|
236
246
|
def visit_environment_record_update_stage(
|
|
237
247
|
self, stage: stages.EnvironmentRecordUpdateStage
|
|
238
248
|
) -> t.Optional[Tree]:
|
|
239
249
|
return None
|
|
240
250
|
|
|
241
|
-
def
|
|
251
|
+
def visit_unpause_stage(self, stage: stages.UnpauseStage) -> t.Optional[Tree]:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def visit_finalize_environment_stage(
|
|
255
|
+
self, stage: stages.FinalizeEnvironmentStage
|
|
256
|
+
) -> t.Optional[Tree]:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def _display_name(
|
|
260
|
+
self,
|
|
261
|
+
snapshot: SnapshotInfoMixin,
|
|
262
|
+
environment_naming_info: t.Optional[EnvironmentNamingInfo] = None,
|
|
263
|
+
) -> str:
|
|
242
264
|
return snapshot.display_name(
|
|
243
|
-
self.environment_naming_info,
|
|
265
|
+
environment_naming_info or self.environment_naming_info,
|
|
244
266
|
self.default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None,
|
|
245
267
|
dialect=self.dialect,
|
|
246
268
|
)
|
|
@@ -273,9 +295,3 @@ def _get_explainer_console(
|
|
|
273
295
|
verbosity=console.verbosity,
|
|
274
296
|
console=console.console,
|
|
275
297
|
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _to_snake_case(name: str) -> str:
|
|
279
|
-
return "".join(
|
|
280
|
-
f"_{c.lower()}" if c.isupper() and idx != 0 else c.lower() for idx, c in enumerate(name)
|
|
281
|
-
)
|
sqlmesh/core/plan/stages.py
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from sqlmesh.core.environment import EnvironmentStatements, EnvironmentNamingInfo
|
|
4
5
|
from sqlmesh.core.plan.definition import EvaluatablePlan
|
|
5
|
-
from sqlmesh.core.plan.evaluator import (
|
|
6
|
-
get_audit_only_snapshots,
|
|
7
|
-
get_snapshots_to_create,
|
|
8
|
-
)
|
|
9
6
|
from sqlmesh.core.state_sync import StateReader
|
|
10
7
|
from sqlmesh.core.scheduler import merged_missing_intervals, SnapshotToIntervals
|
|
11
8
|
from sqlmesh.core.snapshot.definition import (
|
|
@@ -19,68 +16,193 @@ from sqlmesh.core.snapshot.definition import (
|
|
|
19
16
|
|
|
20
17
|
@dataclass
|
|
21
18
|
class BeforeAllStage:
|
|
22
|
-
statements
|
|
19
|
+
"""Run environment statements before every other stage.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
statements: Environment statements to run before every other stage.
|
|
23
|
+
all_snapshots: All snapshots in the plan by name.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
statements: t.List[EnvironmentStatements]
|
|
27
|
+
all_snapshots: t.Dict[str, Snapshot]
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
@dataclass
|
|
26
31
|
class AfterAllStage:
|
|
27
|
-
statements
|
|
32
|
+
"""Run environment statements after all other stages.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
statements: Environment statements to run after all other stages.
|
|
36
|
+
all_snapshots: All snapshots in the plan by name.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
statements: t.List[EnvironmentStatements]
|
|
40
|
+
all_snapshots: t.Dict[str, Snapshot]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class CreateSnapshotRecordsStage:
|
|
45
|
+
"""Create new snapshot reecords in the state.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
snapshots: New snapshots to create records for.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
snapshots: t.List[Snapshot]
|
|
28
52
|
|
|
29
53
|
|
|
30
54
|
@dataclass
|
|
31
55
|
class PhysicalLayerUpdateStage:
|
|
56
|
+
"""Update the physical layer by creating physical tables and views for given snapshots.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
snapshots: Snapshots to create physical tables and views for. This collection can be empty in which case
|
|
60
|
+
no physical layer update is needed. This can be useful to report the lack of physical layer updates
|
|
61
|
+
back to the user.
|
|
62
|
+
all_snapshots: All snapshots in the plan by snapshot ID.
|
|
63
|
+
snapshots_with_missing_intervals: Snapshots that have missing intervals.
|
|
64
|
+
deployability_index: Deployability index for this stage.
|
|
65
|
+
"""
|
|
66
|
+
|
|
32
67
|
snapshots: t.List[Snapshot]
|
|
68
|
+
all_snapshots: t.Dict[SnapshotId, Snapshot]
|
|
69
|
+
snapshots_with_missing_intervals: t.Set[SnapshotId]
|
|
33
70
|
deployability_index: DeployabilityIndex
|
|
34
71
|
|
|
35
72
|
|
|
36
73
|
@dataclass
|
|
37
74
|
class AuditOnlyRunStage:
|
|
75
|
+
"""Run audits only for given snapshots.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
snapshots: Snapshots to run audits for.
|
|
79
|
+
"""
|
|
80
|
+
|
|
38
81
|
snapshots: t.List[Snapshot]
|
|
39
82
|
|
|
40
83
|
|
|
41
84
|
@dataclass
|
|
42
85
|
class RestatementStage:
|
|
86
|
+
"""Restate intervals for given snapshots.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
snapshot_intervals: Intervals to restate.
|
|
90
|
+
all_snapshots: All snapshots in the plan by name.
|
|
91
|
+
"""
|
|
92
|
+
|
|
43
93
|
snapshot_intervals: t.Dict[SnapshotTableInfo, Interval]
|
|
94
|
+
all_snapshots: t.Dict[str, Snapshot]
|
|
44
95
|
|
|
45
96
|
|
|
46
97
|
@dataclass
|
|
47
98
|
class BackfillStage:
|
|
99
|
+
"""Backfill given missing intervals.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
snapshot_to_intervals: Intervals to backfill. This collection can be empty in which case no backfill is needed.
|
|
103
|
+
This can be useful to report the lack of backfills back to the user.
|
|
104
|
+
all_snapshots: All snapshots in the plan by name.
|
|
105
|
+
deployability_index: Deployability index for this stage.
|
|
106
|
+
before_promote: Whether this stage is before the promotion stage.
|
|
107
|
+
"""
|
|
108
|
+
|
|
48
109
|
snapshot_to_intervals: SnapshotToIntervals
|
|
110
|
+
all_snapshots: t.Dict[str, Snapshot]
|
|
49
111
|
deployability_index: DeployabilityIndex
|
|
50
112
|
before_promote: bool = True
|
|
51
113
|
|
|
52
114
|
|
|
115
|
+
@dataclass
|
|
116
|
+
class EnvironmentRecordUpdateStage:
|
|
117
|
+
"""Update the environment record in the state.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
no_gaps_snapshot_names: Names of snapshots for which there should be no interval gaps.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
no_gaps_snapshot_names: t.Set[str]
|
|
124
|
+
|
|
125
|
+
|
|
53
126
|
@dataclass
|
|
54
127
|
class MigrateSchemasStage:
|
|
128
|
+
"""Migrate schemas of physical tables for given snapshots.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
snapshots: Snapshots to migrate schemas for.
|
|
132
|
+
all_snapshots: All snapshots in the plan by snapshot ID.
|
|
133
|
+
deployability_index: Deployability index for this stage.
|
|
134
|
+
"""
|
|
135
|
+
|
|
55
136
|
snapshots: t.List[Snapshot]
|
|
137
|
+
all_snapshots: t.Dict[SnapshotId, Snapshot]
|
|
138
|
+
deployability_index: DeployabilityIndex
|
|
56
139
|
|
|
57
140
|
|
|
58
141
|
@dataclass
|
|
59
142
|
class VirtualLayerUpdateStage:
|
|
143
|
+
"""Update the virtual layer by creating and deleting views for given snapshots.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
promoted_snapshots: Snapshots to create views for.
|
|
147
|
+
demoted_snapshots: Snapshots to delete views for.
|
|
148
|
+
demoted_environment_naming_info: Environment naming info of the previous environment record.
|
|
149
|
+
all_snapshots: All snapshots in the plan by snapshot ID.
|
|
150
|
+
deployability_index: Deployability index for this stage.
|
|
151
|
+
"""
|
|
152
|
+
|
|
60
153
|
promoted_snapshots: t.Set[SnapshotTableInfo]
|
|
61
154
|
demoted_snapshots: t.Set[SnapshotTableInfo]
|
|
155
|
+
demoted_environment_naming_info: t.Optional[EnvironmentNamingInfo]
|
|
156
|
+
all_snapshots: t.Dict[SnapshotId, Snapshot]
|
|
62
157
|
deployability_index: DeployabilityIndex
|
|
63
158
|
|
|
64
159
|
|
|
65
160
|
@dataclass
|
|
66
|
-
class
|
|
161
|
+
class UnpauseStage:
|
|
162
|
+
"""Unpause given snapshots that are being deployed to prod.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
promoted_snapshots: Snapshots to unpause.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
promoted_snapshots: t.Set[SnapshotTableInfo]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass
|
|
172
|
+
class FinalizeEnvironmentStage:
|
|
173
|
+
"""Finalize the enviornment record in the state.
|
|
174
|
+
|
|
175
|
+
Finalization means that all stages have been applied and that the environment has been transitioned
|
|
176
|
+
to the new state successfully. This should be the last stage in the plan application process.
|
|
177
|
+
"""
|
|
178
|
+
|
|
67
179
|
pass
|
|
68
180
|
|
|
69
181
|
|
|
70
182
|
PlanStage = t.Union[
|
|
71
183
|
BeforeAllStage,
|
|
72
184
|
AfterAllStage,
|
|
185
|
+
CreateSnapshotRecordsStage,
|
|
73
186
|
PhysicalLayerUpdateStage,
|
|
74
187
|
AuditOnlyRunStage,
|
|
75
188
|
RestatementStage,
|
|
76
189
|
BackfillStage,
|
|
190
|
+
EnvironmentRecordUpdateStage,
|
|
77
191
|
MigrateSchemasStage,
|
|
78
192
|
VirtualLayerUpdateStage,
|
|
79
|
-
|
|
193
|
+
UnpauseStage,
|
|
194
|
+
FinalizeEnvironmentStage,
|
|
80
195
|
]
|
|
81
196
|
|
|
82
197
|
|
|
83
198
|
class PlanStagesBuilder:
|
|
199
|
+
"""The builder for the plan stages.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
state_reader: The state reader to use to read the snapshots and environment.
|
|
203
|
+
default_catalog: The default catalog to use for the snapshots.
|
|
204
|
+
"""
|
|
205
|
+
|
|
84
206
|
def __init__(
|
|
85
207
|
self,
|
|
86
208
|
state_reader: StateReader,
|
|
@@ -90,6 +212,16 @@ class PlanStagesBuilder:
|
|
|
90
212
|
self.default_catalog = default_catalog
|
|
91
213
|
|
|
92
214
|
def build(self, plan: EvaluatablePlan) -> t.List[PlanStage]:
|
|
215
|
+
"""Builds the plan stages for the given plan.
|
|
216
|
+
|
|
217
|
+
NOTE: Building the plan stages should NOT produce any side effects in the state or the data warehouse.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
plan: The plan to build the stages for.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
A list of plan stages.
|
|
224
|
+
"""
|
|
93
225
|
new_snapshots = {s.snapshot_id: s for s in plan.new_snapshots}
|
|
94
226
|
stored_snapshots = self.state_reader.get_snapshots(plan.environment.snapshots)
|
|
95
227
|
snapshots = {**new_snapshots, **stored_snapshots}
|
|
@@ -99,6 +231,8 @@ class PlanStagesBuilder:
|
|
|
99
231
|
s.snapshot_id for s in snapshots.values() if plan.is_selected_for_backfill(s.name)
|
|
100
232
|
}
|
|
101
233
|
|
|
234
|
+
self._adjust_intervals(snapshots_by_name, plan)
|
|
235
|
+
|
|
102
236
|
deployability_index = DeployabilityIndex.create(snapshots, start=plan.start)
|
|
103
237
|
deployability_index_for_creation = deployability_index
|
|
104
238
|
if plan.is_dev:
|
|
@@ -119,8 +253,9 @@ class PlanStagesBuilder:
|
|
|
119
253
|
s
|
|
120
254
|
for s in snapshots.values()
|
|
121
255
|
if s.is_paused
|
|
122
|
-
and s.
|
|
123
|
-
and not
|
|
256
|
+
and s.is_model
|
|
257
|
+
and not s.is_symbolic
|
|
258
|
+
and (not deployability_index_for_creation.is_representative(s) or s.is_view)
|
|
124
259
|
]
|
|
125
260
|
|
|
126
261
|
snapshots_to_intervals = self._missing_intervals(
|
|
@@ -138,19 +273,26 @@ class PlanStagesBuilder:
|
|
|
138
273
|
elif snapshot.snapshot_id in after_promote_snapshots:
|
|
139
274
|
missing_intervals_after_promote[snapshot] = intervals
|
|
140
275
|
|
|
276
|
+
promoted_snapshots, demoted_snapshots, demoted_environment_naming_info = (
|
|
277
|
+
self._get_promoted_demoted_snapshots(plan)
|
|
278
|
+
)
|
|
279
|
+
|
|
141
280
|
stages: t.List[PlanStage] = []
|
|
142
281
|
|
|
143
|
-
before_all_stage = self._get_before_all_stage(plan)
|
|
282
|
+
before_all_stage = self._get_before_all_stage(plan, snapshots_by_name)
|
|
144
283
|
if before_all_stage:
|
|
145
284
|
stages.append(before_all_stage)
|
|
146
285
|
|
|
286
|
+
if plan.new_snapshots:
|
|
287
|
+
stages.append(CreateSnapshotRecordsStage(snapshots=plan.new_snapshots))
|
|
288
|
+
|
|
147
289
|
stages.append(
|
|
148
290
|
self._get_physical_layer_update_stage(
|
|
149
291
|
plan, snapshots, snapshots_to_intervals, deployability_index_for_creation
|
|
150
292
|
)
|
|
151
293
|
)
|
|
152
294
|
|
|
153
|
-
audit_only_snapshots =
|
|
295
|
+
audit_only_snapshots = self._get_audit_only_snapshots(new_snapshots)
|
|
154
296
|
if audit_only_snapshots:
|
|
155
297
|
stages.append(AuditOnlyRunStage(snapshots=list(audit_only_snapshots.values())))
|
|
156
298
|
|
|
@@ -162,53 +304,101 @@ class PlanStagesBuilder:
|
|
|
162
304
|
stages.append(
|
|
163
305
|
BackfillStage(
|
|
164
306
|
snapshot_to_intervals=missing_intervals_before_promote,
|
|
307
|
+
all_snapshots=snapshots_by_name,
|
|
165
308
|
deployability_index=deployability_index,
|
|
166
309
|
)
|
|
167
310
|
)
|
|
168
311
|
elif not needs_backfill:
|
|
169
312
|
# Append an empty backfill stage so that explainer can show that the stage is skipped
|
|
170
313
|
stages.append(
|
|
171
|
-
BackfillStage(
|
|
314
|
+
BackfillStage(
|
|
315
|
+
snapshot_to_intervals={},
|
|
316
|
+
all_snapshots=snapshots_by_name,
|
|
317
|
+
deployability_index=deployability_index,
|
|
318
|
+
)
|
|
172
319
|
)
|
|
173
320
|
|
|
174
|
-
stages.append(
|
|
321
|
+
stages.append(
|
|
322
|
+
EnvironmentRecordUpdateStage(
|
|
323
|
+
no_gaps_snapshot_names={s.name for s in before_promote_snapshots}
|
|
324
|
+
)
|
|
325
|
+
)
|
|
175
326
|
|
|
176
327
|
if snapshots_with_schema_migration:
|
|
177
|
-
stages.append(
|
|
328
|
+
stages.append(
|
|
329
|
+
MigrateSchemasStage(
|
|
330
|
+
snapshots=snapshots_with_schema_migration,
|
|
331
|
+
all_snapshots=snapshots,
|
|
332
|
+
deployability_index=deployability_index_for_creation,
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if not plan.is_dev and not plan.ensure_finalized_snapshots and promoted_snapshots:
|
|
337
|
+
# Only unpause at this point if we don't have to use the finalized snapshots
|
|
338
|
+
# for subsequent plan applications. Otherwise, unpause right before updating
|
|
339
|
+
# the virtual layer.
|
|
340
|
+
stages.append(UnpauseStage(promoted_snapshots=promoted_snapshots))
|
|
178
341
|
|
|
179
342
|
if missing_intervals_after_promote:
|
|
180
343
|
stages.append(
|
|
181
344
|
BackfillStage(
|
|
182
345
|
snapshot_to_intervals=missing_intervals_after_promote,
|
|
346
|
+
all_snapshots=snapshots_by_name,
|
|
183
347
|
deployability_index=deployability_index,
|
|
184
348
|
)
|
|
185
349
|
)
|
|
186
350
|
|
|
187
|
-
|
|
351
|
+
if not plan.is_dev and plan.ensure_finalized_snapshots and promoted_snapshots:
|
|
352
|
+
# Unpause right before updating the virtual layer and finalizing the environment in case when
|
|
353
|
+
# we need to use the finalized snapshots for subsequent plan applications.
|
|
354
|
+
# Otherwise, unpause right after updatig the environment record.
|
|
355
|
+
stages.append(UnpauseStage(promoted_snapshots=promoted_snapshots))
|
|
356
|
+
|
|
357
|
+
virtual_layer_update_stage = self._get_virtual_layer_update_stage(
|
|
358
|
+
promoted_snapshots,
|
|
359
|
+
demoted_snapshots,
|
|
360
|
+
demoted_environment_naming_info,
|
|
361
|
+
snapshots,
|
|
362
|
+
deployability_index,
|
|
363
|
+
)
|
|
188
364
|
if virtual_layer_update_stage:
|
|
189
365
|
stages.append(virtual_layer_update_stage)
|
|
190
366
|
|
|
191
|
-
|
|
367
|
+
stages.append(FinalizeEnvironmentStage())
|
|
368
|
+
|
|
369
|
+
after_all_stage = self._get_after_all_stage(plan, snapshots_by_name)
|
|
192
370
|
if after_all_stage:
|
|
193
371
|
stages.append(after_all_stage)
|
|
194
372
|
|
|
195
373
|
return stages
|
|
196
374
|
|
|
197
|
-
def _get_before_all_stage(
|
|
375
|
+
def _get_before_all_stage(
|
|
376
|
+
self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]
|
|
377
|
+
) -> t.Optional[BeforeAllStage]:
|
|
198
378
|
before_all = [
|
|
199
|
-
|
|
379
|
+
environment_statements
|
|
200
380
|
for environment_statements in plan.environment_statements or []
|
|
201
|
-
|
|
381
|
+
if environment_statements.before_all
|
|
202
382
|
]
|
|
203
|
-
return
|
|
383
|
+
return (
|
|
384
|
+
BeforeAllStage(statements=before_all, all_snapshots=snapshots_by_name)
|
|
385
|
+
if before_all
|
|
386
|
+
else None
|
|
387
|
+
)
|
|
204
388
|
|
|
205
|
-
def _get_after_all_stage(
|
|
389
|
+
def _get_after_all_stage(
|
|
390
|
+
self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]
|
|
391
|
+
) -> t.Optional[AfterAllStage]:
|
|
206
392
|
after_all = [
|
|
207
|
-
|
|
393
|
+
environment_statements
|
|
208
394
|
for environment_statements in plan.environment_statements or []
|
|
209
|
-
|
|
395
|
+
if environment_statements.after_all
|
|
210
396
|
]
|
|
211
|
-
return
|
|
397
|
+
return (
|
|
398
|
+
AfterAllStage(statements=after_all, all_snapshots=snapshots_by_name)
|
|
399
|
+
if after_all
|
|
400
|
+
else None
|
|
401
|
+
)
|
|
212
402
|
|
|
213
403
|
def _get_restatement_stage(
|
|
214
404
|
self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]
|
|
@@ -220,7 +410,9 @@ class PlanStagesBuilder:
|
|
|
220
410
|
snapshot_intervals_to_restate[restated_snapshot.table_info] = interval
|
|
221
411
|
if not snapshot_intervals_to_restate or plan.is_dev:
|
|
222
412
|
return None
|
|
223
|
-
return RestatementStage(
|
|
413
|
+
return RestatementStage(
|
|
414
|
+
snapshot_intervals=snapshot_intervals_to_restate, all_snapshots=snapshots_by_name
|
|
415
|
+
)
|
|
224
416
|
|
|
225
417
|
def _get_physical_layer_update_stage(
|
|
226
418
|
self,
|
|
@@ -229,50 +421,80 @@ class PlanStagesBuilder:
|
|
|
229
421
|
snapshots_to_intervals: SnapshotToIntervals,
|
|
230
422
|
deployability_index: DeployabilityIndex,
|
|
231
423
|
) -> PhysicalLayerUpdateStage:
|
|
232
|
-
snapshots_to_create = [
|
|
233
|
-
s
|
|
234
|
-
for s in get_snapshots_to_create(plan, snapshots)
|
|
235
|
-
if s in snapshots_to_intervals and s.is_model and not s.is_symbolic
|
|
236
|
-
]
|
|
237
424
|
return PhysicalLayerUpdateStage(
|
|
238
|
-
snapshots=
|
|
425
|
+
snapshots=self._get_snapshots_to_create(plan, snapshots),
|
|
426
|
+
all_snapshots=snapshots,
|
|
427
|
+
snapshots_with_missing_intervals={s.snapshot_id for s in snapshots_to_intervals},
|
|
239
428
|
deployability_index=deployability_index,
|
|
240
429
|
)
|
|
241
430
|
|
|
242
431
|
def _get_virtual_layer_update_stage(
|
|
243
|
-
self,
|
|
432
|
+
self,
|
|
433
|
+
promoted_snapshots: t.Set[SnapshotTableInfo],
|
|
434
|
+
demoted_snapshots: t.Set[SnapshotTableInfo],
|
|
435
|
+
demoted_environment_naming_info: t.Optional[EnvironmentNamingInfo],
|
|
436
|
+
all_snapshots: t.Dict[SnapshotId, Snapshot],
|
|
437
|
+
deployability_index: DeployabilityIndex,
|
|
244
438
|
) -> t.Optional[VirtualLayerUpdateStage]:
|
|
245
|
-
promoted_snapshots
|
|
439
|
+
promoted_snapshots = {s for s in promoted_snapshots if s.is_model and not s.is_symbolic}
|
|
440
|
+
demoted_snapshots = {s for s in demoted_snapshots if s.is_model and not s.is_symbolic}
|
|
246
441
|
if not promoted_snapshots and not demoted_snapshots:
|
|
247
442
|
return None
|
|
248
443
|
return VirtualLayerUpdateStage(
|
|
249
444
|
promoted_snapshots=promoted_snapshots,
|
|
250
445
|
demoted_snapshots=demoted_snapshots,
|
|
446
|
+
demoted_environment_naming_info=demoted_environment_naming_info,
|
|
447
|
+
all_snapshots=all_snapshots,
|
|
251
448
|
deployability_index=deployability_index,
|
|
252
449
|
)
|
|
253
450
|
|
|
254
451
|
def _get_promoted_demoted_snapshots(
|
|
255
452
|
self, plan: EvaluatablePlan
|
|
256
|
-
) -> t.Tuple[
|
|
453
|
+
) -> t.Tuple[
|
|
454
|
+
t.Set[SnapshotTableInfo], t.Set[SnapshotTableInfo], t.Optional[EnvironmentNamingInfo]
|
|
455
|
+
]:
|
|
257
456
|
existing_environment = self.state_reader.get_environment(plan.environment.name)
|
|
258
457
|
if existing_environment:
|
|
259
|
-
|
|
260
|
-
|
|
458
|
+
new_table_infos = {
|
|
459
|
+
table_info.name: table_info for table_info in plan.environment.promoted_snapshots
|
|
460
|
+
}
|
|
461
|
+
existing_table_infos = {
|
|
462
|
+
table_info.name: table_info
|
|
463
|
+
for table_info in existing_environment.promoted_snapshots
|
|
464
|
+
}
|
|
465
|
+
views_that_changed_location = {
|
|
466
|
+
existing_table_info
|
|
467
|
+
for existing_table_info in existing_environment.promoted_snapshots
|
|
468
|
+
if existing_table_info.name in new_table_infos
|
|
469
|
+
and existing_table_info.qualified_view_name.for_environment(
|
|
470
|
+
existing_environment.naming_info
|
|
471
|
+
)
|
|
472
|
+
!= new_table_infos[existing_table_info.name].qualified_view_name.for_environment(
|
|
473
|
+
plan.environment.naming_info
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
missing_model_names = set(existing_table_infos) - {
|
|
261
477
|
s.name for s in plan.environment.promoted_snapshots
|
|
262
478
|
}
|
|
263
|
-
demoted_snapshots = {
|
|
479
|
+
demoted_snapshots = {
|
|
480
|
+
existing_table_infos[name] for name in missing_model_names
|
|
481
|
+
} | views_that_changed_location
|
|
264
482
|
else:
|
|
265
483
|
demoted_snapshots = set()
|
|
484
|
+
|
|
266
485
|
promoted_snapshots = set(plan.environment.promoted_snapshots)
|
|
267
486
|
if existing_environment and plan.environment.can_partially_promote(existing_environment):
|
|
268
487
|
promoted_snapshots -= set(existing_environment.promoted_snapshots)
|
|
269
488
|
|
|
270
|
-
|
|
271
|
-
|
|
489
|
+
demoted_environment_naming_info = (
|
|
490
|
+
existing_environment.naming_info if demoted_snapshots and existing_environment else None
|
|
491
|
+
)
|
|
272
492
|
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
|
|
493
|
+
return (
|
|
494
|
+
promoted_snapshots,
|
|
495
|
+
demoted_snapshots,
|
|
496
|
+
demoted_environment_naming_info,
|
|
497
|
+
)
|
|
276
498
|
|
|
277
499
|
def _missing_intervals(
|
|
278
500
|
self,
|
|
@@ -294,6 +516,69 @@ class PlanStagesBuilder:
|
|
|
294
516
|
interval_end_per_model=plan.interval_end_per_model,
|
|
295
517
|
)
|
|
296
518
|
|
|
519
|
+
def _get_audit_only_snapshots(
|
|
520
|
+
self, new_snapshots: t.Dict[SnapshotId, Snapshot]
|
|
521
|
+
) -> t.Dict[SnapshotId, Snapshot]:
|
|
522
|
+
metadata_snapshots = []
|
|
523
|
+
for snapshot in new_snapshots.values():
|
|
524
|
+
if not snapshot.is_metadata or not snapshot.is_model or not snapshot.evaluatable:
|
|
525
|
+
continue
|
|
526
|
+
|
|
527
|
+
metadata_snapshots.append(snapshot)
|
|
528
|
+
|
|
529
|
+
# Bulk load all the previous snapshots
|
|
530
|
+
previous_snapshots = self.state_reader.get_snapshots(
|
|
531
|
+
[
|
|
532
|
+
s.previous_version.snapshot_id(s.name)
|
|
533
|
+
for s in metadata_snapshots
|
|
534
|
+
if s.previous_version
|
|
535
|
+
]
|
|
536
|
+
).values()
|
|
537
|
+
|
|
538
|
+
# Check if any of the snapshots have modifications to the audits field by comparing the hashes
|
|
539
|
+
audit_snapshots = {}
|
|
540
|
+
for snapshot, previous_snapshot in zip(metadata_snapshots, previous_snapshots):
|
|
541
|
+
new_audits_hash = snapshot.model.audit_metadata_hash()
|
|
542
|
+
previous_audit_hash = previous_snapshot.model.audit_metadata_hash()
|
|
543
|
+
|
|
544
|
+
if snapshot.model.audits and previous_audit_hash != new_audits_hash:
|
|
545
|
+
audit_snapshots[snapshot.snapshot_id] = snapshot
|
|
546
|
+
|
|
547
|
+
return audit_snapshots
|
|
548
|
+
|
|
549
|
+
def _get_snapshots_to_create(
|
|
550
|
+
self, plan: EvaluatablePlan, snapshots: t.Dict[SnapshotId, Snapshot]
|
|
551
|
+
) -> t.List[Snapshot]:
|
|
552
|
+
promoted_snapshot_ids = (
|
|
553
|
+
set(plan.environment.promoted_snapshot_ids)
|
|
554
|
+
if plan.environment.promoted_snapshot_ids is not None
|
|
555
|
+
else None
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _should_create(s: Snapshot) -> bool:
|
|
559
|
+
if not s.is_model or s.is_symbolic:
|
|
560
|
+
return False
|
|
561
|
+
# Only create tables for snapshots that we're planning to promote or that were selected for backfill
|
|
562
|
+
return (
|
|
563
|
+
plan.is_selected_for_backfill(s.name)
|
|
564
|
+
or promoted_snapshot_ids is None
|
|
565
|
+
or s.snapshot_id in promoted_snapshot_ids
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return [s for s in snapshots.values() if _should_create(s)]
|
|
569
|
+
|
|
570
|
+
def _adjust_intervals(
|
|
571
|
+
self, snapshots_by_name: t.Dict[str, Snapshot], plan: EvaluatablePlan
|
|
572
|
+
) -> None:
|
|
573
|
+
# Make sure the intervals are up to date and restatements are reflected
|
|
574
|
+
self.state_reader.refresh_snapshot_intervals(snapshots_by_name.values())
|
|
575
|
+
for new_snapshot in plan.new_snapshots:
|
|
576
|
+
if new_snapshot.is_forward_only:
|
|
577
|
+
# Forward-only snapshots inherit intervals in dev because of cloning
|
|
578
|
+
new_snapshot.dev_intervals = new_snapshot.intervals.copy()
|
|
579
|
+
for s_name, interval in plan.restatements.items():
|
|
580
|
+
snapshots_by_name[s_name].remove_interval(interval)
|
|
581
|
+
|
|
297
582
|
|
|
298
583
|
def build_plan_stages(
|
|
299
584
|
plan: EvaluatablePlan,
|