@@ -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
16931690class 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