Repository: TobikoData/sqlmesh
Pull request #4654 (2025-06-04 15:35:52)

Chore: detect xdb diff syntax and raise early
Behavior in main:

```
((.venv) ) ➜  sqlmesh table_diff 'mssql|sales.fact_sales:databricks|george.sales.fact_sales' --on fact_id --show-sample
Error: Failed to parse 'mssql|sales.fact_sales' into <class 'sqlglot.expressions.Table'>
```

Behavior in this PR:

```
((.venv) ) ➜  sqlmesh table_diff 'mssql|sales.fact_sales:databricks|george.sales.fact_sales' --on fact_id --show-sample
Error: Cross-database table diffing is available in Tobiko Cloud. Read more here: https://sqlmesh.readthedocs.io/en/stable/guides/tablediff/#diffing-tables-or-views-across-gateways
```

Additionally, I've updated the [docs](https://sqlmesh.readthedocs.io/en/stable/guides/tablediff/#configuration-and-syntax) so that `tcloud sqlmesh` is used instead of `sqlmesh`, as the latter does not route to the correct xdb diffing logic.

cc @erindru @afzaljasani


Patch:
diff --git a/docs/guides/tablediff.md b/docs/guides/tablediff.md
index b9362c11..d4ed61e0 100644
--- a/docs/guides/tablediff.md
+++ b/docs/guides/tablediff.md
@@ -253,12 +253,12 @@ Then, specify each table's gateway in the `table_diff` command with this syntax:
 For example, we could diff the `landing.table` table across `bigquery` and `snowflake` gateways like this:
 
 ```sh
-$ sqlmesh table_diff 'bigquery|landing.table:snowflake|lake.table'
+$ tcloud sqlmesh table_diff 'bigquery|landing.table:snowflake|lake.table'
 ```
 
 This syntax tells SQLMesh to use the cross-database diffing algorithm instead of the normal within-database diffing algorithm.
 
-After adding gateways to the table names, use `table_diff` as described above - the same options apply for specifying the join keys, decimal precision, etc. See `sqlmesh table_diff --help` for a [full list of options](../reference/cli.md#table_diff).
+After adding gateways to the table names, use `table_diff` as described above - the same options apply for specifying the join keys, decimal precision, etc. See `tcloud sqlmesh table_diff --help` for a [full list of options](../reference/cli.md#table_diff).
 
 !!! warning
 
@@ -273,7 +273,7 @@ A cross-database diff is broken up into two stages.
 The first stage is a schema diff. This example shows that differences in column name case across the two tables are identified as schema differences:
 
 ```bash
-$ sqlmesh table_diff 'bigquery|sqlmesh_example.full_model:snowflake|sqlmesh_example.full_model' --on item_id --show-sample
+$ tcloud sqlmesh table_diff 'bigquery|sqlmesh_example.full_model:snowflake|sqlmesh_example.full_model' --on item_id --show-sample
 
 Schema Diff Between 'BIGQUERY|SQLMESH_EXAMPLE.FULL_MODEL' and 'SNOWFLAKE|SQLMESH_EXAMPLE.FULL_MODEL':
 ├── Added Columns:
diff --git a/sqlmesh/core/context.py b/sqlmesh/core/context.py
index 1bc12767..3dc77ea2 100644
--- a/sqlmesh/core/context.py
+++ b/sqlmesh/core/context.py
@@ -1677,6 +1677,12 @@ class GenericContext(BaseContext, t.Generic[C]):
             The list of TableDiff objects containing schema and summary differences.
         """
 
+        if "|" in source or "|" in target:
+            raise ConfigError(
+                "Cross-database table diffing is available in Tobiko Cloud. Read more here: "
+                "https://sqlmesh.readthedocs.io/en/stable/guides/tablediff/#diffing-tables-or-views-across-gateways"
+            )
+
         table_diffs: t.List[TableDiff] = []
 
         # Diffs multiple or a single model across two environments
diff --git a/sqlmesh/core/macros.py b/sqlmesh/core/macros.py
index db530481..d573fdac 100644
--- a/sqlmesh/core/macros.py
+++ b/sqlmesh/core/macros.py
@@ -1452,6 +1452,26 @@ def _coerce(
         if base is SQL and isinstance(expr, exp.Expression):
             return expr.sql(dialect)
 
+        if base is t.Literal:
+            if not isinstance(expr, (exp.Literal, exp.Boolean)):
+                raise SQLMeshError(
+                    f"{base_err_msg} Coercion to {base} requires a literal expression."
+                )
+            literal_type_args = t.get_args(typ)
+            try:
+                for literal_type_arg in literal_type_args:
+                    expr_is_bool = isinstance(expr.this, bool)
+                    literal_is_bool = isinstance(literal_type_arg, bool)
+                    if (expr_is_bool and literal_is_bool and literal_type_arg == expr.this) or (
+                        not expr_is_bool
+                        and not literal_is_bool
+                        and str(literal_type_arg) == str(expr.this)
+                    ):
+                        return type(literal_type_arg)(expr.this)
+            except Exception:
+                raise SQLMeshError(base_err_msg)
+            raise SQLMeshError(base_err_msg)
+
         if isinstance(expr, base):
             return expr
         if issubclass(base, exp.Expression):


Test patch:
diff --git a/tests/core/test_macros.py b/tests/core/test_macros.py
index 5b79d5ff..7f4a417c 100644
--- a/tests/core/test_macros.py
+++ b/tests/core/test_macros.py
@@ -96,6 +96,12 @@ def macro_evaluator() -> MacroEvaluator:
     def test_select_macro(evaluator):
         return "SELECT 1 AS col"
 
+    @macro()
+    def test_literal_type(evaluator, a: t.Literal["test_literal_a", "test_literal_b", 1, True]):
+        if isinstance(a, exp.Expression):
+            raise SQLMeshError("Coercion failed")
+        return f"'{a}'"
+
     return MacroEvaluator(
         "hive",
         {"test": Executable(name="test", payload="def test(_):\n    return 'test'")},
@@ -1087,3 +1093,33 @@ def test_macro_with_spaces():
         ("d.@z", 'd.a."b c"'),
     ):
         assert evaluator.transform(parse_one(sql)).sql() == expected
+
+
+def test_macro_coerce_literal_type(macro_evaluator):
+    expression = d.parse_one("@TEST_LITERAL_TYPE('test_literal_a')")
+    assert macro_evaluator.transform(expression).sql() == "'test_literal_a'"
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE('test_literal_b')")
+    assert macro_evaluator.transform(expression).sql() == "'test_literal_b'"
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE(1)")
+    assert macro_evaluator.transform(expression).sql() == "'1'"
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE(True)")
+    assert macro_evaluator.transform(expression).sql() == "'True'"
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE('test_literal_c')")
+    with pytest.raises(MacroEvalError, match=".*Coercion failed"):
+        macro_evaluator.transform(expression)
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE(2)")
+    with pytest.raises(MacroEvalError, match=".*Coercion failed"):
+        macro_evaluator.transform(expression)
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE(False)")
+    with pytest.raises(MacroEvalError, match=".*Coercion failed"):
+        macro_evaluator.transform(expression)
+
+    expression = d.parse_one("@TEST_LITERAL_TYPE(1.0)")
+    with pytest.raises(MacroEvalError, match=".*Coercion failed"):
+        macro_evaluator.transform(expression)