Skip to content

Commit 3e9d6e3

Browse files
authored
Merge pull request #9722 from eth3lbert/default-version-for-publish
`default_version` for publish
2 parents bb6df6f + a5d24b3 commit 3e9d6e3

File tree

3 files changed

+49
-26
lines changed

3 files changed

+49
-26
lines changed

src/controllers/krate/publish.rs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use tokio::runtime::Handle;
2323
use url::Url;
2424

2525
use crate::models::{
26-
insert_version_owner_action, Category, Crate, DependencyKind, Keyword, NewCrate, NewVersion,
27-
Rights, VersionAction,
26+
default_versions::Version as DefaultVersion, insert_version_owner_action, Category, Crate,
27+
DependencyKind, Keyword, NewCrate, NewVersion, Rights, VersionAction,
2828
};
2929

3030
use crate::licenses::parse_license_expr;
@@ -64,7 +64,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
6464

6565
Crate::validate_crate_name("crate", &metadata.name).map_err(bad_request)?;
6666

67-
let version = match semver::Version::parse(&metadata.vers) {
67+
let semver = match semver::Version::parse(&metadata.vers) {
6868
Ok(parsed) => parsed,
6969
Err(_) => {
7070
return Err(bad_request(format_args!(
@@ -75,7 +75,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
7575
};
7676

7777
// Convert the version back to a string to deal with any inconsistencies
78-
let version_string = version.to_string();
78+
let version_string = semver.to_string();
7979

8080
let request_log = req.request_log();
8181
request_log.add("crate_name", &*metadata.name);
@@ -385,17 +385,46 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
385385
// Link this new version to all dependencies
386386
add_dependencies(conn, &deps, version.id)?;
387387

388-
// Insert the default version if it doesn't already exist. Compared
389-
// to only using a background job, this prevents us from getting
390-
// into a situation where a crate exists in the `crates` table but
391-
// doesn't have a default version in the `default_versions` table.
392-
let inserted_default_versions = diesel::insert_into(default_versions::table)
393-
.values((
394-
default_versions::crate_id.eq(krate.id),
395-
default_versions::version_id.eq(version.id),
396-
))
397-
.on_conflict_do_nothing()
398-
.execute(conn)?;
388+
let existing_default_version = default_versions::table
389+
.inner_join(versions::table)
390+
.filter(default_versions::crate_id.eq(krate.id))
391+
.select(DefaultVersion::as_select())
392+
.first(conn)
393+
.optional()?;
394+
395+
// Upsert the `default_value` determined by the existing `default_value` and the
396+
// published version. Note that this could potentially write an outdated version
397+
// (although this should not happen regularly), as we might be comparing to an
398+
// outdated value.
399+
//
400+
// Compared to only using a background job, this prevents us from getting into a
401+
// situation where a crate exists in the `crates` table but doesn't have a default
402+
// version in the `default_versions` table.
403+
if let Some(existing_default_version) = existing_default_version {
404+
let published_default_version = DefaultVersion {
405+
id: version.id,
406+
num: semver,
407+
yanked: false,
408+
};
409+
410+
if existing_default_version < published_default_version {
411+
diesel::update(default_versions::table)
412+
.filter(default_versions::crate_id.eq(krate.id))
413+
.set(default_versions::version_id.eq(version.id))
414+
.execute(conn)?;
415+
}
416+
417+
// Update the default version asynchronously in a background job
418+
// to ensure correctness and eventual consistency.
419+
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
420+
} else {
421+
diesel::insert_into(default_versions::table)
422+
.values((
423+
default_versions::crate_id.eq(krate.id),
424+
default_versions::version_id.eq(version.id),
425+
))
426+
.execute(conn)?;
427+
}
399428

400429
// Update all keywords for this crate
401430
Keyword::update_crate(conn, &krate, &keywords)?;
@@ -446,12 +475,6 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
446475

447476
SendPublishNotificationsJob::new(version.id).enqueue(conn)?;
448477

449-
// If this is a new version for an existing crate it is sufficient
450-
// to update the default version asynchronously in a background job.
451-
if inserted_default_versions == 0 {
452-
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
453-
}
454-
455478
// Experiment: check new crates for potential typosquatting.
456479
if existing_crate.is_none() {
457480
CheckTyposquat::new(&krate.name).enqueue(conn)?;

src/models.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub mod helpers;
2020
mod action;
2121
pub mod category;
2222
mod crate_owner_invitation;
23-
mod default_versions;
23+
pub mod default_versions;
2424
pub mod dependency;
2525
mod download;
2626
mod email;

src/models/default_versions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ use diesel::prelude::*;
1515
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Selectable)]
1616
#[diesel(table_name = versions)]
1717
#[diesel(check_for_backend(diesel::pg::Pg))]
18-
struct Version {
19-
id: i32,
18+
pub struct Version {
19+
pub id: i32,
2020
#[diesel(deserialize_as = SemverVersion)]
21-
num: semver::Version,
22-
yanked: bool,
21+
pub num: semver::Version,
22+
pub yanked: bool,
2323
}
2424

2525
impl Version {

0 commit comments

Comments
 (0)