Skip to content

Commit 1cd32c4

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 1460bc1 commit 1cd32c4

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
@@ -1054,6 +1054,7 @@ def _clone_snapshot_in_dev(
10541054
allow_additive_snapshots=allow_additive_snapshots,
10551055
run_pre_post_statements=run_pre_post_statements,
10561056
)
1057+
10571058
except Exception:
10581059
adapter.drop_table(target_table_name)
10591060
raise
@@ -1154,6 +1155,7 @@ def _migrate_target_table(
11541155
rendered_physical_properties=rendered_physical_properties,
11551156
dry_run=False,
11561157
run_pre_post_statements=run_pre_post_statements,
1158+
skip_grants=True, # skip grants for tmp table
11571159
)
11581160
try:
11591161
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1419,6 +1421,7 @@ def _execute_create(
14191421
rendered_physical_properties: t.Dict[str, exp.Expression],
14201422
dry_run: bool,
14211423
run_pre_post_statements: bool = True,
1424+
skip_grants: bool = False,
14221425
) -> None:
14231426
adapter = self.get_adapter(snapshot.model.gateway)
14241427
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1442,12 +1445,11 @@ def _execute_create(
14421445
is_snapshot_representative=is_snapshot_representative,
14431446
dry_run=dry_run,
14441447
physical_properties=rendered_physical_properties,
1448+
skip_grants=skip_grants,
14451449
)
14461450
if run_pre_post_statements:
14471451
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14481452

1449-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1450-
14511453
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14521454
adapter = self.get_adapter(snapshot.model.gateway)
14531455
return (
@@ -1716,16 +1718,11 @@ def _apply_grants(
17161718
return
17171719

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

17301727

17311728
class SymbolicStrategy(EvaluationStrategy):
@@ -1890,6 +1887,10 @@ def create(
18901887
column_descriptions=model.column_descriptions if is_table_deployable else None,
18911888
)
18921889

1890+
# Apply grants after table creation (unless explicitly skipped by caller)
1891+
if not kwargs.get("skip_grants", False):
1892+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1893+
18931894
def migrate(
18941895
self,
18951896
target_table_name: str,
@@ -1915,6 +1916,9 @@ def migrate(
19151916
)
19161917
self.adapter.alter_table(alter_operations)
19171918

1919+
# Apply grants after schema migration
1920+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1921+
19181922
def delete(self, name: str, **kwargs: t.Any) -> None:
19191923
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
19201924
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1962,6 +1966,10 @@ def _replace_query_for_model(
19621966
source_columns=source_columns,
19631967
)
19641968

1969+
# Apply grants after table replacement (unless explicitly skipped by caller)
1970+
if not kwargs.get("skip_grants", False):
1971+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1972+
19651973
def _get_target_and_source_columns(
19661974
self,
19671975
model: Model,
@@ -2222,12 +2230,18 @@ def create(
22222230
)
22232231
return
22242232

2225-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2233+
# Skip grants in parent create call since we'll apply them after data insertion
2234+
kwargs_no_grants = {**kwargs}
2235+
kwargs_no_grants["skip_grants"] = True
2236+
2237+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
22262238
# For seeds we insert data at the time of table creation.
22272239
try:
22282240
for index, df in enumerate(model.render_seed()):
22292241
if index == 0:
2230-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2242+
self._replace_query_for_model(
2243+
model, table_name, df, render_kwargs, **kwargs_no_grants
2244+
)
22312245
else:
22322246
self.adapter.insert_append(
22332247
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2236,6 +2250,9 @@ def create(
22362250
self.adapter.drop_table(table_name)
22372251
raise
22382252

2253+
# Apply grants after seed table creation or data insertion
2254+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2255+
22392256
def insert(
22402257
self,
22412258
table_name: str,
@@ -2299,6 +2316,9 @@ def create(
22992316
**kwargs,
23002317
)
23012318

2319+
# Apply grants after SCD Type 2 table creation
2320+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2321+
23022322
def insert(
23032323
self,
23042324
table_name: str,
@@ -2422,7 +2442,7 @@ def insert(
24222442
column_descriptions=model.column_descriptions,
24232443
)
24242444

2425-
# Apply grants after view creation/replacement
2445+
# Apply grants after view creation / replacement
24262446
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24272447

24282448
def append(
@@ -2447,6 +2467,8 @@ def create(
24472467
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24482468
# binding support (because of DROP CASCADE).
24492469
logger.info("View '%s' already exists", table_name)
2470+
# Always apply grants when present, even if view exists, to handle grants updates
2471+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24502472
return
24512473

24522474
logger.info("Creating view '%s'", table_name)
@@ -2470,6 +2492,9 @@ def create(
24702492
column_descriptions=model.column_descriptions if is_table_deployable else None,
24712493
)
24722494

2495+
# Apply grants after view creation
2496+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2497+
24732498
def migrate(
24742499
self,
24752500
target_table_name: str,
@@ -2650,7 +2675,7 @@ def create(
26502675
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26512676

26522677
if is_table_deployable and is_snapshot_deployable:
2653-
# We could deploy this to prod; create a proper managed table
2678+
# We cloud deploy this to prod; create a proper managed table
26542679
logger.info("Creating managed table: %s", table_name)
26552680
self.adapter.create_managed_table(
26562681
table_name=table_name,
@@ -2666,17 +2691,23 @@ def create(
26662691

26672692
# Apply grants after managed table creation
26682693
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2694+
26692695
elif not is_table_deployable:
26702696
# Only create the dev preview table as a normal table.
26712697
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26722698
# Any downstream models that reference it will be updated to point to the dev preview table.
26732699
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2700+
2701+
# Create preview table but don't apply grants here since the table is not deployable
2702+
# Grants will be applied later when the table becomes deployable
2703+
kwargs_no_grants = {**kwargs}
2704+
kwargs_no_grants["skip_grants"] = True
26742705
super().create(
26752706
table_name=table_name,
26762707
model=model,
26772708
is_table_deployable=is_table_deployable,
26782709
render_kwargs=render_kwargs,
2679-
**kwargs,
2710+
**kwargs_no_grants,
26802711
)
26812712

26822713
def insert(
@@ -2691,7 +2722,6 @@ def insert(
26912722
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
26922723
snapshot: Snapshot = kwargs["snapshot"]
26932724
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2694-
26952725
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
26962726
self.adapter.create_managed_table(
26972727
table_name=table_name,
@@ -2704,9 +2734,6 @@ def insert(
27042734
column_descriptions=model.column_descriptions,
27052735
table_format=model.table_format,
27062736
)
2707-
2708-
# Apply grants after managed table creation during first insert
2709-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
27102737
elif not is_snapshot_deployable:
27112738
# Snapshot isnt deployable; update the preview table instead
27122739
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2755,6 +2782,10 @@ def migrate(
27552782
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27562783
)
27572784

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

0 commit comments

Comments
 (0)