Skip to content

Commit bdff229

Browse files
Fix(snowflake): Support cloning transient tables for dev previews (#4155)
1 parent 151da8c commit bdff229

5 files changed

Lines changed: 64 additions & 1 deletion

File tree

sqlmesh/core/engine_adapter/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,8 @@ def clone_table(
890890
"""
891891
if not self.SUPPORTS_CLONING:
892892
raise NotImplementedError(f"Engine does not support cloning: {type(self)}")
893+
894+
kwargs.pop("rendered_physical_properties", None)
893895
self.execute(
894896
exp.Create(
895897
this=exp.to_table(target_table_name),

sqlmesh/core/engine_adapter/snowflake.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,25 @@ def _create_column_comments(
469469
f"Column comments for table '{table.alias_or_name}' not registered - this may be due to limited permissions.",
470470
exc_info=True,
471471
)
472+
473+
def clone_table(
474+
self,
475+
target_table_name: TableName,
476+
source_table_name: TableName,
477+
replace: bool = False,
478+
clone_kwargs: t.Optional[t.Dict[str, t.Any]] = None,
479+
**kwargs: t.Any,
480+
) -> None:
481+
# The Snowflake adapter should use the transient property to clone transient tables
482+
if physical_properties := kwargs.get("rendered_physical_properties"):
483+
table_type = self._pop_creatable_type_from_properties(physical_properties)
484+
if isinstance(table_type, exp.TransientProperty):
485+
kwargs["properties"] = exp.Properties(expressions=[table_type])
486+
487+
super().clone_table(
488+
target_table_name,
489+
source_table_name,
490+
replace=replace,
491+
clone_kwargs=clone_kwargs,
492+
**kwargs,
493+
)

sqlmesh/core/snapshot/evaluator.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,12 @@ def _create_snapshot(
830830
)
831831

832832
try:
833-
adapter.clone_table(target_table_name, snapshot.table_name(), replace=True)
833+
adapter.clone_table(
834+
target_table_name,
835+
snapshot.table_name(),
836+
replace=True,
837+
rendered_physical_properties=rendered_physical_properties,
838+
)
834839
alter_expressions = adapter.get_alter_expressions(
835840
target_table_name, tmp_table_name
836841
)

tests/core/engine_adapter/test_snowflake.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import sqlmesh.core.dialect as d
99
from sqlmesh.core.dialect import normalize_model_name
10+
from sqlmesh.core.engine_adapter.base import EngineAdapter
1011
from sqlmesh.core.model import load_sql_based_model
1112
from sqlmesh.core.engine_adapter import SnowflakeEngineAdapter
1213
from sqlmesh.core.model.definition import SqlModel
@@ -634,3 +635,34 @@ def test_create_view(make_mocked_engine_adapter: t.Callable):
634635
'CREATE OR REPLACE VIEW "test_view" COPY GRANTS AS SELECT 1',
635636
'CREATE VIEW "test_view" AS SELECT 1',
636637
]
638+
639+
640+
def test_clone_table(mocker: MockerFixture, make_mocked_engine_adapter: t.Callable):
641+
mocker.patch("sqlmesh.core.engine_adapter.snowflake.SnowflakeEngineAdapter.set_current_catalog")
642+
adapter = make_mocked_engine_adapter(SnowflakeEngineAdapter, default_catalog="test_catalog")
643+
adapter.clone_table("target_table", "source_table")
644+
adapter.cursor.execute.assert_called_once_with(
645+
'CREATE TABLE "target_table" CLONE "source_table"'
646+
)
647+
648+
# Validate with transient type we create the clone table accordingly
649+
rendered_physical_properties = {
650+
"creatable_type": exp.column("transient"),
651+
}
652+
adapter = make_mocked_engine_adapter(SnowflakeEngineAdapter, default_catalog="test_catalog")
653+
adapter.clone_table(
654+
"target_table", "source_table", rendered_physical_properties=rendered_physical_properties
655+
)
656+
adapter.cursor.execute.assert_called_once_with(
657+
'CREATE TRANSIENT TABLE "target_table" CLONE "source_table"'
658+
)
659+
660+
# Validate other engine adapters would work as usual even when we pass the properties
661+
adapter = make_mocked_engine_adapter(EngineAdapter, default_catalog="test_catalog")
662+
adapter.SUPPORTS_CLONING = True
663+
adapter.clone_table(
664+
"target_table", "source_table", rendered_physical_properties=rendered_physical_properties
665+
)
666+
adapter.cursor.execute.assert_called_once_with(
667+
'CREATE TABLE "target_table" CLONE "source_table"'
668+
)

tests/core/test_snapshot_evaluator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,7 @@ def test_create_clone_in_dev(mocker: MockerFixture, adapter_mock, make_snapshot)
15031503
f"sqlmesh__test_schema.test_schema__test_model__{snapshot.version}__dev",
15041504
f"sqlmesh__test_schema.test_schema__test_model__{snapshot.version}",
15051505
replace=True,
1506+
rendered_physical_properties={},
15061507
)
15071508

15081509
adapter_mock.get_alter_expressions.assert_called_once_with(
@@ -1601,6 +1602,7 @@ def test_drop_clone_in_dev_when_migration_fails(mocker: MockerFixture, adapter_m
16011602
f"sqlmesh__test_schema.test_schema__test_model__{snapshot.version}__dev",
16021603
f"sqlmesh__test_schema.test_schema__test_model__{snapshot.version}",
16031604
replace=True,
1605+
rendered_physical_properties={},
16041606
)
16051607

16061608
adapter_mock.get_alter_expressions.assert_called_once_with(

0 commit comments

Comments
 (0)