sqlmesh 0.219.0__py3-none-any.whl → 0.219.1.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 CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.219.0'
32
- __version_tuple__ = version_tuple = (0, 219, 0)
31
+ __version__ = version = '0.219.1.dev2'
32
+ __version_tuple__ = version_tuple = (0, 219, 1, 'dev2')
33
33
 
34
34
  __commit_id__ = commit_id = None
sqlmesh/core/context.py CHANGED
@@ -1429,6 +1429,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1429
1429
  explain: t.Optional[bool] = None,
1430
1430
  ignore_cron: t.Optional[bool] = None,
1431
1431
  min_intervals: t.Optional[int] = None,
1432
+ always_include_local_changes: t.Optional[bool] = None,
1432
1433
  ) -> PlanBuilder:
1433
1434
  """Creates a plan builder.
1434
1435
 
@@ -1467,6 +1468,8 @@ class GenericContext(BaseContext, t.Generic[C]):
1467
1468
  diff_rendered: Whether the diff should compare raw vs rendered models
1468
1469
  min_intervals: Adjust the plan start date on a per-model basis in order to ensure at least this many intervals are covered
1469
1470
  on every model when checking for missing intervals
1471
+ always_include_local_changes: Usually when restatements are present, local changes in the filesystem are ignored.
1472
+ However, it can be desirable to deploy changes + restatements in the same plan, so this flag overrides the default behaviour.
1470
1473
 
1471
1474
  Returns:
1472
1475
  The plan builder.
@@ -1583,13 +1586,20 @@ class GenericContext(BaseContext, t.Generic[C]):
1583
1586
  "Selector did not return any models. Please check your model selection and try again."
1584
1587
  )
1585
1588
 
1589
+ if always_include_local_changes is None:
1590
+ # default behaviour - if restatements are detected; we operate entirely out of state and ignore local changes
1591
+ force_no_diff = restate_models is not None or (
1592
+ backfill_models is not None and not backfill_models
1593
+ )
1594
+ else:
1595
+ force_no_diff = not always_include_local_changes
1596
+
1586
1597
  snapshots = self._snapshots(models_override)
1587
1598
  context_diff = self._context_diff(
1588
1599
  environment or c.PROD,
1589
1600
  snapshots=snapshots,
1590
1601
  create_from=create_from,
1591
- force_no_diff=restate_models is not None
1592
- or (backfill_models is not None and not backfill_models),
1602
+ force_no_diff=force_no_diff,
1593
1603
  ensure_finalized_snapshots=self.config.plan.use_finalized_state,
1594
1604
  diff_rendered=diff_rendered,
1595
1605
  always_recreate_environment=self.config.plan.always_recreate_environment,
@@ -1644,6 +1654,14 @@ class GenericContext(BaseContext, t.Generic[C]):
1644
1654
  elif forward_only is None:
1645
1655
  forward_only = self.config.plan.forward_only
1646
1656
 
1657
+ # When handling prod restatements, only clear intervals from other model versions if we are using full virtual environments
1658
+ # If we are not, then there is no point, because none of the data in dev environments can be promoted by definition
1659
+ restate_all_snapshots = (
1660
+ expanded_restate_models is not None
1661
+ and not is_dev
1662
+ and self.config.virtual_environment_mode.is_full
1663
+ )
1664
+
1647
1665
  return self.PLAN_BUILDER_TYPE(
1648
1666
  context_diff=context_diff,
1649
1667
  start=start,
@@ -1651,6 +1669,7 @@ class GenericContext(BaseContext, t.Generic[C]):
1651
1669
  execution_time=execution_time,
1652
1670
  apply=self.apply,
1653
1671
  restate_models=expanded_restate_models,
1672
+ restate_all_snapshots=restate_all_snapshots,
1654
1673
  backfill_models=backfill_models,
1655
1674
  no_gaps=no_gaps,
1656
1675
  skip_backfill=skip_backfill,
@@ -65,6 +65,9 @@ class PlanBuilder:
65
65
  restate_models: A list of models for which the data should be restated for the time range
66
66
  specified in this plan. Note: models defined outside SQLMesh (external) won't be a part
67
67
  of the restatement.
68
+ restate_all_snapshots: If restatements are present, this flag indicates whether or not the intervals
69
+ being restated should be cleared from state for other versions of this model (typically, versions that are present in other environments).
70
+ If set to None, the default behaviour is to not clear anything unless the target environment is prod.
68
71
  backfill_models: A list of fully qualified model names for which the data should be backfilled as part of this plan.
69
72
  no_gaps: Whether to ensure that new snapshots for nodes that are already a
70
73
  part of the target environment have no data gaps when compared against previous
@@ -103,6 +106,7 @@ class PlanBuilder:
103
106
  execution_time: t.Optional[TimeLike] = None,
104
107
  apply: t.Optional[t.Callable[[Plan], None]] = None,
105
108
  restate_models: t.Optional[t.Iterable[str]] = None,
109
+ restate_all_snapshots: bool = False,
106
110
  backfill_models: t.Optional[t.Iterable[str]] = None,
107
111
  no_gaps: bool = False,
108
112
  skip_backfill: bool = False,
@@ -154,6 +158,7 @@ class PlanBuilder:
154
158
  self._auto_categorization_enabled = auto_categorization_enabled
155
159
  self._include_unmodified = include_unmodified
156
160
  self._restate_models = set(restate_models) if restate_models is not None else None
161
+ self._restate_all_snapshots = restate_all_snapshots
157
162
  self._effective_from = effective_from
158
163
 
159
164
  # note: this deliberately doesnt default to now() here.
@@ -277,7 +282,6 @@ class PlanBuilder:
277
282
  if self._latest_plan:
278
283
  return self._latest_plan
279
284
 
280
- self._ensure_no_new_snapshots_with_restatements()
281
285
  self._ensure_new_env_with_changes()
282
286
  self._ensure_valid_date_range()
283
287
  self._ensure_no_broken_references()
@@ -340,6 +344,7 @@ class PlanBuilder:
340
344
  deployability_index=deployability_index,
341
345
  selected_models_to_restate=self._restate_models,
342
346
  restatements=restatements,
347
+ restate_all_snapshots=self._restate_all_snapshots,
343
348
  start_override_per_model=self._start_override_per_model,
344
349
  end_override_per_model=end_override_per_model,
345
350
  selected_models_to_backfill=self._backfill_models,
@@ -859,15 +864,6 @@ class PlanBuilder:
859
864
  f"""Removed {broken_references_msg} are referenced in '{snapshot.name}'. Please remove broken references before proceeding."""
860
865
  )
861
866
 
862
- def _ensure_no_new_snapshots_with_restatements(self) -> None:
863
- if self._restate_models is not None and (
864
- self._context_diff.new_snapshots or self._context_diff.modified_snapshots
865
- ):
866
- raise PlanError(
867
- "Model changes and restatements can't be a part of the same plan. "
868
- "Revert or apply changes before proceeding with restatements."
869
- )
870
-
871
867
  def _ensure_new_env_with_changes(self) -> None:
872
868
  if (
873
869
  self._is_dev
@@ -67,6 +67,8 @@ class Plan(PydanticModel, frozen=True):
67
67
  Note that dev previews are also considered restatements, so :selected_models_to_restate can be empty
68
68
  while :restatements is still populated with dev previews
69
69
  """
70
+ restate_all_snapshots: bool
71
+ """Whether or not to clear intervals from state for other versions of the models listed in :restatements"""
70
72
 
71
73
  start_override_per_model: t.Optional[t.Dict[str, datetime]]
72
74
  end_override_per_model: t.Optional[t.Dict[str, datetime]]
@@ -268,6 +270,7 @@ class Plan(PydanticModel, frozen=True):
268
270
  skip_backfill=self.skip_backfill,
269
271
  empty_backfill=self.empty_backfill,
270
272
  restatements={s.name: i for s, i in self.restatements.items()},
273
+ restate_all_snapshots=self.restate_all_snapshots,
271
274
  is_dev=self.is_dev,
272
275
  allow_destructive_models=self.allow_destructive_models,
273
276
  allow_additive_models=self.allow_additive_models,
@@ -312,6 +315,7 @@ class EvaluatablePlan(PydanticModel):
312
315
  skip_backfill: bool
313
316
  empty_backfill: bool
314
317
  restatements: t.Dict[str, Interval]
318
+ restate_all_snapshots: bool
315
319
  is_dev: bool
316
320
  allow_destructive_models: t.Set[str]
317
321
  allow_additive_models: t.Set[str]
@@ -14,6 +14,7 @@ from sqlmesh.core.snapshot.definition import (
14
14
  SnapshotId,
15
15
  snapshots_to_dag,
16
16
  )
17
+ from sqlmesh.utils.errors import PlanError
17
18
 
18
19
 
19
20
  @dataclass
@@ -461,13 +462,18 @@ class PlanStagesBuilder:
461
462
  def _get_restatement_stage(
462
463
  self, plan: EvaluatablePlan, snapshots_by_name: t.Dict[str, Snapshot]
463
464
  ) -> t.Optional[RestatementStage]:
464
- if not plan.restatements or plan.is_dev:
465
- # The RestatementStage to clear intervals from state across all environments is not needed for plans against dev, only prod
466
- return None
465
+ if plan.restate_all_snapshots:
466
+ if plan.is_dev:
467
+ raise PlanError(
468
+ "Clearing intervals from state across dev model versions is only valid for prod plans"
469
+ )
467
470
 
468
- return RestatementStage(
469
- all_snapshots=snapshots_by_name,
470
- )
471
+ if plan.restatements:
472
+ return RestatementStage(
473
+ all_snapshots=snapshots_by_name,
474
+ )
475
+
476
+ return None
471
477
 
472
478
  def _get_physical_layer_update_stage(
473
479
  self,
@@ -746,11 +746,15 @@ class SnapshotEvaluator:
746
746
  adapter.execute(model.render_pre_statements(**render_statements_kwargs))
747
747
 
748
748
  if not target_table_exists or (model.is_seed and not snapshot.intervals):
749
- columns_to_types_provided = (
749
+ # Only create the empty table if the columns were provided explicitly by the user
750
+ should_create_empty_table = (
750
751
  model.kind.is_materialized
751
752
  and model.columns_to_types_
752
753
  and columns_to_types_all_known(model.columns_to_types_)
753
754
  )
755
+ if not should_create_empty_table:
756
+ # Or if the model is self-referential and its query is fully annotated with types
757
+ should_create_empty_table = model.depends_on_self and model.annotated
754
758
  if self._can_clone(snapshot, deployability_index):
755
759
  self._clone_snapshot_in_dev(
756
760
  snapshot=snapshot,
@@ -763,7 +767,7 @@ class SnapshotEvaluator:
763
767
  )
764
768
  runtime_stage = RuntimeStage.EVALUATING
765
769
  target_table_exists = True
766
- elif columns_to_types_provided or model.is_seed or model.kind.is_scd_type_2:
770
+ elif should_create_empty_table or model.is_seed or model.kind.is_scd_type_2:
767
771
  self._execute_create(
768
772
  snapshot=snapshot,
769
773
  table_name=target_table_name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.219.0
3
+ Version: 0.219.1.dev2
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=n60rwpz60kADQ732QDDScWY-wCxM24JjP95JV38Zj3U,708
2
+ sqlmesh/_version.py,sha256=gcGONnIcxilCZ4d_C8R95DfiO4eV7bcVx6lyptEjD7Y,721
3
3
  sqlmesh/magics.py,sha256=xLh3u4eqpVrKRVN5KF3X84RPRqjygAB9AJP1TXwH8hg,42086
4
4
  sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -13,7 +13,7 @@ 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=0LGXUw7wFGeX-PhsKUIscE1d8mMtoqgPe4WzMiiqw1g,172752
15
15
  sqlmesh/core/constants.py,sha256=y92qUS6s5upGr1Kvbf9KXJticNmehBxSM1kzbwSXFAE,2664
16
- sqlmesh/core/context.py,sha256=1B-xz4t81nopIm9hR7-PwYf3QZAFDDCNRrB0vMPShKA,129421
16
+ sqlmesh/core/context.py,sha256=uU9GGzXETS28Sjr-_8gNskshgO-KbTp-Th4Tf4FdQI4,130553
17
17
  sqlmesh/core/context_diff.py,sha256=mxkJu0IthFMOlaQ_kcq5C09mlgkq2RQb-pG2rd-x_nA,21648
18
18
  sqlmesh/core/dialect.py,sha256=CnKcPj6BnREfu9Zn1OyS7hZ3ktnaX03ygOg91nADlTU,53029
19
19
  sqlmesh/core/environment.py,sha256=Kgs_gUEUI072mh0JJFWNRynrCxp1TzRHZhX_NWJRfXc,13142
@@ -94,17 +94,17 @@ sqlmesh/core/model/meta.py,sha256=a68bnvc-qCCRLo-KCqc1JTrI9faDxsgrR4-P9jeZB0w,20
94
94
  sqlmesh/core/model/schema.py,sha256=_HMYfzK9wWXh7_CQDIIGnuQUD4aiX3o5D2cRp2sERzc,3387
95
95
  sqlmesh/core/model/seed.py,sha256=a0M-1zY1gOkN5ph2GQyataEdBSCtq50YjeFk2LyvInI,5058
96
96
  sqlmesh/core/plan/__init__.py,sha256=NKSvM7ZBVjw9ho3J65M1wFvG3KURB8PJ0FHHLmtSF44,443
97
- sqlmesh/core/plan/builder.py,sha256=1o7T5ErB46kJ1Qn5lUWNiXoopoud5uIoOanxtHwijXA,43615
97
+ sqlmesh/core/plan/builder.py,sha256=enW5o_PaWYZ0YRT7UMPy-mk5lqkwUgy7kndvegbTTA0,43679
98
98
  sqlmesh/core/plan/common.py,sha256=V5mBYa_iQEB-fe9eqQQdMO2ow8cDVXRtnowUucQi9Mg,10453
99
- sqlmesh/core/plan/definition.py,sha256=6ZazmxzWOxdmx4Ckclmp1GBsB1a62pR0qtaXTU-nkTY,14987
99
+ sqlmesh/core/plan/definition.py,sha256=YPpp7ABu77UznIHjhcPiDi7vxLyhj4II9CED_cQ19g4,15225
100
100
  sqlmesh/core/plan/evaluator.py,sha256=lJe3cwSICGiJ3KdGxvfpPfkaWzoJg49LAAjawz5QYoU,21062
101
101
  sqlmesh/core/plan/explainer.py,sha256=UITln7f4vxf6-nx7mV_IBbtIZ4f8ob4TyooqZMB9Pqg,15442
102
- sqlmesh/core/plan/stages.py,sha256=nDd8MrRwasLmgkwADPzIovz4h9e295yuiZbUbR5S66o,27338
102
+ sqlmesh/core/plan/stages.py,sha256=ZRwJqf1XM7Yep9qzynYPpX9XzhaL4ZkxxPlLhcuKoIE,27476
103
103
  sqlmesh/core/snapshot/__init__.py,sha256=NUhvP-glftOWwxONK79Bud93yNQJv8ApBUjkV35RhMY,1465
104
104
  sqlmesh/core/snapshot/cache.py,sha256=bgqCR2hyf6r2A_8QP1EnXFK25gDX37-Zg0YeMuETWxg,3934
105
105
  sqlmesh/core/snapshot/categorizer.py,sha256=iNBEqK2KIyTAYURlB9KLfyKCpXN7vjxSqA7QjFa7e5c,2418
106
106
  sqlmesh/core/snapshot/definition.py,sha256=2Pob60pngLy70C5HWbb_tE7wkgsUddZdOZ4HNxJq0sk,95140
107
- sqlmesh/core/snapshot/evaluator.py,sha256=hTN6UqSQvOyuEcWHq1252vy9g7cPAa3Or80-CAVwKKY,111546
107
+ sqlmesh/core/snapshot/evaluator.py,sha256=sSHRnkYoW8jhPiKmmckTl8eo1FiG0jnLDfFVvSpyalI,111886
108
108
  sqlmesh/core/snapshot/execution_tracker.py,sha256=Ss1oYgH28Fy1mQ4HriX-luE9MG0eLdecrE1SssUveQI,3651
109
109
  sqlmesh/core/state_sync/__init__.py,sha256=B60xj-_Xk-4jas3UphLvwkuf04G-9AyGckcblXE0aRg,869
110
110
  sqlmesh/core/state_sync/base.py,sha256=1Q_E8yixUg75PxfaVKOfWQote83ZXwOGN0r9BPiRVpE,19241
@@ -233,12 +233,12 @@ sqlmesh/utils/pydantic.py,sha256=o_NsXbIpDqNpUA1Uc5xF0ZzoXQYYB0DfHwdRxBwXPNk,120
233
233
  sqlmesh/utils/rich.py,sha256=cwQ5nJ6sgz64xHtoh6_ec7ReV5YpsOGhMtUJnwoRfEI,3549
234
234
  sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
235
235
  sqlmesh/utils/yaml.py,sha256=KFBd7hsKNRTtRudGR7d410qUYffQv0EWRcDM8hVNNZg,3025
236
- sqlmesh-0.219.0.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
236
+ sqlmesh-0.219.1.dev2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
237
237
  sqlmesh_dbt/__init__.py,sha256=awYS5y5mz-1NUmx6i5h5NSTJ7tidRl9NC0FAnFWSF6U,350
238
- sqlmesh_dbt/cli.py,sha256=28I7NEKIYvLc1bSOnKL-4uYDhc2QVHaZW4RZHjq2Lg0,4483
238
+ sqlmesh_dbt/cli.py,sha256=z07hDwEU46k3mAbSKVBsvPWnwXOOOFnYw2kQSRJahPA,4524
239
239
  sqlmesh_dbt/console.py,sha256=80sCEbrKfR5f3SvxIyI7hw_SuiDiiUwuJYxXbQO2014,830
240
240
  sqlmesh_dbt/error.py,sha256=1sPNU6Dik30DR9WTCvGp3ED-pzNmAA3LhP95BXb3ndI,1146
241
- sqlmesh_dbt/operations.py,sha256=BhNtnQRLPmP1uF8h10k_nv6MQj9jnSNymjVEmDFj_ew,10714
241
+ sqlmesh_dbt/operations.py,sha256=PrbS2wiWCliMMgMBxnBl6eDltSwUEXcZ3a4Zy6qOaVA,13089
242
242
  sqlmesh_dbt/options.py,sha256=noB_qK4uGGi7Erqk1XkkMaFz5aUc6lp44wwn1Nv_LI4,737
243
243
  sqlmesh_dbt/selectors.py,sha256=4w8eE0qAgoYC2K8afk3whfju-u3e5muXGwgkfXxQR3Y,5228
244
244
  web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -358,8 +358,8 @@ web/server/api/endpoints/models.py,sha256=kwj0s7uve3iZSMfmjkoPVMFMeY1sD0peTeyrWf
358
358
  web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
359
359
  web/server/api/endpoints/plan.py,sha256=bbbY50W_2MsZSTxOHWMKz0tbIm75nsRSlPy8GI2fg9Q,9306
360
360
  web/server/api/endpoints/table_diff.py,sha256=8XTwgOh6QBbNy_hTM1JuHgRjbnie-pGPrphiW-FNLjQ,6058
361
- sqlmesh-0.219.0.dist-info/METADATA,sha256=del8UbRPS_cvD74EuN6EiqAyO_P0r6R7lZUO2lmWjY0,26686
362
- sqlmesh-0.219.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
363
- sqlmesh-0.219.0.dist-info/entry_points.txt,sha256=sHAf6tQczIM8xZoduN4qaUjV7QEPVUUW_LCT8EDUMv4,155
364
- sqlmesh-0.219.0.dist-info/top_level.txt,sha256=RQ-33FPe2IgL0rgossAfJkCRtqslz9b7wFARqiWLC5Q,24
365
- sqlmesh-0.219.0.dist-info/RECORD,,
361
+ sqlmesh-0.219.1.dev2.dist-info/METADATA,sha256=R34m7XRaIfBEA4SGbU-rkE8uA2QMR59dY2LtbULxm_o,26691
362
+ sqlmesh-0.219.1.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
363
+ sqlmesh-0.219.1.dev2.dist-info/entry_points.txt,sha256=sHAf6tQczIM8xZoduN4qaUjV7QEPVUUW_LCT8EDUMv4,155
364
+ sqlmesh-0.219.1.dev2.dist-info/top_level.txt,sha256=RQ-33FPe2IgL0rgossAfJkCRtqslz9b7wFARqiWLC5Q,24
365
+ sqlmesh-0.219.1.dev2.dist-info/RECORD,,
sqlmesh_dbt/cli.py CHANGED
@@ -90,7 +90,9 @@ def dbt(
90
90
  @click.option(
91
91
  "-f",
92
92
  "--full-refresh",
93
- help="If specified, dbt will drop incremental models and fully-recalculate the incremental table from the model definition.",
93
+ is_flag=True,
94
+ default=False,
95
+ help="If specified, sqlmesh will drop incremental models and fully-recalculate the incremental table from the model definition.",
94
96
  )
95
97
  @click.option(
96
98
  "--env",
sqlmesh_dbt/operations.py CHANGED
@@ -11,7 +11,7 @@ if t.TYPE_CHECKING:
11
11
  from sqlmesh.dbt.project import Project
12
12
  from sqlmesh_dbt.console import DbtCliConsole
13
13
  from sqlmesh.core.model import Model
14
- from sqlmesh.core.plan import Plan
14
+ from sqlmesh.core.plan import Plan, PlanBuilder
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -42,8 +42,39 @@ class DbtOperations:
42
42
  full_refresh: bool = False,
43
43
  empty: bool = False,
44
44
  ) -> Plan:
45
- return self.context.plan(
46
- **self._plan_options(
45
+ plan_builder = self._plan_builder(
46
+ environment=environment,
47
+ select=select,
48
+ exclude=exclude,
49
+ full_refresh=full_refresh,
50
+ empty=empty,
51
+ )
52
+
53
+ plan = plan_builder.build()
54
+
55
+ self.console.plan(
56
+ plan_builder,
57
+ default_catalog=self.context.default_catalog,
58
+ # start doing work immediately (since no_diff is set, there isnt really anything for the user to say yes/no to)
59
+ auto_apply=True,
60
+ # dont output a diff of model changes
61
+ no_diff=True,
62
+ # don't throw up any prompts like "set the effective date" - use defaults
63
+ no_prompts=True,
64
+ )
65
+
66
+ return plan
67
+
68
+ def _plan_builder(
69
+ self,
70
+ environment: t.Optional[str] = None,
71
+ select: t.Optional[t.List[str]] = None,
72
+ exclude: t.Optional[t.List[str]] = None,
73
+ full_refresh: bool = False,
74
+ empty: bool = False,
75
+ ) -> PlanBuilder:
76
+ return self.context.plan_builder(
77
+ **self._plan_builder_options(
47
78
  environment=environment,
48
79
  select=select,
49
80
  exclude=exclude,
@@ -71,13 +102,15 @@ class DbtOperations:
71
102
 
72
103
  return selected_models
73
104
 
74
- def _plan_options(
105
+ def _plan_builder_options(
75
106
  self,
76
- environment: t.Optional[str] = None,
107
+ # upstream dbt options
77
108
  select: t.Optional[t.List[str]] = None,
78
109
  exclude: t.Optional[t.List[str]] = None,
79
110
  empty: bool = False,
80
111
  full_refresh: bool = False,
112
+ # sqlmesh extra options
113
+ environment: t.Optional[str] = None,
81
114
  ) -> t.Dict[str, t.Any]:
82
115
  import sqlmesh.core.constants as c
83
116
 
@@ -130,24 +163,38 @@ class DbtOperations:
130
163
  # `dbt --empty` adds LIMIT 0 to the queries, resulting in empty tables. In addition, it happily clobbers existing tables regardless of if they are populated.
131
164
  # This *partially* lines up with --skip-backfill in SQLMesh, which indicates to not populate tables if they happened to be created/updated as part of this plan.
132
165
  # However, if a table already exists and has data in it, there is no change so SQLMesh will not recreate the table and thus it will not be cleared.
133
- # So in order to fully replicate dbt's --empty, we also need --full-refresh semantics in order to replace existing tables
166
+ # Currently, SQLMesh has no way to say "restate with empty data", because --restate-model coupled with --skip-backfill ends up being a no-op
134
167
  options["skip_backfill"] = True
135
- full_refresh = True
168
+
169
+ self.console.log_warning(
170
+ "dbt's `--empty` drops the tables for all selected models and replaces them with empty ones.\n"
171
+ "This can easily result in accidental data loss, so SQLMesh limits this to only new or modified models and leaves the tables for existing unmodified models alone.\n\n"
172
+ "If you were creating empty tables to preview model changes, please consider using `--environment` to preview these changes in an isolated Virtual Data Environment instead.\n\n"
173
+ "Otherwise, if you really do want dbt's `--empty` behaviour of clearing every selected table, please file an issue on GitHub so we can better understand the use-case.\n"
174
+ )
175
+
176
+ if full_refresh:
177
+ # --full-refresh is implemented in terms of "add every model as a restatement"
178
+ # however, `--empty` sets skip_backfill=True, which causes the BackfillStage of the plan to be skipped.
179
+ # the re-processing of data intervals happens in the BackfillStage, so if it gets skipped, restatements become a no-op
180
+ raise ValueError("`--full-refresh` alongside `--empty` is not currently supported.")
136
181
 
137
182
  if full_refresh:
138
- # TODO: handling this requires some updates in the engine to enable restatements+changes in the same plan without affecting prod
139
- # if the plan targets dev
140
- pass
183
+ options.update(
184
+ dict(
185
+ # Add every selected model as a restatement to force them to get repopulated from scratch
186
+ restate_models=list(self.context.models)
187
+ if not select_models
188
+ else select_models,
189
+ # by default in SQLMesh, restatements only operate on what has been committed to state.
190
+ # in order to emulate dbt, we need to use the local filesystem instead, so we override this default
191
+ always_include_local_changes=True,
192
+ )
193
+ )
141
194
 
142
195
  return dict(
143
196
  environment=environment,
144
197
  select_models=select_models,
145
- # dont output a diff of model changes
146
- no_diff=True,
147
- # don't throw up any prompts like "set the effective date" - use defaults
148
- no_prompts=True,
149
- # start doing work immediately (since no_diff is set, there isnt really anything for the user to say yes/no to)
150
- auto_apply=True,
151
198
  **options,
152
199
  )
153
200