Skip to content

Commit 5cd638f

Browse files
committed
publish: Save default_versions.total value
1 parent 65be740 commit 5cd638f

File tree

2 files changed

+58
-27
lines changed

2 files changed

+58
-27
lines changed

src/controllers/krate/publish.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ use diesel::dsl::{exists, select};
1515
use diesel::prelude::*;
1616
use diesel_async::scoped_futures::ScopedFutureExt;
1717
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
18-
use futures_util::TryFutureExt;
19-
use futures_util::TryStreamExt;
18+
use futures_util::{FutureExt, TryFutureExt, TryStreamExt};
2019
use hex::ToHex;
2120
use http::request::Parts;
2221
use http::StatusCode;
@@ -433,10 +432,10 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
433432
// Link this new version to all dependencies
434433
add_dependencies(conn, &deps, version.id).await?;
435434

436-
let existing_default_version = default_versions::table
435+
let existing_default_version: Option<(DefaultVersion, i32)> = default_versions::table
437436
.inner_join(versions::table)
438437
.filter(default_versions::crate_id.eq(krate.id))
439-
.select(DefaultVersion::as_select())
438+
.select((DefaultVersion::as_select(), default_versions::total))
440439
.first(conn)
441440
.await
442441
.optional()?;
@@ -450,22 +449,45 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
450449
// Compared to only using a background job, this prevents us from getting into a
451450
// situation where a crate exists in the `crates` table but doesn't have a default
452451
// version in the `default_versions` table.
453-
if let Some(existing_default_version) = existing_default_version {
452+
if let Some((existing_default_version, existing_total)) = existing_default_version {
454453
let published_default_version = DefaultVersion {
455454
id: version.id,
456455
num: semver,
457456
yanked: false,
458457
};
459-
460-
if existing_default_version < published_default_version {
461-
diesel::update(default_versions::table)
462-
.filter(default_versions::crate_id.eq(krate.id))
463-
.set(default_versions::version_id.eq(version.id))
458+
let update_base = diesel::update(default_versions::table)
459+
.filter(default_versions::crate_id.eq(krate.id));
460+
461+
// If `total` isn't set (it defaults to 0), this only occurs when a crate already
462+
// exists and releases a new version before the admin sets the total.
463+
// In this scenario, the `total` value is updated in a background job. This case should
464+
// be safe to remove once all `total` values have been set.
465+
let is_new = existing_default_version < published_default_version;
466+
let default_total = existing_total == 0;
467+
let fut = match (is_new, default_total) {
468+
(true, true) => {
469+
update_base.set(default_versions::version_id.eq(version.id)).execute(conn)
470+
},
471+
(false, true) => {
472+
default_version = Some(existing_default_version.num.to_string());
473+
async { Ok(0) }.boxed()
474+
},
475+
(true, false) => {
476+
update_base.set((
477+
default_versions::version_id.eq(version.id),
478+
default_versions::total.eq(default_versions::total + 1)
479+
))
464480
.execute(conn)
465-
.await?;
466-
} else {
467-
default_version = Some(existing_default_version.num.to_string());
468-
}
481+
},
482+
(false, false) => {
483+
default_version = Some(existing_default_version.num.to_string());
484+
update_base.set(
485+
default_versions::total.eq(default_versions::total + 1)
486+
)
487+
.execute(conn)
488+
},
489+
};
490+
fut.await?;
469491

470492
// Update the default version asynchronously in a background job
471493
// to ensure correctness and eventual consistency.
@@ -475,6 +497,7 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
475497
.values((
476498
default_versions::crate_id.eq(krate.id),
477499
default_versions::version_id.eq(version.id),
500+
default_versions::total.eq(1),
478501
))
479502
.execute(conn)
480503
.await?;

src/models/default_versions.rs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::schema::{default_versions, versions};
2-
use crates_io_diesel_helpers::SemverVersion;
2+
use crates_io_diesel_helpers::{greatest, SemverVersion};
33
use diesel::prelude::*;
44
use diesel_async::{AsyncPgConnection, RunQueryDsl};
55

@@ -60,7 +60,7 @@ pub async fn update_default_version(
6060
crate_id: i32,
6161
conn: &mut AsyncPgConnection,
6262
) -> QueryResult<()> {
63-
let default_version = calculate_default_version(crate_id, conn).await?;
63+
let (default_version, total) = calculate_default_version(crate_id, conn).await?;
6464

6565
debug!(
6666
"Updating default version to {} (id: {})…",
@@ -74,7 +74,10 @@ pub async fn update_default_version(
7474
))
7575
.on_conflict(default_versions::crate_id)
7676
.do_update()
77-
.set(default_versions::version_id.eq(default_version.id))
77+
.set((
78+
default_versions::version_id.eq(default_version.id),
79+
default_versions::total.eq(greatest(total as i32, default_versions::total)),
80+
))
7881
.execute(conn)
7982
.await?;
8083

@@ -87,22 +90,22 @@ pub async fn verify_default_version(
8790
crate_id: i32,
8891
conn: &mut AsyncPgConnection,
8992
) -> QueryResult<()> {
90-
let calculated = calculate_default_version(crate_id, conn).await?;
93+
let (calculated, total) = calculate_default_version(crate_id, conn).await?;
9194

9295
let saved = default_versions::table
93-
.select(default_versions::version_id)
96+
.select((default_versions::version_id, default_versions::total))
9497
.filter(default_versions::crate_id.eq(crate_id))
95-
.first::<i32>(conn)
98+
.first::<(i32, i32)>(conn)
9699
.await
97100
.optional()?;
98101

99-
if let Some(saved) = saved {
100-
if saved == calculated.id {
102+
if let Some((saved_id, saved_total)) = saved {
103+
if saved_id == calculated.id && saved_total == (total as i32) {
101104
debug!("Default version for crate {crate_id} is up to date");
102105
} else {
103106
warn!(
104-
"Default version for crate {crate_id} is outdated (expected: {saved}, actual: {})",
105-
calculated.id,
107+
"Default version for crate {crate_id} is outdated (expected: {saved_id} with total: {saved_total}, actual: {} with total: {})",
108+
calculated.id, total
106109
);
107110
}
108111
} else {
@@ -118,7 +121,7 @@ pub async fn verify_default_version(
118121
async fn calculate_default_version(
119122
crate_id: i32,
120123
conn: &mut AsyncPgConnection,
121-
) -> QueryResult<Version> {
124+
) -> QueryResult<(Version, usize)> {
122125
use diesel::result::Error::NotFound;
123126

124127
debug!("Loading all versions for the crate…");
@@ -128,9 +131,14 @@ async fn calculate_default_version(
128131
.load::<Version>(conn)
129132
.await?;
130133

131-
debug!("Found {} versions", versions.len());
134+
let total = versions.len();
135+
debug!("Found {} versions", total);
132136

133-
versions.into_iter().max().ok_or(NotFound)
137+
versions
138+
.into_iter()
139+
.max()
140+
.map(|version| (version, total))
141+
.ok_or(NotFound)
134142
}
135143

136144
#[cfg(test)]

0 commit comments

Comments
 (0)