Skip to content

fix: prevent default catalog leak into catalog-unsupported gateways#5752

Merged
themisvaltinos merged 1 commit intoSQLMesh:mainfrom
mday-io:mday-io/fix/prevent-default-catalog-leak-to-unsupported-gateways
Mar 30, 2026
Merged

fix: prevent default catalog leak into catalog-unsupported gateways#5752
themisvaltinos merged 1 commit intoSQLMesh:mainfrom
mday-io:mday-io/fix/prevent-default-catalog-leak-to-unsupported-gateways

Conversation

@mday-io
Copy link
Copy Markdown
Contributor

@mday-io mday-io commented Mar 30, 2026

Description:

When a model's gateway is not in default_catalog_per_gateway, explicitly sets default_catalog to None instead of letting the global default leak through. Implements the fix in definition.py as suggested by @themisvaltinos in #5750 (comment).

Test Plan:

  • 3 new regression tests in tests/core/test_catalog_leak.py
  • Existing gateway tests pass: test_gateway_macro, test_gateway_macro_jinja, test_gateway_python_model, test_gateway_specific_render, test_model_cache_gateway
  • Multi-gateway integration tests pass: test_multi_virtual_layer, test_multiple_gateways
  • ruff check and ruff format pass

Checklist:

  • ran make style
  • added tests for my changes
  • commits are signed off per the DCO
  • all existing test pass: haven't yet run the full suite of tests via make fast-test (and it has pre-existing failures).

@mday-io mday-io force-pushed the mday-io/fix/prevent-default-catalog-leak-to-unsupported-gateways branch from 6870d2e to 90a3566 Compare March 30, 2026 14:53
Copy link
Copy Markdown
Collaborator

@themisvaltinos themisvaltinos left a comment

Choose a reason for hiding this comment

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

thank you one minor adjustment needed in the code otherwise lgtm. and if possible add also an extra test for the blueprint case I explained

from sqlmesh.core.model.definition import load_sql_based_models


def test_default_catalog_not_leaked_to_unsupported_gateway():
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.

nit: if you could move all these tests under core/test_model.py instead of having them in a separate file

Copy link
Copy Markdown
Contributor Author

@mday-io mday-io Mar 30, 2026

Choose a reason for hiding this comment

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

Comment on lines 2062 to 2097
module_path: Path = Path(),
dialect: DialectType = None,
default_catalog_per_gateway: t.Optional[t.Dict[str, str]] = None,
**loader_kwargs: t.Any,
) -> t.List[Model]:
model_blueprints: t.List[Model] = []
for blueprint in _extract_blueprints(blueprints, path):
blueprint_variables = _extract_blueprint_variables(blueprint, path)

if gateway:
rendered_gateway = render_expression(
expression=exp.maybe_parse(gateway, dialect=dialect),
module_path=module_path,
macros=loader_kwargs.get("macros"),
jinja_macros=loader_kwargs.get("jinja_macros"),
path=path,
dialect=dialect,
default_catalog=loader_kwargs.get("default_catalog"),
blueprint_variables=blueprint_variables,
)
gateway_name = rendered_gateway[0].name if rendered_gateway else None
else:
gateway_name = None

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

model_blueprints.append(
loader(
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.

we also need to adapt in line 2068 to keep the original catalog between blueprint iterations since otherwise it might get overwritten and permanently lost:

    original_default_catalog = loader_kwargs.get("default_catalog")
    for blueprint in _extract_blueprints(blueprints, path):
        loader_kwargs["default_catalog"] = original_default_catalog

so keeping the catalog and restore it at the start of each iteration to prevent cross contamination between blueprints in the case that a model uses the gateway controlled by blueprint:

MODEL (
  name @{blueprint}.my_model,
  kind FULL,
  gateway @{gw},
  blueprints (
    (blueprint := my_schema, gw := clickhouse_gw),
    (blueprint := other_schema, gw := default_gw)
),
);

SELECT 1 AS id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixes SQLMesh#5748

When the default gateway has a default catalog set (e.g., Trino with
catalog: example_catalog), that catalog was silently prepended to model
names targeting secondary gateways that do not support catalogs (e.g.,
ClickHouse), causing UnsupportedCatalogOperationError at evaluation time.

The per-gateway catalog dict omits catalog-unsupported gateways entirely,
so the model loader could not distinguish "no catalog" from "not checked"
and fell through to the global default. This change explicitly sets
default_catalog to None when a gateway is known but absent from the dict.

Additionally preserves the original default_catalog across blueprint
iterations to prevent cross-contamination when blueprints target
different gateways.

Signed-off-by: Michael Day <michael@mday.io>
@mday-io mday-io force-pushed the mday-io/fix/prevent-default-catalog-leak-to-unsupported-gateways branch from 90a3566 to f6188e7 Compare March 30, 2026 17:16
@themisvaltinos themisvaltinos merged commit c787415 into SQLMesh:main Mar 30, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants