Skip to content

Commit 00688bd

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 01ae6b7 commit 00688bd

4 files changed

Lines changed: 927 additions & 86 deletions

File tree

sqlmesh/core/snapshot/evaluator.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ def _clone_snapshot_in_dev(
10501050
allow_destructive_snapshots=allow_destructive_snapshots,
10511051
allow_additive_snapshots=allow_additive_snapshots,
10521052
)
1053+
10531054
except Exception:
10541055
adapter.drop_table(target_table_name)
10551056
raise
@@ -1150,6 +1151,7 @@ def _migrate_target_table(
11501151
rendered_physical_properties=rendered_physical_properties,
11511152
dry_run=False,
11521153
run_pre_post_statements=run_pre_post_statements,
1154+
skip_grants=True, # skip grants for tmp table
11531155
)
11541156
try:
11551157
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1415,6 +1417,7 @@ def _execute_create(
14151417
rendered_physical_properties: t.Dict[str, exp.Expression],
14161418
dry_run: bool,
14171419
run_pre_post_statements: bool = True,
1420+
skip_grants: bool = False,
14181421
) -> None:
14191422
adapter = self.get_adapter(snapshot.model.gateway)
14201423
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1438,12 +1441,11 @@ def _execute_create(
14381441
is_snapshot_representative=is_snapshot_representative,
14391442
dry_run=dry_run,
14401443
physical_properties=rendered_physical_properties,
1444+
skip_grants=skip_grants,
14411445
)
14421446
if run_pre_post_statements:
14431447
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14441448

1445-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1446-
14471449
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14481450
adapter = self.get_adapter(snapshot.model.gateway)
14491451
return (
@@ -1712,16 +1714,11 @@ def _apply_grants(
17121714
return
17131715

17141716
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1715-
1716-
try:
1717-
self.adapter.sync_grants_config(
1718-
exp.to_table(table_name, dialect=model.dialect),
1719-
grants_config,
1720-
model.grants_table_type,
1721-
)
1722-
except Exception:
1723-
# Log error but don't fail evaluation if grants fail
1724-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1717+
self.adapter.sync_grants_config(
1718+
exp.to_table(table_name, dialect=self.adapter.dialect),
1719+
grants_config,
1720+
model.grants_table_type,
1721+
)
17251722

17261723

17271724
class SymbolicStrategy(EvaluationStrategy):
@@ -1886,6 +1883,10 @@ def create(
18861883
column_descriptions=model.column_descriptions if is_table_deployable else None,
18871884
)
18881885

1886+
# Apply grants after table creation (unless explicitly skipped by caller)
1887+
if not kwargs.get("skip_grants", False):
1888+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1889+
18891890
def migrate(
18901891
self,
18911892
target_table_name: str,
@@ -1911,6 +1912,9 @@ def migrate(
19111912
)
19121913
self.adapter.alter_table(alter_operations)
19131914

1915+
# Apply grants after schema migration
1916+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1917+
19141918
def delete(self, name: str, **kwargs: t.Any) -> None:
19151919
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
19161920
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1958,6 +1962,10 @@ def _replace_query_for_model(
19581962
source_columns=source_columns,
19591963
)
19601964

1965+
# Apply grants after table replacement (unless explicitly skipped by caller)
1966+
if not kwargs.get("skip_grants", False):
1967+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1968+
19611969
def _get_target_and_source_columns(
19621970
self,
19631971
model: Model,
@@ -2218,12 +2226,18 @@ def create(
22182226
)
22192227
return
22202228

2221-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2229+
# Skip grants in parent create call since we'll apply them after data insertion
2230+
kwargs_no_grants = {**kwargs}
2231+
kwargs_no_grants["skip_grants"] = True
2232+
2233+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
22222234
# For seeds we insert data at the time of table creation.
22232235
try:
22242236
for index, df in enumerate(model.render_seed()):
22252237
if index == 0:
2226-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2238+
self._replace_query_for_model(
2239+
model, table_name, df, render_kwargs, **kwargs_no_grants
2240+
)
22272241
else:
22282242
self.adapter.insert_append(
22292243
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2232,6 +2246,9 @@ def create(
22322246
self.adapter.drop_table(table_name)
22332247
raise
22342248

2249+
# Apply grants after seed table creation or data insertion
2250+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2251+
22352252
def insert(
22362253
self,
22372254
table_name: str,
@@ -2295,6 +2312,9 @@ def create(
22952312
**kwargs,
22962313
)
22972314

2315+
# Apply grants after SCD Type 2 table creation
2316+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2317+
22982318
def insert(
22992319
self,
23002320
table_name: str,
@@ -2418,7 +2438,7 @@ def insert(
24182438
column_descriptions=model.column_descriptions,
24192439
)
24202440

2421-
# Apply grants after view creation/replacement
2441+
# Apply grants after view creation / replacement
24222442
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24232443

24242444
def append(
@@ -2443,6 +2463,8 @@ def create(
24432463
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24442464
# binding support (because of DROP CASCADE).
24452465
logger.info("View '%s' already exists", table_name)
2466+
# Always apply grants when present, even if view exists, to handle grants updates
2467+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24462468
return
24472469

24482470
logger.info("Creating view '%s'", table_name)
@@ -2466,6 +2488,9 @@ def create(
24662488
column_descriptions=model.column_descriptions if is_table_deployable else None,
24672489
)
24682490

2491+
# Apply grants after view creation
2492+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2493+
24692494
def migrate(
24702495
self,
24712496
target_table_name: str,
@@ -2646,7 +2671,7 @@ def create(
26462671
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26472672

26482673
if is_table_deployable and is_snapshot_deployable:
2649-
# We could deploy this to prod; create a proper managed table
2674+
# We cloud deploy this to prod; create a proper managed table
26502675
logger.info("Creating managed table: %s", table_name)
26512676
self.adapter.create_managed_table(
26522677
table_name=table_name,
@@ -2662,17 +2687,23 @@ def create(
26622687

26632688
# Apply grants after managed table creation
26642689
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2690+
26652691
elif not is_table_deployable:
26662692
# Only create the dev preview table as a normal table.
26672693
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26682694
# Any downstream models that reference it will be updated to point to the dev preview table.
26692695
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2696+
2697+
# Create preview table but don't apply grants here since the table is not deployable
2698+
# Grants will be applied later when the table becomes deployable
2699+
kwargs_no_grants = {**kwargs}
2700+
kwargs_no_grants["skip_grants"] = True
26702701
super().create(
26712702
table_name=table_name,
26722703
model=model,
26732704
is_table_deployable=is_table_deployable,
26742705
render_kwargs=render_kwargs,
2675-
**kwargs,
2706+
**kwargs_no_grants,
26762707
)
26772708

26782709
def insert(
@@ -2687,7 +2718,6 @@ def insert(
26872718
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
26882719
snapshot: Snapshot = kwargs["snapshot"]
26892720
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2690-
26912721
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
26922722
self.adapter.create_managed_table(
26932723
table_name=table_name,
@@ -2700,9 +2730,6 @@ def insert(
27002730
column_descriptions=model.column_descriptions,
27012731
table_format=model.table_format,
27022732
)
2703-
2704-
# Apply grants after managed table creation during first insert
2705-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
27062733
elif not is_snapshot_deployable:
27072734
# Snapshot isnt deployable; update the preview table instead
27082735
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2751,6 +2778,10 @@ def migrate(
27512778
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27522779
)
27532780

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

0 commit comments

Comments
 (0)