@@ -37,6 +37,12 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
37
37
let conn = req. db_conn ( ) ?;
38
38
let ( offset, limit) = req. pagination ( 10 , 100 ) ?;
39
39
let params = req. query ( ) ;
40
+ //extract the search param for loose searching
41
+ let search_q = if let Some ( q) = params. get ( "q" ) {
42
+ format ! ( "%{}%" , q)
43
+ } else {
44
+ String :: new ( )
45
+ } ;
40
46
let sort = params
41
47
. get ( "sort" )
42
48
. map ( |s| & * * s)
@@ -56,7 +62,7 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
56
62
let q = plainto_tsquery ( q_string) ;
57
63
query = query. filter (
58
64
q. matches ( crates:: textsearchable_index_col)
59
- . or ( Crate :: with_name ( q_string ) ) ,
65
+ . or ( Crate :: like_name ( & search_q ) ) ,
60
66
) ;
61
67
62
68
query = query. select ( (
@@ -206,3 +212,182 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
206
212
meta : Meta { total } ,
207
213
} ) )
208
214
}
215
+ pub fn loose_search ( req : & mut dyn Request ) -> CargoResult < Response > {
216
+ use diesel:: sql_types:: Bool ;
217
+
218
+ let conn = req. db_conn ( ) ?;
219
+ let ( offset, limit) = req. pagination ( 10 , 100 ) ?;
220
+ let params = req. query ( ) ;
221
+ let sort = params
222
+ . get ( "sort" )
223
+ . map ( |s| & * * s)
224
+ . unwrap_or ( "recent-downloads" ) ;
225
+
226
+ let loose_q = if let Some ( q_string) = params. get ( "q" ) {
227
+ format ! ( "%{}%" , q_string)
228
+ } else {
229
+ String :: new ( )
230
+ } ;
231
+ let mut query = crates:: table
232
+ . left_join ( recent_crate_downloads:: table)
233
+ . select ( (
234
+ ALL_COLUMNS ,
235
+ false . into_sql :: < Bool > ( ) ,
236
+ recent_crate_downloads:: downloads. nullable ( ) ,
237
+ ) ) . into_boxed ( ) ;
238
+ if let Some ( q_string) = params. get ( "q" ) {
239
+ if !q_string. is_empty ( ) {
240
+ let sort = params. get ( "sort" ) . map ( |s| & * * s) . unwrap_or ( "relevance" ) ;
241
+ let q = plainto_tsquery ( q_string) ;
242
+ query = query. filter (
243
+ q. matches ( crates:: textsearchable_index_col)
244
+ . or ( Crate :: like_name ( & loose_q) ) ,
245
+ ) ;
246
+
247
+ query = query. select ( (
248
+ ALL_COLUMNS ,
249
+ Crate :: with_name ( q_string) ,
250
+ recent_crate_downloads:: downloads. nullable ( ) ,
251
+ ) ) ;
252
+ query = query. order ( Crate :: with_name ( q_string) . desc ( ) ) ;
253
+
254
+ if sort == "relevance" {
255
+ let rank = ts_rank_cd ( crates:: textsearchable_index_col, q) ;
256
+ query = query. then_order_by ( rank. desc ( ) )
257
+ }
258
+ }
259
+ }
260
+
261
+ if let Some ( cat) = params. get ( "category" ) {
262
+ query = query. filter (
263
+ crates:: id. eq_any (
264
+ crates_categories:: table
265
+ . select ( crates_categories:: crate_id)
266
+ . inner_join ( categories:: table)
267
+ . filter (
268
+ categories:: slug
269
+ . eq ( cat)
270
+ . or ( categories:: slug. like ( format ! ( "{}::%" , cat) ) ) ,
271
+ ) ,
272
+ ) ,
273
+ ) ;
274
+ }
275
+
276
+ if let Some ( kw) = params. get ( "keyword" ) {
277
+ query = query. filter (
278
+ crates:: id. eq_any (
279
+ crates_keywords:: table
280
+ . select ( crates_keywords:: crate_id)
281
+ . inner_join ( keywords:: table)
282
+ . filter ( :: lower ( keywords:: keyword) . eq ( :: lower ( kw) ) ) ,
283
+ ) ,
284
+ ) ;
285
+ } else if let Some ( letter) = params. get ( "letter" ) {
286
+ let pattern = format ! (
287
+ "{}%" ,
288
+ letter
289
+ . chars( )
290
+ . next( )
291
+ . unwrap( )
292
+ . to_lowercase( )
293
+ . collect:: <String >( )
294
+ ) ;
295
+ query = query. filter ( canon_crate_name ( crates:: name) . like ( pattern) ) ;
296
+ } else if let Some ( user_id) = params. get ( "user_id" ) . and_then ( |s| s. parse :: < i32 > ( ) . ok ( ) ) {
297
+ query = query. filter (
298
+ crates:: id. eq_any (
299
+ crate_owners:: table
300
+ . select ( crate_owners:: crate_id)
301
+ . filter ( crate_owners:: owner_id. eq ( user_id) )
302
+ . filter ( crate_owners:: deleted. eq ( false ) )
303
+ . filter ( crate_owners:: owner_kind. eq ( OwnerKind :: User as i32 ) ) ,
304
+ ) ,
305
+ ) ;
306
+ } else if let Some ( team_id) = params. get ( "team_id" ) . and_then ( |s| s. parse :: < i32 > ( ) . ok ( ) ) {
307
+ query = query. filter (
308
+ crates:: id. eq_any (
309
+ crate_owners:: table
310
+ . select ( crate_owners:: crate_id)
311
+ . filter ( crate_owners:: owner_id. eq ( team_id) )
312
+ . filter ( crate_owners:: deleted. eq ( false ) )
313
+ . filter ( crate_owners:: owner_kind. eq ( OwnerKind :: Team as i32 ) ) ,
314
+ ) ,
315
+ ) ;
316
+ } else if params. get ( "following" ) . is_some ( ) {
317
+ query = query. filter (
318
+ crates:: id. eq_any (
319
+ follows:: table
320
+ . select ( follows:: crate_id)
321
+ . filter ( follows:: user_id. eq ( req. user ( ) ?. id ) ) ,
322
+ ) ,
323
+ ) ;
324
+ }
325
+
326
+ if sort == "downloads" {
327
+ query = query. then_order_by ( crates:: downloads. desc ( ) )
328
+ } else if sort == "recent-downloads" {
329
+ query = query. then_order_by ( recent_crate_downloads:: downloads. desc ( ) . nulls_last ( ) )
330
+ } else if sort == "recent-updates" {
331
+ query = query. order ( crates:: updated_at. desc ( ) ) ;
332
+ } else {
333
+ query = query. then_order_by ( crates:: name. asc ( ) )
334
+ }
335
+ println ! ( "{:?}" , diesel:: debug_query( & query) ) ;
336
+ // The database query returns a tuple within a tuple, with the root
337
+ // tuple containing 3 items.
338
+ let data = query
339
+ . paginate ( limit, offset)
340
+ . load :: < ( ( Crate , bool , Option < i64 > ) , i64 ) > ( & * conn) ?;
341
+ let total = data. first ( ) . map ( |& ( _, t) | t) . unwrap_or ( 0 ) ;
342
+ let perfect_matches = data. iter ( ) . map ( |& ( ( _, b, _) , _) | b) . collect :: < Vec < _ > > ( ) ;
343
+ let recent_downloads = data
344
+ . iter ( )
345
+ . map ( |& ( ( _, _, s) , _) | s. unwrap_or ( 0 ) )
346
+ . collect :: < Vec < _ > > ( ) ;
347
+ let crates = data. into_iter ( ) . map ( |( ( c, _, _) , _) | c) . collect :: < Vec < _ > > ( ) ;
348
+
349
+ let versions = crates
350
+ . versions ( )
351
+ . load :: < Version > ( & * conn) ?
352
+ . grouped_by ( & crates)
353
+ . into_iter ( )
354
+ . map ( |versions| Version :: max ( versions. into_iter ( ) . map ( |v| v. num ) ) ) ;
355
+
356
+ let badges = CrateBadge :: belonging_to ( & crates)
357
+ . select ( ( badges:: crate_id, badges:: all_columns) )
358
+ . load :: < CrateBadge > ( & conn) ?
359
+ . grouped_by ( & crates)
360
+ . into_iter ( )
361
+ . map ( |badges| badges. into_iter ( ) . map ( |cb| cb. badge ) . collect ( ) ) ;
362
+
363
+ let crates = versions
364
+ . zip ( crates)
365
+ . zip ( perfect_matches)
366
+ . zip ( recent_downloads)
367
+ . zip ( badges)
368
+ . map (
369
+ |( ( ( ( max_version, krate) , perfect_match) , recent_downloads) , badges) | {
370
+ krate. minimal_encodable (
371
+ & max_version,
372
+ Some ( badges) ,
373
+ perfect_match,
374
+ Some ( recent_downloads) ,
375
+ )
376
+ } ,
377
+ ) . collect ( ) ;
378
+
379
+ #[ derive( Serialize ) ]
380
+ struct R {
381
+ crates : Vec < EncodableCrate > ,
382
+ meta : Meta ,
383
+ }
384
+ #[ derive( Serialize ) ]
385
+ struct Meta {
386
+ total : i64 ,
387
+ }
388
+
389
+ Ok ( req. json ( & R {
390
+ crates,
391
+ meta : Meta { total } ,
392
+ } ) )
393
+ }
0 commit comments