Skip to content

Commit 820aaaf

Browse files
committed
fix: ensure dev_only VDE always applies grants in production
1 parent 4307679 commit 820aaaf

3 files changed

Lines changed: 443 additions & 17 deletions

File tree

sqlmesh/core/snapshot/evaluator.py

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,7 @@ def _render_and_insert_snapshot(
928928
model = snapshot.model
929929
adapter = self.get_adapter(model.gateway)
930930
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
931+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
931932

932933
queries_or_dfs = self._render_snapshot_for_evaluation(
933934
snapshot,
@@ -951,6 +952,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
951952
execution_time=execution_time,
952953
physical_properties=rendered_physical_properties,
953954
render_kwargs=create_render_kwargs,
955+
is_snapshot_deployable=is_snapshot_deployable,
954956
)
955957
else:
956958
logger.info(
@@ -973,6 +975,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
973975
execution_time=execution_time,
974976
physical_properties=rendered_physical_properties,
975977
render_kwargs=create_render_kwargs,
978+
is_snapshot_deployable=is_snapshot_deployable,
976979
)
977980

978981
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1181,6 +1184,7 @@ def _migrate_target_table(
11811184
allow_additive_snapshots=allow_additive_snapshots,
11821185
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
11831186
ignore_additive=snapshot.model.on_additive_change.is_ignore,
1187+
deployability_index=deployability_index,
11841188
)
11851189
finally:
11861190
if snapshot.is_materialized:
@@ -1230,6 +1234,7 @@ def _promote_snapshot(
12301234
model=snapshot.model,
12311235
environment=environment_naming_info.name,
12321236
snapshots=snapshots,
1237+
snapshot=snapshot,
12331238
**render_kwargs,
12341239
)
12351240

@@ -1456,6 +1461,8 @@ def _execute_create(
14561461
is_snapshot_representative=is_snapshot_representative,
14571462
dry_run=dry_run,
14581463
physical_properties=rendered_physical_properties,
1464+
snapshot=snapshot,
1465+
deployability_index=deployability_index,
14591466
)
14601467
if run_pre_post_statements:
14611468
evaluation_strategy.run_post_statements(
@@ -1732,6 +1739,7 @@ def _apply_grants(
17321739
model: Model,
17331740
table_name: str,
17341741
target_layer: GrantsTargetLayer,
1742+
is_snapshot_deployable: bool = False,
17351743
) -> None:
17361744
"""Apply grants for a model if grants are configured.
17371745
@@ -1743,6 +1751,7 @@ def _apply_grants(
17431751
model: The SQLMesh model containing grants configuration
17441752
table_name: The target table/view name to apply grants to
17451753
target_layer: The grants application layer (physical or virtual)
1754+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
17461755
"""
17471756
grants_config = model.grants
17481757
if grants_config is None:
@@ -1756,7 +1765,16 @@ def _apply_grants(
17561765
return
17571766

17581767
model_grants_target_layer = model.grants_target_layer
1759-
if not (model_grants_target_layer.is_all or model_grants_target_layer == target_layer):
1768+
1769+
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
1770+
1771+
if not (
1772+
model_grants_target_layer.is_all
1773+
or model_grants_target_layer == target_layer
1774+
# Always apply grants in production when VDE is dev_only regardless of target_layer
1775+
# since only physical tables are created in production
1776+
or is_prod_and_dev_only
1777+
):
17601778
logger.debug(
17611779
f"Skipping grants application for model {model.name} in {target_layer} layer"
17621780
)
@@ -1880,11 +1898,15 @@ def promote(
18801898
view_properties=model.render_virtual_properties(**render_kwargs),
18811899
)
18821900

1901+
snapshot = kwargs["snapshot"]
1902+
deployability_index = kwargs["deployability_index"]
1903+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
1904+
18831905
# Apply grants to the physical layer (referenced table / view) after promotion
1884-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1906+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
18851907

18861908
# Apply grants to the virtual layer (view) after promotion
1887-
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL)
1909+
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL, is_snapshot_deployable)
18881910

18891911
def demote(self, view_name: str, **kwargs: t.Any) -> None:
18901912
logger.info("Dropping view '%s'", view_name)
@@ -1951,7 +1973,10 @@ def create(
19511973

19521974
# Apply grants after table creation (unless explicitly skipped by caller)
19531975
if not skip_grants:
1954-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1976+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1977+
self._apply_grants(
1978+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1979+
)
19551980

19561981
def migrate(
19571982
self,
@@ -1979,7 +2004,13 @@ def migrate(
19792004
self.adapter.alter_table(alter_operations)
19802005

19812006
# Apply grants after schema migration
1982-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2007+
deployability_index = kwargs.get("deployability_index")
2008+
is_snapshot_deployable = (
2009+
deployability_index.is_deployable(snapshot) if deployability_index else False
2010+
)
2011+
self._apply_grants(
2012+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2013+
)
19832014

19842015
def delete(self, name: str, **kwargs: t.Any) -> None:
19852016
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
@@ -2031,7 +2062,8 @@ def _replace_query_for_model(
20312062

20322063
# Apply grants after table replacement (unless explicitly skipped by caller)
20332064
if not skip_grants:
2034-
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2065+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2066+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
20352067

20362068
def _get_target_and_source_columns(
20372069
self,
@@ -2321,7 +2353,10 @@ def create(
23212353

23222354
if not skip_grants:
23232355
# Apply grants after seed table creation and data insertion
2324-
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+
)
23252360
except Exception:
23262361
self.adapter.drop_table(table_name)
23272362
raise
@@ -2405,7 +2440,10 @@ def create(
24052440

24062441
if not skip_grants:
24072442
# Apply grants after SCD Type 2 table creation
2408-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2443+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2444+
self._apply_grants(
2445+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2446+
)
24092447

24102448
def insert(
24112449
self,
@@ -2531,7 +2569,8 @@ def insert(
25312569
)
25322570

25332571
# Apply grants after view creation / replacement
2534-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2572+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2573+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
25352574

25362575
def append(
25372576
self,
@@ -2552,14 +2591,18 @@ def create(
25522591
skip_grants: bool,
25532592
**kwargs: t.Any,
25542593
) -> None:
2594+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2595+
25552596
if self.adapter.table_exists(table_name):
25562597
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25572598
# binding support (because of DROP CASCADE).
25582599
logger.info("View '%s' already exists", table_name)
25592600

25602601
if not skip_grants:
25612602
# Always apply grants when present, even if view exists, to handle grants updates
2562-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2603+
self._apply_grants(
2604+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2605+
)
25632606
return
25642607

25652608
logger.info("Creating view '%s'", table_name)
@@ -2585,7 +2628,9 @@ def create(
25852628

25862629
if not skip_grants:
25872630
# Apply grants after view creation
2588-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2631+
self._apply_grants(
2632+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2633+
)
25892634

25902635
def migrate(
25912636
self,
@@ -2614,7 +2659,13 @@ def migrate(
26142659
)
26152660

26162661
# Apply grants after view migration
2617-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2662+
deployability_index = kwargs.get("deployability_index")
2663+
is_snapshot_deployable = (
2664+
deployability_index.is_deployable(snapshot) if deployability_index else False
2665+
)
2666+
self._apply_grants(
2667+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2668+
)
26182669

26192670
def delete(self, name: str, **kwargs: t.Any) -> None:
26202671
cascade = kwargs.pop("cascade", False)
@@ -2923,7 +2974,9 @@ def create(
29232974

29242975
# Apply grants after managed table creation
29252976
if not skip_grants:
2926-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2977+
self._apply_grants(
2978+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2979+
)
29272980

29282981
elif not is_table_deployable:
29292982
# Only create the dev preview table as a normal table.
@@ -2963,7 +3016,9 @@ def insert(
29633016
column_descriptions=model.column_descriptions,
29643017
table_format=model.table_format,
29653018
)
2966-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
3019+
self._apply_grants(
3020+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
3021+
)
29673022
elif not is_snapshot_deployable:
29683023
# Snapshot isnt deployable; update the preview table instead
29693024
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -3013,8 +3068,13 @@ def migrate(
30133068
)
30143069

30153070
# Apply grants after verifying no schema changes
3016-
# This ensures metadata-only grants changes are applied
3017-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
3071+
deployability_index = kwargs.get("deployability_index")
3072+
is_snapshot_deployable = (
3073+
deployability_index.is_deployable(snapshot) if deployability_index else False
3074+
)
3075+
self._apply_grants(
3076+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
3077+
)
30183078

30193079
def delete(self, name: str, **kwargs: t.Any) -> None:
30203080
# 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)