Skip to content

Commit 3271ae1

Browse files
authored
Fix!: Improve the table_name command (#4299)
1 parent 2ab08af commit 3271ae1

File tree

6 files changed

+115
-24
lines changed

6 files changed

+115
-24
lines changed

docs/guides/table_migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Consider an existing table named `my_schema.existing_table`. Migrating this tabl
131131

132132
c. Create the model in the SQLMesh project without backfilling any data by running `sqlmesh plan [environment name] --empty-backfill --start 2024-01-01`, replacing "[environment name]" with an environment name other than `prod` and using the same start date from the `MODEL` DDL in step 3b.
133133

134-
4. Determine the name of the model's snapshot physical table by running `sqlmesh table_name my_schema.existing_table`. For example, it might return `sqlmesh__my_schema.existing_table_123456`.
134+
4. Determine the name of the model's snapshot physical table by running `sqlmesh table_name --env [environment name] my_schema.existing_table`. For example, it might return `sqlmesh__my_schema.existing_table_123456`.
135135
5. Rename the original table `my_schema.existing_table_temp` to `sqlmesh__my_schema.existing_table_123456`
136136
137137
The model would have code similar to:

docs/reference/cli.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,9 +559,11 @@ Usage: sqlmesh table_name [OPTIONS] MODEL_NAME
559559
Prints the name of the physical table for the given model.
560560
561561
Options:
562-
--dev Print the name of the snapshot table used for previews in
563-
development environments.
564-
--help Show this message and exit.
562+
--environment, --env TEXT The environment to source the model version from.
563+
--prod If set, return the name of the physical table
564+
that will be used in production for the model
565+
version promoted in the target environment.
566+
--help Show this message and exit.
565567
```
566568

567569
## test

sqlmesh/cli/main.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"run",
3030
"environments",
3131
"invalidate",
32+
"table_name",
3233
)
3334
SKIP_CONTEXT_COMMANDS = ("init", "ui")
3435

@@ -988,17 +989,24 @@ def clean(obj: Context) -> None:
988989
@cli.command("table_name")
989990
@click.argument("model_name", required=True)
990991
@click.option(
991-
"--dev",
992+
"--environment",
993+
"--env",
994+
help="The environment to source the model version from.",
995+
)
996+
@click.option(
997+
"--prod",
992998
is_flag=True,
993-
help="Print the name of the snapshot table used for previews in development environments.",
994999
default=False,
1000+
help="If set, return the name of the physical table that will be used in production for the model version promoted in the target environment.",
9951001
)
9961002
@click.pass_obj
9971003
@error_handler
9981004
@cli_analytics
999-
def table_name(obj: Context, model_name: str, dev: bool) -> None:
1005+
def table_name(
1006+
obj: Context, model_name: str, environment: t.Optional[str] = None, prod: bool = False
1007+
) -> None:
10001008
"""Prints the name of the physical table for the given model."""
1001-
print(obj.table_name(model_name, dev))
1009+
print(obj.table_name(model_name, environment, prod))
10021010

10031011

10041012
@cli.command("dlt_refresh")

sqlmesh/core/context.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,25 +2135,45 @@ def _apply(self, plan: Plan, circuit_breaker: t.Optional[t.Callable[[], bool]])
21352135
)
21362136

21372137
@python_api_analytics
2138-
def table_name(self, model_name: str, dev: bool) -> str:
2139-
"""Returns the name of the pysical table for the given model name.
2138+
def table_name(
2139+
self, model_name: str, environment: t.Optional[str] = None, prod: bool = False
2140+
) -> str:
2141+
"""Returns the name of the pysical table for the given model name in the target environment.
21402142
21412143
Args:
21422144
model_name: The name of the model.
2143-
dev: Whether to use the deployability index for the table name.
2145+
environment: The environment to source the model version from.
2146+
prod: If True, return the name of the physical table that will be used in production for the model version
2147+
promoted in the target environment.
21442148
21452149
Returns:
21462150
The name of the physical table.
21472151
"""
2148-
deployability_index = (
2149-
DeployabilityIndex.create(self.snapshots.values())
2150-
if dev
2151-
else DeployabilityIndex.all_deployable()
2152+
environment = environment or self.config.default_target_environment
2153+
fqn = self._node_or_snapshot_to_fqn(model_name)
2154+
target_env = self.state_reader.get_environment(environment)
2155+
if not target_env:
2156+
raise SQLMeshError(f"Environment '{environment}' was not found.")
2157+
2158+
snapshot_info = None
2159+
for s in target_env.snapshots:
2160+
if s.name == fqn:
2161+
snapshot_info = s
2162+
break
2163+
if not snapshot_info:
2164+
raise SQLMeshError(
2165+
f"Model '{model_name}' was not found in environment '{environment}'."
2166+
)
2167+
2168+
if target_env.name == c.PROD or prod:
2169+
return snapshot_info.table_name()
2170+
2171+
snapshots = self.state_reader.get_snapshots(target_env.snapshots)
2172+
deployability_index = DeployabilityIndex.create(snapshots)
2173+
2174+
return snapshot_info.table_name(
2175+
is_deployable=deployability_index.is_deployable(snapshot_info.snapshot_id)
21522176
)
2153-
snapshot = self.get_snapshot(model_name)
2154-
if not snapshot:
2155-
raise SQLMeshError(f"Model '{model_name}' was not found.")
2156-
return snapshot.table_name(is_deployable=deployability_index.is_deployable(snapshot))
21572177

21582178
def clear_caches(self) -> None:
21592179
for path in self.configs:

sqlmesh/magics.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -721,16 +721,23 @@ def table_diff(self, context: Context, line: str) -> None:
721721
help="The name of the model to get the table name for.",
722722
)
723723
@argument(
724-
"--dev",
724+
"--environment",
725+
type=str,
726+
help="The environment to source the model version from.",
727+
)
728+
@argument(
729+
"--prod",
725730
action="store_true",
726-
help="Print the name of the snapshot table used for previews in development environments.",
731+
help="If set, return the name of the physical table that will be used in production for the model version promoted in the target environment.",
727732
)
728733
@line_magic
729734
@pass_sqlmesh_context
730735
def table_name(self, context: Context, line: str) -> None:
731736
"""Prints the name of the physical table for the given model."""
732737
args = parse_argstring(self.table_name, line)
733-
context.console.log_status_update(context.table_name(args.model_name, args.dev))
738+
context.console.log_status_update(
739+
context.table_name(args.model_name, args.environment, args.prod)
740+
)
734741

735742
@magic_arguments()
736743
@argument(

tests/core/test_integration.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
SnapshotTableInfo,
6262
)
6363
from sqlmesh.utils.date import TimeLike, now, to_date, to_datetime, to_timestamp
64-
from sqlmesh.utils.errors import NoChangesPlanError
64+
from sqlmesh.utils.errors import NoChangesPlanError, SQLMeshError
6565
from sqlmesh.utils.pydantic import validate_string
6666
from tests.conftest import DuckDBMetadata, SushiDataValidator
6767
from tests.utils.test_helpers import use_terminal_console
@@ -3034,7 +3034,7 @@ def _dates_in_table(table_name: str) -> t.List[str]:
30343034
]
30353035

30363036
# mess with A independently of SQLMesh to prove a whole day gets restated for B instead of just 1hr
3037-
snapshot_table_name = ctx.table_name("test.a", False)
3037+
snapshot_table_name = ctx.table_name("test.a", "dev")
30383038
engine_adapter.execute(
30393039
f"delete from {snapshot_table_name} where cast(ts as date) == '2024-01-01'"
30403040
)
@@ -4094,6 +4094,60 @@ def test_evaluate_uncategorized_snapshot(init_and_plan_context: t.Callable):
40944094
assert set(df["one"].tolist()) == {1}
40954095

40964096

4097+
@time_machine.travel("2023-01-08 15:00:00 UTC")
4098+
def test_table_name(init_and_plan_context: t.Callable):
4099+
context, plan = init_and_plan_context("examples/sushi")
4100+
context.apply(plan)
4101+
4102+
snapshot = context.get_snapshot("sushi.waiter_revenue_by_day")
4103+
assert snapshot
4104+
assert (
4105+
context.table_name("sushi.waiter_revenue_by_day", "prod")
4106+
== f"memory.sqlmesh__sushi.sushi__waiter_revenue_by_day__{snapshot.version}"
4107+
)
4108+
4109+
with pytest.raises(SQLMeshError, match="Environment 'dev' was not found."):
4110+
context.table_name("sushi.waiter_revenue_by_day", "dev")
4111+
4112+
with pytest.raises(
4113+
SQLMeshError, match="Model 'sushi.missing' was not found in environment 'prod'."
4114+
):
4115+
context.table_name("sushi.missing", "prod")
4116+
4117+
# Add a new projection
4118+
model = context.get_model("sushi.waiter_revenue_by_day")
4119+
context.upsert_model(add_projection_to_model(t.cast(SqlModel, model)))
4120+
4121+
context.plan("dev_a", auto_apply=True, no_prompts=True, skip_tests=True)
4122+
4123+
new_snapshot = context.get_snapshot("sushi.waiter_revenue_by_day")
4124+
assert new_snapshot.version != snapshot.version
4125+
4126+
assert (
4127+
context.table_name("sushi.waiter_revenue_by_day", "dev_a")
4128+
== f"memory.sqlmesh__sushi.sushi__waiter_revenue_by_day__{new_snapshot.version}"
4129+
)
4130+
4131+
# Make a forward-only change
4132+
context.upsert_model(model, stamp="forward_only")
4133+
4134+
context.plan("dev_b", auto_apply=True, no_prompts=True, skip_tests=True, forward_only=True)
4135+
4136+
forward_only_snapshot = context.get_snapshot("sushi.waiter_revenue_by_day")
4137+
assert forward_only_snapshot.version == snapshot.version
4138+
assert forward_only_snapshot.dev_version != snapshot.version
4139+
4140+
assert (
4141+
context.table_name("sushi.waiter_revenue_by_day", "dev_b")
4142+
== f"memory.sqlmesh__sushi.sushi__waiter_revenue_by_day__{forward_only_snapshot.dev_version}__dev"
4143+
)
4144+
4145+
assert (
4146+
context.table_name("sushi.waiter_revenue_by_day", "dev_b", prod=True)
4147+
== f"memory.sqlmesh__sushi.sushi__waiter_revenue_by_day__{snapshot.version}"
4148+
)
4149+
4150+
40974151
@time_machine.travel("2023-01-08 15:00:00 UTC")
40984152
def test_dbt_requirements(sushi_dbt_context: Context):
40994153
assert set(sushi_dbt_context.requirements) == {"dbt-core", "dbt-duckdb"}

0 commit comments

Comments
 (0)