Skip to content

Commit 21adbfb

Browse files
committed
test: prune redundant table resolution tests
Remove tests that duplicate stronger coverage or guard nothing a credible regression would break: the table_mapping-only lookup already covered by the dialect-mismatch case, the string-literal property case, the model-level single-entry check already covered at the promotion boundary, and the mapping-skip check that only guarded a redundant second find(exp.Table). Drop that second check from _resolve_tables, build the mapping in the same place as before this change, drop the unused TableMapping.copy() override, and fold the per-dialect normalized-keys cases into one test. Signed-off-by: mday-io <mdaytn@gmail.com>
1 parent f165353 commit 21adbfb

2 files changed

Lines changed: 16 additions & 205 deletions

File tree

‎sqlmesh/core/renderer.py‎

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ def normalized_keys(self, dialect: DialectType) -> t.Dict[str, str]:
6565
self._normalized_keys[dialect] = normalized_keys
6666
return normalized_keys
6767

68-
def copy(self) -> TableMapping:
69-
# dict.copy() would return a plain dict and lose the cache.
70-
return TableMapping(self)
71-
7268
def __setitem__(self, key: str, value: str) -> None:
7369
self._normalized_keys.clear()
7470
super().__setitem__(key, value)
@@ -441,15 +437,17 @@ def _resolve_tables(
441437

442438
expression = expression.copy()
443439
with self._normalize_and_quote(expression) as expression:
444-
# An expression with no exp.Table node at all (e.g. session/virtual properties) has
445-
# nothing for `expand` to expand or for a table mapping to replace - skip building
446-
# the expand set and model_mapping too, not just the mapping/replace_tables below,
447-
# since both of those are themselves O(N) in the number of snapshots.
440+
# An expression with no table (e.g. most session or virtual properties) has nothing
441+
# to expand or replace, so skip building the O(N) expand set and mapping.
448442
if not expression.find(exp.Table):
449443
return expression
450444

451445
snapshots = snapshots or {}
452446
table_mapping = table_mapping or {}
447+
mapping = {
448+
**self._to_table_mapping(snapshots.values(), deployability_index),
449+
**table_mapping,
450+
}
453451
expand = set(expand) | {
454452
name for name, snapshot in snapshots.items() if snapshot.is_embedded
455453
}
@@ -491,22 +489,10 @@ def _expand(node: exp.Expr) -> exp.Expr:
491489

492490
expression = expression.transform(_expand, copy=False) # type: ignore
493491

494-
# Building the full snapshot -> table-name mapping and normalizing it in
495-
# exp.replace_tables is O(N) in the number of snapshots in the environment; skip it
496-
# entirely for expressions that don't reference any table at all (e.g. session/
497-
# virtual properties), since there's nothing for the mapping to replace.
498-
if expression.find(exp.Table):
499-
# mypy loses the `snapshots`/`table_mapping` narrowing above because they're
500-
# captured by the `_expand` closure defined earlier in this block.
501-
assert snapshots is not None and table_mapping is not None
502-
mapping = {
503-
**self._to_table_mapping(snapshots.values(), deployability_index),
504-
**table_mapping,
505-
}
506-
if mapping:
507-
expression = exp.replace_tables(
508-
expression, mapping, dialect=self._dialect, copy=False
509-
)
492+
if mapping:
493+
expression = exp.replace_tables(
494+
expression, mapping, dialect=self._dialect, copy=False
495+
)
510496

511497
return expression
512498

‎tests/core/test_model.py‎

Lines changed: 6 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
99059843
def 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-
99929903
def 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-
100559935
def 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

Comments
 (0)