sqlmesh 0.188.2.dev30__py3-none-any.whl → 0.188.2.dev31__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.188.2.dev30'
21
- __version_tuple__ = version_tuple = (0, 188, 2, 'dev30')
20
+ __version__ = version = '0.188.2.dev31'
21
+ __version_tuple__ = version_tuple = (0, 188, 2, 'dev31')
sqlmesh/cli/main.py CHANGED
@@ -457,7 +457,13 @@ def diff(ctx: click.Context, environment: t.Optional[str] = None) -> None:
457
457
  @click.option(
458
458
  "--diff-rendered",
459
459
  is_flag=True,
460
- help="Output text differences for the rendered versions of the models and standalone audits",
460
+ help="Output text differences for the rendered versions of the models and standalone audits.",
461
+ default=None,
462
+ )
463
+ @click.option(
464
+ "--explain",
465
+ is_flag=True,
466
+ help="Explain the plan instead of applying it.",
461
467
  default=None,
462
468
  )
463
469
  @opt.verbose
sqlmesh/core/context.py CHANGED
@@ -88,7 +88,7 @@ from sqlmesh.core.notification_target import (
88
88
  NotificationTarget,
89
89
  NotificationTargetManager,
90
90
  )
91
- from sqlmesh.core.plan import Plan, PlanBuilder, SnapshotIntervals
91
+ from sqlmesh.core.plan import Plan, PlanBuilder, SnapshotIntervals, PlanExplainer
92
92
  from sqlmesh.core.plan.definition import UserProvidedFlags
93
93
  from sqlmesh.core.reference import ReferenceGraph
94
94
  from sqlmesh.core.scheduler import Scheduler, CompletionStatus
@@ -1211,6 +1211,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1211
1211
  run: t.Optional[bool] = None,
1212
1212
  diff_rendered: t.Optional[bool] = None,
1213
1213
  skip_linter: t.Optional[bool] = None,
1214
+ explain: t.Optional[bool] = None,
1214
1215
  ) -> Plan:
1215
1216
  """Interactively creates a plan.
1216
1217
 
@@ -1256,6 +1257,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1256
1257
  run: Whether to run latest intervals as part of the plan application.
1257
1258
  diff_rendered: Whether the diff should compare raw vs rendered models
1258
1259
  skip_linter: Linter runs by default so this will skip it if enabled
1260
+ explain: Whether to explain the plan instead of applying it.
1259
1261
 
1260
1262
  Returns:
1261
1263
  The populated Plan object.
@@ -1283,6 +1285,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1283
1285
  run=run,
1284
1286
  diff_rendered=diff_rendered,
1285
1287
  skip_linter=skip_linter,
1288
+ explain=explain,
1286
1289
  )
1287
1290
 
1288
1291
  plan = plan_builder.build()
@@ -1292,6 +1295,9 @@ class GenericContext(BaseContext, t.Generic[C]):
1292
1295
  # or if there are any uncategorized snapshots in the plan
1293
1296
  no_prompts = False
1294
1297
 
1298
+ if explain:
1299
+ auto_apply = True
1300
+
1295
1301
  self.console.plan(
1296
1302
  plan_builder,
1297
1303
  auto_apply if auto_apply is not None else self.config.plan.auto_apply,
@@ -1328,6 +1334,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1328
1334
  run: t.Optional[bool] = None,
1329
1335
  diff_rendered: t.Optional[bool] = None,
1330
1336
  skip_linter: t.Optional[bool] = None,
1337
+ explain: t.Optional[bool] = None,
1331
1338
  ) -> PlanBuilder:
1332
1339
  """Creates a plan builder.
1333
1340
 
@@ -1544,6 +1551,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1544
1551
  interval_end_per_model=max_interval_end_per_model,
1545
1552
  console=self.console,
1546
1553
  user_provided_flags=user_provided_flags,
1554
+ explain=explain or False,
1547
1555
  )
1548
1556
 
1549
1557
  def apply(
@@ -1568,6 +1576,16 @@ class GenericContext(BaseContext, t.Generic[C]):
1568
1576
  return
1569
1577
  if plan.uncategorized:
1570
1578
  raise UncategorizedPlanError("Can't apply a plan with uncategorized changes.")
1579
+
1580
+ if plan.explain:
1581
+ explainer = PlanExplainer(
1582
+ state_reader=self.state_reader,
1583
+ default_catalog=self.default_catalog,
1584
+ console=self.console,
1585
+ )
1586
+ explainer.evaluate(plan.to_evaluatable())
1587
+ return
1588
+
1571
1589
  self.notification_target_manager.notify(
1572
1590
  NotificationEvent.APPLY_START,
1573
1591
  environment=plan.environment_naming_info.name,
@@ -229,6 +229,19 @@ class Environment(EnvironmentNamingInfo, EnvironmentSummary):
229
229
  finalized_ts=self.finalized_ts,
230
230
  )
231
231
 
232
+ def can_partially_promote(self, existing_environment: Environment) -> bool:
233
+ """Returns True if the existing environment can be partially promoted to the current environment.
234
+
235
+ Partial promotion means that we don't need to re-create views for snapshots that are already promoted in the
236
+ target environment.
237
+ """
238
+ return (
239
+ bool(existing_environment.finalized_ts)
240
+ and not existing_environment.expired
241
+ and existing_environment.gateway_managed == self.gateway_managed
242
+ and existing_environment.name == c.PROD
243
+ )
244
+
232
245
  def _convert_list_to_models_and_store(
233
246
  self, field: str, type_: t.Type[PydanticType]
234
247
  ) -> t.Optional[t.List[PydanticType]]:
@@ -10,3 +10,4 @@ from sqlmesh.core.plan.evaluator import (
10
10
  PlanEvaluator as PlanEvaluator,
11
11
  update_intervals_for_new_snapshots as update_intervals_for_new_snapshots,
12
12
  )
13
+ from sqlmesh.core.plan.explainer import PlanExplainer as PlanExplainer
@@ -83,6 +83,7 @@ class PlanBuilder:
83
83
  environment state, or to use whatever snapshots are in the current environment state even if
84
84
  the environment is not finalized.
85
85
  interval_end_per_model: The mapping from model FQNs to target end dates.
86
+ explain: Whether to explain the plan instead of applying it.
86
87
  """
87
88
 
88
89
  def __init__(
@@ -112,6 +113,7 @@ class PlanBuilder:
112
113
  enable_preview: bool = False,
113
114
  end_bounded: bool = False,
114
115
  ensure_finalized_snapshots: bool = False,
116
+ explain: bool = False,
115
117
  interval_end_per_model: t.Optional[t.Dict[str, int]] = None,
116
118
  console: t.Optional[PlanBuilderConsole] = None,
117
119
  user_provided_flags: t.Optional[t.Dict[str, UserProvidedFlags]] = None,
@@ -142,6 +144,7 @@ class PlanBuilder:
142
144
  self._console = console or get_console()
143
145
  self._choices: t.Dict[SnapshotId, SnapshotChangeCategory] = {}
144
146
  self._user_provided_flags = user_provided_flags
147
+ self._explain = explain
145
148
 
146
149
  self._start = start
147
150
  if not self._start and (
@@ -273,6 +276,7 @@ class PlanBuilder:
273
276
  empty_backfill=self._empty_backfill,
274
277
  no_gaps=self._no_gaps,
275
278
  forward_only=self._forward_only,
279
+ explain=self._explain,
276
280
  allow_destructive_models=t.cast(t.Set, self._allow_destructive_models),
277
281
  include_unmodified=self._include_unmodified,
278
282
  environment_ttl=self._environment_ttl,
@@ -46,6 +46,7 @@ class Plan(PydanticModel, frozen=True):
46
46
  include_unmodified: bool
47
47
  end_bounded: bool
48
48
  ensure_finalized_snapshots: bool
49
+ explain: bool
49
50
 
50
51
  environment_ttl: t.Optional[str] = None
51
52
  environment_naming_info: EnvironmentNamingInfo
@@ -36,7 +36,7 @@ from sqlmesh.core.snapshot import (
36
36
  SnapshotCreationFailedError,
37
37
  )
38
38
  from sqlmesh.utils import CompletionStatus
39
- from sqlmesh.core.state_sync import StateSync
39
+ from sqlmesh.core.state_sync import StateSync, StateReader
40
40
  from sqlmesh.core.state_sync.base import PromotionResult
41
41
  from sqlmesh.utils.concurrency import NodeExecutionFailedError
42
42
  from sqlmesh.utils.errors import PlanError
@@ -284,23 +284,7 @@ class BuiltInPlanEvaluator(PlanEvaluator):
284
284
  new_snapshots=plan.new_snapshots, plan_id=plan.plan_id
285
285
  )
286
286
 
287
- promoted_snapshot_ids = (
288
- set(plan.environment.promoted_snapshot_ids)
289
- if plan.environment.promoted_snapshot_ids is not None
290
- else None
291
- )
292
-
293
- def _should_create(s: Snapshot) -> bool:
294
- if not s.is_model or s.is_symbolic:
295
- return False
296
- # Only create tables for snapshots that we're planning to promote or that were selected for backfill
297
- return (
298
- plan.is_selected_for_backfill(s.name)
299
- or promoted_snapshot_ids is None
300
- or s.snapshot_id in promoted_snapshot_ids
301
- )
302
-
303
- snapshots_to_create = [s for s in snapshots.values() if _should_create(s)]
287
+ snapshots_to_create = get_snapshots_to_create(plan, snapshots)
304
288
 
305
289
  completion_status = None
306
290
  progress_stopped = False
@@ -573,32 +557,7 @@ class BuiltInPlanEvaluator(PlanEvaluator):
573
557
  plan: EvaluatablePlan,
574
558
  new_snapshots: t.Dict[SnapshotId, Snapshot],
575
559
  ) -> None:
576
- # Filter out snapshots that are not categorized as metadata changes on models
577
- metadata_snapshots = []
578
- for snapshot in new_snapshots.values():
579
- if not snapshot.is_metadata or not snapshot.is_model or not snapshot.evaluatable:
580
- continue
581
-
582
- metadata_snapshots.append(snapshot)
583
-
584
- # Bulk load all the previous snapshots
585
- previous_snapshots = self.state_sync.get_snapshots(
586
- [
587
- s.previous_version.snapshot_id(s.name)
588
- for s in metadata_snapshots
589
- if s.previous_version
590
- ]
591
- ).values()
592
-
593
- # Check if any of the snapshots have modifications to the audits field by comparing the hashes
594
- audit_snapshots = {}
595
- for snapshot, previous_snapshot in zip(metadata_snapshots, previous_snapshots):
596
- new_audits_hash = snapshot.model.audit_metadata_hash()
597
- previous_audit_hash = previous_snapshot.model.audit_metadata_hash()
598
-
599
- if snapshot.model.audits and previous_audit_hash != new_audits_hash:
600
- audit_snapshots[snapshot.snapshot_id] = snapshot
601
-
560
+ audit_snapshots = get_audit_only_snapshots(new_snapshots, self.state_sync)
602
561
  if not audit_snapshots:
603
562
  return
604
563
 
@@ -636,3 +595,52 @@ def update_intervals_for_new_snapshots(
636
595
 
637
596
  if snapshots_intervals:
638
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
+
646
+ return [s for s in snapshots.values() if _should_create(s)]
@@ -0,0 +1,281 @@
1
+ import abc
2
+ import typing as t
3
+ import logging
4
+
5
+ from rich.console import Console as RichConsole
6
+ from rich.tree import Tree
7
+ from sqlglot.dialects.dialect import DialectType
8
+ from sqlmesh.core import constants as c
9
+ from sqlmesh.core.console import Console, TerminalConsole, get_console
10
+ from sqlmesh.core.environment import EnvironmentNamingInfo
11
+ from sqlmesh.core.plan.definition import EvaluatablePlan, SnapshotIntervals
12
+ from sqlmesh.core.plan import stages
13
+ from sqlmesh.core.plan.evaluator import (
14
+ PlanEvaluator,
15
+ )
16
+ from sqlmesh.core.state_sync import StateReader
17
+ from sqlmesh.core.snapshot.definition import (
18
+ SnapshotInfoMixin,
19
+ )
20
+ from sqlmesh.utils import Verbosity, rich as srich
21
+ from sqlmesh.utils.date import to_ts
22
+ from sqlmesh.utils.errors import SQLMeshError
23
+
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class PlanExplainer(PlanEvaluator):
29
+ def __init__(
30
+ self,
31
+ state_reader: StateReader,
32
+ default_catalog: t.Optional[str],
33
+ console: t.Optional[Console] = None,
34
+ ):
35
+ self.state_reader = state_reader
36
+ self.default_catalog = default_catalog
37
+ self.console = console or get_console()
38
+
39
+ def evaluate(
40
+ self, plan: EvaluatablePlan, circuit_breaker: t.Optional[t.Callable[[], bool]] = None
41
+ ) -> None:
42
+ plan_stages = stages.build_plan_stages(plan, self.state_reader, self.default_catalog)
43
+ explainer_console = _get_explainer_console(
44
+ self.console, plan.environment, self.default_catalog
45
+ )
46
+ explainer_console.explain(plan_stages)
47
+
48
+
49
+ class ExplainerConsole(abc.ABC):
50
+ @abc.abstractmethod
51
+ def explain(self, stages: t.List[stages.PlanStage]) -> None:
52
+ pass
53
+
54
+
55
+ MAX_TREE_LENGTH = 10
56
+
57
+
58
+ class RichExplainerConsole(ExplainerConsole):
59
+ def __init__(
60
+ self,
61
+ environment_naming_info: EnvironmentNamingInfo,
62
+ dialect: DialectType,
63
+ default_catalog: t.Optional[str],
64
+ verbosity: Verbosity = Verbosity.DEFAULT,
65
+ console: t.Optional[RichConsole] = None,
66
+ ):
67
+ self.environment_naming_info = environment_naming_info
68
+ self.dialect = dialect
69
+ self.default_catalog = default_catalog
70
+ self.verbosity = verbosity
71
+ self.console: RichConsole = console or srich.console
72
+
73
+ def explain(self, stages: t.List[stages.PlanStage]) -> None:
74
+ tree = Tree("[bold]Explained plan[/bold]")
75
+ for stage in stages:
76
+ handler_name = f"visit_{_to_snake_case(stage.__class__.__name__)}"
77
+ if not hasattr(self, handler_name):
78
+ logger.error("Unexpected stage: %s", stage.__class__.__name__)
79
+ continue
80
+ handler = getattr(self, handler_name)
81
+ result = handler(stage)
82
+ if result:
83
+ tree.add(self._limit_tree(result))
84
+ self.console.print(tree)
85
+
86
+ def visit_before_all_stage(self, stage: stages.BeforeAllStage) -> Tree:
87
+ return Tree("[bold]Execute before all statements[/bold]")
88
+
89
+ def visit_after_all_stage(self, stage: stages.AfterAllStage) -> Tree:
90
+ return Tree("[bold]Execute after all statements[/bold]")
91
+
92
+ def visit_physical_layer_update_stage(self, stage: stages.PhysicalLayerUpdateStage) -> Tree:
93
+ if not stage.snapshots:
94
+ return Tree("[bold]SKIP: No physical layer updates to perform[/bold]")
95
+
96
+ tree = Tree(
97
+ "[bold]Validate SQL and create physical layer tables and views if they do not exist[/bold]"
98
+ )
99
+ for snapshot in stage.snapshots:
100
+ is_deployable = (
101
+ stage.deployability_index.is_deployable(snapshot)
102
+ if self.environment_naming_info.name != c.PROD
103
+ else True
104
+ )
105
+ display_name = self._display_name(snapshot)
106
+ table_name = snapshot.table_name(is_deployable)
107
+ model_tree = Tree(f"{display_name} -> {table_name}")
108
+
109
+ if snapshot.is_model:
110
+ if snapshot.model.pre_statements:
111
+ model_tree.add("Run pre-statements")
112
+ if snapshot.model.annotated:
113
+ model_tree.add("Dry run model query without inserting results")
114
+
115
+ if snapshot.is_view:
116
+ create_tree = Tree("Create view if it doesn't exist")
117
+ elif snapshot.is_forward_only and snapshot.previous_versions:
118
+ prod_table = snapshot.table_name(True)
119
+ create_tree = Tree(
120
+ f"Clone {prod_table} into {table_name} and then update its schema if it doesn't exist"
121
+ )
122
+ else:
123
+ create_tree = Tree("Create table if it doesn't exist")
124
+
125
+ if not is_deployable:
126
+ create_tree.add("[orange1]preview[/orange1]: data will NOT be reused in production")
127
+ model_tree.add(create_tree)
128
+
129
+ if snapshot.is_model and snapshot.model.post_statements:
130
+ model_tree.add("Run post-statements")
131
+
132
+ tree.add(model_tree)
133
+ return tree
134
+
135
+ def visit_audit_only_run_stage(self, stage: stages.AuditOnlyRunStage) -> Tree:
136
+ tree = Tree("[bold]Audit-only execution[/bold]")
137
+ for snapshot in stage.snapshots:
138
+ display_name = self._display_name(snapshot)
139
+ tree.add(display_name)
140
+ return tree
141
+
142
+ def visit_restatement_stage(self, stage: stages.RestatementStage) -> Tree:
143
+ tree = Tree("[bold]Invalidate data intervals as part of restatement[/bold]")
144
+ for snapshot_table_info, interval in stage.snapshot_intervals.items():
145
+ display_name = self._display_name(snapshot_table_info)
146
+ tree.add(f"{display_name} [{to_ts(interval[0])} - {to_ts(interval[1])}]")
147
+ return tree
148
+
149
+ def visit_backfill_stage(self, stage: stages.BackfillStage) -> Tree:
150
+ if not stage.snapshot_to_intervals:
151
+ return Tree("[bold]SKIP: No model batches to execute[/bold]")
152
+
153
+ tree = Tree(
154
+ "[bold]Backfill models by running their queries and run standalone audits[/bold]"
155
+ )
156
+ for snapshot, intervals in stage.snapshot_to_intervals.items():
157
+ display_name = self._display_name(snapshot)
158
+ if snapshot.is_model:
159
+ table_name = snapshot.table_name(stage.deployability_index.is_deployable(snapshot))
160
+ model_tree = Tree(f"{display_name} -> {table_name}")
161
+
162
+ for signal_name, _ in snapshot.model.signals:
163
+ model_tree.add(f"Check '{signal_name}' signal")
164
+
165
+ if snapshot.model.pre_statements:
166
+ model_tree.add("Run pre-statements")
167
+
168
+ if snapshot.is_incremental:
169
+ current_intervals = (
170
+ snapshot.intervals
171
+ if stage.deployability_index.is_deployable(snapshot)
172
+ else snapshot.dev_intervals
173
+ )
174
+ if current_intervals:
175
+ formatted_range = SnapshotIntervals(
176
+ snapshot_id=snapshot.snapshot_id, intervals=intervals
177
+ ).format_intervals(snapshot.node.interval_unit)
178
+ model_tree.add(
179
+ f"Incrementally insert records within the range [{formatted_range}]"
180
+ )
181
+ else:
182
+ # If there are no intervals, the table will be fully refreshed
183
+ model_tree.add("Fully refresh table")
184
+ elif snapshot.is_view:
185
+ model_tree.add("Recreate view")
186
+ else:
187
+ model_tree.add("Fully refresh table")
188
+
189
+ if snapshot.model.post_statements:
190
+ model_tree.add("Run post-statements")
191
+
192
+ if snapshot.model.audits:
193
+ for audit_name, _ in snapshot.model.audits:
194
+ model_tree.add(f"Run '{audit_name}' audit")
195
+
196
+ tree.add(model_tree)
197
+ else:
198
+ tree.add(f"{display_name} \[standalone audit]")
199
+ return tree
200
+
201
+ def visit_migrate_schemas_stage(self, stage: stages.MigrateSchemasStage) -> Tree:
202
+ tree = Tree(
203
+ "[bold]Update schemas (add, drop, alter columns) of production physical tables to reflect forward-only changes[/bold]"
204
+ )
205
+ for snapshot in stage.snapshots:
206
+ display_name = self._display_name(snapshot)
207
+ table_name = snapshot.table_name(True)
208
+ tree.add(f"{display_name} -> {table_name}")
209
+ return tree
210
+
211
+ def visit_virtual_layer_update_stage(self, stage: stages.VirtualLayerUpdateStage) -> Tree:
212
+ tree = Tree(
213
+ f"[bold]Update the virtual layer for environment '{self.environment_naming_info.name}'[/bold]"
214
+ )
215
+ promote_tree = Tree(
216
+ "[bold]Create or update views in the virtual layer to point at new physical tables and views[/bold]"
217
+ )
218
+ for snapshot in stage.promoted_snapshots:
219
+ display_name = self._display_name(snapshot)
220
+ table_name = snapshot.table_name(stage.deployability_index.is_representative(snapshot))
221
+ promote_tree.add(f"{display_name} -> {table_name}")
222
+
223
+ demote_tree = Tree(
224
+ "[bold]Delete views in the virtual layer for models that were removed[/bold]"
225
+ )
226
+ for snapshot in stage.demoted_snapshots:
227
+ display_name = self._display_name(snapshot)
228
+ demote_tree.add(display_name)
229
+
230
+ if stage.promoted_snapshots:
231
+ tree.add(self._limit_tree(promote_tree))
232
+ if stage.demoted_snapshots:
233
+ tree.add(self._limit_tree(demote_tree))
234
+ return tree
235
+
236
+ def visit_environment_record_update_stage(
237
+ self, stage: stages.EnvironmentRecordUpdateStage
238
+ ) -> t.Optional[Tree]:
239
+ return None
240
+
241
+ def _display_name(self, snapshot: SnapshotInfoMixin) -> str:
242
+ return snapshot.display_name(
243
+ self.environment_naming_info,
244
+ self.default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None,
245
+ dialect=self.dialect,
246
+ )
247
+
248
+ def _limit_tree(self, tree: Tree) -> Tree:
249
+ tree_length = len(tree.children)
250
+ if tree_length <= MAX_TREE_LENGTH:
251
+ return tree
252
+ if self.verbosity < Verbosity.VERY_VERBOSE:
253
+ tree.children = [
254
+ tree.children[0],
255
+ Tree(f".... {tree_length - 2} more ...."),
256
+ tree.children[-1],
257
+ ]
258
+ return tree
259
+
260
+
261
+ def _get_explainer_console(
262
+ console: t.Optional[Console],
263
+ environment_naming_info: EnvironmentNamingInfo,
264
+ default_catalog: t.Optional[str],
265
+ ) -> ExplainerConsole:
266
+ console = console or get_console()
267
+ if not isinstance(console, TerminalConsole):
268
+ raise SQLMeshError("Plain explaination is only supported in the terminal.")
269
+ return RichExplainerConsole(
270
+ environment_naming_info=environment_naming_info,
271
+ dialect=console.dialect,
272
+ default_catalog=default_catalog,
273
+ verbosity=console.verbosity,
274
+ console=console.console,
275
+ )
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
+ )
@@ -0,0 +1,303 @@
1
+ import typing as t
2
+
3
+ from dataclasses import dataclass
4
+ 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
+ from sqlmesh.core.state_sync import StateReader
10
+ from sqlmesh.core.scheduler import merged_missing_intervals, SnapshotToIntervals
11
+ from sqlmesh.core.snapshot.definition import (
12
+ DeployabilityIndex,
13
+ Snapshot,
14
+ SnapshotTableInfo,
15
+ SnapshotId,
16
+ Interval,
17
+ )
18
+
19
+
20
+ @dataclass
21
+ class BeforeAllStage:
22
+ statements: t.List[str]
23
+
24
+
25
+ @dataclass
26
+ class AfterAllStage:
27
+ statements: t.List[str]
28
+
29
+
30
+ @dataclass
31
+ class PhysicalLayerUpdateStage:
32
+ snapshots: t.List[Snapshot]
33
+ deployability_index: DeployabilityIndex
34
+
35
+
36
+ @dataclass
37
+ class AuditOnlyRunStage:
38
+ snapshots: t.List[Snapshot]
39
+
40
+
41
+ @dataclass
42
+ class RestatementStage:
43
+ snapshot_intervals: t.Dict[SnapshotTableInfo, Interval]
44
+
45
+
46
+ @dataclass
47
+ class BackfillStage:
48
+ snapshot_to_intervals: SnapshotToIntervals
49
+ deployability_index: DeployabilityIndex
50
+ before_promote: bool = True
51
+
52
+
53
+ @dataclass
54
+ class MigrateSchemasStage:
55
+ snapshots: t.List[Snapshot]
56
+
57
+
58
+ @dataclass
59
+ class VirtualLayerUpdateStage:
60
+ promoted_snapshots: t.Set[SnapshotTableInfo]
61
+ demoted_snapshots: t.Set[SnapshotTableInfo]
62
+ deployability_index: DeployabilityIndex
63
+
64
+
65
+ @dataclass
66
+ class EnvironmentRecordUpdateStage:
67
+ pass
68
+
69
+
70
+ PlanStage = t.Union[
71
+ BeforeAllStage,
72
+ AfterAllStage,
73
+ PhysicalLayerUpdateStage,
74
+ AuditOnlyRunStage,
75
+ RestatementStage,
76
+ BackfillStage,
77
+ MigrateSchemasStage,
78
+ VirtualLayerUpdateStage,
79
+ EnvironmentRecordUpdateStage,
80
+ ]
81
+
82
+
83
+ class PlanStagesBuilder:
84
+ def __init__(
85
+ self,
86
+ state_reader: StateReader,
87
+ default_catalog: t.Optional[str],
88
+ ):
89
+ self.state_reader = state_reader
90
+ self.default_catalog = default_catalog
91
+
92
+ def build(self, plan: EvaluatablePlan) -> t.List[PlanStage]:
93
+ new_snapshots = {s.snapshot_id: s for s in plan.new_snapshots}
94
+ stored_snapshots = self.state_reader.get_snapshots(plan.environment.snapshots)
95
+ snapshots = {**new_snapshots, **stored_snapshots}
96
+ snapshots_by_name = {s.name: s for s in snapshots.values()}
97
+
98
+ all_selected_for_backfill_snapshots = {
99
+ s.snapshot_id for s in snapshots.values() if plan.is_selected_for_backfill(s.name)
100
+ }
101
+
102
+ deployability_index = DeployabilityIndex.create(snapshots, start=plan.start)
103
+ deployability_index_for_creation = deployability_index
104
+ if plan.is_dev:
105
+ before_promote_snapshots = all_selected_for_backfill_snapshots
106
+ after_promote_snapshots = set()
107
+ snapshots_with_schema_migration = []
108
+ else:
109
+ before_promote_snapshots = {
110
+ s.snapshot_id
111
+ for s in snapshots.values()
112
+ if deployability_index.is_representative(s)
113
+ and plan.is_selected_for_backfill(s.name)
114
+ }
115
+ after_promote_snapshots = all_selected_for_backfill_snapshots - before_promote_snapshots
116
+ deployability_index = DeployabilityIndex.all_deployable()
117
+
118
+ snapshots_with_schema_migration = [
119
+ s
120
+ for s in snapshots.values()
121
+ if s.is_paused
122
+ and s.is_materialized
123
+ and not deployability_index_for_creation.is_representative(s)
124
+ ]
125
+
126
+ snapshots_to_intervals = self._missing_intervals(
127
+ plan, snapshots_by_name, deployability_index
128
+ )
129
+ needs_backfill = (
130
+ not plan.empty_backfill and not plan.skip_backfill and bool(snapshots_to_intervals)
131
+ )
132
+ missing_intervals_before_promote: SnapshotToIntervals = {}
133
+ missing_intervals_after_promote: SnapshotToIntervals = {}
134
+ if needs_backfill:
135
+ for snapshot, intervals in snapshots_to_intervals.items():
136
+ if snapshot.snapshot_id in before_promote_snapshots:
137
+ missing_intervals_before_promote[snapshot] = intervals
138
+ elif snapshot.snapshot_id in after_promote_snapshots:
139
+ missing_intervals_after_promote[snapshot] = intervals
140
+
141
+ stages: t.List[PlanStage] = []
142
+
143
+ before_all_stage = self._get_before_all_stage(plan)
144
+ if before_all_stage:
145
+ stages.append(before_all_stage)
146
+
147
+ stages.append(
148
+ self._get_physical_layer_update_stage(
149
+ plan, snapshots, snapshots_to_intervals, deployability_index_for_creation
150
+ )
151
+ )
152
+
153
+ audit_only_snapshots = get_audit_only_snapshots(new_snapshots, self.state_reader)
154
+ if audit_only_snapshots:
155
+ stages.append(AuditOnlyRunStage(snapshots=list(audit_only_snapshots.values())))
156
+
157
+ restatement_stage = self._get_restatement_stage(plan, snapshots_by_name)
158
+ if restatement_stage:
159
+ stages.append(restatement_stage)
160
+
161
+ if missing_intervals_before_promote:
162
+ stages.append(
163
+ BackfillStage(
164
+ snapshot_to_intervals=missing_intervals_before_promote,
165
+ deployability_index=deployability_index,
166
+ )
167
+ )
168
+ elif not needs_backfill:
169
+ # Append an empty backfill stage so that explainer can show that the stage is skipped
170
+ stages.append(
171
+ BackfillStage(snapshot_to_intervals={}, deployability_index=deployability_index)
172
+ )
173
+
174
+ stages.append(EnvironmentRecordUpdateStage())
175
+
176
+ if snapshots_with_schema_migration:
177
+ stages.append(MigrateSchemasStage(snapshots=snapshots_with_schema_migration))
178
+
179
+ if missing_intervals_after_promote:
180
+ stages.append(
181
+ BackfillStage(
182
+ snapshot_to_intervals=missing_intervals_after_promote,
183
+ deployability_index=deployability_index,
184
+ )
185
+ )
186
+
187
+ virtual_layer_update_stage = self._get_virtual_layer_update_stage(plan, deployability_index)
188
+ if virtual_layer_update_stage:
189
+ stages.append(virtual_layer_update_stage)
190
+
191
+ after_all_stage = self._get_after_all_stage(plan)
192
+ if after_all_stage:
193
+ stages.append(after_all_stage)
194
+
195
+ return stages
196
+
197
+ def _get_before_all_stage(self, plan: EvaluatablePlan) -> t.Optional[BeforeAllStage]:
198
+ before_all = [
199
+ statement
200
+ for environment_statements in plan.environment_statements or []
201
+ for statement in environment_statements.before_all
202
+ ]
203
+ return BeforeAllStage(statements=before_all) if before_all else None
204
+
205
+ def _get_after_all_stage(self, plan: EvaluatablePlan) -> t.Optional[AfterAllStage]:
206
+ after_all = [
207
+ statement
208
+ for environment_statements in plan.environment_statements or []
209
+ for statement in environment_statements.after_all
210
+ ]
211
+ return AfterAllStage(statements=after_all) if after_all else None
212
+
213
+ def _get_restatement_stage(
214
+ self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]
215
+ ) -> t.Optional[RestatementStage]:
216
+ snapshot_intervals_to_restate = {}
217
+ for name, interval in plan.restatements.items():
218
+ restated_snapshot = snapshots_by_name[name]
219
+ restated_snapshot.remove_interval(interval)
220
+ snapshot_intervals_to_restate[restated_snapshot.table_info] = interval
221
+ if not snapshot_intervals_to_restate or plan.is_dev:
222
+ return None
223
+ return RestatementStage(snapshot_intervals=snapshot_intervals_to_restate)
224
+
225
+ def _get_physical_layer_update_stage(
226
+ self,
227
+ plan: EvaluatablePlan,
228
+ snapshots: t.Dict[SnapshotId, Snapshot],
229
+ snapshots_to_intervals: SnapshotToIntervals,
230
+ deployability_index: DeployabilityIndex,
231
+ ) -> 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
+ return PhysicalLayerUpdateStage(
238
+ snapshots=snapshots_to_create,
239
+ deployability_index=deployability_index,
240
+ )
241
+
242
+ def _get_virtual_layer_update_stage(
243
+ self, plan: EvaluatablePlan, deployability_index: DeployabilityIndex
244
+ ) -> t.Optional[VirtualLayerUpdateStage]:
245
+ promoted_snapshots, demoted_snapshots = self._get_promoted_demoted_snapshots(plan)
246
+ if not promoted_snapshots and not demoted_snapshots:
247
+ return None
248
+ return VirtualLayerUpdateStage(
249
+ promoted_snapshots=promoted_snapshots,
250
+ demoted_snapshots=demoted_snapshots,
251
+ deployability_index=deployability_index,
252
+ )
253
+
254
+ def _get_promoted_demoted_snapshots(
255
+ self, plan: EvaluatablePlan
256
+ ) -> t.Tuple[t.Set[SnapshotTableInfo], t.Set[SnapshotTableInfo]]:
257
+ existing_environment = self.state_reader.get_environment(plan.environment.name)
258
+ if existing_environment:
259
+ snapshots_by_name = {s.name: s for s in existing_environment.snapshots}
260
+ demoted_snapshot_names = {s.name for s in existing_environment.promoted_snapshots} - {
261
+ s.name for s in plan.environment.promoted_snapshots
262
+ }
263
+ demoted_snapshots = {snapshots_by_name[name] for name in demoted_snapshot_names}
264
+ else:
265
+ demoted_snapshots = set()
266
+ promoted_snapshots = set(plan.environment.promoted_snapshots)
267
+ if existing_environment and plan.environment.can_partially_promote(existing_environment):
268
+ promoted_snapshots -= set(existing_environment.promoted_snapshots)
269
+
270
+ def _snapshot_filter(snapshot: SnapshotTableInfo) -> bool:
271
+ return snapshot.is_model and not snapshot.is_symbolic
272
+
273
+ return {s for s in promoted_snapshots if _snapshot_filter(s)}, {
274
+ s for s in demoted_snapshots if _snapshot_filter(s)
275
+ }
276
+
277
+ def _missing_intervals(
278
+ self,
279
+ plan: EvaluatablePlan,
280
+ snapshots_by_name: t.Dict[str, Snapshot],
281
+ deployability_index: DeployabilityIndex,
282
+ ) -> SnapshotToIntervals:
283
+ return merged_missing_intervals(
284
+ snapshots=snapshots_by_name.values(),
285
+ start=plan.start,
286
+ end=plan.end,
287
+ execution_time=plan.execution_time,
288
+ restatements={
289
+ snapshots_by_name[name].snapshot_id: interval
290
+ for name, interval in plan.restatements.items()
291
+ },
292
+ deployability_index=deployability_index,
293
+ end_bounded=plan.end_bounded,
294
+ interval_end_per_model=plan.interval_end_per_model,
295
+ )
296
+
297
+
298
+ def build_plan_stages(
299
+ plan: EvaluatablePlan,
300
+ state_reader: StateReader,
301
+ default_catalog: t.Optional[str],
302
+ ) -> t.List[PlanStage]:
303
+ return PlanStagesBuilder(state_reader, default_catalog).build(plan)
sqlmesh/core/scheduler.py CHANGED
@@ -118,16 +118,12 @@ class Scheduler:
118
118
  allow_partials, and other attributes that could cause the intervals to exceed the target end date.
119
119
  selected_snapshots: A set of snapshot names to run. If not provided, all snapshots will be run.
120
120
  """
121
- restatements = restatements or {}
122
- validate_date_range(start, end)
123
-
124
- snapshots: t.Collection[Snapshot] = self.snapshot_per_version.values()
125
- snapshots_to_intervals = compute_interval_params(
126
- snapshots,
127
- start=start or earliest_start_date(snapshots),
128
- end=end or now_timestamp(),
121
+ snapshots_to_intervals = merged_missing_intervals(
122
+ snapshots=self.snapshot_per_version.values(),
123
+ start=start,
124
+ end=end,
125
+ execution_time=execution_time,
129
126
  deployability_index=deployability_index,
130
- execution_time=execution_time or now_timestamp(),
131
127
  restatements=restatements,
132
128
  interval_end_per_model=interval_end_per_model,
133
129
  ignore_cron=ignore_cron,
@@ -700,6 +696,52 @@ class Scheduler:
700
696
  return audit_results
701
697
 
702
698
 
699
+ def merged_missing_intervals(
700
+ snapshots: t.Collection[Snapshot],
701
+ start: t.Optional[TimeLike] = None,
702
+ end: t.Optional[TimeLike] = None,
703
+ execution_time: t.Optional[TimeLike] = None,
704
+ deployability_index: t.Optional[DeployabilityIndex] = None,
705
+ restatements: t.Optional[t.Dict[SnapshotId, Interval]] = None,
706
+ interval_end_per_model: t.Optional[t.Dict[str, int]] = None,
707
+ ignore_cron: bool = False,
708
+ end_bounded: bool = False,
709
+ ) -> SnapshotToIntervals:
710
+ """Find the largest contiguous date interval parameters based only on what is missing.
711
+
712
+ For each node name, find all dependencies and look for a stored snapshot from the metastore. If a snapshot is found,
713
+ calculate the missing intervals that need to be processed given the passed in start and end intervals.
714
+
715
+ This is a superset of what may actually get processed at runtime based on things like batch size, signal readiness, etc.
716
+
717
+ Args:
718
+ snapshots: A set of target snapshots for which intervals should be computed.
719
+ start: The start of the run. Defaults to the min node start date.
720
+ end: The end of the run. Defaults to now.
721
+ execution_time: The date/time reference to use for execution time. Defaults to now.
722
+ deployability_index: Determines snapshots that are deployable in the context of this evaluation.
723
+ restatements: A set of snapshot names being restated.
724
+ interval_end_per_model: The mapping from model FQNs to target end dates.
725
+ ignore_cron: Whether to ignore the node's cron schedule.
726
+ end_bounded: If set to true, the returned intervals will be bounded by the target end date, disregarding lookback,
727
+ allow_partials, and other attributes that could cause the intervals to exceed the target end date.
728
+ """
729
+ restatements = restatements or {}
730
+ validate_date_range(start, end)
731
+
732
+ return compute_interval_params(
733
+ snapshots,
734
+ start=start or earliest_start_date(snapshots),
735
+ end=end or now_timestamp(),
736
+ deployability_index=deployability_index,
737
+ execution_time=execution_time or now_timestamp(),
738
+ restatements=restatements,
739
+ interval_end_per_model=interval_end_per_model,
740
+ ignore_cron=ignore_cron,
741
+ end_bounded=end_bounded,
742
+ )
743
+
744
+
703
745
  def compute_interval_params(
704
746
  snapshots: t.Collection[Snapshot],
705
747
  *,
@@ -24,7 +24,6 @@ from datetime import datetime
24
24
 
25
25
  from sqlglot import exp
26
26
 
27
- from sqlmesh.core import constants as c
28
27
  from sqlmesh.core.console import Console, get_console
29
28
  from sqlmesh.core.engine_adapter import EngineAdapter
30
29
  from sqlmesh.core.environment import Environment, EnvironmentStatements, EnvironmentSummary
@@ -224,13 +223,7 @@ class EngineAdapterStateSync(StateSync):
224
223
  }
225
224
 
226
225
  added_table_infos = set(table_infos.values())
227
- if (
228
- existing_environment
229
- and existing_environment.finalized_ts
230
- and not existing_environment.expired
231
- and existing_environment.gateway_managed == environment.gateway_managed
232
- and existing_environment.name == c.PROD
233
- ):
226
+ if existing_environment and environment.can_partially_promote(existing_environment):
234
227
  # Only promote new snapshots.
235
228
  added_table_infos -= set(existing_environment.promoted_snapshots)
236
229
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.188.2.dev30
3
+ Version: 0.188.2.dev31
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
2
- sqlmesh/_version.py,sha256=DU1AIAOe9GaxIAn4LmIG-6XbdsSxIRpDdIhvly6cfeg,530
2
+ sqlmesh/_version.py,sha256=gzm94aWOg0XB5l8YtYf7gt6I9XBNkBbbsBL_cOtPCFo,530
3
3
  sqlmesh/magics.py,sha256=fu7-OeT_0_TAkWZOLoiioE3-3iFpvTRxFCbQUlRnfuw,39944
4
4
  sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -7,16 +7,16 @@ sqlmesh/cicd/bot.py,sha256=2zlbn-DXkqQzr3lA0__IGU4XaIfXBXBKLWXNI2DRJX8,759
7
7
  sqlmesh/cicd/config.py,sha256=mpgkisSBBovlNrNZir2unT8Wes6b38RW2UsU_o4e8vY,109
8
8
  sqlmesh/cli/__init__.py,sha256=MAXAyO7isbyM-PaSdFjkJNcjn32WjOl0XzxT5gvp_vo,1690
9
9
  sqlmesh/cli/example_project.py,sha256=QAFQbw_kzqn30GLTthqsuWXbFDBpX8W6RsCgQ-jx65M,9308
10
- sqlmesh/cli/main.py,sha256=dt92CJPuMzUZvBYqXMh-tkUCaLfj2Z5RC2COa98_9Co,32310
10
+ sqlmesh/cli/main.py,sha256=gk2iNJ3trprWtVpqGafbaCubYR3U6ByoacwZGFubRPM,32434
11
11
  sqlmesh/cli/options.py,sha256=aC_L5WnMScKzMU55BD1Y-8R-QzqCkPSYrlCI-payHRA,1333
12
12
  sqlmesh/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  sqlmesh/core/_typing.py,sha256=e1f7xO2aZCy38M_3VoDy-XI3shyd99-IFctgIFSgXWM,497
14
14
  sqlmesh/core/console.py,sha256=x7qWETjHgu8ZpMtfW8V3bGKByT4nZXDjgtO6xmvAb-I,151143
15
15
  sqlmesh/core/constants.py,sha256=oqt6zjepUhQoWlgapKn_HpG7Pu9mrFwepdX5caNfJUI,2499
16
- sqlmesh/core/context.py,sha256=ZZskVvTciGvJO6weRzGlrAyQsqRkSqsjCSY2rc_cFZ4,116729
16
+ sqlmesh/core/context.py,sha256=PoqGhpRxIes9Qo59YW_KggqL8Wbcj3rleH8RAQkLHT4,117312
17
17
  sqlmesh/core/context_diff.py,sha256=I7luvY2za30E5ynVaFPaEQQEmNViJpWAmW48PGSyDCU,21135
18
18
  sqlmesh/core/dialect.py,sha256=93cXYVuNoZ9CvJNTVn2Yz7WDOdHINwVXflbn0eZ-p14,49764
19
- sqlmesh/core/environment.py,sha256=Fsih6WZ7Tx4ec0XIlmIIspUWcmijPNW5uHPRQSWuhA8,12315
19
+ sqlmesh/core/environment.py,sha256=bNj-JCG_l13dwrp--YPIuGNsuWLTMLcFA5sY8IazVK8,12917
20
20
  sqlmesh/core/lineage.py,sha256=zjB0Zfamo2Fja2r5SSZPMbrEKTXr1WjozZPVqvxdybI,3143
21
21
  sqlmesh/core/loader.py,sha256=L2pxQZ2OuXnBHcruJW2fsclPIrFyb-TogoWHwm_qKC8,36417
22
22
  sqlmesh/core/macros.py,sha256=L_UlI4kysfDiYYpv78kODZw8kYbC9-FdBx7nHm1hBjs,60584
@@ -24,7 +24,7 @@ sqlmesh/core/node.py,sha256=r4_1MRTPcU0MAlwZ5KzrykLT58B7dkKvHzPCsJmEKD8,14497
24
24
  sqlmesh/core/notification_target.py,sha256=PPGoDrgbRKxr27vJEu03XqNTQLYTw0ZF_b0yAapxGeI,16158
25
25
  sqlmesh/core/reference.py,sha256=k7OSkLqTjPR8WJjNeFj0xAJ297nZUMgb_iTVwKRRKjc,4875
26
26
  sqlmesh/core/renderer.py,sha256=KOr7rQ-RfWiS51cEj2fzdhxCrYbU0A5wNTGwKyeqLt4,26636
27
- sqlmesh/core/scheduler.py,sha256=TB03rSvPyaPMRANOJq_O_cNXvu0fc5rVaDVC9olE5kg,34196
27
+ sqlmesh/core/scheduler.py,sha256=pbSBaKjHwPMW8XIVDChcACv8THSjTPLA_FGwvHKB6rk,36283
28
28
  sqlmesh/core/schema_diff.py,sha256=Sp2MoqAngp-yTYevkLaf9jb26SuAdGXoZ54g5MjWowU,30516
29
29
  sqlmesh/core/schema_loader.py,sha256=lvrI9k7hr1P4X4E6t3U-KrcuvdQuw0UdNlN90P2s52w,3563
30
30
  sqlmesh/core/selector.py,sha256=_hcktUQiKXCPE-mOqXClHUVWBfEZKMPVVNDtpSSRhbI,12660
@@ -92,10 +92,12 @@ sqlmesh/core/model/kind.py,sha256=i6_AMBarXPGPoMdF9kzBkyqS9tu__a2339-BoX7TgCM,36
92
92
  sqlmesh/core/model/meta.py,sha256=TRsCztBea2gSknPQxoY1ct13hdWgD0mM9XTHqu5KDgs,21167
93
93
  sqlmesh/core/model/schema.py,sha256=OClbAeA8LpW-IiSiLT_TlDWkEqmIIvSk0GXq8SWI-GQ,3443
94
94
  sqlmesh/core/model/seed.py,sha256=a0M-1zY1gOkN5ph2GQyataEdBSCtq50YjeFk2LyvInI,5058
95
- sqlmesh/core/plan/__init__.py,sha256=ctpnu9mK3uTujs0It8qRMqcj9GdObXDnvSgn4rD2zqc,450
96
- sqlmesh/core/plan/builder.py,sha256=WM-jnBgViLSfnL2-nUdZH9dCkD6TkYUnRuO9TgD8DsQ,38329
97
- sqlmesh/core/plan/definition.py,sha256=SI3zi2poPkKJKTZogYv60Q7eIOhksaAcFUcu-J2-XqI,13218
98
- sqlmesh/core/plan/evaluator.py,sha256=RAHJgQoV5Y5SIG_pV2O_Bn0VFlD1yzMC0UrUU6eaB_E,26842
95
+ sqlmesh/core/plan/__init__.py,sha256=aieod5xxcJJl0m7Xp9BXCwwrYnhMbeltGoNZGjwoVTA,521
96
+ sqlmesh/core/plan/builder.py,sha256=mZ64P0m4LDtkyy4NoPsz8gqhtezF8Piaw5aj0_YuTjA,38496
97
+ sqlmesh/core/plan/definition.py,sha256=pBjQu1P5U-XheWOe9WkSNAZeLzYE7LFgYtq6-4Fx67g,13236
98
+ sqlmesh/core/plan/evaluator.py,sha256=7dPtjxc4SuKf9fHsMDwaGsprlXmhcoKi0N6Q1N8eFY4,27009
99
+ sqlmesh/core/plan/explainer.py,sha256=-12__YFHEeKfvcggquZVMFq-Q3DBIy-kMvdyU5j8K5U,11237
100
+ sqlmesh/core/plan/stages.py,sha256=AIBzEji7OorFx9B0Hm50E6h8zWIGSuLkoVWqIhq9AVw,10911
99
101
  sqlmesh/core/snapshot/__init__.py,sha256=Z5HTetNCZGoKF1WAKXk4LWL9dc8EC8lwS2O4TtoBmt4,1317
100
102
  sqlmesh/core/snapshot/cache.py,sha256=bgqCR2hyf6r2A_8QP1EnXFK25gDX37-Zg0YeMuETWxg,3934
101
103
  sqlmesh/core/snapshot/categorizer.py,sha256=qGG5870mu1jR2whKisSBH_GFbsyh96RnzJDgkN7DV9g,2482
@@ -108,7 +110,7 @@ sqlmesh/core/state_sync/common.py,sha256=THK-6AGHFBAuLXydD8QhzmzCdyl0BMrz2rB2xpm
108
110
  sqlmesh/core/state_sync/export_import.py,sha256=WJ7sFX2b_RPAFE4yspJRi_4JQnaO23xpJgRxwXD0agA,8709
109
111
  sqlmesh/core/state_sync/db/__init__.py,sha256=jGCqKYB7npq4eh6H2LyfBAJWdOP-IFu0DC4CMiQgYvw,107
110
112
  sqlmesh/core/state_sync/db/environment.py,sha256=RKaP71CDI7YFhLYuyYVbtyBEWfwvNd2_XsDvjtrQq2Q,13645
111
- sqlmesh/core/state_sync/db/facade.py,sha256=GOrcJSWeFxLbqf2fuGGLewYCoRhDoXBoLA4AsJ5MixI,25558
113
+ sqlmesh/core/state_sync/db/facade.py,sha256=xCK8zb3mKM3c5Oa8HgId_f2iYzC7Rw5hx5bSfPmGw_g,25319
112
114
  sqlmesh/core/state_sync/db/interval.py,sha256=qDo5qX7vulibbg9M8mDA0PjoQQJlUCouBJeWuTuLJ6Y,18043
113
115
  sqlmesh/core/state_sync/db/migrator.py,sha256=2LNUwOBNFkA1ntp-Z93S_OWxFQhOBj3ZD_ZqPCyPEAE,18139
114
116
  sqlmesh/core/state_sync/db/snapshot.py,sha256=7ccdRxni4UGAul-_15z6-I9GAifC94jfNTvAdUEQ9pw,29480
@@ -270,7 +272,7 @@ sqlmesh/utils/pydantic.py,sha256=I898jAFqa3qU6lG2uPpRebVFncOUo8ZrLWM_R3K3DHU,116
270
272
  sqlmesh/utils/rich.py,sha256=xNZyPCqEc4TAlJlsEthg-5eYUWpaizz0QjBzO7a1Yv4,1509
271
273
  sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
272
274
  sqlmesh/utils/yaml.py,sha256=np-WVcwmwsKghCgAj1OBBMsK1SXSRn4wR-H37cGiFr8,2282
273
- sqlmesh-0.188.2.dev30.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
275
+ sqlmesh-0.188.2.dev31.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
274
276
  web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
275
277
  web/client/dist/index.html,sha256=LbR9XrOXE77otBPoDnaomssYz6h2TBwLANs6hUJ0p2M,5472
276
278
  web/client/dist/assets/Audits-DB1HcP9M.js,sha256=eGI_Soj69Jntsixw1NqQpdwG94N5jipQZv7T1Y7WUSs,1041
@@ -388,8 +390,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
388
390
  web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
389
391
  web/server/api/endpoints/plan.py,sha256=OYuQ_4dgbhG9ibTcX1a8mvYgH3ANxw9Fhbb4wg6QWqc,9222
390
392
  web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
391
- sqlmesh-0.188.2.dev30.dist-info/METADATA,sha256=I3prHMgrH2I8rGoRDnej5HvQgoGCxnCKSQGzluA0UBQ,26541
392
- sqlmesh-0.188.2.dev30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
393
- sqlmesh-0.188.2.dev30.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
394
- sqlmesh-0.188.2.dev30.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
395
- sqlmesh-0.188.2.dev30.dist-info/RECORD,,
393
+ sqlmesh-0.188.2.dev31.dist-info/METADATA,sha256=XR2JL_XCGcYhXBnInxphjWqebJk2X9_KPXtW94Sgjrg,26541
394
+ sqlmesh-0.188.2.dev31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
395
+ sqlmesh-0.188.2.dev31.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
396
+ sqlmesh-0.188.2.dev31.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
397
+ sqlmesh-0.188.2.dev31.dist-info/RECORD,,