5050 ViewKind ,
5151 CustomKind ,
5252)
53- from sqlmesh .core .model .kind import _Incremental
53+ from sqlmesh .core .model .kind import _Incremental , DbtCustomKind
5454from sqlmesh .utils import CompletionStatus , columns_to_types_all_known
5555from sqlmesh .core .schema_diff import (
5656 has_drop_alteration ,
8383 format_additive_change_msg ,
8484 AdditiveChangeError ,
8585)
86+ from sqlmesh .utils .jinja import JinjaMacroRegistry , MacroReturnVal
8687
8788if sys .version_info >= (3 , 12 ):
8889 from importlib import metadata
@@ -747,7 +748,8 @@ def _evaluate_snapshot(
747748 adapter .transaction (),
748749 adapter .session (snapshot .model .render_session_properties (** render_statements_kwargs )),
749750 ):
750- adapter .execute (model .render_pre_statements (** render_statements_kwargs ))
751+ if not snapshot .is_dbt_custom :
752+ adapter .execute (model .render_pre_statements (** render_statements_kwargs ))
751753
752754 if not target_table_exists or (model .is_seed and not snapshot .intervals ):
753755 # Only create the empty table if the columns were provided explicitly by the user
@@ -817,7 +819,8 @@ def _evaluate_snapshot(
817819 batch_index = batch_index ,
818820 )
819821
820- adapter .execute (model .render_post_statements (** render_statements_kwargs ))
822+ if not snapshot .is_dbt_custom :
823+ adapter .execute (model .render_post_statements (** render_statements_kwargs ))
821824
822825 return wap_id
823826
@@ -1432,7 +1435,7 @@ def _execute_create(
14321435 ** create_render_kwargs ,
14331436 "table_mapping" : {snapshot .name : table_name },
14341437 }
1435- if run_pre_post_statements :
1438+ if run_pre_post_statements and not snapshot . is_dbt_custom :
14361439 adapter .execute (snapshot .model .render_pre_statements (** create_render_kwargs ))
14371440 evaluation_strategy .create (
14381441 table_name = table_name ,
@@ -1444,7 +1447,7 @@ def _execute_create(
14441447 dry_run = dry_run ,
14451448 physical_properties = rendered_physical_properties ,
14461449 )
1447- if run_pre_post_statements :
1450+ if run_pre_post_statements and not snapshot . is_dbt_custom :
14481451 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
14491452
14501453 def _can_clone (self , snapshot : Snapshot , deployability_index : DeployabilityIndex ) -> bool :
@@ -1456,6 +1459,7 @@ def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex
14561459 and adapter .SUPPORTS_CLONING
14571460 # managed models cannot have their schema mutated because theyre based on queries, so clone + alter wont work
14581461 and not snapshot .is_managed
1462+ and not snapshot .is_dbt_custom
14591463 and not deployability_index .is_deployable (snapshot )
14601464 # If the deployable table is missing we can't clone it
14611465 and adapter .table_exists (snapshot .table_name ())
@@ -1540,6 +1544,19 @@ def _evaluation_strategy(snapshot: SnapshotInfoLike, adapter: EngineAdapter) ->
15401544 klass = ViewStrategy
15411545 elif snapshot .is_scd_type_2 :
15421546 klass = SCDType2Strategy
1547+ elif snapshot .is_dbt_custom :
1548+ if hasattr (snapshot , "model" ) and isinstance (
1549+ (model_kind := snapshot .model .kind ), DbtCustomKind
1550+ ):
1551+ return DbtCustomMaterialization (
1552+ adapter = adapter ,
1553+ materialization_name = model_kind .materialization ,
1554+ materialization_template = model_kind .definition ,
1555+ )
1556+
1557+ raise SQLMeshError (
1558+ f"Expected DbtCustomKind for dbt custom materialization in model '{ snapshot .name } '"
1559+ )
15431560 elif snapshot .is_custom :
15441561 if snapshot .custom_materialization is None :
15451562 raise SQLMeshError (
@@ -2593,6 +2610,139 @@ def get_custom_materialization_type_or_raise(
25932610 raise SQLMeshError (f"Custom materialization '{ name } ' not present in the Python environment" )
25942611
25952612
2613+ class DbtCustomMaterialization (MaterializableStrategy ):
2614+ def __init__ (
2615+ self ,
2616+ adapter : EngineAdapter ,
2617+ materialization_name : str ,
2618+ materialization_template : str ,
2619+ ):
2620+ super ().__init__ (adapter )
2621+ self .materialization_name = materialization_name
2622+ self .materialization_template = materialization_template
2623+
2624+ def create (
2625+ self ,
2626+ table_name : str ,
2627+ model : Model ,
2628+ is_table_deployable : bool ,
2629+ render_kwargs : t .Dict [str , t .Any ],
2630+ ** kwargs : t .Any ,
2631+ ) -> None :
2632+ original_query = model .render_query_or_raise (** render_kwargs )
2633+ self ._execute_materialization (
2634+ table_name = table_name ,
2635+ query_or_df = original_query .limit (0 ),
2636+ model = model ,
2637+ is_first_insert = True ,
2638+ render_kwargs = render_kwargs ,
2639+ create_only = True ,
2640+ ** kwargs ,
2641+ )
2642+
2643+ def insert (
2644+ self ,
2645+ table_name : str ,
2646+ query_or_df : QueryOrDF ,
2647+ model : Model ,
2648+ is_first_insert : bool ,
2649+ render_kwargs : t .Dict [str , t .Any ],
2650+ ** kwargs : t .Any ,
2651+ ) -> None :
2652+ self ._execute_materialization (
2653+ table_name = table_name ,
2654+ query_or_df = query_or_df ,
2655+ model = model ,
2656+ is_first_insert = is_first_insert ,
2657+ render_kwargs = render_kwargs ,
2658+ ** kwargs ,
2659+ )
2660+
2661+ def append (
2662+ self ,
2663+ table_name : str ,
2664+ query_or_df : QueryOrDF ,
2665+ model : Model ,
2666+ render_kwargs : t .Dict [str , t .Any ],
2667+ ** kwargs : t .Any ,
2668+ ) -> None :
2669+ return self .insert (
2670+ table_name ,
2671+ query_or_df ,
2672+ model ,
2673+ is_first_insert = False ,
2674+ render_kwargs = render_kwargs ,
2675+ ** kwargs ,
2676+ )
2677+
2678+ def _execute_materialization (
2679+ self ,
2680+ table_name : str ,
2681+ query_or_df : QueryOrDF ,
2682+ model : Model ,
2683+ is_first_insert : bool ,
2684+ render_kwargs : t .Dict [str , t .Any ],
2685+ create_only : bool = False ,
2686+ ** kwargs : t .Any ,
2687+ ) -> None :
2688+ from sqlmesh .dbt .builtin import create_builtin_globals
2689+
2690+ jinja_macros = getattr (model , "jinja_macros" , JinjaMacroRegistry ())
2691+ existing_globals = jinja_macros .global_objs .copy ()
2692+
2693+ # For vdes we need to use the table, since we don't know the schema/table at parse time
2694+ parts = exp .to_table (table_name , dialect = self .adapter .dialect )
2695+
2696+ relation_info = existing_globals .pop ("this" )
2697+ if isinstance (relation_info , dict ):
2698+ relation_info ["database" ] = parts .catalog
2699+ relation_info ["identifier" ] = parts .name
2700+ relation_info ["name" ] = parts .name
2701+
2702+ jinja_globals = {
2703+ ** existing_globals ,
2704+ "this" : relation_info ,
2705+ "database" : parts .catalog ,
2706+ "schema" : parts .db ,
2707+ "identifier" : parts .name ,
2708+ "target" : existing_globals .get ("target" , {"type" : self .adapter .dialect }),
2709+ "execution_dt" : kwargs .get ("execution_time" ),
2710+ }
2711+
2712+ context = create_builtin_globals (
2713+ jinja_macros = jinja_macros , jinja_globals = jinja_globals , engine_adapter = self .adapter
2714+ )
2715+
2716+ context .update (
2717+ {
2718+ "sql" : str (query_or_df ),
2719+ "is_first_insert" : is_first_insert ,
2720+ "create_only" : create_only ,
2721+ "pre_hooks" : model .render_pre_statements (** render_kwargs ),
2722+ "post_hooks" : model .render_post_statements (** render_kwargs ),
2723+ ** kwargs ,
2724+ }
2725+ )
2726+
2727+ try :
2728+ jinja_env = jinja_macros .build_environment (** context )
2729+ template = jinja_env .from_string (self .materialization_template )
2730+
2731+ try :
2732+ template .render (** context )
2733+ except MacroReturnVal as ret :
2734+ # this is a succesful return from a macro call (dbt uses this list of Relations to update their relation cache)
2735+ returned_relations = ret .value .get ("relations" , [])
2736+ logger .info (
2737+ f"Materialization { self .materialization_name } returned relations: { returned_relations } "
2738+ )
2739+
2740+ except Exception as e :
2741+ raise SQLMeshError (
2742+ f"Failed to execute dbt materialization '{ self .materialization_name } ': { e } "
2743+ ) from e
2744+
2745+
25962746class EngineManagedStrategy (MaterializableStrategy ):
25972747 def create (
25982748 self ,
0 commit comments