Skip to content

Commit 7545f47

Browse files
Merge #903
903: Port `categories::sync` over to use Diesel r=carols10cents I've changed this to do the whole thing in one insert rather than one per category.
2 parents 147f8aa + 7ac79bf commit 7545f47

File tree

4 files changed

+147
-40
lines changed

4 files changed

+147
-40
lines changed

src/bin/server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ fn main() {
144144

145145
// On every server restart, ensure the categories available in the database match
146146
// the information in *src/categories.toml*.
147-
cargo_registry::categories::sync().unwrap();
147+
let categories_toml = include_str!("../categories.toml");
148+
cargo_registry::categories::sync(&categories_toml).unwrap();
148149

149150
let port = if heroku {
150151
8888

src/categories.rs

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Sync available crate categories from `src/categories.toml`.
22
// Runs when the server is started.
33

4+
use diesel;
5+
use diesel::prelude::*;
6+
use diesel::pg::PgConnection;
47
use toml;
58

69
use db;
10+
use schema::categories;
711
use util::errors::{CargoResult, ChainError, internal};
812

913
#[derive(Debug)]
@@ -87,45 +91,56 @@ fn categories_from_toml(
8791
Ok(result)
8892
}
8993

90-
pub fn sync() -> CargoResult<()> {
91-
let conn = db::connect_now_old();
92-
let tx = conn.transaction().unwrap();
94+
#[derive(Insertable, Debug)]
95+
#[table_name = "categories"]
96+
struct NewCategory {
97+
slug: String,
98+
category: String,
99+
description: String,
100+
}
93101

94-
let categories = include_str!("./categories.toml");
95-
let toml: toml::value::Table =
96-
toml::from_str(categories).expect("Could not parse categories.toml");
97-
98-
let categories =
99-
categories_from_toml(&toml, None).expect("Could not convert categories from TOML");
100-
101-
for category in &categories {
102-
tx.execute(
103-
"\
104-
INSERT INTO categories (slug, category, description) \
105-
VALUES (LOWER($1), $2, $3) \
106-
ON CONFLICT (slug) DO UPDATE \
107-
SET category = EXCLUDED.category, \
108-
description = EXCLUDED.description;",
109-
&[&category.slug, &category.name, &category.description],
110-
)?;
111-
}
102+
pub fn sync(toml_str: &str) -> CargoResult<()> {
103+
let conn = db::connect_now().unwrap();
104+
sync_with_connection(toml_str, &conn)
105+
}
106+
107+
pub fn sync_with_connection(toml_str: &str, conn: &PgConnection) -> CargoResult<()> {
108+
use diesel::pg::upsert::*;
109+
use diesel::expression::dsl::all;
112110

113-
let in_clause = categories
114-
.iter()
115-
.map(|category| format!("LOWER('{}')", category.slug))
116-
.collect::<Vec<_>>()
117-
.join(",");
118-
119-
tx.execute(
120-
&format!(
121-
"\
122-
DELETE FROM categories \
123-
WHERE slug NOT IN ({});",
124-
in_clause
125-
),
126-
&[],
127-
)?;
128-
tx.set_commit();
129-
tx.finish().unwrap();
130-
Ok(())
111+
let toml: toml::value::Table =
112+
toml::from_str(toml_str).expect("Could not parse categories toml");
113+
114+
let categories = categories_from_toml(&toml, None)
115+
.expect("Could not convert categories from TOML")
116+
.into_iter()
117+
.map(|c| {
118+
NewCategory {
119+
slug: c.slug.to_lowercase(),
120+
category: c.name,
121+
description: c.description,
122+
}
123+
})
124+
.collect::<Vec<_>>();
125+
126+
let to_insert = categories.on_conflict(
127+
categories::slug,
128+
do_update().set((
129+
categories::category.eq(excluded(categories::category)),
130+
categories::description.eq(
131+
excluded(categories::description),
132+
),
133+
)),
134+
);
135+
136+
conn.transaction(|| {
137+
let slugs = diesel::insert(&to_insert)
138+
.into(categories::table)
139+
.returning(categories::slug)
140+
.get_results::<String>(&*conn)?;
141+
142+
let to_delete = categories::table.filter(categories::slug.ne(all(slugs)));
143+
diesel::delete(to_delete).execute(&*conn)?;
144+
Ok(())
145+
})
131146
}

src/tests/all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ struct Bad {
8787
}
8888

8989
mod badge;
90+
mod categories;
9091
mod category;
9192
mod git;
9293
mod keyword;

src/tests/categories.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use cargo_registry::schema::categories;
2+
use diesel::*;
3+
use diesel::pg::PgConnection;
4+
use dotenv::dotenv;
5+
6+
use std::env;
7+
8+
const ALGORITHMS: &'static str = r#"
9+
[algorithms]
10+
name = "Algorithms"
11+
description = """
12+
Rust implementations of core algorithms such as hashing, sorting, \
13+
searching, and more.\
14+
""""#;
15+
16+
const ALGORITHMS_AND_SUCH: &'static str = r#"
17+
[algorithms]
18+
name = "Algorithms"
19+
description = """
20+
Rust implementations of core algorithms such as hashing, sorting, \
21+
searching, and more.\
22+
"""
23+
24+
[algorithms.categories.such]
25+
name = "Such"
26+
description = """
27+
Other stuff
28+
""""#;
29+
30+
const ALGORITHMS_AND_ANOTHER: &'static str = r#"
31+
[algorithms]
32+
name = "Algorithms"
33+
description = """
34+
Rust implementations of core algorithms such as hashing, sorting, \
35+
searching, and more.\
36+
"""
37+
38+
[another]
39+
name = "Another"
40+
description = "Another category ho hum"
41+
"#;
42+
43+
fn pg_connection() -> PgConnection {
44+
let _ = dotenv();
45+
let database_url =
46+
env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set to run tests");
47+
let conn = PgConnection::establish(&database_url).unwrap();
48+
conn.begin_test_transaction().unwrap();
49+
conn
50+
}
51+
52+
fn select_slugs(conn: &PgConnection) -> Vec<String> {
53+
categories::table
54+
.select(categories::slug)
55+
.order(categories::slug)
56+
.load::<String>(conn)
57+
.unwrap()
58+
}
59+
60+
#[test]
61+
fn sync_adds_new_categories() {
62+
let conn = pg_connection();
63+
64+
::cargo_registry::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &conn).unwrap();
65+
66+
let categories = select_slugs(&conn);
67+
assert_eq!(categories, vec!["algorithms", "algorithms::such"]);
68+
}
69+
70+
#[test]
71+
fn sync_removes_missing_categories() {
72+
let conn = pg_connection();
73+
74+
::cargo_registry::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &conn).unwrap();
75+
::cargo_registry::categories::sync_with_connection(ALGORITHMS, &conn).unwrap();
76+
77+
let categories = select_slugs(&conn);
78+
assert_eq!(categories, vec!["algorithms"]);
79+
}
80+
81+
#[test]
82+
fn sync_adds_and_removes() {
83+
let conn = pg_connection();
84+
85+
::cargo_registry::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &conn).unwrap();
86+
::cargo_registry::categories::sync_with_connection(ALGORITHMS_AND_ANOTHER, &conn).unwrap();
87+
88+
let categories = select_slugs(&conn);
89+
assert_eq!(categories, vec!["algorithms", "another"]);
90+
}

0 commit comments

Comments
 (0)