Skip to content

Commit e6f75fd

Browse files
Fix: Pass a copy to avoid mutation of actual properties dict
1 parent af3a16f commit e6f75fd

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ def _evaluate_snapshot(
763763
snapshots=snapshots,
764764
deployability_index=deployability_index,
765765
render_kwargs=create_render_kwargs,
766-
rendered_physical_properties=rendered_physical_properties,
766+
rendered_physical_properties=rendered_physical_properties.copy(),
767767
allow_destructive_snapshots=allow_destructive_snapshots,
768768
allow_additive_snapshots=allow_additive_snapshots,
769769
)
@@ -776,7 +776,7 @@ def _evaluate_snapshot(
776776
is_table_deployable=is_snapshot_deployable,
777777
deployability_index=deployability_index,
778778
create_render_kwargs=create_render_kwargs,
779-
rendered_physical_properties=rendered_physical_properties,
779+
rendered_physical_properties=rendered_physical_properties.copy(),
780780
dry_run=False,
781781
run_pre_post_statements=False,
782782
)

tests/core/test_snapshot_evaluator.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4680,3 +4680,79 @@ def test_wap_publish_failure(adapter_mock: Mock, make_snapshot: t.Callable[...,
46804680
# Execute audit with WAP ID and expect it to raise the exception
46814681
with pytest.raises(Exception, match="WAP publish failed"):
46824682
evaluator.audit(snapshot, snapshots={}, wap_id=wap_id)
4683+
4684+
4685+
def test_properties_are_preserved_in_both_create_statements(
4686+
adapter_mock: Mock, make_snapshot: t.Callable[..., Snapshot]
4687+
) -> None:
4688+
# the below mocks are needed to create a situation
4689+
# where we trigger two create statements during evaluation
4690+
transaction_mock = Mock()
4691+
transaction_mock.__enter__ = Mock()
4692+
transaction_mock.__exit__ = Mock()
4693+
session_mock = Mock()
4694+
session_mock.__enter__ = Mock()
4695+
session_mock.__exit__ = Mock()
4696+
adapter_mock = Mock()
4697+
adapter_mock.transaction.return_value = transaction_mock
4698+
adapter_mock.session.return_value = session_mock
4699+
adapter_mock.dialect = "trino"
4700+
adapter_mock.HAS_VIEW_BINDING = False
4701+
adapter_mock.wap_supported.return_value = False
4702+
adapter_mock.get_data_objects.return_value = []
4703+
adapter_mock.with_settings.return_value = adapter_mock
4704+
adapter_mock.table_exists.return_value = False
4705+
4706+
props = []
4707+
4708+
def mutate_view_properties(*args, **kwargs):
4709+
view_props = kwargs.get("view_properties")
4710+
if isinstance(view_props, dict):
4711+
props.append(view_props["creatable_type"].sql())
4712+
# simulate view pop
4713+
view_props.pop("creatable_type")
4714+
return None
4715+
4716+
adapter_mock.create_view.side_effect = mutate_view_properties
4717+
4718+
evaluator = SnapshotEvaluator(adapter_mock)
4719+
4720+
# create a view model with SECURITY INVOKER physical property
4721+
# AND self referenctial to trigger two create statements
4722+
model = load_sql_based_model(
4723+
parse( # type: ignore
4724+
"""
4725+
MODEL (
4726+
name test_schema.security_view,
4727+
kind VIEW,
4728+
physical_properties (
4729+
'creatable_type' = 'SECURITY INVOKER'
4730+
)
4731+
);
4732+
4733+
SELECT 1 as col from test_schema.security_view;
4734+
"""
4735+
),
4736+
)
4737+
4738+
snapshot = make_snapshot(model)
4739+
snapshot.categorize_as(SnapshotChangeCategory.BREAKING)
4740+
evaluator.evaluate(
4741+
snapshot,
4742+
start="2024-01-01",
4743+
end="2024-01-02",
4744+
execution_time="2024-01-02",
4745+
snapshots={},
4746+
)
4747+
4748+
# Verify create_view was called twice
4749+
assert adapter_mock.create_view.call_count == 2
4750+
first_call = adapter_mock.create_view.call_args_list[0]
4751+
second_call = adapter_mock.create_view.call_args_list[1]
4752+
4753+
# First call should be CREATE VIEW (replace=False) second CREATE OR REPLACE VIEW (replace=True)
4754+
assert first_call.kwargs.get("replace") == False
4755+
assert second_call.kwargs.get("replace") == True
4756+
4757+
# Both calls should have view_properties with security invoker
4758+
assert props == ["'SECURITY INVOKER'", "'SECURITY INVOKER'"]

0 commit comments

Comments
 (0)