Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4e0756a90 |
@@ -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,
|
||||||
|
}
|
||||||
@@ -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
@@ -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")))
|
||||||
|
|||||||
Reference in New Issue
Block a user