Skip to content

Commit f53113c

Browse files
authored
Merge pull request #1 from FreeMasen/pull_1560
Pull 1560
2 parents c56e87b + 8f9d966 commit f53113c

File tree

3 files changed

+65
-183
lines changed

3 files changed

+65
-183
lines changed

src/controllers/krate/search.rs

+2-181
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
7171
recent_crate_downloads::downloads.nullable(),
7272
));
7373
query = query.order(Crate::with_name(q_string).desc());
74-
74+
7575
if sort == "relevance" {
7676
let rank = ts_rank_cd(crates::textsearchable_index_col, q);
7777
query = query.then_order_by(rank.desc())
@@ -211,183 +211,4 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
211211
crates,
212212
meta: Meta { total },
213213
}))
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-
}
214+
}

src/models/krate.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ type CanonCrateName<T> = self::canon_crate_name::HelperType<T>;
9191
type All = diesel::dsl::Select<crates::table, AllColumns>;
9292
type WithName<'a> = diesel::dsl::Eq<CanonCrateName<crates::name>, CanonCrateName<&'a str>>;
9393
/// The result of a loose search
94-
type LikeName<'a> = diesel::dsl::Like<CanonCrateName<crates::name>, CanonCrateName<&'a str>>;
94+
type LikeName<'a> = diesel::pg::expression::helper_types::ILike<CanonCrateName<crates::name>, CanonCrateName<&'a str>>;
9595
type ByName<'a> = diesel::dsl::Filter<All, WithName<'a>>;
9696
type ByExactName<'a> = diesel::dsl::Filter<All, diesel::dsl::Eq<crates::name, &'a str>>;
9797

@@ -246,7 +246,7 @@ impl Crate {
246246
/// WHERE name like $1
247247
/// ```
248248
pub fn like_name(name: &str) -> LikeName<'_> {
249-
canon_crate_name(crates::name).like(canon_crate_name(name))
249+
canon_crate_name(crates::name).ilike(canon_crate_name(name))
250250
}
251251
/// SQL filter with the = binary operator
252252
/// ```sql

src/tests/krate.rs

+61
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,67 @@ fn exact_match_on_queries_with_sort() {
414414
assert_eq!(json.crates[3].name, "other_sort");
415415
}
416416

417+
#[test]
418+
fn loose_search_order() {
419+
let (app, anon, user) = TestApp::init().with_user();
420+
let user = user.as_model();
421+
422+
let ordered = app.db(|conn| {
423+
// exact match should be first
424+
let one = CrateBuilder::new("temp", user.id)
425+
.readme("readme")
426+
.description("description")
427+
.keyword("kw1")
428+
.expect_build(conn);
429+
// file shouldn't match at all
430+
CrateBuilder::new("file", user.id)
431+
.readme("readme")
432+
.description("description")
433+
.keyword("kw1")
434+
.expect_build(conn);
435+
// temp_udp should match second
436+
let two = CrateBuilder::new("temp_utp", user.id)
437+
.readme("readme")
438+
.description("description")
439+
.keyword("kw1")
440+
.expect_build(conn);
441+
// evalrs should match 3rd because of readme
442+
let three = CrateBuilder::new("evalrs", user.id)
443+
.readme(r#"$ echo 'println!("Hello World!")' | evalrs
444+
Compiling evalrs_temp v0.0.0 (file:///tmp/evalrs_temp.daiPxHtjV2VR)
445+
Finished debug [unoptimized + debuginfo] target(s) in 0.51 secs
446+
Running `target\debug\evalrs_temp.exe`
447+
Hello World!"#)
448+
.description("description")
449+
.keyword("kw1")
450+
.expect_build(conn);
451+
// tempfile should appear 4th
452+
let four = CrateBuilder::new("tempfile", user.id)
453+
.readme("readme")
454+
.description("description")
455+
.keyword("kw1")
456+
.expect_build(conn);
457+
// mkstemp should appear 5th
458+
let five = CrateBuilder::new("mkstemp", user.id)
459+
.readme("readme")
460+
.description("description")
461+
.keyword("kw1")
462+
.expect_build(conn);
463+
vec![one, two, three, four, five]
464+
});
465+
let search_temp = anon.search("q=temp");
466+
assert_eq!(search_temp.meta.total, 5);
467+
assert_eq!(search_temp.crates.len(), 5);
468+
for (lhs, rhs) in search_temp.crates.iter().zip(ordered) {
469+
assert_eq!(lhs.name, rhs.name);
470+
}
471+
let search_file = anon.search("q=file");
472+
assert_eq!(search_file.meta.total, 2);
473+
assert_eq!(search_file.crates.len(), 2);
474+
assert_eq!(&search_file.crates[0].name, "file");
475+
assert_eq!(&search_file.crates[1].name, "tempfile");
476+
}
477+
417478
#[test]
418479
fn show() {
419480
let (app, anon, user) = TestApp::init().with_user();

0 commit comments

Comments
 (0)