Skip to content

Commit 1a8b917

Browse files
committed
fix: ensure dev_only VDE always applies grants in production
1 parent 927c14d commit 1a8b917

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
@@ -918,6 +918,7 @@ def _render_and_insert_snapshot(
918918
model = snapshot.model
919919
adapter = self.get_adapter(model.gateway)
920920
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
921+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
921922

922923
queries_or_dfs = self._render_snapshot_for_evaluation(
923924
snapshot,
@@ -941,6 +942,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
941942
execution_time=execution_time,
942943
physical_properties=rendered_physical_properties,
943944
render_kwargs=create_render_kwargs,
945+
is_snapshot_deployable=is_snapshot_deployable,
944946
)
945947
else:
946948
logger.info(
@@ -963,6 +965,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
963965
execution_time=execution_time,
964966
physical_properties=rendered_physical_properties,
965967
render_kwargs=create_render_kwargs,
968+
is_snapshot_deployable=is_snapshot_deployable,
966969
)
967970

968971
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1169,6 +1172,7 @@ def _migrate_target_table(
11691172
allow_additive_snapshots=allow_additive_snapshots,
11701173
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
11711174
ignore_additive=snapshot.model.on_additive_change.is_ignore,
1175+
deployability_index=deployability_index,
11721176
)
11731177
finally:
11741178
if snapshot.is_materialized:
@@ -1218,6 +1222,7 @@ def _promote_snapshot(
12181222
model=snapshot.model,
12191223
environment=environment_naming_info.name,
12201224
snapshots=snapshots,
1225+
snapshot=snapshot,
12211226
**render_kwargs,
12221227
)
12231228

@@ -1442,6 +1447,8 @@ def _execute_create(
14421447
is_snapshot_representative=is_snapshot_representative,
14431448
dry_run=dry_run,
14441449
physical_properties=rendered_physical_properties,
1450+
snapshot=snapshot,
1451+
deployability_index=deployability_index,
14451452
)
14461453
if run_pre_post_statements:
14471454
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
@@ -1684,6 +1691,7 @@ def _apply_grants(
16841691
model: Model,
16851692
table_name: str,
16861693
target_layer: GrantsTargetLayer,
1694+
is_snapshot_deployable: bool = False,
16871695
) -> None:
16881696
"""Apply grants for a model if grants are configured.
16891697
@@ -1695,6 +1703,7 @@ def _apply_grants(
16951703
model: The SQLMesh model containing grants configuration
16961704
table_name: The target table/view name to apply grants to
16971705
target_layer: The grants application layer (physical or virtual)
1706+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
16981707
"""
16991708
grants_config = model.grants
17001709
if grants_config is None:
@@ -1708,7 +1717,16 @@ def _apply_grants(
17081717
return
17091718

17101719
model_grants_target_layer = model.grants_target_layer
1711-
if not (model_grants_target_layer.is_all or model_grants_target_layer == target_layer):
1720+
1721+
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
1722+
1723+
if not (
1724+
model_grants_target_layer.is_all
1725+
or model_grants_target_layer == target_layer
1726+
# Always apply grants in production when VDE is dev_only regardless of target_layer
1727+
# since only physical tables are created in production
1728+
or is_prod_and_dev_only
1729+
):
17121730
logger.debug(
17131731
f"Skipping grants application for model {model.name} in {target_layer} layer"
17141732
)
@@ -1826,11 +1844,15 @@ def promote(
18261844
view_properties=model.render_virtual_properties(**render_kwargs),
18271845
)
18281846

1847+
snapshot = kwargs["snapshot"]
1848+
deployability_index = kwargs["deployability_index"]
1849+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
1850+
18291851
# Apply grants to the physical layer (referenced table / view) after promotion
1830-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1852+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
18311853

18321854
# Apply grants to the virtual layer (view) after promotion
1833-
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL)
1855+
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL, is_snapshot_deployable)
18341856

18351857
def demote(self, view_name: str, **kwargs: t.Any) -> None:
18361858
logger.info("Dropping view '%s'", view_name)
@@ -1891,7 +1913,10 @@ def create(
18911913

18921914
# Apply grants after table creation (unless explicitly skipped by caller)
18931915
if not skip_grants:
1894-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1916+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1917+
self._apply_grants(
1918+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1919+
)
18951920

18961921
def migrate(
18971922
self,
@@ -1919,7 +1944,13 @@ def migrate(
19191944
self.adapter.alter_table(alter_operations)
19201945

19211946
# Apply grants after schema migration
1922-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1947+
deployability_index = kwargs.get("deployability_index")
1948+
is_snapshot_deployable = (
1949+
deployability_index.is_deployable(snapshot) if deployability_index else False
1950+
)
1951+
self._apply_grants(
1952+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1953+
)
19231954

19241955
def delete(self, name: str, **kwargs: t.Any) -> None:
19251956
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
@@ -1971,7 +2002,8 @@ def _replace_query_for_model(
19712002

19722003
# Apply grants after table replacement (unless explicitly skipped by caller)
19732004
if not skip_grants:
1974-
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2005+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2006+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
19752007

19762008
def _get_target_and_source_columns(
19772009
self,
@@ -2261,7 +2293,10 @@ def create(
22612293

22622294
if not skip_grants:
22632295
# Apply grants after seed table creation and data insertion
2264-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2296+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2297+
self._apply_grants(
2298+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2299+
)
22652300
except Exception:
22662301
self.adapter.drop_table(table_name)
22672302
raise
@@ -2333,7 +2368,10 @@ def create(
23332368

23342369
if not skip_grants:
23352370
# Apply grants after SCD Type 2 table creation
2336-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2371+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2372+
self._apply_grants(
2373+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2374+
)
23372375

23382376
def insert(
23392377
self,
@@ -2459,7 +2497,8 @@ def insert(
24592497
)
24602498

24612499
# Apply grants after view creation / replacement
2462-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2500+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2501+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
24632502

24642503
def append(
24652504
self,
@@ -2480,14 +2519,18 @@ def create(
24802519
skip_grants: bool,
24812520
**kwargs: t.Any,
24822521
) -> None:
2522+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2523+
24832524
if self.adapter.table_exists(table_name):
24842525
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24852526
# binding support (because of DROP CASCADE).
24862527
logger.info("View '%s' already exists", table_name)
24872528

24882529
if not skip_grants:
24892530
# Always apply grants when present, even if view exists, to handle grants updates
2490-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2531+
self._apply_grants(
2532+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2533+
)
24912534
return
24922535

24932536
logger.info("Creating view '%s'", table_name)
@@ -2513,7 +2556,9 @@ def create(
25132556

25142557
if not skip_grants:
25152558
# Apply grants after view creation
2516-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2559+
self._apply_grants(
2560+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2561+
)
25172562

25182563
def migrate(
25192564
self,
@@ -2542,7 +2587,13 @@ def migrate(
25422587
)
25432588

25442589
# Apply grants after view migration
2545-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2590+
deployability_index = kwargs.get("deployability_index")
2591+
is_snapshot_deployable = (
2592+
deployability_index.is_deployable(snapshot) if deployability_index else False
2593+
)
2594+
self._apply_grants(
2595+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2596+
)
25462597

25472598
def delete(self, name: str, **kwargs: t.Any) -> None:
25482599
cascade = kwargs.pop("cascade", False)
@@ -2712,7 +2763,9 @@ def create(
27122763

27132764
# Apply grants after managed table creation
27142765
if not skip_grants:
2715-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2766+
self._apply_grants(
2767+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2768+
)
27162769

27172770
elif not is_table_deployable:
27182771
# Only create the dev preview table as a normal table.
@@ -2752,7 +2805,9 @@ def insert(
27522805
column_descriptions=model.column_descriptions,
27532806
table_format=model.table_format,
27542807
)
2755-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2808+
self._apply_grants(
2809+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2810+
)
27562811
elif not is_snapshot_deployable:
27572812
# Snapshot isnt deployable; update the preview table instead
27582813
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2802,8 +2857,13 @@ def migrate(
28022857
)
28032858

28042859
# Apply grants after verifying no schema changes
2805-
# This ensures metadata-only grants changes are applied
2806-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2860+
deployability_index = kwargs.get("deployability_index")
2861+
is_snapshot_deployable = (
2862+
deployability_index.is_deployable(snapshot) if deployability_index else False
2863+
)
2864+
self._apply_grants(
2865+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2866+
)
28072867

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