Skip to content

Commit f97e977

Browse files
committed
Fix: Forward-only models can't be categorized manually
1 parent 044854f commit f97e977

3 files changed

Lines changed: 61 additions & 7 deletions

File tree

sqlmesh/core/console.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,8 @@ def _prompt_categorize(
16251625
self._show_categorized_snapshots(plan, default_catalog)
16261626

16271627
for snapshot in plan.uncategorized:
1628+
if snapshot.is_model and snapshot.model.forward_only:
1629+
continue
16281630
if not no_diff:
16291631
self.show_sql(plan.context_diff.text_diff(snapshot.name))
16301632
tree = Tree(

sqlmesh/core/plan/builder.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
yesterday_ds,
3434
to_timestamp,
3535
)
36-
from sqlmesh.utils.errors import NoChangesPlanError, PlanError, SQLMeshError
36+
from sqlmesh.utils.errors import NoChangesPlanError, PlanError
3737

3838
logger = logging.getLogger(__name__)
3939

@@ -197,16 +197,16 @@ def set_choice(self, snapshot: Snapshot, choice: SnapshotChangeCategory) -> Plan
197197
if self._forward_only:
198198
raise PlanError("Choice setting is not supported by a forward-only plan.")
199199
if not self._is_new_snapshot(snapshot):
200-
raise SQLMeshError(
201-
f"A choice can't be changed for the existing version of '{snapshot.name}'."
200+
raise PlanError(
201+
f"A choice can't be changed for the existing version of {snapshot.name}."
202202
)
203203
if (
204204
not self._context_diff.directly_modified(snapshot.name)
205205
and snapshot.snapshot_id not in self._context_diff.added
206206
):
207-
raise SQLMeshError(
208-
f"Only directly modified models can be categorized ({snapshot.name})."
209-
)
207+
raise PlanError(f"Only directly modified models can be categorized ({snapshot.name}).")
208+
if snapshot.is_model and snapshot.model.forward_only:
209+
raise PlanError(f"Forward-only model {snapshot.name} cannot be categorized manually.")
210210

211211
self._choices[snapshot.snapshot_id] = choice
212212
self._latest_plan = None
@@ -215,7 +215,7 @@ def set_choice(self, snapshot: Snapshot, choice: SnapshotChangeCategory) -> Plan
215215
def apply(self) -> None:
216216
"""Builds and applies the plan."""
217217
if not self._apply:
218-
raise SQLMeshError("Plan was not initialized with an applier.")
218+
raise PlanError("Plan was not initialized with an applier.")
219219
self._apply(self.build())
220220

221221
def build(self) -> Plan:

tests/core/test_plan.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,3 +3053,55 @@ def test_plan_environment_statements_diff(make_snapshot):
30533053
)
30543054
assert stripped == expected_output
30553055
console_output.close()
3056+
3057+
3058+
def test_set_choice_for_forward_only_model(make_snapshot):
3059+
snapshot = make_snapshot(
3060+
SqlModel(
3061+
name="a",
3062+
query=parse_one("select 1, ds"),
3063+
dialect="duckdb",
3064+
kind=IncrementalByTimeRangeKind(time_column="ds", forward_only=True),
3065+
)
3066+
)
3067+
snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
3068+
updated_snapshot = make_snapshot(
3069+
SqlModel(
3070+
name="a",
3071+
query=parse_one("select 3, ds"),
3072+
kind=IncrementalByTimeRangeKind(time_column="ds", forward_only=True),
3073+
dialect="duckdb",
3074+
)
3075+
)
3076+
updated_snapshot.previous_versions = snapshot.all_versions
3077+
3078+
context_diff = ContextDiff(
3079+
environment="test_environment",
3080+
is_new_environment=True,
3081+
is_unfinalized_environment=False,
3082+
normalize_environment_name=True,
3083+
create_from="prod",
3084+
create_from_env_exists=True,
3085+
added=set(),
3086+
removed_snapshots={},
3087+
modified_snapshots={updated_snapshot.name: (updated_snapshot, snapshot)},
3088+
snapshots={updated_snapshot.snapshot_id: updated_snapshot},
3089+
new_snapshots={updated_snapshot.snapshot_id: updated_snapshot},
3090+
previous_plan_id=None,
3091+
previously_promoted_snapshot_ids=set(),
3092+
previous_finalized_snapshots=None,
3093+
previous_gateway_managed_virtual_layer=False,
3094+
gateway_managed_virtual_layer=False,
3095+
)
3096+
3097+
schema_differ = DuckDBEngineAdapter.SCHEMA_DIFFER
3098+
plan_builder = PlanBuilder(context_diff, schema_differ, is_dev=True)
3099+
3100+
with pytest.raises(PlanError, match='Forward-only model "a" cannot be categorized manually.'):
3101+
plan_builder.set_choice(updated_snapshot, SnapshotChangeCategory.BREAKING)
3102+
3103+
plan = plan_builder.build()
3104+
assert (
3105+
plan.snapshots[updated_snapshot.snapshot_id].change_category
3106+
== SnapshotChangeCategory.FORWARD_ONLY
3107+
)

0 commit comments

Comments
 (0)