@@ -1067,6 +1067,7 @@ def _clone_snapshot_in_dev(
10671067 allow_destructive_snapshots = allow_destructive_snapshots ,
10681068 allow_additive_snapshots = allow_additive_snapshots ,
10691069 )
1070+
10701071 except Exception :
10711072 adapter .drop_table (target_table_name )
10721073 raise
@@ -1143,6 +1144,7 @@ def _migrate_target_table(
11431144 rendered_physical_properties = rendered_physical_properties ,
11441145 dry_run = False ,
11451146 run_pre_post_statements = run_pre_post_statements ,
1147+ skip_grants = True , # skip grants for tmp table
11461148 )
11471149 try :
11481150 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1408,6 +1410,7 @@ def _execute_create(
14081410 rendered_physical_properties : t .Dict [str , exp .Expression ],
14091411 dry_run : bool ,
14101412 run_pre_post_statements : bool = True ,
1413+ skip_grants : bool = False ,
14111414 ) -> None :
14121415 adapter = self .get_adapter (snapshot .model .gateway )
14131416 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1431,12 +1434,11 @@ def _execute_create(
14311434 is_snapshot_representative = is_snapshot_representative ,
14321435 dry_run = dry_run ,
14331436 physical_properties = rendered_physical_properties ,
1437+ skip_grants = skip_grants ,
14341438 )
14351439 if run_pre_post_statements :
14361440 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
14371441
1438- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1439-
14401442 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14411443 adapter = self .get_adapter (snapshot .model .gateway )
14421444 return (
@@ -1648,16 +1650,11 @@ def _apply_grants(
16481650 return
16491651
16501652 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1651-
1652- try :
1653- self .adapter .sync_grants_config (
1654- exp .to_table (table_name , dialect = model .dialect ),
1655- grants_config ,
1656- model .grants_table_type ,
1657- )
1658- except Exception :
1659- # Log error but don't fail evaluation if grants fail
1660- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1653+ self .adapter .sync_grants_config (
1654+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1655+ grants_config ,
1656+ model .grants_table_type ,
1657+ )
16611658
16621659
16631660class SymbolicStrategy (EvaluationStrategy ):
@@ -1822,6 +1819,10 @@ def create(
18221819 column_descriptions = model .column_descriptions if is_table_deployable else None ,
18231820 )
18241821
1822+ # Apply grants after table creation (unless explicitly skipped by caller)
1823+ if not kwargs .get ("skip_grants" , False ):
1824+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1825+
18251826 def migrate (
18261827 self ,
18271828 target_table_name : str ,
@@ -1847,6 +1848,9 @@ def migrate(
18471848 )
18481849 self .adapter .alter_table (alter_operations )
18491850
1851+ # Apply grants after schema migration
1852+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1853+
18501854 def delete (self , name : str , ** kwargs : t .Any ) -> None :
18511855 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
18521856 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -1894,6 +1898,10 @@ def _replace_query_for_model(
18941898 source_columns = source_columns ,
18951899 )
18961900
1901+ # Apply grants after table replacement (unless explicitly skipped by caller)
1902+ if not kwargs .get ("skip_grants" , False ):
1903+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
1904+
18971905 def _get_target_and_source_columns (
18981906 self ,
18991907 model : Model ,
@@ -2154,14 +2162,19 @@ def create(
21542162 )
21552163 return
21562164
2157- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2165+ # Skip grants in parent create call since we'll apply them after data insertion
2166+ kwargs_no_grants = {** kwargs }
2167+ kwargs_no_grants ["skip_grants" ] = True
2168+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
2169+
21582170 if is_table_deployable :
21592171 # For seeds we insert data at the time of table creation.
21602172 try :
21612173 for index , df in enumerate (model .render_seed ()):
21622174 if index == 0 :
2175+ # Skip grants in replace_query call since we'll apply them at the end
21632176 self ._replace_query_for_model (
2164- model , table_name , df , render_kwargs , ** kwargs
2177+ model , table_name , df , render_kwargs , ** kwargs_no_grants
21652178 )
21662179 else :
21672180 self .adapter .insert_append (
@@ -2171,6 +2184,9 @@ def create(
21712184 self .adapter .drop_table (table_name )
21722185 raise
21732186
2187+ # Apply grants after seed table creation or data insertion
2188+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2189+
21742190 def insert (
21752191 self ,
21762192 table_name : str ,
@@ -2234,6 +2250,9 @@ def create(
22342250 ** kwargs ,
22352251 )
22362252
2253+ # Apply grants after SCD Type 2 table creation
2254+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2255+
22372256 def insert (
22382257 self ,
22392258 table_name : str ,
@@ -2384,7 +2403,7 @@ def insert(
23842403 column_descriptions = model .column_descriptions ,
23852404 )
23862405
2387- # Apply grants after view creation/ replacement
2406+ # Apply grants after view creation / replacement
23882407 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
23892408
23902409 def append (
@@ -2409,6 +2428,8 @@ def create(
24092428 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24102429 # binding support (because of DROP CASCADE).
24112430 logger .info ("View '%s' already exists" , table_name )
2431+ # Always apply grants when present, even if view exists, to handle grants updates
2432+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
24122433 return
24132434
24142435 logger .info ("Creating view '%s'" , table_name )
@@ -2432,6 +2453,9 @@ def create(
24322453 column_descriptions = model .column_descriptions if is_table_deployable else None ,
24332454 )
24342455
2456+ # Apply grants after view creation
2457+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2458+
24352459 def migrate (
24362460 self ,
24372461 target_table_name : str ,
@@ -2612,7 +2636,7 @@ def create(
26122636 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
26132637
26142638 if is_table_deployable and is_snapshot_deployable :
2615- # We could deploy this to prod; create a proper managed table
2639+ # We cloud deploy this to prod; create a proper managed table
26162640 logger .info ("Creating managed table: %s" , table_name )
26172641 self .adapter .create_managed_table (
26182642 table_name = table_name ,
@@ -2628,17 +2652,23 @@ def create(
26282652
26292653 # Apply grants after managed table creation
26302654 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2655+
26312656 elif not is_table_deployable :
26322657 # Only create the dev preview table as a normal table.
26332658 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
26342659 # Any downstream models that reference it will be updated to point to the dev preview table.
26352660 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2661+
2662+ # Create preview table but don't apply grants here since the table is not deployable
2663+ # Grants will be applied later when the table becomes deployable
2664+ kwargs_no_grants = {** kwargs }
2665+ kwargs_no_grants ["skip_grants" ] = True
26362666 super ().create (
26372667 table_name = table_name ,
26382668 model = model ,
26392669 is_table_deployable = is_table_deployable ,
26402670 render_kwargs = render_kwargs ,
2641- ** kwargs ,
2671+ ** kwargs_no_grants ,
26422672 )
26432673
26442674 def insert (
@@ -2653,7 +2683,6 @@ def insert(
26532683 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
26542684 snapshot : Snapshot = kwargs ["snapshot" ]
26552685 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2656-
26572686 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
26582687 self .adapter .create_managed_table (
26592688 table_name = table_name ,
@@ -2666,9 +2695,6 @@ def insert(
26662695 column_descriptions = model .column_descriptions ,
26672696 table_format = model .table_format ,
26682697 )
2669-
2670- # Apply grants after managed table creation during first insert
2671- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
26722698 elif not is_snapshot_deployable :
26732699 # Snapshot isnt deployable; update the preview table instead
26742700 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2717,6 +2743,10 @@ def migrate(
27172743 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
27182744 )
27192745
2746+ # Apply grants after verifying no schema changes
2747+ # This ensures metadata-only changes (grants) are applied
2748+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2749+
27202750 def delete (self , name : str , ** kwargs : t .Any ) -> None :
27212751 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
27222752 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments