navidrome/db/migrations/20260905004449_initial_schema.sql
Deluan 99c9adc69e fix(pglite): give the initial migration a valid goose timestamp
The migration validator requires a 14-digit timestamp newer than the
newest migration on master; 00000000000001 fails both checks.
2026-09-04 20:45:22 -04:00

571 lines
26 KiB
SQL

-- +goose Up
-- Whole Navidrome schema for PostgreSQL, translated from the SQLite migrations (spike).
CREATE TABLE IF NOT EXISTS "album"
(
id text not null
primary key,
name text default '' not null,
embed_art_path text default '' not null,
album_artist text default '' not null,
min_year int default 0 not null,
max_year integer default 0 not null,
compilation boolean default FALSE not null,
song_count integer default 0 not null,
duration double precision default 0 not null,
genre text default '' not null,
created_at timestamp,
updated_at timestamp,
full_text text default '',
album_artist_id text default '',
size bigint default 0 not null,
description text default '' not null,
small_image_url text default '' not null,
medium_image_url text default '' not null,
large_image_url text default '' not null,
external_url text default '' not null,
external_info_updated_at timestamp,
date text default '' not null,
min_original_year int default 0 not null,
max_original_year int default 0 not null,
original_date text default '' not null,
release_date text default '' not null,
releases integer default 0 not null,
order_album_name text default '' not null,
order_album_artist_name text default '' not null,
sort_album_name text default '' not null,
sort_album_artist_name text default '' not null,
catalog_num text default '' not null,
comment text default '' not null,
mbz_album_id text default '' not null,
mbz_album_artist_id text default '' not null,
mbz_album_type text default '' not null,
mbz_album_comment text default '' not null,
discs jsonb default '{}' not null,
library_id integer default 1 not null
, imported_at timestamp default '0001-01-01 00:00:00' not null, missing boolean default false not null, mbz_release_group_id text default '' not null, tags jsonb default '{}' not null, participants jsonb default '{}' not null, folder_ids jsonb default '[]' not null, explicit_status text default '' not null, average_rating double precision NOT NULL DEFAULT 0, search_participants TEXT NOT NULL DEFAULT '', search_normalized TEXT NOT NULL DEFAULT '', rg_album_gain double precision, rg_album_peak double precision);
CREATE TABLE IF NOT EXISTS "album_artists"
(
album_id text not null
,
artist_id text not null
,
role text default '' not null,
sub_role text default '' not null,
CONSTRAINT album_artists_uq UNIQUE (artist_id, album_id, role, sub_role)
);
CREATE TABLE IF NOT EXISTS album_tags(
album_id text not null
,
tag_id text not null
,
CONSTRAINT album_tags_uq UNIQUE (album_id, tag_id)
);
CREATE TABLE IF NOT EXISTS "annotation"
(
user_id text not null
,
item_id text default '' not null,
item_type text default '' not null,
play_count integer default 0,
play_date timestamp,
rating integer default 0,
starred boolean default FALSE not null,
starred_at timestamp, rated_at timestamp,
unique (user_id, item_id, item_type)
);
CREATE TABLE IF NOT EXISTS "artist"
(
id text not null
primary key,
name text default '' not null,
full_text text default '',
biography text default '' not null,
small_image_url text default '' not null,
medium_image_url text default '' not null,
large_image_url text default '' not null,
external_url text default '' not null,
external_info_updated_at timestamp,
order_artist_name text default '' not null,
sort_artist_name text default '' not null,
mbz_artist_id text default '' not null
, missing boolean default false not null, similar_artists jsonb default '[]' not null, updated_at timestamp default current_timestamp not null, created_at timestamp default current_timestamp not null, average_rating double precision NOT NULL DEFAULT 0, search_normalized TEXT NOT NULL DEFAULT '', uploaded_image text DEFAULT '');
CREATE TABLE IF NOT EXISTS artwork (
hash TEXT PRIMARY KEY,
mime TEXT NOT NULL,
width INTEGER NOT NULL DEFAULT 0,
height INTEGER NOT NULL DEFAULT 0,
size_bytes INTEGER NOT NULL DEFAULT 0,
blur_hash TEXT NOT NULL DEFAULT '',
thumb_hash TEXT NOT NULL DEFAULT '',
dominant_color TEXT NOT NULL DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS artwork_queue (
item_kind TEXT NOT NULL,
item_id TEXT NOT NULL,
image_type TEXT NOT NULL DEFAULT 'primary',
priority INTEGER NOT NULL DEFAULT 0,
attempts INTEGER NOT NULL DEFAULT 0,
retry_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
enqueued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, trace text NOT NULL DEFAULT '[]',
PRIMARY KEY (item_kind, item_id, image_type)
);
CREATE TABLE IF NOT EXISTS bookmark
(
user_id text not null
,
item_id text not null,
item_type text not null,
comment text,
position integer,
changed_by text,
created_at timestamp,
updated_at timestamp,
CONSTRAINT bookmark_pk_uq UNIQUE (user_id, item_id, item_type)
);
CREATE TABLE IF NOT EXISTS folder(
id text not null
primary key,
library_id integer not null
,
path text default '' not null,
name text default '' not null,
missing boolean default false not null,
parent_id text default '' not null,
num_audio_files integer default 0 not null,
num_playlists integer default 0 not null,
image_files jsonb default '[]' not null,
images_updated_at timestamp default '0001-01-01 00:00:00' not null,
updated_at timestamp default now() not null,
created_at timestamp default now() not null
, hash text default '' not null);
CREATE TABLE IF NOT EXISTS item_artwork (
item_kind TEXT NOT NULL,
item_id TEXT NOT NULL,
image_type TEXT NOT NULL DEFAULT 'primary',
hash TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
source_path TEXT NOT NULL DEFAULT '',
ref_mtime BIGINT NOT NULL DEFAULT 0,
attempted_at TIMESTAMP,
updated_at TIMESTAMP, trace text NOT NULL DEFAULT '[]', last_failure text NOT NULL DEFAULT '[]',
PRIMARY KEY (item_kind, item_id, image_type)
);
CREATE TABLE IF NOT EXISTS library (
id serial primary key,
name text not null unique,
path text not null unique,
remote_path text null default '',
last_scan_at timestamp not null default '0001-01-01 00:00:00',
updated_at timestamp not null default current_timestamp,
created_at timestamp not null default current_timestamp
, last_scan_started_at timestamp default '0001-01-01 00:00:00' not null, full_scan_in_progress boolean default false not null, total_songs integer default 0 not null, total_albums integer default 0 not null, total_artists integer default 0 not null, total_folders integer default 0 not null, total_files integer default 0 not null, total_missing_files integer default 0 not null, total_size integer default 0 not null, total_duration double precision DEFAULT 0, default_new_users boolean DEFAULT false);
CREATE TABLE IF NOT EXISTS "library_artist"
(
library_id integer NOT NULL DEFAULT 1
,
artist_id text NOT NULL
,
stats jsonb DEFAULT '{}'::jsonb,
CONSTRAINT library_artist_ux_uq UNIQUE (library_id, artist_id)
);
CREATE TABLE IF NOT EXISTS library_tag (
tag_id text NOT NULL,
library_id INTEGER NOT NULL,
album_count INTEGER DEFAULT 0 NOT NULL,
media_file_count INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY (tag_id, library_id));
CREATE TABLE IF NOT EXISTS "media_file"
(
id text not null
primary key,
path text default '' not null,
title text default '' not null,
album text default '' not null,
artist text default '' not null,
artist_id text default '' not null,
album_artist text default '' not null,
album_id text default '' not null,
has_cover_art boolean default FALSE not null,
track_number integer default 0 not null,
disc_number integer default 0 not null,
year integer default 0 not null,
size bigint default 0 not null,
suffix text default '' not null,
duration double precision default 0 not null,
bit_rate integer default 0 not null,
genre text default '' not null,
compilation boolean default FALSE not null,
created_at timestamp,
updated_at timestamp,
full_text text default '',
album_artist_id text default '',
date text default '' not null,
original_year int default 0 not null,
original_date text default '' not null,
release_year int default 0 not null,
release_date text default '' not null,
order_album_name text default '' not null,
order_album_artist_name text default '' not null,
order_artist_name text default '' not null,
sort_album_name text default '' not null,
sort_artist_name text default '' not null,
sort_album_artist_name text default '' not null,
sort_title text default '' not null,
disc_subtitle text default '' not null,
catalog_num text default '' not null,
comment text default '' not null,
order_title text default '' not null,
mbz_recording_id text default '' not null,
mbz_album_id text default '' not null,
mbz_artist_id text default '' not null,
mbz_album_artist_id text default '' not null,
mbz_album_type text default '' not null,
mbz_album_comment text default '' not null,
mbz_release_track_id text default '' not null,
channels integer default 0 not null,
lyrics jsonb default '[]' not null,
sample_rate integer default 0 not null,
library_id integer default 1 not null
, folder_id text default '' not null, pid text default '' not null, missing boolean default false not null, mbz_release_group_id text default '' not null, tags jsonb default '{}' not null, participants jsonb default '{}' not null, explicit_status text default '' not null, birth_time timestamp default current_timestamp not null, rg_album_gain double precision, rg_album_peak double precision, rg_track_gain double precision, rg_track_peak double precision, average_rating double precision NOT NULL DEFAULT 0, search_participants TEXT NOT NULL DEFAULT '', search_normalized TEXT NOT NULL DEFAULT '', codec text DEFAULT '' NOT NULL, probe_data TEXT DEFAULT '' NOT NULL, bpm integer, bit_depth integer);
CREATE TABLE IF NOT EXISTS media_file_artists(
media_file_id text not null
,
artist_id text not null
,
role text default '' not null,
sub_role text default '' not null,
CONSTRAINT artist_tracks_uq UNIQUE (artist_id, media_file_id, role, sub_role)
);
CREATE TABLE IF NOT EXISTS media_file_tags(
media_file_id text not null
,
tag_id text not null
,
CONSTRAINT media_file_tags_uq UNIQUE (media_file_id, tag_id)
);
CREATE TABLE IF NOT EXISTS "player"
(
id text not null
primary key,
name text not null,
user_agent text,
user_id text not null
,
client text not null,
ip text,
last_seen timestamp,
max_bit_rate int default 0,
transcoding_id text,
report_real_path boolean default FALSE not null,
scrobble_enabled boolean default true
);
CREATE TABLE IF NOT EXISTS "playlist"
(
id text not null
primary key,
name text default '' not null,
comment text default '' not null,
duration double precision default 0 not null,
song_count integer default 0 not null,
public boolean default FALSE not null,
created_at timestamp,
updated_at timestamp,
path text default '' not null,
sync boolean default false not null,
size bigint default 0 not null,
rules text,
evaluated_at timestamp,
owner_id text not null
, uploaded_image text DEFAULT '', external_image_url text DEFAULT '', average_rating double precision NOT NULL DEFAULT 0, imported_hash text default '' not null);
CREATE TABLE IF NOT EXISTS playlist_fields (
field text not null,
playlist_id text not null
);
CREATE TABLE IF NOT EXISTS "playlist_tracks"
(
id integer default 0 not null,
playlist_id text not null
,
media_file_id text not null
);
CREATE TABLE IF NOT EXISTS "playqueue"(
id text not null,
user_id text not null
,
current integer not null default 0,
changed_by text,
items text,
created_at timestamp,
updated_at timestamp
, position integer);
CREATE TABLE IF NOT EXISTS plugin (
id TEXT PRIMARY KEY,
path TEXT NOT NULL,
manifest JSONB NOT NULL,
config JSONB,
users JSONB,
all_users boolean NOT NULL DEFAULT false,
libraries JSONB,
all_libraries boolean NOT NULL DEFAULT false,
enabled boolean NOT NULL DEFAULT false,
last_error TEXT,
sha256 TEXT NOT NULL,
created_at timestamp NOT NULL,
updated_at timestamp NOT NULL
, allow_write_access boolean NOT NULL DEFAULT false);
CREATE TABLE IF NOT EXISTS property
(
id text not null
primary key,
value text default '' not null
);
CREATE TABLE IF NOT EXISTS "radio"
(
id text not null
primary key,
name text not null
unique,
stream_url text not null,
home_page_url text default '' not null,
created_at timestamp,
updated_at timestamp
, uploaded_image text NOT NULL DEFAULT '');
CREATE TABLE IF NOT EXISTS "scrobble_buffer"
(
user_id text NOT NULL
,
service text NOT NULL,
media_file_id text NOT NULL
,
play_time timestamp NOT NULL,
enqueue_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
id text NOT NULL DEFAULT '',
CONSTRAINT scrobble_buffer_pk_uq UNIQUE (user_id, service, media_file_id, play_time)
);
CREATE TABLE IF NOT EXISTS "scrobbles"(
id INTEGER PRIMARY KEY,
media_file_id text NOT NULL
,
user_id text NOT NULL
,
submission_time INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "share"
(
id text not null
primary key,
expires_at timestamp,
last_visited_at timestamp,
resource_ids text not null,
created_at timestamp,
updated_at timestamp,
user_id text not null
,
downloadable boolean not null default false,
description text not null default '',
resource_type text not null default '',
contents text not null default '',
format text not null default '',
max_bit_rate integer not null default 0,
visit_count integer not null default 0
);
CREATE TABLE IF NOT EXISTS tag(
id text not null primary key,
tag_name text default '' not null,
tag_value text default '' not null,
CONSTRAINT tags_name_value_uq UNIQUE (tag_name, tag_value)
);
CREATE TABLE IF NOT EXISTS transcoding
(
id text not null primary key,
name text not null,
target_format text not null,
command text default '' not null,
default_bit_rate int default 192,
unique (name),
unique (target_format)
);
CREATE TABLE IF NOT EXISTS user_account
(
id text not null
primary key,
user_name text default '' not null
unique,
name text default '' not null,
email text default '' not null,
password text default '' not null,
is_admin boolean default FALSE not null,
last_login_at timestamp,
last_access_at timestamp,
created_at timestamp not null,
updated_at timestamp not null
, scrobble_filter text default '' not null, token_epoch INTEGER NOT NULL DEFAULT 0);
CREATE TABLE IF NOT EXISTS user_library (
user_id text NOT NULL,
library_id INTEGER NOT NULL,
PRIMARY KEY (user_id, library_id));
CREATE TABLE IF NOT EXISTS "user_props"
(
user_id text not null
,
key text not null,
value text,
constraint user_props_pk
primary key (user_id, key)
);
CREATE INDEX IF NOT EXISTS album_alphabetical_by_artist
on album (compilation, order_album_artist_name, order_album_name);
CREATE INDEX IF NOT EXISTS album_artist_album_id
on album (album_artist_id);
CREATE INDEX IF NOT EXISTS album_artists_album_id
ON album_artists (album_id);
CREATE INDEX IF NOT EXISTS album_artists_role
ON album_artists (role);
CREATE INDEX IF NOT EXISTS album_created_at ON album(created_at, id);
CREATE INDEX IF NOT EXISTS album_genre
on album (genre);
CREATE INDEX IF NOT EXISTS album_imported_at
on album (imported_at);
CREATE INDEX IF NOT EXISTS album_max_year
on album (max_year);
CREATE INDEX IF NOT EXISTS album_mbz_album_id
on album (mbz_album_id);
CREATE INDEX IF NOT EXISTS album_mbz_album_type
on album (mbz_album_type);
CREATE INDEX IF NOT EXISTS album_mbz_release_group_id
on album (mbz_release_group_id);
CREATE INDEX IF NOT EXISTS album_min_year
on album (min_year);
CREATE INDEX IF NOT EXISTS album_name
on album (name);
CREATE INDEX IF NOT EXISTS album_order_album_artist_name
on album (order_album_artist_name);
CREATE INDEX IF NOT EXISTS album_order_album_name
on album (order_album_name);
CREATE INDEX IF NOT EXISTS album_size
on album (size);
CREATE INDEX IF NOT EXISTS album_sort_album_artist_name
on album (coalesce(nullif(sort_album_artist_name,''),order_album_artist_name) );
CREATE INDEX IF NOT EXISTS album_sort_name
on album (coalesce(nullif(sort_album_name,''),order_album_name) );
CREATE INDEX IF NOT EXISTS album_tags_tag_id on album_tags (tag_id);
CREATE INDEX IF NOT EXISTS album_updated_at ON album(updated_at, id);
CREATE INDEX IF NOT EXISTS annotation_play_count
on annotation (play_count);
CREATE INDEX IF NOT EXISTS annotation_play_date
on annotation (play_date);
CREATE INDEX IF NOT EXISTS annotation_rating
on annotation (rating);
CREATE INDEX IF NOT EXISTS annotation_starred
on annotation (starred);
CREATE INDEX IF NOT EXISTS annotation_starred_at
on annotation (starred_at);
CREATE INDEX IF NOT EXISTS artist_mbz_artist_id
on artist (mbz_artist_id);
CREATE INDEX IF NOT EXISTS artist_name
on artist (name);
CREATE INDEX IF NOT EXISTS artist_order_artist_name
on artist (order_artist_name);
CREATE INDEX IF NOT EXISTS artist_sort_name
on artist (coalesce(nullif(sort_artist_name,''),order_artist_name) );
CREATE INDEX IF NOT EXISTS artist_updated_at on artist (updated_at);
CREATE INDEX IF NOT EXISTS folder_parent_id on folder(parent_id);
CREATE INDEX IF NOT EXISTS idx_library_tag_library_id ON library_tag(library_id);
CREATE INDEX IF NOT EXISTS idx_library_tag_tag_id ON library_tag(tag_id);
CREATE INDEX IF NOT EXISTS idx_user_library_library_id ON user_library(library_id);
CREATE INDEX IF NOT EXISTS idx_user_library_user_id ON user_library(user_id);
CREATE INDEX IF NOT EXISTS ix_artwork_queue_drain ON artwork_queue(item_kind, priority DESC, enqueued_at, retry_at);
CREATE INDEX IF NOT EXISTS ix_item_artwork_hash ON item_artwork(hash);
CREATE INDEX IF NOT EXISTS media_file_album_artist_sort
on media_file(order_album_artist_name, order_album_name, release_date, disc_number, track_number);
CREATE INDEX IF NOT EXISTS media_file_album_id
on media_file (album_id);
CREATE INDEX IF NOT EXISTS media_file_album_sort
on media_file(order_album_name, album_id, disc_number, track_number, order_artist_name, title);
CREATE INDEX IF NOT EXISTS media_file_artist_album_id
on media_file (album_artist_id);
CREATE INDEX IF NOT EXISTS media_file_artist_id
on media_file (artist_id);
CREATE INDEX IF NOT EXISTS media_file_artist_sort
on media_file(order_artist_name, order_album_name, release_date, disc_number, track_number);
CREATE INDEX IF NOT EXISTS media_file_artists_media_file_id_role
ON media_file_artists (media_file_id, role);
CREATE INDEX IF NOT EXISTS media_file_artists_role
on media_file_artists (role);
CREATE INDEX IF NOT EXISTS media_file_bpm on media_file (bpm);
CREATE INDEX IF NOT EXISTS media_file_channels
on media_file (channels);
CREATE INDEX IF NOT EXISTS media_file_codec ON media_file(codec);
CREATE INDEX IF NOT EXISTS media_file_created_at ON media_file(created_at, id);
CREATE INDEX IF NOT EXISTS media_file_duration
on media_file (duration);
CREATE INDEX IF NOT EXISTS media_file_folder_id
on media_file (folder_id);
CREATE INDEX IF NOT EXISTS media_file_genre
on media_file (genre);
CREATE INDEX IF NOT EXISTS media_file_mbz_release_track_id
on media_file (mbz_release_track_id);
CREATE INDEX IF NOT EXISTS media_file_mbz_track_id
on media_file (mbz_recording_id);
CREATE INDEX IF NOT EXISTS media_file_missing_library_id
on media_file(missing, library_id);
CREATE INDEX IF NOT EXISTS media_file_missing_library_order_title
on media_file(missing, library_id, order_title, id);
CREATE INDEX IF NOT EXISTS media_file_order_title
on media_file (order_title);
CREATE INDEX IF NOT EXISTS media_file_path
on media_file (path);
CREATE INDEX IF NOT EXISTS media_file_path_nocase
on media_file (path );
CREATE INDEX IF NOT EXISTS media_file_pid
on media_file (pid);
CREATE INDEX IF NOT EXISTS media_file_sample_rate
on media_file (sample_rate);
CREATE INDEX IF NOT EXISTS media_file_tags_tag_id on media_file_tags (tag_id);
CREATE INDEX IF NOT EXISTS media_file_title
on media_file (title);
CREATE INDEX IF NOT EXISTS media_file_track_number
on media_file (disc_number, track_number);
CREATE INDEX IF NOT EXISTS media_file_updated_at ON media_file(updated_at, id);
CREATE INDEX IF NOT EXISTS media_file_year
on media_file (year);
CREATE INDEX IF NOT EXISTS player_match
on player (client, user_agent, user_id);
CREATE INDEX IF NOT EXISTS player_name
on player (name);
CREATE INDEX IF NOT EXISTS playlist_created_at
on playlist (created_at);
CREATE INDEX IF NOT EXISTS playlist_evaluated_at
on playlist (evaluated_at);
CREATE UNIQUE INDEX IF NOT EXISTS playlist_fields_idx
on playlist_fields (field, playlist_id);
CREATE INDEX IF NOT EXISTS playlist_name
on playlist (name);
CREATE INDEX IF NOT EXISTS playlist_size
on playlist (size);
CREATE UNIQUE INDEX IF NOT EXISTS playlist_tracks_pos
on playlist_tracks (playlist_id, id);
CREATE INDEX IF NOT EXISTS playlist_updated_at
on playlist (updated_at);
CREATE INDEX IF NOT EXISTS radio_name
on radio(name);
CREATE UNIQUE INDEX IF NOT EXISTS scrobble_buffer_id_ix ON scrobble_buffer (id);
CREATE INDEX IF NOT EXISTS scrobbles_user_time ON scrobbles(user_id, submission_time);
CREATE INDEX IF NOT EXISTS user_username_password
on user_account(user_name , password);
-- Seed rows the old migrations used to create.
INSERT INTO library (id, name, path) VALUES (1, 'Music Library', '');
SELECT setval(pg_get_serial_sequence('library', 'id'), 1);
INSERT INTO transcoding (id, name, target_format, default_bit_rate, command) VALUES
('mp3', 'mp3 audio', 'mp3', 192, 'ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -f mp3 -'),
('opus', 'opus audio', 'opus', 128, 'ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:a libopus -f opus -'),
('aac', 'aac audio', 'aac', 256, 'ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -b:a %bk -v 0 -c:a aac -f adts -'),
('flac', 'flac audio', 'flac', 0, 'ffmpeg -ss %t -i %s -map 0:a:0 -map_metadata 0 -map_metadata 0:s:a:0 -v 0 -c:a flac -f flac -');
-- +goose Down
-- not supported