11//! `sqb build --defer-clone-from`: clone out-of-selection boundaries before build planning.
22
3+ use std:: collections:: { HashMap , HashSet } ;
34use std:: time:: Instant ;
45
56use pyo3:: prelude:: {
67 Bound , Py , PyAny , PyAnyMethods , PyErr , PyModule , PyModuleMethods , PyResult , Python ,
78} ;
89use pyo3:: types:: { PyCFunction , PyDict , PyFrozenSet , PyString , PyTuple , PyTupleMethods } ;
910use pyo3:: { IntoPyObject , IntoPyObjectExt , pyfunction, wrap_pyfunction} ;
11+ use sqlbuild_analysis:: assembly:: project:: types:: ObjectKey ;
1012use sqlbuild_core:: build_command:: constants:: EXECUTION_LIMIT_BUILD_NOTE ;
13+ use sqlbuild_core:: graph_walk:: main:: clone_ancestors:: clone_ancestors;
14+ use sqlbuild_core:: graph_walk:: models:: ClonePolicy ;
1115
1216use sqlbuild_core:: build_command:: errors:: BuildCommandError ;
1317use sqlbuild_host:: entries:: models:: ExecutorHost ;
@@ -21,14 +25,11 @@ use crate::bindings::_helpers::compile_driver::project::build_compiled_project;
2125use crate :: bindings:: _helpers:: contracts:: spec_resolution:: resolve_target_config;
2226use crate :: bindings:: _helpers:: dbt_interop:: reference_resolver:: resolve_external_sql_reference_resolver;
2327use crate :: bindings:: _helpers:: native_host:: host;
24- use crate :: bindings:: _helpers:: planner_entries:: clone_plans:: {
25- resolve_clone_boundary, resolve_skipped_view_chain,
26- } ;
28+ use crate :: bindings:: _helpers:: planner_scope:: retained:: { ScopeData , scope_data} ;
2729use crate :: bindings:: _helpers:: planner_scope:: scope:: build_planner_scope;
2830use crate :: bindings:: _helpers:: planner_selection:: unit_tests:: split_unit_test_selectors;
2931use crate :: bindings:: _helpers:: records:: construct:: { enum_member, new_record_with} ;
3032use crate :: bindings:: _helpers:: records:: fields:: field as native_field;
31- use crate :: bindings:: _helpers:: records:: fields:: mapping_get;
3233use crate :: bindings:: _helpers:: records:: fields:: replaced;
3334use crate :: bindings:: _helpers:: records:: ids:: RecordId ;
3435
@@ -52,19 +53,14 @@ fn user_error(py: Python<'_>, message: String, code: &str) -> PyErr {
5253 )
5354}
5455
55- fn resource_type ( key : & Bound < ' _ , PyAny > ) -> PyResult < String > {
56- native_field ( key, "resource_type" ) ?
57- . getattr ( "value" ) ?
58- . extract ( )
59- }
60-
6156/// Functions and view models are recreated over cloned boundaries instead of cloned.
62- fn recreated_over_clones ( scope : & Bound < ' _ , PyAny > , key : & Bound < ' _ , PyAny > ) -> PyResult < bool > {
63- if RECREATED_TYPES . contains ( & resource_type ( key) ? . as_str ( ) ) {
57+ fn recreated_over_clones ( py : Python < ' _ > , scope : & ScopeData , key : & ObjectKey ) -> PyResult < bool > {
58+ if RECREATED_TYPES . contains ( & key. 0 . as_str ( ) ) {
6459 return Ok ( true ) ;
6560 }
66- let model: Bound < ' _ , PyAny > = native_field ( scope, "models_by_name" ) ?
67- . call_method1 ( "get" , ( native_field ( key, "name" ) ?, ) ) ?;
61+ let Some ( model) = scope. model ( py, & key. 1 ) else {
62+ return Ok ( false ) ;
63+ } ;
6864 if model. is_none ( ) {
6965 return Ok ( false ) ;
7066 }
@@ -76,83 +72,59 @@ fn recreated_over_clones(scope: &Bound<'_, PyAny>, key: &Bound<'_, PyAny>) -> Py
7672}
7773
7874/// Every key the scope's traversal can visit: the selection and the upstream graph.
79- fn scope_keys < ' py > ( scope : & Bound < ' py , PyAny > ) -> PyResult < Vec < Bound < ' py , PyAny > > > {
80- let mut keys: Vec < Bound < ' py , PyAny > > = native_field ( scope, "selected_keys" ) ?
81- . try_iter ( ) ?
82- . collect :: < PyResult < _ > > ( ) ?;
83- for item in native_field ( scope, "upstream_deps" ) ?
84- . call_method0 ( "items" ) ?
85- . try_iter ( ) ?
86- {
87- let ( key, upstream) : ( Bound < ' py , PyAny > , Bound < ' py , PyAny > ) = item?. extract ( ) ?;
88- keys. push ( key) ;
89- for upstream_key in upstream. try_iter ( ) ? {
90- keys. push ( upstream_key?) ;
91- }
75+ fn scope_keys ( scope : & ScopeData ) -> Vec < ObjectKey > {
76+ let mut keys: Vec < _ > = scope. selected . iter ( ) . cloned ( ) . collect ( ) ;
77+ for ( key, upstream) in & scope. upstream {
78+ keys. push ( key. clone ( ) ) ;
79+ keys. extend ( upstream. iter ( ) . cloned ( ) ) ;
9280 }
93- Ok ( keys)
81+ keys
9482}
9583
96- /// `frozenset.__contains__` over the keys a predicate accepts, as the traversal callback.
97- fn membership < ' py > (
98- py : Python < ' py > ,
99- keys : & [ Bound < ' py , PyAny > ] ,
100- accepted : impl Fn ( & Bound < ' py , PyAny > ) -> PyResult < bool > ,
101- ) -> PyResult < Bound < ' py , PyAny > > {
102- let mut members: Vec < Bound < ' py , PyAny > > = Vec :: new ( ) ;
103- for key in keys {
104- if accepted ( key) ? {
105- members. push ( key. clone ( ) ) ;
106- }
107- }
108- PyFrozenSet :: new ( py, & members) ?. getattr ( "__contains__" )
109- }
110-
111- /// A clone-plan traversal from selected keys over upstream dependencies.
112- type TraversalFunction =
113- for <' py > fn ( Python < ' py > , Option < Bound < ' py , PyDict > > ) -> PyResult < Bound < ' py , PyFrozenSet > > ;
114-
115- fn traversed_names < ' py > (
116- scope : & Bound < ' py , PyAny > ,
117- function : TraversalFunction ,
118- ( is_clonable, is_view) : ( & Bound < ' py , PyAny > , & Bound < ' py , PyAny > ) ,
119- ) -> PyResult < Vec < String > > {
120- let py: Python < ' py > = scope. py ( ) ;
121- let keys: Bound < ' py , PyFrozenSet > = function (
122- py,
123- Some ( keywords (
124- py,
125- & [
126- ( "selected" , native_field ( scope, "selected_keys" ) ?) ,
127- ( "upstream" , native_field ( scope, "upstream_deps" ) ?) ,
128- ( "is_clonable" , is_clonable. clone ( ) ) ,
129- ( "is_view" , is_view. clone ( ) ) ,
130- ] ,
131- ) ?) ,
132- ) ?;
133- let mut names: Vec < String > = Vec :: new ( ) ;
134- for key in keys. try_iter ( ) ? {
135- names. push ( native_field ( & key?, "name" ) ?. extract ( ) ?) ;
136- }
84+ /// Clone boundaries or recreated ancestors, without Python graph or predicate projections.
85+ fn traversed_names (
86+ scope : & ScopeData ,
87+ upstream : & HashMap < & ObjectKey , & Vec < ObjectKey > > ,
88+ recreated : & HashSet < ObjectKey > ,
89+ skipped_views : bool ,
90+ ) -> Vec < String > {
91+ let mut visited: HashSet < ObjectKey > = HashSet :: new ( ) ;
92+ let mut found: HashSet < ObjectKey > = HashSet :: new ( ) ;
93+ let Ok ( ( ) ) = clone_ancestors (
94+ scope. selected . iter ( ) . cloned ( ) . map ( Ok ) ,
95+ ( & mut visited, & mut found) ,
96+ |node| {
97+ Ok ( upstream
98+ . get ( node)
99+ . map_or_else ( Vec :: new, |parents| ( * parents) . clone ( ) ) )
100+ } ,
101+ ClonePolicy {
102+ selected : |node : & ObjectKey | Ok ( scope. selected . contains ( node) ) ,
103+ clonable : |node : & ObjectKey | Ok ( CLONABLE_TYPES . contains ( & node. 0 . as_str ( ) ) ) ,
104+ view : |node : & ObjectKey | Ok ( recreated. contains ( node) ) ,
105+ skipped_views,
106+ } ,
107+ ) ;
108+ let mut names: Vec < _ > = found. into_iter ( ) . map ( |key| key. 1 ) . collect ( ) ;
137109 names. sort ( ) ;
138- Ok ( names)
110+ names
139111}
140112
141113/// Selected models except those explicitly disabled.
142- fn selected_executable_model_count ( scope : & Bound < ' _ , PyAny > ) -> PyResult < usize > {
143- let models: Bound < ' _ , PyAny > = native_field ( scope, "models_by_name" ) ?;
114+ fn selected_executable_model_count ( py : Python < ' _ > , scope : & ScopeData ) -> PyResult < usize > {
144115 let mut count: usize = 0 ;
145- for key in native_field ( scope, "selected_keys" ) ?. try_iter ( ) ? {
146- let key: Bound < ' _ , PyAny > = key?;
147- if resource_type ( & key) ? != MODEL_TYPE {
116+ for key in & scope. selected {
117+ if key. 0 != MODEL_TYPE {
148118 continue ;
149119 }
150- let model: Bound < ' _ , PyAny > = mapping_get ( & models, native_field ( & key, "name" ) ?) ?;
120+ let model = scope
121+ . model ( py, & key. 1 )
122+ . unwrap_or_else ( || py. None ( ) . into_bound ( py) ) ;
151123 if !model. is_none ( )
152124 && native_field ( & model, "config" ) ?
153125 . getattr ( "values" ) ?
154126 . call_method1 ( "get" , ( "enabled" , ) ) ?
155- . is ( false . into_pyobject ( scope . py ( ) ) ?. to_owned ( ) . into_any ( ) )
127+ . is ( false . into_pyobject ( py ) ?. to_owned ( ) . into_any ( ) )
156128 {
157129 continue ;
158130 }
@@ -164,16 +136,22 @@ fn selected_executable_model_count(scope: &Bound<'_, PyAny>) -> PyResult<usize>
164136/// Boundary clone selectors, rebuilt view-chain selectors and the selected model count.
165137fn defer_clone_selection ( scope : & Bound < ' _ , PyAny > ) -> PyResult < ( Vec < String > , Vec < String > , usize ) > {
166138 let py: Python < ' _ > = scope. py ( ) ;
167- let keys: Vec < Bound < ' _ , PyAny > > = scope_keys ( scope) ?;
168- let is_clonable: Bound < ' _ , PyAny > = membership ( py, & keys, |key| {
169- Ok ( CLONABLE_TYPES . contains ( & resource_type ( key) ?. as_str ( ) ) )
170- } ) ?;
171- let is_view: Bound < ' _ , PyAny > = membership ( py, & keys, |key| recreated_over_clones ( scope, key) ) ?;
172- let callbacks: ( & Bound < ' _ , PyAny > , & Bound < ' _ , PyAny > ) = ( & is_clonable, & is_view) ;
139+ let scope = scope_data ( scope) ?;
140+ let mut recreated: HashSet < ObjectKey > = HashSet :: new ( ) ;
141+ for key in scope_keys ( & scope) {
142+ if recreated_over_clones ( py, & scope, & key) ? {
143+ recreated. insert ( key) ;
144+ }
145+ }
146+ let upstream: HashMap < & ObjectKey , & Vec < ObjectKey > > = scope
147+ . upstream
148+ . iter ( )
149+ . map ( |( key, parents) | ( key, parents) )
150+ . collect ( ) ;
173151 Ok ( (
174- traversed_names ( scope, resolve_clone_boundary , callbacks ) ? ,
175- traversed_names ( scope, resolve_skipped_view_chain , callbacks ) ? ,
176- selected_executable_model_count ( scope) ?,
152+ traversed_names ( & scope, & upstream , & recreated , false ) ,
153+ traversed_names ( & scope, & upstream , & recreated , true ) ,
154+ selected_executable_model_count ( py , & scope) ?,
177155 ) )
178156}
179157
0 commit comments