Skip to content

Commit 3c14e8a

Browse files
sgrifcarols10cents
authored andcommitted
Port crates#summary over to use Diesel
This endpoint was really contained, so very little supporting code had to change. This endpoint is extremely light on test coverage, so it should be verified on staging. As a bonus, it no longer has an N+1 queries bug, and will execute 8 queries instead of 35 (we can get that down to 6 technically but I don't think it's worth it)
1 parent 4bc09ee commit 3c14e8a

File tree

3 files changed

+87
-52
lines changed

3 files changed

+87
-52
lines changed

src/category.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use diesel::pg::PgConnection;
88
use pg::GenericConnection;
99
use pg::rows::Row;
1010

11-
use {Model, Crate};
1211
use db::RequestTransaction;
1312
use schema::*;
14-
use util::{RequestUtils, CargoResult, ChainError};
1513
use util::errors::NotFound;
14+
use util::{RequestUtils, CargoResult, ChainError};
15+
use {Model, Crate};
1616

1717
#[derive(Clone, Identifiable, Associations, Queryable)]
1818
#[has_many(crates_categories)]
@@ -181,7 +181,33 @@ impl Category {
181181
Ok(rows.iter().next().unwrap().get("count"))
182182
}
183183

184-
pub fn toplevel(conn: &GenericConnection,
184+
pub fn toplevel(conn: &PgConnection,
185+
sort: &str,
186+
limit: i64,
187+
offset: i64) -> QueryResult<Vec<Category>> {
188+
use diesel::select;
189+
use diesel::expression::dsl::*;
190+
191+
let sort_sql = match sort {
192+
"crates" => "ORDER BY crates_cnt DESC",
193+
_ => "ORDER BY category ASC",
194+
};
195+
196+
// Collect all the top-level categories and sum up the crates_cnt of
197+
// the crates in all subcategories
198+
select(sql::<categories::SqlType>(&format!(
199+
"c.id, c.category, c.slug, c.description,
200+
sum(c2.crates_cnt)::int as crates_cnt, c.created_at
201+
FROM categories as c
202+
INNER JOIN categories c2 ON split_part(c2.slug, '::', 1) = c.slug
203+
WHERE split_part(c.slug, '::', 1) = c.slug
204+
GROUP BY c.id
205+
{} LIMIT {} OFFSET {}",
206+
sort_sql, limit, offset
207+
))).load(conn)
208+
}
209+
210+
pub fn toplevel_old(conn: &GenericConnection,
185211
sort: &str,
186212
limit: i64,
187213
offset: i64) -> CargoResult<Vec<Category>> {
@@ -278,7 +304,7 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
278304
let query = req.query();
279305
let sort = query.get("sort").map_or("alpha", String::as_str);
280306

281-
let categories = Category::toplevel(conn, sort, limit, offset)?;
307+
let categories = Category::toplevel_old(conn, sort, limit, offset)?;
282308
let categories = categories.into_iter().map(Category::encodable).collect();
283309

284310
// Query for the total count of categories
@@ -342,15 +368,15 @@ pub fn slugs(req: &mut Request) -> CargoResult<Response> {
342368
#[cfg(test)]
343369
mod tests {
344370
use super::*;
345-
use pg::{Connection, TlsMode};
371+
use diesel::connection::SimpleConnection;
346372
use dotenv::dotenv;
347373
use std::env;
348374

349-
fn pg_connection() -> Connection {
375+
fn pg_connection() -> PgConnection {
350376
let _ = dotenv();
351377
let database_url = env::var("TEST_DATABASE_URL")
352378
.expect("TEST_DATABASE_URL must be set to run tests");
353-
let conn = Connection::connect(database_url, TlsMode::None).unwrap();
379+
let conn = PgConnection::establish(&database_url).unwrap();
354380
// These tests deadlock if run concurrently
355381
conn.batch_execute("BEGIN; LOCK categories IN ACCESS EXCLUSIVE MODE").unwrap();
356382
conn

src/krate.rs

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,36 @@ use std::collections::HashMap;
44

55
use conduit::{Request, Response};
66
use conduit_router::RequestParams;
7-
use diesel::prelude::*;
87
use diesel::pg::PgConnection;
98
use diesel::pg::upsert::*;
9+
use diesel::prelude::*;
1010
use diesel_full_text_search::*;
1111
use license_exprs;
1212
use pg::GenericConnection;
1313
use pg::rows::Row;
14-
use pg;
1514
use rustc_serialize::hex::ToHex;
1615
use rustc_serialize::json;
1716
use semver;
1817
use time::{Timespec, Duration};
1918
use url::Url;
2019

21-
use {Model, User, Keyword, Version, Category, Badge, Replica};
2220
use app::{App, RequestApp};
21+
use badge::EncodableBadge;
22+
use category::EncodableCategory;
2323
use db::RequestTransaction;
2424
use dependency::{Dependency, EncodableDependency};
2525
use download::{VersionDownload, EncodableVersionDownload};
2626
use git;
2727
use keyword::EncodableKeyword;
28-
use category::EncodableCategory;
29-
use badge::EncodableBadge;
28+
use owner::{EncodableOwner, Owner, Rights, OwnerKind, Team, rights, CrateOwner};
29+
use schema::*;
3030
use upload;
3131
use user::RequestUser;
32-
use owner::{EncodableOwner, Owner, Rights, OwnerKind, Team, rights, CrateOwner};
3332
use util::errors::NotFound;
3433
use util::{read_le_u32, read_fill};
3534
use util::{RequestUtils, CargoResult, internal, ChainError, human};
3635
use version::EncodableVersion;
37-
use schema::*;
36+
use {Model, User, Keyword, Version, Category, Badge, Replica};
3837

3938
#[derive(Clone, Queryable, Identifiable, AsChangeset)]
4039
pub struct Crate {
@@ -719,40 +718,50 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
719718

720719
/// Handles the `GET /summary` route.
721720
pub fn summary(req: &mut Request) -> CargoResult<Response> {
722-
let tx = req.tx()?;
723-
let num_crates = Crate::count(tx)?;
724-
let num_downloads = {
725-
let stmt = tx.prepare("SELECT total_downloads FROM metadata")?;
726-
let rows = stmt.query(&[])?;
727-
rows.iter().next().unwrap().get("total_downloads")
728-
};
721+
use schema::crates::dsl::*;
729722

730-
let to_crates = |stmt: pg::stmt::Statement| -> CargoResult<Vec<_>> {
731-
let rows = stmt.query(&[])?;
732-
rows.iter().map(|r| {
733-
let krate: Crate = Model::from_row(&r);
734-
let max_version = krate.max_version(tx)?;
735-
Ok(krate.minimal_encodable(max_version, None))
736-
}).collect()
723+
let conn = req.db_conn()?;
724+
let num_crates = crates.count().get_result(conn)?;
725+
let num_downloads = metadata::table.select(metadata::total_downloads)
726+
.get_result(conn)?;
727+
728+
let encode_crates = |krates: Vec<Crate>| -> CargoResult<Vec<_>> {
729+
Version::belonging_to(&krates)
730+
.load::<Version>(conn)?
731+
.grouped_by(&krates)
732+
.into_iter()
733+
.map(|versions| Version::max(versions.into_iter().map(|v| v.num)))
734+
.zip(krates)
735+
.map(|(max_version, krate)| {
736+
Ok(krate.minimal_encodable(max_version, None))
737+
}).collect()
737738
};
738-
let new_crates = tx.prepare("SELECT * FROM crates \
739-
ORDER BY created_at DESC LIMIT 10")?;
740-
let just_updated = tx.prepare("SELECT * FROM crates \
741-
WHERE updated_at::timestamp(0) !=
742-
created_at::timestamp(0)
743-
ORDER BY updated_at DESC LIMIT 10")?;
744-
let most_downloaded = tx.prepare("SELECT * FROM crates \
745-
ORDER BY downloads DESC LIMIT 10")?;
746-
747-
let popular_keywords = Keyword::all(tx, "crates", 10, 0)?;
748-
let popular_keywords = popular_keywords.into_iter()
749-
.map(Keyword::encodable)
750-
.collect();
751-
752-
let popular_categories = Category::toplevel(tx, "crates", 10, 0)?;
753-
let popular_categories = popular_categories.into_iter()
754-
.map(Category::encodable)
755-
.collect();
739+
740+
let new_crates = crates.order(created_at.desc())
741+
.select(ALL_COLUMNS)
742+
.limit(10)
743+
.load(conn)?;
744+
let just_updated = crates.filter(updated_at.ne(created_at))
745+
.order(updated_at.desc())
746+
.select(ALL_COLUMNS)
747+
.limit(10)
748+
.load(conn)?;
749+
let most_downloaded = crates.order(downloads.desc())
750+
.select(ALL_COLUMNS)
751+
.limit(10)
752+
.load(conn)?;
753+
754+
let popular_keywords = keywords::table.order(keywords::crates_cnt.desc())
755+
.limit(10)
756+
.load(conn)?
757+
.into_iter()
758+
.map(Keyword::encodable)
759+
.collect();
760+
761+
let popular_categories = Category::toplevel(conn, "crates", 10, 0)?
762+
.into_iter()
763+
.map(Category::encodable)
764+
.collect();
756765

757766
#[derive(RustcEncodable)]
758767
struct R {
@@ -767,9 +776,9 @@ pub fn summary(req: &mut Request) -> CargoResult<Response> {
767776
Ok(req.json(&R {
768777
num_downloads: num_downloads,
769778
num_crates: num_crates,
770-
new_crates: to_crates(new_crates)?,
771-
most_downloaded: to_crates(most_downloaded)?,
772-
just_updated: to_crates(just_updated)?,
779+
new_crates: encode_crates(new_crates)?,
780+
most_downloaded: encode_crates(most_downloaded)?,
781+
just_updated: encode_crates(just_updated)?,
773782
popular_keywords: popular_keywords,
774783
popular_categories: popular_categories,
775784
}))

src/version.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use url;
1111

1212
use app::RequestApp;
1313
use db::RequestTransaction;
14-
use diesel::prelude::*;
15-
use diesel::pg::Pg;
1614
use dependency::{Dependency, EncodableDependency, Kind};
15+
use diesel::pg::Pg;
16+
use diesel::prelude::*;
1717
use download::{VersionDownload, EncodableVersionDownload};
1818
use git;
1919
use owner::{rights, Rights};
20-
use schema::versions;
20+
use schema::*;
2121
use upload;
2222
use user::RequestUser;
2323
use util::{RequestUtils, CargoResult, ChainError, internal, human};

0 commit comments

Comments
 (0)