Repository: TobikoData/sqlmesh
Pull request #2524 (2024-04-26 17:23:49)

Fix: force-lowercase dialect names, normalize macro columns_to_types
This fixes a couple of bugs I came across while trying to get the sushi project to run in snowflake.


Patch:
diff --git a/sqlmesh/core/macros.py b/sqlmesh/core/macros.py
index 0d0ee8b5..844250ab 100644
--- a/sqlmesh/core/macros.py
+++ b/sqlmesh/core/macros.py
@@ -402,14 +402,12 @@ class MacroEvaluator:
             self.columns_to_types_called = True
             return {"__schema_unavailable_at_load__": exp.DataType.build("unknown")}
 
-        if isinstance(model_name, exp.Column):
-            model_name = exp.table_(
-                model_name.this,
-                db=model_name.args.get("table"),
-                catalog=model_name.args.get("db"),
-            )
-
-        columns_to_types = self._schema.find(exp.to_table(model_name))
+        normalized_model_name = normalize_model_name(
+            model_name,
+            default_catalog=self.default_catalog,
+            dialect=self.dialect,
+        )
+        columns_to_types = self._schema.find(exp.to_table(normalized_model_name))
         if columns_to_types is None:
             raise SQLMeshError(f"Schema for model '{model_name}' can't be statically determined.")
 
diff --git a/sqlmesh/core/model/meta.py b/sqlmesh/core/model/meta.py
index e04ee4f5..5d69ab16 100644
--- a/sqlmesh/core/model/meta.py
+++ b/sqlmesh/core/model/meta.py
@@ -144,10 +144,17 @@ class ModelMeta(_Node):
 
         return v
 
-    @field_validator("dialect", "storage_format", mode="before")
-    def _string_validator(cls, v: t.Any) -> t.Optional[str]:
+    @field_validator("storage_format", mode="before")
+    def _storage_format_validator(cls, v: t.Any) -> t.Optional[str]:
         return str_or_exp_to_str(v)
 
+    @field_validator("dialect", mode="before")
+    def _dialect_validator(cls, v: t.Any) -> t.Optional[str]:
+        # dialects are parsed as identifiers and may get normalized as uppercase,
+        # so this ensures they'll be stored as lowercase
+        dialect = str_or_exp_to_str(v)
+        return dialect and dialect.lower()
+
     @field_validator("partitioned_by_", mode="before")
     @field_validator_v1_args
     def _partition_by_validator(
diff --git a/sqlmesh/core/renderer.py b/sqlmesh/core/renderer.py
index 77decd1d..cebc10cd 100644
--- a/sqlmesh/core/renderer.py
+++ b/sqlmesh/core/renderer.py
@@ -100,10 +100,17 @@ class BaseExpressionRenderer:
             return self._cache
 
         if self._model_fqn and "this_model" not in kwargs:
-            kwargs["this_model"] = self._to_table_mapping(
-                [snapshots[self._model_fqn]] if snapshots and self._model_fqn in snapshots else [],
-                deployability_index,
-            ).get(self._model_fqn, self._model_fqn)
+            kwargs["this_model"] = exp.to_table(
+                self._to_table_mapping(
+                    (
+                        [snapshots[self._model_fqn]]
+                        if snapshots and self._model_fqn in snapshots
+                        else []
+                    ),
+                    deployability_index,
+                ).get(self._model_fqn, self._model_fqn),
+                dialect=self._dialect,
+            ).sql(dialect=self._dialect, identify=True)
 
         expressions = [self._expression]
 


Test patch:
diff --git a/tests/core/test_model.py b/tests/core/test_model.py
index bf936a56..70729d83 100644
--- a/tests/core/test_model.py
+++ b/tests/core/test_model.py
@@ -1802,6 +1802,12 @@ def test_case_sensitivity(assert_exp_eq):
         """,
     )
 
+    model = load_sql_based_model(
+        d.parse("MODEL (name test, dialect hive); SELECT 1 AS c"),
+        dialect="snowflake",
+    )
+    assert model.dialect == "hive"
+
 
 def test_batch_size_validation():
     expressions = d.parse(
@@ -3814,7 +3820,8 @@ def test_this_model() -> None:
     expressions = d.parse(
         """
         MODEL (
-            name db.table,
+            name `project-1.table`,
+            dialect bigquery,
         );
 
         JINJA_STATEMENT_BEGIN;
@@ -3832,18 +3839,38 @@ def test_this_model() -> None:
     )
     model = load_sql_based_model(expressions)
 
-    assert model.render_query_or_raise().sql() == '''SELECT '"db"."table"' AS "x"'''
+    assert (
+        model.render_query_or_raise().sql(dialect="bigquery")
+        == """SELECT '`project-1`.`table`' AS `x`"""
+    )
 
-    assert model.render_pre_statements()[0].sql() == """COPY "db"."table" TO 'a'"""
-    assert model.render_post_statements()[0].sql() == """COPY "db"."table" TO 'b'"""
+    assert (
+        model.render_pre_statements()[0].sql(dialect="bigquery")
+        == """COPY `project-1`.`table` TO 'a'"""
+    )
+    assert (
+        model.render_post_statements()[0].sql(dialect="bigquery")
+        == """COPY `project-1`.`table` TO 'b'"""
+    )
 
     snapshot = Snapshot.from_node(model, nodes={})
+
+    assert (
+        model.render_query_or_raise(
+            start="2020-01-01",
+            snapshots={snapshot.name: snapshot},
+        ).sql(dialect="bigquery")
+        == """SELECT '`project-1`.`table`' AS `x`"""
+    )
+
+    snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
+
     assert (
         model.render_query_or_raise(
             start="2020-01-01",
             snapshots={snapshot.name: snapshot},
-        ).sql()
-        == '''SELECT '"db"."table"' AS "x"'''
+        ).sql(dialect="bigquery")
+        == "SELECT '`sqlmesh__project-1`.`project_1__table__1178373199`' AS `x`"
     )