@@ -1041,6 +1041,7 @@ def _clone_snapshot_in_dev(
1041
1041
allow_destructive_snapshots = allow_destructive_snapshots ,
1042
1042
allow_additive_snapshots = allow_additive_snapshots ,
1043
1043
)
1044
+
1044
1045
except Exception :
1045
1046
adapter .drop_table (target_table_name )
1046
1047
raise
@@ -1117,6 +1118,7 @@ def _migrate_target_table(
1117
1118
rendered_physical_properties = rendered_physical_properties ,
1118
1119
dry_run = False ,
1119
1120
run_pre_post_statements = run_pre_post_statements ,
1121
+ skip_grants = True , # skip grants for tmp table
1120
1122
)
1121
1123
try :
1122
1124
evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1382,6 +1384,7 @@ def _execute_create(
1382
1384
rendered_physical_properties : t .Dict [str , exp .Expression ],
1383
1385
dry_run : bool ,
1384
1386
run_pre_post_statements : bool = True ,
1387
+ skip_grants : bool = False ,
1385
1388
) -> None :
1386
1389
adapter = self .get_adapter (snapshot .model .gateway )
1387
1390
evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1405,12 +1408,11 @@ def _execute_create(
1405
1408
is_snapshot_representative = is_snapshot_representative ,
1406
1409
dry_run = dry_run ,
1407
1410
physical_properties = rendered_physical_properties ,
1411
+ skip_grants = skip_grants ,
1408
1412
)
1409
1413
if run_pre_post_statements :
1410
1414
adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
1411
1415
1412
- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1413
-
1414
1416
def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
1415
1417
adapter = self .get_adapter (snapshot .model .gateway )
1416
1418
return (
@@ -1678,16 +1680,11 @@ def _apply_grants(
1678
1680
return
1679
1681
1680
1682
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
+ )
1691
1688
1692
1689
1693
1690
class SymbolicStrategy (EvaluationStrategy ):
@@ -1852,6 +1849,10 @@ def create(
1852
1849
column_descriptions = model .column_descriptions if is_table_deployable else None ,
1853
1850
)
1854
1851
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
+
1855
1856
def migrate (
1856
1857
self ,
1857
1858
target_table_name : str ,
@@ -1877,6 +1878,9 @@ def migrate(
1877
1878
)
1878
1879
self .adapter .alter_table (alter_operations )
1879
1880
1881
+ # Apply grants after schema migration
1882
+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1883
+
1880
1884
def delete (self , name : str , ** kwargs : t .Any ) -> None :
1881
1885
_check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
1882
1886
self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -1924,6 +1928,10 @@ def _replace_query_for_model(
1924
1928
source_columns = source_columns ,
1925
1929
)
1926
1930
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
+
1927
1935
def _get_target_and_source_columns (
1928
1936
self ,
1929
1937
model : Model ,
@@ -2184,12 +2192,18 @@ def create(
2184
2192
)
2185
2193
return
2186
2194
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 )
2188
2200
# For seeds we insert data at the time of table creation.
2189
2201
try :
2190
2202
for index , df in enumerate (model .render_seed ()):
2191
2203
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
+ )
2193
2207
else :
2194
2208
self .adapter .insert_append (
2195
2209
table_name , df , target_columns_to_types = model .columns_to_types
@@ -2198,6 +2212,9 @@ def create(
2198
2212
self .adapter .drop_table (table_name )
2199
2213
raise
2200
2214
2215
+ # Apply grants after seed table creation or data insertion
2216
+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2217
+
2201
2218
def insert (
2202
2219
self ,
2203
2220
table_name : str ,
@@ -2261,6 +2278,9 @@ def create(
2261
2278
** kwargs ,
2262
2279
)
2263
2280
2281
+ # Apply grants after SCD Type 2 table creation
2282
+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2283
+
2264
2284
def insert (
2265
2285
self ,
2266
2286
table_name : str ,
@@ -2384,7 +2404,7 @@ def insert(
2384
2404
column_descriptions = model .column_descriptions ,
2385
2405
)
2386
2406
2387
- # Apply grants after view creation/ replacement
2407
+ # Apply grants after view creation / replacement
2388
2408
self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2389
2409
2390
2410
def append (
@@ -2409,6 +2429,8 @@ def create(
2409
2429
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
2410
2430
# binding support (because of DROP CASCADE).
2411
2431
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 )
2412
2434
return
2413
2435
2414
2436
logger .info ("Creating view '%s'" , table_name )
@@ -2432,6 +2454,9 @@ def create(
2432
2454
column_descriptions = model .column_descriptions if is_table_deployable else None ,
2433
2455
)
2434
2456
2457
+ # Apply grants after view creation
2458
+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2459
+
2435
2460
def migrate (
2436
2461
self ,
2437
2462
target_table_name : str ,
@@ -2612,7 +2637,7 @@ def create(
2612
2637
is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2613
2638
2614
2639
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
2616
2641
logger .info ("Creating managed table: %s" , table_name )
2617
2642
self .adapter .create_managed_table (
2618
2643
table_name = table_name ,
@@ -2628,17 +2653,23 @@ def create(
2628
2653
2629
2654
# Apply grants after managed table creation
2630
2655
self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2656
+
2631
2657
elif not is_table_deployable :
2632
2658
# Only create the dev preview table as a normal table.
2633
2659
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
2634
2660
# Any downstream models that reference it will be updated to point to the dev preview table.
2635
2661
# 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
2636
2667
super ().create (
2637
2668
table_name = table_name ,
2638
2669
model = model ,
2639
2670
is_table_deployable = is_table_deployable ,
2640
2671
render_kwargs = render_kwargs ,
2641
- ** kwargs ,
2672
+ ** kwargs_no_grants ,
2642
2673
)
2643
2674
2644
2675
def insert (
@@ -2653,7 +2684,6 @@ def insert(
2653
2684
deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
2654
2685
snapshot : Snapshot = kwargs ["snapshot" ]
2655
2686
is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2656
-
2657
2687
if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
2658
2688
self .adapter .create_managed_table (
2659
2689
table_name = table_name ,
@@ -2666,9 +2696,6 @@ def insert(
2666
2696
column_descriptions = model .column_descriptions ,
2667
2697
table_format = model .table_format ,
2668
2698
)
2669
-
2670
- # Apply grants after managed table creation during first insert
2671
- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2672
2699
elif not is_snapshot_deployable :
2673
2700
# Snapshot isnt deployable; update the preview table instead
2674
2701
# 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(
2717
2744
f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
2718
2745
)
2719
2746
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
+
2720
2751
def delete (self , name : str , ** kwargs : t .Any ) -> None :
2721
2752
# a dev preview table is created as a normal table, so it needs to be dropped as a normal table
2722
2753
_check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments