@@ -765,6 +765,54 @@ def test_create_only_dev_table_exists(mocker: MockerFixture, adapter_mock, make_
765765 )
766766
767767
768+ def test_create_new_forward_only_model (mocker : MockerFixture , adapter_mock , make_snapshot ):
769+ model = load_sql_based_model (
770+ parse ( # type: ignore
771+ """
772+ MODEL (
773+ name test_schema.test_model,
774+ kind INCREMENTAL_BY_TIME_RANGE (
775+ time_column ds,
776+ forward_only true,
777+ )
778+ );
779+
780+ SELECT a::int, '2024-01-01' as ds FROM tbl;
781+ """
782+ ),
783+ )
784+
785+ snapshot = make_snapshot (model )
786+ snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
787+
788+ adapter_mock .get_data_objects .return_value = []
789+ adapter_mock .table_exists .return_value = False
790+ evaluator = SnapshotEvaluator (adapter_mock )
791+
792+ evaluator .create ([snapshot ], {}, deployability_index = DeployabilityIndex .none_deployable ())
793+ adapter_mock .create_schema .assert_called_once_with (to_schema ("sqlmesh__test_schema" ))
794+ # Only non-deployable table should be created
795+ adapter_mock .create_table .assert_called_once_with (
796+ f"sqlmesh__test_schema.test_schema__test_model__{ snapshot .temp_version_get_or_generate ()} __temp" ,
797+ columns_to_types = {"a" : exp .DataType .build ("int" ), "ds" : exp .DataType .build ("varchar" )},
798+ table_format = None ,
799+ storage_format = None ,
800+ partitioned_by = model .partitioned_by ,
801+ partition_interval_unit = model .partition_interval_unit ,
802+ clustered_by = [],
803+ table_properties = {},
804+ table_description = None ,
805+ column_descriptions = None ,
806+ )
807+ adapter_mock .get_data_objects .assert_called_once_with (
808+ schema_ ("sqlmesh__test_schema" ),
809+ {
810+ f"test_schema__test_model__{ snapshot .version } " ,
811+ f"test_schema__test_model__{ snapshot .temp_version_get_or_generate ()} __temp" ,
812+ },
813+ )
814+
815+
768816@pytest .mark .parametrize (
769817 "deployability_index, snapshot_category, deployability_flags" ,
770818 [
@@ -1122,6 +1170,7 @@ def columns(table_name):
11221170 }
11231171
11241172 adapter .columns = columns # type: ignore
1173+ adapter .table_exists = lambda _ : True # type: ignore
11251174
11261175 evaluator = SnapshotEvaluator (adapter )
11271176
@@ -1148,6 +1197,42 @@ def columns(table_name):
11481197 )
11491198
11501199
1200+ def test_migrate_missing_table (mocker : MockerFixture , make_snapshot ):
1201+ connection_mock = mocker .NonCallableMock ()
1202+ cursor_mock = mocker .Mock ()
1203+ connection_mock .cursor .return_value = cursor_mock
1204+ adapter = EngineAdapter (lambda : connection_mock , "" )
1205+
1206+ adapter .table_exists = lambda _ : False # type: ignore
1207+
1208+ evaluator = SnapshotEvaluator (adapter )
1209+
1210+ model = SqlModel (
1211+ name = "test_schema.test_model" ,
1212+ kind = IncrementalByTimeRangeKind (
1213+ time_column = "a" , on_destructive_change = OnDestructiveChange .ALLOW
1214+ ),
1215+ storage_format = "parquet" ,
1216+ query = parse_one ("SELECT c, a FROM tbl WHERE ds BETWEEN @start_ds and @end_ds" ),
1217+ pre_statements = [parse_one ("CREATE TABLE pre (a INT)" )],
1218+ post_statements = [parse_one ("DROP TABLE pre" )],
1219+ )
1220+ snapshot = make_snapshot (model , version = "1" )
1221+ snapshot .change_category = SnapshotChangeCategory .FORWARD_ONLY
1222+
1223+ evaluator .migrate ([snapshot ], {})
1224+
1225+ cursor_mock .execute .assert_has_calls (
1226+ [
1227+ call ('CREATE TABLE "pre" ("a" INT)' ),
1228+ call (
1229+ 'CREATE TABLE IF NOT EXISTS "sqlmesh__test_schema"."test_schema__test_model__1" AS SELECT "c" AS "c", "a" AS "a" FROM "tbl" AS "tbl" WHERE "ds" BETWEEN \' 1970-01-01\' AND \' 1970-01-01\' AND FALSE LIMIT 0'
1230+ ),
1231+ call ('DROP TABLE "pre"' ),
1232+ ]
1233+ )
1234+
1235+
11511236@pytest .mark .parametrize (
11521237 "change_category" ,
11531238 [SnapshotChangeCategory .FORWARD_ONLY , SnapshotChangeCategory .INDIRECT_NON_BREAKING ],
@@ -1386,6 +1471,14 @@ def test_create_clone_in_dev(mocker: MockerFixture, adapter_mock, make_snapshot)
13861471 snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
13871472 snapshot .previous_versions = snapshot .all_versions
13881473
1474+ adapter_mock .get_data_objects .return_value = [
1475+ DataObject (
1476+ name = f"test_schema__test_model__{ snapshot .version } " ,
1477+ schema = "sqlmesh__test_schema" ,
1478+ type = DataObjectType .TABLE ,
1479+ ),
1480+ ]
1481+
13891482 evaluator .create ([snapshot ], {})
13901483
13911484 adapter_mock .create_table .assert_called_once_with (
@@ -1419,6 +1512,50 @@ def test_create_clone_in_dev(mocker: MockerFixture, adapter_mock, make_snapshot)
14191512 )
14201513
14211514
1515+ def test_create_clone_in_dev_missing_table (mocker : MockerFixture , adapter_mock , make_snapshot ):
1516+ adapter_mock .SUPPORTS_CLONING = True
1517+ adapter_mock .get_alter_expressions .return_value = []
1518+ evaluator = SnapshotEvaluator (adapter_mock )
1519+
1520+ model = load_sql_based_model (
1521+ parse ( # type: ignore
1522+ """
1523+ MODEL (
1524+ name test_schema.test_model,
1525+ kind INCREMENTAL_BY_TIME_RANGE (
1526+ time_column ds,
1527+ forward_only true,
1528+ )
1529+ );
1530+
1531+ SELECT 1::INT as a, ds::DATE FROM a;
1532+ """
1533+ ),
1534+ )
1535+
1536+ snapshot = make_snapshot (model )
1537+ snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
1538+ snapshot .previous_versions = snapshot .all_versions
1539+
1540+ evaluator .create ([snapshot ], {}, deployability_index = DeployabilityIndex .none_deployable ())
1541+
1542+ adapter_mock .create_table .assert_called_once_with (
1543+ f"sqlmesh__test_schema.test_schema__test_model__{ snapshot .temp_version_get_or_generate ()} __temp" ,
1544+ columns_to_types = {"a" : exp .DataType .build ("int" ), "ds" : exp .DataType .build ("date" )},
1545+ table_format = None ,
1546+ storage_format = None ,
1547+ partitioned_by = [exp .to_column ("ds" , quoted = True )],
1548+ partition_interval_unit = IntervalUnit .DAY ,
1549+ clustered_by = [],
1550+ table_properties = {},
1551+ table_description = None ,
1552+ column_descriptions = None ,
1553+ )
1554+
1555+ adapter_mock .clone_table .assert_not_called ()
1556+ adapter_mock .alter_table .assert_not_called ()
1557+
1558+
14221559def test_drop_clone_in_dev_when_migration_fails (mocker : MockerFixture , adapter_mock , make_snapshot ):
14231560 adapter_mock .SUPPORTS_CLONING = True
14241561 adapter_mock .get_alter_expressions .return_value = []
@@ -1445,6 +1582,14 @@ def test_drop_clone_in_dev_when_migration_fails(mocker: MockerFixture, adapter_m
14451582 snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
14461583 snapshot .previous_versions = snapshot .all_versions
14471584
1585+ adapter_mock .get_data_objects .return_value = [
1586+ DataObject (
1587+ name = f"test_schema__test_model__{ snapshot .version } " ,
1588+ schema = "sqlmesh__test_schema" ,
1589+ type = DataObjectType .TABLE ,
1590+ ),
1591+ ]
1592+
14481593 evaluator .create ([snapshot ], {})
14491594
14501595 adapter_mock .clone_table .assert_called_once_with (
@@ -1494,6 +1639,14 @@ def test_create_clone_in_dev_self_referencing(mocker: MockerFixture, adapter_moc
14941639 snapshot .categorize_as (SnapshotChangeCategory .FORWARD_ONLY )
14951640 snapshot .previous_versions = snapshot .all_versions
14961641
1642+ adapter_mock .get_data_objects .return_value = [
1643+ DataObject (
1644+ name = f"test_schema__test_model__{ snapshot .version } " ,
1645+ schema = "sqlmesh__test_schema" ,
1646+ type = DataObjectType .TABLE ,
1647+ ),
1648+ ]
1649+
14971650 evaluator .create ([snapshot ], {})
14981651
14991652 adapter_mock .create_table .assert_called_once_with (
0 commit comments