@@ -17,6 +17,7 @@ use duckdb::Connection;
1717use duckdb:: ToSql ;
1818use duckdb:: params_from_iter;
1919
20+ use super :: dto:: ChartHistory ;
2021use super :: dto:: ChartResponse ;
2122use super :: dto:: CommitPoint ;
2223use super :: dto:: GroupChartsResponse ;
@@ -193,7 +194,12 @@ impl SeriesAccumulator {
193194 }
194195 }
195196
196- fn finish ( self , display_name : String , unit_kind : UnitKind ) -> ChartResponse {
197+ fn finish (
198+ self ,
199+ display_name : String ,
200+ unit_kind : UnitKind ,
201+ history : ChartHistory ,
202+ ) -> ChartResponse {
197203 let total = self . commits . len ( ) ;
198204 let mut series_map = serde_json:: Map :: new ( ) ;
199205 for ( k, mut v) in self . series {
@@ -205,13 +211,19 @@ impl SeriesAccumulator {
205211 ChartResponse {
206212 display_name,
207213 unit_kind,
214+ history,
208215 commits : self . commits ,
209216 series : series_map,
210217 series_meta : self . tags ,
211218 }
212219 }
213220}
214221
222+ struct SeededCommits {
223+ commits : Vec < CommitPoint > ,
224+ history : ChartHistory ,
225+ }
226+
215227/// Resolve a chart's x-axis: every commit in the requested commit-window
216228/// whose timestamp is at or after the earliest commit that has a row in the
217229/// fact table for this chart. Returns the list oldest-first; an empty list
@@ -234,32 +246,65 @@ fn seeded_commits_in_window(
234246 earliest_subquery : & str ,
235247 subquery_binds : Vec < Box < dyn ToSql > > ,
236248 window : & CommitWindow ,
237- ) -> Result < Vec < CommitPoint > > {
249+ ) -> Result < SeededCommits > {
250+ let window_filter = match window {
251+ CommitWindow :: All => "" ,
252+ CommitWindow :: Last ( _) => "WHERE rn > total_commits - ?" ,
253+ } ;
238254 let sql = format ! (
239255 r#"
240- SELECT c.commit_sha,
241- CAST(c.timestamp AS VARCHAR),
242- COALESCE(c.message, ''),
243- c.url
244- FROM commits c
245- WHERE c.timestamp >= ({earliest_subquery}){window_filter}
246- ORDER BY c.timestamp ASC
256+ WITH eligible AS (
257+ SELECT c.commit_sha,
258+ c.timestamp,
259+ COALESCE(c.message, '') AS message,
260+ c.url,
261+ row_number() OVER (ORDER BY c.timestamp ASC, c.commit_sha ASC) AS rn,
262+ count(*) OVER () AS total_commits
263+ FROM commits c
264+ WHERE c.timestamp >= ({earliest_subquery})
265+ )
266+ SELECT commit_sha,
267+ CAST(timestamp AS VARCHAR),
268+ message,
269+ url,
270+ total_commits
271+ FROM eligible
272+ {window_filter}
273+ ORDER BY timestamp ASC, commit_sha ASC
247274 "# ,
248- window_filter = window. sql_filter( ) ,
249275 ) ;
250276 let mut stmt = conn. prepare ( & sql) ?;
251277 let mut binds = subquery_binds;
252278 push_window_limit ( & mut binds, window) ;
253279 let rows = stmt. query_map ( params_from_iter ( binds. iter ( ) ) , |row| {
254- Ok ( CommitPoint {
255- sha : row. get ( 0 ) ?,
256- timestamp : row. get ( 1 ) ?,
257- message : row. get ( 2 ) ?,
258- url : row. get ( 3 ) ?,
259- } )
280+ Ok ( (
281+ CommitPoint {
282+ sha : row. get ( 0 ) ?,
283+ timestamp : row. get ( 1 ) ?,
284+ message : row. get ( 2 ) ?,
285+ url : row. get ( 3 ) ?,
286+ } ,
287+ row. get :: < _ , i64 > ( 4 ) ?,
288+ ) )
260289 } ) ?;
261- let out: Vec < CommitPoint > = rows. collect :: < Result < _ , _ > > ( ) ?;
262- Ok ( out)
290+ let rows: Vec < ( CommitPoint , i64 ) > = rows. collect :: < Result < _ , _ > > ( ) ?;
291+ let total_commits = rows
292+ . first ( )
293+ . map ( |( _, total) | usize:: try_from ( * total) )
294+ . transpose ( ) ?
295+ . unwrap_or_default ( ) ;
296+ let commits: Vec < CommitPoint > = rows. into_iter ( ) . map ( |( commit, _) | commit) . collect ( ) ;
297+ let loaded_commits = commits. len ( ) ;
298+ let start_index = total_commits. saturating_sub ( loaded_commits) ;
299+ Ok ( SeededCommits {
300+ commits,
301+ history : ChartHistory {
302+ total_commits,
303+ start_index,
304+ loaded_commits,
305+ complete : loaded_commits == total_commits,
306+ } ,
307+ } )
263308}
264309
265310/// Append the commit-window `LIMIT` bind value to a parameter list, when the
@@ -302,11 +347,12 @@ fn collect_query_chart(
302347 ] ,
303348 window,
304349 ) ?;
305- if seeded. is_empty ( ) {
350+ if seeded. commits . is_empty ( ) {
306351 return Ok ( None ) ;
307352 }
353+ let history = seeded. history ;
308354 let mut acc = SeriesAccumulator :: new ( ) ;
309- acc. seed_commits ( seeded) ;
355+ acc. seed_commits ( seeded. commits ) ;
310356
311357 let sql = format ! (
312358 r#"
@@ -359,7 +405,7 @@ fn collect_query_chart(
359405 name. push_str ( sf) ;
360406 }
361407 name. push_str ( & format ! ( " Q{query_idx} [{storage}]" ) ) ;
362- Ok ( Some ( acc. finish ( name, UnitKind :: TimeNs ) ) )
408+ Ok ( Some ( acc. finish ( name, UnitKind :: TimeNs , history ) ) )
363409}
364410
365411fn collect_compression_time_chart (
@@ -381,11 +427,12 @@ fn collect_compression_time_chart(
381427 ] ,
382428 window,
383429 ) ?;
384- if seeded. is_empty ( ) {
430+ if seeded. commits . is_empty ( ) {
385431 return Ok ( None ) ;
386432 }
433+ let history = seeded. history ;
387434 let mut acc = SeriesAccumulator :: new ( ) ;
388- acc. seed_commits ( seeded) ;
435+ acc. seed_commits ( seeded. commits ) ;
389436
390437 let sql = format ! (
391438 r#"
@@ -427,7 +474,7 @@ fn collect_compression_time_chart(
427474 name. push ( '/' ) ;
428475 name. push_str ( v) ;
429476 }
430- Ok ( Some ( acc. finish ( name, UnitKind :: TimeNs ) ) )
477+ Ok ( Some ( acc. finish ( name, UnitKind :: TimeNs , history ) ) )
431478}
432479
433480fn collect_compression_size_chart (
@@ -449,11 +496,12 @@ fn collect_compression_size_chart(
449496 ] ,
450497 window,
451498 ) ?;
452- if seeded. is_empty ( ) {
499+ if seeded. commits . is_empty ( ) {
453500 return Ok ( None ) ;
454501 }
502+ let history = seeded. history ;
455503 let mut acc = SeriesAccumulator :: new ( ) ;
456- acc. seed_commits ( seeded) ;
504+ acc. seed_commits ( seeded. commits ) ;
457505
458506 let sql = format ! (
459507 r#"
@@ -493,7 +541,7 @@ fn collect_compression_size_chart(
493541 name. push ( '/' ) ;
494542 name. push_str ( v) ;
495543 }
496- Ok ( Some ( acc. finish ( name, UnitKind :: Bytes ) ) )
544+ Ok ( Some ( acc. finish ( name, UnitKind :: Bytes , history ) ) )
497545}
498546
499547fn collect_random_access_chart (
@@ -510,11 +558,12 @@ fn collect_random_access_chart(
510558 vec ! [ Box :: new( dataset. to_string( ) ) ] ,
511559 window,
512560 ) ?;
513- if seeded. is_empty ( ) {
561+ if seeded. commits . is_empty ( ) {
514562 return Ok ( None ) ;
515563 }
564+ let history = seeded. history ;
516565 let mut acc = SeriesAccumulator :: new ( ) ;
517- acc. seed_commits ( seeded) ;
566+ acc. seed_commits ( seeded. commits ) ;
518567
519568 let sql = format ! (
520569 r#"
@@ -545,7 +594,11 @@ fn collect_random_access_chart(
545594 acc. record ( & format, idx, value_ns as f64 ) ;
546595 acc. tag ( & format, None , Some ( & format) ) ;
547596 }
548- Ok ( Some ( acc. finish ( dataset. to_string ( ) , UnitKind :: TimeNs ) ) )
597+ Ok ( Some ( acc. finish (
598+ dataset. to_string ( ) ,
599+ UnitKind :: TimeNs ,
600+ history,
601+ ) ) )
549602}
550603
551604fn collect_vector_search_chart (
@@ -570,11 +623,12 @@ fn collect_vector_search_chart(
570623 ] ,
571624 window,
572625 ) ?;
573- if seeded. is_empty ( ) {
626+ if seeded. commits . is_empty ( ) {
574627 return Ok ( None ) ;
575628 }
629+ let history = seeded. history ;
576630 let mut acc = SeriesAccumulator :: new ( ) ;
577- acc. seed_commits ( seeded) ;
631+ acc. seed_commits ( seeded. commits ) ;
578632
579633 let sql = format ! (
580634 r#"
@@ -613,5 +667,6 @@ fn collect_vector_search_chart(
613667 Ok ( Some ( acc. finish (
614668 format ! ( "{dataset} / {layout} (threshold={threshold})" ) ,
615669 UnitKind :: TimeNs ,
670+ history,
616671 ) ) )
617672}
0 commit comments