Skip to content

Commit 0c463d7

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 7eaea21 commit 0c463d7

4 files changed

Lines changed: 924 additions & 86 deletions

File tree

sqlmesh/core/snapshot/evaluator.py

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,7 @@ def _clone_snapshot_in_dev(
10621062
allow_additive_snapshots=allow_additive_snapshots,
10631063
run_pre_post_statements=run_pre_post_statements,
10641064
)
1065+
10651066
except Exception:
10661067
adapter.drop_table(target_table_name)
10671068
raise
@@ -1162,6 +1163,7 @@ def _migrate_target_table(
11621163
rendered_physical_properties=rendered_physical_properties,
11631164
dry_run=False,
11641165
run_pre_post_statements=run_pre_post_statements,
1166+
skip_grants=True, # skip grants for tmp table
11651167
)
11661168
try:
11671169
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1427,6 +1429,7 @@ def _execute_create(
14271429
rendered_physical_properties: t.Dict[str, exp.Expression],
14281430
dry_run: bool,
14291431
run_pre_post_statements: bool = True,
1432+
skip_grants: bool = False,
14301433
) -> None:
14311434
adapter = self.get_adapter(snapshot.model.gateway)
14321435
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1452,14 +1455,13 @@ def _execute_create(
14521455
is_snapshot_representative=is_snapshot_representative,
14531456
dry_run=dry_run,
14541457
physical_properties=rendered_physical_properties,
1458+
skip_grants=skip_grants,
14551459
)
14561460
if run_pre_post_statements:
14571461
evaluation_strategy.run_post_statements(
14581462
snapshot=snapshot, render_kwargs=create_render_kwargs
14591463
)
14601464

1461-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1462-
14631465
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14641466
adapter = self.get_adapter(snapshot.model.gateway)
14651467
return (
@@ -1760,16 +1762,11 @@ def _apply_grants(
17601762
return
17611763

17621764
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1763-
1764-
try:
1765-
self.adapter.sync_grants_config(
1766-
exp.to_table(table_name, dialect=model.dialect),
1767-
grants_config,
1768-
model.grants_table_type,
1769-
)
1770-
except Exception:
1771-
# Log error but don't fail evaluation if grants fail
1772-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1765+
self.adapter.sync_grants_config(
1766+
exp.to_table(table_name, dialect=self.adapter.dialect),
1767+
grants_config,
1768+
model.grants_table_type,
1769+
)
17731770

17741771

17751772
class SymbolicStrategy(EvaluationStrategy):
@@ -1946,6 +1943,10 @@ def create(
19461943
column_descriptions=model.column_descriptions if is_table_deployable else None,
19471944
)
19481945

1946+
# Apply grants after table creation (unless explicitly skipped by caller)
1947+
if not kwargs.get("skip_grants", False):
1948+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1949+
19491950
def migrate(
19501951
self,
19511952
target_table_name: str,
@@ -1971,6 +1972,9 @@ def migrate(
19711972
)
19721973
self.adapter.alter_table(alter_operations)
19731974

1975+
# Apply grants after schema migration
1976+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1977+
19741978
def delete(self, name: str, **kwargs: t.Any) -> None:
19751979
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
19761980
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -2018,6 +2022,10 @@ def _replace_query_for_model(
20182022
source_columns=source_columns,
20192023
)
20202024

2025+
# Apply grants after table replacement (unless explicitly skipped by caller)
2026+
if not kwargs.get("skip_grants", False):
2027+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2028+
20212029
def _get_target_and_source_columns(
20222030
self,
20232031
model: Model,
@@ -2278,12 +2286,18 @@ def create(
22782286
)
22792287
return
22802288

2281-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2289+
# Skip grants in parent create call since we'll apply them after data insertion
2290+
kwargs_no_grants = {**kwargs}
2291+
kwargs_no_grants["skip_grants"] = True
2292+
2293+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
22822294
# For seeds we insert data at the time of table creation.
22832295
try:
22842296
for index, df in enumerate(model.render_seed()):
22852297
if index == 0:
2286-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2298+
self._replace_query_for_model(
2299+
model, table_name, df, render_kwargs, **kwargs_no_grants
2300+
)
22872301
else:
22882302
self.adapter.insert_append(
22892303
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2367,6 +2381,9 @@ def create(
23672381
**kwargs,
23682382
)
23692383

2384+
# Apply grants after SCD Type 2 table creation
2385+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2386+
23702387
def insert(
23712388
self,
23722389
table_name: str,
@@ -2490,7 +2507,7 @@ def insert(
24902507
column_descriptions=model.column_descriptions,
24912508
)
24922509

2493-
# Apply grants after view creation/replacement
2510+
# Apply grants after view creation / replacement
24942511
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24952512

24962513
def append(
@@ -2515,6 +2532,8 @@ def create(
25152532
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25162533
# binding support (because of DROP CASCADE).
25172534
logger.info("View '%s' already exists", table_name)
2535+
# Always apply grants when present, even if view exists, to handle grants updates
2536+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
25182537
return
25192538

25202539
logger.info("Creating view '%s'", table_name)
@@ -2538,6 +2557,9 @@ def create(
25382557
column_descriptions=model.column_descriptions if is_table_deployable else None,
25392558
)
25402559

2560+
# Apply grants after view creation
2561+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2562+
25412563
def migrate(
25422564
self,
25432565
target_table_name: str,
@@ -2857,7 +2879,7 @@ def create(
28572879
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
28582880

28592881
if is_table_deployable and is_snapshot_deployable:
2860-
# We could deploy this to prod; create a proper managed table
2882+
# We cloud deploy this to prod; create a proper managed table
28612883
logger.info("Creating managed table: %s", table_name)
28622884
self.adapter.create_managed_table(
28632885
table_name=table_name,
@@ -2873,17 +2895,23 @@ def create(
28732895

28742896
# Apply grants after managed table creation
28752897
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2898+
28762899
elif not is_table_deployable:
28772900
# Only create the dev preview table as a normal table.
28782901
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
28792902
# Any downstream models that reference it will be updated to point to the dev preview table.
28802903
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2904+
2905+
# Create preview table but don't apply grants here since the table is not deployable
2906+
# Grants will be applied later when the table becomes deployable
2907+
kwargs_no_grants = {**kwargs}
2908+
kwargs_no_grants["skip_grants"] = True
28812909
super().create(
28822910
table_name=table_name,
28832911
model=model,
28842912
is_table_deployable=is_table_deployable,
28852913
render_kwargs=render_kwargs,
2886-
**kwargs,
2914+
**kwargs_no_grants,
28872915
)
28882916

28892917
def insert(
@@ -2898,7 +2926,6 @@ def insert(
28982926
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
28992927
snapshot: Snapshot = kwargs["snapshot"]
29002928
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2901-
29022929
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
29032930
self.adapter.create_managed_table(
29042931
table_name=table_name,
@@ -2911,9 +2938,6 @@ def insert(
29112938
column_descriptions=model.column_descriptions,
29122939
table_format=model.table_format,
29132940
)
2914-
2915-
# Apply grants after managed table creation during first insert
2916-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
29172941
elif not is_snapshot_deployable:
29182942
# Snapshot isnt deployable; update the preview table instead
29192943
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2962,6 +2986,10 @@ def migrate(
29622986
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
29632987
)
29642988

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

0 commit comments

Comments
 (0)