@@ -107,10 +107,6 @@ pub struct UpdateStats {
107107 pub num_errors : Counter ,
108108 /// Processing counters for tracking in-process rows.
109109 pub processing : ProcessingCounters ,
110- /// Cumulative total count of items (for display purposes)
111- /// This represents the actual total after applying additions and deletions
112- #[ serde( skip) ]
113- pub cumulative_total : Counter ,
114110}
115111
116112impl UpdateStats {
@@ -123,7 +119,6 @@ impl UpdateStats {
123119 num_reprocesses : self . num_reprocesses . delta ( & base. num_reprocesses ) ,
124120 num_errors : self . num_errors . delta ( & base. num_errors ) ,
125121 processing : self . processing . delta ( & base. processing ) ,
126- cumulative_total : self . cumulative_total . clone ( ) ,
127122 }
128123 }
129124
@@ -135,9 +130,6 @@ impl UpdateStats {
135130 self . num_reprocesses . merge ( & delta. num_reprocesses ) ;
136131 self . num_errors . merge ( & delta. num_errors ) ;
137132 self . processing . merge ( & delta. processing ) ;
138- // Update cumulative total: add insertions, subtract deletions
139- let net_change = delta. num_insertions . get ( ) - delta. num_deletions . get ( ) ;
140- self . cumulative_total . inc ( net_change) ;
141133 }
142134
143135 pub fn has_any_change ( & self ) -> bool {
@@ -199,113 +191,96 @@ impl OperationInProcessStats {
199191 }
200192}
201193
194+ struct UpdateStatsSegment {
195+ count : i64 ,
196+ symbol : char ,
197+ label : & ' static str ,
198+ bar_width : usize ,
199+ }
200+
201+ impl UpdateStatsSegment {
202+ pub fn new ( count : i64 , symbol : char , label : & ' static str ) -> Self {
203+ Self {
204+ count,
205+ symbol,
206+ label,
207+ bar_width : 0 ,
208+ }
209+ }
210+ }
211+
212+ const BAR_WIDTH : usize = 40 ;
213+
214+ fn indices_of < T , const N : usize > ( _: & [ T ; N ] ) -> [ usize ; N ] {
215+ std:: array:: from_fn ( |i| i)
216+ }
202217impl std:: fmt:: Display for UpdateStats {
203218 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
204- let mut segments = Vec :: new ( ) ;
205- let num_insertions = self . num_insertions . get ( ) ;
206- let num_deletions = self . num_deletions . get ( ) ;
207- let num_updates = self . num_updates . get ( ) ;
208- let num_no_change = self . num_no_change . get ( ) ;
209- let num_errors = self . num_errors . get ( ) ;
219+ let mut segments: [ UpdateStatsSegment ; _] = [
220+ UpdateStatsSegment :: new ( self . num_insertions . get ( ) , '+' , "added" ) ,
221+ UpdateStatsSegment :: new ( self . num_updates . get ( ) , '~' , "updated" ) ,
222+ UpdateStatsSegment :: new ( self . num_reprocesses . get ( ) , '*' , "reprocessed" ) ,
223+ UpdateStatsSegment :: new ( self . num_deletions . get ( ) , '-' , "deleted" ) ,
224+ UpdateStatsSegment :: new ( self . num_no_change . get ( ) , '.' , "no change" ) ,
225+ UpdateStatsSegment :: new ( self . num_errors . get ( ) , '!' , "errors" ) ,
226+ ] ;
210227 let num_in_process = self . processing . get_in_process ( ) ;
211- let num_reprocesses = self . num_reprocesses . get ( ) ;
212- let total = num_insertions + num_deletions + num_updates + num_no_change + num_reprocesses ;
228+ let processed_count = segments . iter ( ) . map ( |seg| seg . count ) . sum :: < i64 > ( ) ;
229+ let total = num_in_process + processed_count ;
213230
214- // Progress bar segments
215- if total > 0 {
216- if num_insertions > 0 {
217- segments. push ( ( num_insertions, "+" , format ! ( "(+{} added)" , num_insertions) ) ) ;
218- }
219- if num_deletions > 0 {
220- segments. push ( ( num_deletions, "-" , format ! ( "(-{} removed)" , num_deletions) ) ) ;
221- }
222- if num_updates > 0 {
223- segments. push ( ( num_updates, "~" , format ! ( "(~{} updated)" , num_updates) ) ) ;
224- }
225- if num_no_change > 0 {
226- segments. push ( ( num_no_change, " " , "" . to_string ( ) ) ) ;
227- }
231+ if total <= 0 {
232+ write ! ( f, "No input data" ) ?;
233+ return Ok ( ( ) ) ;
228234 }
229235
230- // Error handling
231- if num_errors > 0 {
232- write ! ( f, "{} rows failed" , num_errors) ?;
233- if !segments. is_empty ( ) {
234- write ! ( f, "; " ) ?;
236+ let mut segments_indices = indices_of ( & segments) ;
237+ segments_indices. sort_by_key ( |& i| segments[ i] . count ) ;
238+
239+ let mut remaining_width = BAR_WIDTH as u64 ;
240+ let mut remaining_count = total as u64 ;
241+ for idx in segments_indices. iter ( ) {
242+ let seg = & mut segments[ * idx] ;
243+ if seg. count > 0 {
244+ if remaining_count == 0 {
245+ error ! ( "remaining_count is 0, but still have segments to process" ) ;
246+ break ;
247+ }
248+ let width = std:: cmp:: max (
249+ // rounded division of remaining_width * seg.count / remaining_count
250+ ( remaining_width * ( seg. count as u64 ) + remaining_count / 2 ) / remaining_count,
251+ 1 ,
252+ ) ;
253+ seg. bar_width = width as usize ;
254+ remaining_width -= width;
255+ remaining_count -= seg. count as u64 ;
235256 }
236257 }
237258
238- // Progress bar
239- if !segments. is_empty ( ) {
240- let mut sorted_segments = segments. clone ( ) ;
241- sorted_segments. sort_by_key ( |s| s. 0 ) ;
242- sorted_segments. reverse ( ) ;
243-
244- let bar_width = 40 ;
245- let mut bar = String :: new ( ) ;
246-
247- let mut remaining_width = bar_width;
248-
249- for ( count, segment_type, _) in sorted_segments. iter ( ) {
250- let segment_width = ( * count * bar_width as i64 / total as i64 ) as usize ;
251- let width = std:: cmp:: min ( segment_width, remaining_width) ;
252- if width > 0 {
253- // Calculate completed and remaining portions
254- let completed_portion =
255- ( width as f64 * ( total - num_in_process) as f64 / total as f64 ) as usize ;
256- let remaining_portion = width - completed_portion;
257-
258- // Add segment with appropriate characters based on type
259- if completed_portion > 0 {
260- let completed_char = match * segment_type {
261- "+" => "█" ,
262- "-" => "▓" ,
263- "~" => "▒" ,
264- _ => "░" ,
265- } ;
266- bar. push_str ( & completed_char. repeat ( completed_portion) ) ;
267- }
268-
269- if remaining_portion > 0 {
270- let remaining_char = match * segment_type {
271- "+" => "▒" ,
272- "-" => "░" ,
273- "~" => "░" ,
274- _ => " " ,
275- } ;
276- bar. push_str ( & remaining_char. repeat ( remaining_portion) ) ;
277- }
278-
279- remaining_width = remaining_width. saturating_sub ( width) ;
280- }
281- }
282- if remaining_width > 0 {
283- bar. push_str ( & " " . repeat ( remaining_width) ) ;
259+ write ! ( f, "[" ) ?;
260+ for segment in segments. iter ( ) {
261+ for _ in 0 ..segment. bar_width {
262+ write ! ( f, "{}" , segment. symbol) ?;
284263 }
285- // Use total from current operations - this represents the actual record count
286- write ! ( f, "[{}] {}/{} records " , bar, total - num_in_process, total) ?;
287-
288- // Add segment labels with different grey shades for each segment type
289- let mut first = true ;
290- for ( _, segment_type, label) in segments. iter ( ) {
291- if !label. is_empty ( ) {
292- if !first {
293- write ! ( f, " " ) ?;
294- }
295- write ! ( f, "{}" , label) ?;
296- first = false ;
297- }
298- }
299- } else {
300- write ! ( f, "No changes" ) ?;
301264 }
302-
303- // In-process info with grey coloring
304- if num_in_process > 0 {
305- if !segments. is_empty ( ) {
306- write ! ( f, " " ) ?;
265+ for _ in 0 ..remaining_width {
266+ write ! ( f, " " ) ?;
267+ }
268+ write ! ( f, "] {processed_count}/{total} source rows" ) ?;
269+
270+ if processed_count > 0 {
271+ let mut delimiter = ':' ;
272+ for seg in segments. iter ( ) {
273+ if seg. count > 0 {
274+ write ! (
275+ f,
276+ "{delimiter} ({symbol}) {count} {label}" ,
277+ count = seg. count,
278+ symbol = seg. symbol,
279+ label = seg. label,
280+ ) ?;
281+ delimiter = ',' ;
282+ }
307283 }
308- write ! ( f, "({} in process)" , num_in_process) ?;
309284 }
310285
311286 Ok ( ( ) )
@@ -575,21 +550,24 @@ mod tests {
575550 let stats = UpdateStats :: default ( ) ;
576551
577552 // Test with no activity
578- assert_eq ! ( format!( "{}" , stats) , "No changes " ) ;
553+ assert_eq ! ( format!( "{}" , stats) , "No input data " ) ;
579554
580555 // Test with in-process rows (no segments yet, so just shows in-process)
581556 stats. processing . start ( 5 ) ;
582557 let display = format ! ( "{}" , stats) ;
583- assert ! ( display. contains( "5 in process" ) ) ;
558+ assert_eq ! (
559+ display,
560+ "[ ] 0/5 source rows"
561+ ) ;
584562
585563 // Test with mixed activity
586564 stats. num_insertions . inc ( 3 ) ;
587565 stats. num_errors . inc ( 1 ) ;
588- stats. cumulative_total . inc ( 3 ) ;
589566 let display = format ! ( "{}" , stats) ;
590- assert ! ( display. contains( "1 rows failed" ) ) ;
591- assert ! ( display. contains( "(+3 added)" ) ) ;
592- assert ! ( display. contains( "5 in process" ) ) ;
567+ assert_eq ! (
568+ display,
569+ "[++++++++++++++!!!! ] 4/9 source rows: (+) 3 added, (!) 1 errors"
570+ ) ;
593571 }
594572
595573 #[ test]
0 commit comments