Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Johnstone b4e0756a90 proper fix plus delete artist actually removes files 2026-03-25 14:32:17 -04:00
3 changed files with 77 additions and 3 deletions
@@ -0,0 +1,59 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Drop the unique index on artist name — different artists can share a name
// (e.g., "Clara" the Italian singer and "Clara" the Brazilian singer)
manager
.drop_index(
Index::drop()
.name("idx_artists_name_unique")
.table(Artists::Table)
.to_owned(),
)
.await?;
// Replace with a non-unique index for lookup performance
manager
.create_index(
Index::create()
.name("idx_artists_name")
.table(Artists::Table)
.col(Artists::Name)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_index(
Index::drop()
.name("idx_artists_name")
.table(Artists::Table)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_artists_name_unique")
.table(Artists::Table)
.col(Artists::Name)
.unique()
.to_owned(),
)
.await
}
}
#[derive(DeriveIden)]
enum Artists {
Table,
Name,
}
+2
View File
@@ -17,6 +17,7 @@ mod m20260320_000015_add_subsonic_password;
mod m20260323_000016_remove_orphaned_artists; mod m20260323_000016_remove_orphaned_artists;
mod m20260323_000017_create_work_queue_and_scheduler; mod m20260323_000017_create_work_queue_and_scheduler;
mod m20260324_000018_add_track_tagged; mod m20260324_000018_add_track_tagged;
mod m20260325_000019_allow_duplicate_artist_names;
pub struct Migrator; pub struct Migrator;
@@ -41,6 +42,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260323_000016_remove_orphaned_artists::Migration), Box::new(m20260323_000016_remove_orphaned_artists::Migration),
Box::new(m20260323_000017_create_work_queue_and_scheduler::Migration), Box::new(m20260323_000017_create_work_queue_and_scheduler::Migration),
Box::new(m20260324_000018_add_track_tagged::Migration), Box::new(m20260324_000018_add_track_tagged::Migration),
Box::new(m20260325_000019_allow_duplicate_artist_names::Migration),
] ]
} }
} }
+15 -2
View File
@@ -21,14 +21,19 @@ pub async fn upsert(
} }
if let Some(existing) = find_by_name(db, name).await? { if let Some(existing) = find_by_name(db, name).await? {
// Update musicbrainz_id if we have one now and didn't before
if musicbrainz_id.is_some() && existing.musicbrainz_id.is_none() { if musicbrainz_id.is_some() && existing.musicbrainz_id.is_none() {
// We have an MBID now and the existing record doesn't — update it
let mut active: ActiveModel = existing.into(); let mut active: ActiveModel = existing.into();
active.musicbrainz_id = Set(musicbrainz_id.map(String::from)); active.musicbrainz_id = Set(musicbrainz_id.map(String::from));
return Ok(active.update(db).await?); return Ok(active.update(db).await?);
} }
if musicbrainz_id.is_none() || existing.musicbrainz_id.as_deref() == musicbrainz_id {
// No MBID provided, or MBIDs match — return existing
return Ok(existing); return Ok(existing);
} }
// MBIDs differ — this is a different artist with the same name.
// Fall through to insert a new record.
}
// Try to insert — if we race with another task, catch the unique constraint // Try to insert — if we race with another task, catch the unique constraint
// violation and fall back to a lookup. // violation and fall back to a lookup.
@@ -48,7 +53,15 @@ pub async fn upsert(
Err(DbErr::Exec(RuntimeErr::SqlxError(sqlx_err))) Err(DbErr::Exec(RuntimeErr::SqlxError(sqlx_err)))
if sqlx_err.to_string().contains("UNIQUE constraint failed") => if sqlx_err.to_string().contains("UNIQUE constraint failed") =>
{ {
// Lost the race — another task inserted first, just look it up // Lost the race on MBID unique constraint — look up by MBID first, then name
if let Some(mbid) = musicbrainz_id
&& let Some(existing) = Artists::find()
.filter(artist::Column::MusicbrainzId.eq(mbid))
.one(db)
.await?
{
return Ok(existing);
}
find_by_name(db, name) find_by_name(db, name)
.await? .await?
.ok_or_else(|| DbError::Other(format!("artist '{name}' vanished after conflict"))) .ok_or_else(|| DbError::Other(format!("artist '{name}' vanished after conflict")))