@@ -1045,6 +1045,7 @@ def _clone_snapshot_in_dev(
10451045 allow_destructive_snapshots = allow_destructive_snapshots ,
10461046 allow_additive_snapshots = allow_additive_snapshots ,
10471047 )
1048+
10481049 except Exception :
10491050 adapter .drop_table (target_table_name )
10501051 raise
@@ -1136,6 +1137,7 @@ def _migrate_target_table(
11361137 rendered_physical_properties = rendered_physical_properties ,
11371138 dry_run = False ,
11381139 run_pre_post_statements = run_pre_post_statements ,
1140+ skip_grants = True , # skip grants for tmp table
11391141 )
11401142 try :
11411143 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1401,6 +1403,7 @@ def _execute_create(
14011403 rendered_physical_properties : t .Dict [str , exp .Expression ],
14021404 dry_run : bool ,
14031405 run_pre_post_statements : bool = True ,
1406+ skip_grants : bool = False ,
14041407 ) -> None :
14051408 adapter = self .get_adapter (snapshot .model .gateway )
14061409 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1424,12 +1427,11 @@ def _execute_create(
14241427 is_snapshot_representative = is_snapshot_representative ,
14251428 dry_run = dry_run ,
14261429 physical_properties = rendered_physical_properties ,
1430+ skip_grants = skip_grants ,
14271431 )
14281432 if run_pre_post_statements :
14291433 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
14301434
1431- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1432-
14331435 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14341436 adapter = self .get_adapter (snapshot .model .gateway )
14351437 return (
@@ -1697,16 +1699,11 @@ def _apply_grants(
16971699 return
16981700
16991701 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1700-
1701- try :
1702- self .adapter .sync_grants_config (
1703- exp .to_table (table_name , dialect = model .dialect ),
1704- grants_config ,
1705- model .grants_table_type ,
1706- )
1707- except Exception :
1708- # Log error but don't fail evaluation if grants fail
1709- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1702+ self .adapter .sync_grants_config (
1703+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1704+ grants_config ,
1705+ model .grants_table_type ,
1706+ )
17101707
17111708
17121709class SymbolicStrategy (EvaluationStrategy ):
@@ -1871,6 +1868,10 @@ def create(
18711868 column_descriptions = model .column_descriptions if is_table_deployable else None ,
18721869 )
18731870
1871+ # Apply grants after table creation (unless explicitly skipped by caller)
1872+ if not kwargs .get ("skip_grants" , False ):
1873+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1874+
18741875 def migrate (
18751876 self ,
18761877 target_table_name : str ,
@@ -1896,6 +1897,9 @@ def migrate(
18961897 )
18971898 self .adapter .alter_table (alter_operations )
18981899
1900+ # Apply grants after schema migration
1901+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1902+
18991903 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19001904 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
19011905 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -1943,6 +1947,10 @@ def _replace_query_for_model(
19431947 source_columns = source_columns ,
19441948 )
19451949
1950+ # Apply grants after table replacement (unless explicitly skipped by caller)
1951+ if not kwargs .get ("skip_grants" , False ):
1952+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
1953+
19461954 def _get_target_and_source_columns (
19471955 self ,
19481956 model : Model ,
@@ -2203,12 +2211,18 @@ def create(
22032211 )
22042212 return
22052213
2206- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2214+ # Skip grants in parent create call since we'll apply them after data insertion
2215+ kwargs_no_grants = {** kwargs }
2216+ kwargs_no_grants ["skip_grants" ] = True
2217+
2218+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
22072219 # For seeds we insert data at the time of table creation.
22082220 try :
22092221 for index , df in enumerate (model .render_seed ()):
22102222 if index == 0 :
2211- self ._replace_query_for_model (model , table_name , df , render_kwargs , ** kwargs )
2223+ self ._replace_query_for_model (
2224+ model , table_name , df , render_kwargs , ** kwargs_no_grants
2225+ )
22122226 else :
22132227 self .adapter .insert_append (
22142228 table_name , df , target_columns_to_types = model .columns_to_types
@@ -2217,6 +2231,9 @@ def create(
22172231 self .adapter .drop_table (table_name )
22182232 raise
22192233
2234+ # Apply grants after seed table creation or data insertion
2235+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2236+
22202237 def insert (
22212238 self ,
22222239 table_name : str ,
@@ -2280,6 +2297,9 @@ def create(
22802297 ** kwargs ,
22812298 )
22822299
2300+ # Apply grants after SCD Type 2 table creation
2301+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2302+
22832303 def insert (
22842304 self ,
22852305 table_name : str ,
@@ -2403,7 +2423,7 @@ def insert(
24032423 column_descriptions = model .column_descriptions ,
24042424 )
24052425
2406- # Apply grants after view creation/ replacement
2426+ # Apply grants after view creation / replacement
24072427 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24082428
24092429 def append (
@@ -2428,6 +2448,8 @@ def create(
24282448 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24292449 # binding support (because of DROP CASCADE).
24302450 logger .info ("View '%s' already exists" , table_name )
2451+ # Always apply grants when present, even if view exists, to handle grants updates
2452+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24312453 return
24322454
24332455 logger .info ("Creating view '%s'" , table_name )
@@ -2451,6 +2473,9 @@ def create(
24512473 column_descriptions = model .column_descriptions if is_table_deployable else None ,
24522474 )
24532475
2476+ # Apply grants after view creation
2477+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2478+
24542479 def migrate (
24552480 self ,
24562481 target_table_name : str ,
@@ -2631,7 +2656,7 @@ def create(
26312656 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
26322657
26332658 if is_table_deployable and is_snapshot_deployable :
2634- # We could deploy this to prod; create a proper managed table
2659+ # We cloud deploy this to prod; create a proper managed table
26352660 logger .info ("Creating managed table: %s" , table_name )
26362661 self .adapter .create_managed_table (
26372662 table_name = table_name ,
@@ -2647,17 +2672,23 @@ def create(
26472672
26482673 # Apply grants after managed table creation
26492674 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2675+
26502676 elif not is_table_deployable :
26512677 # Only create the dev preview table as a normal table.
26522678 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26532679 # Any downstream models that reference it will be updated to point to the dev preview table.
26542680 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2681+
2682+ # Create preview table but don't apply grants here since the table is not deployable
2683+ # Grants will be applied later when the table becomes deployable
2684+ kwargs_no_grants = {** kwargs }
2685+ kwargs_no_grants ["skip_grants" ] = True
26552686 super ().create (
26562687 table_name = table_name ,
26572688 model = model ,
26582689 is_table_deployable = is_table_deployable ,
26592690 render_kwargs = render_kwargs ,
2660- ** kwargs ,
2691+ ** kwargs_no_grants ,
26612692 )
26622693
26632694 def insert (
@@ -2672,7 +2703,6 @@ def insert(
26722703 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
26732704 snapshot : Snapshot = kwargs ["snapshot" ]
26742705 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2675-
26762706 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
26772707 self .adapter .create_managed_table (
26782708 table_name = table_name ,
@@ -2685,9 +2715,6 @@ def insert(
26852715 column_descriptions = model .column_descriptions ,
26862716 table_format = model .table_format ,
26872717 )
2688-
2689- # Apply grants after managed table creation during first insert
2690- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
26912718 elif not is_snapshot_deployable :
26922719 # Snapshot isnt deployable; update the preview table instead
26932720 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2736,6 +2763,10 @@ def migrate(
27362763 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
27372764 )
27382765
2766+ # Apply grants after verifying no schema changes
2767+ # This ensures metadata-only changes (grants) are applied
2768+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2769+
27392770 def delete (self , name : str , ** kwargs : t .Any ) -> None :
27402771 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
27412772 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments