feat: a release catalogue from MusicBrainz; request search asks it directly (M502 #5519)
release / govulncheck (push) Successful in 22s
release / web (push) Failing after 24s
release / go (push) Successful in 1m57s
release / integration (push) Successful in 4m1s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 4m38s
release / Build signed APK (releases and dev) (push) Canceled after 4m37s
release / govulncheck (push) Successful in 22s
release / web (push) Failing after 24s
release / go (push) Successful in 1m57s
release / integration (push) Successful in 4m1s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 4m38s
release / Build signed APK (releases and dev) (push) Canceled after 4m37s
Minstrel keeps its own record of what artists have released, read from MusicBrainz, so the wanted list, search and import no longer need Lidarr. - internal/musicbrainz: searches (artists, release groups, recordings) and browses (an artist's release groups; a group's releases with track lists). One process-wide pacer at 1.1s, shared with tag enrichment, because MusicBrainz's limit is per address. A 503 throttle is retried. - Migration 0080: mb_artists, mb_release_groups, mb_artist_release_groups (a collaboration belongs to each credited artist), mb_releases, mb_release_tracks. - internal/catalogue: Discography and Releases serve the held copy and re-read it when older than 7 or 30 days. An unreachable MusicBrainz serves the stale copy, marked. An hourly pass re-reads due artists. - Art for what isn't held comes through /api/catalogue/art from the enabled cover-art sources (Cover Art Archive by release group, then the name-based sources) and is kept on disk, so the browser never fetches a third-party host. A miss is a 404 and the card shows its icon. - Request search moves from /api/lidarr/search to /api/catalogue/search, with the same response shape. Albums are release groups, and "in library" matches albums.release_group_mbid. A 10-minute cache absorbs search-as-you-type. Discover suggestions point at the art endpoint instead of looking up Lidarr. - Android calls the new path and loads Discover art through ServerImage. Installed builds older than this one lose Discover search until they update (no compatibility path, rule 22). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: catalogue.sql
|
||||
|
||||
package dbq
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addMBArtistReleaseGroup = `-- name: AddMBArtistReleaseGroup :exec
|
||||
INSERT INTO mb_artist_release_groups (artist_mbid, release_group_mbid)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
type AddMBArtistReleaseGroupParams struct {
|
||||
ArtistMbid string
|
||||
ReleaseGroupMbid string
|
||||
}
|
||||
|
||||
func (q *Queries) AddMBArtistReleaseGroup(ctx context.Context, arg AddMBArtistReleaseGroupParams) error {
|
||||
_, err := q.db.Exec(ctx, addMBArtistReleaseGroup, arg.ArtistMbid, arg.ReleaseGroupMbid)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteMBArtistReleaseGroups = `-- name: DeleteMBArtistReleaseGroups :exec
|
||||
DELETE FROM mb_artist_release_groups WHERE artist_mbid = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteMBArtistReleaseGroups(ctx context.Context, artistMbid string) error {
|
||||
_, err := q.db.Exec(ctx, deleteMBArtistReleaseGroups, artistMbid)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteMBReleasesForGroup = `-- name: DeleteMBReleasesForGroup :exec
|
||||
DELETE FROM mb_releases WHERE release_group_mbid = $1
|
||||
`
|
||||
|
||||
// Tracks go with their releases (ON DELETE CASCADE).
|
||||
func (q *Queries) DeleteMBReleasesForGroup(ctx context.Context, releaseGroupMbid string) error {
|
||||
_, err := q.db.Exec(ctx, deleteMBReleasesForGroup, releaseGroupMbid)
|
||||
return err
|
||||
}
|
||||
|
||||
const getMBArtist = `-- name: GetMBArtist :one
|
||||
SELECT mbid, name, sort_name, disambiguation, artist_type, country, discography_refreshed_at, created_at, updated_at FROM mb_artists WHERE mbid = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetMBArtist(ctx context.Context, mbid string) (MbArtist, error) {
|
||||
row := q.db.QueryRow(ctx, getMBArtist, mbid)
|
||||
var i MbArtist
|
||||
err := row.Scan(
|
||||
&i.Mbid,
|
||||
&i.Name,
|
||||
&i.SortName,
|
||||
&i.Disambiguation,
|
||||
&i.ArtistType,
|
||||
&i.Country,
|
||||
&i.DiscographyRefreshedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getMBReleaseGroup = `-- name: GetMBReleaseGroup :one
|
||||
SELECT mbid, title, primary_type, secondary_types, first_release_date, artist_credit, releases_refreshed_at, created_at, updated_at FROM mb_release_groups WHERE mbid = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetMBReleaseGroup(ctx context.Context, mbid string) (MbReleaseGroup, error) {
|
||||
row := q.db.QueryRow(ctx, getMBReleaseGroup, mbid)
|
||||
var i MbReleaseGroup
|
||||
err := row.Scan(
|
||||
&i.Mbid,
|
||||
&i.Title,
|
||||
&i.PrimaryType,
|
||||
&i.SecondaryTypes,
|
||||
&i.FirstReleaseDate,
|
||||
&i.ArtistCredit,
|
||||
&i.ReleasesRefreshedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertMBRelease = `-- name: InsertMBRelease :exec
|
||||
INSERT INTO mb_releases (mbid, release_group_mbid, title, status, release_date, country,
|
||||
disambiguation, formats, track_count)
|
||||
VALUES ($1, $2, $3, $4,
|
||||
$5, $6, $7, $8,
|
||||
$9)
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
release_group_mbid = EXCLUDED.release_group_mbid,
|
||||
title = EXCLUDED.title,
|
||||
status = EXCLUDED.status,
|
||||
release_date = EXCLUDED.release_date,
|
||||
country = EXCLUDED.country,
|
||||
disambiguation = EXCLUDED.disambiguation,
|
||||
formats = EXCLUDED.formats,
|
||||
track_count = EXCLUDED.track_count
|
||||
`
|
||||
|
||||
type InsertMBReleaseParams struct {
|
||||
Mbid string
|
||||
ReleaseGroupMbid string
|
||||
Title string
|
||||
Status string
|
||||
ReleaseDate string
|
||||
Country string
|
||||
Disambiguation string
|
||||
Formats string
|
||||
TrackCount int32
|
||||
}
|
||||
|
||||
func (q *Queries) InsertMBRelease(ctx context.Context, arg InsertMBReleaseParams) error {
|
||||
_, err := q.db.Exec(ctx, insertMBRelease,
|
||||
arg.Mbid,
|
||||
arg.ReleaseGroupMbid,
|
||||
arg.Title,
|
||||
arg.Status,
|
||||
arg.ReleaseDate,
|
||||
arg.Country,
|
||||
arg.Disambiguation,
|
||||
arg.Formats,
|
||||
arg.TrackCount,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertMBReleaseTrack = `-- name: InsertMBReleaseTrack :exec
|
||||
INSERT INTO mb_release_tracks (release_mbid, medium_position, position, medium_format, number,
|
||||
title, length_ms, recording_mbid)
|
||||
VALUES ($1, $2, $3,
|
||||
$4, $5, $6, $7,
|
||||
$8)
|
||||
`
|
||||
|
||||
type InsertMBReleaseTrackParams struct {
|
||||
ReleaseMbid string
|
||||
MediumPosition int32
|
||||
Position int32
|
||||
MediumFormat string
|
||||
Number string
|
||||
Title string
|
||||
LengthMs int32
|
||||
RecordingMbid string
|
||||
}
|
||||
|
||||
func (q *Queries) InsertMBReleaseTrack(ctx context.Context, arg InsertMBReleaseTrackParams) error {
|
||||
_, err := q.db.Exec(ctx, insertMBReleaseTrack,
|
||||
arg.ReleaseMbid,
|
||||
arg.MediumPosition,
|
||||
arg.Position,
|
||||
arg.MediumFormat,
|
||||
arg.Number,
|
||||
arg.Title,
|
||||
arg.LengthMs,
|
||||
arg.RecordingMbid,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const listMBArtistReleaseGroups = `-- name: ListMBArtistReleaseGroups :many
|
||||
SELECT g.mbid, g.title, g.primary_type, g.secondary_types, g.first_release_date, g.artist_credit, g.releases_refreshed_at, g.created_at, g.updated_at FROM mb_release_groups g
|
||||
JOIN mb_artist_release_groups ag ON ag.release_group_mbid = g.mbid
|
||||
WHERE ag.artist_mbid = $1
|
||||
ORDER BY g.first_release_date = '', g.first_release_date, g.title, g.mbid
|
||||
`
|
||||
|
||||
// An artist's discography, oldest first; undated groups last.
|
||||
func (q *Queries) ListMBArtistReleaseGroups(ctx context.Context, artistMbid string) ([]MbReleaseGroup, error) {
|
||||
rows, err := q.db.Query(ctx, listMBArtistReleaseGroups, artistMbid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []MbReleaseGroup
|
||||
for rows.Next() {
|
||||
var i MbReleaseGroup
|
||||
if err := rows.Scan(
|
||||
&i.Mbid,
|
||||
&i.Title,
|
||||
&i.PrimaryType,
|
||||
&i.SecondaryTypes,
|
||||
&i.FirstReleaseDate,
|
||||
&i.ArtistCredit,
|
||||
&i.ReleasesRefreshedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listMBArtistsDueForRefresh = `-- name: ListMBArtistsDueForRefresh :many
|
||||
SELECT mbid FROM mb_artists
|
||||
WHERE discography_refreshed_at IS NULL OR discography_refreshed_at < $1
|
||||
ORDER BY discography_refreshed_at NULLS FIRST, mbid
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListMBArtistsDueForRefreshParams struct {
|
||||
Before pgtype.Timestamptz
|
||||
MaxRows int32
|
||||
}
|
||||
|
||||
// Artists whose discography has never been read, or was read before the
|
||||
// cutoff: never-read first, then oldest first.
|
||||
func (q *Queries) ListMBArtistsDueForRefresh(ctx context.Context, arg ListMBArtistsDueForRefreshParams) ([]string, error) {
|
||||
rows, err := q.db.Query(ctx, listMBArtistsDueForRefresh, arg.Before, arg.MaxRows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []string
|
||||
for rows.Next() {
|
||||
var mbid string
|
||||
if err := rows.Scan(&mbid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, mbid)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listMBReleaseTracksForGroup = `-- name: ListMBReleaseTracksForGroup :many
|
||||
SELECT t.release_mbid, t.medium_position, t.position, t.medium_format, t.number, t.title, t.length_ms, t.recording_mbid FROM mb_release_tracks t
|
||||
JOIN mb_releases r ON r.mbid = t.release_mbid
|
||||
WHERE r.release_group_mbid = $1
|
||||
ORDER BY t.release_mbid, t.medium_position, t.position
|
||||
`
|
||||
|
||||
func (q *Queries) ListMBReleaseTracksForGroup(ctx context.Context, releaseGroupMbid string) ([]MbReleaseTrack, error) {
|
||||
rows, err := q.db.Query(ctx, listMBReleaseTracksForGroup, releaseGroupMbid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []MbReleaseTrack
|
||||
for rows.Next() {
|
||||
var i MbReleaseTrack
|
||||
if err := rows.Scan(
|
||||
&i.ReleaseMbid,
|
||||
&i.MediumPosition,
|
||||
&i.Position,
|
||||
&i.MediumFormat,
|
||||
&i.Number,
|
||||
&i.Title,
|
||||
&i.LengthMs,
|
||||
&i.RecordingMbid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listMBReleasesForGroup = `-- name: ListMBReleasesForGroup :many
|
||||
SELECT mbid, release_group_mbid, title, status, release_date, country, disambiguation, formats, track_count, created_at FROM mb_releases
|
||||
WHERE release_group_mbid = $1
|
||||
ORDER BY status <> 'Official', release_date = '', release_date, title, mbid
|
||||
`
|
||||
|
||||
// Official editions first, then by date; undated last.
|
||||
func (q *Queries) ListMBReleasesForGroup(ctx context.Context, releaseGroupMbid string) ([]MbRelease, error) {
|
||||
rows, err := q.db.Query(ctx, listMBReleasesForGroup, releaseGroupMbid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []MbRelease
|
||||
for rows.Next() {
|
||||
var i MbRelease
|
||||
if err := rows.Scan(
|
||||
&i.Mbid,
|
||||
&i.ReleaseGroupMbid,
|
||||
&i.Title,
|
||||
&i.Status,
|
||||
&i.ReleaseDate,
|
||||
&i.Country,
|
||||
&i.Disambiguation,
|
||||
&i.Formats,
|
||||
&i.TrackCount,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const markMBArtistDiscographyRefreshed = `-- name: MarkMBArtistDiscographyRefreshed :exec
|
||||
UPDATE mb_artists SET discography_refreshed_at = now() WHERE mbid = $1
|
||||
`
|
||||
|
||||
func (q *Queries) MarkMBArtistDiscographyRefreshed(ctx context.Context, mbid string) error {
|
||||
_, err := q.db.Exec(ctx, markMBArtistDiscographyRefreshed, mbid)
|
||||
return err
|
||||
}
|
||||
|
||||
const markMBReleaseGroupReleasesRefreshed = `-- name: MarkMBReleaseGroupReleasesRefreshed :exec
|
||||
UPDATE mb_release_groups SET releases_refreshed_at = now() WHERE mbid = $1
|
||||
`
|
||||
|
||||
func (q *Queries) MarkMBReleaseGroupReleasesRefreshed(ctx context.Context, mbid string) error {
|
||||
_, err := q.db.Exec(ctx, markMBReleaseGroupReleasesRefreshed, mbid)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertMBArtist = `-- name: UpsertMBArtist :exec
|
||||
|
||||
INSERT INTO mb_artists (mbid, name, sort_name, disambiguation, artist_type, country)
|
||||
VALUES ($1, $2, $3, $4,
|
||||
$5, $6)
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
sort_name = EXCLUDED.sort_name,
|
||||
disambiguation = EXCLUDED.disambiguation,
|
||||
artist_type = EXCLUDED.artist_type,
|
||||
country = EXCLUDED.country,
|
||||
updated_at = now()
|
||||
`
|
||||
|
||||
type UpsertMBArtistParams struct {
|
||||
Mbid string
|
||||
Name string
|
||||
SortName string
|
||||
Disambiguation string
|
||||
ArtistType string
|
||||
Country string
|
||||
}
|
||||
|
||||
// The release catalogue read from MusicBrainz (M502 #5519).
|
||||
func (q *Queries) UpsertMBArtist(ctx context.Context, arg UpsertMBArtistParams) error {
|
||||
_, err := q.db.Exec(ctx, upsertMBArtist,
|
||||
arg.Mbid,
|
||||
arg.Name,
|
||||
arg.SortName,
|
||||
arg.Disambiguation,
|
||||
arg.ArtistType,
|
||||
arg.Country,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertMBReleaseGroup = `-- name: UpsertMBReleaseGroup :exec
|
||||
INSERT INTO mb_release_groups (mbid, title, primary_type, secondary_types, first_release_date, artist_credit)
|
||||
VALUES ($1, $2, $3, $4::text[],
|
||||
$5, $6)
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
primary_type = EXCLUDED.primary_type,
|
||||
secondary_types = EXCLUDED.secondary_types,
|
||||
first_release_date = EXCLUDED.first_release_date,
|
||||
artist_credit = EXCLUDED.artist_credit,
|
||||
updated_at = now()
|
||||
`
|
||||
|
||||
type UpsertMBReleaseGroupParams struct {
|
||||
Mbid string
|
||||
Title string
|
||||
PrimaryType string
|
||||
SecondaryTypes []string
|
||||
FirstReleaseDate string
|
||||
ArtistCredit string
|
||||
}
|
||||
|
||||
// Leaves releases_refreshed_at alone: re-reading a discography says nothing
|
||||
// about whether the group's track lists are current.
|
||||
func (q *Queries) UpsertMBReleaseGroup(ctx context.Context, arg UpsertMBReleaseGroupParams) error {
|
||||
_, err := q.db.Exec(ctx, upsertMBReleaseGroup,
|
||||
arg.Mbid,
|
||||
arg.Title,
|
||||
arg.PrimaryType,
|
||||
arg.SecondaryTypes,
|
||||
arg.FirstReleaseDate,
|
||||
arg.ArtistCredit,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@@ -482,6 +482,59 @@ type LoudnessSetting struct {
|
||||
LeveledCacheMb int32
|
||||
}
|
||||
|
||||
type MbArtist struct {
|
||||
Mbid string
|
||||
Name string
|
||||
SortName string
|
||||
Disambiguation string
|
||||
ArtistType string
|
||||
Country string
|
||||
DiscographyRefreshedAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type MbArtistReleaseGroup struct {
|
||||
ArtistMbid string
|
||||
ReleaseGroupMbid string
|
||||
}
|
||||
|
||||
type MbRelease struct {
|
||||
Mbid string
|
||||
ReleaseGroupMbid string
|
||||
Title string
|
||||
Status string
|
||||
ReleaseDate string
|
||||
Country string
|
||||
Disambiguation string
|
||||
Formats string
|
||||
TrackCount int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type MbReleaseGroup struct {
|
||||
Mbid string
|
||||
Title string
|
||||
PrimaryType string
|
||||
SecondaryTypes []string
|
||||
FirstReleaseDate string
|
||||
ArtistCredit string
|
||||
ReleasesRefreshedAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type MbReleaseTrack struct {
|
||||
ReleaseMbid string
|
||||
MediumPosition int32
|
||||
Position int32
|
||||
MediumFormat string
|
||||
Number string
|
||||
Title string
|
||||
LengthMs int32
|
||||
RecordingMbid string
|
||||
}
|
||||
|
||||
type MissingReacquisition struct {
|
||||
AlbumID pgtype.UUID
|
||||
Attempts int32
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE IF EXISTS mb_release_tracks;
|
||||
DROP TABLE IF EXISTS mb_releases;
|
||||
DROP TABLE IF EXISTS mb_artist_release_groups;
|
||||
DROP TABLE IF EXISTS mb_release_groups;
|
||||
DROP TABLE IF EXISTS mb_artists;
|
||||
@@ -0,0 +1,86 @@
|
||||
-- 0080_release_catalogue.up.sql — Minstrel's own copy of what artists have
|
||||
-- released, read from MusicBrainz (Scribe milestone #502, #5519).
|
||||
--
|
||||
-- With Lidarr gone, the wanted list, search and import need to know an
|
||||
-- artist's discography and each release's track list. MusicBrainz allows one
|
||||
-- request a second, so what has been read is kept here and refreshed on a
|
||||
-- schedule rather than asked again for every page.
|
||||
--
|
||||
-- mb_artists artists the catalogue has been asked about.
|
||||
-- mb_release_groups what a person calls an album (Lidarr's albums
|
||||
-- were release groups too), with MusicBrainz's
|
||||
-- primary and secondary types.
|
||||
-- mb_artist_release_groups who is credited on each group: a collaboration
|
||||
-- belongs to every credited artist's discography.
|
||||
-- mb_releases the editions of a group, read when something
|
||||
-- needs a track list.
|
||||
-- mb_release_tracks each release's track list.
|
||||
--
|
||||
-- Dates stay as MusicBrainz's partial-date text ("1997", "1997-05",
|
||||
-- "1997-05-21"): most early releases have only a year.
|
||||
|
||||
CREATE TABLE mb_artists (
|
||||
mbid text PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
sort_name text NOT NULL DEFAULT '',
|
||||
disambiguation text NOT NULL DEFAULT '',
|
||||
artist_type text NOT NULL DEFAULT '',
|
||||
country text NOT NULL DEFAULT '',
|
||||
-- When the discography was last read in full; NULL means never.
|
||||
discography_refreshed_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX mb_artists_refresh_idx ON mb_artists (discography_refreshed_at NULLS FIRST);
|
||||
|
||||
CREATE TABLE mb_release_groups (
|
||||
mbid text PRIMARY KEY,
|
||||
title text NOT NULL,
|
||||
primary_type text NOT NULL DEFAULT '',
|
||||
secondary_types text[] NOT NULL DEFAULT '{}',
|
||||
first_release_date text NOT NULL DEFAULT '',
|
||||
artist_credit text NOT NULL DEFAULT '',
|
||||
-- When the releases and their track lists were last read; NULL means never.
|
||||
releases_refreshed_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE mb_artist_release_groups (
|
||||
artist_mbid text NOT NULL REFERENCES mb_artists (mbid) ON DELETE CASCADE,
|
||||
release_group_mbid text NOT NULL REFERENCES mb_release_groups (mbid) ON DELETE CASCADE,
|
||||
PRIMARY KEY (artist_mbid, release_group_mbid)
|
||||
);
|
||||
|
||||
CREATE INDEX mb_artist_release_groups_group_idx ON mb_artist_release_groups (release_group_mbid);
|
||||
|
||||
CREATE TABLE mb_releases (
|
||||
mbid text PRIMARY KEY,
|
||||
release_group_mbid text NOT NULL REFERENCES mb_release_groups (mbid) ON DELETE CASCADE,
|
||||
title text NOT NULL,
|
||||
status text NOT NULL DEFAULT '',
|
||||
release_date text NOT NULL DEFAULT '',
|
||||
country text NOT NULL DEFAULT '',
|
||||
disambiguation text NOT NULL DEFAULT '',
|
||||
formats text NOT NULL DEFAULT '',
|
||||
track_count integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX mb_releases_group_idx ON mb_releases (release_group_mbid);
|
||||
|
||||
CREATE TABLE mb_release_tracks (
|
||||
release_mbid text NOT NULL REFERENCES mb_releases (mbid) ON DELETE CASCADE,
|
||||
medium_position integer NOT NULL,
|
||||
position integer NOT NULL,
|
||||
medium_format text NOT NULL DEFAULT '',
|
||||
number text NOT NULL DEFAULT '',
|
||||
title text NOT NULL,
|
||||
length_ms integer NOT NULL DEFAULT 0,
|
||||
recording_mbid text NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (release_mbid, medium_position, position)
|
||||
);
|
||||
|
||||
CREATE INDEX mb_release_tracks_recording_idx ON mb_release_tracks (recording_mbid)
|
||||
WHERE recording_mbid <> '';
|
||||
@@ -0,0 +1,101 @@
|
||||
-- The release catalogue read from MusicBrainz (M502 #5519).
|
||||
|
||||
-- name: UpsertMBArtist :exec
|
||||
INSERT INTO mb_artists (mbid, name, sort_name, disambiguation, artist_type, country)
|
||||
VALUES (sqlc.arg(mbid), sqlc.arg(name), sqlc.arg(sort_name), sqlc.arg(disambiguation),
|
||||
sqlc.arg(artist_type), sqlc.arg(country))
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
sort_name = EXCLUDED.sort_name,
|
||||
disambiguation = EXCLUDED.disambiguation,
|
||||
artist_type = EXCLUDED.artist_type,
|
||||
country = EXCLUDED.country,
|
||||
updated_at = now();
|
||||
|
||||
-- name: GetMBArtist :one
|
||||
SELECT * FROM mb_artists WHERE mbid = sqlc.arg(mbid);
|
||||
|
||||
-- name: MarkMBArtistDiscographyRefreshed :exec
|
||||
UPDATE mb_artists SET discography_refreshed_at = now() WHERE mbid = sqlc.arg(mbid);
|
||||
|
||||
-- name: ListMBArtistsDueForRefresh :many
|
||||
-- Artists whose discography has never been read, or was read before the
|
||||
-- cutoff: never-read first, then oldest first.
|
||||
SELECT mbid FROM mb_artists
|
||||
WHERE discography_refreshed_at IS NULL OR discography_refreshed_at < sqlc.arg(before)
|
||||
ORDER BY discography_refreshed_at NULLS FIRST, mbid
|
||||
LIMIT sqlc.arg(max_rows);
|
||||
|
||||
-- name: UpsertMBReleaseGroup :exec
|
||||
-- Leaves releases_refreshed_at alone: re-reading a discography says nothing
|
||||
-- about whether the group's track lists are current.
|
||||
INSERT INTO mb_release_groups (mbid, title, primary_type, secondary_types, first_release_date, artist_credit)
|
||||
VALUES (sqlc.arg(mbid), sqlc.arg(title), sqlc.arg(primary_type), sqlc.arg(secondary_types)::text[],
|
||||
sqlc.arg(first_release_date), sqlc.arg(artist_credit))
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
primary_type = EXCLUDED.primary_type,
|
||||
secondary_types = EXCLUDED.secondary_types,
|
||||
first_release_date = EXCLUDED.first_release_date,
|
||||
artist_credit = EXCLUDED.artist_credit,
|
||||
updated_at = now();
|
||||
|
||||
-- name: GetMBReleaseGroup :one
|
||||
SELECT * FROM mb_release_groups WHERE mbid = sqlc.arg(mbid);
|
||||
|
||||
-- name: DeleteMBArtistReleaseGroups :exec
|
||||
DELETE FROM mb_artist_release_groups WHERE artist_mbid = sqlc.arg(artist_mbid);
|
||||
|
||||
-- name: AddMBArtistReleaseGroup :exec
|
||||
INSERT INTO mb_artist_release_groups (artist_mbid, release_group_mbid)
|
||||
VALUES (sqlc.arg(artist_mbid), sqlc.arg(release_group_mbid))
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: ListMBArtistReleaseGroups :many
|
||||
-- An artist's discography, oldest first; undated groups last.
|
||||
SELECT g.* FROM mb_release_groups g
|
||||
JOIN mb_artist_release_groups ag ON ag.release_group_mbid = g.mbid
|
||||
WHERE ag.artist_mbid = sqlc.arg(artist_mbid)
|
||||
ORDER BY g.first_release_date = '', g.first_release_date, g.title, g.mbid;
|
||||
|
||||
-- name: MarkMBReleaseGroupReleasesRefreshed :exec
|
||||
UPDATE mb_release_groups SET releases_refreshed_at = now() WHERE mbid = sqlc.arg(mbid);
|
||||
|
||||
-- name: DeleteMBReleasesForGroup :exec
|
||||
-- Tracks go with their releases (ON DELETE CASCADE).
|
||||
DELETE FROM mb_releases WHERE release_group_mbid = sqlc.arg(release_group_mbid);
|
||||
|
||||
-- name: InsertMBRelease :exec
|
||||
INSERT INTO mb_releases (mbid, release_group_mbid, title, status, release_date, country,
|
||||
disambiguation, formats, track_count)
|
||||
VALUES (sqlc.arg(mbid), sqlc.arg(release_group_mbid), sqlc.arg(title), sqlc.arg(status),
|
||||
sqlc.arg(release_date), sqlc.arg(country), sqlc.arg(disambiguation), sqlc.arg(formats),
|
||||
sqlc.arg(track_count))
|
||||
ON CONFLICT (mbid) DO UPDATE SET
|
||||
release_group_mbid = EXCLUDED.release_group_mbid,
|
||||
title = EXCLUDED.title,
|
||||
status = EXCLUDED.status,
|
||||
release_date = EXCLUDED.release_date,
|
||||
country = EXCLUDED.country,
|
||||
disambiguation = EXCLUDED.disambiguation,
|
||||
formats = EXCLUDED.formats,
|
||||
track_count = EXCLUDED.track_count;
|
||||
|
||||
-- name: InsertMBReleaseTrack :exec
|
||||
INSERT INTO mb_release_tracks (release_mbid, medium_position, position, medium_format, number,
|
||||
title, length_ms, recording_mbid)
|
||||
VALUES (sqlc.arg(release_mbid), sqlc.arg(medium_position), sqlc.arg(position),
|
||||
sqlc.arg(medium_format), sqlc.arg(number), sqlc.arg(title), sqlc.arg(length_ms),
|
||||
sqlc.arg(recording_mbid));
|
||||
|
||||
-- name: ListMBReleasesForGroup :many
|
||||
-- Official editions first, then by date; undated last.
|
||||
SELECT * FROM mb_releases
|
||||
WHERE release_group_mbid = sqlc.arg(release_group_mbid)
|
||||
ORDER BY status <> 'Official', release_date = '', release_date, title, mbid;
|
||||
|
||||
-- name: ListMBReleaseTracksForGroup :many
|
||||
SELECT t.* FROM mb_release_tracks t
|
||||
JOIN mb_releases r ON r.mbid = t.release_mbid
|
||||
WHERE r.release_group_mbid = sqlc.arg(release_group_mbid)
|
||||
ORDER BY t.release_mbid, t.medium_position, t.position;
|
||||
Reference in New Issue
Block a user