@@ -9789,22 +9789,6 @@ def resolve_named(evaluator, name):
97899789 for call in spy .call_args_list :
97909790 assert len (call .args [1 ]) <= 1
97919791
9792- # an explicit table_mapping entry takes precedence over the snapshot-derived one (rendered
9793- # via a separate model instance so the statement-render cache doesn't return the earlier result)
9794- child_for_override = load_sql_based_model (
9795- d .parse (
9796- """
9797- MODEL (name child_override);
9798- SELECT c FROM target;
9799- @resolve_named('target')
9800- """
9801- )
9802- )
9803- override = child_for_override .render_post_statements (
9804- snapshots = snapshots , table_mapping = {'"target"' : "overridden_table" }
9805- )
9806- assert override [0 ].sql () == '"overridden_table"'
9807-
98089792 # a name absent from both snapshots and table_mapping resolves unchanged
98099793 unmapped = load_sql_based_model (
98109794 d .parse (
@@ -9856,52 +9840,6 @@ def resolve_named(evaluator, name):
98569840 assert post_statements [0 ].sql () == '"override_table"'
98579841
98589842
9859- def test_render_virtual_properties_skips_mapping_without_table_refs (
9860- make_snapshot : t .Callable , mocker : MockerFixture
9861- ):
9862- """Rendering a property expression with no table references shouldn't build the full
9863- snapshot -> table-name mapping at all (https://github.com/SQLMesh/sqlmesh/issues/6017)."""
9864- import sqlmesh .core .snapshot as snapshot_module
9865-
9866- model = load_sql_based_model (
9867- d .parse (
9868- """
9869- MODEL (
9870- name test_schema.test_model,
9871- virtual_properties (
9872- labels = [('team', 'data')]
9873- ),
9874- session_properties (
9875- "spark.executor.memory" = '1G'
9876- ),
9877- );
9878- SELECT a FROM tbl;
9879- """
9880- )
9881- )
9882-
9883- snapshots = {}
9884- for i in range (50 ):
9885- other = load_sql_based_model (d .parse (f"MODEL (name other_{ i } ); SELECT 1 AS c" ))
9886- other_snapshot = make_snapshot (other )
9887- other_snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
9888- snapshots [f'"other_{ i } "' ] = other_snapshot
9889-
9890- to_table_mapping_spy = mocker .spy (snapshot_module , "to_table_mapping" )
9891-
9892- assert model .render_virtual_properties (snapshots = snapshots ) == {
9893- "labels" : exp .maybe_parse ("[('team', 'data')]" )
9894- }
9895- assert model .render_session_properties (snapshots = snapshots ) == {
9896- "spark.executor.memory" : "1G" ,
9897- }
9898-
9899- # `this_model` resolution may still make a narrow, single-snapshot (or empty) call, but the
9900- # full N-snapshot mapping build in `_resolve_tables` must never fire for a table-less property
9901- for call in to_table_mapping_spy .call_args_list :
9902- assert len (call .args [0 ]) <= 1
9903-
9904-
99059843def test_resolve_table_cross_dialect_fqn_mismatch (make_snapshot : t .Callable ):
99069844 """`_resolve_table`'s narrowed lookup keys `snapshots` by the caller's already-normalized
99079845 `table_name` string. That string is built with the *referencing* model's own dialect
@@ -9962,33 +9900,6 @@ def resolve_named(evaluator, name):
99629900 )
99639901
99649902
9965- def test_resolve_table_table_mapping_only_no_snapshots (make_snapshot : t .Callable ):
9966- """A `table_mapping` entry with no corresponding `snapshots` entry should still be honored
9967- by the narrowed lookup in `_resolve_table` (mirrors the override case in
9968- `test_resolve_table_large_environment`, but with `snapshots=None`/empty entirely, to make
9969- sure the narrowed code path doesn't assume `snapshots` is non-empty before consulting
9970- `table_mapping`)."""
9971-
9972- @macro ()
9973- def resolve_named (evaluator , name ):
9974- return evaluator .resolve_table (name .name )
9975-
9976- child = load_sql_based_model (
9977- d .parse (
9978- """
9979- MODEL (name child);
9980- SELECT 1 AS c;
9981- @resolve_named('parent')
9982- """
9983- )
9984- )
9985-
9986- post_statements = child .render_post_statements (
9987- snapshots = None , table_mapping = {'"parent"' : "explicit_physical_table" }
9988- )
9989- assert post_statements [0 ].sql () == '"explicit_physical_table"'
9990-
9991-
99929903def test_resolve_table_non_string_expr_path (make_snapshot : t .Callable ):
99939904 """When `table_name` is an `exp.Expr` (not a `str`), `_resolve_table` falls back to building
99949905 the full snapshot mapping (the `else` branch of the new code). This exercises that branch --
@@ -10021,37 +9932,6 @@ def test_resolve_table_non_string_expr_path(make_snapshot: t.Callable):
100219932 assert resolved .sql (comments = False ) == f'"sqlmesh__default"."parent__{ parent_snapshot .version } "'
100229933
100239934
10024- def test_resolve_tables_table_ref_only_in_string_literal_not_expanded (make_snapshot : t .Callable ):
10025- """Adversarial case for the `expression.find(exp.Table)` short-circuit in `_resolve_tables`:
10026- an expression that references a table only inside a string literal (not a parsed `exp.Table`
10027- node) has no `exp.Table` node for `find()` to see, so the mapping build is correctly skipped.
10028- This documents/locks in that the short-circuit is safe because `exp.replace_tables` itself
10029- only ever rewrites `exp.Table` nodes -- it would never have touched a string literal either,
10030- mapping built or not -- so skipping the mapping cannot change behavior here."""
10031-
10032- parent = load_sql_based_model (d .parse ("MODEL (name parent); SELECT 1 AS c" ))
10033- parent_snapshot = make_snapshot (parent )
10034- parent_snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
10035-
10036- model = load_sql_based_model (
10037- d .parse (
10038- """
10039- MODEL (
10040- name test_schema.string_ref_model,
10041- virtual_properties (
10042- description = 'references parent as a plain string, not a table node'
10043- ),
10044- );
10045- SELECT a FROM tbl;
10046- """
10047- )
10048- )
10049-
10050- snapshots = {'"parent"' : parent_snapshot }
10051- props = model .render_virtual_properties (snapshots = snapshots )
10052- assert props ["description" ].this == "references parent as a plain string, not a table node"
10053-
10054-
100559935def test_resolve_tables_expand_reveals_table_after_find_check (make_snapshot : t .Callable ):
100569936 """Embedded-model expansion (`expand=`) runs as an `expression.transform` *before* the new
100579937 `expression.find(exp.Table)` short-circuit in `_resolve_tables`, so a table reference that
@@ -10223,70 +10103,15 @@ def items(self):
1022310103 assert ItemsCountingDict .items_call_count == 0
1022410104
1022510105
10226- def test_resolve_table_with_view_mapping_uses_single_entry (
10227- make_snapshot : t .Callable , mocker : MockerFixture
10228- ):
10229- """During promotion `table_mapping` maps every model in the environment to its view. Resolving
10230- one table against it must not normalize every key in that mapping on every call
10231- (https://github.com/SQLMesh/sqlmesh/issues/6017)."""
10232- from sqlmesh .core .snapshot .definition import to_view_mapping
10233-
10234- @macro ()
10235- def resolve_named (evaluator , name ):
10236- return evaluator .resolve_table (name .name )
10237-
10238- snapshots = {}
10239- for i in range (50 ):
10240- other = load_sql_based_model (d .parse (f"MODEL (name db.other_{ i } ); SELECT 1 AS c" ))
10241- other_snapshot = make_snapshot (other )
10242- other_snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
10243- snapshots [other .fqn ] = other_snapshot
10244-
10245- children = [
10246- load_sql_based_model (
10247- d .parse (
10248- f"""
10249- MODEL (name db.child_{ i } );
10250- SELECT 1 AS c;
10251- @resolve_named('db.other_{ i } ')
10252- """
10253- )
10254- )
10255- for i in range (3 )
10256- ]
10257- for child in children :
10258- child_snapshot = make_snapshot (child )
10259- child_snapshot .categorize_as (SnapshotChangeCategory .BREAKING )
10260- snapshots [child .fqn ] = child_snapshot
10261-
10262- table_mapping = to_view_mapping (snapshots .values (), EnvironmentNamingInfo (name = "dev" ))
10263- spy = mocker .spy (exp , "replace_tables" )
10264-
10265- for i , child in enumerate (children ):
10266- rendered = child .render_post_statements (snapshots = snapshots , table_mapping = table_mapping )
10267- assert rendered [0 ].sql () == f'"db__dev"."other_{ i } "'
10268-
10269- # One call for `this_model` and one for the resolved table, per child.
10270- assert spy .call_count == 6
10271- for call in spy .call_args_list :
10272- assert len (call .args [1 ]) == 1
10273-
10274-
10275- @pytest .mark .parametrize ("dialect" , ["duckdb" , "snowflake" ])
10276- def test_table_mapping_normalized_keys (dialect : str ):
10106+ def test_table_mapping_normalized_keys ():
1027710107 table_mapping = TableMapping ({'"db"."a"' : "view_a" , "db.A" : "view_a_upper" })
1027810108
10279- normalized = table_mapping .normalized_keys (dialect )
1028010109 # Keys that normalize to the same name resolve to the last one, like exp.replace_tables.
10281- if dialect == "snowflake" :
10282- assert normalized == {"db.a" : '"db"."a"' , "DB.A" : "db.A" }
10283- else :
10284- assert normalized == {"db.a" : "db.A" }
10285- # Normalization happens once per dialect.
10286- assert table_mapping .normalized_keys (dialect ) is normalized
10287-
10288- assert isinstance (table_mapping .copy (), TableMapping )
10289- assert table_mapping .copy () == table_mapping
10110+ duckdb_keys = table_mapping .normalized_keys ("duckdb" )
10111+ assert duckdb_keys == {"db.a" : "db.A" }
10112+ # Normalization happens once per dialect, and each dialect gets its own normalization.
10113+ assert table_mapping .normalized_keys ("duckdb" ) is duckdb_keys
10114+ assert table_mapping .normalized_keys ("snowflake" ) == {"db.a" : '"db"."a"' , "DB.A" : "db.A" }
1029010115
1029110116 # Every mutation invalidates the cache.
1029210117 table_mapping ["db.b" ] = "view_b"
0 commit comments