Skip to content

Commit 0b6f028

Browse files
authored
Merge pull request #9736 from Turbo87/delete-crate-info
admin/delete_crate: Improve crate listing
2 parents 765f96e + db4682b commit 0b6f028

File tree

3 files changed

+102
-26
lines changed

3 files changed

+102
-26
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ base64 = "=0.22.1"
4949
bigdecimal = { version = "=0.4.5", features = ["serde"] }
5050
bon = "=2.3.0"
5151
cargo-manifest = "=0.15.2"
52+
colored = "=2.1.0"
5253
crates_io_cdn_logs = { path = "crates/crates_io_cdn_logs" }
5354
crates_io_database = { path = "crates/crates_io_database" }
5455
crates_io_database_dump = { path = "crates/crates_io_database_dump" }

src/admin/delete_crate.rs

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use crate::schema::{crate_owners, teams, users};
1+
use crate::schema::crate_downloads;
22
use crate::worker::jobs;
33
use crate::{admin::dialoguer, db, schema::crates};
44
use anyhow::Context;
5+
use colored::Colorize;
56
use crates_io_worker::BackgroundJob;
67
use diesel::dsl::sql;
7-
use diesel::sql_types::Text;
8-
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl};
8+
use diesel::sql_types::{Array, BigInt, Text};
9+
use diesel::{ExpressionMethods, QueryDsl};
910
use diesel_async::RunQueryDsl;
11+
use futures_util::TryStreamExt;
1012
use std::collections::HashMap;
13+
use std::fmt::Display;
1114

1215
#[derive(clap::Parser, Debug)]
1316
#[command(
@@ -33,39 +36,60 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
3336
let mut crate_names = opts.crate_names;
3437
crate_names.sort();
3538

36-
let query_result = crates::table
39+
let existing_crates = crates::table
40+
.inner_join(crate_downloads::table)
41+
.filter(crates::name.eq_any(&crate_names))
3742
.select((
3843
crates::name,
3944
crates::id,
40-
sql::<Text>(
41-
"CASE WHEN crate_owners.owner_kind = 1 THEN teams.login ELSE users.gh_login END",
45+
crate_downloads::downloads,
46+
sql::<Array<Text>>(
47+
r#"
48+
ARRAY(
49+
SELECT
50+
CASE WHEN crate_owners.owner_kind = 1 THEN
51+
teams.login
52+
ELSE
53+
users.gh_login
54+
END
55+
FROM crate_owners
56+
LEFT JOIN teams ON teams.id = crate_owners.owner_id
57+
LEFT JOIN users ON users.id = crate_owners.owner_id
58+
WHERE crate_owners.crate_id = crates.id
59+
)
60+
"#,
61+
),
62+
sql::<BigInt>(
63+
// This is an incorrect reverse dependencies query, since it
64+
// includes the `dependencies` rows for all versions, not just
65+
// the "default version" per crate. However, it's good enough
66+
// for our purposes here.
67+
r#"
68+
(
69+
SELECT COUNT(*)
70+
FROM dependencies
71+
WHERE dependencies.crate_id = crates.id
72+
)
73+
"#,
4274
),
4375
))
44-
.left_join(crate_owners::table.on(crate_owners::crate_id.eq(crates::id)))
45-
.left_join(teams::table.on(teams::id.eq(crate_owners::owner_id)))
46-
.left_join(users::table.on(users::id.eq(crate_owners::owner_id)))
47-
.filter(crates::name.eq_any(&crate_names))
48-
.load::<(String, i32, String)>(&mut conn)
76+
.load_stream::<(String, i32, i64, Vec<String>, i64)>(&mut conn)
4977
.await
50-
.context("Failed to look up crate name from the database")?;
51-
52-
let mut existing_crates: HashMap<String, (i32, Vec<String>)> = HashMap::new();
53-
for (name, id, login) in query_result {
54-
let entry = existing_crates
55-
.entry(name)
56-
.or_insert_with(|| (id, Vec::new()));
57-
58-
entry.1.push(login);
59-
}
78+
.context("Failed to look up crate name from the database")?
79+
.try_fold(
80+
HashMap::new(),
81+
|mut map, (name, id, downloads, owners, rev_deps)| {
82+
map.insert(name, CrateInfo::new(id, downloads, owners, rev_deps));
83+
futures_util::future::ready(Ok(map))
84+
},
85+
)
86+
.await?;
6087

6188
println!("Deleting the following crates:");
6289
println!();
6390
for name in &crate_names {
6491
match existing_crates.get(name) {
65-
Some((id, owners)) => {
66-
let owners = owners.join(", ");
67-
println!(" - {name} (id={id}, owners={owners})");
68-
}
92+
Some(info) => println!(" - {} ({info})", name.bold()),
6993
None => println!(" - {name} (⚠️ crate not found)"),
7094
}
7195
}
@@ -78,7 +102,9 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
78102
}
79103

80104
for name in &crate_names {
81-
if let Some((id, _)) = existing_crates.get(name) {
105+
if let Some(crate_info) = existing_crates.get(name) {
106+
let id = crate_info.id;
107+
82108
info!("{name}: Deleting crate from the database…");
83109
if let Err(error) = diesel::delete(crates::table.find(id))
84110
.execute(&mut conn)
@@ -110,3 +136,41 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
110136

111137
Ok(())
112138
}
139+
140+
#[derive(Debug, Clone)]
141+
struct CrateInfo {
142+
id: i32,
143+
downloads: i64,
144+
owners: Vec<String>,
145+
rev_deps: i64,
146+
}
147+
148+
impl CrateInfo {
149+
pub fn new(id: i32, downloads: i64, owners: Vec<String>, rev_deps: i64) -> Self {
150+
Self {
151+
id,
152+
downloads,
153+
owners,
154+
rev_deps,
155+
}
156+
}
157+
}
158+
159+
impl Display for CrateInfo {
160+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161+
let id = self.id;
162+
let owners = self.owners.join(", ");
163+
164+
write!(f, "id={id}, owners={owners}")?;
165+
if self.downloads > 5000 {
166+
let downloads = format!("downloads={}", self.downloads).bright_red().bold();
167+
write!(f, ", {downloads}")?;
168+
}
169+
if self.rev_deps > 0 {
170+
let rev_deps = format!("rev_deps={}", self.rev_deps).bright_red().bold();
171+
write!(f, ", {rev_deps}")?;
172+
}
173+
174+
Ok(())
175+
}
176+
}

0 commit comments

Comments
 (0)