@@ -1062,6 +1062,7 @@ def _clone_snapshot_in_dev(
10621062 allow_additive_snapshots = allow_additive_snapshots ,
10631063 run_pre_post_statements = run_pre_post_statements ,
10641064 )
1065+
10651066 except Exception :
10661067 adapter .drop_table (target_table_name )
10671068 raise
@@ -1162,6 +1163,7 @@ def _migrate_target_table(
11621163 rendered_physical_properties = rendered_physical_properties ,
11631164 dry_run = False ,
11641165 run_pre_post_statements = run_pre_post_statements ,
1166+ skip_grants = True , # skip grants for tmp table
11651167 )
11661168 try :
11671169 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1427,6 +1429,7 @@ def _execute_create(
14271429 rendered_physical_properties : t .Dict [str , exp .Expression ],
14281430 dry_run : bool ,
14291431 run_pre_post_statements : bool = True ,
1432+ skip_grants : bool = False ,
14301433 ) -> None :
14311434 adapter = self .get_adapter (snapshot .model .gateway )
14321435 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1452,14 +1455,13 @@ def _execute_create(
14521455 is_snapshot_representative = is_snapshot_representative ,
14531456 dry_run = dry_run ,
14541457 physical_properties = rendered_physical_properties ,
1458+ skip_grants = skip_grants ,
14551459 )
14561460 if run_pre_post_statements :
14571461 evaluation_strategy .run_post_statements (
14581462 snapshot = snapshot , render_kwargs = create_render_kwargs
14591463 )
14601464
1461- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1462-
14631465 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14641466 adapter = self .get_adapter (snapshot .model .gateway )
14651467 return (
@@ -1760,16 +1762,11 @@ def _apply_grants(
17601762 return
17611763
17621764 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1763-
1764- try :
1765- self .adapter .sync_grants_config (
1766- exp .to_table (table_name , dialect = model .dialect ),
1767- grants_config ,
1768- model .grants_table_type ,
1769- )
1770- except Exception :
1771- # Log error but don't fail evaluation if grants fail
1772- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1765+ self .adapter .sync_grants_config (
1766+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1767+ grants_config ,
1768+ model .grants_table_type ,
1769+ )
17731770
17741771
17751772class SymbolicStrategy (EvaluationStrategy ):
@@ -1946,6 +1943,10 @@ def create(
19461943 column_descriptions = model .column_descriptions if is_table_deployable else None ,
19471944 )
19481945
1946+ # Apply grants after table creation (unless explicitly skipped by caller)
1947+ if not kwargs .get ("skip_grants" , False ):
1948+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1949+
19491950 def migrate (
19501951 self ,
19511952 target_table_name : str ,
@@ -1971,6 +1972,9 @@ def migrate(
19711972 )
19721973 self .adapter .alter_table (alter_operations )
19731974
1975+ # Apply grants after schema migration
1976+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1977+
19741978 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19751979 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
19761980 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -2018,6 +2022,10 @@ def _replace_query_for_model(
20182022 source_columns = source_columns ,
20192023 )
20202024
2025+ # Apply grants after table replacement (unless explicitly skipped by caller)
2026+ if not kwargs .get ("skip_grants" , False ):
2027+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
2028+
20212029 def _get_target_and_source_columns (
20222030 self ,
20232031 model : Model ,
@@ -2278,12 +2286,18 @@ def create(
22782286 )
22792287 return
22802288
2281- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2289+ # Skip grants in parent create call since we'll apply them after data insertion
2290+ kwargs_no_grants = {** kwargs }
2291+ kwargs_no_grants ["skip_grants" ] = True
2292+
2293+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
22822294 # For seeds we insert data at the time of table creation.
22832295 try :
22842296 for index , df in enumerate (model .render_seed ()):
22852297 if index == 0 :
2286- self ._replace_query_for_model (model , table_name , df , render_kwargs , ** kwargs )
2298+ self ._replace_query_for_model (
2299+ model , table_name , df , render_kwargs , ** kwargs_no_grants
2300+ )
22872301 else :
22882302 self .adapter .insert_append (
22892303 table_name , df , target_columns_to_types = model .columns_to_types
@@ -2367,6 +2381,9 @@ def create(
23672381 ** kwargs ,
23682382 )
23692383
2384+ # Apply grants after SCD Type 2 table creation
2385+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2386+
23702387 def insert (
23712388 self ,
23722389 table_name : str ,
@@ -2490,7 +2507,7 @@ def insert(
24902507 column_descriptions = model .column_descriptions ,
24912508 )
24922509
2493- # Apply grants after view creation/ replacement
2510+ # Apply grants after view creation / replacement
24942511 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24952512
24962513 def append (
@@ -2515,6 +2532,8 @@ def create(
25152532 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25162533 # binding support (because of DROP CASCADE).
25172534 logger .info ("View '%s' already exists" , table_name )
2535+ # Always apply grants when present, even if view exists, to handle grants updates
2536+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
25182537 return
25192538
25202539 logger .info ("Creating view '%s'" , table_name )
@@ -2538,6 +2557,9 @@ def create(
25382557 column_descriptions = model .column_descriptions if is_table_deployable else None ,
25392558 )
25402559
2560+ # Apply grants after view creation
2561+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2562+
25412563 def migrate (
25422564 self ,
25432565 target_table_name : str ,
@@ -2857,7 +2879,7 @@ def create(
28572879 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
28582880
28592881 if is_table_deployable and is_snapshot_deployable :
2860- # We could deploy this to prod; create a proper managed table
2882+ # We cloud deploy this to prod; create a proper managed table
28612883 logger .info ("Creating managed table: %s" , table_name )
28622884 self .adapter .create_managed_table (
28632885 table_name = table_name ,
@@ -2873,17 +2895,23 @@ def create(
28732895
28742896 # Apply grants after managed table creation
28752897 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2898+
28762899 elif not is_table_deployable :
28772900 # Only create the dev preview table as a normal table.
28782901 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
28792902 # Any downstream models that reference it will be updated to point to the dev preview table.
28802903 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2904+
2905+ # Create preview table but don't apply grants here since the table is not deployable
2906+ # Grants will be applied later when the table becomes deployable
2907+ kwargs_no_grants = {** kwargs }
2908+ kwargs_no_grants ["skip_grants" ] = True
28812909 super ().create (
28822910 table_name = table_name ,
28832911 model = model ,
28842912 is_table_deployable = is_table_deployable ,
28852913 render_kwargs = render_kwargs ,
2886- ** kwargs ,
2914+ ** kwargs_no_grants ,
28872915 )
28882916
28892917 def insert (
@@ -2898,7 +2926,6 @@ def insert(
28982926 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
28992927 snapshot : Snapshot = kwargs ["snapshot" ]
29002928 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2901-
29022929 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
29032930 self .adapter .create_managed_table (
29042931 table_name = table_name ,
@@ -2911,9 +2938,6 @@ def insert(
29112938 column_descriptions = model .column_descriptions ,
29122939 table_format = model .table_format ,
29132940 )
2914-
2915- # Apply grants after managed table creation during first insert
2916- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
29172941 elif not is_snapshot_deployable :
29182942 # Snapshot isnt deployable; update the preview table instead
29192943 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2962,6 +2986,10 @@ def migrate(
29622986 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
29632987 )
29642988
2989+ # Apply grants after verifying no schema changes
2990+ # This ensures metadata-only changes (grants) are applied
2991+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2992+
29652993 def delete (self , name : str , ** kwargs : t .Any ) -> None :
29662994 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
29672995 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments