From fcc2f7d1b6549c7798fc858e7ca55ccbf450e4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Sat, 21 Nov 2020 16:58:11 +0100 Subject: [PATCH] fix(api): db error on duplicate music library import When the spotify crawler loop would import an artist multiple times in parallel the first would succeed but the following queries would throw with following exception: QueryFailedError: duplicate key value violates unique constraint "IDX_ARTIST_SPOTIFY_ID" This error also could happen for the album or track. --- src/database/error-codes.ts | 8 +++++ src/music-library/music-library.service.ts | 42 ++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/database/error-codes.ts diff --git a/src/database/error-codes.ts b/src/database/error-codes.ts new file mode 100644 index 0000000..10f188f --- /dev/null +++ b/src/database/error-codes.ts @@ -0,0 +1,8 @@ +/** + * Offical postgres error codes to match against when checking database exceptions. + * + * https://www.postgresql.org/docs/current/errcodes-appendix.html + */ +export enum PostgresErrorCodes { + UNIQUE_VIOLATION = "23505", +} diff --git a/src/music-library/music-library.service.ts b/src/music-library/music-library.service.ts index 2e61496..2f34503 100644 --- a/src/music-library/music-library.service.ts +++ b/src/music-library/music-library.service.ts @@ -1,4 +1,5 @@ import { Injectable } from "@nestjs/common"; +import { PostgresErrorCodes } from "../database/error-codes"; import { Album } from "./album.entity"; import { AlbumRepository } from "./album.repository"; import { Artist } from "./artist.entity"; @@ -32,7 +33,20 @@ export class MusicLibraryService { artist.name = data.name; artist.spotify = data.spotify; - await this.artistRepository.save(artist); + try { + await this.artistRepository.save(artist); + } catch (err) { + if ( + err.code === PostgresErrorCodes.UNIQUE_VIOLATION && + err.constraint === "IDX_ARTIST_SPOTIFY_ID" + ) { + // Multiple simultaneous importArtist calls for the same artist were + // executed and it is now available in the database for use to retrieve + return this.findArtist({ spotify: { id: data.spotify.id } }); + } + + throw err; + } return artist; } @@ -50,7 +64,18 @@ export class MusicLibraryService { album.artists = data.artists; album.spotify = data.spotify; - await this.albumRepository.save(album); + try { + await this.albumRepository.save(album); + } catch (err) { + if ( + err.code === PostgresErrorCodes.UNIQUE_VIOLATION && + err.constraint === "IDX_ALBUM_SPOTIFY_ID" + ) { + // Multiple simultaneous importAlbum calls for the same album were + // executed and it is now available in the database for use to retrieve + return this.findAlbum({ spotify: { id: data.spotify.id } }); + } + } return album; } @@ -69,7 +94,18 @@ export class MusicLibraryService { track.album = data.album; track.spotify = data.spotify; - await this.trackRepository.save(track); + try { + await this.trackRepository.save(track); + } catch (err) { + if ( + err.code === PostgresErrorCodes.UNIQUE_VIOLATION && + err.constraint === "IDX_TRACK_SPOTIFY_ID" + ) { + // Multiple simultaneous findTrack calls for the same track were + // executed and it is now available in the database for use to retrieve + return this.findTrack({ spotify: { id: data.spotify.id } }); + } + } return track; }