|
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