@@ -1050,6 +1050,7 @@ def _clone_snapshot_in_dev(
10501050 allow_destructive_snapshots = allow_destructive_snapshots ,
10511051 allow_additive_snapshots = allow_additive_snapshots ,
10521052 )
1053+
10531054 except Exception :
10541055 adapter .drop_table (target_table_name )
10551056 raise
@@ -1150,6 +1151,7 @@ def _migrate_target_table(
11501151 rendered_physical_properties = rendered_physical_properties ,
11511152 dry_run = False ,
11521153 run_pre_post_statements = run_pre_post_statements ,
1154+ skip_grants = True , # skip grants for tmp table
11531155 )
11541156 try :
11551157 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1415,6 +1417,7 @@ def _execute_create(
14151417 rendered_physical_properties : t .Dict [str , exp .Expression ],
14161418 dry_run : bool ,
14171419 run_pre_post_statements : bool = True ,
1420+ skip_grants : bool = False ,
14181421 ) -> None :
14191422 adapter = self .get_adapter (snapshot .model .gateway )
14201423 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1438,12 +1441,11 @@ def _execute_create(
14381441 is_snapshot_representative = is_snapshot_representative ,
14391442 dry_run = dry_run ,
14401443 physical_properties = rendered_physical_properties ,
1444+ skip_grants = skip_grants ,
14411445 )
14421446 if run_pre_post_statements :
14431447 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
14441448
1445- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1446-
14471449 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14481450 adapter = self .get_adapter (snapshot .model .gateway )
14491451 return (
@@ -1712,16 +1714,11 @@ def _apply_grants(
17121714 return
17131715
17141716 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1715-
1716- try :
1717- self .adapter .sync_grants_config (
1718- exp .to_table (table_name , dialect = model .dialect ),
1719- grants_config ,
1720- model .grants_table_type ,
1721- )
1722- except Exception :
1723- # Log error but don't fail evaluation if grants fail
1724- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1717+ self .adapter .sync_grants_config (
1718+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1719+ grants_config ,
1720+ model .grants_table_type ,
1721+ )
17251722
17261723
17271724class SymbolicStrategy (EvaluationStrategy ):
@@ -1886,6 +1883,10 @@ def create(
18861883 column_descriptions = model .column_descriptions if is_table_deployable else None ,
18871884 )
18881885
1886+ # Apply grants after table creation (unless explicitly skipped by caller)
1887+ if not kwargs .get ("skip_grants" , False ):
1888+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1889+
18891890 def migrate (
18901891 self ,
18911892 target_table_name : str ,
@@ -1911,6 +1912,9 @@ def migrate(
19111912 )
19121913 self .adapter .alter_table (alter_operations )
19131914
1915+ # Apply grants after schema migration
1916+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1917+
19141918 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19151919 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
19161920 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -1958,6 +1962,10 @@ def _replace_query_for_model(
19581962 source_columns = source_columns ,
19591963 )
19601964
1965+ # Apply grants after table replacement (unless explicitly skipped by caller)
1966+ if not kwargs .get ("skip_grants" , False ):
1967+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
1968+
19611969 def _get_target_and_source_columns (
19621970 self ,
19631971 model : Model ,
@@ -2218,12 +2226,18 @@ def create(
22182226 )
22192227 return
22202228
2221- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2229+ # Skip grants in parent create call since we'll apply them after data insertion
2230+ kwargs_no_grants = {** kwargs }
2231+ kwargs_no_grants ["skip_grants" ] = True
2232+
2233+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
22222234 # For seeds we insert data at the time of table creation.
22232235 try :
22242236 for index , df in enumerate (model .render_seed ()):
22252237 if index == 0 :
2226- self ._replace_query_for_model (model , table_name , df , render_kwargs , ** kwargs )
2238+ self ._replace_query_for_model (
2239+ model , table_name , df , render_kwargs , ** kwargs_no_grants
2240+ )
22272241 else :
22282242 self .adapter .insert_append (
22292243 table_name , df , target_columns_to_types = model .columns_to_types
@@ -2232,6 +2246,9 @@ def create(
22322246 self .adapter .drop_table (table_name )
22332247 raise
22342248
2249+ # Apply grants after seed table creation or data insertion
2250+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2251+
22352252 def insert (
22362253 self ,
22372254 table_name : str ,
@@ -2295,6 +2312,9 @@ def create(
22952312 ** kwargs ,
22962313 )
22972314
2315+ # Apply grants after SCD Type 2 table creation
2316+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2317+
22982318 def insert (
22992319 self ,
23002320 table_name : str ,
@@ -2418,7 +2438,7 @@ def insert(
24182438 column_descriptions = model .column_descriptions ,
24192439 )
24202440
2421- # Apply grants after view creation/ replacement
2441+ # Apply grants after view creation / replacement
24222442 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24232443
24242444 def append (
@@ -2443,6 +2463,8 @@ def create(
24432463 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24442464 # binding support (because of DROP CASCADE).
24452465 logger .info ("View '%s' already exists" , table_name )
2466+ # Always apply grants when present, even if view exists, to handle grants updates
2467+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24462468 return
24472469
24482470 logger .info ("Creating view '%s'" , table_name )
@@ -2466,6 +2488,9 @@ def create(
24662488 column_descriptions = model .column_descriptions if is_table_deployable else None ,
24672489 )
24682490
2491+ # Apply grants after view creation
2492+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2493+
24692494 def migrate (
24702495 self ,
24712496 target_table_name : str ,
@@ -2646,7 +2671,7 @@ def create(
26462671 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
26472672
26482673 if is_table_deployable and is_snapshot_deployable :
2649- # We could deploy this to prod; create a proper managed table
2674+ # We cloud deploy this to prod; create a proper managed table
26502675 logger .info ("Creating managed table: %s" , table_name )
26512676 self .adapter .create_managed_table (
26522677 table_name = table_name ,
@@ -2662,17 +2687,23 @@ def create(
26622687
26632688 # Apply grants after managed table creation
26642689 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2690+
26652691 elif not is_table_deployable :
26662692 # Only create the dev preview table as a normal table.
26672693 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26682694 # Any downstream models that reference it will be updated to point to the dev preview table.
26692695 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2696+
2697+ # Create preview table but don't apply grants here since the table is not deployable
2698+ # Grants will be applied later when the table becomes deployable
2699+ kwargs_no_grants = {** kwargs }
2700+ kwargs_no_grants ["skip_grants" ] = True
26702701 super ().create (
26712702 table_name = table_name ,
26722703 model = model ,
26732704 is_table_deployable = is_table_deployable ,
26742705 render_kwargs = render_kwargs ,
2675- ** kwargs ,
2706+ ** kwargs_no_grants ,
26762707 )
26772708
26782709 def insert (
@@ -2687,7 +2718,6 @@ def insert(
26872718 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
26882719 snapshot : Snapshot = kwargs ["snapshot" ]
26892720 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2690-
26912721 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
26922722 self .adapter .create_managed_table (
26932723 table_name = table_name ,
@@ -2700,9 +2730,6 @@ def insert(
27002730 column_descriptions = model .column_descriptions ,
27012731 table_format = model .table_format ,
27022732 )
2703-
2704- # Apply grants after managed table creation during first insert
2705- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
27062733 elif not is_snapshot_deployable :
27072734 # Snapshot isnt deployable; update the preview table instead
27082735 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2751,6 +2778,10 @@ def migrate(
27512778 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
27522779 )
27532780
2781+ # Apply grants after verifying no schema changes
2782+ # This ensures metadata-only changes (grants) are applied
2783+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2784+
27542785 def delete (self , name : str , ** kwargs : t .Any ) -> None :
27552786 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
27562787 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments