@@ -677,6 +677,137 @@ def _capture_create_schema(
677677 )
678678
679679
680+ @pytest .mark .fast
681+ def test_warn_if_virtual_catalog_rematerialization_emits_warning (mocker ):
682+ """_warn_if_virtual_catalog_rematerialization must emit a log_warning when new snapshots have
683+ 3-level FQNs that map to existing 2-level FQNs in the current environment, indicating that the
684+ virtual catalog prefix was added to previously-applied ClickHouse models."""
685+ from unittest .mock import MagicMock
686+
687+ from sqlmesh .core .engine_adapter .clickhouse import ClickhouseEngineAdapter
688+ from sqlmesh .core .snapshot .definition import SnapshotId
689+
690+ # Build a minimal Context with no models.
691+ ctx = Context (config = Config ())
692+
693+ # Create a ClickHouse adapter with a virtual catalog already injected.
694+ ch_adapter = ClickhouseEngineAdapter (
695+ lambda * a , ** k : mocker .NonCallableMock (),
696+ dialect = "clickhouse" ,
697+ )
698+ ch_adapter ._default_catalog = "__ch_gw__"
699+
700+ # Override engine_adapters so the context sees our prepared adapter.
701+ mocker .patch .object (
702+ type (ctx ), "engine_adapters" , new_callable = PropertyMock , return_value = {"ch_gw" : ch_adapter }
703+ )
704+
705+ # Build a mock snapshot with a 3-level name that has the virtual catalog prefix.
706+ new_snapshot = MagicMock ()
707+ new_snapshot .name = "__ch_gw__.mydb.my_table"
708+
709+ # The old 2-level name must appear in snapshots_by_name so we detect the rename.
710+ old_snapshot_id = SnapshotId (name = "mydb.my_table" , identifier = "abc123" )
711+
712+ context_diff = MagicMock ()
713+ context_diff .new_snapshots = {new_snapshot .name : new_snapshot }
714+ context_diff .removed_snapshots = {}
715+ context_diff .snapshots_by_name = {"mydb.my_table" : MagicMock ()}
716+
717+ plan = MagicMock ()
718+ plan .new_snapshots = [new_snapshot ]
719+ plan .context_diff = context_diff
720+
721+ warning_mock = mocker .patch .object (ctx .console , "log_warning" )
722+
723+ ctx ._warn_if_virtual_catalog_rematerialization (plan )
724+
725+ warning_mock .assert_called_once ()
726+ warning_text = warning_mock .call_args [0 ][0 ]
727+ assert "__ch_gw__" in warning_text
728+ assert "mydb.my_table" in warning_text
729+
730+
731+ @pytest .mark .fast
732+ def test_warn_if_virtual_catalog_rematerialization_no_warning_when_genuinely_new (mocker ):
733+ """_warn_if_virtual_catalog_rematerialization must NOT warn when there is no matching old
734+ 2-level name — i.e. the model is a brand-new model, not a renamed existing one."""
735+ from unittest .mock import MagicMock
736+
737+ from sqlmesh .core .engine_adapter .clickhouse import ClickhouseEngineAdapter
738+
739+ ctx = Context (config = Config ())
740+
741+ ch_adapter = ClickhouseEngineAdapter (
742+ lambda * a , ** k : mocker .NonCallableMock (),
743+ dialect = "clickhouse" ,
744+ )
745+ ch_adapter ._default_catalog = "__ch_gw__"
746+
747+ mocker .patch .object (
748+ type (ctx ), "engine_adapters" , new_callable = PropertyMock , return_value = {"ch_gw" : ch_adapter }
749+ )
750+
751+ new_snapshot = MagicMock ()
752+ new_snapshot .name = "__ch_gw__.mydb.brand_new_table"
753+
754+ context_diff = MagicMock ()
755+ context_diff .new_snapshots = {new_snapshot .name : new_snapshot }
756+ context_diff .removed_snapshots = {}
757+ # No matching old name.
758+ context_diff .snapshots_by_name = {}
759+
760+ plan = MagicMock ()
761+ plan .new_snapshots = [new_snapshot ]
762+ plan .context_diff = context_diff
763+
764+ warning_mock = mocker .patch .object (ctx .console , "log_warning" )
765+
766+ ctx ._warn_if_virtual_catalog_rematerialization (plan )
767+
768+ warning_mock .assert_not_called ()
769+
770+
771+ @pytest .mark .fast
772+ def test_warn_if_virtual_catalog_rematerialization_no_warning_without_virtual_catalog (mocker ):
773+ """_warn_if_virtual_catalog_rematerialization must NOT warn when the ClickHouse adapter has no
774+ virtual catalog injected (i.e. _default_catalog is None)."""
775+ from unittest .mock import MagicMock
776+
777+ from sqlmesh .core .engine_adapter .clickhouse import ClickhouseEngineAdapter
778+
779+ ctx = Context (config = Config ())
780+
781+ ch_adapter = ClickhouseEngineAdapter (
782+ lambda * a , ** k : mocker .NonCallableMock (),
783+ dialect = "clickhouse" ,
784+ )
785+ # No virtual catalog injected — adapter stays at 2-level mode.
786+ assert ch_adapter ._default_catalog is None
787+
788+ mocker .patch .object (
789+ type (ctx ), "engine_adapters" , new_callable = PropertyMock , return_value = {"ch_gw" : ch_adapter }
790+ )
791+
792+ new_snapshot = MagicMock ()
793+ new_snapshot .name = "mydb.my_table"
794+
795+ context_diff = MagicMock ()
796+ context_diff .new_snapshots = {new_snapshot .name : new_snapshot }
797+ context_diff .removed_snapshots = {}
798+ context_diff .snapshots_by_name = {}
799+
800+ plan = MagicMock ()
801+ plan .new_snapshots = [new_snapshot ]
802+ plan .context_diff = context_diff
803+
804+ warning_mock = mocker .patch .object (ctx .console , "log_warning" )
805+
806+ ctx ._warn_if_virtual_catalog_rematerialization (plan )
807+
808+ warning_mock .assert_not_called ()
809+
810+
680811def test_plan_execution_time ():
681812 context = Context (config = Config ())
682813 context .upsert_model (
0 commit comments