Skip to content

Commit 529c7ce

Browse files
committed
fix: ensure dev_only VDE always applies grants in production
1 parent d3e6efa commit 529c7ce

File tree

3 files changed

+443
-17
lines changed

3 files changed

+443
-17
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ def _render_and_insert_snapshot(
912912
model = snapshot.model
913913
adapter = self.get_adapter(model.gateway)
914914
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
915+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
915916

916917
queries_or_dfs = self._render_snapshot_for_evaluation(
917918
snapshot,
@@ -935,6 +936,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
935936
execution_time=execution_time,
936937
physical_properties=rendered_physical_properties,
937938
render_kwargs=create_render_kwargs,
939+
is_snapshot_deployable=is_snapshot_deployable,
938940
)
939941
else:
940942
logger.info(
@@ -957,6 +959,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
957959
execution_time=execution_time,
958960
physical_properties=rendered_physical_properties,
959961
render_kwargs=create_render_kwargs,
962+
is_snapshot_deployable=is_snapshot_deployable,
960963
)
961964

962965
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1155,6 +1158,7 @@ def _migrate_target_table(
11551158
allow_additive_snapshots=allow_additive_snapshots,
11561159
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
11571160
ignore_additive=snapshot.model.on_additive_change.is_ignore,
1161+
deployability_index=deployability_index,
11581162
)
11591163
finally:
11601164
if snapshot.is_materialized:
@@ -1204,6 +1208,7 @@ def _promote_snapshot(
12041208
model=snapshot.model,
12051209
environment=environment_naming_info.name,
12061210
snapshots=snapshots,
1211+
snapshot=snapshot,
12071212
**render_kwargs,
12081213
)
12091214

@@ -1428,6 +1433,8 @@ def _execute_create(
14281433
is_snapshot_representative=is_snapshot_representative,
14291434
dry_run=dry_run,
14301435
physical_properties=rendered_physical_properties,
1436+
snapshot=snapshot,
1437+
deployability_index=deployability_index,
14311438
)
14321439
if run_pre_post_statements:
14331440
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
@@ -1669,6 +1676,7 @@ def _apply_grants(
16691676
model: Model,
16701677
table_name: str,
16711678
target_layer: GrantsTargetLayer,
1679+
is_snapshot_deployable: bool = False,
16721680
) -> None:
16731681
"""Apply grants for a model if grants are configured.
16741682
@@ -1680,6 +1688,7 @@ def _apply_grants(
16801688
model: The SQLMesh model containing grants configuration
16811689
table_name: The target table/view name to apply grants to
16821690
target_layer: The grants application layer (physical or virtual)
1691+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
16831692
"""
16841693
grants_config = model.grants
16851694
if grants_config is None:
@@ -1693,7 +1702,16 @@ def _apply_grants(
16931702
return
16941703

16951704
model_grants_target_layer = model.grants_target_layer
1696-
if not (model_grants_target_layer.is_all or model_grants_target_layer == target_layer):
1705+
1706+
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
1707+
1708+
if not (
1709+
model_grants_target_layer.is_all
1710+
or model_grants_target_layer == target_layer
1711+
# Always apply grants in production when VDE is dev_only regardless of target_layer
1712+
# since only physical tables are created in production
1713+
or is_prod_and_dev_only
1714+
):
16971715
logger.debug(
16981716
f"Skipping grants application for model {model.name} in {target_layer} layer"
16991717
)
@@ -1811,11 +1829,15 @@ def promote(
18111829
view_properties=model.render_virtual_properties(**render_kwargs),
18121830
)
18131831

1832+
snapshot = kwargs["snapshot"]
1833+
deployability_index = kwargs["deployability_index"]
1834+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
1835+
18141836
# Apply grants to the physical layer (referenced table / view) after promotion
1815-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1837+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
18161838

18171839
# Apply grants to the virtual layer (view) after promotion
1818-
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL)
1840+
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL, is_snapshot_deployable)
18191841

18201842
def demote(self, view_name: str, **kwargs: t.Any) -> None:
18211843
logger.info("Dropping view '%s'", view_name)
@@ -1876,7 +1898,10 @@ def create(
18761898

18771899
# Apply grants after table creation (unless explicitly skipped by caller)
18781900
if not skip_grants:
1879-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1901+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1902+
self._apply_grants(
1903+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1904+
)
18801905

18811906
def migrate(
18821907
self,
@@ -1904,7 +1929,13 @@ def migrate(
19041929
self.adapter.alter_table(alter_operations)
19051930

19061931
# Apply grants after schema migration
1907-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1932+
deployability_index = kwargs.get("deployability_index")
1933+
is_snapshot_deployable = (
1934+
deployability_index.is_deployable(snapshot) if deployability_index else False
1935+
)
1936+
self._apply_grants(
1937+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1938+
)
19081939

19091940
def delete(self, name: str, **kwargs: t.Any) -> None:
19101941
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
@@ -1956,7 +1987,8 @@ def _replace_query_for_model(
19561987

19571988
# Apply grants after table replacement (unless explicitly skipped by caller)
19581989
if not skip_grants:
1959-
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1990+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1991+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
19601992

19611993
def _get_target_and_source_columns(
19621994
self,
@@ -2246,7 +2278,10 @@ def create(
22462278

22472279
if not skip_grants:
22482280
# Apply grants after seed table creation and data insertion
2249-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2281+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2282+
self._apply_grants(
2283+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2284+
)
22502285
except Exception:
22512286
self.adapter.drop_table(table_name)
22522287
raise
@@ -2318,7 +2353,10 @@ def create(
23182353

23192354
if not skip_grants:
23202355
# Apply grants after SCD Type 2 table creation
2321-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2356+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2357+
self._apply_grants(
2358+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2359+
)
23222360

23232361
def insert(
23242362
self,
@@ -2444,7 +2482,8 @@ def insert(
24442482
)
24452483

24462484
# Apply grants after view creation / replacement
2447-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2485+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2486+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
24482487

24492488
def append(
24502489
self,
@@ -2465,14 +2504,18 @@ def create(
24652504
skip_grants: bool,
24662505
**kwargs: t.Any,
24672506
) -> None:
2507+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2508+
24682509
if self.adapter.table_exists(table_name):
24692510
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24702511
# binding support (because of DROP CASCADE).
24712512
logger.info("View '%s' already exists", table_name)
24722513

24732514
if not skip_grants:
24742515
# Always apply grants when present, even if view exists, to handle grants updates
2475-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2516+
self._apply_grants(
2517+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2518+
)
24762519
return
24772520

24782521
logger.info("Creating view '%s'", table_name)
@@ -2498,7 +2541,9 @@ def create(
24982541

24992542
if not skip_grants:
25002543
# Apply grants after view creation
2501-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2544+
self._apply_grants(
2545+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2546+
)
25022547

25032548
def migrate(
25042549
self,
@@ -2527,7 +2572,13 @@ def migrate(
25272572
)
25282573

25292574
# Apply grants after view migration
2530-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2575+
deployability_index = kwargs.get("deployability_index")
2576+
is_snapshot_deployable = (
2577+
deployability_index.is_deployable(snapshot) if deployability_index else False
2578+
)
2579+
self._apply_grants(
2580+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2581+
)
25312582

25322583
def delete(self, name: str, **kwargs: t.Any) -> None:
25332584
cascade = kwargs.pop("cascade", False)
@@ -2697,7 +2748,9 @@ def create(
26972748

26982749
# Apply grants after managed table creation
26992750
if not skip_grants:
2700-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2751+
self._apply_grants(
2752+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2753+
)
27012754

27022755
elif not is_table_deployable:
27032756
# Only create the dev preview table as a normal table.
@@ -2737,7 +2790,9 @@ def insert(
27372790
column_descriptions=model.column_descriptions,
27382791
table_format=model.table_format,
27392792
)
2740-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2793+
self._apply_grants(
2794+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2795+
)
27412796
elif not is_snapshot_deployable:
27422797
# Snapshot isnt deployable; update the preview table instead
27432798
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2787,8 +2842,13 @@ def migrate(
27872842
)
27882843

27892844
# Apply grants after verifying no schema changes
2790-
# This ensures metadata-only grants changes are applied
2791-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2845+
deployability_index = kwargs.get("deployability_index")
2846+
is_snapshot_deployable = (
2847+
deployability_index.is_deployable(snapshot) if deployability_index else False
2848+
)
2849+
self._apply_grants(
2850+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2851+
)
27922852

27932853
def delete(self, name: str, **kwargs: t.Any) -> None:
27942854
# a dev preview table is created as a normal table, so it needs to be dropped as a normal table

0 commit comments

Comments
 (0)