diff --git a/src/bin/migrate.rs b/src/bin/migrate.rs index 3ac19387806..e1d2789b96b 100644 --- a/src/bin/migrate.rs +++ b/src/bin/migrate.rs @@ -418,6 +418,28 @@ fn migrations() -> Vec { crate_owners_unique_user_per_crate", &[])); Ok(()) }), + Migration::new(20150319224700, |tx| { + try!(tx.execute(" + CREATE FUNCTION canon_crate_name(text) RETURNS text AS $$ + SELECT replace(lower($1), '-', '_') + $$ LANGUAGE SQL + ", &[])); + Ok(()) + }, |tx| { + try!(tx.execute("DROP FUNCTION canon_crate_name(text)", &[])); + Ok(()) + }), + Migration::new(20150319224701, |tx| { + try!(tx.execute("DROP INDEX index_crates_name", &[])); + try!(tx.execute("CREATE UNIQUE INDEX index_crates_name \ + ON crates (canon_crate_name(name))", &[])); + Ok(()) + }, |tx| { + try!(tx.execute("DROP INDEX index_crates_name", &[])); + try!(tx.execute("CREATE UNIQUE INDEX index_crates_name \ + ON crates (lower(name))", &[])); + Ok(()) + }), ]; // NOTE: Generate a new id via `date +"%Y%m%d%H%M%S"` diff --git a/src/krate.rs b/src/krate.rs index 4abcad1b97f..903c2de5053 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -84,7 +84,8 @@ impl Crate { pub fn find_by_name(conn: &Connection, name: &str) -> CargoResult { let stmt = try!(conn.prepare("SELECT * FROM crates \ - WHERE lower(name) = lower($1) LIMIT 1")); + WHERE canon_crate_name(name) = + canon_crate_name($1) LIMIT 1")); let row = try!(stmt.query(&[&name as &ToSql])).into_iter().next(); let row = try!(row.chain_error(|| NotFound)); Ok(Model::from_row(&row)) @@ -136,7 +137,8 @@ impl Crate { keywords = $5, license = $6, repository = $7 - WHERE lower(name) = lower($8) + WHERE canon_crate_name(name) = + canon_crate_name($8) RETURNING *")); let rows = try!(stmt.query(&[&documentation, &homepage, &description, &readme, &keywords, @@ -448,9 +450,10 @@ pub fn index(req: &mut Request) -> CargoResult { pattern = format!("{}%", letter.as_slice().char_at(0) .to_lowercase().collect::()); needs_pattern = true; - (format!("SELECT * FROM crates WHERE lower(name) LIKE $1 {} - LIMIT $2 OFFSET $3", sort_sql), - "SELECT COUNT(*) FROM crates WHERE lower(name) LIKE $1".to_string()) + (format!("SELECT * FROM crates WHERE canon_crate_name(name) \ + LIKE $1 {} LIMIT $2 OFFSET $3", sort_sql), + "SELECT COUNT(*) FROM crates WHERE canon_crate_name(name) \ + LIKE $1".to_string()) }) }).or_else(|| { query.get("keyword").map(|kw| { @@ -789,7 +792,8 @@ pub fn download(req: &mut Request) -> CargoResult { FROM crates INNER JOIN versions ON crates.id = versions.crate_id - WHERE lower(crates.name) = lower($1) + WHERE canon_crate_name(crates.name) = + canon_crate_name($1) AND versions.num = $2 LIMIT 1")); let rows = try!(stmt.query(&[&crate_name as &ToSql, &version as &ToSql])); diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 4b5bb19040d..18867770fd6 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -401,6 +401,28 @@ fn new_crate_similar_name() { "{:?}", json.errors); } +#[test] +fn new_crate_similar_name_hyphen() { + { + let (_b, app, middle) = ::app(); + let mut req = new_req(app, "foo-bar", "1.1.0"); + ::mock_user(&mut req, ::user("foo")); + ::mock_crate(&mut req, ::krate("foo_bar")); + let json = bad_resp!(middle.call(&mut req)); + assert!(json.errors[0].detail.as_slice().contains("previously named"), + "{:?}", json.errors); + } + { + let (_b, app, middle) = ::app(); + let mut req = new_req(app, "foo_bar", "1.1.0"); + ::mock_user(&mut req, ::user("foo")); + ::mock_crate(&mut req, ::krate("foo-bar")); + let json = bad_resp!(middle.call(&mut req)); + assert!(json.errors[0].detail.as_slice().contains("previously named"), + "{:?}", json.errors); + } +} + #[test] fn new_krate_git_upload() { let (_b, app, middle) = ::app();