Skip to content

Commit f1a66c1

Browse files
committed
Remove the max_version column from Crates
Updating columns like this tends to be pretty brittle unless it's done via a trigger. We can't update this one from a trigger, since PG doesn't know about semver's comparison rules. Either way, we don't really need to cache this unless it turns out to be a performance bottleneck. We can instead calculate the value on the fly. This does introduce some N+1 queries, and those may end up being a hotspot. We can definitely get rid of them, but it's a pain in the ass to do manually and Diesel has support for it built in, so I'd rather just fix them as we move those endpoints over.
1 parent 3932e8d commit f1a66c1

File tree

4 files changed

+41
-29
lines changed

4 files changed

+41
-29
lines changed

src/bin/migrate.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,12 @@ fn migrations() -> Vec<Migration> {
868868
tx.execute("DROP TABLE reserved_crate_names", &[])?;
869869
Ok(())
870870
}),
871+
Migration::new(20170305123234, |tx| {
872+
tx.execute("ALTER TABLE crates DROP COLUMN max_version", &[])?;
873+
Ok(())
874+
}, |_| {
875+
panic!("Unreversible migration")
876+
}),
871877
];
872878
// NOTE: Generate a new id via `date +"%Y%m%d%H%M%S"`
873879

src/krate.rs

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ pub struct Crate {
4444
pub updated_at: Timespec,
4545
pub created_at: Timespec,
4646
pub downloads: i32,
47-
pub max_version: semver::Version,
4847
pub description: Option<String>,
4948
pub homepage: Option<String>,
5049
pub documentation: Option<String>,
@@ -233,18 +232,20 @@ impl Crate {
233232
}
234233

235234
pub fn minimal_encodable(self,
235+
max_version: semver::Version,
236236
badges: Option<Vec<Badge>>) -> EncodableCrate {
237-
self.encodable(None, None, None, badges)
237+
self.encodable(max_version, None, None, None, badges)
238238
}
239239

240240
pub fn encodable(self,
241+
max_version: semver::Version,
241242
versions: Option<Vec<i32>>,
242243
keywords: Option<&[Keyword]>,
243244
categories: Option<&[Category]>,
244245
badges: Option<Vec<Badge>>)
245246
-> EncodableCrate {
246247
let Crate {
247-
name, created_at, updated_at, downloads, max_version, description,
248+
name, created_at, updated_at, downloads, description,
248249
homepage, documentation, license, repository,
249250
readme: _, id: _, max_upload_size: _,
250251
} = self;
@@ -255,7 +256,7 @@ impl Crate {
255256
let keyword_ids = keywords.map(|kws| kws.iter().map(|kw| kw.keyword.clone()).collect());
256257
let category_ids = categories.map(|cats| cats.iter().map(|cat| cat.slug.clone()).collect());
257258
let badges = badges.map(|bs| {
258-
bs.iter().map(|b| b.clone().encodable()).collect()
259+
bs.into_iter().map(|b| b.encodable()).collect()
259260
});
260261
EncodableCrate {
261262
id: name.clone(),
@@ -282,6 +283,16 @@ impl Crate {
282283
}
283284
}
284285

286+
pub fn max_version(&self, conn: &GenericConnection) -> CargoResult<semver::Version> {
287+
let stmt = conn.prepare("SELECT num FROM versions WHERE crate_id = $1
288+
AND yanked = 'f'")?;
289+
let rows = stmt.query(&[&self.id])?;
290+
Ok(rows.iter()
291+
.map(|r| semver::Version::parse(&r.get::<_, String>("num")).unwrap())
292+
.max()
293+
.unwrap_or_else(|| semver::Version::parse("0.0.0").unwrap()))
294+
}
295+
285296
pub fn versions(&self, conn: &GenericConnection) -> CargoResult<Vec<Version>> {
286297
let stmt = conn.prepare("SELECT * FROM versions \
287298
WHERE crate_id = $1")?;
@@ -384,14 +395,6 @@ impl Crate {
384395
}
385396
None => {}
386397
}
387-
let zero = semver::Version::parse("0.0.0").unwrap();
388-
if *ver > self.max_version || self.max_version == zero {
389-
self.max_version = ver.clone();
390-
}
391-
let stmt = conn.prepare("UPDATE crates SET max_version = $1
392-
WHERE id = $2 RETURNING updated_at")?;
393-
let rows = stmt.query(&[&self.max_version.to_string(), &self.id])?;
394-
self.updated_at = rows.get(0).get("updated_at");
395398
Version::insert(conn, self.id, ver, features, authors)
396399
}
397400

@@ -434,34 +437,34 @@ impl Crate {
434437
INNER JOIN crates
435438
ON crates.id = versions.crate_id
436439
WHERE dependencies.crate_id = $1
437-
AND versions.num = crates.max_version
440+
AND versions.num = $2
438441
";
439442
let fetch_sql = format!("SELECT DISTINCT ON (crate_downloads, crate_name)
440443
dependencies.*,
441444
crates.downloads AS crate_downloads,
442445
crates.name AS crate_name
443446
{}
444447
ORDER BY crate_downloads DESC
445-
OFFSET $2
446-
LIMIT $3",
448+
OFFSET $3
449+
LIMIT $4",
447450
select_sql);
448451
let count_sql = format!("SELECT COUNT(DISTINCT(crates.id)) {}", select_sql);
449452

450453
let stmt = conn.prepare(&fetch_sql)?;
451-
let vec: Vec<_> = stmt.query(&[&self.id, &offset, &limit])?
454+
let max_version = self.max_version(conn)?.to_string();
455+
let vec: Vec<_> = stmt.query(&[&self.id, &max_version, &offset, &limit])?
452456
.iter()
453457
.map(|r| (Model::from_row(&r), r.get("crate_name"), r.get("crate_downloads")))
454458
.collect();
455459
let stmt = conn.prepare(&count_sql)?;
456-
let cnt: i64 = stmt.query(&[&self.id])?.iter().next().unwrap().get(0);
460+
let cnt: i64 = stmt.query(&[&self.id, &max_version])?.iter().next().unwrap().get(0);
457461

458462
Ok((vec, cnt))
459463
}
460464
}
461465

462466
impl Model for Crate {
463467
fn from_row(row: &Row) -> Crate {
464-
let max: String = row.get("max_version");
465468
Crate {
466469
id: row.get("id"),
467470
name: row.get("name"),
@@ -472,7 +475,6 @@ impl Model for Crate {
472475
documentation: row.get("documentation"),
473476
homepage: row.get("homepage"),
474477
readme: row.get("readme"),
475-
max_version: semver::Version::parse(&max).unwrap(),
476478
license: row.get("license"),
477479
repository: row.get("repository"),
478480
max_upload_size: row.get("max_upload_size"),
@@ -603,8 +605,9 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
603605
let mut crates = Vec::new();
604606
for row in stmt.query(&args)?.iter() {
605607
let krate: Crate = Model::from_row(&row);
606-
let badges = krate.badges(conn);
607-
crates.push(krate.minimal_encodable(badges.ok()));
608+
let badges = krate.badges(conn)?;
609+
let max_version = krate.max_version(conn)?;
610+
crates.push(krate.minimal_encodable(max_version, Some(badges)));
608611
}
609612

610613
// Query for the total count of crates
@@ -637,10 +640,11 @@ pub fn summary(req: &mut Request) -> CargoResult<Response> {
637640

638641
let to_crates = |stmt: pg::stmt::Statement| -> CargoResult<Vec<_>> {
639642
let rows = stmt.query(&[])?;
640-
Ok(rows.iter().map(|r| {
643+
rows.iter().map(|r| {
641644
let krate: Crate = Model::from_row(&r);
642-
krate.minimal_encodable(None)
643-
}).collect::<Vec<EncodableCrate>>())
645+
let max_version = krate.max_version(tx)?;
646+
Ok(krate.minimal_encodable(max_version, None))
647+
}).collect()
644648
};
645649
let new_crates = tx.prepare("SELECT * FROM crates \
646650
ORDER BY created_at DESC LIMIT 10")?;
@@ -692,6 +696,7 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
692696
let kws = krate.keywords(conn)?;
693697
let cats = krate.categories(conn)?;
694698
let badges = krate.badges(conn)?;
699+
let max_version = krate.max_version(conn)?;
695700

696701
#[derive(RustcEncodable)]
697702
struct R {
@@ -702,7 +707,7 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
702707
}
703708
Ok(req.json(&R {
704709
krate: krate.clone().encodable(
705-
Some(ids), Some(&kws), Some(&cats), Some(badges)
710+
max_version, Some(ids), Some(&kws), Some(&cats), Some(badges)
706711
),
707712
versions: versions.into_iter().map(|v| {
708713
v.encodable(&krate.name)
@@ -785,6 +790,7 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
785790
&krate,
786791
new_crate.badges.unwrap_or_else(HashMap::new)
787792
)?;
793+
let max_version = krate.max_version(req.tx()?)?;
788794

789795
// Upload the crate to S3
790796
let mut handle = req.app().handle();
@@ -855,7 +861,7 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
855861
#[derive(RustcEncodable)]
856862
struct R { krate: EncodableCrate, warnings: Warnings }
857863
Ok(req.json(&R {
858-
krate: krate.minimal_encodable(None),
864+
krate: krate.minimal_encodable(max_version, None),
859865
warnings: warnings
860866
}))
861867
}

src/tests/all.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ fn krate(name: &str) -> Crate {
193193
updated_at: time::now().to_timespec(),
194194
created_at: time::now().to_timespec(),
195195
downloads: 10,
196-
max_version: semver::Version::parse("0.0.0").unwrap(),
197196
documentation: None,
198197
homepage: None,
199198
description: None,

src/user/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,9 @@ pub fn updates(req: &mut Request) -> CargoResult<Response> {
337337

338338
// Encode everything!
339339
let crates = crates.into_iter().map(|c| {
340-
c.minimal_encodable(None)
341-
}).collect();
340+
let max_version = c.max_version(tx)?;
341+
Ok(c.minimal_encodable(max_version, None))
342+
}).collect::<CargoResult<_>>()?;
342343
let versions = versions.into_iter().map(|v| {
343344
let id = v.crate_id;
344345
v.encodable(&map[&id])

0 commit comments

Comments
 (0)