Skip to content

Commit 4e42ba1

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 603cef1 commit 4e42ba1

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
@@ -1067,6 +1067,7 @@ def _clone_snapshot_in_dev(
10671067
allow_additive_snapshots=allow_additive_snapshots,
10681068
run_pre_post_statements=run_pre_post_statements,
10691069
)
1070+
10701071
except Exception:
10711072
adapter.drop_table(target_table_name)
10721073
raise
@@ -1167,6 +1168,7 @@ def _migrate_target_table(
11671168
rendered_physical_properties=rendered_physical_properties,
11681169
dry_run=False,
11691170
run_pre_post_statements=run_pre_post_statements,
1171+
skip_grants=True, # skip grants for tmp table
11701172
)
11711173
try:
11721174
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1432,6 +1434,7 @@ def _execute_create(
14321434
rendered_physical_properties: t.Dict[str, exp.Expression],
14331435
dry_run: bool,
14341436
run_pre_post_statements: bool = True,
1437+
skip_grants: bool = False,
14351438
) -> None:
14361439
adapter = self.get_adapter(snapshot.model.gateway)
14371440
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1457,14 +1460,13 @@ def _execute_create(
14571460
is_snapshot_representative=is_snapshot_representative,
14581461
dry_run=dry_run,
14591462
physical_properties=rendered_physical_properties,
1463+
skip_grants=skip_grants,
14601464
)
14611465
if run_pre_post_statements:
14621466
evaluation_strategy.run_post_statements(
14631467
snapshot=snapshot, render_kwargs=create_render_kwargs
14641468
)
14651469

1466-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1467-
14681470
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14691471
adapter = self.get_adapter(snapshot.model.gateway)
14701472
return (
@@ -1819,16 +1821,11 @@ def _apply_grants(
18191821
return
18201822

18211823
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1822-
1823-
try:
1824-
self.adapter.sync_grants_config(
1825-
exp.to_table(table_name, dialect=model.dialect),
1826-
grants_config,
1827-
model.grants_table_type,
1828-
)
1829-
except Exception:
1830-
# Log error but don't fail evaluation if grants fail
1831-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1824+
self.adapter.sync_grants_config(
1825+
exp.to_table(table_name, dialect=self.adapter.dialect),
1826+
grants_config,
1827+
model.grants_table_type,
1828+
)
18321829

18331830

18341831
class SymbolicStrategy(EvaluationStrategy):
@@ -2005,6 +2002,10 @@ def create(
20052002
column_descriptions=model.column_descriptions if is_table_deployable else None,
20062003
)
20072004

2005+
# Apply grants after table creation (unless explicitly skipped by caller)
2006+
if not kwargs.get("skip_grants", False):
2007+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2008+
20082009
def migrate(
20092010
self,
20102011
target_table_name: str,
@@ -2030,6 +2031,9 @@ def migrate(
20302031
)
20312032
self.adapter.alter_table(alter_operations)
20322033

2034+
# Apply grants after schema migration
2035+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
2036+
20332037
def delete(self, name: str, **kwargs: t.Any) -> None:
20342038
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
20352039
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -2077,6 +2081,10 @@ def _replace_query_for_model(
20772081
source_columns=source_columns,
20782082
)
20792083

2084+
# Apply grants after table replacement (unless explicitly skipped by caller)
2085+
if not kwargs.get("skip_grants", False):
2086+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
2087+
20802088
def _get_target_and_source_columns(
20812089
self,
20822090
model: Model,
@@ -2337,12 +2345,18 @@ def create(
23372345
)
23382346
return
23392347

2340-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2348+
# Skip grants in parent create call since we'll apply them after data insertion
2349+
kwargs_no_grants = {**kwargs}
2350+
kwargs_no_grants["skip_grants"] = True
2351+
2352+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
23412353
# For seeds we insert data at the time of table creation.
23422354
try:
23432355
for index, df in enumerate(model.render_seed()):
23442356
if index == 0:
2345-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2357+
self._replace_query_for_model(
2358+
model, table_name, df, render_kwargs, **kwargs_no_grants
2359+
)
23462360
else:
23472361
self.adapter.insert_append(
23482362
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2426,6 +2440,9 @@ def create(
24262440
**kwargs,
24272441
)
24282442

2443+
# Apply grants after SCD Type 2 table creation
2444+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2445+
24292446
def insert(
24302447
self,
24312448
table_name: str,
@@ -2549,7 +2566,7 @@ def insert(
25492566
column_descriptions=model.column_descriptions,
25502567
)
25512568

2552-
# Apply grants after view creation/replacement
2569+
# Apply grants after view creation / replacement
25532570
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
25542571

25552572
def append(
@@ -2574,6 +2591,8 @@ def create(
25742591
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25752592
# binding support (because of DROP CASCADE).
25762593
logger.info("View '%s' already exists", table_name)
2594+
# Always apply grants when present, even if view exists, to handle grants updates
2595+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
25772596
return
25782597

25792598
logger.info("Creating view '%s'", table_name)
@@ -2597,6 +2616,9 @@ def create(
25972616
column_descriptions=model.column_descriptions if is_table_deployable else None,
25982617
)
25992618

2619+
# Apply grants after view creation
2620+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2621+
26002622
def migrate(
26012623
self,
26022624
target_table_name: str,
@@ -2916,7 +2938,7 @@ def create(
29162938
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
29172939

29182940
if is_table_deployable and is_snapshot_deployable:
2919-
# We could deploy this to prod; create a proper managed table
2941+
# We cloud deploy this to prod; create a proper managed table
29202942
logger.info("Creating managed table: %s", table_name)
29212943
self.adapter.create_managed_table(
29222944
table_name=table_name,
@@ -2932,17 +2954,23 @@ def create(
29322954

29332955
# Apply grants after managed table creation
29342956
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2957+
29352958
elif not is_table_deployable:
29362959
# Only create the dev preview table as a normal table.
29372960
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
29382961
# Any downstream models that reference it will be updated to point to the dev preview table.
29392962
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2963+
2964+
# Create preview table but don't apply grants here since the table is not deployable
2965+
# Grants will be applied later when the table becomes deployable
2966+
kwargs_no_grants = {**kwargs}
2967+
kwargs_no_grants["skip_grants"] = True
29402968
super().create(
29412969
table_name=table_name,
29422970
model=model,
29432971
is_table_deployable=is_table_deployable,
29442972
render_kwargs=render_kwargs,
2945-
**kwargs,
2973+
**kwargs_no_grants,
29462974
)
29472975

29482976
def insert(
@@ -2957,7 +2985,6 @@ def insert(
29572985
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
29582986
snapshot: Snapshot = kwargs["snapshot"]
29592987
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2960-
29612988
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
29622989
self.adapter.create_managed_table(
29632990
table_name=table_name,
@@ -2970,9 +2997,6 @@ def insert(
29702997
column_descriptions=model.column_descriptions,
29712998
table_format=model.table_format,
29722999
)
2973-
2974-
# Apply grants after managed table creation during first insert
2975-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
29763000
elif not is_snapshot_deployable:
29773001
# Snapshot isnt deployable; update the preview table instead
29783002
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -3021,6 +3045,10 @@ def migrate(
30213045
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
30223046
)
30233047

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

0 commit comments

Comments
 (0)