Skip to content

Commit 7e95498

Browse files
committed
fix: ensure dev_only VDE always applies grants in production
1 parent 5d67fa0 commit 7e95498

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
@@ -920,6 +920,7 @@ def _render_and_insert_snapshot(
920920
model = snapshot.model
921921
adapter = self.get_adapter(model.gateway)
922922
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
923+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
923924

924925
queries_or_dfs = self._render_snapshot_for_evaluation(
925926
snapshot,
@@ -943,6 +944,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
943944
execution_time=execution_time,
944945
physical_properties=rendered_physical_properties,
945946
render_kwargs=create_render_kwargs,
947+
is_snapshot_deployable=is_snapshot_deployable,
946948
)
947949
else:
948950
logger.info(
@@ -965,6 +967,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
965967
execution_time=execution_time,
966968
physical_properties=rendered_physical_properties,
967969
render_kwargs=create_render_kwargs,
970+
is_snapshot_deployable=is_snapshot_deployable,
968971
)
969972

970973
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1173,6 +1176,7 @@ def _migrate_target_table(
11731176
allow_additive_snapshots=allow_additive_snapshots,
11741177
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
11751178
ignore_additive=snapshot.model.on_additive_change.is_ignore,
1179+
deployability_index=deployability_index,
11761180
)
11771181
finally:
11781182
if snapshot.is_materialized:
@@ -1222,6 +1226,7 @@ def _promote_snapshot(
12221226
model=snapshot.model,
12231227
environment=environment_naming_info.name,
12241228
snapshots=snapshots,
1229+
snapshot=snapshot,
12251230
**render_kwargs,
12261231
)
12271232

@@ -1446,6 +1451,8 @@ def _execute_create(
14461451
is_snapshot_representative=is_snapshot_representative,
14471452
dry_run=dry_run,
14481453
physical_properties=rendered_physical_properties,
1454+
snapshot=snapshot,
1455+
deployability_index=deployability_index,
14491456
)
14501457
if run_pre_post_statements:
14511458
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
@@ -1688,6 +1695,7 @@ def _apply_grants(
16881695
model: Model,
16891696
table_name: str,
16901697
target_layer: GrantsTargetLayer,
1698+
is_snapshot_deployable: bool = False,
16911699
) -> None:
16921700
"""Apply grants for a model if grants are configured.
16931701
@@ -1699,6 +1707,7 @@ def _apply_grants(
16991707
model: The SQLMesh model containing grants configuration
17001708
table_name: The target table/view name to apply grants to
17011709
target_layer: The grants application layer (physical or virtual)
1710+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
17021711
"""
17031712
grants_config = model.grants
17041713
if grants_config is None:
@@ -1712,7 +1721,16 @@ def _apply_grants(
17121721
return
17131722

17141723
model_grants_target_layer = model.grants_target_layer
1715-
if not (model_grants_target_layer.is_all or model_grants_target_layer == target_layer):
1724+
1725+
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
1726+
1727+
if not (
1728+
model_grants_target_layer.is_all
1729+
or model_grants_target_layer == target_layer
1730+
# Always apply grants in production when VDE is dev_only regardless of target_layer
1731+
# since only physical tables are created in production
1732+
or is_prod_and_dev_only
1733+
):
17161734
logger.debug(
17171735
f"Skipping grants application for model {model.name} in {target_layer} layer"
17181736
)
@@ -1830,11 +1848,15 @@ def promote(
18301848
view_properties=model.render_virtual_properties(**render_kwargs),
18311849
)
18321850

1851+
snapshot = kwargs["snapshot"]
1852+
deployability_index = kwargs["deployability_index"]
1853+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
1854+
18331855
# Apply grants to the physical layer (referenced table / view) after promotion
1834-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1856+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
18351857

18361858
# Apply grants to the virtual layer (view) after promotion
1837-
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL)
1859+
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL, is_snapshot_deployable)
18381860

18391861
def demote(self, view_name: str, **kwargs: t.Any) -> None:
18401862
logger.info("Dropping view '%s'", view_name)
@@ -1895,7 +1917,10 @@ def create(
18951917

18961918
# Apply grants after table creation (unless explicitly skipped by caller)
18971919
if not skip_grants:
1898-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1920+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1921+
self._apply_grants(
1922+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1923+
)
18991924

19001925
def migrate(
19011926
self,
@@ -1923,7 +1948,13 @@ def migrate(
19231948
self.adapter.alter_table(alter_operations)
19241949

19251950
# Apply grants after schema migration
1926-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1951+
deployability_index = kwargs.get("deployability_index")
1952+
is_snapshot_deployable = (
1953+
deployability_index.is_deployable(snapshot) if deployability_index else False
1954+
)
1955+
self._apply_grants(
1956+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1957+
)
19271958

19281959
def delete(self, name: str, **kwargs: t.Any) -> None:
19291960
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
@@ -1975,7 +2006,8 @@ def _replace_query_for_model(
19752006

19762007
# Apply grants after table replacement (unless explicitly skipped by caller)
19772008
if not skip_grants:
1978-
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2009+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2010+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
19792011

19802012
def _get_target_and_source_columns(
19812013
self,
@@ -2265,7 +2297,10 @@ def create(
22652297

22662298
if not skip_grants:
22672299
# Apply grants after seed table creation and data insertion
2268-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2300+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2301+
self._apply_grants(
2302+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2303+
)
22692304
except Exception:
22702305
self.adapter.drop_table(table_name)
22712306
raise
@@ -2337,7 +2372,10 @@ def create(
23372372

23382373
if not skip_grants:
23392374
# Apply grants after SCD Type 2 table creation
2340-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2375+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2376+
self._apply_grants(
2377+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2378+
)
23412379

23422380
def insert(
23432381
self,
@@ -2463,7 +2501,8 @@ def insert(
24632501
)
24642502

24652503
# Apply grants after view creation / replacement
2466-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2504+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2505+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
24672506

24682507
def append(
24692508
self,
@@ -2484,14 +2523,18 @@ def create(
24842523
skip_grants: bool,
24852524
**kwargs: t.Any,
24862525
) -> None:
2526+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2527+
24872528
if self.adapter.table_exists(table_name):
24882529
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24892530
# binding support (because of DROP CASCADE).
24902531
logger.info("View '%s' already exists", table_name)
24912532

24922533
if not skip_grants:
24932534
# Always apply grants when present, even if view exists, to handle grants updates
2494-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2535+
self._apply_grants(
2536+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2537+
)
24952538
return
24962539

24972540
logger.info("Creating view '%s'", table_name)
@@ -2517,7 +2560,9 @@ def create(
25172560

25182561
if not skip_grants:
25192562
# Apply grants after view creation
2520-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2563+
self._apply_grants(
2564+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2565+
)
25212566

25222567
def migrate(
25232568
self,
@@ -2546,7 +2591,13 @@ def migrate(
25462591
)
25472592

25482593
# Apply grants after view migration
2549-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2594+
deployability_index = kwargs.get("deployability_index")
2595+
is_snapshot_deployable = (
2596+
deployability_index.is_deployable(snapshot) if deployability_index else False
2597+
)
2598+
self._apply_grants(
2599+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2600+
)
25502601

25512602
def delete(self, name: str, **kwargs: t.Any) -> None:
25522603
cascade = kwargs.pop("cascade", False)
@@ -2716,7 +2767,9 @@ def create(
27162767

27172768
# Apply grants after managed table creation
27182769
if not skip_grants:
2719-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2770+
self._apply_grants(
2771+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2772+
)
27202773

27212774
elif not is_table_deployable:
27222775
# Only create the dev preview table as a normal table.
@@ -2756,7 +2809,9 @@ def insert(
27562809
column_descriptions=model.column_descriptions,
27572810
table_format=model.table_format,
27582811
)
2759-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2812+
self._apply_grants(
2813+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2814+
)
27602815
elif not is_snapshot_deployable:
27612816
# Snapshot isnt deployable; update the preview table instead
27622817
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2806,8 +2861,13 @@ def migrate(
28062861
)
28072862

28082863
# Apply grants after verifying no schema changes
2809-
# This ensures metadata-only grants changes are applied
2810-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2864+
deployability_index = kwargs.get("deployability_index")
2865+
is_snapshot_deployable = (
2866+
deployability_index.is_deployable(snapshot) if deployability_index else False
2867+
)
2868+
self._apply_grants(
2869+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2870+
)
28112871

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