@@ -14,6 +14,7 @@ use crate::views::EncodableVersion;
1414use axum:: Json ;
1515use axum:: extract:: FromRequestParts ;
1616use axum_extra:: extract:: Query ;
17+ use crates_io_diesel_helpers:: semver_ord;
1718use diesel:: dsl:: not;
1819use diesel:: prelude:: * ;
1920use diesel_async:: { AsyncPgConnection , RunQueryDsl } ;
@@ -234,9 +235,6 @@ async fn list_by_date(
234235
235236/// Seek-based pagination of versions by semver
236237///
237- /// Unfortunately, Heroku Postgres has no support for the semver PG extension.
238- /// Therefore, we need to perform both sorting and pagination manually on the server.
239- ///
240238/// # Panics
241239///
242240/// This function will panic if `option` is built with `enable_pages` set to true.
@@ -249,131 +247,93 @@ async fn list_by_semver(
249247) -> AppResult < PaginatedVersionsAndPublishers > {
250248 use seek:: * ;
251249
252- let include = params. include ( ) ?;
253- let mut query = versions:: table
254- . filter ( versions:: crate_id. eq ( crate_id) )
255- . into_boxed ( ) ;
256-
257- if !params. nums . is_empty ( ) {
258- query = query. filter ( versions:: num. eq_any ( params. nums . iter ( ) . map ( |s| s. as_str ( ) ) ) ) ;
259- }
260-
261- let ( data, total, release_tracks) = if let Some ( options) = options {
262- // Since versions will only increase in the future and both sorting and pagination need to
263- // happen on the app server, implementing it with fetching only the data needed for sorting
264- // and pagination, then making another query for the data to respond with, would minimize
265- // payload and memory usage. This way, we can utilize the sorted map and enrich it later
266- // without sorting twice.
267- // Sorting by semver but opted for id as the seek key because num can be quite lengthy,
268- // while id values are significantly smaller.
250+ let make_base_query = || {
251+ let mut query = versions:: table
252+ . filter ( versions:: crate_id. eq ( crate_id) )
253+ . left_outer_join ( users:: table)
254+ . select ( <( Version , Option < User > ) >:: as_select ( ) )
255+ . into_boxed ( ) ;
269256
270- let mut sorted_versions = IndexMap :: new ( ) ;
257+ if !params. nums . is_empty ( ) {
258+ query = query. filter ( versions:: num. eq_any ( params. nums . iter ( ) . map ( |s| s. as_str ( ) ) ) ) ;
259+ }
271260 query
272- . select ( ( versions:: id, versions:: num, versions:: yanked) )
273- . load_stream :: < ( i32 , String , bool ) > ( conn)
274- . await ?
275- . try_for_each ( |( id, num, yanked) | {
276- let semver = semver:: Version :: parse ( & num) . ok ( ) ;
277- sorted_versions. insert ( id, ( semver, yanked, None ) ) ;
278- future:: ready ( Ok ( ( ) ) )
279- } )
280- . await ?;
261+ } ;
281262
282- sorted_versions
283- . sort_unstable_by ( |_, ( semver_a, _, _) , _, ( semver_b, _, _) | semver_b. cmp ( semver_a) ) ;
263+ let mut query = make_base_query ( ) ;
284264
265+ if let Some ( options) = options {
285266 assert ! (
286267 !matches!( & options. page, Page :: Numeric ( _) ) ,
287268 "?page= is not supported"
288269 ) ;
289-
290- let release_tracks = include. release_tracks . then ( || {
291- ReleaseTracks :: from_sorted_semver_iter (
292- sorted_versions
293- . values ( )
294- . filter ( |( _, yanked, _) | !yanked)
295- . filter_map ( |( semver, _, _) | semver. as_ref ( ) ) ,
270+ if let Some ( SeekPayload :: Semver ( Semver { num, id } ) ) = Seek :: Semver . after ( & options. page ) ? {
271+ query = query. filter (
272+ versions:: semver_ord
273+ . eq ( semver_ord ( num. clone ( ) ) )
274+ . and ( versions:: id. lt ( id) )
275+ . or ( versions:: semver_ord. lt ( semver_ord ( num) ) ) ,
296276 )
297- } ) ;
298-
299- let mut idx = Some ( 0 ) ;
300- if let Some ( SeekPayload :: Semver ( Semver { id } ) ) = Seek :: Semver . after ( & options. page ) ? {
301- idx = sorted_versions
302- . get_index_of ( & id)
303- . filter ( |i| i + 1 < sorted_versions. len ( ) )
304- . map ( |i| i + 1 ) ;
305277 }
306- if let Some ( start) = idx {
307- let end = ( start + options. per_page as usize ) . min ( sorted_versions. len ( ) ) ;
308- let ids = sorted_versions[ start..end]
309- . keys ( )
310- . cloned ( )
311- . collect :: < Vec < _ > > ( ) ;
278+ query = query. limit ( options. per_page ) ;
279+ }
280+
281+ query = query. order ( ( versions:: semver_ord. desc ( ) , versions:: id. desc ( ) ) ) ;
282+
283+ let data: Vec < ( Version , Option < User > ) > = query. load ( conn) . await ?;
284+ let mut next_page = None ;
285+ if let Some ( options) = options {
286+ next_page = next_seek_params ( & data, options, |last| Seek :: Semver . to_payload ( last) ) ?
287+ . map ( |p| req. query_with_params ( p) ) ;
288+ } ;
289+
290+ let release_tracks = if params. include ( ) ?. release_tracks {
291+ let mut sorted_versions = IndexSet :: new ( ) ;
292+ if options. is_some ( ) {
312293 versions:: table
313294 . filter ( versions:: crate_id. eq ( crate_id) )
314- . left_outer_join ( users:: table)
315- . select ( <( Version , Option < User > ) >:: as_select ( ) )
316- . filter ( versions:: id. eq_any ( ids) )
317- . load_stream :: < ( Version , Option < User > ) > ( conn)
295+ . filter ( not ( versions:: yanked) )
296+ . select ( versions:: num)
297+ . load_stream :: < String > ( conn)
318298 . await ?
319- . try_for_each ( |row| {
320- // The versions are already sorted, and we only need to enrich the fetched rows into them.
321- // Therefore, other values can now be safely ignored.
322- sorted_versions
323- . entry ( row. 0 . id )
324- . and_modify ( |entry| * entry = ( None , false , Some ( row) ) ) ;
325-
299+ . try_for_each ( |num| {
300+ if let Ok ( semver) = semver:: Version :: parse ( & num) {
301+ sorted_versions. insert ( semver) ;
302+ } ;
326303 future:: ready ( Ok ( ( ) ) )
327304 } )
328305 . await ?;
329-
330- let len = sorted_versions. len ( ) ;
331- (
332- sorted_versions
333- . into_values ( )
334- . filter_map ( |( _, _, v) | v)
335- . collect ( ) ,
336- len,
337- release_tracks,
338- )
339306 } else {
340- ( vec ! [ ] , 0 , release_tracks)
307+ sorted_versions = data
308+ . iter ( )
309+ . flat_map ( |( version, _) | {
310+ ( !version. yanked )
311+ . then_some ( version)
312+ . and_then ( |v| semver:: Version :: parse ( & v. num ) . ok ( ) )
313+ } )
314+ . collect ( ) ;
341315 }
316+
317+ sorted_versions. sort_unstable_by ( |a, b| b. cmp ( a) ) ;
318+ Some ( ReleaseTracks :: from_sorted_semver_iter (
319+ sorted_versions. iter ( ) ,
320+ ) )
342321 } else {
343- let mut data = IndexMap :: new ( ) ;
344- query
345- . left_outer_join ( users:: table)
346- . select ( <( Version , Option < User > ) >:: as_select ( ) )
347- . load_stream :: < ( Version , Option < User > ) > ( conn)
348- . await ?
349- . try_for_each ( |row| {
350- if let Ok ( semver) = semver:: Version :: parse ( & row. 0 . num ) {
351- data. insert ( semver, row) ;
352- } ;
353- future:: ready ( Ok ( ( ) ) )
354- } )
355- . await ?;
356- data. sort_unstable_by ( |a, _, b, _| b. cmp ( a) ) ;
357- let total = data. len ( ) ;
358- let release_tracks = include. release_tracks . then ( || {
359- ReleaseTracks :: from_sorted_semver_iter (
360- data. iter ( )
361- . flat_map ( |( semver, ( version, _) ) | ( !version. yanked ) . then_some ( semver) ) ,
362- )
363- } ) ;
364- ( data. into_values ( ) . collect ( ) , total, release_tracks)
322+ None
365323 } ;
366324
367- let mut next_page = None ;
368- if let Some ( options) = options {
369- next_page = next_seek_params ( & data, options, |last| Seek :: Semver . to_payload ( last) ) ?
370- . map ( |p| req. query_with_params ( p) )
325+ // Since the total count is retrieved through an additional query, to maintain consistency
326+ // with other pagination methods, we only make a count query while data is not empty.
327+ let total = if !data. is_empty ( ) {
328+ make_base_query ( ) . count ( ) . get_result ( conn) . await ?
329+ } else {
330+ 0
371331 } ;
372332
373333 Ok ( PaginatedVersionsAndPublishers {
374334 data,
375335 meta : ResponseMeta {
376- total : total as i64 ,
336+ total,
377337 next_page,
378338 release_tracks,
379339 } ,
@@ -392,6 +352,7 @@ mod seek {
392352 seek ! (
393353 pub enum Seek {
394354 Semver {
355+ num: String ,
395356 id: i32 ,
396357 } ,
397358 Date {
@@ -406,7 +367,10 @@ mod seek {
406367 pub ( crate ) fn to_payload ( & self , record : & ( Version , Option < User > ) ) -> SeekPayload {
407368 let ( Version { id, created_at, .. } , _) = * record;
408369 match * self {
409- Seek :: Semver => SeekPayload :: Semver ( Semver { id } ) ,
370+ Seek :: Semver => SeekPayload :: Semver ( Semver {
371+ num : record. 0 . num . clone ( ) ,
372+ id,
373+ } ) ,
410374 Seek :: Date => SeekPayload :: Date ( Date { created_at, id } ) ,
411375 }
412376 }
0 commit comments