@@ -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 } ;
@@ -98,13 +99,8 @@ pub async fn list_versions(
9899 None => None ,
99100 } ;
100101
101- // Sort by semver by default
102- let versions_and_publishers = match & params. sort . as_ref ( ) . map ( |s| s. to_lowercase ( ) ) . as_deref ( ) {
103- Some ( "date" ) => {
104- list_by_date ( crate_id, pagination. as_ref ( ) , & params, & req, & mut conn) . await ?
105- }
106- _ => list_by_semver ( crate_id, pagination. as_ref ( ) , & params, & req, & mut conn) . await ?,
107- } ;
102+ let versions_and_publishers =
103+ list ( crate_id, pagination. as_ref ( ) , & params, & req, & mut conn) . await ?;
108104
109105 let versions = versions_and_publishers
110106 . data
@@ -125,12 +121,12 @@ pub async fn list_versions(
125121 } ) )
126122}
127123
128- /// Seek-based pagination of versions by date
124+ /// Seek-based pagination of versions
129125///
130126/// # Panics
131127///
132- /// This function will panic if `option ` is built with `enable_pages` set to true.
133- async fn list_by_date (
128+ /// This function will panic if `options ` is built with `enable_pages` set to true.
129+ async fn list (
134130 crate_id : i32 ,
135131 options : Option < & PaginationOptions > ,
136132 params : & ListQueryParams ,
@@ -139,6 +135,11 @@ async fn list_by_date(
139135) -> AppResult < PaginatedVersionsAndPublishers > {
140136 use seek:: * ;
141137
138+ let seek = match & params. sort . as_ref ( ) . map ( |s| s. to_lowercase ( ) ) . as_deref ( ) {
139+ Some ( "date" ) => Seek :: Date ,
140+ _ => Seek :: Semver ,
141+ } ;
142+
142143 let make_base_query = || {
143144 let mut query = versions:: table
144145 . filter ( versions:: crate_id. eq ( crate_id) )
@@ -159,23 +160,40 @@ async fn list_by_date(
159160 !matches!( & options. page, Page :: Numeric ( _) ) ,
160161 "?page= is not supported"
161162 ) ;
162- if let Some ( SeekPayload :: Date ( Date { created_at, id } ) ) = Seek :: Date . after ( & options. page ) ? {
163- query = query. filter (
164- versions:: created_at
165- . eq ( created_at)
166- . and ( versions:: id. lt ( id) )
167- . or ( versions:: created_at. lt ( created_at) ) ,
168- )
163+
164+ match seek. after ( & options. page ) ? {
165+ Some ( SeekPayload :: Date ( Date { created_at, id } ) ) => {
166+ query = query. filter (
167+ versions:: created_at
168+ . eq ( created_at)
169+ . and ( versions:: id. lt ( id) )
170+ . or ( versions:: created_at. lt ( created_at) ) ,
171+ )
172+ }
173+ Some ( SeekPayload :: Semver ( Semver { num, id } ) ) => {
174+ query = query. filter (
175+ versions:: semver_ord
176+ . eq ( semver_ord ( num. clone ( ) ) )
177+ . and ( versions:: id. lt ( id) )
178+ . or ( versions:: semver_ord. lt ( semver_ord ( num) ) ) ,
179+ )
180+ }
181+ None => { }
169182 }
183+
170184 query = query. limit ( options. per_page ) ;
171185 }
172186
173- query = query. order ( ( versions:: created_at. desc ( ) , versions:: id. desc ( ) ) ) ;
187+ if seek == Seek :: Date {
188+ query = query. order ( ( versions:: created_at. desc ( ) , versions:: id. desc ( ) ) ) ;
189+ } else {
190+ query = query. order ( ( versions:: semver_ord. desc ( ) , versions:: id. desc ( ) ) ) ;
191+ }
174192
175193 let data: Vec < ( Version , Option < User > ) > = query. load ( conn) . await ?;
176194 let mut next_page = None ;
177195 if let Some ( options) = options {
178- next_page = next_seek_params ( & data, options, |last| Seek :: Date . to_payload ( last) ) ?
196+ next_page = next_seek_params ( & data, options, |last| seek . to_payload ( last) ) ?
179197 . map ( |p| req. query_with_params ( p) ) ;
180198 } ;
181199
@@ -232,154 +250,6 @@ async fn list_by_date(
232250 } )
233251}
234252
235- /// Seek-based pagination of versions by semver
236- ///
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- ///
240- /// # Panics
241- ///
242- /// This function will panic if `option` is built with `enable_pages` set to true.
243- async fn list_by_semver (
244- crate_id : i32 ,
245- options : Option < & PaginationOptions > ,
246- params : & ListQueryParams ,
247- req : & Parts ,
248- conn : & mut AsyncPgConnection ,
249- ) -> AppResult < PaginatedVersionsAndPublishers > {
250- use seek:: * ;
251-
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.
269-
270- let mut sorted_versions = IndexMap :: new ( ) ;
271- 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 ?;
281-
282- sorted_versions
283- . sort_unstable_by ( |_, ( semver_a, _, _) , _, ( semver_b, _, _) | semver_b. cmp ( semver_a) ) ;
284-
285- assert ! (
286- !matches!( & options. page, Page :: Numeric ( _) ) ,
287- "?page= is not supported"
288- ) ;
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 ( ) ) ,
296- )
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 ) ;
305- }
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 < _ > > ( ) ;
312- versions:: table
313- . 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)
318- . 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-
326- future:: ready ( Ok ( ( ) ) )
327- } )
328- . 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- )
339- } else {
340- ( vec ! [ ] , 0 , release_tracks)
341- }
342- } 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)
365- } ;
366-
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) )
371- } ;
372-
373- Ok ( PaginatedVersionsAndPublishers {
374- data,
375- meta : ResponseMeta {
376- total : total as i64 ,
377- next_page,
378- release_tracks,
379- } ,
380- } )
381- }
382-
383253mod seek {
384254 use crate :: controllers:: helpers:: pagination:: seek;
385255 use crate :: models:: { User , Version } ;
@@ -392,6 +262,7 @@ mod seek {
392262 seek ! (
393263 pub enum Seek {
394264 Semver {
265+ num: String ,
395266 id: i32 ,
396267 } ,
397268 Date {
@@ -406,7 +277,10 @@ mod seek {
406277 pub ( crate ) fn to_payload ( & self , record : & ( Version , Option < User > ) ) -> SeekPayload {
407278 let ( Version { id, created_at, .. } , _) = * record;
408279 match * self {
409- Seek :: Semver => SeekPayload :: Semver ( Semver { id } ) ,
280+ Seek :: Semver => SeekPayload :: Semver ( Semver {
281+ num : record. 0 . num . clone ( ) ,
282+ id,
283+ } ) ,
410284 Seek :: Date => SeekPayload :: Date ( Date { created_at, id } ) ,
411285 }
412286 }
0 commit comments