@@ -344,7 +344,7 @@ pub struct UpdatePackageVisibility {
344344pub struct ListPackages {
345345 pub ident : BuilderPackageIdent ,
346346 pub visibility : Vec < PackageVisibility > ,
347- pub page : i64 ,
347+ pub offset : i64 ,
348348 pub limit : i64 ,
349349}
350350
@@ -619,72 +619,82 @@ impl Package {
619619 let name_str = pl. ident . name . clone ( ) ;
620620 let parts = pl. ident . clone ( ) . parts ( ) ;
621621 let visibility = pl. visibility . clone ( ) ;
622- let page = pl. page ;
622+ let offset_val = pl. offset ;
623623 let limit = pl. limit ;
624624
625- let mut query = packages_with_channel_platform:: table
626- . filter ( packages_with_channel_platform:: origin. eq ( origin_str) )
625+ let mut base_query = packages_with_channel_platform:: table
626+ . filter ( packages_with_channel_platform:: origin. eq ( origin_str. clone ( ) ) )
627627 . into_boxed ( ) ;
628+
628629 // We need the into_boxed above to be able to conditionally filter and not break the
629630 // typesystem.
630631 if !pl. ident . name . is_empty ( ) {
631- query = query. filter ( packages_with_channel_platform:: name. eq ( name_str) )
632+ base_query =
633+ base_query. filter ( packages_with_channel_platform:: name. eq ( name_str. clone ( ) ) )
632634 } ;
633635
634- let mut pkgs = if pl. limit < 0 {
635- let query =
636- query. filter ( packages_with_channel_platform:: ident_array. contains ( parts. clone ( ) ) )
637- . filter ( packages_with_channel_platform:: visibility. eq_any ( visibility. clone ( ) ) )
638- . order ( packages_with_channel_platform:: ident. desc ( ) ) ;
639- let pkgs: std:: vec:: Vec < PackageWithChannelPlatform > = query. get_results ( conn) ?;
640- pkgs
641- } else {
642- // Use window function with COUNT(*) OVER() for efficient pagination
643- // This prevents loading all data into memory and avoids slice index panics
644- let offset_val = ( page. saturating_sub ( 1 ) ) * limit;
636+ base_query = base_query
637+ . filter ( packages_with_channel_platform:: ident_array. contains ( parts. clone ( ) ) )
638+ . filter ( packages_with_channel_platform:: visibility. eq_any ( visibility. clone ( ) ) ) ;
639+
640+ if pl. limit < 0 {
641+ // No pagination - return all records
642+ let query = base_query. order ( packages_with_channel_platform:: ident. desc ( ) ) ;
643+ let mut pkgs: std:: vec:: Vec < PackageWithChannelPlatform > = query. get_results ( conn) ?;
645644
645+ // Apply deduplication
646+ pkgs = pkgs. into_iter ( ) . unique ( ) . collect ( ) ;
647+ let count = pkgs. len ( ) as i64 ;
648+ Ok ( ( pkgs, count) )
649+ } else {
650+ // First get the total count before pagination and deduplication
651+ // We need to rebuild the query instead of cloning because BoxedSelectStatement doesn't
652+ // implement Clone
653+ let mut count_query = packages_with_channel_platform:: table
654+ . filter ( packages_with_channel_platform:: origin. eq ( origin_str. clone ( ) ) )
655+ . into_boxed ( ) ;
656+
657+ if !pl. ident . name . is_empty ( ) {
658+ count_query =
659+ count_query. filter ( packages_with_channel_platform:: name. eq ( name_str. clone ( ) ) ) ;
660+ }
661+
662+ let total_count: i64 = count_query
663+ . filter ( packages_with_channel_platform:: ident_array. contains ( parts. clone ( ) ) )
664+ . filter ( packages_with_channel_platform:: visibility. eq_any ( visibility. clone ( ) ) )
665+ . select ( count_star ( ) )
666+ . first ( conn) ?;
667+
668+ // Then get the paginated results using direct offset
646669 let query_with_pagination =
647- query. filter ( packages_with_channel_platform:: ident_array. contains ( parts. clone ( ) ) )
648- . filter ( packages_with_channel_platform:: visibility. eq_any ( visibility. clone ( ) ) )
649- . order ( packages_with_channel_platform:: ident. desc ( ) )
650- . limit ( limit)
651- . offset ( offset_val) ;
670+ base_query. order ( packages_with_channel_platform:: ident. desc ( ) )
671+ . limit ( limit)
672+ . offset ( offset_val) ;
652673
653- let paginated_rows: Vec < PackageWithChannelPlatform > = query_with_pagination. load ( conn) ?;
674+ let mut paginated_rows: Vec < PackageWithChannelPlatform > =
675+ query_with_pagination. load ( conn) ?;
654676
655677 // Apply deduplication consistently using unique_by for package identity
656- let unique_rows: Vec < PackageWithChannelPlatform > =
657- paginated_rows. into_iter ( )
658- . unique_by ( |p| ( p. ident . clone ( ) , p. origin . clone ( ) ) )
659- . collect ( ) ;
678+ paginated_rows = paginated_rows. into_iter ( )
679+ . unique_by ( |p| ( p. ident . clone ( ) , p. origin . clone ( ) ) )
680+ . collect ( ) ;
660681
661- unique_rows
662- } ;
682+ let duration_millis = start_time. elapsed ( ) . as_millis ( ) ;
683+ Histogram :: DbCallTime . set ( duration_millis as f64 ) ;
684+ Histogram :: PackageListCallTime . set ( duration_millis as f64 ) ;
663685
664- // helpful trick when debugging queries, this has Debug trait:
665- // diesel::query_builder::debug_query::<diesel::pg::Pg, _>(&query)
686+ // Package list for a whole origin is still not very
687+ // performant, and we want to track that
688+ if !pl. ident . name . is_empty ( ) {
689+ Histogram :: PackageListOriginOnlyCallTime . set ( duration_millis as f64 ) ;
690+ } else {
691+ Histogram :: PackageListOriginNameCallTime . set ( duration_millis as f64 ) ;
692+ }
666693
667- let duration_millis = start_time. elapsed ( ) . as_millis ( ) ;
668- Histogram :: DbCallTime . set ( duration_millis as f64 ) ;
669- Histogram :: PackageListCallTime . set ( duration_millis as f64 ) ;
694+ trace ! ( target: "habitat_builder_api::server::resources::pkgs::versions" , "Package::list for {:?}, returned {} items out of {} total" , pl. ident, paginated_rows. len( ) , total_count) ;
670695
671- // Package list for a whole origin is still not very
672- // performant, and we want to track that
673- if !pl. ident . name . is_empty ( ) {
674- Histogram :: PackageListOriginOnlyCallTime . set ( duration_millis as f64 ) ;
675- } else {
676- Histogram :: PackageListOriginNameCallTime . set ( duration_millis as f64 ) ;
696+ Ok ( ( paginated_rows, total_count) )
677697 }
678-
679- trace ! ( target: "habitat_builder_api::server::resources::pkgs::versions" , "Package::list for {:?}, returned {} items" , pl. ident, pkgs. len( ) ) ;
680-
681- // TODO: Look for a performant Postgresql fix
682- // and possibly rethink the channels design
683- pkgs = pkgs. into_iter ( ) . unique ( ) . collect ( ) ;
684- trace ! ( target: "habitat_builder_api::server::resources::pkgs::versions" , "Package::list for {:?} after de-dup has {} items" , pl. ident, pkgs. len( ) ) ;
685-
686- let new_count = pkgs. len ( ) as i64 ;
687- Ok ( ( pkgs, new_count) )
688698 }
689699
690700 pub fn list_distinct ( pl : & ListPackages ,
@@ -698,7 +708,7 @@ impl Package {
698708 let name_str = pl. ident . name . clone ( ) ;
699709 let parts = pl. ident . clone ( ) . parts ( ) ;
700710 let visibility = pl. visibility . clone ( ) ;
701- let page = pl. page ;
711+ let offset_val = pl. offset ;
702712 let limit = pl. limit ;
703713
704714 let mut count_query =
@@ -733,7 +743,7 @@ impl Package {
733743 }
734744
735745 let limit_i64 = limit;
736- let offset_i64 = ( page . saturating_sub ( 1 ) ) * limit ;
746+ let offset_i64 = offset_val ;
737747
738748 let rows: Vec < ( String , String ) > =
739749 page_query. limit ( limit_i64) . offset ( offset_i64) . load ( conn) ?;
@@ -771,7 +781,7 @@ impl Package {
771781 // Extract cloned copies out of pl
772782 let origin_str = pl. ident . origin . clone ( ) ;
773783 let visibility = pl. visibility . clone ( ) ;
774- let page = pl. page ;
784+ let offset_val = pl. offset ;
775785 let limit = pl. limit ;
776786
777787 let base_query = origin_package_settings:: table
@@ -791,7 +801,7 @@ impl Package {
791801 . collect ( ) ;
792802
793803 let total_count = unique_by_name. len ( ) as i64 ;
794- let start = ( ( page . saturating_sub ( 1 ) ) * limit ) as usize ;
804+ let start = offset_val as usize ;
795805 let end = ( start + limit as usize ) . min ( unique_by_name. len ( ) ) ;
796806 let results = if limit < 0 {
797807 unique_by_name. clone ( )
0 commit comments