Skip to content

Commit 8fe0816

Browse files
authored
Merge branch 'main' into fix/databricks-oauth-shared-connection
2 parents 7fdd83e + c787415 commit 8fe0816

File tree

2 files changed

+178
-6
lines changed

2 files changed

+178
-6
lines changed

sqlmesh/core/model/definition.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,9 @@ def create_models_from_blueprints(
20652065
**loader_kwargs: t.Any,
20662066
) -> t.List[Model]:
20672067
model_blueprints: t.List[Model] = []
2068+
original_default_catalog = loader_kwargs.get("default_catalog")
20682069
for blueprint in _extract_blueprints(blueprints, path):
2070+
loader_kwargs["default_catalog"] = original_default_catalog
20692071
blueprint_variables = _extract_blueprint_variables(blueprint, path)
20702072

20712073
if gateway:
@@ -2083,12 +2085,15 @@ def create_models_from_blueprints(
20832085
else:
20842086
gateway_name = None
20852087

2086-
if (
2087-
default_catalog_per_gateway
2088-
and gateway_name
2089-
and (catalog := default_catalog_per_gateway.get(gateway_name)) is not None
2090-
):
2091-
loader_kwargs["default_catalog"] = catalog
2088+
if default_catalog_per_gateway and gateway_name:
2089+
catalog = default_catalog_per_gateway.get(gateway_name)
2090+
if catalog is not None:
2091+
loader_kwargs["default_catalog"] = catalog
2092+
else:
2093+
# Gateway exists but has no entry in the dict (e.g., catalog-unsupported
2094+
# engines like ClickHouse). Clear the default catalog so the global
2095+
# default from the primary gateway doesn't leak into this model's name.
2096+
loader_kwargs["default_catalog"] = None
20922097

20932098
model_blueprints.append(
20942099
loader(

tests/core/test_model.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12342,3 +12342,170 @@ def test_audits_in_embedded_model():
1234212342
)
1234312343
with pytest.raises(ConfigError, match="Audits are not supported for embedded models"):
1234412344
load_sql_based_model(expression).validate_definition()
12345+
12346+
12347+
def test_default_catalog_not_leaked_to_unsupported_gateway():
12348+
"""
12349+
Regression test for https://github.com/SQLMesh/sqlmesh/issues/5748
12350+
12351+
When a model targets a gateway that is NOT in default_catalog_per_gateway,
12352+
the global default_catalog should be cleared (set to None) instead of
12353+
leaking through from the default gateway.
12354+
"""
12355+
from sqlglot import parse
12356+
12357+
expressions = parse(
12358+
"""
12359+
MODEL (
12360+
name my_schema.my_model,
12361+
kind FULL,
12362+
gateway clickhouse_gw,
12363+
dialect clickhouse,
12364+
);
12365+
12366+
SELECT 1 AS id
12367+
""",
12368+
read="clickhouse",
12369+
)
12370+
12371+
default_catalog_per_gateway = {
12372+
"default_gw": "example_catalog",
12373+
}
12374+
12375+
models = load_sql_based_models(
12376+
expressions,
12377+
get_variables=lambda gw: {},
12378+
dialect="clickhouse",
12379+
default_catalog_per_gateway=default_catalog_per_gateway,
12380+
default_catalog="example_catalog",
12381+
)
12382+
12383+
assert len(models) == 1
12384+
model = models[0]
12385+
12386+
assert not model.catalog, (
12387+
f"Default gateway catalog leaked into catalog-unsupported gateway model. "
12388+
f"Expected no catalog, got: {model.catalog}"
12389+
)
12390+
assert "example_catalog" not in model.fqn, (
12391+
f"Default gateway catalog found in model FQN: {model.fqn}"
12392+
)
12393+
12394+
12395+
def test_default_catalog_still_applied_to_supported_gateway():
12396+
"""
12397+
Control test: when a model targets a gateway that IS in default_catalog_per_gateway,
12398+
the catalog should still be correctly applied.
12399+
"""
12400+
from sqlglot import parse
12401+
12402+
expressions = parse(
12403+
"""
12404+
MODEL (
12405+
name my_schema.my_model,
12406+
kind FULL,
12407+
gateway other_duckdb,
12408+
);
12409+
12410+
SELECT 1 AS id
12411+
""",
12412+
read="duckdb",
12413+
)
12414+
12415+
default_catalog_per_gateway = {
12416+
"default_gw": "example_catalog",
12417+
"other_duckdb": "other_db",
12418+
}
12419+
12420+
models = load_sql_based_models(
12421+
expressions,
12422+
get_variables=lambda gw: {},
12423+
dialect="duckdb",
12424+
default_catalog_per_gateway=default_catalog_per_gateway,
12425+
default_catalog="example_catalog",
12426+
)
12427+
12428+
assert len(models) == 1
12429+
model = models[0]
12430+
12431+
assert model.catalog == "other_db", f"Expected catalog 'other_db', got: {model.catalog}"
12432+
12433+
12434+
def test_no_gateway_uses_global_default_catalog():
12435+
"""
12436+
Control test: when a model does NOT specify a gateway, the global
12437+
default_catalog should still be applied as before.
12438+
"""
12439+
from sqlglot import parse
12440+
12441+
expressions = parse(
12442+
"""
12443+
MODEL (
12444+
name my_schema.my_model,
12445+
kind FULL,
12446+
);
12447+
12448+
SELECT 1 AS id
12449+
""",
12450+
read="duckdb",
12451+
)
12452+
12453+
model = load_sql_based_model(
12454+
expressions,
12455+
default_catalog="example_catalog",
12456+
dialect="duckdb",
12457+
)
12458+
12459+
assert model.catalog == "example_catalog"
12460+
12461+
12462+
def test_blueprint_catalog_not_cross_contaminated():
12463+
"""
12464+
When blueprints iterate over different gateways, the catalog from one
12465+
blueprint iteration should not leak into the next. A ClickHouse blueprint
12466+
setting default_catalog to None should not prevent the following blueprint
12467+
from getting its correct catalog.
12468+
"""
12469+
from sqlglot import parse
12470+
12471+
expressions = parse(
12472+
"""
12473+
MODEL (
12474+
name @{blueprint}.my_model,
12475+
kind FULL,
12476+
gateway @{gw},
12477+
blueprints (
12478+
(blueprint := ch_schema, gw := clickhouse_gw),
12479+
(blueprint := db_schema, gw := default_gw),
12480+
),
12481+
);
12482+
12483+
SELECT 1 AS id
12484+
""",
12485+
read="duckdb",
12486+
)
12487+
12488+
default_catalog_per_gateway = {
12489+
"default_gw": "example_catalog",
12490+
}
12491+
12492+
models = load_sql_based_models(
12493+
expressions,
12494+
get_variables=lambda gw: {},
12495+
dialect="duckdb",
12496+
default_catalog_per_gateway=default_catalog_per_gateway,
12497+
default_catalog="example_catalog",
12498+
)
12499+
12500+
assert len(models) == 2
12501+
12502+
ch_model = next(m for m in models if "ch_schema" in m.fqn)
12503+
db_model = next(m for m in models if "db_schema" in m.fqn)
12504+
12505+
assert not ch_model.catalog, (
12506+
f"Catalog leaked into ClickHouse blueprint. Got: {ch_model.catalog}"
12507+
)
12508+
12509+
assert db_model.catalog == "example_catalog", (
12510+
f"Catalog lost for DuckDB blueprint after ClickHouse iteration. Got: {db_model.catalog}"
12511+
)

0 commit comments

Comments
 (0)