|
1 |
| -use crate::controllers::helpers::pagination::decode_seek; |
2 | 1 | use crate::models::Category;
|
3 | 2 | use crate::schema::{crates, users};
|
4 | 3 | use crate::tests::builders::{CrateBuilder, VersionBuilder};
|
@@ -308,7 +307,7 @@ async fn index_sorting() -> anyhow::Result<()> {
|
308 | 307 | .await;
|
309 | 308 |
|
310 | 309 | let krate3 = CrateBuilder::new("baz_sort", user.id)
|
311 |
| - .description("foo_sort bar_sort foo_sort bar_sort foo_sort bar_sort const") |
| 310 | + .description("foo_sort bar_sort foo_sort bar_sort bar_sort const") |
312 | 311 | .downloads(100_000)
|
313 | 312 | .recent_downloads(50)
|
314 | 313 | .expect_build(&mut conn)
|
@@ -412,76 +411,75 @@ async fn index_sorting() -> anyhow::Result<()> {
|
412 | 411 | assert_eq!(resp[3].meta.total, 4);
|
413 | 412 | assert_eq!(calls, 5);
|
414 | 413 |
|
415 |
| - use std::cmp::Reverse; |
416 |
| - |
417 | 414 | // Sort by alpha with query
|
418 |
| - for query in ["sort=alpha&q=bar_sort", "sort=alpha&q=sort"] { |
419 |
| - let (resp, calls) = page_with_seek(&anon, query).await; |
420 |
| - assert_eq!(calls, resp[0].meta.total + 1); |
421 |
| - let decoded_seeks = resp |
422 |
| - .iter() |
423 |
| - .filter_map(|cl| { |
424 |
| - cl.meta |
425 |
| - .next_page |
426 |
| - .as_ref() |
427 |
| - .map(|next_page| (next_page, cl.crates[0].name.to_owned())) |
428 |
| - }) |
429 |
| - .filter_map(|(q, name)| { |
430 |
| - let query = url::form_urlencoded::parse(q.trim_start_matches('?').as_bytes()) |
431 |
| - .into_owned() |
432 |
| - .collect::<indexmap::IndexMap<String, String>>(); |
433 |
| - query.get("seek").map(|s| { |
434 |
| - let d = decode_seek::<(bool, i32)>(s).unwrap(); |
435 |
| - (d.0, name) |
436 |
| - }) |
437 |
| - }) |
438 |
| - .collect::<Vec<_>>(); |
439 |
| - // ordering (exact match desc, name asc) |
440 |
| - let mut sorted = decoded_seeks.to_vec(); |
441 |
| - sorted.sort_by_key(|k| (Reverse(k.0), k.1.to_owned())); |
442 |
| - assert_eq!(sorted, decoded_seeks); |
443 |
| - for json in search_both(&anon, query).await { |
444 |
| - assert_eq!(json.meta.total, resp[0].meta.total); |
445 |
| - for (c, r) in json.crates.iter().zip(&resp) { |
446 |
| - assert_eq!(c.name, r.crates[0].name); |
447 |
| - } |
448 |
| - } |
| 415 | + // ordering (exact match desc, name asc) |
| 416 | + let query = "sort=alpha&q=bar_sort"; |
| 417 | + let (resp, calls) = page_with_seek(&anon, query).await; |
| 418 | + for json in search_both(&anon, query).await { |
| 419 | + assert_eq!(json.meta.total, 3); |
| 420 | + assert_eq!(resp[0].crates[0].name, "bar_sort"); |
| 421 | + assert_eq!(resp[1].crates[0].name, "baz_sort"); |
| 422 | + assert_eq!(resp[2].crates[0].name, "foo_sort"); |
449 | 423 | }
|
| 424 | + assert_eq!(calls, 4); |
| 425 | + |
| 426 | + let query = "sort=alpha&q=sort"; |
| 427 | + let (resp, calls) = page_with_seek(&anon, query).await; |
| 428 | + for json in search_both(&anon, query).await { |
| 429 | + assert_eq!(json.meta.total, 4); |
| 430 | + assert_eq!(resp[0].crates[0].name, "bar_sort"); |
| 431 | + assert_eq!(resp[1].crates[0].name, "baz_sort"); |
| 432 | + assert_eq!(resp[2].crates[0].name, "foo_sort"); |
| 433 | + assert_eq!(resp[3].crates[0].name, "other_sort"); |
| 434 | + } |
| 435 | + assert_eq!(calls, 5); |
450 | 436 |
|
451 | 437 | // Sort by relevance
|
| 438 | + // ordering (exact match desc, rank desc, name asc) |
| 439 | + let query = "q=foo_sort"; |
| 440 | + let (resp, calls) = page_with_seek(&anon, query).await; |
| 441 | + for json in search_both(&anon, query).await { |
| 442 | + assert_eq!(json.meta.total, 3); |
| 443 | + assert_eq!(resp[0].crates[0].name, "foo_sort"); |
| 444 | + // same rank, by name asc |
| 445 | + assert_eq!(resp[1].crates[0].name, "bar_sort"); |
| 446 | + assert_eq!(resp[2].crates[0].name, "baz_sort"); |
| 447 | + } |
| 448 | + assert_eq!(calls, 4); |
| 449 | + let ranks = querystring_rank(&mut conn, "foo_sort").await; |
| 450 | + assert_eq!(ranks.get("bar_sort"), ranks.get("baz_sort")); |
| 451 | + |
452 | 452 | // Add query containing a space to ensure tsquery works
|
453 |
| - for query in ["q=foo_sort", "q=sort", "q=foo%20sort"] { |
454 |
| - let (resp, calls) = page_with_seek(&anon, query).await; |
455 |
| - assert_eq!(calls, resp[0].meta.total + 1); |
456 |
| - let decoded_seeks = resp |
457 |
| - .iter() |
458 |
| - .filter_map(|cl| { |
459 |
| - cl.meta |
460 |
| - .next_page |
461 |
| - .as_ref() |
462 |
| - .map(|next_page| (next_page, cl.crates[0].name.to_owned())) |
463 |
| - }) |
464 |
| - .filter_map(|(q, name)| { |
465 |
| - let query = url::form_urlencoded::parse(q.trim_start_matches('?').as_bytes()) |
466 |
| - .into_owned() |
467 |
| - .collect::<indexmap::IndexMap<String, String>>(); |
468 |
| - query.get("seek").map(|s| { |
469 |
| - let d = decode_seek::<(bool, f32, i32)>(s).unwrap(); |
470 |
| - (d.0, (d.1 * 1e12) as i64, name) |
471 |
| - }) |
472 |
| - }) |
473 |
| - .collect::<Vec<_>>(); |
474 |
| - // ordering (exact match desc, rank desc, name asc) |
475 |
| - let mut sorted = decoded_seeks.clone(); |
476 |
| - sorted.sort_by_key(|k| (Reverse(k.0), Reverse(k.1), k.2.to_owned())); |
477 |
| - assert_eq!(sorted, decoded_seeks); |
478 |
| - for json in search_both(&anon, query).await { |
479 |
| - assert_eq!(json.meta.total, resp[0].meta.total); |
480 |
| - for (c, r) in json.crates.iter().zip(&resp) { |
481 |
| - assert_eq!(c.name, r.crates[0].name); |
482 |
| - } |
483 |
| - } |
| 453 | + // "foo_sort" and "foo sort" would generate same tsquery |
| 454 | + let query = "q=foo%20sort"; |
| 455 | + let (resp, calls) = page_with_seek(&anon, query).await; |
| 456 | + for json in search_both(&anon, query).await { |
| 457 | + assert_eq!(json.meta.total, 3); |
| 458 | + assert_eq!(resp[0].crates[0].name, "foo_sort"); |
| 459 | + // same rank, by name asc |
| 460 | + assert_eq!(resp[1].crates[0].name, "bar_sort"); |
| 461 | + assert_eq!(resp[2].crates[0].name, "baz_sort"); |
| 462 | + } |
| 463 | + assert_eq!(calls, 4); |
| 464 | + let ranks = querystring_rank(&mut conn, "foo%20sort").await; |
| 465 | + assert_eq!(ranks.get("bar_sort"), ranks.get("baz_sort")); |
| 466 | + |
| 467 | + let query = "q=sort"; |
| 468 | + let (resp, calls) = page_with_seek(&anon, query).await; |
| 469 | + for json in search_both(&anon, query).await { |
| 470 | + assert_eq!(json.meta.total, 4); |
| 471 | + // by rank desc (items with more "sort" should have a hider rank value) |
| 472 | + assert_eq!(resp[0].crates[0].name, "baz_sort"); |
| 473 | + assert_eq!(resp[1].crates[0].name, "bar_sort"); |
| 474 | + assert_eq!(resp[2].crates[0].name, "foo_sort"); |
| 475 | + assert_eq!(resp[3].crates[0].name, "other_sort"); |
484 | 476 | }
|
| 477 | + assert_eq!(calls, 5); |
| 478 | + let ranks = querystring_rank(&mut conn, "sort").await; |
| 479 | + assert_eq!( |
| 480 | + ranks.keys().collect::<Vec<_>>(), |
| 481 | + ["baz_sort", "bar_sort", "foo_sort", "other_sort"] |
| 482 | + ); |
485 | 483 |
|
486 | 484 | // Test for bug with showing null results first when sorting
|
487 | 485 | // by descending downloads
|
@@ -1286,3 +1284,28 @@ fn default_versions_iter(
|
1286 | 1284 | fn yanked_iter(crates: &[crate::tests::EncodableCrate]) -> impl Iterator<Item = &bool> {
|
1287 | 1285 | crates.iter().map(|c| &c.yanked)
|
1288 | 1286 | }
|
| 1287 | + |
| 1288 | +async fn querystring_rank( |
| 1289 | + conn: &mut diesel_async::AsyncPgConnection, |
| 1290 | + q: &str, |
| 1291 | +) -> indexmap::IndexMap<String, f32> { |
| 1292 | + use diesel_full_text_search::configuration::TsConfigurationByName; |
| 1293 | + use diesel_full_text_search::{plainto_tsquery_with_search_config, ts_rank_cd}; |
| 1294 | + use futures_util::future::ready; |
| 1295 | + use futures_util::TryStreamExt; |
| 1296 | + |
| 1297 | + let tsquery = plainto_tsquery_with_search_config(TsConfigurationByName("english"), q); |
| 1298 | + let rank = ts_rank_cd(crates::textsearchable_index_col, tsquery); |
| 1299 | + crates::table |
| 1300 | + .select((crates::name, rank)) |
| 1301 | + .order_by(rank.desc()) |
| 1302 | + .load_stream::<(String, f32)>(conn) |
| 1303 | + .await |
| 1304 | + .unwrap() |
| 1305 | + .try_fold(indexmap::IndexMap::new(), |mut map, (name, id)| { |
| 1306 | + map.insert(name, id); |
| 1307 | + ready(Ok(map)) |
| 1308 | + }) |
| 1309 | + .await |
| 1310 | + .unwrap() |
| 1311 | +} |
0 commit comments