Skip to content

Commit 6b3d53d

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 9cb919f commit 6b3d53d

File tree

4 files changed

+41
-29
lines changed

4 files changed

+41
-29
lines changed

src/bin/migrate.rs

+6
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,12 @@ fn migrations() -> Vec<Migration> {
831831
tx.execute("DROP INDEX badges_crate_type", &[])?;
832832
Ok(())
833833
}),
834+
Migration::new(20170305123234, |tx| {
835+
tx.execute("ALTER TABLE crates DROP COLUMN max_version", &[])?;
836+
Ok(())
837+
}, |_| {
838+
panic!("Unreversible migration")
839+
}),
834840
];
835841
// NOTE: Generate a new id via `date +"%Y%m%d%H%M%S"`
836842

src/krate.rs

+32-26
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>,
@@ -232,18 +231,20 @@ impl Crate {
232231
}
233232

234233
pub fn minimal_encodable(self,
234+
max_version: semver::Version,
235235
badges: Option<Vec<Badge>>) -> EncodableCrate {
236-
self.encodable(None, None, None, badges)
236+
self.encodable(max_version, None, None, None, badges)
237237
}
238238

239239
pub fn encodable(self,
240+
max_version: semver::Version,
240241
versions: Option<Vec<i32>>,
241242
keywords: Option<&[Keyword]>,
242243
categories: Option<&[Category]>,
243244
badges: Option<Vec<Badge>>)
244245
-> EncodableCrate {
245246
let Crate {
246-
name, created_at, updated_at, downloads, max_version, description,
247+
name, created_at, updated_at, downloads, description,
247248
homepage, documentation, license, repository,
248249
readme: _, id: _, max_upload_size: _,
249250
} = self;
@@ -254,7 +255,7 @@ impl Crate {
254255
let keyword_ids = keywords.map(|kws| kws.iter().map(|kw| kw.keyword.clone()).collect());
255256
let category_ids = categories.map(|cats| cats.iter().map(|cat| cat.slug.clone()).collect());
256257
let badges = badges.map(|bs| {
257-
bs.iter().map(|b| b.clone().encodable()).collect()
258+
bs.into_iter().map(|b| b.encodable()).collect()
258259
});
259260
EncodableCrate {
260261
id: name.clone(),
@@ -281,6 +282,16 @@ impl Crate {
281282
}
282283
}
283284

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

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

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

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

461465
impl Model for Crate {
462466
fn from_row(row: &Row) -> Crate {
463-
let max: String = row.get("max_version");
464467
Crate {
465468
id: row.get("id"),
466469
name: row.get("name"),
@@ -471,7 +474,6 @@ impl Model for Crate {
471474
documentation: row.get("documentation"),
472475
homepage: row.get("homepage"),
473476
readme: row.get("readme"),
474-
max_version: semver::Version::parse(&max).unwrap(),
475477
license: row.get("license"),
476478
repository: row.get("repository"),
477479
max_upload_size: row.get("max_upload_size"),
@@ -602,8 +604,9 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
602604
let mut crates = Vec::new();
603605
for row in stmt.query(&args)?.iter() {
604606
let krate: Crate = Model::from_row(&row);
605-
let badges = krate.badges(conn);
606-
crates.push(krate.minimal_encodable(badges.ok()));
607+
let badges = krate.badges(conn)?;
608+
let max_version = krate.max_version(conn)?;
609+
crates.push(krate.minimal_encodable(max_version, Some(badges)));
607610
}
608611

609612
// Query for the total count of crates
@@ -636,10 +639,11 @@ pub fn summary(req: &mut Request) -> CargoResult<Response> {
636639

637640
let to_crates = |stmt: pg::stmt::Statement| -> CargoResult<Vec<_>> {
638641
let rows = stmt.query(&[])?;
639-
Ok(rows.iter().map(|r| {
642+
rows.iter().map(|r| {
640643
let krate: Crate = Model::from_row(&r);
641-
krate.minimal_encodable(None)
642-
}).collect::<Vec<EncodableCrate>>())
644+
let max_version = krate.max_version(tx)?;
645+
Ok(krate.minimal_encodable(max_version, None))
646+
}).collect()
643647
};
644648
let new_crates = tx.prepare("SELECT * FROM crates \
645649
ORDER BY created_at DESC LIMIT 10")?;
@@ -691,6 +695,7 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
691695
let kws = krate.keywords(conn)?;
692696
let cats = krate.categories(conn)?;
693697
let badges = krate.badges(conn)?;
698+
let max_version = krate.max_version(conn)?;
694699

695700
#[derive(RustcEncodable)]
696701
struct R {
@@ -701,7 +706,7 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
701706
}
702707
Ok(req.json(&R {
703708
krate: krate.clone().encodable(
704-
Some(ids), Some(&kws), Some(&cats), Some(badges)
709+
max_version, Some(ids), Some(&kws), Some(&cats), Some(badges)
705710
),
706711
versions: versions.into_iter().map(|v| {
707712
v.encodable(&krate.name)
@@ -784,6 +789,7 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
784789
&krate,
785790
new_crate.badges.unwrap_or_else(HashMap::new)
786791
)?;
792+
let max_version = krate.max_version(req.tx()?)?;
787793

788794
// Upload the crate to S3
789795
let mut handle = req.app().handle();
@@ -854,7 +860,7 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
854860
#[derive(RustcEncodable)]
855861
struct R { krate: EncodableCrate, warnings: Warnings }
856862
Ok(req.json(&R {
857-
krate: krate.minimal_encodable(None),
863+
krate: krate.minimal_encodable(max_version, None),
858864
warnings: warnings
859865
}))
860866
}

src/tests/all.rs

-1
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

+3-2
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)