Skip to content

Commit 17e71c9

Browse files
committed
Push apply grants down to evaluation strategies
and always apply grants on migrate(). This way, grants will always be applied even when grants are the only metadata that changed.
1 parent 03c7eb4 commit 17e71c9

File tree

4 files changed

+927
-86
lines changed

4 files changed

+927
-86
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,7 @@ def _clone_snapshot_in_dev(
10451045
allow_destructive_snapshots=allow_destructive_snapshots,
10461046
allow_additive_snapshots=allow_additive_snapshots,
10471047
)
1048+
10481049
except Exception:
10491050
adapter.drop_table(target_table_name)
10501051
raise
@@ -1136,6 +1137,7 @@ def _migrate_target_table(
11361137
rendered_physical_properties=rendered_physical_properties,
11371138
dry_run=False,
11381139
run_pre_post_statements=run_pre_post_statements,
1140+
skip_grants=True, # skip grants for tmp table
11391141
)
11401142
try:
11411143
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1401,6 +1403,7 @@ def _execute_create(
14011403
rendered_physical_properties: t.Dict[str, exp.Expression],
14021404
dry_run: bool,
14031405
run_pre_post_statements: bool = True,
1406+
skip_grants: bool = False,
14041407
) -> None:
14051408
adapter = self.get_adapter(snapshot.model.gateway)
14061409
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1424,12 +1427,11 @@ def _execute_create(
14241427
is_snapshot_representative=is_snapshot_representative,
14251428
dry_run=dry_run,
14261429
physical_properties=rendered_physical_properties,
1430+
skip_grants=skip_grants,
14271431
)
14281432
if run_pre_post_statements:
14291433
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14301434

1431-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1432-
14331435
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14341436
adapter = self.get_adapter(snapshot.model.gateway)
14351437
return (
@@ -1697,16 +1699,11 @@ def _apply_grants(
16971699
return
16981700

16991701
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1700-
1701-
try:
1702-
self.adapter.sync_grants_config(
1703-
exp.to_table(table_name, dialect=model.dialect),
1704-
grants_config,
1705-
model.grants_table_type,
1706-
)
1707-
except Exception:
1708-
# Log error but don't fail evaluation if grants fail
1709-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1702+
self.adapter.sync_grants_config(
1703+
exp.to_table(table_name, dialect=self.adapter.dialect),
1704+
grants_config,
1705+
model.grants_table_type,
1706+
)
17101707

17111708

17121709
class SymbolicStrategy(EvaluationStrategy):
@@ -1871,6 +1868,10 @@ def create(
18711868
column_descriptions=model.column_descriptions if is_table_deployable else None,
18721869
)
18731870

1871+
# Apply grants after table creation (unless explicitly skipped by caller)
1872+
if not kwargs.get("skip_grants", False):
1873+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1874+
18741875
def migrate(
18751876
self,
18761877
target_table_name: str,
@@ -1896,6 +1897,9 @@ def migrate(
18961897
)
18971898
self.adapter.alter_table(alter_operations)
18981899

1900+
# Apply grants after schema migration
1901+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1902+
18991903
def delete(self, name: str, **kwargs: t.Any) -> None:
19001904
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
19011905
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1943,6 +1947,10 @@ def _replace_query_for_model(
19431947
source_columns=source_columns,
19441948
)
19451949

1950+
# Apply grants after table replacement (unless explicitly skipped by caller)
1951+
if not kwargs.get("skip_grants", False):
1952+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1953+
19461954
def _get_target_and_source_columns(
19471955
self,
19481956
model: Model,
@@ -2203,12 +2211,18 @@ def create(
22032211
)
22042212
return
22052213

2206-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2214+
# Skip grants in parent create call since we'll apply them after data insertion
2215+
kwargs_no_grants = {**kwargs}
2216+
kwargs_no_grants["skip_grants"] = True
2217+
2218+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
22072219
# For seeds we insert data at the time of table creation.
22082220
try:
22092221
for index, df in enumerate(model.render_seed()):
22102222
if index == 0:
2211-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2223+
self._replace_query_for_model(
2224+
model, table_name, df, render_kwargs, **kwargs_no_grants
2225+
)
22122226
else:
22132227
self.adapter.insert_append(
22142228
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2217,6 +2231,9 @@ def create(
22172231
self.adapter.drop_table(table_name)
22182232
raise
22192233

2234+
# Apply grants after seed table creation or data insertion
2235+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2236+
22202237
def insert(
22212238
self,
22222239
table_name: str,
@@ -2280,6 +2297,9 @@ def create(
22802297
**kwargs,
22812298
)
22822299

2300+
# Apply grants after SCD Type 2 table creation
2301+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2302+
22832303
def insert(
22842304
self,
22852305
table_name: str,
@@ -2403,7 +2423,7 @@ def insert(
24032423
column_descriptions=model.column_descriptions,
24042424
)
24052425

2406-
# Apply grants after view creation/replacement
2426+
# Apply grants after view creation / replacement
24072427
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24082428

24092429
def append(
@@ -2428,6 +2448,8 @@ def create(
24282448
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24292449
# binding support (because of DROP CASCADE).
24302450
logger.info("View '%s' already exists", table_name)
2451+
# Always apply grants when present, even if view exists, to handle grants updates
2452+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24312453
return
24322454

24332455
logger.info("Creating view '%s'", table_name)
@@ -2451,6 +2473,9 @@ def create(
24512473
column_descriptions=model.column_descriptions if is_table_deployable else None,
24522474
)
24532475

2476+
# Apply grants after view creation
2477+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2478+
24542479
def migrate(
24552480
self,
24562481
target_table_name: str,
@@ -2631,7 +2656,7 @@ def create(
26312656
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26322657

26332658
if is_table_deployable and is_snapshot_deployable:
2634-
# We could deploy this to prod; create a proper managed table
2659+
# We cloud deploy this to prod; create a proper managed table
26352660
logger.info("Creating managed table: %s", table_name)
26362661
self.adapter.create_managed_table(
26372662
table_name=table_name,
@@ -2647,17 +2672,23 @@ def create(
26472672

26482673
# Apply grants after managed table creation
26492674
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2675+
26502676
elif not is_table_deployable:
26512677
# Only create the dev preview table as a normal table.
26522678
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26532679
# Any downstream models that reference it will be updated to point to the dev preview table.
26542680
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2681+
2682+
# Create preview table but don't apply grants here since the table is not deployable
2683+
# Grants will be applied later when the table becomes deployable
2684+
kwargs_no_grants = {**kwargs}
2685+
kwargs_no_grants["skip_grants"] = True
26552686
super().create(
26562687
table_name=table_name,
26572688
model=model,
26582689
is_table_deployable=is_table_deployable,
26592690
render_kwargs=render_kwargs,
2660-
**kwargs,
2691+
**kwargs_no_grants,
26612692
)
26622693

26632694
def insert(
@@ -2672,7 +2703,6 @@ def insert(
26722703
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
26732704
snapshot: Snapshot = kwargs["snapshot"]
26742705
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2675-
26762706
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
26772707
self.adapter.create_managed_table(
26782708
table_name=table_name,
@@ -2685,9 +2715,6 @@ def insert(
26852715
column_descriptions=model.column_descriptions,
26862716
table_format=model.table_format,
26872717
)
2688-
2689-
# Apply grants after managed table creation during first insert
2690-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
26912718
elif not is_snapshot_deployable:
26922719
# Snapshot isnt deployable; update the preview table instead
26932720
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2736,6 +2763,10 @@ def migrate(
27362763
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27372764
)
27382765

2766+
# Apply grants after verifying no schema changes
2767+
# This ensures metadata-only changes (grants) are applied
2768+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2769+
27392770
def delete(self, name: str, **kwargs: t.Any) -> None:
27402771
# a dev preview table is created as a normal table, so it needs to be dropped as a normal table
27412772
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])

0 commit comments

Comments
 (0)