Updated name to be unique. Hopefully not a problem
This commit is contained in:
@@ -13,6 +13,9 @@ pub enum DbError {
|
|||||||
|
|
||||||
#[error("serialization error: {0}")]
|
#[error("serialization error: {0}")]
|
||||||
Serialization(#[from] serde_json::Error),
|
Serialization(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DbResult<T> = Result<T, DbError>;
|
pub type DbResult<T> = Result<T, DbError>;
|
||||||
|
|||||||
101
src/migration/m20260317_000007_unique_artist_album.rs
Normal file
101
src/migration/m20260317_000007_unique_artist_album.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ mod m20260317_000003_create_tracks;
|
|||||||
mod m20260317_000004_create_wanted_items;
|
mod m20260317_000004_create_wanted_items;
|
||||||
mod m20260317_000005_create_download_queue;
|
mod m20260317_000005_create_download_queue;
|
||||||
mod m20260317_000006_create_search_cache;
|
mod m20260317_000006_create_search_cache;
|
||||||
|
mod m20260317_000007_unique_artist_album;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260317_000004_create_wanted_items::Migration),
|
Box::new(m20260317_000004_create_wanted_items::Migration),
|
||||||
Box::new(m20260317_000005_create_download_queue::Migration),
|
Box::new(m20260317_000005_create_download_queue::Migration),
|
||||||
Box::new(m20260317_000006_create_search_cache::Migration),
|
Box::new(m20260317_000006_create_search_cache::Migration),
|
||||||
|
Box::new(m20260317_000007_unique_artist_album::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub async fn upsert(
|
|||||||
return Ok(existing);
|
return Ok(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to insert — catch unique constraint race
|
||||||
let active = ActiveModel {
|
let active = ActiveModel {
|
||||||
name: Set(name.to_string()),
|
name: Set(name.to_string()),
|
||||||
album_artist: Set(album_artist.to_string()),
|
album_artist: Set(album_artist.to_string()),
|
||||||
@@ -31,7 +32,21 @@ pub async fn upsert(
|
|||||||
artist_id: Set(artist_id),
|
artist_id: Set(artist_id),
|
||||||
..Default::default()
|
..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> {
|
pub async fn get_by_id(db: &DatabaseConnection, id: i32) -> DbResult<Album> {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ pub async fn upsert(db: &DatabaseConnection, name: &str, musicbrainz_id: Option<
|
|||||||
return Ok(existing);
|
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 now = Utc::now().naive_utc();
|
||||||
let active = ActiveModel {
|
let active = ActiveModel {
|
||||||
name: Set(name.to_string()),
|
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()),
|
similar_artists: Set("[]".to_string()),
|
||||||
..Default::default()
|
..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> {
|
pub async fn get_by_id(db: &DatabaseConnection, id: i32) -> DbResult<Artist> {
|
||||||
|
|||||||
Reference in New Issue
Block a user