@@ -1054,6 +1054,7 @@ def _clone_snapshot_in_dev(
10541054 allow_additive_snapshots = allow_additive_snapshots ,
10551055 run_pre_post_statements = run_pre_post_statements ,
10561056 )
1057+
10571058 except Exception :
10581059 adapter .drop_table (target_table_name )
10591060 raise
@@ -1154,6 +1155,7 @@ def _migrate_target_table(
11541155 rendered_physical_properties = rendered_physical_properties ,
11551156 dry_run = False ,
11561157 run_pre_post_statements = run_pre_post_statements ,
1158+ skip_grants = True , # skip grants for tmp table
11571159 )
11581160 try :
11591161 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1419,6 +1421,7 @@ def _execute_create(
14191421 rendered_physical_properties : t .Dict [str , exp .Expression ],
14201422 dry_run : bool ,
14211423 run_pre_post_statements : bool = True ,
1424+ skip_grants : bool = False ,
14221425 ) -> None :
14231426 adapter = self .get_adapter (snapshot .model .gateway )
14241427 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1442,12 +1445,11 @@ def _execute_create(
14421445 is_snapshot_representative = is_snapshot_representative ,
14431446 dry_run = dry_run ,
14441447 physical_properties = rendered_physical_properties ,
1448+ skip_grants = skip_grants ,
14451449 )
14461450 if run_pre_post_statements :
14471451 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
14481452
1449- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1450-
14511453 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14521454 adapter = self .get_adapter (snapshot .model .gateway )
14531455 return (
@@ -1716,16 +1718,11 @@ def _apply_grants(
17161718 return
17171719
17181720 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1719-
1720- try :
1721- self .adapter .sync_grants_config (
1722- exp .to_table (table_name , dialect = model .dialect ),
1723- grants_config ,
1724- model .grants_table_type ,
1725- )
1726- except Exception :
1727- # Log error but don't fail evaluation if grants fail
1728- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1721+ self .adapter .sync_grants_config (
1722+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1723+ grants_config ,
1724+ model .grants_table_type ,
1725+ )
17291726
17301727
17311728class SymbolicStrategy (EvaluationStrategy ):
@@ -1890,6 +1887,10 @@ def create(
18901887 column_descriptions = model .column_descriptions if is_table_deployable else None ,
18911888 )
18921889
1890+ # Apply grants after table creation (unless explicitly skipped by caller)
1891+ if not kwargs .get ("skip_grants" , False ):
1892+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1893+
18931894 def migrate (
18941895 self ,
18951896 target_table_name : str ,
@@ -1915,6 +1916,9 @@ def migrate(
19151916 )
19161917 self .adapter .alter_table (alter_operations )
19171918
1919+ # Apply grants after schema migration
1920+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1921+
19181922 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19191923 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
19201924 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -1962,6 +1966,10 @@ def _replace_query_for_model(
19621966 source_columns = source_columns ,
19631967 )
19641968
1969+ # Apply grants after table replacement (unless explicitly skipped by caller)
1970+ if not kwargs .get ("skip_grants" , False ):
1971+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
1972+
19651973 def _get_target_and_source_columns (
19661974 self ,
19671975 model : Model ,
@@ -2222,12 +2230,18 @@ def create(
22222230 )
22232231 return
22242232
2225- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2233+ # Skip grants in parent create call since we'll apply them after data insertion
2234+ kwargs_no_grants = {** kwargs }
2235+ kwargs_no_grants ["skip_grants" ] = True
2236+
2237+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
22262238 # For seeds we insert data at the time of table creation.
22272239 try :
22282240 for index , df in enumerate (model .render_seed ()):
22292241 if index == 0 :
2230- self ._replace_query_for_model (model , table_name , df , render_kwargs , ** kwargs )
2242+ self ._replace_query_for_model (
2243+ model , table_name , df , render_kwargs , ** kwargs_no_grants
2244+ )
22312245 else :
22322246 self .adapter .insert_append (
22332247 table_name , df , target_columns_to_types = model .columns_to_types
@@ -2236,6 +2250,9 @@ def create(
22362250 self .adapter .drop_table (table_name )
22372251 raise
22382252
2253+ # Apply grants after seed table creation or data insertion
2254+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2255+
22392256 def insert (
22402257 self ,
22412258 table_name : str ,
@@ -2299,6 +2316,9 @@ def create(
22992316 ** kwargs ,
23002317 )
23012318
2319+ # Apply grants after SCD Type 2 table creation
2320+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2321+
23022322 def insert (
23032323 self ,
23042324 table_name : str ,
@@ -2422,7 +2442,7 @@ def insert(
24222442 column_descriptions = model .column_descriptions ,
24232443 )
24242444
2425- # Apply grants after view creation/ replacement
2445+ # Apply grants after view creation / replacement
24262446 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24272447
24282448 def append (
@@ -2447,6 +2467,8 @@ def create(
24472467 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24482468 # binding support (because of DROP CASCADE).
24492469 logger .info ("View '%s' already exists" , table_name )
2470+ # Always apply grants when present, even if view exists, to handle grants updates
2471+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24502472 return
24512473
24522474 logger .info ("Creating view '%s'" , table_name )
@@ -2470,6 +2492,9 @@ def create(
24702492 column_descriptions = model .column_descriptions if is_table_deployable else None ,
24712493 )
24722494
2495+ # Apply grants after view creation
2496+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2497+
24732498 def migrate (
24742499 self ,
24752500 target_table_name : str ,
@@ -2650,7 +2675,7 @@ def create(
26502675 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
26512676
26522677 if is_table_deployable and is_snapshot_deployable :
2653- # We could deploy this to prod; create a proper managed table
2678+ # We cloud deploy this to prod; create a proper managed table
26542679 logger .info ("Creating managed table: %s" , table_name )
26552680 self .adapter .create_managed_table (
26562681 table_name = table_name ,
@@ -2666,17 +2691,23 @@ def create(
26662691
26672692 # Apply grants after managed table creation
26682693 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2694+
26692695 elif not is_table_deployable :
26702696 # Only create the dev preview table as a normal table.
26712697 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26722698 # Any downstream models that reference it will be updated to point to the dev preview table.
26732699 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2700+
2701+ # Create preview table but don't apply grants here since the table is not deployable
2702+ # Grants will be applied later when the table becomes deployable
2703+ kwargs_no_grants = {** kwargs }
2704+ kwargs_no_grants ["skip_grants" ] = True
26742705 super ().create (
26752706 table_name = table_name ,
26762707 model = model ,
26772708 is_table_deployable = is_table_deployable ,
26782709 render_kwargs = render_kwargs ,
2679- ** kwargs ,
2710+ ** kwargs_no_grants ,
26802711 )
26812712
26822713 def insert (
@@ -2691,7 +2722,6 @@ def insert(
26912722 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
26922723 snapshot : Snapshot = kwargs ["snapshot" ]
26932724 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2694-
26952725 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
26962726 self .adapter .create_managed_table (
26972727 table_name = table_name ,
@@ -2704,9 +2734,6 @@ def insert(
27042734 column_descriptions = model .column_descriptions ,
27052735 table_format = model .table_format ,
27062736 )
2707-
2708- # Apply grants after managed table creation during first insert
2709- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
27102737 elif not is_snapshot_deployable :
27112738 # Snapshot isnt deployable; update the preview table instead
27122739 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2755,6 +2782,10 @@ def migrate(
27552782 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
27562783 )
27572784
2785+ # Apply grants after verifying no schema changes
2786+ # This ensures metadata-only changes (grants) are applied
2787+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2788+
27582789 def delete (self , name : str , ** kwargs : t .Any ) -> None :
27592790 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
27602791 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments