Updated name to be unique. Hopefully not a problem

This commit is contained in:
Connor Johnstone
2026-03-17 14:44:16 -04:00
parent 305ddff278
commit 2997d7e4f9
5 changed files with 136 additions and 2 deletions

View File

@@ -13,6 +13,9 @@ pub enum DbError {
#[error("serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("{0}")]
Other(String),
}
pub type DbResult<T> = Result<T, DbError>;

View File

@@ -0,0 +1,101 @@
use sea_orm_migration::prelude::*;
use super::m20260317_000001_create_artists::Artists;
use super::m20260317_000002_create_albums::Albums;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Drop the old non-unique index on artists.name
manager
.drop_index(
Index::drop()
.name("idx_artists_name")
.table(Artists::Table)
.to_owned(),
)
.await?;
// Create a unique index on artists.name
manager
.create_index(
Index::create()
.name("idx_artists_name_unique")
.table(Artists::Table)
.col(Artists::Name)
.unique()
.to_owned(),
)
.await?;
// Drop the old non-unique index on albums.name
manager
.drop_index(
Index::drop()
.name("idx_albums_name")
.table(Albums::Table)
.to_owned(),
)
.await?;
// Create a unique composite index on albums(name, album_artist)
manager
.create_index(
Index::create()
.name("idx_albums_name_artist_unique")
.table(Albums::Table)
.col(Albums::Name)
.col(Albums::AlbumArtist)
.unique()
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_index(
Index::drop()
.name("idx_albums_name_artist_unique")
.table(Albums::Table)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_albums_name")
.table(Albums::Table)
.col(Albums::Name)
.to_owned(),
)
.await?;
manager
.drop_index(
Index::drop()
.name("idx_artists_name_unique")
.table(Artists::Table)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_artists_name")
.table(Artists::Table)
.col(Artists::Name)
.to_owned(),
)
.await?;
Ok(())
}
}

View File

@@ -6,6 +6,7 @@ mod m20260317_000003_create_tracks;
mod m20260317_000004_create_wanted_items;
mod m20260317_000005_create_download_queue;
mod m20260317_000006_create_search_cache;
mod m20260317_000007_unique_artist_album;
pub struct Migrator;
@@ -19,6 +20,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260317_000004_create_wanted_items::Migration),
Box::new(m20260317_000005_create_download_queue::Migration),
Box::new(m20260317_000006_create_search_cache::Migration),
Box::new(m20260317_000007_unique_artist_album::Migration),
]
}
}

View File

@@ -24,6 +24,7 @@ pub async fn upsert(
return Ok(existing);
}
// Try to insert — catch unique constraint race
let active = ActiveModel {
name: Set(name.to_string()),
album_artist: Set(album_artist.to_string()),
@@ -31,7 +32,21 @@ pub async fn upsert(
artist_id: Set(artist_id),
..Default::default()
};
Ok(active.insert(db).await?)
match active.insert(db).await {
Ok(inserted) => Ok(inserted),
Err(DbErr::Exec(RuntimeErr::SqlxError(sqlx_err)))
if sqlx_err.to_string().contains("UNIQUE constraint failed") =>
{
find_by_name_and_artist(db, name, album_artist)
.await?
.ok_or_else(|| {
DbError::Other(format!(
"album '{name}' by '{album_artist}' vanished after conflict"
))
})
}
Err(e) => Err(e.into()),
}
}
pub async fn get_by_id(db: &DatabaseConnection, id: i32) -> DbResult<Album> {

View File

@@ -26,6 +26,8 @@ pub async fn upsert(db: &DatabaseConnection, name: &str, musicbrainz_id: Option<
return Ok(existing);
}
// Try to insert — if we race with another task, catch the unique constraint
// violation and fall back to a lookup.
let now = Utc::now().naive_utc();
let active = ActiveModel {
name: Set(name.to_string()),
@@ -35,7 +37,18 @@ pub async fn upsert(db: &DatabaseConnection, name: &str, musicbrainz_id: Option<
similar_artists: Set("[]".to_string()),
..Default::default()
};
Ok(active.insert(db).await?)
match active.insert(db).await {
Ok(inserted) => Ok(inserted),
Err(DbErr::Exec(RuntimeErr::SqlxError(sqlx_err)))
if sqlx_err.to_string().contains("UNIQUE constraint failed") =>
{
// Lost the race — another task inserted first, just look it up
find_by_name(db, name)
.await?
.ok_or_else(|| DbError::Other(format!("artist '{name}' vanished after conflict")))
}
Err(e) => Err(e.into()),
}
}
pub async fn get_by_id(db: &DatabaseConnection, id: i32) -> DbResult<Artist> {