Skip to content

Commit 3b7937c

Browse files
committed
fix: ensure dev_only VDE always applies grants in production
1 parent ac45320 commit 3b7937c

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
@@ -922,6 +922,7 @@ def _render_and_insert_snapshot(
922922
model = snapshot.model
923923
adapter = self.get_adapter(model.gateway)
924924
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
925+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
925926

926927
queries_or_dfs = self._render_snapshot_for_evaluation(
927928
snapshot,
@@ -945,6 +946,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
945946
execution_time=execution_time,
946947
physical_properties=rendered_physical_properties,
947948
render_kwargs=create_render_kwargs,
949+
is_snapshot_deployable=is_snapshot_deployable,
948950
)
949951
else:
950952
logger.info(
@@ -967,6 +969,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
967969
execution_time=execution_time,
968970
physical_properties=rendered_physical_properties,
969971
render_kwargs=create_render_kwargs,
972+
is_snapshot_deployable=is_snapshot_deployable,
970973
)
971974

972975
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1175,6 +1178,7 @@ def _migrate_target_table(
11751178
allow_additive_snapshots=allow_additive_snapshots,
11761179
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
11771180
ignore_additive=snapshot.model.on_additive_change.is_ignore,
1181+
deployability_index=deployability_index,
11781182
)
11791183
finally:
11801184
if snapshot.is_materialized:
@@ -1224,6 +1228,7 @@ def _promote_snapshot(
12241228
model=snapshot.model,
12251229
environment=environment_naming_info.name,
12261230
snapshots=snapshots,
1231+
snapshot=snapshot,
12271232
**render_kwargs,
12281233
)
12291234

@@ -1448,6 +1453,8 @@ def _execute_create(
14481453
is_snapshot_representative=is_snapshot_representative,
14491454
dry_run=dry_run,
14501455
physical_properties=rendered_physical_properties,
1456+
snapshot=snapshot,
1457+
deployability_index=deployability_index,
14511458
)
14521459
if run_pre_post_statements:
14531460
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
@@ -1690,6 +1697,7 @@ def _apply_grants(
16901697
model: Model,
16911698
table_name: str,
16921699
target_layer: GrantsTargetLayer,
1700+
is_snapshot_deployable: bool = False,
16931701
) -> None:
16941702
"""Apply grants for a model if grants are configured.
16951703
@@ -1701,6 +1709,7 @@ def _apply_grants(
17011709
model: The SQLMesh model containing grants configuration
17021710
table_name: The target table/view name to apply grants to
17031711
target_layer: The grants application layer (physical or virtual)
1712+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
17041713
"""
17051714
grants_config = model.grants
17061715
if grants_config is None:
@@ -1714,7 +1723,16 @@ def _apply_grants(
17141723
return
17151724

17161725
model_grants_target_layer = model.grants_target_layer
1717-
if not (model_grants_target_layer.is_all or model_grants_target_layer == target_layer):
1726+
1727+
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
1728+
1729+
if not (
1730+
model_grants_target_layer.is_all
1731+
or model_grants_target_layer == target_layer
1732+
# Always apply grants in production when VDE is dev_only regardless of target_layer
1733+
# since only physical tables are created in production
1734+
or is_prod_and_dev_only
1735+
):
17181736
logger.debug(
17191737
f"Skipping grants application for model {model.name} in {target_layer} layer"
17201738
)
@@ -1832,11 +1850,15 @@ def promote(
18321850
view_properties=model.render_virtual_properties(**render_kwargs),
18331851
)
18341852

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

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

18411863
def demote(self, view_name: str, **kwargs: t.Any) -> None:
18421864
logger.info("Dropping view '%s'", view_name)
@@ -1897,7 +1919,10 @@ def create(
18971919

18981920
# Apply grants after table creation (unless explicitly skipped by caller)
18991921
if not skip_grants:
1900-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1922+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
1923+
self._apply_grants(
1924+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1925+
)
19011926

19021927
def migrate(
19031928
self,
@@ -1925,7 +1950,13 @@ def migrate(
19251950
self.adapter.alter_table(alter_operations)
19261951

19271952
# Apply grants after schema migration
1928-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1953+
deployability_index = kwargs.get("deployability_index")
1954+
is_snapshot_deployable = (
1955+
deployability_index.is_deployable(snapshot) if deployability_index else False
1956+
)
1957+
self._apply_grants(
1958+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
1959+
)
19291960

19301961
def delete(self, name: str, **kwargs: t.Any) -> None:
19311962
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
@@ -1977,7 +2008,8 @@ def _replace_query_for_model(
19772008

19782009
# Apply grants after table replacement (unless explicitly skipped by caller)
19792010
if not skip_grants:
1980-
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2011+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2012+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
19812013

19822014
def _get_target_and_source_columns(
19832015
self,
@@ -2267,7 +2299,10 @@ def create(
22672299

22682300
if not skip_grants:
22692301
# Apply grants after seed table creation and data insertion
2270-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2302+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2303+
self._apply_grants(
2304+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2305+
)
22712306
except Exception:
22722307
self.adapter.drop_table(table_name)
22732308
raise
@@ -2351,7 +2386,10 @@ def create(
23512386

23522387
if not skip_grants:
23532388
# Apply grants after SCD Type 2 table creation
2354-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2389+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2390+
self._apply_grants(
2391+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2392+
)
23552393

23562394
def insert(
23572395
self,
@@ -2477,7 +2515,8 @@ def insert(
24772515
)
24782516

24792517
# Apply grants after view creation / replacement
2480-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2518+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2519+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
24812520

24822521
def append(
24832522
self,
@@ -2498,14 +2537,18 @@ def create(
24982537
skip_grants: bool,
24992538
**kwargs: t.Any,
25002539
) -> None:
2540+
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
2541+
25012542
if self.adapter.table_exists(table_name):
25022543
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25032544
# binding support (because of DROP CASCADE).
25042545
logger.info("View '%s' already exists", table_name)
25052546

25062547
if not skip_grants:
25072548
# Always apply grants when present, even if view exists, to handle grants updates
2508-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2549+
self._apply_grants(
2550+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2551+
)
25092552
return
25102553

25112554
logger.info("Creating view '%s'", table_name)
@@ -2531,7 +2574,9 @@ def create(
25312574

25322575
if not skip_grants:
25332576
# Apply grants after view creation
2534-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2577+
self._apply_grants(
2578+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2579+
)
25352580

25362581
def migrate(
25372582
self,
@@ -2560,7 +2605,13 @@ def migrate(
25602605
)
25612606

25622607
# Apply grants after view migration
2563-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2608+
deployability_index = kwargs.get("deployability_index")
2609+
is_snapshot_deployable = (
2610+
deployability_index.is_deployable(snapshot) if deployability_index else False
2611+
)
2612+
self._apply_grants(
2613+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2614+
)
25642615

25652616
def delete(self, name: str, **kwargs: t.Any) -> None:
25662617
cascade = kwargs.pop("cascade", False)
@@ -2730,7 +2781,9 @@ def create(
27302781

27312782
# Apply grants after managed table creation
27322783
if not skip_grants:
2733-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2784+
self._apply_grants(
2785+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2786+
)
27342787

27352788
elif not is_table_deployable:
27362789
# Only create the dev preview table as a normal table.
@@ -2770,7 +2823,9 @@ def insert(
27702823
column_descriptions=model.column_descriptions,
27712824
table_format=model.table_format,
27722825
)
2773-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2826+
self._apply_grants(
2827+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2828+
)
27742829
elif not is_snapshot_deployable:
27752830
# Snapshot isnt deployable; update the preview table instead
27762831
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2820,8 +2875,13 @@ def migrate(
28202875
)
28212876

28222877
# Apply grants after verifying no schema changes
2823-
# This ensures metadata-only grants changes are applied
2824-
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2878+
deployability_index = kwargs.get("deployability_index")
2879+
is_snapshot_deployable = (
2880+
deployability_index.is_deployable(snapshot) if deployability_index else False
2881+
)
2882+
self._apply_grants(
2883+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
2884+
)
28252885

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