Skip to content

Commit 8a93de3

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 9f29630 commit 8a93de3

File tree

4 files changed

+940
-100
lines changed

4 files changed

+940
-100
lines changed

sqlmesh/core/snapshot/evaluator.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ def _clone_snapshot_in_dev(
10671067
allow_destructive_snapshots=allow_destructive_snapshots,
10681068
allow_additive_snapshots=allow_additive_snapshots,
10691069
)
1070+
10701071
except Exception:
10711072
adapter.drop_table(target_table_name)
10721073
raise
@@ -1143,6 +1144,7 @@ def _migrate_target_table(
11431144
rendered_physical_properties=rendered_physical_properties,
11441145
dry_run=False,
11451146
run_pre_post_statements=run_pre_post_statements,
1147+
skip_grants=True, # skip grants for tmp table
11461148
)
11471149
try:
11481150
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1408,6 +1410,7 @@ def _execute_create(
14081410
rendered_physical_properties: t.Dict[str, exp.Expression],
14091411
dry_run: bool,
14101412
run_pre_post_statements: bool = True,
1413+
skip_grants: bool = False,
14111414
) -> None:
14121415
adapter = self.get_adapter(snapshot.model.gateway)
14131416
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
@@ -1431,12 +1434,11 @@ def _execute_create(
14311434
is_snapshot_representative=is_snapshot_representative,
14321435
dry_run=dry_run,
14331436
physical_properties=rendered_physical_properties,
1437+
skip_grants=skip_grants,
14341438
)
14351439
if run_pre_post_statements:
14361440
adapter.execute(snapshot.model.render_post_statements(**create_render_kwargs))
14371441

1438-
evaluation_strategy._apply_grants(snapshot.model, table_name, GrantsTargetLayer.PHYSICAL)
1439-
14401442
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
14411443
adapter = self.get_adapter(snapshot.model.gateway)
14421444
return (
@@ -1648,16 +1650,11 @@ def _apply_grants(
16481650
return
16491651

16501652
logger.info(f"Applying grants for model {model.name} to table {table_name}")
1651-
1652-
try:
1653-
self.adapter.sync_grants_config(
1654-
exp.to_table(table_name, dialect=model.dialect),
1655-
grants_config,
1656-
model.grants_table_type,
1657-
)
1658-
except Exception:
1659-
# Log error but don't fail evaluation if grants fail
1660-
logger.error(f"Failed to apply grants for model {model.name}", exc_info=True)
1653+
self.adapter.sync_grants_config(
1654+
exp.to_table(table_name, dialect=self.adapter.dialect),
1655+
grants_config,
1656+
model.grants_table_type,
1657+
)
16611658

16621659

16631660
class SymbolicStrategy(EvaluationStrategy):
@@ -1822,6 +1819,10 @@ def create(
18221819
column_descriptions=model.column_descriptions if is_table_deployable else None,
18231820
)
18241821

1822+
# Apply grants after table creation (unless explicitly skipped by caller)
1823+
if not kwargs.get("skip_grants", False):
1824+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
1825+
18251826
def migrate(
18261827
self,
18271828
target_table_name: str,
@@ -1847,6 +1848,9 @@ def migrate(
18471848
)
18481849
self.adapter.alter_table(alter_operations)
18491850

1851+
# Apply grants after schema migration
1852+
self._apply_grants(snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL)
1853+
18501854
def delete(self, name: str, **kwargs: t.Any) -> None:
18511855
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
18521856
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
@@ -1894,6 +1898,10 @@ def _replace_query_for_model(
18941898
source_columns=source_columns,
18951899
)
18961900

1901+
# Apply grants after table replacement (unless explicitly skipped by caller)
1902+
if not kwargs.get("skip_grants", False):
1903+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL)
1904+
18971905
def _get_target_and_source_columns(
18981906
self,
18991907
model: Model,
@@ -2154,14 +2162,19 @@ def create(
21542162
)
21552163
return
21562164

2157-
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs)
2165+
# Skip grants in parent create call since we'll apply them after data insertion
2166+
kwargs_no_grants = {**kwargs}
2167+
kwargs_no_grants["skip_grants"] = True
2168+
super().create(table_name, model, is_table_deployable, render_kwargs, **kwargs_no_grants)
2169+
21582170
if is_table_deployable:
21592171
# For seeds we insert data at the time of table creation.
21602172
try:
21612173
for index, df in enumerate(model.render_seed()):
21622174
if index == 0:
2175+
# Skip grants in replace_query call since we'll apply them at the end
21632176
self._replace_query_for_model(
2164-
model, table_name, df, render_kwargs, **kwargs
2177+
model, table_name, df, render_kwargs, **kwargs_no_grants
21652178
)
21662179
else:
21672180
self.adapter.insert_append(
@@ -2171,6 +2184,9 @@ def create(
21712184
self.adapter.drop_table(table_name)
21722185
raise
21732186

2187+
# Apply grants after seed table creation or data insertion
2188+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2189+
21742190
def insert(
21752191
self,
21762192
table_name: str,
@@ -2234,6 +2250,9 @@ def create(
22342250
**kwargs,
22352251
)
22362252

2253+
# Apply grants after SCD Type 2 table creation
2254+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2255+
22372256
def insert(
22382257
self,
22392258
table_name: str,
@@ -2384,7 +2403,7 @@ def insert(
23842403
column_descriptions=model.column_descriptions,
23852404
)
23862405

2387-
# Apply grants after view creation/replacement
2406+
# Apply grants after view creation / replacement
23882407
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
23892408

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

24142435
logger.info("Creating view '%s'", table_name)
@@ -2432,6 +2453,9 @@ def create(
24322453
column_descriptions=model.column_descriptions if is_table_deployable else None,
24332454
)
24342455

2456+
# Apply grants after view creation
2457+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2458+
24352459
def migrate(
24362460
self,
24372461
target_table_name: str,
@@ -2612,7 +2636,7 @@ def create(
26122636
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
26132637

26142638
if is_table_deployable and is_snapshot_deployable:
2615-
# We could deploy this to prod; create a proper managed table
2639+
# We cloud deploy this to prod; create a proper managed table
26162640
logger.info("Creating managed table: %s", table_name)
26172641
self.adapter.create_managed_table(
26182642
table_name=table_name,
@@ -2628,17 +2652,23 @@ def create(
26282652

26292653
# Apply grants after managed table creation
26302654
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
2655+
26312656
elif not is_table_deployable:
26322657
# Only create the dev preview table as a normal table.
26332658
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26342659
# Any downstream models that reference it will be updated to point to the dev preview table.
26352660
# If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2661+
2662+
# Create preview table but don't apply grants here since the table is not deployable
2663+
# Grants will be applied later when the table becomes deployable
2664+
kwargs_no_grants = {**kwargs}
2665+
kwargs_no_grants["skip_grants"] = True
26362666
super().create(
26372667
table_name=table_name,
26382668
model=model,
26392669
is_table_deployable=is_table_deployable,
26402670
render_kwargs=render_kwargs,
2641-
**kwargs,
2671+
**kwargs_no_grants,
26422672
)
26432673

26442674
def insert(
@@ -2653,7 +2683,6 @@ def insert(
26532683
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
26542684
snapshot: Snapshot = kwargs["snapshot"]
26552685
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
2656-
26572686
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
26582687
self.adapter.create_managed_table(
26592688
table_name=table_name,
@@ -2666,9 +2695,6 @@ def insert(
26662695
column_descriptions=model.column_descriptions,
26672696
table_format=model.table_format,
26682697
)
2669-
2670-
# Apply grants after managed table creation during first insert
2671-
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL)
26722698
elif not is_snapshot_deployable:
26732699
# Snapshot isnt deployable; update the preview table instead
26742700
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2717,6 +2743,10 @@ def migrate(
27172743
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
27182744
)
27192745

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

0 commit comments

Comments
 (0)