Skip to content

Commit 6056dbf

Browse files
Fix: Resolution of this_model within macros of on_virtual_update (#3962)
1 parent 3d6f042 commit 6056dbf

5 files changed

Lines changed: 111 additions & 14 deletions

File tree

sqlmesh/core/model/definition.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,6 @@ def render_on_virtual_update(
452452
engine_adapter: t.Optional[EngineAdapter] = None,
453453
**kwargs: t.Any,
454454
) -> t.List[exp.Expression]:
455-
if "this_model" not in kwargs:
456-
kwargs["this_model"] = self.fully_qualified_table
457455
return self._render_statements(
458456
self.on_virtual_update,
459457
start=start,

sqlmesh/core/renderer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ def render(
386386
execution_time=execution_time,
387387
snapshots=snapshots,
388388
deployability_index=deployability_index,
389+
table_mapping=table_mapping,
389390
**kwargs,
390391
)
391392
except ParsetimeAdapterCallError:

tests/core/test_context.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,3 +1813,75 @@ def test_plan_selector_expression_no_match(sushi_context: Context) -> None:
18131813
match="Selector did not return any models. Please check your model selection and try again.",
18141814
):
18151815
sushi_context.plan("prod", restate_models=["*missing*"])
1816+
1817+
1818+
def test_plan_on_virtual_update_this_model_in_macro(tmp_path: pathlib.Path):
1819+
models_dir = pathlib.Path("models")
1820+
macros_dir = pathlib.Path("macros")
1821+
dialect = "duckdb"
1822+
1823+
config = Config(
1824+
model_defaults=ModelDefaultsConfig(dialect=dialect),
1825+
)
1826+
1827+
model_file = """
1828+
MODEL(
1829+
name db.test_view_macro_this_model,
1830+
kind full,
1831+
);
1832+
1833+
1834+
SELECT 1 AS cola;
1835+
1836+
ON_VIRTUAL_UPDATE_BEGIN;
1837+
CREATE OR REPLACE TABLE log_schema AS SELECT @resolve_template('@{schema_name}') as my_schema;
1838+
@create_log_view(@this_model);
1839+
ON_VIRTUAL_UPDATE_END;
1840+
1841+
"""
1842+
1843+
create_temp_file(
1844+
tmp_path,
1845+
pathlib.Path(models_dir, "db", "test_view_macro_this_model.sql"),
1846+
model_file,
1847+
)
1848+
1849+
create_temp_file(
1850+
tmp_path,
1851+
pathlib.Path(macros_dir, "create_log_view.py"),
1852+
"""
1853+
from sqlmesh.core.macros import macro
1854+
1855+
@macro()
1856+
def create_log_view(evaluator, view_name):
1857+
return f"CREATE OR REPLACE TABLE log_view AS SELECT '{view_name}' as fqn_this_model, '{evaluator.this_model}' as evaluator_this_model;"
1858+
""",
1859+
)
1860+
1861+
context = Context(paths=tmp_path, config=config)
1862+
context.plan(environment="dev", auto_apply=True, no_prompts=True)
1863+
1864+
model = context.get_model("db.test_view_macro_this_model")
1865+
assert (
1866+
model.on_virtual_update[0].sql(dialect=dialect)
1867+
== "CREATE OR REPLACE TABLE log_schema AS SELECT @resolve_template('@{schema_name}') AS my_schema"
1868+
)
1869+
assert model.on_virtual_update[1].sql(dialect=dialect) == "@create_log_view(@this_model)"
1870+
1871+
snapshot = context.get_snapshot("db.test_view_macro_this_model")
1872+
assert snapshot and snapshot.version
1873+
1874+
log_view = context.fetchdf("select * from log_view").to_dict()
1875+
log_schema = context.fetchdf("select * from log_schema").to_dict()
1876+
1877+
# Validate that within macro for this_model we resolve to the environment-specific view
1878+
assert (
1879+
log_view["fqn_this_model"][0]
1880+
== '"db__dev"."test_view_macro_this_model" /* memory.db.test_view_macro_this_model */'
1881+
)
1882+
1883+
# Validate that from the macro evaluator this_model we get the environment-specific fqn
1884+
assert log_view["evaluator_this_model"][0] == '"db__dev"."test_view_macro_this_model"'
1885+
1886+
# Validate the schema is retrieved using resolve_template for the environment-specific schema
1887+
assert log_schema["my_schema"][0] == "db__dev"

tests/core/test_model.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from sqlglot.errors import ParseError
1414
from sqlglot.schema import MappingSchema
1515
from sqlmesh.cli.example_project import init_example_project, ProjectTemplate
16+
from sqlmesh.core.environment import EnvironmentNamingInfo
1617
from sqlmesh.core.model.kind import TimeColumn, ModelKindName
1718

1819
from sqlmesh import CustomMaterialization, CustomKind
@@ -7843,6 +7844,7 @@ def test_model_on_virtual_update(make_snapshot: t.Callable):
78437844
def resolve_parent_name(evaluator, name):
78447845
return evaluator.resolve_table(name.name)
78457846

7847+
dialect = "postgres"
78467848
virtual_update_statements = """
78477849
CREATE OR REPLACE VIEW test_view FROM demo_db.table;
78487850
GRANT SELECT ON VIEW @this_model TO ROLE owner_name;
@@ -7869,7 +7871,8 @@ def resolve_parent_name(evaluator, name):
78697871
78707872
on_virtual_update_end;
78717873
7872-
"""
7874+
""",
7875+
default_dialect=dialect,
78737876
)
78747877

78757878
parent_expressions = d.parse(
@@ -7886,49 +7889,57 @@ def resolve_parent_name(evaluator, name):
78867889
JINJA_END;
78877890
ON_VIRTUAL_UPDATE_END;
78887891
7889-
"""
7892+
""",
7893+
default_dialect=dialect,
78907894
)
78917895

7892-
model = load_sql_based_model(expressions)
7893-
parent = load_sql_based_model(parent_expressions)
7896+
model = load_sql_based_model(expressions, dialect=dialect)
7897+
parent = load_sql_based_model(parent_expressions, dialect=dialect)
78947898

78957899
parent_snapshot = make_snapshot(parent)
78967900
parent_snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
7897-
version = parent_snapshot.version
78987901

78997902
model_snapshot = make_snapshot(model)
79007903
model_snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
79017904

7902-
assert model.on_virtual_update == d.parse(virtual_update_statements)
7905+
assert model.on_virtual_update == d.parse(virtual_update_statements, default_dialect=dialect)
79037906

79047907
assert parent.on_virtual_update == d.parse(
7905-
"JINJA_STATEMENT_BEGIN; GRANT SELECT ON VIEW {{this_model}} TO ROLE admin; JINJA_END;"
7908+
"JINJA_STATEMENT_BEGIN; GRANT SELECT ON VIEW {{this_model}} TO ROLE admin; JINJA_END;",
7909+
default_dialect=dialect,
79067910
)
79077911

7912+
environment_naming_info = EnvironmentNamingInfo(name="dev")
79087913
table_mapping = {model.fqn: "demo_db__dev.table", parent.fqn: "default__dev.parent"}
79097914
snapshots = {
79107915
parent_snapshot.name: parent_snapshot,
79117916
model_snapshot.name: model_snapshot,
79127917
}
79137918

79147919
rendered_on_virtual_update = model.render_on_virtual_update(
7915-
snapshots=snapshots, table_mapping=table_mapping
7920+
snapshots=snapshots,
7921+
table_mapping=table_mapping,
7922+
this_model=model_snapshot.qualified_view_name.table_for_environment(
7923+
environment_naming_info, dialect=dialect
7924+
),
79167925
)
79177926

79187927
assert len(rendered_on_virtual_update) == 6
79197928
assert (
79207929
rendered_on_virtual_update[0].sql()
79217930
== 'CREATE OR REPLACE VIEW "test_view" AS SELECT * FROM "demo_db__dev"."table" AS "table" /* demo_db.table */'
79227931
)
7932+
79237933
assert (
79247934
rendered_on_virtual_update[1].sql()
7925-
== 'GRANT SELECT ON VIEW "demo_db__dev"."table" /* demo_db.table */ TO ROLE "owner_name"'
7935+
== 'GRANT SELECT ON VIEW "demo_db__dev"."table" TO ROLE "owner_name"'
79267936
)
79277937
assert (
79287938
rendered_on_virtual_update[3].sql()
79297939
== "GRANT REFERENCES, SELECT ON FUTURE VIEWS IN DATABASE demo_db TO ROLE owner_name"
79307940
)
7931-
assert rendered_on_virtual_update[4].sql() == f'"sqlmesh__default"."parent__{version}"'
7941+
7942+
assert rendered_on_virtual_update[4].sql() == '"default__dev"."parent"'
79327943

79337944
# When replace=false the table should remain as is
79347945
assert (
@@ -7937,12 +7948,16 @@ def resolve_parent_name(evaluator, name):
79377948
)
79387949

79397950
rendered_parent_on_virtual_update = parent.render_on_virtual_update(
7940-
snapshots=snapshots, table_mapping=table_mapping
7951+
snapshots=snapshots,
7952+
table_mapping=table_mapping,
7953+
this_model=parent_snapshot.qualified_view_name.table_for_environment(
7954+
environment_naming_info, dialect=dialect
7955+
),
79417956
)
79427957
assert len(rendered_parent_on_virtual_update) == 1
79437958
assert (
79447959
rendered_parent_on_virtual_update[0].sql()
7945-
== 'GRANT SELECT ON VIEW "default__dev"."parent" /* parent */ TO ROLE "admin"'
7960+
== 'GRANT SELECT ON VIEW "default__dev"."parent" TO ROLE "admin"'
79467961
)
79477962

79487963

tests/core/test_snapshot_evaluator.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,10 @@ def model_with_statements(context, **kwargs):
29192919
def test_on_virtual_update_statements(mocker: MockerFixture, adapter_mock, make_snapshot):
29202920
evaluator = SnapshotEvaluator(adapter_mock)
29212921

2922+
@macro()
2923+
def create_log_table(evaluator, view_name):
2924+
return f"CREATE OR REPLACE TABLE log_table AS SELECT '{view_name}' as fqn_this_model, '{evaluator.this_model}' as eval_this_model"
2925+
29222926
model = load_sql_based_model(
29232927
d.parse(
29242928
"""
@@ -2937,6 +2941,7 @@ def test_on_virtual_update_statements(mocker: MockerFixture, adapter_mock, make_
29372941
GRANT SELECT ON VIEW test_schema.test_model TO ROLE admin;
29382942
JINJA_END;
29392943
GRANT REFERENCES, SELECT ON FUTURE VIEWS IN DATABASE demo_db TO ROLE owner_name;
2944+
@create_log_table(@this_model);
29402945
ON_VIRTUAL_UPDATE_END;
29412946
29422947
"""
@@ -2988,6 +2993,12 @@ def test_on_virtual_update_statements(mocker: MockerFixture, adapter_mock, make_
29882993
== "GRANT REFERENCES, SELECT ON FUTURE VIEWS IN DATABASE demo_db TO ROLE owner_name"
29892994
)
29902995

2996+
# Validation that within the macro the environment specific view is used
2997+
assert (
2998+
on_virtual_update_calls[2].sql(dialect="postgres")
2999+
== 'CREATE OR REPLACE TABLE "log_table" AS SELECT \'"test_schema__test_env"."test_model" /* test_schema.test_model */\' AS "fqn_this_model", \'"test_schema__test_env"."test_model"\' AS "eval_this_model"'
3000+
)
3001+
29913002

29923003
def test_on_virtual_update_python_model_macro(mocker: MockerFixture, adapter_mock, make_snapshot):
29933004
evaluator = SnapshotEvaluator(adapter_mock)

0 commit comments

Comments
 (0)