@@ -1067,6 +1067,7 @@ def _clone_snapshot_in_dev(
10671067 allow_additive_snapshots = allow_additive_snapshots ,
10681068 run_pre_post_statements = run_pre_post_statements ,
10691069 )
1070+
10701071 except Exception :
10711072 adapter .drop_table (target_table_name )
10721073 raise
@@ -1167,6 +1168,7 @@ def _migrate_target_table(
11671168 rendered_physical_properties = rendered_physical_properties ,
11681169 dry_run = False ,
11691170 run_pre_post_statements = run_pre_post_statements ,
1171+ skip_grants = True , # skip grants for tmp table
11701172 )
11711173 try :
11721174 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1432,6 +1434,7 @@ def _execute_create(
14321434 rendered_physical_properties : t .Dict [str , exp .Expression ],
14331435 dry_run : bool ,
14341436 run_pre_post_statements : bool = True ,
1437+ skip_grants : bool = False ,
14351438 ) -> None :
14361439 adapter = self .get_adapter (snapshot .model .gateway )
14371440 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
@@ -1457,14 +1460,13 @@ def _execute_create(
14571460 is_snapshot_representative = is_snapshot_representative ,
14581461 dry_run = dry_run ,
14591462 physical_properties = rendered_physical_properties ,
1463+ skip_grants = skip_grants ,
14601464 )
14611465 if run_pre_post_statements :
14621466 evaluation_strategy .run_post_statements (
14631467 snapshot = snapshot , render_kwargs = create_render_kwargs
14641468 )
14651469
1466- evaluation_strategy ._apply_grants (snapshot .model , table_name , GrantsTargetLayer .PHYSICAL )
1467-
14681470 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
14691471 adapter = self .get_adapter (snapshot .model .gateway )
14701472 return (
@@ -1819,16 +1821,11 @@ def _apply_grants(
18191821 return
18201822
18211823 logger .info (f"Applying grants for model { model .name } to table { table_name } " )
1822-
1823- try :
1824- self .adapter .sync_grants_config (
1825- exp .to_table (table_name , dialect = model .dialect ),
1826- grants_config ,
1827- model .grants_table_type ,
1828- )
1829- except Exception :
1830- # Log error but don't fail evaluation if grants fail
1831- logger .error (f"Failed to apply grants for model { model .name } " , exc_info = True )
1824+ self .adapter .sync_grants_config (
1825+ exp .to_table (table_name , dialect = self .adapter .dialect ),
1826+ grants_config ,
1827+ model .grants_table_type ,
1828+ )
18321829
18331830
18341831class SymbolicStrategy (EvaluationStrategy ):
@@ -2005,6 +2002,10 @@ def create(
20052002 column_descriptions = model .column_descriptions if is_table_deployable else None ,
20062003 )
20072004
2005+ # Apply grants after table creation (unless explicitly skipped by caller)
2006+ if not kwargs .get ("skip_grants" , False ):
2007+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2008+
20082009 def migrate (
20092010 self ,
20102011 target_table_name : str ,
@@ -2030,6 +2031,9 @@ def migrate(
20302031 )
20312032 self .adapter .alter_table (alter_operations )
20322033
2034+ # Apply grants after schema migration
2035+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2036+
20332037 def delete (self , name : str , ** kwargs : t .Any ) -> None :
20342038 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
20352039 self .adapter .drop_table (name , cascade = kwargs .pop ("cascade" , False ))
@@ -2077,6 +2081,10 @@ def _replace_query_for_model(
20772081 source_columns = source_columns ,
20782082 )
20792083
2084+ # Apply grants after table replacement (unless explicitly skipped by caller)
2085+ if not kwargs .get ("skip_grants" , False ):
2086+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
2087+
20802088 def _get_target_and_source_columns (
20812089 self ,
20822090 model : Model ,
@@ -2337,12 +2345,18 @@ def create(
23372345 )
23382346 return
23392347
2340- super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs )
2348+ # Skip grants in parent create call since we'll apply them after data insertion
2349+ kwargs_no_grants = {** kwargs }
2350+ kwargs_no_grants ["skip_grants" ] = True
2351+
2352+ super ().create (table_name , model , is_table_deployable , render_kwargs , ** kwargs_no_grants )
23412353 # For seeds we insert data at the time of table creation.
23422354 try :
23432355 for index , df in enumerate (model .render_seed ()):
23442356 if index == 0 :
2345- self ._replace_query_for_model (model , table_name , df , render_kwargs , ** kwargs )
2357+ self ._replace_query_for_model (
2358+ model , table_name , df , render_kwargs , ** kwargs_no_grants
2359+ )
23462360 else :
23472361 self .adapter .insert_append (
23482362 table_name , df , target_columns_to_types = model .columns_to_types
@@ -2426,6 +2440,9 @@ def create(
24262440 ** kwargs ,
24272441 )
24282442
2443+ # Apply grants after SCD Type 2 table creation
2444+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2445+
24292446 def insert (
24302447 self ,
24312448 table_name : str ,
@@ -2549,7 +2566,7 @@ def insert(
25492566 column_descriptions = model .column_descriptions ,
25502567 )
25512568
2552- # Apply grants after view creation/ replacement
2569+ # Apply grants after view creation / replacement
25532570 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
25542571
25552572 def append (
@@ -2574,6 +2591,8 @@ def create(
25742591 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25752592 # binding support (because of DROP CASCADE).
25762593 logger .info ("View '%s' already exists" , table_name )
2594+ # Always apply grants when present, even if view exists, to handle grants updates
2595+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
25772596 return
25782597
25792598 logger .info ("Creating view '%s'" , table_name )
@@ -2597,6 +2616,9 @@ def create(
25972616 column_descriptions = model .column_descriptions if is_table_deployable else None ,
25982617 )
25992618
2619+ # Apply grants after view creation
2620+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2621+
26002622 def migrate (
26012623 self ,
26022624 target_table_name : str ,
@@ -2916,7 +2938,7 @@ def create(
29162938 is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
29172939
29182940 if is_table_deployable and is_snapshot_deployable :
2919- # We could deploy this to prod; create a proper managed table
2941+ # We cloud deploy this to prod; create a proper managed table
29202942 logger .info ("Creating managed table: %s" , table_name )
29212943 self .adapter .create_managed_table (
29222944 table_name = table_name ,
@@ -2932,17 +2954,23 @@ def create(
29322954
29332955 # Apply grants after managed table creation
29342956 self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2957+
29352958 elif not is_table_deployable :
29362959 # Only create the dev preview table as a normal table.
29372960 # For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
29382961 # Any downstream models that reference it will be updated to point to the dev preview table.
29392962 # If the user eventually tries to deploy it, the logic in insert() will see it doesnt exist and create it
2963+
2964+ # Create preview table but don't apply grants here since the table is not deployable
2965+ # Grants will be applied later when the table becomes deployable
2966+ kwargs_no_grants = {** kwargs }
2967+ kwargs_no_grants ["skip_grants" ] = True
29402968 super ().create (
29412969 table_name = table_name ,
29422970 model = model ,
29432971 is_table_deployable = is_table_deployable ,
29442972 render_kwargs = render_kwargs ,
2945- ** kwargs ,
2973+ ** kwargs_no_grants ,
29462974 )
29472975
29482976 def insert (
@@ -2957,7 +2985,6 @@ def insert(
29572985 deployability_index : DeployabilityIndex = kwargs ["deployability_index" ]
29582986 snapshot : Snapshot = kwargs ["snapshot" ]
29592987 is_snapshot_deployable = deployability_index .is_deployable (snapshot )
2960-
29612988 if is_first_insert and is_snapshot_deployable and not self .adapter .table_exists (table_name ):
29622989 self .adapter .create_managed_table (
29632990 table_name = table_name ,
@@ -2970,9 +2997,6 @@ def insert(
29702997 column_descriptions = model .column_descriptions ,
29712998 table_format = model .table_format ,
29722999 )
2973-
2974- # Apply grants after managed table creation during first insert
2975- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
29763000 elif not is_snapshot_deployable :
29773001 # Snapshot isnt deployable; update the preview table instead
29783002 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -3021,6 +3045,10 @@ def migrate(
30213045 f"The schema of the managed model '{ target_table_name } ' cannot be updated in a forward-only fashion."
30223046 )
30233047
3048+ # Apply grants after verifying no schema changes
3049+ # This ensures metadata-only changes (grants) are applied
3050+ self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
3051+
30243052 def delete (self , name : str , ** kwargs : t .Any ) -> None :
30253053 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
30263054 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
0 commit comments