Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sqlmesh/core/config/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ def get_default_catalog_per_gateway(self, context: GenericContext) -> t.Dict[str
for gateway, adapter in context.engine_adapters.items():
if catalog := adapter.default_catalog:
default_catalogs_per_gateway[gateway] = catalog
elif adapter.catalog_support.is_unsupported:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will leak the empty string "" into downstream operations as actual catalog value. None is already the representation for no catalog in most code paths, so the fix should be in definition.py instead, so take a look and adapt it to be similar to the option b suggested in the original issue

default_catalogs_per_gateway[gateway] = ""
return default_catalogs_per_gateway


Expand Down
64 changes: 64 additions & 0 deletions tests/core/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7186,6 +7186,70 @@ def test_gateway_macro_jinja() -> None:
assert model.render_query_or_raise().sql() == "SELECT 'in_memory' AS \"gateway_jinja\""


def test_default_catalog_not_leaked_to_unsupported_gateway() -> None:
"""Regression test for https://github.com/SQLMesh/sqlmesh/issues/5748.

When a model targets a gateway that does not support catalogs (e.g. ClickHouse),
the default gateway's catalog must not leak into the model's FQN. The scheduler
registers such gateways with empty string in default_catalog_per_gateway, and
the model loader should use that to override the global default_catalog.
"""
expressions = d.parse(
"""
MODEL (
name my_schema.my_model,
kind VIEW,
gateway clickhouse_gw,
dialect clickhouse,
);
SELECT 1 AS id
"""
)

# Simulate a multi-gateway setup: default gateway has catalog "example_catalog",
# but clickhouse_gw is catalog-unsupported (registered with empty string).
models = load_sql_based_models(
expressions,
get_variables=lambda gw: {},
dialect="clickhouse",
default_catalog="example_catalog",
default_catalog_per_gateway={
"default_gw": "example_catalog",
"clickhouse_gw": "",
},
)

assert len(models) == 1
model_result = models[0]
# The catalog should be empty — not the leaked "example_catalog"
assert model_result.catalog != "example_catalog"
# The FQN should be a two-part name, not three-part with the leaked catalog
assert "example_catalog" not in model_result.fqn

# Verify the positive case: without the per-gateway override, catalog leaks
models_leaked = load_sql_based_models(
d.parse(
"""
MODEL (
name my_schema.my_model2,
kind VIEW,
gateway clickhouse_gw,
dialect clickhouse,
);
SELECT 1 AS id
"""
),
get_variables=lambda gw: {},
dialect="clickhouse",
default_catalog="example_catalog",
# No entry for clickhouse_gw — catalog will leak
default_catalog_per_gateway={"default_gw": "example_catalog"},
)
assert len(models_leaked) == 1
# Without the fix, example_catalog leaks into the model name
assert "example_catalog" in models_leaked[0].fqn


def test_gateway_python_model(mocker: MockerFixture) -> None:
@model(
"test_gateway_python_model",
Expand Down
Loading