@@ -5,7 +5,7 @@ use std::sync::Mutex;
55use std:: time:: { Duration , Instant } ;
66
77use pyo3:: exceptions:: PyRuntimeError ;
8- use pyo3:: prelude:: { Bound , Py , PyAny , PyAnyMethods , PyResult , Python } ;
8+ use pyo3:: prelude:: { Bound , PyAny , PyAnyMethods , PyResult , Python } ;
99use pyo3:: { pyclass, pymethods} ;
1010use sqlbuild_state:: consumption:: constants:: RECENT_EXECUTIONS ;
1111use sqlbuild_state:: consumption:: main:: changed_run_set:: changed_run_set;
@@ -20,8 +20,8 @@ use sqlbuild_state::consumption::models::{
2020use uuid:: Uuid ;
2121
2222use crate :: bindings:: _helpers:: consumption:: facts:: { BuildFacts , build_facts} ;
23- use crate :: bindings:: _helpers:: consumption :: state_io :: {
24- Location , append , ensure_table , read_recent ,
23+ use crate :: bindings:: _helpers:: native_host :: consumption_state :: {
24+ ConsumptionConnection , Location , NodeCompleteHook ,
2525} ;
2626use crate :: bindings:: _helpers:: records:: fields:: field as native_field;
2727
@@ -33,6 +33,7 @@ const MODEL_RESULT: &str = "model_name";
3333const SEED_RESULT : & str = "seed_name" ;
3434const SUCCESS : & str = "success" ;
3535const ERROR_OUTCOME : & str = "error" ;
36+ const SEED_KIND : & str = "seed" ;
3637
3738/// The mutable side of one build's recording.
3839struct Recording {
@@ -44,17 +45,29 @@ struct Recording {
4445 warnings : Vec < String > ,
4546}
4647
48+ impl Default for Recording {
49+ fn default ( ) -> Self {
50+ Self {
51+ pending : BTreeMap :: new ( ) ,
52+ pending_executions : 0 ,
53+ pending_seeds : Vec :: new ( ) ,
54+ tables : BTreeSet :: new ( ) ,
55+ last_flush : Instant :: now ( ) ,
56+ warnings : Vec :: new ( ) ,
57+ }
58+ }
59+ }
60+
4761/// Appends `started` rows before a build and `succeeded` rows as its models and seeds complete.
4862#[ pyclass( module = "sqlbuild._native" , frozen) ]
4963pub ( crate ) struct ConsumptionRecorder {
50- adapter : Py < PyAny > ,
51- connection : Py < PyAny > ,
64+ connection : ConsumptionConnection ,
5265 run_id : String ,
5366 nodes : HashMap < NodeKey , NodeFacts > ,
5467 history : ConsumptionHistory ,
5568 executions : HashMap < NodeKey , String > ,
5669 failed_start : HashMap < NodeKey , String > ,
57- base_on_node_complete : Py < PyAny > ,
70+ base_on_node_complete : NodeCompleteHook ,
5871 recording : Mutex < Recording > ,
5972}
6073
@@ -86,36 +99,38 @@ impl ConsumptionRecorder {
8699 . map_err ( |_| PyRuntimeError :: new_err ( "consumption recorder state is poisoned" ) )
87100 }
88101
89- /// Queue the succeeded rows of one execution that wrote its relation.
90- fn queue ( & self , recording : & mut Recording , key : & NodeKey ) {
102+ /// The succeeded rows of one execution that wrote its relation, with their schema .
103+ fn succeeded ( & self , key : & NodeKey ) -> Option < ( Location , Vec < ConsumptionAppend > ) > {
91104 let ( Some ( node) , Some ( execution_id) ) = ( self . nodes . get ( key) , self . executions . get ( key) )
92105 else {
93- return ;
106+ return None ;
94107 } ;
95108 let inputs = consumed_inputs ( node, ( & self . nodes , & self . history ) , & self . executions ) ;
109+ Some ( (
110+ Location :: of ( & node. relation ) ,
111+ succeeded_appends ( node, ( execution_id, & self . run_id ) , & inputs) ,
112+ ) )
113+ }
114+
115+ /// The recording with `key`'s succeeded rows queued.
116+ fn queued ( & self , mut recording : Recording , key : & NodeKey ) -> Recording {
117+ if let Some ( ( location, rows) ) = self . succeeded ( key) {
118+ recording. pending . entry ( location) . or_default ( ) . extend ( rows) ;
119+ recording. pending_executions += 1 ;
120+ }
96121 recording
97- . pending
98- . entry ( Location :: of ( & node. relation ) )
99- . or_default ( )
100- . extend ( succeeded_appends (
101- node,
102- ( execution_id, & self . run_id ) ,
103- & inputs,
104- ) ) ;
105- recording. pending_executions += 1 ;
106122 }
107123
108- /// Append every pending row; a failure is reported once, and its executions simply run again.
109- fn flush ( & self , py : Python < ' _ > , recording : & mut Recording ) {
110- let adapter: & Bound < ' _ , PyAny > = self . adapter . bind ( py) ;
111- let connection: & Bound < ' _ , PyAny > = self . connection . bind ( py) ;
124+ /// The recording after appending every pending row; a failure is reported, and its executions run again.
125+ fn flushed ( & self , py : Python < ' _ > , mut recording : Recording ) -> Recording {
112126 for ( location, rows) in std:: mem:: take ( & mut recording. pending ) {
113- let written: PyResult < ( ) > = ( || {
114- if recording. tables . insert ( location. clone ( ) ) {
115- ensure_table ( adapter, connection, & location) ?;
116- }
117- append ( adapter, connection, & location, & rows)
118- } ) ( ) ;
127+ let created: bool = !recording. tables . insert ( location. clone ( ) ) ;
128+ let written: PyResult < ( ) > = ( if created {
129+ Ok ( ( ) )
130+ } else {
131+ self . connection . ensure_table ( py, & location)
132+ } )
133+ . and_then ( |( ) | self . connection . append ( py, & location, & rows) ) ;
119134 if let Err ( error) = written {
120135 recording. warnings . push ( format ! (
121136 "could not record consumed inputs in schema {}: {error}; those models and seeds run again under --changed" ,
@@ -125,6 +140,7 @@ impl ConsumptionRecorder {
125140 }
126141 recording. pending_executions = 0 ;
127142 recording. last_flush = Instant :: now ( ) ;
143+ recording
128144 }
129145
130146 /// Queue the seeds whose error audits passed, flush everything and close the connection.
@@ -145,12 +161,11 @@ impl ConsumptionRecorder {
145161 let mut recording = self . lock ( ) ?;
146162 for seed in std:: mem:: take ( & mut recording. pending_seeds ) {
147163 if !failed_audits. contains ( & seed. name ) {
148- self . queue ( & mut recording, & seed) ;
164+ * recording = self . queued ( std :: mem :: take ( & mut * recording) , & seed) ;
149165 }
150166 }
151- self . flush ( py, & mut recording) ;
152- let adapter: & Bound < ' _ , PyAny > = self . adapter . bind ( py) ;
153- let closed = adapter. call_method1 ( "close" , ( self . connection . bind ( py) , ) ) ;
167+ * recording = self . flushed ( py, std:: mem:: take ( & mut * recording) ) ;
168+ let closed = self . connection . close ( py) ;
154169 let warnings: Vec < String > = std:: mem:: take ( & mut recording. warnings ) ;
155170 closed?;
156171 Ok ( warnings)
@@ -161,7 +176,7 @@ impl ConsumptionRecorder {
161176impl ConsumptionRecorder {
162177 /// Forward a finished node, then queue a successful model, or a seed until its audits are known.
163178 fn on_node_complete ( & self , py : Python < ' _ > , node_result : Bound < ' _ , PyAny > ) -> PyResult < ( ) > {
164- self . base_on_node_complete . bind ( py ) . call1 ( ( & node_result, ) ) ?;
179+ self . base_on_node_complete . forward ( & node_result) ?;
165180 let Some ( ( kind, name) ) = kind_of ( & node_result) ? else {
166181 return Ok ( ( ) ) ;
167182 } ;
@@ -173,15 +188,15 @@ impl ConsumptionRecorder {
173188 name,
174189 } ;
175190 let mut recording = self . lock ( ) ?;
176- if kind == "seed" {
191+ if kind == SEED_KIND {
177192 recording. pending_seeds . push ( key) ;
178193 return Ok ( ( ) ) ;
179194 }
180- self . queue ( & mut recording, & key) ;
195+ * recording = self . queued ( std :: mem :: take ( & mut * recording) , & key) ;
181196 if recording. pending_executions >= FLUSH_EXECUTIONS
182197 || recording. last_flush . elapsed ( ) >= FLUSH_INTERVAL
183198 {
184- self . flush ( py, & mut recording) ;
199+ * recording = self . flushed ( py, std :: mem :: take ( & mut * recording) ) ;
185200 }
186201 Ok ( ( ) )
187202 }
@@ -213,8 +228,7 @@ impl ConsumptionRecorder {
213228 }
214229}
215230
216- /// The locations whose records this build reads: all of them under `--changed`, else the
217- /// relations of unselected upstreams whose last execution the selected nodes record.
231+ /// Schemas to read records from: all under `--changed`, else those of unselected upstreams.
218232fn read_locations ( facts : & BuildFacts , changed : bool ) -> BTreeSet < Location > {
219233 let by_key: HashMap < & NodeKey , & NodeFacts > =
220234 facts. nodes . iter ( ) . map ( |node| ( & node. key , node) ) . collect ( ) ;
@@ -254,11 +268,11 @@ pub(crate) fn prepare<'py>(
254268) -> PyResult < Prepared < ' py > > {
255269 let py: Python < ' py > = plan. py ( ) ;
256270 let facts: BuildFacts = build_facts ( ( plan, project) , ( changed, full_refresh) ) ?;
257- let connection: Bound < ' py , PyAny > = adapter . call_method1 ( "connect" , ( connection_config, ) ) ?;
271+ let connection = ConsumptionConnection :: open ( adapter , connection_config) ?;
258272 let mut warnings: Vec < String > = Vec :: new ( ) ;
259273 let mut rows: Vec < ConsumptionRow > = Vec :: new ( ) ;
260274 for location in read_locations ( & facts, changed) {
261- match read_recent ( adapter , & connection , & location) {
275+ match connection . read_recent ( py , & location) {
262276 Ok ( read) => rows. extend ( read) ,
263277 Err ( error) => warnings. push ( format ! (
264278 "could not read consumed inputs in schema {}: {error}; its models and seeds count as changed" ,
@@ -312,8 +326,9 @@ pub(crate) fn prepare<'py>(
312326 let mut failed_start: HashMap < NodeKey , String > = HashMap :: new ( ) ;
313327 let mut tables: BTreeSet < Location > = BTreeSet :: new ( ) ;
314328 for ( location, rows) in started {
315- let written: PyResult < ( ) > = ensure_table ( adapter, & connection, & location)
316- . and_then ( |( ) | append ( adapter, & connection, & location, & rows) ) ;
329+ let written: PyResult < ( ) > = connection
330+ . ensure_table ( py, & location)
331+ . and_then ( |( ) | connection. append ( py, & location, & rows) ) ;
317332 match written {
318333 Ok ( ( ) ) => {
319334 tables. insert ( location) ;
@@ -327,21 +342,16 @@ pub(crate) fn prepare<'py>(
327342 }
328343 }
329344 let recorder = ConsumptionRecorder {
330- adapter : adapter. clone ( ) . unbind ( ) ,
331- connection : connection. unbind ( ) ,
345+ connection,
332346 run_id,
333347 nodes,
334348 history,
335349 executions,
336350 failed_start,
337- base_on_node_complete : base_on_node_complete . unbind ( ) ,
351+ base_on_node_complete : NodeCompleteHook :: new ( & base_on_node_complete ) ,
338352 recording : Mutex :: new ( Recording {
339- pending : BTreeMap :: new ( ) ,
340- pending_executions : 0 ,
341- pending_seeds : Vec :: new ( ) ,
342353 tables,
343- last_flush : Instant :: now ( ) ,
344- warnings : Vec :: new ( ) ,
354+ ..Recording :: default ( )
345355 } ) ,
346356 } ;
347357 Ok ( Prepared {
0 commit comments