Skip to content

Commit ef2ca9b

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 471f723 commit ef2ca9b

File tree

4 files changed

+941
-100
lines changed

4 files changed

+941
-100
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ def _clone_snapshot_in_dev(
10411041
allow_destructive_snapshots=allow_destructive_snapshots,
10421042
allow_additive_snapshots=allow_additive_snapshots,
10431043
)
1044+
10441045
except Exception:
10451046
adapter.drop_table(target_table_name)
10461047
raise
@@ -1117,6 +1118,7 @@ def _migrate_target_table(
11171118
rendered_physical_properties=rendered_physical_properties,
11181119
dry_run=False,
11191120
run_pre_post_statements=run_pre_post_statements,
1121+
skip_grants=True, # skip grants for tmp table
11201122
)
11211123
try:
11221124
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1382,6 +1384,7 @@ def _execute_create(
13821384
rendered_physical_properties: t.Dict[str, exp.Expression],
13831385
dry_run: bool,
13841386
run_pre_post_statements: bool = True,
1387+
skip_grants: bool = False,
13851388
) -> None:
13861389
adapter = self.get_adapter(snapshot.model.gateway)
13871390
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1405,12 +1408,11 @@ def _execute_create(
14051408
is_snapshot_representative=is_snapshot_representative,
14061409
dry_run=dry_run,
14071410
physical_properties=rendered_physical_properties,
1411+
skip_grants=skip_grants,
14081412
)
14091413
if run_pre_post_statements:
14101414
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14111415

1412-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1413-
14141416
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14151417
adapter = self.get_adapter(snapshot.model.gateway)
14161418
return (
@@ -1678,16 +1680,11 @@ def _apply_grants(
16781680
return
16791681

16801682
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1681-
1682-
try:
1683-
self.adapter.sync_grants_config(
1684-
exp.to_table(table_name, dialect=model.dialect),
1685-
grants_config,
1686-
model.grants_table_type,
1687-
)
1688-
except Exception:
1689-
# Log error but don't fail evaluation if grants fail
1690-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1683+
self.adapter.sync_grants_config(
1684+
exp.to_table(table_name, dialect=self.adapter.dialect),
1685+
grants_config,
1686+
model.grants_table_type,
1687+
)
16911688

16921689

16931690
class SymbolicStrategy(EvaluationStrategy):
@@ -1852,6 +1849,10 @@ def create(
18521849
column_descriptions=model.column_descriptions if is_table_deployable else None,
18531850
)
18541851

1852+
# Apply grants after table creation (unless explicitly skipped by caller)
1853+
if not kwargs.get("skip_grants", False):
1854+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1855+
18551856
def migrate(
18561857
self,
18571858
target_table_name: str,
@@ -1877,6 +1878,9 @@ def migrate(
18771878
)
18781879
self.adapter.alter_table(alter_operations)
18791880

1881+
# Apply grants after schema migration
1882+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1883+
18801884
def delete(self, name: str, **kwargs: t.Any) -> None:
18811885
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
18821886
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1924,6 +1928,10 @@ def _replace_query_for_model(
19241928
source_columns=source_columns,
19251929
)
19261930

1931+
# Apply grants after table replacement (unless explicitly skipped by caller)
1932+
if not kwargs.get("skip_grants", False):
1933+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1934+
19271935
def _get_target_and_source_columns(
19281936
self,
19291937
model: Model,
@@ -2184,12 +2192,18 @@ def create(
21842192
)
21852193
return
21862194

2187-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2195+
# Skip grants in parent create call since we'll apply them after data insertion
2196+
kwargs_no_grants = {**kwargs}
2197+
kwargs_no_grants["skip_grants"] = True
2198+
2199+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
21882200
# For seeds we insert data at the time of table creation.
21892201
try:
21902202
for index, df in enumerate(model.render_seed()):
21912203
if index == 0:
2192-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2204+
self._replace_query_for_model(
2205+
model, table_name, df, render_kwargs, **kwargs_no_grants
2206+
)
21932207
else:
21942208
self.adapter.insert_append(
21952209
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2198,6 +2212,9 @@ def create(
21982212
self.adapter.drop_table(table_name)
21992213
raise
22002214

2215+
# Apply grants after seed table creation or data insertion
2216+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2217+
22012218
def insert(
22022219
self,
22032220
table_name: str,
@@ -2261,6 +2278,9 @@ def create(
22612278
**kwargs,
22622279
)
22632280

2281+
# Apply grants after SCD Type 2 table creation
2282+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2283+
22642284
def insert(
22652285
self,
22662286
table_name: str,
@@ -2384,7 +2404,7 @@ def insert(
23842404
column_descriptions=model.column_descriptions,
23852405
)
23862406

2387-
# Apply grants after view creation/replacement
2407+
# Apply grants after view creation / replacement
23882408
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
23892409

23902410
def append(
@@ -2409,6 +2429,8 @@ def create(
24092429
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24102430
# binding support (because of DROP CASCADE).
24112431
logger.info("View '%s' already exists", table_name)
2432+
# Always apply grants when present, even if view exists, to handle grants updates
2433+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24122434
return
24132435

24142436
logger.info("Creating view '%s'", table_name)
@@ -2432,6 +2454,9 @@ def create(
24322454
column_descriptions=model.column_descriptions if is_table_deployable else None,
24332455
)
24342456

2457+
# Apply grants after view creation
2458+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2459+
24352460
def migrate(
24362461
self,
24372462
target_table_name: str,
@@ -2612,7 +2637,7 @@ def create(
26122637
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26132638

26142639
if is_table_deployable and is_snapshot_deployable:
2615-
# We could deploy this to prod; create a proper managed table
2640+
# We cloud deploy this to prod; create a proper managed table
26162641
logger.info("Creating managed table: %s", table_name)
26172642
self.adapter.create_managed_table(
26182643
table_name=table_name,
@@ -2628,17 +2653,23 @@ def create(
26282653

26292654
# Apply grants after managed table creation
26302655
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2656+
26312657
elif not is_table_deployable:
26322658
# Only create the dev preview table as a normal table.
26332659
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26342660
# Any downstream models that reference it will be updated to point to the dev preview table.
26352661
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2662+
2663+
# Create preview table but don't apply grants here since the table is not deployable
2664+
# Grants will be applied later when the table becomes deployable
2665+
kwargs_no_grants = {**kwargs}
2666+
kwargs_no_grants["skip_grants"] = True
26362667
super().create(
26372668
table_name=table_name,
26382669
model=model,
26392670
is_table_deployable=is_table_deployable,
26402671
render_kwargs=render_kwargs,
2641-
**kwargs,
2672+
**kwargs_no_grants,
26422673
)
26432674

26442675
def insert(
@@ -2653,7 +2684,6 @@ def insert(
26532684
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
26542685
snapshot: Snapshot = kwargs["snapshot"]
26552686
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2656-
26572687
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
26582688
self.adapter.create_managed_table(
26592689
table_name=table_name,
@@ -2666,9 +2696,6 @@ def insert(
26662696
column_descriptions=model.column_descriptions,
26672697
table_format=model.table_format,
26682698
)
2669-
2670-
# Apply grants after managed table creation during first insert
2671-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
26722699
elif not is_snapshot_deployable:
26732700
# Snapshot isnt deployable; update the preview table instead
26742701
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2717,6 +2744,10 @@ def migrate(
27172744
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27182745
)
27192746

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

0 commit comments

Comments
 (0)