Skip to content

Commit eee6bde

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 0b7c918 commit eee6bde

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
@@ -1056,6 +1056,7 @@ def _clone_snapshot_in_dev(
10561056
allow_additive_snapshots=allow_additive_snapshots,
10571057
run_pre_post_statements=run_pre_post_statements,
10581058
)
1059+
10591060
except Exception:
10601061
adapter.drop_table(target_table_name)
10611062
raise
@@ -1156,6 +1157,7 @@ def _migrate_target_table(
11561157
rendered_physical_properties=rendered_physical_properties,
11571158
dry_run=False,
11581159
run_pre_post_statements=run_pre_post_statements,
1160+
skip_grants=True, # skip grants for tmp table
11591161
)
11601162
try:
11611163
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1421,6 +1423,7 @@ def _execute_create(
14211423
rendered_physical_properties: t.Dict[str, exp.Expression],
14221424
dry_run: bool,
14231425
run_pre_post_statements: bool = True,
1426+
skip_grants: bool = False,
14241427
) -> None:
14251428
adapter = self.get_adapter(snapshot.model.gateway)
14261429
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1444,12 +1447,11 @@ def _execute_create(
14441447
is_snapshot_representative=is_snapshot_representative,
14451448
dry_run=dry_run,
14461449
physical_properties=rendered_physical_properties,
1450+
skip_grants=skip_grants,
14471451
)
14481452
if run_pre_post_statements:
14491453
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14501454

1451-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1452-
14531455
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14541456
adapter = self.get_adapter(snapshot.model.gateway)
14551457
return (
@@ -1718,16 +1720,11 @@ def _apply_grants(
17181720
return
17191721

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

17321729

17331730
class SymbolicStrategy(EvaluationStrategy):
@@ -1892,6 +1889,10 @@ def create(
18921889
column_descriptions=model.column_descriptions if is_table_deployable else None,
18931890
)
18941891

1892+
# Apply grants after table creation (unless explicitly skipped by caller)
1893+
if not kwargs.get("skip_grants", False):
1894+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1895+
18951896
def migrate(
18961897
self,
18971898
target_table_name: str,
@@ -1917,6 +1918,9 @@ def migrate(
19171918
)
19181919
self.adapter.alter_table(alter_operations)
19191920

1921+
# Apply grants after schema migration
1922+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1923+
19201924
def delete(self, name: str, **kwargs: t.Any) -> None:
19211925
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
19221926
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1964,6 +1968,10 @@ def _replace_query_for_model(
19641968
source_columns=source_columns,
19651969
)
19661970

1971+
# Apply grants after table replacement (unless explicitly skipped by caller)
1972+
if not kwargs.get("skip_grants", False):
1973+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1974+
19671975
def _get_target_and_source_columns(
19681976
self,
19691977
model: Model,
@@ -2224,12 +2232,18 @@ def create(
22242232
)
22252233
return
22262234

2227-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2235+
# Skip grants in parent create call since we'll apply them after data insertion
2236+
kwargs_no_grants = {**kwargs}
2237+
kwargs_no_grants["skip_grants"] = True
2238+
2239+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
22282240
# For seeds we insert data at the time of table creation.
22292241
try:
22302242
for index, df in enumerate(model.render_seed()):
22312243
if index == 0:
2232-
self._replace_query_for_model(model, table_name, df, render_kwargs, **kwargs)
2244+
self._replace_query_for_model(
2245+
model, table_name, df, render_kwargs, **kwargs_no_grants
2246+
)
22332247
else:
22342248
self.adapter.insert_append(
22352249
table_name, df, target_columns_to_types=model.columns_to_types
@@ -2313,6 +2327,9 @@ def create(
23132327
**kwargs,
23142328
)
23152329

2330+
# Apply grants after SCD Type 2 table creation
2331+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2332+
23162333
def insert(
23172334
self,
23182335
table_name: str,
@@ -2436,7 +2453,7 @@ def insert(
24362453
column_descriptions=model.column_descriptions,
24372454
)
24382455

2439-
# Apply grants after view creation/replacement
2456+
# Apply grants after view creation / replacement
24402457
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24412458

24422459
def append(
@@ -2461,6 +2478,8 @@ def create(
24612478
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24622479
# binding support (because of DROP CASCADE).
24632480
logger.info("View '%s' already exists", table_name)
2481+
# Always apply grants when present, even if view exists, to handle grants updates
2482+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
24642483
return
24652484

24662485
logger.info("Creating view '%s'", table_name)
@@ -2484,6 +2503,9 @@ def create(
24842503
column_descriptions=model.column_descriptions if is_table_deployable else None,
24852504
)
24862505

2506+
# Apply grants after view creation
2507+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2508+
24872509
def migrate(
24882510
self,
24892511
target_table_name: str,
@@ -2664,7 +2686,7 @@ def create(
26642686
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26652687

26662688
if is_table_deployable and is_snapshot_deployable:
2667-
# We could deploy this to prod; create a proper managed table
2689+
# We cloud deploy this to prod; create a proper managed table
26682690
logger.info("Creating managed table: %s", table_name)
26692691
self.adapter.create_managed_table(
26702692
table_name=table_name,
@@ -2680,17 +2702,23 @@ def create(
26802702

26812703
# Apply grants after managed table creation
26822704
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2705+
26832706
elif not is_table_deployable:
26842707
# Only create the dev preview table as a normal table.
26852708
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26862709
# Any downstream models that reference it will be updated to point to the dev preview table.
26872710
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2711+
2712+
# Create preview table but don't apply grants here since the table is not deployable
2713+
# Grants will be applied later when the table becomes deployable
2714+
kwargs_no_grants = {**kwargs}
2715+
kwargs_no_grants["skip_grants"] = True
26882716
super().create(
26892717
table_name=table_name,
26902718
model=model,
26912719
is_table_deployable=is_table_deployable,
26922720
render_kwargs=render_kwargs,
2693-
**kwargs,
2721+
**kwargs_no_grants,
26942722
)
26952723

26962724
def insert(
@@ -2705,7 +2733,6 @@ def insert(
27052733
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
27062734
snapshot: Snapshot = kwargs["snapshot"]
27072735
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2708-
27092736
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
27102737
self.adapter.create_managed_table(
27112738
table_name=table_name,
@@ -2718,9 +2745,6 @@ def insert(
27182745
column_descriptions=model.column_descriptions,
27192746
table_format=model.table_format,
27202747
)
2721-
2722-
# Apply grants after managed table creation during first insert
2723-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
27242748
elif not is_snapshot_deployable:
27252749
# Snapshot isnt deployable; update the preview table instead
27262750
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2769,6 +2793,10 @@ def migrate(
27692793
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27702794
)
27712795

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

0 commit comments

Comments
 (0)