Skip to content

Commit 2afe32e

Browse files
jtgeibelcarols10cents
authored andcommitted
Switch several category routes over to diesel
1 parent c05b2ea commit 2afe32e

File tree

4 files changed

+126
-121
lines changed

4 files changed

+126
-121
lines changed

src/category.rs

+49-47
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,18 @@ impl Category {
193193
Ok(invalid_categories)
194194
}
195195

196-
pub fn count_toplevel(conn: &GenericConnection) -> CargoResult<i64> {
197-
let sql = format!(
196+
pub fn count_toplevel(conn: &PgConnection) -> CargoResult<i64> {
197+
use diesel::expression::*;
198+
use diesel::types::*;
199+
200+
let query = sql::<BigInt>(
198201
"\
199-
SELECT COUNT(*) \
200-
FROM {} \
201-
WHERE category NOT LIKE '%::%'",
202-
Model::table_name(None::<Self>)
202+
SELECT COUNT(*) \
203+
FROM categories \
204+
WHERE category NOT LIKE '%::%'"
203205
);
204-
let stmt = conn.prepare(&sql)?;
205-
let rows = stmt.query(&[])?;
206-
Ok(rows.iter().next().unwrap().get("count"))
206+
let count = query.get_result(&*conn)?;
207+
Ok(count)
207208
}
208209

209210
pub fn toplevel(
@@ -269,48 +270,47 @@ impl Category {
269270
Ok(categories)
270271
}
271272

272-
pub fn subcategories(&self, conn: &GenericConnection) -> CargoResult<Vec<Category>> {
273-
let stmt = conn.prepare(
274-
"\
275-
SELECT c.id, c.category, c.slug, c.description, c.created_at, \
276-
COALESCE (( \
277-
SELECT sum(c2.crates_cnt)::int \
278-
FROM categories as c2 \
279-
WHERE c2.slug = c.slug \
280-
OR c2.slug LIKE c.slug || '::%' \
281-
), 0) as crates_cnt \
282-
FROM categories as c \
283-
WHERE c.category ILIKE $1 || '::%' \
284-
AND c.category NOT ILIKE $1 || '::%::%'",
285-
)?;
273+
pub fn subcategories(&self, conn: &PgConnection) -> CargoResult<Vec<Category>> {
274+
use diesel::expression::*;
275+
use diesel::types::*;
286276

287-
let rows = stmt.query(&[&self.category])?;
288-
Ok(rows.iter().map(|r| Model::from_row(&r)).collect())
277+
let query = sql::<(Integer, Text, Text, Text, Integer, Timestamp)>(
278+
"\
279+
SELECT c.id, c.category, c.slug, c.description, \
280+
COALESCE (( \
281+
SELECT sum(c2.crates_cnt)::int \
282+
FROM categories as c2 \
283+
WHERE c2.slug = c.slug \
284+
OR c2.slug LIKE c.slug || '::%' \
285+
), 0) as crates_cnt, \
286+
c.created_at \
287+
FROM categories as c \
288+
WHERE c.category ILIKE $1 || '::%' \
289+
AND c.category NOT ILIKE $1 || '::%::%'",
290+
).bind::<Text, _>(&self.category);
291+
292+
let rows = query.get_results(conn)?;
293+
Ok(rows)
289294
}
290295
}
291296

292-
#[derive(Insertable, Default, Debug)]
297+
#[derive(Insertable, AsChangeset, Default, Debug)]
293298
#[table_name = "categories"]
294299
pub struct NewCategory<'a> {
295300
pub category: &'a str,
296301
pub slug: &'a str,
297302
}
298303

299304
impl<'a> NewCategory<'a> {
300-
pub fn find_or_create(&self, conn: &PgConnection) -> QueryResult<Category> {
301-
use schema::categories::dsl::*;
305+
/// Inserts the category into the database, or updates an existing one.
306+
pub fn create_or_update(&self, conn: &PgConnection) -> CargoResult<Category> {
307+
use diesel::insert;
302308
use diesel::pg::upsert::*;
303309

304-
let maybe_inserted = insert(&self.on_conflict_do_nothing())
305-
.into(categories)
310+
insert(&self.on_conflict(categories::slug, do_update().set(self)))
311+
.into(categories::table)
306312
.get_result(conn)
307-
.optional()?;
308-
309-
if let Some(c) = maybe_inserted {
310-
return Ok(c);
311-
}
312-
313-
categories.filter(slug.eq(self.slug)).first(conn)
313+
.map_err(Into::into)
314314
}
315315
}
316316

@@ -332,16 +332,16 @@ impl Model for Category {
332332

333333
/// Handles the `GET /categories` route.
334334
pub fn index(req: &mut Request) -> CargoResult<Response> {
335-
let conn = req.tx()?;
335+
let conn = req.db_conn()?;
336336
let (offset, limit) = req.pagination(10, 100)?;
337337
let query = req.query();
338338
let sort = query.get("sort").map_or("alpha", String::as_str);
339339

340-
let categories = Category::toplevel_old(conn, sort, limit, offset)?;
340+
let categories = Category::toplevel(&conn, sort, limit, offset)?;
341341
let categories = categories.into_iter().map(Category::encodable).collect();
342342

343343
// Query for the total count of categories
344-
let total = Category::count_toplevel(conn)?;
344+
let total = Category::count_toplevel(&conn)?;
345345

346346
#[derive(RustcEncodable)]
347347
struct R {
@@ -361,13 +361,15 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
361361

362362
/// Handles the `GET /categories/:category_id` route.
363363
pub fn show(req: &mut Request) -> CargoResult<Response> {
364-
let slug = &req.params()["category_id"];
365-
let conn = req.tx()?;
366-
let cat = Category::find_by_slug(conn, slug)?;
367-
let subcats = cat.subcategories(conn)?
368-
.into_iter()
369-
.map(|s| s.encodable())
370-
.collect();
364+
use self::categories::dsl::{categories, slug};
365+
366+
let id = &req.params()["category_id"];
367+
let conn = req.db_conn()?;
368+
let cat = categories.filter(slug.eq(id)).first::<Category>(&*conn)?;
369+
let subcats = cat.subcategories(&*conn)?.into_iter().map(|s| {
370+
s.encodable()
371+
}).collect();
372+
371373
let cat = cat.encodable();
372374
let cat_with_subcats = EncodableCategoryWithSubcategories {
373375
id: cat.id,

src/tests/all.rs

+1-36
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use cargo_registry::user::NewUser;
3535
use cargo_registry::owner::{CrateOwner, NewTeam, Team};
3636
use cargo_registry::version::NewVersion;
3737
use cargo_registry::user::AuthenticationSource;
38-
use cargo_registry::{User, Crate, Version, Dependency, Category, Model, Replica};
38+
use cargo_registry::{User, Crate, Version, Dependency, Replica};
3939
use conduit::{Request, Method};
4040
use conduit_test::MockRequest;
4141
use diesel::pg::PgConnection;
@@ -467,29 +467,6 @@ fn sign_in(req: &mut Request, app: &App) {
467467
sign_in_as(req, &user);
468468
}
469469

470-
fn mock_crate(req: &mut Request, krate: Crate) -> (Crate, Version) {
471-
mock_crate_vers(req, krate, &semver::Version::parse("1.0.0").unwrap())
472-
}
473-
474-
fn mock_crate_vers(req: &mut Request, krate: Crate, v: &semver::Version) -> (Crate, Version) {
475-
let user = req.extensions().find::<User>().unwrap();
476-
let mut krate = Crate::find_or_insert(
477-
req.tx().unwrap(),
478-
&krate.name,
479-
user.id,
480-
&krate.description,
481-
&krate.homepage,
482-
&krate.documentation,
483-
&krate.readme,
484-
&krate.repository,
485-
&krate.license,
486-
&None,
487-
krate.max_upload_size,
488-
).unwrap();
489-
let v = krate.add_version(req.tx().unwrap(), v, &HashMap::new(), &[]);
490-
(krate, v.unwrap())
491-
}
492-
493470
fn new_dependency(conn: &PgConnection, version: &Version, krate: &Crate) -> Dependency {
494471
use diesel::insert;
495472
use cargo_registry::schema::dependencies;
@@ -515,18 +492,6 @@ fn new_category<'a>(category: &'a str, slug: &'a str) -> NewCategory<'a> {
515492
}
516493
}
517494

518-
fn mock_category(req: &mut Request, name: &str, slug: &str) -> Category {
519-
let conn = req.tx().unwrap();
520-
let stmt = conn.prepare(
521-
" \
522-
INSERT INTO categories (category, slug) \
523-
VALUES ($1, $2) \
524-
RETURNING *",
525-
).unwrap();
526-
let rows = stmt.query(&[&name, &slug]).unwrap();
527-
Model::from_row(&rows.iter().next().unwrap())
528-
}
529-
530495
fn logout(req: &mut Request) {
531496
req.mut_extensions().pop::<User>();
532497
}

src/tests/category.rs

+73-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use conduit::{Handler, Method};
22
use conduit_test::MockRequest;
33

4-
use cargo_registry::db::RequestTransaction;
54
use cargo_registry::category::{Category, EncodableCategory, EncodableCategoryWithSubcategories};
65

76
#[derive(RustcDecodable)]
@@ -25,7 +24,7 @@ struct CategoryWithSubcategories {
2524
#[test]
2625
fn index() {
2726
let (_b, app, middle) = ::app();
28-
let mut req = ::req(app, Method::Get, "/api/v1/categories");
27+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories");
2928

3029
// List 0 categories if none exist
3130
let mut response = ok_resp!(middle.call(&mut req));
@@ -34,9 +33,11 @@ fn index() {
3433
assert_eq!(json.meta.total, 0);
3534

3635
// Create a category and a subcategory
37-
::mock_category(&mut req, "foo", "foo");
38-
::mock_category(&mut req, "foo::bar", "foo::bar");
39-
36+
{
37+
let conn = t!(app.diesel_database.get());
38+
::new_category("foo", "foo").create_or_update(&conn).unwrap();
39+
::new_category("foo::bar", "foo::bar").create_or_update(&conn).unwrap();
40+
}
4041
let mut response = ok_resp!(middle.call(&mut req));
4142
let json: CategoryList = ::json(&mut response);
4243

@@ -51,13 +52,18 @@ fn show() {
5152
let (_b, app, middle) = ::app();
5253

5354
// Return not found if a category doesn't exist
54-
let mut req = ::req(app, Method::Get, "/api/v1/categories/foo-bar");
55+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories/foo-bar");
5556
let response = t_resp!(middle.call(&mut req));
5657
assert_eq!(response.status.0, 404);
5758

5859
// Create a category and a subcategory
59-
::mock_category(&mut req, "Foo Bar", "foo-bar");
60-
::mock_category(&mut req, "Foo Bar::Baz", "foo-bar::baz");
60+
{
61+
let conn = t!(app.diesel_database.get());
62+
63+
// Create a category and a subcategory
64+
t!(::new_category("Foo Bar", "foo-bar").create_or_update(&conn));
65+
t!(::new_category("Foo Bar::Baz", "foo-bar::baz").create_or_update(&conn));
66+
}
6167

6268
// The category and its subcategories should be in the json
6369
let mut response = ok_resp!(middle.call(&mut req));
@@ -71,57 +77,83 @@ fn show() {
7177
#[test]
7278
fn update_crate() {
7379
let (_b, app, middle) = ::app();
74-
let mut req = ::req(app, Method::Get, "/api/v1/categories/foo");
80+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories/foo");
7581
let cnt = |req: &mut MockRequest, cat: &str| {
7682
req.with_path(&format!("/api/v1/categories/{}", cat));
7783
let mut response = ok_resp!(middle.call(req));
7884
::json::<GoodCategory>(&mut response).category.crates_cnt as usize
7985
};
80-
::mock_user(&mut req, ::user("foo"));
81-
let (krate, _) = ::mock_crate(&mut req, ::krate("foocat"));
82-
::mock_category(&mut req, "cat1", "cat1");
83-
::mock_category(&mut req, "Category 2", "category-2");
86+
87+
let krate = {
88+
let conn = t!(app.diesel_database.get());
89+
let user = t!(::new_user("foo").create_or_update(&conn));
90+
t!(::new_category("cat1", "cat1").create_or_update(&conn));
91+
t!(::new_category("Category 2", "category-2").create_or_update(&conn));
92+
::CrateBuilder::new("foo_crate", user.id)
93+
.expect_build(&conn)
94+
};
8495

8596
// Updating with no categories has no effect
86-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
97+
{
98+
let conn = t!(app.diesel_database.get());
99+
Category::update_crate(&conn, &krate, &[]).unwrap();
100+
}
87101
assert_eq!(cnt(&mut req, "cat1"), 0);
88102
assert_eq!(cnt(&mut req, "category-2"), 0);
89103

90104
// Happy path adding one category
91-
Category::update_crate_old(req.tx().unwrap(), &krate, &["cat1".to_string()]).unwrap();
105+
{
106+
let conn = t!(app.diesel_database.get());
107+
Category::update_crate(&conn, &krate, &["cat1"]).unwrap();
108+
}
92109
assert_eq!(cnt(&mut req, "cat1"), 1);
93110
assert_eq!(cnt(&mut req, "category-2"), 0);
94111

95112
// Replacing one category with another
96-
Category::update_crate_old(req.tx().unwrap(), &krate, &["category-2".to_string()]).unwrap();
113+
{
114+
let conn = t!(app.diesel_database.get());
115+
Category::update_crate(&conn, &krate, &["category-2"]).unwrap();
116+
}
97117
assert_eq!(cnt(&mut req, "cat1"), 0);
98118
assert_eq!(cnt(&mut req, "category-2"), 1);
99119

100120
// Removing one category
101-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
121+
{
122+
let conn = t!(app.diesel_database.get());
123+
Category::update_crate(&conn, &krate, &[]).unwrap();
124+
}
102125
assert_eq!(cnt(&mut req, "cat1"), 0);
103126
assert_eq!(cnt(&mut req, "category-2"), 0);
104127

105128
// Adding 2 categories
106-
Category::update_crate_old(
107-
req.tx().unwrap(),
108-
&krate,
109-
&["cat1".to_string(), "category-2".to_string()],
110-
).unwrap();
129+
{
130+
let conn = t!(app.diesel_database.get());
131+
Category::update_crate(
132+
&conn,
133+
&krate,
134+
&["cat1", "category-2"],
135+
).unwrap();
136+
}
111137
assert_eq!(cnt(&mut req, "cat1"), 1);
112138
assert_eq!(cnt(&mut req, "category-2"), 1);
113139

114140
// Removing all categories
115-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
141+
{
142+
let conn = t!(app.diesel_database.get());
143+
Category::update_crate(&conn, &krate, &[]).unwrap();
144+
}
116145
assert_eq!(cnt(&mut req, "cat1"), 0);
117146
assert_eq!(cnt(&mut req, "category-2"), 0);
118147

119148
// Attempting to add one valid category and one invalid category
120-
let invalid_categories = Category::update_crate_old(
121-
req.tx().unwrap(),
122-
&krate,
123-
&["cat1".to_string(), "catnope".to_string()],
124-
).unwrap();
149+
let invalid_categories = {
150+
let conn = t!(app.diesel_database.get());
151+
Category::update_crate(
152+
&conn,
153+
&krate,
154+
&["cat1", "catnope"],
155+
).unwrap()
156+
};
125157
assert_eq!(invalid_categories, vec!["catnope".to_string()]);
126158
assert_eq!(cnt(&mut req, "cat1"), 1);
127159
assert_eq!(cnt(&mut req, "category-2"), 0);
@@ -135,17 +167,23 @@ fn update_crate() {
135167
assert_eq!(json.meta.total, 2);
136168

137169
// Attempting to add a category by display text; must use slug
138-
Category::update_crate_old(req.tx().unwrap(), &krate, &["Category 2".to_string()]).unwrap();
170+
{
171+
let conn = t!(app.diesel_database.get());
172+
Category::update_crate(&conn, &krate, &["Category 2"]).unwrap();
173+
}
139174
assert_eq!(cnt(&mut req, "cat1"), 0);
140175
assert_eq!(cnt(&mut req, "category-2"), 0);
141176

142177
// Add a category and its subcategory
143-
::mock_category(&mut req, "cat1::bar", "cat1::bar");
144-
Category::update_crate_old(
145-
req.tx().unwrap(),
146-
&krate,
147-
&["cat1".to_string(), "cat1::bar".to_string()],
148-
).unwrap();
178+
{
179+
let conn = t!(app.diesel_database.get());
180+
t!(::new_category("cat1::bar", "cat1::bar").create_or_update(&conn));
181+
Category::update_crate(
182+
&conn,
183+
&krate,
184+
&["cat1", "cat1::bar"],
185+
).unwrap();
186+
}
149187
assert_eq!(cnt(&mut req, "cat1"), 1);
150188
assert_eq!(cnt(&mut req, "cat1::bar"), 1);
151189
assert_eq!(cnt(&mut req, "category-2"), 0);

0 commit comments

Comments
 (0)