mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-11 11:57:12 +02:00
Compare commits
36 commits
master
...
pglite-spi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d4f667508 | ||
|
|
bf3af008d6 | ||
|
|
943beae278 | ||
|
|
1818f4395b | ||
|
|
02b13f0549 | ||
|
|
5e3dc77f1d | ||
|
|
0628dd61ce | ||
|
|
088a4131b8 | ||
|
|
ba088e7b14 | ||
|
|
61f7934a3b | ||
|
|
01cb20817c | ||
|
|
647aaf1d21 | ||
|
|
1b457784d9 | ||
|
|
d3a1395896 | ||
|
|
b5dc2e43b6 | ||
|
|
d66aa23f88 | ||
|
|
184c4ad040 | ||
|
|
8eb9205dc8 | ||
|
|
a21c4c92fc | ||
|
|
0cf80707ac | ||
|
|
f4effde18e | ||
|
|
ba16ab4158 | ||
|
|
75e14f14d6 | ||
|
|
63e7fe0925 | ||
|
|
7922e93c75 | ||
|
|
ec37e23718 | ||
|
|
9ff44a6854 | ||
|
|
e2c8a1930a | ||
|
|
200783866b | ||
|
|
a528de445d | ||
|
|
4da3f3510b | ||
|
|
99c9adc69e | ||
|
|
1fd3261f7c | ||
|
|
0c34d1845b | ||
|
|
65f6bc2526 | ||
|
|
98555ca499 |
179 changed files with 3006 additions and 8253 deletions
201
.github/workflows/pipeline.yml
vendored
201
.github/workflows/pipeline.yml
vendored
|
|
@ -139,15 +139,10 @@ jobs:
|
||||||
|
|
||||||
# Name must stay unique across the workflow: octocov matches step names
|
# Name must stay unique across the workflow: octocov matches step names
|
||||||
# by name across every job, and waits for each match to finish.
|
# by name across every job, and waits for each match to finish.
|
||||||
|
# Spike branch: the SQLite-backed suites cannot run, so only conf/ is tested here to keep
|
||||||
|
# the coverage artifact (and the coverage workflow) alive.
|
||||||
- name: Test with coverage
|
- name: Test with coverage
|
||||||
run: go test -shuffle=on -tags netgo,sqlite_fts5 -race -v -covermode=atomic -coverprofile=coverage.out $(go list ./... | grep -v '/plugins$')
|
run: go test -shuffle=on -tags netgo,sqlite_fts5 -race -v -covermode=atomic -coverprofile=coverage.out ./conf/
|
||||||
|
|
||||||
- name: Test ndpgen
|
|
||||||
run: |
|
|
||||||
cd plugins/cmd/ndpgen
|
|
||||||
go test -shuffle=on -v
|
|
||||||
go build -o ndpgen .
|
|
||||||
./ndpgen --help
|
|
||||||
|
|
||||||
- name: Upload coverage profile
|
- name: Upload coverage profile
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
|
|
@ -156,41 +151,42 @@ jobs:
|
||||||
path: coverage.out
|
path: coverage.out
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
go-plugins:
|
# go-plugins: disabled on the pglite spike branch (the SQLite test suites do not run)
|
||||||
name: Test Go plugins
|
# go-plugins:
|
||||||
runs-on: ubuntu-latest
|
# name: Test Go plugins
|
||||||
steps:
|
# runs-on: ubuntu-latest
|
||||||
- name: Check out code into the Go module directory
|
# steps:
|
||||||
uses: actions/checkout@v7
|
# - name: Check out code into the Go module directory
|
||||||
|
# uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
# - uses: actions/setup-go@v6
|
||||||
id: setup-go
|
# id: setup-go
|
||||||
with:
|
# with:
|
||||||
go-version-file: go.mod
|
# go-version-file: go.mod
|
||||||
|
|
||||||
# Without this, the suite recompiles every test plugin WASM module,
|
# # Without this, the suite recompiles every test plugin WASM module,
|
||||||
# which dominates its runtime under -race.
|
# # which dominates its runtime under -race.
|
||||||
- name: Cache the WASM compilation cache
|
# - name: Cache the WASM compilation cache
|
||||||
uses: actions/cache@v6
|
# uses: actions/cache@v6
|
||||||
with:
|
# with:
|
||||||
path: plugins/testdata/.wazero-cache
|
# path: plugins/testdata/.wazero-cache
|
||||||
key: wazero-${{ runner.os }}-go${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('plugins/testdata/*/*.go', 'plugins/testdata/*/go.*', 'plugins/pdk/go/**/*.go', 'plugins/pdk/go/go.*') }}
|
# key: wazero-${{ runner.os }}-go${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('plugins/testdata/*/*.go', 'plugins/testdata/*/go.*', 'plugins/pdk/go/**/*.go', 'plugins/pdk/go/go.*') }}
|
||||||
restore-keys: wazero-${{ runner.os }}-
|
# restore-keys: wazero-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Test plugins
|
# - name: Test plugins
|
||||||
run: go tool ginkgo -p -race -tags netgo,sqlite_fts5 --cover --covermode=atomic --coverprofile=coverage.out --output-dir=. ./plugins/
|
# run: go tool ginkgo -p -race -tags netgo,sqlite_fts5 --cover --covermode=atomic --coverprofile=coverage.out --output-dir=. ./plugins/
|
||||||
|
|
||||||
- name: Upload coverage profile
|
# - name: Upload coverage profile
|
||||||
uses: actions/upload-artifact@v7
|
# uses: actions/upload-artifact@v7
|
||||||
with:
|
# with:
|
||||||
name: octocov-plugins
|
# name: octocov-plugins
|
||||||
path: coverage.out
|
# path: coverage.out
|
||||||
if-no-files-found: error
|
# if-no-files-found: error
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
name: Report coverage
|
name: Report coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [go, go-plugins]
|
needs: [go] # go-plugins disabled on this branch
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: write
|
actions: write
|
||||||
|
|
@ -227,78 +223,79 @@ jobs:
|
||||||
pr_number
|
pr_number
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
go-windows:
|
# go-windows: disabled on the pglite spike branch (the SQLite test suites do not run)
|
||||||
name: Test Go code (Windows)
|
# go-windows:
|
||||||
runs-on: windows-2022
|
# name: Test Go code (Windows)
|
||||||
env:
|
# runs-on: windows-2022
|
||||||
FFMPEG_VERSION: "7.1"
|
# env:
|
||||||
FFMPEG_REPOSITORY: navidrome/ffmpeg-windows-builds
|
# FFMPEG_VERSION: "7.1"
|
||||||
steps:
|
# FFMPEG_REPOSITORY: navidrome/ffmpeg-windows-builds
|
||||||
- uses: actions/checkout@v7
|
# steps:
|
||||||
|
# - uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
# - uses: actions/setup-go@v6
|
||||||
with:
|
# with:
|
||||||
go-version-file: go.mod
|
# go-version-file: go.mod
|
||||||
|
|
||||||
- uses: msys2/setup-msys2@v2
|
# - uses: msys2/setup-msys2@v2
|
||||||
with:
|
# with:
|
||||||
msystem: MINGW64
|
# msystem: MINGW64
|
||||||
install: mingw-w64-x86_64-gcc
|
# install: mingw-w64-x86_64-gcc
|
||||||
update: false
|
# update: false
|
||||||
|
|
||||||
- name: Add mingw64 to PATH
|
# - name: Add mingw64 to PATH
|
||||||
shell: bash
|
# shell: bash
|
||||||
run: echo "C:/msys64/mingw64/bin" >> $GITHUB_PATH
|
# run: echo "C:/msys64/mingw64/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Cache ffmpeg
|
# - name: Cache ffmpeg
|
||||||
id: ffmpeg-cache
|
# id: ffmpeg-cache
|
||||||
uses: actions/cache@v6
|
# uses: actions/cache@v6
|
||||||
with:
|
# with:
|
||||||
path: C:\ffmpeg
|
# path: C:\ffmpeg
|
||||||
key: ffmpeg-${{ env.FFMPEG_VERSION }}-win64
|
# key: ffmpeg-${{ env.FFMPEG_VERSION }}-win64
|
||||||
|
|
||||||
- name: Download ffmpeg
|
# - name: Download ffmpeg
|
||||||
if: steps.ffmpeg-cache.outputs.cache-hit != 'true'
|
# if: steps.ffmpeg-cache.outputs.cache-hit != 'true'
|
||||||
shell: pwsh
|
# shell: pwsh
|
||||||
run: |
|
# run: |
|
||||||
$asset = "ffmpeg-n${env:FFMPEG_VERSION}-latest-win64-gpl-${env:FFMPEG_VERSION}"
|
# $asset = "ffmpeg-n${env:FFMPEG_VERSION}-latest-win64-gpl-${env:FFMPEG_VERSION}"
|
||||||
$url = "https://github.com/${env:FFMPEG_REPOSITORY}/releases/download/latest/$asset.zip"
|
# $url = "https://github.com/${env:FFMPEG_REPOSITORY}/releases/download/latest/$asset.zip"
|
||||||
Invoke-WebRequest -Uri $url -OutFile ffmpeg.zip
|
# Invoke-WebRequest -Uri $url -OutFile ffmpeg.zip
|
||||||
Expand-Archive ffmpeg.zip -DestinationPath C:\ffmpeg-extracted
|
# Expand-Archive ffmpeg.zip -DestinationPath C:\ffmpeg-extracted
|
||||||
New-Item -ItemType Directory -Force -Path C:\ffmpeg\bin | Out-Null
|
# New-Item -ItemType Directory -Force -Path C:\ffmpeg\bin | Out-Null
|
||||||
Copy-Item "C:\ffmpeg-extracted\$asset\bin\ffmpeg.exe" C:\ffmpeg\bin
|
# Copy-Item "C:\ffmpeg-extracted\$asset\bin\ffmpeg.exe" C:\ffmpeg\bin
|
||||||
Copy-Item "C:\ffmpeg-extracted\$asset\bin\ffprobe.exe" C:\ffmpeg\bin
|
# Copy-Item "C:\ffmpeg-extracted\$asset\bin\ffprobe.exe" C:\ffmpeg\bin
|
||||||
|
|
||||||
- name: Add ffmpeg to PATH
|
# - name: Add ffmpeg to PATH
|
||||||
shell: bash
|
# shell: bash
|
||||||
run: echo "C:/ffmpeg/bin" >> $GITHUB_PATH
|
# run: echo "C:/ffmpeg/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Verify toolchain
|
# - name: Verify toolchain
|
||||||
shell: pwsh
|
# shell: pwsh
|
||||||
run: |
|
# run: |
|
||||||
go version
|
# go version
|
||||||
where.exe gcc
|
# where.exe gcc
|
||||||
gcc --version
|
# gcc --version
|
||||||
ffmpeg -version
|
# ffmpeg -version
|
||||||
ffprobe -version
|
# ffprobe -version
|
||||||
|
|
||||||
- name: Download dependencies
|
# - name: Download dependencies
|
||||||
shell: bash
|
# shell: bash
|
||||||
run: go mod download
|
# run: go mod download
|
||||||
|
|
||||||
- name: Test
|
# - name: Test
|
||||||
shell: bash
|
# shell: bash
|
||||||
env:
|
# env:
|
||||||
CGO_ENABLED: "1"
|
# CGO_ENABLED: "1"
|
||||||
run: go test -shuffle=on -tags netgo,sqlite_fts5 ./... -v
|
# run: go test -shuffle=on -tags netgo,sqlite_fts5 ./... -v
|
||||||
|
|
||||||
- name: Test ndpgen
|
# - name: Test ndpgen
|
||||||
shell: bash
|
# shell: bash
|
||||||
run: |
|
# run: |
|
||||||
cd plugins/cmd/ndpgen
|
# cd plugins/cmd/ndpgen
|
||||||
go test -shuffle=on -v
|
# go test -shuffle=on -v
|
||||||
go build -o ndpgen.exe .
|
# go build -o ndpgen.exe .
|
||||||
./ndpgen.exe --help
|
# ./ndpgen.exe --help
|
||||||
|
|
||||||
js:
|
js:
|
||||||
name: Test JS code
|
name: Test JS code
|
||||||
|
|
@ -323,10 +320,10 @@ jobs:
|
||||||
cd ui
|
cd ui
|
||||||
npm run check-formatting && npm run lint
|
npm run check-formatting && npm run lint
|
||||||
|
|
||||||
- name: npm test
|
# - name: npm test
|
||||||
run: |
|
# run: |
|
||||||
cd ui
|
# cd ui
|
||||||
npm test
|
# npm test
|
||||||
|
|
||||||
- name: npm build
|
- name: npm build
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -364,7 +361,7 @@ jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
needs: [js, go, go-plugins, go-windows, go-lint, i18n-lint, git-version, check-push-enabled, validate-migrations]
|
needs: [js, go-lint, i18n-lint, git-version, check-push-enabled, validate-migrations] # go, go-plugins, go-windows disabled on this branch
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ linux/amd64, linux/arm64, linux/arm/v5, linux/arm/v6, linux/arm/v7, linux/386, linux/riscv64, darwin/amd64, darwin/arm64, windows/amd64, windows/386 ]
|
platform: [ linux/amd64, linux/arm64, linux/arm/v5, linux/arm/v6, linux/arm/v7, linux/386, linux/riscv64, darwin/amd64, darwin/arm64, windows/amd64, windows/386 ]
|
||||||
|
|
|
||||||
|
|
@ -399,8 +399,10 @@ func Load(noConfigDump bool) {
|
||||||
|
|
||||||
Server.ConfigFile = viper.GetViper().ConfigFileUsed()
|
Server.ConfigFile = viper.GetViper().ConfigFileUsed()
|
||||||
if Server.DbPath == "" {
|
if Server.DbPath == "" {
|
||||||
Server.DbPath = filepath.Join(Server.DataFolder.String(), consts.DefaultDbPath)
|
Server.DbPath = "pglite://" + filepath.Join(Server.DataFolder.String(), "pglite")
|
||||||
}
|
}
|
||||||
|
// The embedded database lives in this process; a scanner subprocess cannot open it too.
|
||||||
|
Server.DevExternalScanner = false
|
||||||
|
|
||||||
out := os.Stderr
|
out := os.Stderr
|
||||||
if Server.LogFile != "" {
|
if Server.LogFile != "" {
|
||||||
|
|
@ -996,7 +998,7 @@ func setViperDefaults() {
|
||||||
viper.SetDefault("autotranscodedownload", false)
|
viper.SetDefault("autotranscodedownload", false)
|
||||||
viper.SetDefault("defaultdownsamplingformat", consts.DefaultDownsamplingFormat)
|
viper.SetDefault("defaultdownsamplingformat", consts.DefaultDownsamplingFormat)
|
||||||
viper.SetDefault("search.fullstring", false)
|
viper.SetDefault("search.fullstring", false)
|
||||||
viper.SetDefault("search.backend", "fts")
|
viper.SetDefault("search.backend", "legacy") // FTS5 is SQLite-only; the LIKE backend works on PostgreSQL
|
||||||
viper.SetDefault("matcher.preferstarred", true)
|
viper.SetDefault("matcher.preferstarred", true)
|
||||||
viper.SetDefault("matcher.fuzzythreshold", 85)
|
viper.SetDefault("matcher.fuzzythreshold", 85)
|
||||||
viper.SetDefault("recentlyaddedbymodtime", false)
|
viper.SetDefault("recentlyaddedbymodtime", false)
|
||||||
|
|
@ -1131,7 +1133,7 @@ func setViperDefaults() {
|
||||||
viper.SetDefault("devartworkexternalmaxrps", 2)
|
viper.SetDefault("devartworkexternalmaxrps", 2)
|
||||||
viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive)
|
viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive)
|
||||||
viper.SetDefault("devalbuminfotimetolive", consts.AlbumInfoTimeToLive)
|
viper.SetDefault("devalbuminfotimetolive", consts.AlbumInfoTimeToLive)
|
||||||
viper.SetDefault("devexternalscanner", true)
|
viper.SetDefault("devexternalscanner", false)
|
||||||
viper.SetDefault("devscannerthreads", 5)
|
viper.SetDefault("devscannerthreads", 5)
|
||||||
viper.SetDefault("devselectivewatcher", true)
|
viper.SetDefault("devselectivewatcher", true)
|
||||||
viper.SetDefault("devinsightsinitialdelay", consts.InsightsInitialDelay)
|
viper.SetDefault("devinsightsinitialdelay", consts.InsightsInitialDelay)
|
||||||
|
|
|
||||||
68
db/backup.go
68
db/backup.go
|
|
@ -2,7 +2,6 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -11,7 +10,6 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
)
|
)
|
||||||
|
|
@ -33,71 +31,7 @@ func backupPath(t time.Time) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupOrRestore(ctx context.Context, isBackup bool, path string) error {
|
func backupOrRestore(ctx context.Context, isBackup bool, path string) error {
|
||||||
// heavily inspired by https://codingrabbits.dev/posts/go_and_sqlite_backup_and_maybe_restore/
|
return errors.New("database backup and restore are not supported with the embedded PGlite database")
|
||||||
existingConn, err := Db().Conn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting existing connection: %w", err)
|
|
||||||
}
|
|
||||||
defer existingConn.Close()
|
|
||||||
|
|
||||||
backupDb, err := sql.Open(Driver, path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("opening backup database in '%s': %w", path, err)
|
|
||||||
}
|
|
||||||
defer backupDb.Close()
|
|
||||||
|
|
||||||
backupConn, err := backupDb.Conn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting backup connection: %w", err)
|
|
||||||
}
|
|
||||||
defer backupConn.Close()
|
|
||||||
|
|
||||||
err = existingConn.Raw(func(existing any) error {
|
|
||||||
return backupConn.Raw(func(backup any) error {
|
|
||||||
var sourceOk, destOk bool
|
|
||||||
var sourceConn, destConn *sqlite3.SQLiteConn
|
|
||||||
|
|
||||||
if isBackup {
|
|
||||||
sourceConn, sourceOk = existing.(*sqlite3.SQLiteConn)
|
|
||||||
destConn, destOk = backup.(*sqlite3.SQLiteConn)
|
|
||||||
} else {
|
|
||||||
sourceConn, sourceOk = backup.(*sqlite3.SQLiteConn)
|
|
||||||
destConn, destOk = existing.(*sqlite3.SQLiteConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sourceOk {
|
|
||||||
return fmt.Errorf("error trying to convert source to sqlite connection")
|
|
||||||
}
|
|
||||||
if !destOk {
|
|
||||||
return fmt.Errorf("error trying to convert destination to sqlite connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
backupOp, err := destConn.Backup("main", sourceConn, "main")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error starting sqlite backup: %w", err)
|
|
||||||
}
|
|
||||||
defer backupOp.Close()
|
|
||||||
|
|
||||||
// Caution: -1 means that sqlite will hold a read lock until the operation finishes
|
|
||||||
// This will lock out other writes that could happen at the same time
|
|
||||||
done, err := backupOp.Step(-1)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error during backup step: %w", err)
|
|
||||||
}
|
|
||||||
if !done {
|
|
||||||
return fmt.Errorf("backup not done with step -1")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = backupOp.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error finishing backup: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Backup(ctx context.Context) (string, error) {
|
func Backup(ctx context.Context) (string, error) {
|
||||||
|
|
|
||||||
109
db/db.go
109
db/db.go
|
|
@ -6,26 +6,22 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-sqlite3"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
_ "github.com/navidrome/navidrome/db/migrations"
|
"github.com/navidrome/navidrome/db/pglite"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/utils/hasher"
|
|
||||||
"github.com/navidrome/navidrome/utils/natural"
|
|
||||||
"github.com/navidrome/navidrome/utils/singleton"
|
"github.com/navidrome/navidrome/utils/singleton"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NaturalCollation sorts embedded numbers by value. It is registered on every
|
|
||||||
// connection, but only referenced when conf.Server.EnableNaturalSorting is on.
|
|
||||||
const NaturalCollation = "NATSORT"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Dialect = "sqlite3"
|
Dialect = "postgres"
|
||||||
Driver = Dialect + "_custom"
|
Driver = "pgx"
|
||||||
Path string
|
Path string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,35 +30,37 @@ var embedMigrations embed.FS
|
||||||
|
|
||||||
const migrationsFolder = "migrations"
|
const migrationsFolder = "migrations"
|
||||||
|
|
||||||
// sql.Register panics if called twice, so guard it: the singleton instance can be reset
|
// embedded is the in-process PGlite instance backing Db().
|
||||||
// (tests/benchmarks) and rebuilt, but the driver is process-global and registers only once.
|
var embedded *pglite.PGlite
|
||||||
var registerDriverOnce sync.Once
|
|
||||||
|
|
||||||
func Db() *sql.DB {
|
func Db() *sql.DB {
|
||||||
return singleton.GetInstance(func() *sql.DB {
|
return singleton.GetInstance(func() *sql.DB {
|
||||||
registerDriverOnce.Do(func() {
|
|
||||||
sql.Register(Driver, &sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
if err := conn.RegisterFunc("SEEDEDRAND", hasher.HashFunc(), false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return conn.RegisterCollation(NaturalCollation, natural.CompareFold)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Path = conf.Server.DbPath
|
Path = conf.Server.DbPath
|
||||||
if Path == ":memory:" {
|
dataDir, ok := strings.CutPrefix(Path, "pglite://")
|
||||||
Path = "file::memory:?cache=shared&_foreign_keys=on"
|
if !ok {
|
||||||
conf.Server.DbPath = Path
|
log.Fatal("DbPath must be a pglite://<dir> location in this build", "dbPath", Path)
|
||||||
} else {
|
|
||||||
conf.Server.DataFolder.MustPath()
|
|
||||||
}
|
}
|
||||||
log.Debug("Opening DataBase", "dbPath", Path, "driver", Driver)
|
socketDir := filepath.Dir(dataDir)
|
||||||
db, err := sql.Open(Driver, Path)
|
logFile, _ := os.Create(filepath.Join(socketDir, "pglite.log"))
|
||||||
db.SetMaxOpenConns(conf.MaxOpenConns())
|
pg, err := pglite.New(context.Background(), pglite.Config{
|
||||||
|
DataDir: dataDir, Stderr: logFile, SocketDir: socketDir,
|
||||||
|
CacheDir: filepath.Join(conf.Server.CacheFolder.String(), "pglite"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error starting embedded PGlite", err)
|
||||||
|
}
|
||||||
|
embedded = pg
|
||||||
|
db, err := pg.OpenDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error opening database", err)
|
log.Fatal("Error opening database", err)
|
||||||
}
|
}
|
||||||
|
// One PGlite backend means one PG session; the bridge serializes clients onto it.
|
||||||
|
// Keep every connection idle rather than churning handshakes through the backend.
|
||||||
|
db.SetMaxOpenConns(4)
|
||||||
|
db.SetMaxIdleConns(4)
|
||||||
|
db.SetConnMaxLifetime(0)
|
||||||
|
abs, _ := filepath.Abs(socketDir)
|
||||||
|
log.Info("Embedded PGlite ready", "psql", "PGPASSWORD=x PGSSLMODE=disable psql -h "+abs+" -U postgres navidrome")
|
||||||
return db
|
return db
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -72,30 +70,19 @@ func Close(ctx context.Context) {
|
||||||
ctx = context.WithoutCancel(ctx)
|
ctx = context.WithoutCancel(ctx)
|
||||||
|
|
||||||
log.Info(ctx, "Closing Database")
|
log.Info(ctx, "Closing Database")
|
||||||
err := Db().Close()
|
if err := Db().Close(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "Error closing Database", err)
|
log.Error(ctx, "Error closing Database", err)
|
||||||
}
|
}
|
||||||
|
if embedded != nil {
|
||||||
|
_ = embedded.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(ctx context.Context) func() {
|
func Init(ctx context.Context) func() {
|
||||||
db := Db()
|
db := Db()
|
||||||
|
|
||||||
// Disable foreign_keys to allow re-creating tables in migrations
|
|
||||||
_, err := db.ExecContext(ctx, "PRAGMA foreign_keys=off")
|
|
||||||
defer func() {
|
|
||||||
_, err := db.ExecContext(ctx, "PRAGMA foreign_keys=on")
|
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "Error re-enabling foreign_keys", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "Error disabling foreign_keys", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
goose.SetBaseFS(embedMigrations)
|
goose.SetBaseFS(embedMigrations)
|
||||||
err = goose.SetDialect(Dialect)
|
if err := goose.SetDialect(Dialect); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(ctx, "Invalid DB driver", "driver", Driver, err)
|
log.Fatal(ctx, "Invalid DB driver", "driver", Driver, err)
|
||||||
}
|
}
|
||||||
schemaEmpty := isSchemaEmpty(ctx, db)
|
schemaEmpty := isSchemaEmpty(ctx, db)
|
||||||
|
|
@ -104,15 +91,13 @@ func Init(ctx context.Context) func() {
|
||||||
log.Info(ctx, "Upgrading DB Schema to latest version")
|
log.Info(ctx, "Upgrading DB Schema to latest version")
|
||||||
}
|
}
|
||||||
goose.SetLogger(&logAdapter{ctx: ctx, silent: schemaEmpty})
|
goose.SetLogger(&logAdapter{ctx: ctx, silent: schemaEmpty})
|
||||||
err = goose.UpContext(ctx, db, migrationsFolder)
|
if err := goose.UpContext(ctx, db, migrationsFolder); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(ctx, "Failed to apply new migrations", err)
|
log.Fatal(ctx, "Failed to apply new migrations", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasSchemaChanges {
|
if hasSchemaChanges {
|
||||||
log.Debug(ctx, "Running ANALYZE after schema changes")
|
log.Debug(ctx, "Running ANALYZE after schema changes")
|
||||||
err = optimizeAt(ctx, db, time.Now())
|
if err := optimizeAt(ctx, db, time.Now()); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "Error running ANALYZE", err)
|
log.Error(ctx, "Error running ANALYZE", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,15 +107,13 @@ func Init(ctx context.Context) func() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorCodes reports the SQLite result code and extended result code carried by err.
|
// SQLState reports the PostgreSQL error code carried by err, if any.
|
||||||
// The extended code is what distinguishes errors that share a message: "database is locked"
|
func SQLState(err error) (string, bool) {
|
||||||
// is both SQLITE_BUSY, which busy_timeout retries, and SQLITE_BUSY_SNAPSHOT, which it never can.
|
var pgErr *pgconn.PgError
|
||||||
func ErrorCodes(err error) (code, extended int, ok bool) {
|
if !errors.As(err, &pgErr) {
|
||||||
var se sqlite3.Error
|
return "", false
|
||||||
if !errors.As(err, &se) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
}
|
||||||
return int(se.Code), int(se.ExtendedCode), true
|
return pgErr.Code, true
|
||||||
}
|
}
|
||||||
|
|
||||||
type statusLogger struct{ numPending int }
|
type statusLogger struct{ numPending int }
|
||||||
|
|
@ -158,12 +141,12 @@ func hasPendingMigrations(ctx context.Context, db *sql.DB, folder string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSchemaEmpty(ctx context.Context, db *sql.DB) bool {
|
func isSchemaEmpty(ctx context.Context, db *sql.DB) bool {
|
||||||
rows, err := db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table' AND name='goose_db_version';") // nolint:rowserrcheck
|
var found *string
|
||||||
|
err := db.QueryRowContext(ctx, "SELECT to_regclass('public.goose_db_version')::text").Scan(&found)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(ctx, "Database could not be opened!", err)
|
log.Fatal(ctx, "Database could not be opened!", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return found == nil
|
||||||
return !rows.Next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type logAdapter struct {
|
type logAdapter struct {
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200130083147, Down20200130083147)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200130083147(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
log.Info("Creating DB Schema")
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table if not exists album
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
cover_art_path varchar(255) default '' not null,
|
|
||||||
cover_art_id varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
year integer default 0 not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
duration integer default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists album_artist
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index if not exists album_artist_id
|
|
||||||
on album (artist_id);
|
|
||||||
|
|
||||||
create index if not exists album_genre
|
|
||||||
on album (genre);
|
|
||||||
|
|
||||||
create index if not exists album_name
|
|
||||||
on album (name);
|
|
||||||
|
|
||||||
create index if not exists album_year
|
|
||||||
on album (year);
|
|
||||||
|
|
||||||
create table if not exists annotation
|
|
||||||
(
|
|
||||||
ann_id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
user_id varchar(255) default '' not null,
|
|
||||||
item_id varchar(255) default '' not null,
|
|
||||||
item_type varchar(255) default '' not null,
|
|
||||||
play_count integer,
|
|
||||||
play_date datetime,
|
|
||||||
rating integer,
|
|
||||||
starred bool default FALSE not null,
|
|
||||||
starred_at datetime,
|
|
||||||
unique (user_id, item_id, item_type)
|
|
||||||
);
|
|
||||||
|
|
||||||
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 table if not exists artist
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
album_count integer default 0 not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists artist_name
|
|
||||||
on artist (name);
|
|
||||||
|
|
||||||
create table if not exists media_file
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
path varchar(255) default '' not null,
|
|
||||||
title varchar(255) default '' not null,
|
|
||||||
album varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
album_id varchar(255) default '' not null,
|
|
||||||
has_cover_art bool 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 integer default 0 not null,
|
|
||||||
suffix varchar(255) default '' not null,
|
|
||||||
duration integer default 0 not null,
|
|
||||||
bit_rate integer default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists media_file_album_id
|
|
||||||
on media_file (album_id);
|
|
||||||
|
|
||||||
create index if not exists media_file_genre
|
|
||||||
on media_file (genre);
|
|
||||||
|
|
||||||
create index if not exists media_file_path
|
|
||||||
on media_file (path);
|
|
||||||
|
|
||||||
create index if not exists media_file_title
|
|
||||||
on media_file (title);
|
|
||||||
|
|
||||||
create table if not exists playlist
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration integer default 0 not null,
|
|
||||||
owner varchar(255) default '' not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
tracks text not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
|
|
||||||
create table if not exists property
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
value varchar(255) default '' not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists search
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
"table" varchar(255) default '' not null,
|
|
||||||
full_text varchar(255) default '' not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists search_full_text
|
|
||||||
on search (full_text);
|
|
||||||
|
|
||||||
create index if not exists search_table
|
|
||||||
on search ("table");
|
|
||||||
|
|
||||||
create table if not exists user
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
user_name varchar(255) default '' not null
|
|
||||||
unique,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
email varchar(255) default '' not null
|
|
||||||
unique,
|
|
||||||
password varchar(255) default '' not null,
|
|
||||||
is_admin bool default FALSE not null,
|
|
||||||
last_login_at datetime,
|
|
||||||
last_access_at datetime,
|
|
||||||
created_at datetime not null,
|
|
||||||
updated_at datetime not null
|
|
||||||
);`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200130083147(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200131183653, Down20200131183653)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200131183653(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table search_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
item_type varchar(255) default '' not null,
|
|
||||||
full_text varchar(255) default '' not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into search_dg_tmp(id, item_type, full_text) select id, "table", full_text from search;
|
|
||||||
|
|
||||||
drop table search;
|
|
||||||
|
|
||||||
alter table search_dg_tmp rename to search;
|
|
||||||
|
|
||||||
create index search_full_text
|
|
||||||
on search (full_text);
|
|
||||||
create index search_table
|
|
||||||
on search (item_type);
|
|
||||||
|
|
||||||
update annotation set item_type = 'media_file' where item_type = 'mediaFile';
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200131183653(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table search_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
"table" varchar(255) default '' not null,
|
|
||||||
full_text varchar(255) default '' not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into search_dg_tmp(id, "table", full_text) select id, item_type, full_text from search;
|
|
||||||
|
|
||||||
drop table search;
|
|
||||||
|
|
||||||
alter table search_dg_tmp rename to search;
|
|
||||||
|
|
||||||
create index search_full_text
|
|
||||||
on search (full_text);
|
|
||||||
create index search_table
|
|
||||||
on search ("table");
|
|
||||||
|
|
||||||
update annotation set item_type = 'mediaFile' where item_type = 'media_file';
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200208222418, Down20200208222418)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200208222418(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
update annotation set play_count = 0 where play_count is null;
|
|
||||||
update annotation set rating = 0 where rating is null;
|
|
||||||
create table annotation_dg_tmp
|
|
||||||
(
|
|
||||||
ann_id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
user_id varchar(255) default '' not null,
|
|
||||||
item_id varchar(255) default '' not null,
|
|
||||||
item_type varchar(255) default '' not null,
|
|
||||||
play_count integer default 0,
|
|
||||||
play_date datetime,
|
|
||||||
rating integer default 0,
|
|
||||||
starred bool default FALSE not null,
|
|
||||||
starred_at datetime,
|
|
||||||
unique (user_id, item_id, item_type)
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into annotation_dg_tmp(ann_id, user_id, item_id, item_type, play_count, play_date, rating, starred, starred_at) select ann_id, user_id, item_id, item_type, play_count, play_date, rating, starred, starred_at from annotation;
|
|
||||||
|
|
||||||
drop table annotation;
|
|
||||||
|
|
||||||
alter table annotation_dg_tmp rename to annotation;
|
|
||||||
|
|
||||||
create index annotation_play_count
|
|
||||||
on annotation (play_count);
|
|
||||||
|
|
||||||
create index annotation_play_date
|
|
||||||
on annotation (play_date);
|
|
||||||
|
|
||||||
create index annotation_rating
|
|
||||||
on annotation (rating);
|
|
||||||
|
|
||||||
create index annotation_starred
|
|
||||||
on annotation (starred);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200208222418(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200220143731, Down20200220143731)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200220143731(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "This migration will force the next scan to be a full rescan!")
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table media_file_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
path varchar(255) default '' not null,
|
|
||||||
title varchar(255) default '' not null,
|
|
||||||
album varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
album_id varchar(255) default '' not null,
|
|
||||||
has_cover_art bool 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 integer default 0 not null,
|
|
||||||
suffix varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
bit_rate integer default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into media_file_dg_tmp(id, path, title, album, artist, artist_id, album_artist, album_id, has_cover_art, track_number, disc_number, year, size, suffix, duration, bit_rate, genre, compilation, created_at, updated_at) select id, path, title, album, artist, artist_id, album_artist, album_id, has_cover_art, track_number, disc_number, year, size, suffix, duration, bit_rate, genre, compilation, created_at, updated_at from media_file;
|
|
||||||
|
|
||||||
drop table media_file;
|
|
||||||
|
|
||||||
alter table media_file_dg_tmp rename to media_file;
|
|
||||||
|
|
||||||
create index media_file_album_id
|
|
||||||
on media_file (album_id);
|
|
||||||
|
|
||||||
create index media_file_genre
|
|
||||||
on media_file (genre);
|
|
||||||
|
|
||||||
create index media_file_path
|
|
||||||
on media_file (path);
|
|
||||||
|
|
||||||
create index media_file_title
|
|
||||||
on media_file (title);
|
|
||||||
|
|
||||||
create table album_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
cover_art_path varchar(255) default '' not null,
|
|
||||||
cover_art_id varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
year integer default 0 not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into album_dg_tmp(id, name, artist_id, cover_art_path, cover_art_id, artist, album_artist, year, compilation, song_count, duration, genre, created_at, updated_at) select id, name, artist_id, cover_art_path, cover_art_id, artist, album_artist, year, compilation, song_count, duration, genre, created_at, updated_at from album;
|
|
||||||
|
|
||||||
drop table album;
|
|
||||||
|
|
||||||
alter table album_dg_tmp rename to album;
|
|
||||||
|
|
||||||
create index album_artist
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index album_artist_id
|
|
||||||
on album (artist_id);
|
|
||||||
|
|
||||||
create index album_genre
|
|
||||||
on album (genre);
|
|
||||||
|
|
||||||
create index album_name
|
|
||||||
on album (name);
|
|
||||||
|
|
||||||
create index album_year
|
|
||||||
on album (year);
|
|
||||||
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
owner varchar(255) default '' not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
tracks text not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, owner, public, tracks) select id, name, comment, duration, owner, public, tracks from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
|
|
||||||
alter table playlist_dg_tmp rename to playlist;
|
|
||||||
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
|
|
||||||
-- Force a full rescan
|
|
||||||
delete from property where id like 'LastScan%';
|
|
||||||
update media_file set updated_at = '0001-01-01';
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200220143731(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200310171621, Down20200310171621)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200310171621(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to enable search by Album Artist!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200310171621(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200310181627, Down20200310181627)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200310181627(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table transcoding
|
|
||||||
(
|
|
||||||
id varchar(255) not null primary key,
|
|
||||||
name varchar(255) not null,
|
|
||||||
target_format varchar(255) not null,
|
|
||||||
command varchar(255) default '' not null,
|
|
||||||
default_bit_rate int default 192,
|
|
||||||
unique (name),
|
|
||||||
unique (target_format)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table player
|
|
||||||
(
|
|
||||||
id varchar(255) not null primary key,
|
|
||||||
name varchar not null,
|
|
||||||
type varchar,
|
|
||||||
user_name varchar not null,
|
|
||||||
client varchar not null,
|
|
||||||
ip_address varchar,
|
|
||||||
last_seen timestamp,
|
|
||||||
max_bit_rate int default 0,
|
|
||||||
transcoding_id varchar,
|
|
||||||
unique (name),
|
|
||||||
foreign key (transcoding_id)
|
|
||||||
references transcoding(id)
|
|
||||||
on update restrict
|
|
||||||
on delete restrict
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200310181627(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
drop table transcoding;
|
|
||||||
drop table player;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200319211049, Down20200319211049)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200319211049(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add full_text varchar(255) default '';
|
|
||||||
create index if not exists media_file_full_text
|
|
||||||
on media_file (full_text);
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add full_text varchar(255) default '';
|
|
||||||
create index if not exists album_full_text
|
|
||||||
on album (full_text);
|
|
||||||
|
|
||||||
alter table artist
|
|
||||||
add full_text varchar(255) default '';
|
|
||||||
create index if not exists artist_full_text
|
|
||||||
on artist (full_text);
|
|
||||||
|
|
||||||
drop table if exists search;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200319211049(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200325185135, Down20200325185135)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200325185135(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table album
|
|
||||||
add album_artist_id varchar(255) default '';
|
|
||||||
create index album_artist_album_id
|
|
||||||
on album (album_artist_id);
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add album_artist_id varchar(255) default '';
|
|
||||||
create index media_file_artist_album_id
|
|
||||||
on media_file (album_artist_id);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200325185135(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200326090707, Down20200326090707)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200326090707(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200326090707(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200327193744, Down20200327193744)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200327193744(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table album_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
cover_art_path varchar(255) default '' not null,
|
|
||||||
cover_art_id varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
min_year int default 0 not null,
|
|
||||||
max_year integer default 0 not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
full_text varchar(255) default '',
|
|
||||||
album_artist_id varchar(255) default ''
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into album_dg_tmp(id, name, artist_id, cover_art_path, cover_art_id, artist, album_artist, max_year, compilation, song_count, duration, genre, created_at, updated_at, full_text, album_artist_id) select id, name, artist_id, cover_art_path, cover_art_id, artist, album_artist, year, compilation, song_count, duration, genre, created_at, updated_at, full_text, album_artist_id from album;
|
|
||||||
|
|
||||||
drop table album;
|
|
||||||
|
|
||||||
alter table album_dg_tmp rename to album;
|
|
||||||
|
|
||||||
create index album_artist
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index album_artist_album
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index album_artist_album_id
|
|
||||||
on album (album_artist_id);
|
|
||||||
|
|
||||||
create index album_artist_id
|
|
||||||
on album (artist_id);
|
|
||||||
|
|
||||||
create index album_full_text
|
|
||||||
on album (full_text);
|
|
||||||
|
|
||||||
create index album_genre
|
|
||||||
on album (genre);
|
|
||||||
|
|
||||||
create index album_name
|
|
||||||
on album (name);
|
|
||||||
|
|
||||||
create index album_min_year
|
|
||||||
on album (min_year);
|
|
||||||
|
|
||||||
create index album_max_year
|
|
||||||
on album (max_year);
|
|
||||||
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200327193744(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200404214704, Down20200404214704)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200404214704(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index if not exists media_file_year
|
|
||||||
on media_file (year);
|
|
||||||
|
|
||||||
create index if not exists media_file_duration
|
|
||||||
on media_file (duration);
|
|
||||||
|
|
||||||
create index if not exists media_file_track_number
|
|
||||||
on media_file (disc_number, track_number);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200404214704(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200409002249, Down20200409002249)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200409002249(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to enable search by individual Artist in an Album!")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200409002249(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200411164603, Down20200411164603)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200411164603(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table playlist
|
|
||||||
add created_at datetime;
|
|
||||||
alter table playlist
|
|
||||||
add updated_at datetime;
|
|
||||||
update playlist
|
|
||||||
set created_at = datetime('now'), updated_at = datetime('now');
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200411164603(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200418110522, Down20200418110522)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200418110522(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to fix search Albums by year")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200418110522(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200419222708, Down20200419222708)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200419222708(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to change the search behaviour")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200419222708(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200423204116, Down20200423204116)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200423204116(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table artist
|
|
||||||
add order_artist_name varchar(255) collate nocase;
|
|
||||||
alter table artist
|
|
||||||
add sort_artist_name varchar(255) collate nocase;
|
|
||||||
create index if not exists artist_order_artist_name
|
|
||||||
on artist (order_artist_name);
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add order_album_name varchar(255) collate nocase;
|
|
||||||
alter table album
|
|
||||||
add order_album_artist_name varchar(255) collate nocase;
|
|
||||||
alter table album
|
|
||||||
add sort_album_name varchar(255) collate nocase;
|
|
||||||
alter table album
|
|
||||||
add sort_artist_name varchar(255) collate nocase;
|
|
||||||
alter table album
|
|
||||||
add sort_album_artist_name varchar(255) collate nocase;
|
|
||||||
create index if not exists album_order_album_name
|
|
||||||
on album (order_album_name);
|
|
||||||
create index if not exists album_order_album_artist_name
|
|
||||||
on album (order_album_artist_name);
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add order_album_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add order_album_artist_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add order_artist_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add sort_album_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add sort_artist_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add sort_album_artist_name varchar(255) collate nocase;
|
|
||||||
alter table media_file
|
|
||||||
add sort_title varchar(255) collate nocase;
|
|
||||||
create index if not exists media_file_order_album_name
|
|
||||||
on media_file (order_album_name);
|
|
||||||
create index if not exists media_file_order_artist_name
|
|
||||||
on media_file (order_artist_name);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to change the search behaviour")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200423204116(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200508093059, Down20200508093059)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200508093059(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table artist
|
|
||||||
add song_count integer default 0 not null;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to calculate artists' song counts")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200508093059(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200512104202, Down20200512104202)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200512104202(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add disc_subtitle varchar(255);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to import disc subtitles")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200512104202(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200516140647, Down20200516140647)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200516140647(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table if not exists playlist_tracks
|
|
||||||
(
|
|
||||||
id integer default 0 not null,
|
|
||||||
playlist_id varchar(255) not null,
|
|
||||||
media_file_id varchar(255) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create unique index if not exists playlist_tracks_pos
|
|
||||||
on playlist_tracks (playlist_id, id);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rows, err := tx.QueryContext(ctx, "select id, tracks from playlist")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var id, tracks string
|
|
||||||
for rows.Next() {
|
|
||||||
err := rows.Scan(&id, &tracks)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = Up20200516140647UpdatePlaylistTracks(tx, id, tracks)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = rows.Err()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
owner varchar(255) default '' not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, owner, public, created_at, updated_at)
|
|
||||||
select id, name, comment, duration, owner, public, created_at, updated_at from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
|
|
||||||
alter table playlist_dg_tmp rename to playlist;
|
|
||||||
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
|
|
||||||
update playlist set song_count = (select count(*) from playlist_tracks where playlist_id = playlist.id)
|
|
||||||
where id <> ''
|
|
||||||
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200516140647UpdatePlaylistTracks(tx *sql.Tx, id string, tracks string) error {
|
|
||||||
trackList := strings.Split(tracks, ",")
|
|
||||||
stmt, err := tx.Prepare("insert into playlist_tracks (playlist_id, media_file_id, id) values (?, ?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, trackId := range trackList {
|
|
||||||
_, err := stmt.Exec(id, trackId, i+1)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error adding track to playlist", "playlistId", id, "trackId", trackId, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200516140647(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20200608153717, Down20200608153717)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20200608153717(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// First delete dangling players
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
delete from player where user_name not in (select user_name from user)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also delete dangling players
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
delete from playlist where owner not in (select user_name from user)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also delete dangling playlist tracks
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
delete from playlist_tracks where playlist_id not in (select id from playlist)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add foreign key to player table
|
|
||||||
err = updatePlayer_20200608153717(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add foreign key to playlist table
|
|
||||||
err = updatePlaylist_20200608153717(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add foreign keys to playlist_tracks table
|
|
||||||
return updatePlaylistTracks_20200608153717(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePlayer_20200608153717(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table player_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar not null
|
|
||||||
unique,
|
|
||||||
type varchar,
|
|
||||||
user_name varchar not null
|
|
||||||
references user (user_name)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
client varchar not null,
|
|
||||||
ip_address varchar,
|
|
||||||
last_seen timestamp,
|
|
||||||
max_bit_rate int default 0,
|
|
||||||
transcoding_id varchar null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into player_dg_tmp(id, name, type, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id) select id, name, type, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id from player;
|
|
||||||
|
|
||||||
drop table player;
|
|
||||||
|
|
||||||
alter table player_dg_tmp rename to player;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePlaylist_20200608153717(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
owner varchar(255) default '' not null
|
|
||||||
constraint playlist_user_user_name_fk
|
|
||||||
references user (user_name)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, song_count, owner, public, created_at, updated_at) select id, name, comment, duration, song_count, owner, public, created_at, updated_at from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
|
|
||||||
alter table playlist_dg_tmp rename to playlist;
|
|
||||||
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePlaylistTracks_20200608153717(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table playlist_tracks_dg_tmp
|
|
||||||
(
|
|
||||||
id integer default 0 not null,
|
|
||||||
playlist_id varchar(255) not null
|
|
||||||
constraint playlist_tracks_playlist_id_fk
|
|
||||||
references playlist
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
media_file_id varchar(255) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_tracks_dg_tmp(id, playlist_id, media_file_id) select id, playlist_id, media_file_id from playlist_tracks;
|
|
||||||
|
|
||||||
drop table playlist_tracks;
|
|
||||||
|
|
||||||
alter table playlist_tracks_dg_tmp rename to playlist_tracks;
|
|
||||||
|
|
||||||
create unique index playlist_tracks_pos
|
|
||||||
on playlist_tracks (playlist_id, id);
|
|
||||||
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20200608153717(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/model/id"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddDefaultTranscodings, downAddDefaultTranscodings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddDefaultTranscodings(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM transcoding")
|
|
||||||
var count int
|
|
||||||
err := row.Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("insert into transcoding (id, name, target_format, default_bit_rate, command) values (?, ?, ?, ?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range consts.DefaultTranscodings {
|
|
||||||
_, err := stmt.Exec(id.NewRandom(), t.Name, t.TargetFormat, t.DefaultBitRate, t.Command)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddDefaultTranscodings(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddPlaylistPath, downAddPlaylistPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddPlaylistPath(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table playlist
|
|
||||||
add path string default '' not null;
|
|
||||||
|
|
||||||
alter table playlist
|
|
||||||
add sync bool default false not null;
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddPlaylistPath(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upCreatePlayQueuesTable, downCreatePlayQueuesTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upCreatePlayQueuesTable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table playqueue
|
|
||||||
(
|
|
||||||
id varchar(255) not null primary key,
|
|
||||||
user_id varchar(255) not null
|
|
||||||
references user (id)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
comment varchar(255),
|
|
||||||
current varchar(255) not null,
|
|
||||||
position integer,
|
|
||||||
changed_by varchar(255),
|
|
||||||
items varchar(255),
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downCreatePlayQueuesTable(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upCreateBookmarkTable, downCreateBookmarkTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upCreateBookmarkTable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table bookmark
|
|
||||||
(
|
|
||||||
user_id varchar(255) not null
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
item_id varchar(255) not null,
|
|
||||||
item_type varchar(255) not null,
|
|
||||||
comment varchar(255),
|
|
||||||
position integer,
|
|
||||||
changed_by varchar(255),
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
constraint bookmark_pk
|
|
||||||
unique (user_id, item_id, item_type)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table playqueue_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null,
|
|
||||||
user_id varchar(255) not null
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
current varchar(255),
|
|
||||||
position real,
|
|
||||||
changed_by varchar(255),
|
|
||||||
items varchar(255),
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
drop table playqueue;
|
|
||||||
alter table playqueue_dg_tmp rename to playqueue;
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downCreateBookmarkTable(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upDropEmailUniqueConstraint, downDropEmailUniqueConstraint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upDropEmailUniqueConstraint(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table user_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
user_name varchar(255) default '' not null
|
|
||||||
unique,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
email varchar(255) default '' not null,
|
|
||||||
password varchar(255) default '' not null,
|
|
||||||
is_admin bool default FALSE not null,
|
|
||||||
last_login_at datetime,
|
|
||||||
last_access_at datetime,
|
|
||||||
created_at datetime not null,
|
|
||||||
updated_at datetime not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into user_dg_tmp(id, user_name, name, email, password, is_admin, last_login_at, last_access_at, created_at, updated_at) select id, user_name, name, email, password, is_admin, last_login_at, last_access_at, created_at, updated_at from user;
|
|
||||||
|
|
||||||
drop table user;
|
|
||||||
|
|
||||||
alter table user_dg_tmp rename to user;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downDropEmailUniqueConstraint(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201003111749, Down20201003111749)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201003111749(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index if not exists annotation_starred_at
|
|
||||||
on annotation (starred_at);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201003111749(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201010162350, Down20201010162350)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201010162350(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table album
|
|
||||||
add size integer default 0 not null;
|
|
||||||
create index if not exists album_size
|
|
||||||
on album(size);
|
|
||||||
|
|
||||||
update album set size = ifnull((
|
|
||||||
select sum(f.size)
|
|
||||||
from media_file f
|
|
||||||
where f.album_id = album.id
|
|
||||||
), 0)
|
|
||||||
where id not null;`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201010162350(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201012210022, Down20201012210022)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201012210022(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table artist
|
|
||||||
add size integer default 0 not null;
|
|
||||||
create index if not exists artist_size
|
|
||||||
on artist(size);
|
|
||||||
|
|
||||||
update artist set size = ifnull((
|
|
||||||
select sum(f.size)
|
|
||||||
from album f
|
|
||||||
where f.album_artist_id = artist.id
|
|
||||||
), 0)
|
|
||||||
where id not null;
|
|
||||||
|
|
||||||
alter table playlist
|
|
||||||
add size integer default 0 not null;
|
|
||||||
create index if not exists playlist_size
|
|
||||||
on playlist(size);
|
|
||||||
|
|
||||||
update playlist set size = ifnull((
|
|
||||||
select sum(size)
|
|
||||||
from media_file f
|
|
||||||
left join playlist_tracks pt on f.id = pt.media_file_id
|
|
||||||
where pt.playlist_id = playlist.id
|
|
||||||
), 0);`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201012210022(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201021085410, Down20201021085410)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201021085410(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add mbz_track_id varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_id varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add mbz_artist_id varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_artist_id varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_type varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_comment varchar(255);
|
|
||||||
alter table media_file
|
|
||||||
add catalog_num varchar(255);
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add mbz_album_id varchar(255);
|
|
||||||
alter table album
|
|
||||||
add mbz_album_artist_id varchar(255);
|
|
||||||
alter table album
|
|
||||||
add mbz_album_type varchar(255);
|
|
||||||
alter table album
|
|
||||||
add mbz_album_comment varchar(255);
|
|
||||||
alter table album
|
|
||||||
add catalog_num varchar(255);
|
|
||||||
|
|
||||||
create index if not exists album_mbz_album_type
|
|
||||||
on album (mbz_album_type);
|
|
||||||
|
|
||||||
alter table artist
|
|
||||||
add mbz_artist_id varchar(255);
|
|
||||||
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201021085410(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201021093209, Down20201021093209)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201021093209(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index if not exists media_file_artist
|
|
||||||
on media_file (artist);
|
|
||||||
create index if not exists media_file_album_artist
|
|
||||||
on media_file (album_artist);
|
|
||||||
create index if not exists media_file_mbz_track_id
|
|
||||||
on media_file (mbz_track_id);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201021093209(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201021135455, Down20201021135455)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201021135455(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index if not exists media_file_artist_id
|
|
||||||
on media_file (artist_id);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201021135455(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddArtistImageUrl, downAddArtistImageUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddArtistImageUrl(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table artist
|
|
||||||
add biography varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add small_image_url varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add medium_image_url varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add large_image_url varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add similar_artists varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add external_url varchar(255) default '' not null;
|
|
||||||
alter table artist
|
|
||||||
add external_info_updated_at datetime;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddArtistImageUrl(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201110205344, Down20201110205344)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201110205344(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add comment varchar;
|
|
||||||
alter table media_file
|
|
||||||
add lyrics varchar;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add comment varchar;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to import comments and lyrics")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201110205344(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201128100726, Down20201128100726)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201128100726(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table player
|
|
||||||
add report_real_path bool default FALSE not null;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201128100726(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/navidrome/navidrome/utils/str"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20201213124814, Down20201213124814)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20201213124814(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table album
|
|
||||||
add all_artist_ids varchar;
|
|
||||||
|
|
||||||
create index if not exists album_all_artist_ids
|
|
||||||
on album (all_artist_ids);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateAlbums20201213124814(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateAlbums20201213124814(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
rows, err := tx.QueryContext(ctx, `
|
|
||||||
select a.id, a.name, a.artist_id, a.album_artist_id, group_concat(mf.artist_id, ' ')
|
|
||||||
from album a left join media_file mf on a.id = mf.album_id group by a.id
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update album set all_artist_ids = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id, name, artistId, albumArtistId string
|
|
||||||
var songArtistIds sql.NullString
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &name, &artistId, &albumArtistId, &songArtistIds)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
all := str.SanitizeStrings(artistId, albumArtistId, songArtistIds.String)
|
|
||||||
_, err = stmt.Exec(all, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error setting album's artist_ids", "album", name, "albumId", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20201213124814(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddTimestampIndexesGo, downAddTimestampIndexesGo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddTimestampIndexesGo(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index if not exists album_updated_at
|
|
||||||
on album (updated_at);
|
|
||||||
create index if not exists album_created_at
|
|
||||||
on album (created_at);
|
|
||||||
create index if not exists playlist_updated_at
|
|
||||||
on playlist (updated_at);
|
|
||||||
create index if not exists playlist_created_at
|
|
||||||
on playlist (created_at);
|
|
||||||
create index if not exists media_file_created_at
|
|
||||||
on media_file (created_at);
|
|
||||||
create index if not exists media_file_updated_at
|
|
||||||
on media_file (updated_at);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddTimestampIndexesGo(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upFixAlbumComments, downFixAlbumComments)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upFixAlbumComments(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
//nolint:gosec
|
|
||||||
rows, err := tx.QueryContext(ctx, `
|
|
||||||
SELECT album.id, group_concat(media_file.comment, '`+consts.Zwsp+`') FROM album, media_file WHERE media_file.album_id = album.id GROUP BY album.id;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("UPDATE album SET comment = ? WHERE id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var id string
|
|
||||||
var comments sql.NullString
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &comments)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !comments.Valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
comment := getComment(comments.String, consts.Zwsp)
|
|
||||||
_, err = stmt.Exec(comment, id)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error setting album's comments", "albumId", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func downFixAlbumComments(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getComment(comments string, separator string) string {
|
|
||||||
cs := strings.Split(comments, separator)
|
|
||||||
if len(cs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
first := cs[0]
|
|
||||||
for _, c := range cs[1:] {
|
|
||||||
if first != c {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddBpmMetadata, downAddBpmMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddBpmMetadata(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add bpm integer;
|
|
||||||
|
|
||||||
create index if not exists media_file_bpm
|
|
||||||
on media_file (bpm);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddBpmMetadata(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upCreateSharesTable, downCreateSharesTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upCreateSharesTable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table share
|
|
||||||
(
|
|
||||||
id varchar(255) not null primary key,
|
|
||||||
name varchar(255) not null unique,
|
|
||||||
description varchar(255),
|
|
||||||
expires datetime,
|
|
||||||
created datetime,
|
|
||||||
last_visited datetime,
|
|
||||||
resource_ids varchar not null,
|
|
||||||
resource_type varchar(255) not null,
|
|
||||||
visit_count integer default 0
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downCreateSharesTable(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upUpdateShareFieldNames, downUpdateShareFieldNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upUpdateShareFieldNames(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table share rename column expires to expires_at;
|
|
||||||
alter table share rename column created to created_at;
|
|
||||||
alter table share rename column last_visited to last_visited_at;
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downUpdateShareFieldNames(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/navidrome/navidrome/utils"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upEncodeAllPasswords, downEncodeAllPasswords)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upEncodeAllPasswords(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
rows, err := tx.QueryContext(ctx, `SELECT id, user_name, password from user;`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("UPDATE user SET password = ? WHERE id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var id string
|
|
||||||
var username, password string
|
|
||||||
|
|
||||||
data := sha256.Sum256([]byte(consts.DefaultEncryptionKey))
|
|
||||||
encKey := data[0:]
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &username, &password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
password, err = utils.Encrypt(ctx, encKey, password)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error encrypting user's password", "id", id, "username", username, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = stmt.Exec(password, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error saving user's encrypted password", "id", id, "username", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func downEncodeAllPasswords(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upDropPlayerNameUniqueConstraint, downDropPlayerNameUniqueConstraint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upDropPlayerNameUniqueConstraint(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table player_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar not null,
|
|
||||||
user_agent varchar,
|
|
||||||
user_name varchar not null
|
|
||||||
references user (user_name)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
client varchar not null,
|
|
||||||
ip_address varchar,
|
|
||||||
last_seen timestamp,
|
|
||||||
max_bit_rate int default 0,
|
|
||||||
transcoding_id varchar,
|
|
||||||
report_real_path bool default FALSE not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into player_dg_tmp(id, name, user_agent, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id, report_real_path) select id, name, type, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id, report_real_path from player;
|
|
||||||
|
|
||||||
drop table player;
|
|
||||||
|
|
||||||
alter table player_dg_tmp rename to player;
|
|
||||||
create index if not exists player_match
|
|
||||||
on player (client, user_agent, user_name);
|
|
||||||
create index if not exists player_name
|
|
||||||
on player (name);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downDropPlayerNameUniqueConstraint(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddUserPrefsPlayerScrobblerEnabled, downAddUserPrefsPlayerScrobblerEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddUserPrefsPlayerScrobblerEnabled(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
err := upAddUserPrefs(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return upPlayerScrobblerEnabled(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddUserPrefs(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table user_props
|
|
||||||
(
|
|
||||||
user_id varchar not null,
|
|
||||||
key varchar not null,
|
|
||||||
value varchar,
|
|
||||||
constraint user_props_pk
|
|
||||||
primary key (user_id, key)
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func upPlayerScrobblerEnabled(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table player add scrobble_enabled bool default true;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddUserPrefsPlayerScrobblerEnabled(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddReferentialIntegrityToUserProps, downAddReferentialIntegrityToUserProps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddReferentialIntegrityToUserProps(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table user_props_dg_tmp
|
|
||||||
(
|
|
||||||
user_id varchar not null
|
|
||||||
constraint user_props_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
key varchar not null,
|
|
||||||
value varchar,
|
|
||||||
constraint user_props_pk
|
|
||||||
primary key (user_id, key)
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into user_props_dg_tmp(user_id, key, value) select user_id, key, value from user_props;
|
|
||||||
|
|
||||||
drop table user_props;
|
|
||||||
|
|
||||||
alter table user_props_dg_tmp rename to user_props;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddReferentialIntegrityToUserProps(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddScrobbleBuffer, downAddScrobbleBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddScrobbleBuffer(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table if not exists scrobble_buffer
|
|
||||||
(
|
|
||||||
user_id varchar not null
|
|
||||||
constraint scrobble_buffer_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
service varchar not null,
|
|
||||||
media_file_id varchar not null
|
|
||||||
constraint scrobble_buffer_media_file_id_fk
|
|
||||||
references media_file
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
play_time datetime not null,
|
|
||||||
enqueue_time datetime not null default current_timestamp,
|
|
||||||
constraint scrobble_buffer_pk
|
|
||||||
unique (user_id, service, media_file_id, play_time, user_id)
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddScrobbleBuffer(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddGenreTables, downAddGenreTables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddGenreTables(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "A full rescan will be performed to import multiple genres!")
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table if not exists genre
|
|
||||||
(
|
|
||||||
id varchar not null primary key,
|
|
||||||
name varchar not null,
|
|
||||||
constraint genre_name_ux
|
|
||||||
unique (name)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists album_genres
|
|
||||||
(
|
|
||||||
album_id varchar default null not null
|
|
||||||
references album
|
|
||||||
on delete cascade,
|
|
||||||
genre_id varchar default null not null
|
|
||||||
references genre
|
|
||||||
on delete cascade,
|
|
||||||
constraint album_genre_ux
|
|
||||||
unique (album_id, genre_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists media_file_genres
|
|
||||||
(
|
|
||||||
media_file_id varchar default null not null
|
|
||||||
references media_file
|
|
||||||
on delete cascade,
|
|
||||||
genre_id varchar default null not null
|
|
||||||
references genre
|
|
||||||
on delete cascade,
|
|
||||||
constraint media_file_genre_ux
|
|
||||||
unique (media_file_id, genre_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists artist_genres
|
|
||||||
(
|
|
||||||
artist_id varchar default null not null
|
|
||||||
references artist
|
|
||||||
on delete cascade,
|
|
||||||
genre_id varchar default null not null
|
|
||||||
references genre
|
|
||||||
on delete cascade,
|
|
||||||
constraint artist_genre_ux
|
|
||||||
unique (artist_id, genre_id)
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddGenreTables(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddMediafileChannels, downAddMediafileChannels)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddMediafileChannels(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add channels integer;
|
|
||||||
|
|
||||||
create index if not exists media_file_channels
|
|
||||||
on media_file (channels);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddMediafileChannels(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddSmartPlaylist, downAddSmartPlaylist)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddSmartPlaylist(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table playlist
|
|
||||||
add column rules varchar null;
|
|
||||||
alter table playlist
|
|
||||||
add column evaluated_at datetime null;
|
|
||||||
create index if not exists playlist_evaluated_at
|
|
||||||
on playlist(evaluated_at);
|
|
||||||
|
|
||||||
create table playlist_fields (
|
|
||||||
field varchar(255) not null,
|
|
||||||
playlist_id varchar(255) not null
|
|
||||||
constraint playlist_fields_playlist_id_fk
|
|
||||||
references playlist
|
|
||||||
on update cascade on delete cascade
|
|
||||||
);
|
|
||||||
create unique index playlist_fields_idx
|
|
||||||
on playlist_fields (field, playlist_id);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddSmartPlaylist(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/deluan/sanitize"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddOrderTitleToMediaFile, downAddOrderTitleToMediaFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddOrderTitleToMediaFile(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table main.media_file
|
|
||||||
add order_title varchar null collate NOCASE;
|
|
||||||
create index if not exists media_file_order_title
|
|
||||||
on media_file (order_title);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return upAddOrderTitleToMediaFile_populateOrderTitle(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
//goland:noinspection GoSnakeCaseUsage
|
|
||||||
func upAddOrderTitleToMediaFile_populateOrderTitle(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
rows, err := tx.QueryContext(ctx, `select id, title from media_file`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update media_file set order_title = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id, title string
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &title)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
orderTitle := strings.TrimSpace(sanitize.Accents(title))
|
|
||||||
_, err = stmt.Exec(orderTitle, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error setting media_file's order_title", "title", title, "id", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddOrderTitleToMediaFile(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/navidrome/navidrome/utils/str"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upUnescapeLyricsAndComments, downUnescapeLyricsAndComments)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upUnescapeLyricsAndComments(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
rows, err := tx.QueryContext(ctx, `select id, comment, lyrics, title from media_file`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update media_file set comment = ?, lyrics = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id, title string
|
|
||||||
var comment, lyrics sql.NullString
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &comment, &lyrics, &title)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newComment := str.SanitizeText(comment.String)
|
|
||||||
newLyrics := str.SanitizeText(lyrics.String)
|
|
||||||
_, err = stmt.Exec(newComment, newLyrics, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error unescaping media_file's lyrics and comments", "title", title, "id", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func downUnescapeLyricsAndComments(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddUseridToPlaylist, downAddUseridToPlaylist)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddUseridToPlaylist(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
path string default '' not null,
|
|
||||||
sync bool default false not null,
|
|
||||||
size integer default 0 not null,
|
|
||||||
rules varchar,
|
|
||||||
evaluated_at datetime,
|
|
||||||
owner_id varchar(255) not null
|
|
||||||
constraint playlist_user_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at, owner_id)
|
|
||||||
select id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at,
|
|
||||||
(select id from user where user_name = owner) as user_id from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
alter table playlist_dg_tmp rename to playlist;
|
|
||||||
create index playlist_created_at
|
|
||||||
on playlist (created_at);
|
|
||||||
create index playlist_evaluated_at
|
|
||||||
on playlist (evaluated_at);
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
create index playlist_size
|
|
||||||
on playlist (size);
|
|
||||||
create index playlist_updated_at
|
|
||||||
on playlist (updated_at);
|
|
||||||
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddUseridToPlaylist(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddAlphabeticalByArtistIndex, downAddAlphabeticalByArtistIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddAlphabeticalByArtistIndex(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create index album_alphabetical_by_artist
|
|
||||||
ON album(compilation, order_album_artist_name, order_album_name)
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddAlphabeticalByArtistIndex(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upRemoveInvalidArtistIds, downRemoveInvalidArtistIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upRemoveInvalidArtistIds(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
update media_file set artist_id = '' where not exists(select 1 from artist where id = artist_id)
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downRemoveInvalidArtistIds(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddMusicbrainzReleaseTrackId, downAddMusicbrainzReleaseTrackId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddMusicbrainzReleaseTrackId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add mbz_release_track_id varchar(255);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddMusicbrainzReleaseTrackId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddAlbumImagePaths, downAddAlbumImagePaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddAlbumImagePaths(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table main.album add image_files varchar;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import all album images")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddAlbumImagePaths(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upRemoveCoverArtId, downRemoveCoverArtId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upRemoveCoverArtId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table album drop column cover_art_id;
|
|
||||||
alter table album rename column cover_art_path to embed_art_path
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import all album images")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downRemoveCoverArtId(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddAlbumPaths, downAddAlbumPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddAlbumPaths(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table album add paths varchar;`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gosec
|
|
||||||
rows, err := tx.QueryContext(ctx, `
|
|
||||||
select album_id, group_concat(path, '`+consts.Zwsp+`') from media_file group by album_id
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update album set paths = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id, filePaths string
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &filePaths)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
paths := upAddAlbumPathsDirs(filePaths)
|
|
||||||
_, err = stmt.Exec(paths, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error updating album's paths", "paths", paths, "id", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddAlbumPathsDirs(filePaths string) string {
|
|
||||||
allPaths := strings.Split(filePaths, consts.Zwsp)
|
|
||||||
var dirs []string
|
|
||||||
for _, p := range allPaths {
|
|
||||||
dir, _ := filepath.Split(p)
|
|
||||||
dirs = append(dirs, filepath.Clean(dir))
|
|
||||||
}
|
|
||||||
slices.Sort(dirs)
|
|
||||||
dirs = slices.Compact(dirs)
|
|
||||||
return strings.Join(dirs, string(filepath.ListSeparator))
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddAlbumPaths(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upTouchPlaylists, downTouchPlaylists)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upTouchPlaylists(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `update playlist set updated_at = datetime('now');`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downTouchPlaylists(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upCreateInternetRadio, downCreateInternetRadio)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upCreateInternetRadio(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table if not exists radio
|
|
||||||
(
|
|
||||||
id varchar(255) not null primary key,
|
|
||||||
name varchar not null unique,
|
|
||||||
stream_url varchar not null,
|
|
||||||
home_page_url varchar default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downCreateInternetRadio(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddReplaygainMetadata, downAddReplaygainMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddReplaygainMetadata(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file add
|
|
||||||
rg_album_gain real;
|
|
||||||
alter table media_file add
|
|
||||||
rg_album_peak real;
|
|
||||||
alter table media_file add
|
|
||||||
rg_track_gain real;
|
|
||||||
alter table media_file add
|
|
||||||
rg_track_peak real;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddReplaygainMetadata(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddAlbumInfo, downAddAlbumInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddAlbumInfo(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table album
|
|
||||||
add description varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add small_image_url varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add medium_image_url varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add large_image_url varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add external_url varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add external_info_updated_at datetime;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddAlbumInfo(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddMissingShareInfo, downAddMissingShareInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddMissingShareInfo(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
drop table if exists share;
|
|
||||||
create table share
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
description varchar(255),
|
|
||||||
expires_at datetime,
|
|
||||||
last_visited_at datetime,
|
|
||||||
resource_ids varchar not null,
|
|
||||||
resource_type varchar(255) not null,
|
|
||||||
contents varchar,
|
|
||||||
format varchar,
|
|
||||||
max_bit_rate integer,
|
|
||||||
visit_count integer default 0,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
user_id varchar(255) not null
|
|
||||||
constraint share_user_id_fk
|
|
||||||
references user
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddMissingShareInfo(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upChangePathListSeparator, downChangePathListSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upChangePathListSeparator(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
//nolint:gosec
|
|
||||||
rows, err := tx.QueryContext(ctx, `
|
|
||||||
select album_id, group_concat(path, '`+consts.Zwsp+`') from media_file group by album_id
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update album set paths = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id, filePaths string
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &filePaths)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
paths := upChangePathListSeparatorDirs(filePaths)
|
|
||||||
_, err = stmt.Exec(paths, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error updating album's paths", "paths", paths, "id", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func upChangePathListSeparatorDirs(filePaths string) string {
|
|
||||||
allPaths := strings.Split(filePaths, consts.Zwsp)
|
|
||||||
var dirs []string
|
|
||||||
for _, p := range allPaths {
|
|
||||||
dir, _ := filepath.Split(p)
|
|
||||||
dirs = append(dirs, filepath.Clean(dir))
|
|
||||||
}
|
|
||||||
slices.Sort(dirs)
|
|
||||||
dirs = slices.Compact(dirs)
|
|
||||||
return strings.Join(dirs, consts.Zwsp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downChangePathListSeparator(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/consts"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upChangeImageFilesListSeparator, downChangeImageFilesListSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upChangeImageFilesListSeparator(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
rows, err := tx.QueryContext(ctx, `select id, image_files from album`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("update album set image_files = ? where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id string
|
|
||||||
var imageFiles sql.NullString
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &imageFiles)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
files := upChangeImageFilesListSeparatorDirs(imageFiles.String)
|
|
||||||
if files == imageFiles.String {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = stmt.Exec(files, id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error updating album's image file list", "files", files, "id", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func upChangeImageFilesListSeparatorDirs(filePaths string) string {
|
|
||||||
allPaths := filepath.SplitList(filePaths)
|
|
||||||
slices.Sort(allPaths)
|
|
||||||
allPaths = slices.Compact(allPaths)
|
|
||||||
return strings.Join(allPaths, consts.Zwsp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downChangeImageFilesListSeparator(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddDownloadToShare, downAddDownloadToShare)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddDownloadToShare(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table share
|
|
||||||
add downloadable bool not null default false;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddDownloadToShare(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
-- This file has intentionally no SQL logic. It is here to avoid an error in the linter:
|
|
||||||
-- db/db.go:23:4: invalid go:embed: build system did not supply embed configuration (typecheck)
|
|
||||||
--
|
|
||||||
|
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
SELECT 'up SQL query';
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
SELECT 'down SQL query';
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddRelRecYear, downAddRelRecYear)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddRelRecYear(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add date varchar(255) default '' not null;
|
|
||||||
alter table media_file
|
|
||||||
add original_year int default 0 not null;
|
|
||||||
alter table media_file
|
|
||||||
add original_date varchar(255) default '' not null;
|
|
||||||
alter table media_file
|
|
||||||
add release_year int default 0 not null;
|
|
||||||
alter table media_file
|
|
||||||
add release_date varchar(255) default '' not null;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add date varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add min_original_year int default 0 not null;
|
|
||||||
alter table album
|
|
||||||
add max_original_year int default 0 not null;
|
|
||||||
alter table album
|
|
||||||
add original_date varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add release_date varchar(255) default '' not null;
|
|
||||||
alter table album
|
|
||||||
add releases integer default 0 not null;
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
notice(ctx, tx, "A full rescan needs to be performed to import more tags")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddRelRecYear(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upRenameMusicbrainzRecordingId, downRenameMusicbrainzRecordingId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upRenameMusicbrainzRecordingId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
rename column mbz_track_id to mbz_recording_id;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downRenameMusicbrainzRecordingId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
rename column mbz_recording_id to mbz_track_id;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddDiscToAlbum, downAddDiscToAlbum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddDiscToAlbum(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table album add discs JSONB default '{}';`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
update album set discs = t.discs
|
|
||||||
from (select album_id, json_group_object(disc_number, disc_subtitle) as discs
|
|
||||||
from (select distinct album_id, disc_number, disc_subtitle
|
|
||||||
from media_file
|
|
||||||
where disc_number > 0
|
|
||||||
order by album_id, disc_number)
|
|
||||||
group by album_id
|
|
||||||
having discs <> '{"1":""}') as t
|
|
||||||
where album.id = t.album_id;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddDiscToAlbum(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table album drop discs;`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAlterLyricColumn, downAlterLyricColumn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAlterLyricColumn(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table media_file rename COLUMN lyrics TO lyrics_old`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `alter table media_file add lyrics JSONB default '[]';`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare(`update media_file SET lyrics = ? where id = ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := tx.QueryContext(ctx, `select id, lyrics_old FROM media_file WHERE lyrics_old <> '';`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id string
|
|
||||||
var lyrics sql.NullString
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&id, &lyrics)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !lyrics.Valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, err := model.ParseLyrics(ctx, ".lrc", "xxx", []byte(lyrics.String))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
text, err := json.Marshal(parsed)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = stmt.Exec(string(text), id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rows.Err()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `ALTER TABLE media_file DROP COLUMN lyrics_old;`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
notice(ctx, tx, "A full rescan should be performed to pick up additional lyrics (existing lyrics have been preserved)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAlterLyricColumn(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,563 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(Up20240122223340, Down20240122223340)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Up20240122223340(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
drop index if exists album_alphabetical_by_artist;
|
|
||||||
drop index if exists album_order_album_name;
|
|
||||||
drop index if exists album_order_album_artist_name;
|
|
||||||
drop index if exists album_mbz_album_type;
|
|
||||||
|
|
||||||
drop index if exists artist_order_artist_name;
|
|
||||||
|
|
||||||
drop index if exists media_file_order_album_name;
|
|
||||||
drop index if exists media_file_order_artist_name;
|
|
||||||
drop index if exists media_file_order_title;
|
|
||||||
drop index if exists media_file_bpm;
|
|
||||||
drop index if exists media_file_channels;
|
|
||||||
drop index if exists media_file_mbz_track_id;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add image_files_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set image_files_new = image_files
|
|
||||||
where image_files is not null;
|
|
||||||
alter table album
|
|
||||||
drop image_files;
|
|
||||||
alter table album
|
|
||||||
rename image_files_new to image_files;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add order_album_name_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set order_album_name_new = order_album_name
|
|
||||||
where order_album_name is not null;
|
|
||||||
alter table album
|
|
||||||
drop order_album_name;
|
|
||||||
alter table album
|
|
||||||
rename order_album_name_new to order_album_name;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add order_album_artist_name_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set order_album_artist_name_new = order_album_artist_name
|
|
||||||
where order_album_artist_name is not null;
|
|
||||||
alter table album
|
|
||||||
drop order_album_artist_name;
|
|
||||||
alter table album
|
|
||||||
rename order_album_artist_name_new to order_album_artist_name;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add sort_album_name_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set sort_album_name_new = sort_album_name
|
|
||||||
where sort_album_name is not null;
|
|
||||||
alter table album
|
|
||||||
drop sort_album_name;
|
|
||||||
alter table album
|
|
||||||
rename sort_album_name_new to sort_album_name;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add sort_artist_name_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set sort_artist_name_new = sort_artist_name
|
|
||||||
where sort_artist_name is not null;
|
|
||||||
alter table album
|
|
||||||
drop sort_artist_name;
|
|
||||||
alter table album
|
|
||||||
rename sort_artist_name_new to sort_artist_name;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add sort_album_artist_name_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set sort_album_artist_name_new = sort_album_artist_name
|
|
||||||
where sort_album_artist_name is not null;
|
|
||||||
alter table album
|
|
||||||
drop sort_album_artist_name;
|
|
||||||
alter table album
|
|
||||||
rename sort_album_artist_name_new to sort_album_artist_name;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add catalog_num_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set catalog_num_new = catalog_num
|
|
||||||
where catalog_num is not null;
|
|
||||||
alter table album
|
|
||||||
drop catalog_num;
|
|
||||||
alter table album
|
|
||||||
rename catalog_num_new to catalog_num;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add comment_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set comment_new = comment
|
|
||||||
where comment is not null;
|
|
||||||
alter table album
|
|
||||||
drop comment;
|
|
||||||
alter table album
|
|
||||||
rename comment_new to comment;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add paths_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set paths_new = paths
|
|
||||||
where paths is not null;
|
|
||||||
alter table album
|
|
||||||
drop paths;
|
|
||||||
alter table album
|
|
||||||
rename paths_new to paths;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add mbz_album_id_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set mbz_album_id_new = mbz_album_id
|
|
||||||
where mbz_album_id is not null;
|
|
||||||
alter table album
|
|
||||||
drop mbz_album_id;
|
|
||||||
alter table album
|
|
||||||
rename mbz_album_id_new to mbz_album_id;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add mbz_album_artist_id_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set mbz_album_artist_id_new = mbz_album_artist_id
|
|
||||||
where mbz_album_artist_id is not null;
|
|
||||||
alter table album
|
|
||||||
drop mbz_album_artist_id;
|
|
||||||
alter table album
|
|
||||||
rename mbz_album_artist_id_new to mbz_album_artist_id;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add mbz_album_type_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set mbz_album_type_new = mbz_album_type
|
|
||||||
where mbz_album_type is not null;
|
|
||||||
alter table album
|
|
||||||
drop mbz_album_type;
|
|
||||||
alter table album
|
|
||||||
rename mbz_album_type_new to mbz_album_type;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add mbz_album_comment_new varchar not null default '';
|
|
||||||
update album
|
|
||||||
set mbz_album_comment_new = mbz_album_comment
|
|
||||||
where mbz_album_comment is not null;
|
|
||||||
alter table album
|
|
||||||
drop mbz_album_comment;
|
|
||||||
alter table album
|
|
||||||
rename mbz_album_comment_new to mbz_album_comment;
|
|
||||||
|
|
||||||
alter table album
|
|
||||||
add discs_new jsonb not null default '{}';
|
|
||||||
update album
|
|
||||||
set discs_new = discs
|
|
||||||
where discs is not null;
|
|
||||||
alter table album
|
|
||||||
drop discs;
|
|
||||||
alter table album
|
|
||||||
rename discs_new to discs;
|
|
||||||
|
|
||||||
-- ARTIST
|
|
||||||
alter table artist
|
|
||||||
add order_artist_name_new varchar not null default '';
|
|
||||||
update artist
|
|
||||||
set order_artist_name_new = order_artist_name
|
|
||||||
where order_artist_name is not null;
|
|
||||||
alter table artist
|
|
||||||
drop order_artist_name;
|
|
||||||
alter table artist
|
|
||||||
rename order_artist_name_new to order_artist_name;
|
|
||||||
|
|
||||||
alter table artist
|
|
||||||
add sort_artist_name_new varchar not null default '';
|
|
||||||
update artist
|
|
||||||
set sort_artist_name_new = sort_artist_name
|
|
||||||
where sort_artist_name is not null;
|
|
||||||
alter table artist
|
|
||||||
drop sort_artist_name;
|
|
||||||
alter table artist
|
|
||||||
rename sort_artist_name_new to sort_artist_name;
|
|
||||||
|
|
||||||
alter table artist
|
|
||||||
add mbz_artist_id_new varchar not null default '';
|
|
||||||
update artist
|
|
||||||
set mbz_artist_id_new = mbz_artist_id
|
|
||||||
where mbz_artist_id is not null;
|
|
||||||
alter table artist
|
|
||||||
drop mbz_artist_id;
|
|
||||||
alter table artist
|
|
||||||
rename mbz_artist_id_new to mbz_artist_id;
|
|
||||||
|
|
||||||
-- MEDIA_FILE
|
|
||||||
alter table media_file
|
|
||||||
add order_album_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set order_album_name_new = order_album_name
|
|
||||||
where order_album_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop order_album_name;
|
|
||||||
alter table media_file
|
|
||||||
rename order_album_name_new to order_album_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add order_album_artist_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set order_album_artist_name_new = order_album_artist_name
|
|
||||||
where order_album_artist_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop order_album_artist_name;
|
|
||||||
alter table media_file
|
|
||||||
rename order_album_artist_name_new to order_album_artist_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add order_artist_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set order_artist_name_new = order_artist_name
|
|
||||||
where order_artist_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop order_artist_name;
|
|
||||||
alter table media_file
|
|
||||||
rename order_artist_name_new to order_artist_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add sort_album_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set sort_album_name_new = sort_album_name
|
|
||||||
where sort_album_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop sort_album_name;
|
|
||||||
alter table media_file
|
|
||||||
rename sort_album_name_new to sort_album_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add sort_artist_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set sort_artist_name_new = sort_artist_name
|
|
||||||
where sort_artist_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop sort_artist_name;
|
|
||||||
alter table media_file
|
|
||||||
rename sort_artist_name_new to sort_artist_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add sort_album_artist_name_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set sort_album_artist_name_new = sort_album_artist_name
|
|
||||||
where sort_album_artist_name is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop sort_album_artist_name;
|
|
||||||
alter table media_file
|
|
||||||
rename sort_album_artist_name_new to sort_album_artist_name;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add sort_title_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set sort_title_new = sort_title
|
|
||||||
where sort_title is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop sort_title;
|
|
||||||
alter table media_file
|
|
||||||
rename sort_title_new to sort_title;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add disc_subtitle_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set disc_subtitle_new = disc_subtitle
|
|
||||||
where disc_subtitle is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop disc_subtitle;
|
|
||||||
alter table media_file
|
|
||||||
rename disc_subtitle_new to disc_subtitle;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add catalog_num_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set catalog_num_new = catalog_num
|
|
||||||
where catalog_num is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop catalog_num;
|
|
||||||
alter table media_file
|
|
||||||
rename catalog_num_new to catalog_num;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add comment_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set comment_new = comment
|
|
||||||
where comment is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop comment;
|
|
||||||
alter table media_file
|
|
||||||
rename comment_new to comment;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add order_title_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set order_title_new = order_title
|
|
||||||
where order_title is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop order_title;
|
|
||||||
alter table media_file
|
|
||||||
rename order_title_new to order_title;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_recording_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_recording_id_new = mbz_recording_id
|
|
||||||
where mbz_recording_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_recording_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_recording_id_new to mbz_recording_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_album_id_new = mbz_album_id
|
|
||||||
where mbz_album_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_album_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_album_id_new to mbz_album_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_artist_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_artist_id_new = mbz_artist_id
|
|
||||||
where mbz_artist_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_artist_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_artist_id_new to mbz_artist_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_artist_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_artist_id_new = mbz_artist_id
|
|
||||||
where mbz_artist_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_artist_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_artist_id_new to mbz_artist_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_artist_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_album_artist_id_new = mbz_album_artist_id
|
|
||||||
where mbz_album_artist_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_album_artist_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_album_artist_id_new to mbz_album_artist_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_type_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_album_type_new = mbz_album_type
|
|
||||||
where mbz_album_type is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_album_type;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_album_type_new to mbz_album_type;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_album_comment_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_album_comment_new = mbz_album_comment
|
|
||||||
where mbz_album_comment is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_album_comment;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_album_comment_new to mbz_album_comment;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add mbz_release_track_id_new varchar not null default '';
|
|
||||||
update media_file
|
|
||||||
set mbz_release_track_id_new = mbz_release_track_id
|
|
||||||
where mbz_release_track_id is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop mbz_release_track_id;
|
|
||||||
alter table media_file
|
|
||||||
rename mbz_release_track_id_new to mbz_release_track_id;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add bpm_new integer not null default 0;
|
|
||||||
update media_file
|
|
||||||
set bpm_new = bpm
|
|
||||||
where bpm is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop bpm;
|
|
||||||
alter table media_file
|
|
||||||
rename bpm_new to bpm;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add channels_new integer not null default 0;
|
|
||||||
update media_file
|
|
||||||
set channels_new = channels
|
|
||||||
where channels is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop channels;
|
|
||||||
alter table media_file
|
|
||||||
rename channels_new to channels;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add rg_album_gain_new real not null default 0;
|
|
||||||
update media_file
|
|
||||||
set rg_album_gain_new = rg_album_gain
|
|
||||||
where rg_album_gain is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop rg_album_gain;
|
|
||||||
alter table media_file
|
|
||||||
rename rg_album_gain_new to rg_album_gain;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add rg_album_peak_new real not null default 0;
|
|
||||||
update media_file
|
|
||||||
set rg_album_peak_new = rg_album_peak
|
|
||||||
where rg_album_peak is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop rg_album_peak;
|
|
||||||
alter table media_file
|
|
||||||
rename rg_album_peak_new to rg_album_peak;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add rg_track_gain_new real not null default 0;
|
|
||||||
update media_file
|
|
||||||
set rg_track_gain_new = rg_track_gain
|
|
||||||
where rg_track_gain is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop rg_track_gain;
|
|
||||||
alter table media_file
|
|
||||||
rename rg_track_gain_new to rg_track_gain;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add rg_track_peak_new real not null default 0;
|
|
||||||
update media_file
|
|
||||||
set rg_track_peak_new = rg_track_peak
|
|
||||||
where rg_track_peak is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop rg_track_peak;
|
|
||||||
alter table media_file
|
|
||||||
rename rg_track_peak_new to rg_track_peak;
|
|
||||||
|
|
||||||
alter table media_file
|
|
||||||
add lyrics_new jsonb not null default '[]';
|
|
||||||
update media_file
|
|
||||||
set lyrics_new = lyrics
|
|
||||||
where lyrics is not null;
|
|
||||||
alter table media_file
|
|
||||||
drop lyrics;
|
|
||||||
alter table media_file
|
|
||||||
rename lyrics_new to lyrics;
|
|
||||||
|
|
||||||
-- SHARE
|
|
||||||
alter table share
|
|
||||||
add description_new varchar not null default '';
|
|
||||||
update share
|
|
||||||
set description_new = description
|
|
||||||
where description is not null;
|
|
||||||
alter table share
|
|
||||||
drop description;
|
|
||||||
alter table share
|
|
||||||
rename description_new to description;
|
|
||||||
|
|
||||||
alter table share
|
|
||||||
add resource_type_new varchar not null default '';
|
|
||||||
update share
|
|
||||||
set resource_type_new = resource_type
|
|
||||||
where resource_type is not null;
|
|
||||||
alter table share
|
|
||||||
drop resource_type;
|
|
||||||
alter table share
|
|
||||||
rename resource_type_new to resource_type;
|
|
||||||
|
|
||||||
alter table share
|
|
||||||
add contents_new varchar not null default '';
|
|
||||||
update share
|
|
||||||
set contents_new = contents
|
|
||||||
where contents is not null;
|
|
||||||
alter table share
|
|
||||||
drop contents;
|
|
||||||
alter table share
|
|
||||||
rename contents_new to contents;
|
|
||||||
|
|
||||||
alter table share
|
|
||||||
add format_new varchar not null default '';
|
|
||||||
update share
|
|
||||||
set format_new = format
|
|
||||||
where format is not null;
|
|
||||||
alter table share
|
|
||||||
drop format;
|
|
||||||
alter table share
|
|
||||||
rename format_new to format;
|
|
||||||
|
|
||||||
alter table share
|
|
||||||
add max_bit_rate_new integer not null default 0;
|
|
||||||
update share
|
|
||||||
set max_bit_rate_new = max_bit_rate
|
|
||||||
where max_bit_rate is not null;
|
|
||||||
alter table share
|
|
||||||
drop max_bit_rate;
|
|
||||||
alter table share
|
|
||||||
rename max_bit_rate_new to max_bit_rate;
|
|
||||||
|
|
||||||
alter table share
|
|
||||||
add visit_count_new integer not null default 0;
|
|
||||||
update share
|
|
||||||
set visit_count_new = visit_count
|
|
||||||
where visit_count is not null;
|
|
||||||
alter table share
|
|
||||||
drop visit_count;
|
|
||||||
alter table share
|
|
||||||
rename visit_count_new to visit_count;
|
|
||||||
|
|
||||||
-- INDEX
|
|
||||||
create index album_alphabetical_by_artist
|
|
||||||
on album (compilation, order_album_artist_name, order_album_name);
|
|
||||||
|
|
||||||
create index album_order_album_name
|
|
||||||
on album (order_album_name);
|
|
||||||
|
|
||||||
create index album_order_album_artist_name
|
|
||||||
on album (order_album_artist_name);
|
|
||||||
|
|
||||||
create index album_mbz_album_type
|
|
||||||
on album (mbz_album_type);
|
|
||||||
|
|
||||||
create index artist_order_artist_name
|
|
||||||
on artist (order_artist_name);
|
|
||||||
|
|
||||||
create index media_file_order_album_name
|
|
||||||
on media_file (order_album_name);
|
|
||||||
|
|
||||||
create index media_file_order_artist_name
|
|
||||||
on media_file (order_artist_name);
|
|
||||||
|
|
||||||
create index media_file_order_title
|
|
||||||
on media_file (order_title);
|
|
||||||
|
|
||||||
create index media_file_bpm
|
|
||||||
on media_file (bpm);
|
|
||||||
|
|
||||||
create index media_file_channels
|
|
||||||
on media_file (channels);
|
|
||||||
|
|
||||||
create index media_file_mbz_track_id
|
|
||||||
on media_file (mbz_recording_id);
|
|
||||||
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Down20240122223340(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddIdToScrobbleBuffer, downAddIdToScrobbleBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddIdToScrobbleBuffer(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
delete from scrobble_buffer where user_id <> '';
|
|
||||||
alter table scrobble_buffer add id varchar not null default '';
|
|
||||||
create unique index scrobble_buffer_id_ix
|
|
||||||
on scrobble_buffer (id);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddIdToScrobbleBuffer(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
drop index scrobble_buffer_id_ix;
|
|
||||||
alter table scrobble_buffer drop id;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddSampleRate, downAddSampleRate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddSampleRate(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file
|
|
||||||
add sample_rate integer not null default 0;
|
|
||||||
|
|
||||||
create index if not exists media_file_sample_rate
|
|
||||||
on media_file (sample_rate);
|
|
||||||
`)
|
|
||||||
notice(ctx, tx, "A full rescan should be performed to pick up additional tags")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddSampleRate(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table media_file drop sample_rate;`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddLibraryTable, downAddLibraryTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddLibraryTable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table library (
|
|
||||||
id integer primary key autoincrement,
|
|
||||||
name text not null unique,
|
|
||||||
path text not null unique,
|
|
||||||
remote_path text null default '',
|
|
||||||
last_scan_at datetime not null default '0000-00-00 00:00:00',
|
|
||||||
updated_at datetime not null default current_timestamp,
|
|
||||||
created_at datetime not null default current_timestamp
|
|
||||||
);`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
|
||||||
insert into library(id, name, path) values(1, 'Music Library', '%s');
|
|
||||||
delete from property where id like 'LastScan-%%';
|
|
||||||
`, conf.Server.MusicFolder))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
alter table media_file add column library_id integer not null default 1
|
|
||||||
references library(id) on delete cascade;
|
|
||||||
alter table album add column library_id integer not null default 1
|
|
||||||
references library(id) on delete cascade;
|
|
||||||
|
|
||||||
create table if not exists library_artist
|
|
||||||
(
|
|
||||||
library_id integer not null default 1
|
|
||||||
references library(id)
|
|
||||||
on delete cascade,
|
|
||||||
artist_id varchar not null default null
|
|
||||||
references artist(id)
|
|
||||||
on delete cascade,
|
|
||||||
constraint library_artist_ux
|
|
||||||
unique (library_id, artist_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into library_artist(library_id, artist_id) select 1, id from artist;
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddLibraryTable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table media_file drop column library_id;
|
|
||||||
alter table album drop column library_id;
|
|
||||||
drop table library_artist;
|
|
||||||
drop table library;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upRemoveAnnotationId, downRemoveAnnotationId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upRemoveAnnotationId(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table annotation_dg_tmp
|
|
||||||
(
|
|
||||||
user_id varchar(255) default '' not null,
|
|
||||||
item_id varchar(255) default '' not null,
|
|
||||||
item_type varchar(255) default '' not null,
|
|
||||||
play_count integer default 0,
|
|
||||||
play_date datetime,
|
|
||||||
rating integer default 0,
|
|
||||||
starred bool default FALSE not null,
|
|
||||||
starred_at datetime,
|
|
||||||
unique (user_id, item_id, item_type)
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into annotation_dg_tmp(user_id, item_id, item_type, play_count, play_date, rating, starred, starred_at)
|
|
||||||
select user_id,
|
|
||||||
item_id,
|
|
||||||
item_type,
|
|
||||||
play_count,
|
|
||||||
play_date,
|
|
||||||
rating,
|
|
||||||
starred,
|
|
||||||
starred_at
|
|
||||||
from annotation;
|
|
||||||
|
|
||||||
drop table annotation;
|
|
||||||
|
|
||||||
alter table annotation_dg_tmp
|
|
||||||
rename to annotation;
|
|
||||||
|
|
||||||
create index annotation_play_count
|
|
||||||
on annotation (play_count);
|
|
||||||
|
|
||||||
create index annotation_play_date
|
|
||||||
on annotation (play_date);
|
|
||||||
|
|
||||||
create index annotation_rating
|
|
||||||
on annotation (rating);
|
|
||||||
|
|
||||||
create index annotation_starred
|
|
||||||
on annotation (starred);
|
|
||||||
|
|
||||||
create index annotation_starred_at
|
|
||||||
on annotation (starred_at);
|
|
||||||
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downRemoveAnnotationId(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upPlayerUseUserIdOverUsername, downPlayerUseUserIdOverUsername)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upPlayerUseUserIdOverUsername(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
CREATE TABLE player_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar not null,
|
|
||||||
user_agent varchar,
|
|
||||||
user_id varchar not null
|
|
||||||
references user (id)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
client varchar not null,
|
|
||||||
ip varchar,
|
|
||||||
last_seen timestamp,
|
|
||||||
max_bit_rate int default 0,
|
|
||||||
transcoding_id varchar,
|
|
||||||
report_real_path bool default FALSE not null,
|
|
||||||
scrobble_enabled bool default true
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO player_dg_tmp(
|
|
||||||
id, name, user_agent, user_id, client, ip, last_seen, max_bit_rate,
|
|
||||||
transcoding_id, report_real_path, scrobble_enabled
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
id, name, user_agent,
|
|
||||||
IFNULL(
|
|
||||||
(select id from user where user_name = player.user_name), 'UNKNOWN_USERNAME'
|
|
||||||
),
|
|
||||||
client, ip_address, last_seen, max_bit_rate, transcoding_id, report_real_path, scrobble_enabled
|
|
||||||
FROM player;
|
|
||||||
|
|
||||||
DELETE FROM player_dg_tmp WHERE user_id = 'UNKNOWN_USERNAME';
|
|
||||||
DROP TABLE player;
|
|
||||||
ALTER TABLE player_dg_tmp RENAME TO player;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS player_match
|
|
||||||
on player (client, user_agent, user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS player_name
|
|
||||||
on player (name);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downPlayerUseUserIdOverUsername(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
create index if not exists media_file_sort_title on media_file(coalesce(nullif(sort_title,''),order_title));
|
|
||||||
create index if not exists album_sort_name on album(coalesce(nullif(sort_album_name,''),order_album_name));
|
|
||||||
create index if not exists artist_sort_name on artist(coalesce(nullif(sort_artist_name,''),order_artist_name));
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
drop index if exists media_file_sort_title;
|
|
||||||
drop index if exists album_sort_name;
|
|
||||||
drop index if exists artist_sort_name;
|
|
||||||
|
|
@ -1,512 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
--region Artist Table
|
|
||||||
create table artist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
album_count integer default 0 not null,
|
|
||||||
full_text varchar(255) default '',
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
size integer default 0 not null,
|
|
||||||
biography varchar(255) default '' not null,
|
|
||||||
small_image_url varchar(255) default '' not null,
|
|
||||||
medium_image_url varchar(255) default '' not null,
|
|
||||||
large_image_url varchar(255) default '' not null,
|
|
||||||
similar_artists varchar(255) default '' not null,
|
|
||||||
external_url varchar(255) default '' not null,
|
|
||||||
external_info_updated_at datetime,
|
|
||||||
order_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
mbz_artist_id varchar default '' not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into artist_dg_tmp(id, name, album_count, full_text, song_count, size, biography, small_image_url,
|
|
||||||
medium_image_url, large_image_url, similar_artists, external_url, external_info_updated_at,
|
|
||||||
order_artist_name, sort_artist_name, mbz_artist_id)
|
|
||||||
select id,
|
|
||||||
name,
|
|
||||||
album_count,
|
|
||||||
full_text,
|
|
||||||
song_count,
|
|
||||||
size,
|
|
||||||
biography,
|
|
||||||
small_image_url,
|
|
||||||
medium_image_url,
|
|
||||||
large_image_url,
|
|
||||||
similar_artists,
|
|
||||||
external_url,
|
|
||||||
external_info_updated_at,
|
|
||||||
order_artist_name,
|
|
||||||
sort_artist_name,
|
|
||||||
mbz_artist_id
|
|
||||||
from artist;
|
|
||||||
|
|
||||||
drop table artist;
|
|
||||||
|
|
||||||
alter table artist_dg_tmp
|
|
||||||
rename to artist;
|
|
||||||
|
|
||||||
create index artist_full_text
|
|
||||||
on artist (full_text);
|
|
||||||
|
|
||||||
create index artist_name
|
|
||||||
on artist (name);
|
|
||||||
|
|
||||||
create index artist_order_artist_name
|
|
||||||
on artist (order_artist_name);
|
|
||||||
|
|
||||||
create index artist_size
|
|
||||||
on artist (size);
|
|
||||||
|
|
||||||
create index artist_sort_name
|
|
||||||
on artist (coalesce(nullif(sort_artist_name,''),order_artist_name) collate NOCASE);
|
|
||||||
|
|
||||||
--endregion
|
|
||||||
|
|
||||||
--region Album Table
|
|
||||||
create table album_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
embed_art_path varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
min_year int default 0 not null,
|
|
||||||
max_year integer default 0 not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
full_text varchar(255) default '',
|
|
||||||
album_artist_id varchar(255) default '',
|
|
||||||
size integer default 0 not null,
|
|
||||||
all_artist_ids varchar,
|
|
||||||
description varchar(255) default '' not null,
|
|
||||||
small_image_url varchar(255) default '' not null,
|
|
||||||
medium_image_url varchar(255) default '' not null,
|
|
||||||
large_image_url varchar(255) default '' not null,
|
|
||||||
external_url varchar(255) default '' not null,
|
|
||||||
external_info_updated_at datetime,
|
|
||||||
date varchar(255) default '' not null,
|
|
||||||
min_original_year int default 0 not null,
|
|
||||||
max_original_year int default 0 not null,
|
|
||||||
original_date varchar(255) default '' not null,
|
|
||||||
release_date varchar(255) default '' not null,
|
|
||||||
releases integer default 0 not null,
|
|
||||||
image_files varchar default '' not null,
|
|
||||||
order_album_name varchar collate NOCASE default '' not null,
|
|
||||||
order_album_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_album_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_album_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
catalog_num varchar default '' not null,
|
|
||||||
comment varchar default '' not null,
|
|
||||||
paths varchar default '' not null,
|
|
||||||
mbz_album_id varchar default '' not null,
|
|
||||||
mbz_album_artist_id varchar default '' not null,
|
|
||||||
mbz_album_type varchar default '' not null,
|
|
||||||
mbz_album_comment varchar default '' not null,
|
|
||||||
discs jsonb default '{}' not null,
|
|
||||||
library_id integer default 1 not null
|
|
||||||
references library
|
|
||||||
on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into album_dg_tmp(id, name, artist_id, embed_art_path, artist, album_artist, min_year, max_year, compilation,
|
|
||||||
song_count, duration, genre, created_at, updated_at, full_text, album_artist_id, size,
|
|
||||||
all_artist_ids, description, small_image_url, medium_image_url, large_image_url, external_url,
|
|
||||||
external_info_updated_at, date, min_original_year, max_original_year, original_date,
|
|
||||||
release_date, releases, image_files, order_album_name, order_album_artist_name,
|
|
||||||
sort_album_name, sort_album_artist_name, catalog_num, comment, paths,
|
|
||||||
mbz_album_id, mbz_album_artist_id, mbz_album_type, mbz_album_comment, discs, library_id)
|
|
||||||
select id,
|
|
||||||
name,
|
|
||||||
artist_id,
|
|
||||||
embed_art_path,
|
|
||||||
artist,
|
|
||||||
album_artist,
|
|
||||||
min_year,
|
|
||||||
max_year,
|
|
||||||
compilation,
|
|
||||||
song_count,
|
|
||||||
duration,
|
|
||||||
genre,
|
|
||||||
created_at,
|
|
||||||
updated_at,
|
|
||||||
full_text,
|
|
||||||
album_artist_id,
|
|
||||||
size,
|
|
||||||
all_artist_ids,
|
|
||||||
description,
|
|
||||||
small_image_url,
|
|
||||||
medium_image_url,
|
|
||||||
large_image_url,
|
|
||||||
external_url,
|
|
||||||
external_info_updated_at,
|
|
||||||
date,
|
|
||||||
min_original_year,
|
|
||||||
max_original_year,
|
|
||||||
original_date,
|
|
||||||
release_date,
|
|
||||||
releases,
|
|
||||||
image_files,
|
|
||||||
order_album_name,
|
|
||||||
order_album_artist_name,
|
|
||||||
sort_album_name,
|
|
||||||
sort_album_artist_name,
|
|
||||||
catalog_num,
|
|
||||||
comment,
|
|
||||||
paths,
|
|
||||||
mbz_album_id,
|
|
||||||
mbz_album_artist_id,
|
|
||||||
mbz_album_type,
|
|
||||||
mbz_album_comment,
|
|
||||||
discs,
|
|
||||||
library_id
|
|
||||||
from album;
|
|
||||||
|
|
||||||
drop table album;
|
|
||||||
|
|
||||||
alter table album_dg_tmp
|
|
||||||
rename to album;
|
|
||||||
|
|
||||||
create index album_all_artist_ids
|
|
||||||
on album (all_artist_ids);
|
|
||||||
|
|
||||||
create index album_alphabetical_by_artist
|
|
||||||
on album (compilation, order_album_artist_name, order_album_name);
|
|
||||||
|
|
||||||
create index album_artist
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index album_artist_album
|
|
||||||
on album (artist);
|
|
||||||
|
|
||||||
create index album_artist_album_id
|
|
||||||
on album (album_artist_id);
|
|
||||||
|
|
||||||
create index album_artist_id
|
|
||||||
on album (artist_id);
|
|
||||||
|
|
||||||
create index album_created_at
|
|
||||||
on album (created_at);
|
|
||||||
|
|
||||||
create index album_full_text
|
|
||||||
on album (full_text);
|
|
||||||
|
|
||||||
create index album_genre
|
|
||||||
on album (genre);
|
|
||||||
|
|
||||||
create index album_max_year
|
|
||||||
on album (max_year);
|
|
||||||
|
|
||||||
create index album_mbz_album_type
|
|
||||||
on album (mbz_album_type);
|
|
||||||
|
|
||||||
create index album_min_year
|
|
||||||
on album (min_year);
|
|
||||||
|
|
||||||
create index album_name
|
|
||||||
on album (name);
|
|
||||||
|
|
||||||
create index album_order_album_artist_name
|
|
||||||
on album (order_album_artist_name);
|
|
||||||
|
|
||||||
create index album_order_album_name
|
|
||||||
on album (order_album_name);
|
|
||||||
|
|
||||||
create index album_size
|
|
||||||
on album (size);
|
|
||||||
|
|
||||||
create index album_sort_name
|
|
||||||
on album (coalesce(nullif(sort_album_name,''),order_album_name) collate NOCASE);
|
|
||||||
|
|
||||||
create index album_sort_album_artist_name
|
|
||||||
on album (coalesce(nullif(sort_album_artist_name,''),order_album_artist_name) collate NOCASE);
|
|
||||||
|
|
||||||
create index album_updated_at
|
|
||||||
on album (updated_at);
|
|
||||||
--endregion
|
|
||||||
|
|
||||||
--region Media File Table
|
|
||||||
create table media_file_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
path varchar(255) default '' not null,
|
|
||||||
title varchar(255) default '' not null,
|
|
||||||
album varchar(255) default '' not null,
|
|
||||||
artist varchar(255) default '' not null,
|
|
||||||
artist_id varchar(255) default '' not null,
|
|
||||||
album_artist varchar(255) default '' not null,
|
|
||||||
album_id varchar(255) default '' not null,
|
|
||||||
has_cover_art bool 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 integer default 0 not null,
|
|
||||||
suffix varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
bit_rate integer default 0 not null,
|
|
||||||
genre varchar(255) default '' not null,
|
|
||||||
compilation bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
full_text varchar(255) default '',
|
|
||||||
album_artist_id varchar(255) default '',
|
|
||||||
date varchar(255) default '' not null,
|
|
||||||
original_year int default 0 not null,
|
|
||||||
original_date varchar(255) default '' not null,
|
|
||||||
release_year int default 0 not null,
|
|
||||||
release_date varchar(255) default '' not null,
|
|
||||||
order_album_name varchar collate NOCASE default '' not null,
|
|
||||||
order_album_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
order_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_album_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_album_artist_name varchar collate NOCASE default '' not null,
|
|
||||||
sort_title varchar collate NOCASE default '' not null,
|
|
||||||
disc_subtitle varchar default '' not null,
|
|
||||||
catalog_num varchar default '' not null,
|
|
||||||
comment varchar default '' not null,
|
|
||||||
order_title varchar collate NOCASE default '' not null,
|
|
||||||
mbz_recording_id varchar default '' not null,
|
|
||||||
mbz_album_id varchar default '' not null,
|
|
||||||
mbz_artist_id varchar default '' not null,
|
|
||||||
mbz_album_artist_id varchar default '' not null,
|
|
||||||
mbz_album_type varchar default '' not null,
|
|
||||||
mbz_album_comment varchar default '' not null,
|
|
||||||
mbz_release_track_id varchar default '' not null,
|
|
||||||
bpm integer default 0 not null,
|
|
||||||
channels integer default 0 not null,
|
|
||||||
rg_album_gain real default 0 not null,
|
|
||||||
rg_album_peak real default 0 not null,
|
|
||||||
rg_track_gain real default 0 not null,
|
|
||||||
rg_track_peak real default 0 not null,
|
|
||||||
lyrics jsonb default '[]' not null,
|
|
||||||
sample_rate integer default 0 not null,
|
|
||||||
library_id integer default 1 not null
|
|
||||||
references library
|
|
||||||
on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into media_file_dg_tmp(id, path, title, album, artist, artist_id, album_artist, album_id, has_cover_art,
|
|
||||||
track_number, disc_number, year, size, suffix, duration, bit_rate, genre, compilation,
|
|
||||||
created_at, updated_at, full_text, album_artist_id, date, original_year, original_date,
|
|
||||||
release_year, release_date, order_album_name, order_album_artist_name, order_artist_name,
|
|
||||||
sort_album_name, sort_artist_name, sort_album_artist_name, sort_title, disc_subtitle,
|
|
||||||
catalog_num, comment, order_title, mbz_recording_id, mbz_album_id, mbz_artist_id,
|
|
||||||
mbz_album_artist_id, mbz_album_type, mbz_album_comment, mbz_release_track_id, bpm,
|
|
||||||
channels, rg_album_gain, rg_album_peak, rg_track_gain, rg_track_peak, lyrics, sample_rate,
|
|
||||||
library_id)
|
|
||||||
select id,
|
|
||||||
path,
|
|
||||||
title,
|
|
||||||
album,
|
|
||||||
artist,
|
|
||||||
artist_id,
|
|
||||||
album_artist,
|
|
||||||
album_id,
|
|
||||||
has_cover_art,
|
|
||||||
track_number,
|
|
||||||
disc_number,
|
|
||||||
year,
|
|
||||||
size,
|
|
||||||
suffix,
|
|
||||||
duration,
|
|
||||||
bit_rate,
|
|
||||||
genre,
|
|
||||||
compilation,
|
|
||||||
created_at,
|
|
||||||
updated_at,
|
|
||||||
full_text,
|
|
||||||
album_artist_id,
|
|
||||||
date,
|
|
||||||
original_year,
|
|
||||||
original_date,
|
|
||||||
release_year,
|
|
||||||
release_date,
|
|
||||||
order_album_name,
|
|
||||||
order_album_artist_name,
|
|
||||||
order_artist_name,
|
|
||||||
sort_album_name,
|
|
||||||
sort_artist_name,
|
|
||||||
sort_album_artist_name,
|
|
||||||
sort_title,
|
|
||||||
disc_subtitle,
|
|
||||||
catalog_num,
|
|
||||||
comment,
|
|
||||||
order_title,
|
|
||||||
mbz_recording_id,
|
|
||||||
mbz_album_id,
|
|
||||||
mbz_artist_id,
|
|
||||||
mbz_album_artist_id,
|
|
||||||
mbz_album_type,
|
|
||||||
mbz_album_comment,
|
|
||||||
mbz_release_track_id,
|
|
||||||
bpm,
|
|
||||||
channels,
|
|
||||||
rg_album_gain,
|
|
||||||
rg_album_peak,
|
|
||||||
rg_track_gain,
|
|
||||||
rg_track_peak,
|
|
||||||
lyrics,
|
|
||||||
sample_rate,
|
|
||||||
library_id
|
|
||||||
from media_file;
|
|
||||||
|
|
||||||
drop table media_file;
|
|
||||||
|
|
||||||
alter table media_file_dg_tmp
|
|
||||||
rename to media_file;
|
|
||||||
|
|
||||||
create index media_file_album_artist
|
|
||||||
on media_file (album_artist);
|
|
||||||
|
|
||||||
create index media_file_album_id
|
|
||||||
on media_file (album_id);
|
|
||||||
|
|
||||||
create index media_file_artist
|
|
||||||
on media_file (artist);
|
|
||||||
|
|
||||||
create index media_file_artist_album_id
|
|
||||||
on media_file (album_artist_id);
|
|
||||||
|
|
||||||
create index media_file_artist_id
|
|
||||||
on media_file (artist_id);
|
|
||||||
|
|
||||||
create index media_file_bpm
|
|
||||||
on media_file (bpm);
|
|
||||||
|
|
||||||
create index media_file_channels
|
|
||||||
on media_file (channels);
|
|
||||||
|
|
||||||
create index media_file_created_at
|
|
||||||
on media_file (created_at);
|
|
||||||
|
|
||||||
create index media_file_duration
|
|
||||||
on media_file (duration);
|
|
||||||
|
|
||||||
create index media_file_full_text
|
|
||||||
on media_file (full_text);
|
|
||||||
|
|
||||||
create index media_file_genre
|
|
||||||
on media_file (genre);
|
|
||||||
|
|
||||||
create index media_file_mbz_track_id
|
|
||||||
on media_file (mbz_recording_id);
|
|
||||||
|
|
||||||
create index media_file_order_album_name
|
|
||||||
on media_file (order_album_name);
|
|
||||||
|
|
||||||
create index media_file_order_artist_name
|
|
||||||
on media_file (order_artist_name);
|
|
||||||
|
|
||||||
create index media_file_order_title
|
|
||||||
on media_file (order_title);
|
|
||||||
|
|
||||||
create index media_file_path
|
|
||||||
on media_file (path);
|
|
||||||
|
|
||||||
create index media_file_path_nocase
|
|
||||||
on media_file (path collate NOCASE);
|
|
||||||
|
|
||||||
create index media_file_sample_rate
|
|
||||||
on media_file (sample_rate);
|
|
||||||
|
|
||||||
create index media_file_sort_title
|
|
||||||
on media_file (coalesce(nullif(sort_title,''),order_title) collate NOCASE);
|
|
||||||
|
|
||||||
create index media_file_sort_artist_name
|
|
||||||
on media_file (coalesce(nullif(sort_artist_name,''),order_artist_name) collate NOCASE);
|
|
||||||
|
|
||||||
create index media_file_sort_album_name
|
|
||||||
on media_file (coalesce(nullif(sort_album_name,''),order_album_name) collate NOCASE);
|
|
||||||
|
|
||||||
create index media_file_title
|
|
||||||
on media_file (title);
|
|
||||||
|
|
||||||
create index media_file_track_number
|
|
||||||
on media_file (disc_number, track_number);
|
|
||||||
|
|
||||||
create index media_file_updated_at
|
|
||||||
on media_file (updated_at);
|
|
||||||
|
|
||||||
create index media_file_year
|
|
||||||
on media_file (year);
|
|
||||||
|
|
||||||
--endregion
|
|
||||||
|
|
||||||
--region Radio Table
|
|
||||||
create table radio_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar collate NOCASE not null
|
|
||||||
unique,
|
|
||||||
stream_url varchar not null,
|
|
||||||
home_page_url varchar default '' not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into radio_dg_tmp(id, name, stream_url, home_page_url, created_at, updated_at)
|
|
||||||
select id, name, stream_url, home_page_url, created_at, updated_at
|
|
||||||
from radio;
|
|
||||||
|
|
||||||
drop table radio;
|
|
||||||
|
|
||||||
alter table radio_dg_tmp
|
|
||||||
rename to radio;
|
|
||||||
|
|
||||||
create index radio_name
|
|
||||||
on radio(name);
|
|
||||||
--endregion
|
|
||||||
|
|
||||||
--region users Table
|
|
||||||
create table user_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
user_name varchar(255) default '' not null
|
|
||||||
unique,
|
|
||||||
name varchar(255) collate NOCASE default '' not null,
|
|
||||||
email varchar(255) default '' not null,
|
|
||||||
password varchar(255) default '' not null,
|
|
||||||
is_admin bool default FALSE not null,
|
|
||||||
last_login_at datetime,
|
|
||||||
last_access_at datetime,
|
|
||||||
created_at datetime not null,
|
|
||||||
updated_at datetime not null
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into user_dg_tmp(id, user_name, name, email, password, is_admin, last_login_at, last_access_at, created_at,
|
|
||||||
updated_at)
|
|
||||||
select id,
|
|
||||||
user_name,
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
is_admin,
|
|
||||||
last_login_at,
|
|
||||||
last_access_at,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
from user;
|
|
||||||
|
|
||||||
drop table user;
|
|
||||||
|
|
||||||
alter table user_dg_tmp
|
|
||||||
rename to user;
|
|
||||||
|
|
||||||
create index user_username_password
|
|
||||||
on user(user_name collate NOCASE, password);
|
|
||||||
--endregion
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
alter table album
|
|
||||||
add column sort_artist_name varchar default '' not null;
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing/fstest"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/navidrome/navidrome/model"
|
|
||||||
"github.com/navidrome/navidrome/utils/run"
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upSupportNewScanner, downSupportNewScanner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upSupportNewScanner(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
execute := createExecuteFunc(ctx, tx)
|
|
||||||
addColumn := createAddColumnFunc(ctx, tx)
|
|
||||||
|
|
||||||
return run.Sequentially(
|
|
||||||
upSupportNewScanner_CreateTableFolder(ctx, execute),
|
|
||||||
upSupportNewScanner_PopulateTableFolder(ctx, tx),
|
|
||||||
upSupportNewScanner_UpdateTableMediaFile(ctx, execute, addColumn),
|
|
||||||
upSupportNewScanner_UpdateTableAlbum(ctx, execute),
|
|
||||||
upSupportNewScanner_UpdateTableArtist(ctx, execute, addColumn),
|
|
||||||
execute(`
|
|
||||||
alter table library
|
|
||||||
add column last_scan_started_at datetime default '0000-00-00 00:00:00' not null;
|
|
||||||
alter table library
|
|
||||||
add column full_scan_in_progress boolean default false not null;
|
|
||||||
|
|
||||||
create table if not exists media_file_artists(
|
|
||||||
media_file_id varchar not null
|
|
||||||
references media_file (id)
|
|
||||||
on delete cascade,
|
|
||||||
artist_id varchar not null
|
|
||||||
references artist (id)
|
|
||||||
on delete cascade,
|
|
||||||
role varchar default '' not null,
|
|
||||||
sub_role varchar default '' not null,
|
|
||||||
constraint artist_tracks
|
|
||||||
unique (artist_id, media_file_id, role, sub_role)
|
|
||||||
);
|
|
||||||
create index if not exists media_file_artists_media_file_id
|
|
||||||
on media_file_artists (media_file_id);
|
|
||||||
create index if not exists media_file_artists_role
|
|
||||||
on media_file_artists (role);
|
|
||||||
|
|
||||||
create table if not exists album_artists(
|
|
||||||
album_id varchar not null
|
|
||||||
references album (id)
|
|
||||||
on delete cascade,
|
|
||||||
artist_id varchar not null
|
|
||||||
references artist (id)
|
|
||||||
on delete cascade,
|
|
||||||
role varchar default '' not null,
|
|
||||||
sub_role varchar default '' not null,
|
|
||||||
constraint album_artists
|
|
||||||
unique (album_id, artist_id, role, sub_role)
|
|
||||||
);
|
|
||||||
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 table if not exists tag(
|
|
||||||
id varchar not null primary key,
|
|
||||||
tag_name varchar default '' not null,
|
|
||||||
tag_value varchar default '' not null,
|
|
||||||
album_count integer default 0 not null,
|
|
||||||
media_file_count integer default 0 not null,
|
|
||||||
constraint tags_name_value
|
|
||||||
unique (tag_name, tag_value)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Genres are now stored in the tag table
|
|
||||||
drop table if exists media_file_genres;
|
|
||||||
drop table if exists album_genres;
|
|
||||||
drop table if exists artist_genres;
|
|
||||||
drop table if exists genre;
|
|
||||||
|
|
||||||
-- Drop full_text indexes, as they are not being used by SQLite
|
|
||||||
drop index if exists media_file_full_text;
|
|
||||||
drop index if exists album_full_text;
|
|
||||||
drop index if exists artist_full_text;
|
|
||||||
|
|
||||||
-- Add PID config to properties
|
|
||||||
insert into property (id, value) values ('PIDTrack', 'track_legacy') on conflict do nothing;
|
|
||||||
insert into property (id, value) values ('PIDAlbum', 'album_legacy') on conflict do nothing;
|
|
||||||
`),
|
|
||||||
func() error {
|
|
||||||
notice(ctx, tx, "A full scan will be triggered to populate the new tables. This may take a while.")
|
|
||||||
return forceFullRescan(ctx, tx)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upSupportNewScanner_CreateTableFolder(_ context.Context, execute execStmtFunc) execFunc {
|
|
||||||
return execute(`
|
|
||||||
create table if not exists folder(
|
|
||||||
id varchar not null
|
|
||||||
primary key,
|
|
||||||
library_id integer not null
|
|
||||||
references library (id)
|
|
||||||
on delete cascade,
|
|
||||||
path varchar default '' not null,
|
|
||||||
name varchar default '' not null,
|
|
||||||
missing boolean default false not null,
|
|
||||||
parent_id varchar 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 datetime default '0000-00-00 00:00:00' not null,
|
|
||||||
updated_at datetime default (datetime(current_timestamp, 'localtime')) not null,
|
|
||||||
created_at datetime default (datetime(current_timestamp, 'localtime')) not null
|
|
||||||
);
|
|
||||||
create index folder_parent_id on folder(parent_id);
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use paths from `media_file` table to populate `folder` table. The `folder` table must contain all paths, including
|
|
||||||
// the ones that do not contain any media_file. We can get all paths from the media_file table to populate a
|
|
||||||
// fstest.MapFS{}, and then walk the filesystem to insert all folders into the DB, including empty parent ones.
|
|
||||||
func upSupportNewScanner_PopulateTableFolder(ctx context.Context, tx *sql.Tx) execFunc {
|
|
||||||
return func() error {
|
|
||||||
// First, get all folder paths from media_file table
|
|
||||||
rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
|
|
||||||
select distinct rtrim(media_file.path, replace(media_file.path, '%s', '')), library_id, library.path
|
|
||||||
from media_file
|
|
||||||
join library on media_file.library_id = library.id`, string(os.PathSeparator)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
// Then create an in-memory filesystem with all paths
|
|
||||||
var path string
|
|
||||||
var lib model.Library
|
|
||||||
fsys := fstest.MapFS{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&path, &lib.ID, &lib.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
path = strings.TrimPrefix(path, filepath.Clean(lib.Path))
|
|
||||||
path = strings.TrimPrefix(path, string(os.PathSeparator))
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
fsys[path] = &fstest.MapFile{Mode: fs.ModeDir}
|
|
||||||
}
|
|
||||||
if err = rows.Err(); err != nil {
|
|
||||||
return fmt.Errorf("error loading folders from media_file table: %w", err)
|
|
||||||
}
|
|
||||||
if len(fsys) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.PrepareContext(ctx,
|
|
||||||
"insert into folder (id, library_id, path, name, parent_id, updated_at) values (?, ?, ?, ?, ?, '0000-00-00 00:00:00')",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, walk the in-mem filesystem and insert all folders into the DB.
|
|
||||||
err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
// Don't abort the walk, just log the error
|
|
||||||
log.Error("error walking folder to DB", "path", path, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Skip entries that are not directories
|
|
||||||
if !d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a folder in the DB
|
|
||||||
f := model.NewFolder(lib, path)
|
|
||||||
_, err = stmt.ExecContext(ctx, f.ID, lib.ID, f.Path, f.Name, f.ParentID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("error writing folder to DB", "path", path, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error populating folder table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the number of characters in the library path
|
|
||||||
libPath := filepath.Clean(lib.Path)
|
|
||||||
libPathLen := utf8.RuneCountInString(libPath)
|
|
||||||
|
|
||||||
// In one go, update all paths in the media_file table, removing the library path prefix
|
|
||||||
// and replacing any backslashes with slashes (the path separator used by the io/fs package)
|
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
|
||||||
update media_file set path = replace(substr(path, %d), '\', '/');`, libPathLen+2))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating media_file path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func upSupportNewScanner_UpdateTableMediaFile(_ context.Context, execute execStmtFunc, addColumn addColumnFunc) execFunc {
|
|
||||||
return func() error {
|
|
||||||
return run.Sequentially(
|
|
||||||
execute(`
|
|
||||||
alter table media_file
|
|
||||||
add column folder_id varchar default '' not null;
|
|
||||||
alter table media_file
|
|
||||||
add column pid varchar default '' not null;
|
|
||||||
alter table media_file
|
|
||||||
add column missing boolean default false not null;
|
|
||||||
alter table media_file
|
|
||||||
add column mbz_release_group_id varchar default '' not null;
|
|
||||||
alter table media_file
|
|
||||||
add column tags jsonb default '{}' not null;
|
|
||||||
alter table media_file
|
|
||||||
add column participants jsonb default '{}' not null;
|
|
||||||
alter table media_file
|
|
||||||
add column bit_depth integer default 0 not null;
|
|
||||||
alter table media_file
|
|
||||||
add column explicit_status varchar default '' not null;
|
|
||||||
`),
|
|
||||||
addColumn("media_file", "birth_time", "datetime", "current_timestamp", "created_at"),
|
|
||||||
execute(`
|
|
||||||
update media_file
|
|
||||||
set pid = id where pid = '';
|
|
||||||
create index if not exists media_file_birth_time
|
|
||||||
on media_file (birth_time);
|
|
||||||
create index if not exists media_file_folder_id
|
|
||||||
on media_file (folder_id);
|
|
||||||
create index if not exists media_file_pid
|
|
||||||
on media_file (pid);
|
|
||||||
create index if not exists media_file_missing
|
|
||||||
on media_file (missing);
|
|
||||||
`),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func upSupportNewScanner_UpdateTableAlbum(_ context.Context, execute execStmtFunc) execFunc {
|
|
||||||
return execute(`
|
|
||||||
drop index if exists album_all_artist_ids;
|
|
||||||
alter table album
|
|
||||||
drop column all_artist_ids;
|
|
||||||
drop index if exists album_artist;
|
|
||||||
drop index if exists album_artist_album;
|
|
||||||
alter table album
|
|
||||||
drop column artist;
|
|
||||||
drop index if exists album_artist_id;
|
|
||||||
alter table album
|
|
||||||
drop column artist_id;
|
|
||||||
alter table album
|
|
||||||
add column imported_at datetime default '0000-00-00 00:00:00' not null;
|
|
||||||
alter table album
|
|
||||||
add column missing boolean default false not null;
|
|
||||||
alter table album
|
|
||||||
add column mbz_release_group_id varchar default '' not null;
|
|
||||||
alter table album
|
|
||||||
add column tags jsonb default '{}' not null;
|
|
||||||
alter table album
|
|
||||||
add column participants jsonb default '{}' not null;
|
|
||||||
alter table album
|
|
||||||
drop column paths;
|
|
||||||
alter table album
|
|
||||||
drop column image_files;
|
|
||||||
alter table album
|
|
||||||
add column folder_ids jsonb default '[]' not null;
|
|
||||||
alter table album
|
|
||||||
add column explicit_status varchar default '' not null;
|
|
||||||
create index if not exists album_imported_at
|
|
||||||
on album (imported_at);
|
|
||||||
create index if not exists album_mbz_release_group_id
|
|
||||||
on album (mbz_release_group_id);
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upSupportNewScanner_UpdateTableArtist(_ context.Context, execute execStmtFunc, addColumn addColumnFunc) execFunc {
|
|
||||||
return func() error {
|
|
||||||
return run.Sequentially(
|
|
||||||
execute(`
|
|
||||||
alter table artist
|
|
||||||
drop column album_count;
|
|
||||||
alter table artist
|
|
||||||
drop column song_count;
|
|
||||||
drop index if exists artist_size;
|
|
||||||
alter table artist
|
|
||||||
drop column size;
|
|
||||||
alter table artist
|
|
||||||
add column missing boolean default false not null;
|
|
||||||
alter table artist
|
|
||||||
add column stats jsonb default '{"albumartist":{}}' not null;
|
|
||||||
alter table artist
|
|
||||||
drop column similar_artists;
|
|
||||||
alter table artist
|
|
||||||
add column similar_artists jsonb default '[]' not null;
|
|
||||||
`),
|
|
||||||
addColumn("artist", "updated_at", "datetime", "current_time", "(select min(album.updated_at) from album where album_artist_id = artist.id)"),
|
|
||||||
addColumn("artist", "created_at", "datetime", "current_time", "(select min(album.created_at) from album where album_artist_id = artist.id)"),
|
|
||||||
execute(`create index if not exists artist_updated_at on artist (updated_at);`),
|
|
||||||
execute(`update artist set external_info_updated_at = '0000-00-00 00:00:00';`),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downSupportNewScanner(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
CREATE TABLE share_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
expires_at datetime,
|
|
||||||
last_visited_at datetime,
|
|
||||||
resource_ids varchar not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
user_id varchar(255) not null
|
|
||||||
constraint share_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
downloadable bool not null default false,
|
|
||||||
description varchar not null default '',
|
|
||||||
resource_type varchar not null default '',
|
|
||||||
contents varchar not null default '',
|
|
||||||
format varchar not null default '',
|
|
||||||
max_bit_rate integer not null default 0,
|
|
||||||
visit_count integer not null default 0
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO share_tmp(
|
|
||||||
id, expires_at, last_visited_at, resource_ids, created_at, updated_at, user_id, downloadable, description, resource_type, contents, format, max_bit_rate, visit_count
|
|
||||||
) SELECT id, expires_at, last_visited_at, resource_ids, created_at, updated_at, user_id, downloadable, description, resource_type, contents, format, max_bit_rate, visit_count
|
|
||||||
FROM share;
|
|
||||||
|
|
||||||
DROP TABLE share;
|
|
||||||
|
|
||||||
ALTER TABLE share_tmp RENAME To share;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upPlayQueueCurrentToIndex, downPlayQueueCurrentToIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upPlayQueueCurrentToIndex(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
create table playqueue_dg_tmp(
|
|
||||||
id varchar(255) not null,
|
|
||||||
user_id varchar(255) not null
|
|
||||||
references user(id)
|
|
||||||
on update cascade on delete cascade,
|
|
||||||
current integer not null default 0,
|
|
||||||
position real,
|
|
||||||
changed_by varchar(255),
|
|
||||||
items varchar(255),
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime
|
|
||||||
);`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := tx.QueryContext(ctx, `select id, user_id, current, position, changed_by, items, created_at, updated_at from playqueue`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
stmt, err := tx.PrepareContext(ctx, `insert into playqueue_dg_tmp(id, user_id, current, position, changed_by, items, created_at, updated_at) values(?,?,?,?,?,?,?,?)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var id, userID, currentID, changedBy, items string
|
|
||||||
var position sql.NullFloat64
|
|
||||||
var createdAt, updatedAt sql.NullString
|
|
||||||
if err = rows.Scan(&id, &userID, ¤tID, &position, &changedBy, &items, &createdAt, &updatedAt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
index := 0
|
|
||||||
if currentID != "" && items != "" {
|
|
||||||
parts := strings.Split(items, ",")
|
|
||||||
for i, p := range parts {
|
|
||||||
if p == currentID {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = stmt.Exec(id, userID, index, position, changedBy, items, createdAt, updatedAt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = rows.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = tx.ExecContext(ctx, `drop table playqueue;`); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `alter table playqueue_dg_tmp rename to playqueue;`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downPlayQueueCurrentToIndex(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddFolderHash, downAddFolderHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddFolderHash(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `alter table folder add column hash varchar default '' not null;`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddFolderHash(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
CREATE TABLE IF NOT EXISTS annotation_tmp
|
|
||||||
(
|
|
||||||
user_id varchar(255) not null
|
|
||||||
REFERENCES user(id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE,
|
|
||||||
item_id varchar(255) default '' not null,
|
|
||||||
item_type varchar(255) default '' not null,
|
|
||||||
play_count integer default 0,
|
|
||||||
play_date datetime,
|
|
||||||
rating integer default 0,
|
|
||||||
starred bool default FALSE not null,
|
|
||||||
starred_at datetime,
|
|
||||||
unique (user_id, item_id, item_type)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO annotation_tmp(
|
|
||||||
user_id, item_id, item_type, play_count, play_date, rating, starred, starred_at
|
|
||||||
)
|
|
||||||
SELECT user_id, item_id, item_type, play_count, play_date, rating, starred, starred_at
|
|
||||||
FROM annotation
|
|
||||||
WHERE user_id IN (
|
|
||||||
SELECT id FROM user
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE annotation;
|
|
||||||
ALTER TABLE annotation_tmp RENAME TO annotation;
|
|
||||||
|
|
||||||
CREATE INDEX annotation_play_count
|
|
||||||
on annotation (play_count);
|
|
||||||
CREATE INDEX annotation_play_date
|
|
||||||
on annotation (play_date);
|
|
||||||
CREATE INDEX annotation_rating
|
|
||||||
on annotation (rating);
|
|
||||||
CREATE INDEX annotation_starred
|
|
||||||
on annotation (starred);
|
|
||||||
CREATE INDEX annotation_starred_at
|
|
||||||
on annotation (starred_at);
|
|
||||||
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddLibraryStats, downAddLibraryStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddLibraryStats(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
alter table library add column total_songs integer default 0 not null;
|
|
||||||
alter table library add column total_albums integer default 0 not null;
|
|
||||||
alter table library add column total_artists integer default 0 not null;
|
|
||||||
alter table library add column total_folders integer default 0 not null;
|
|
||||||
alter table library add column total_files integer default 0 not null;
|
|
||||||
alter table library add column total_missing_files integer default 0 not null;
|
|
||||||
alter table library add column total_size integer default 0 not null;
|
|
||||||
update library set
|
|
||||||
total_songs = (
|
|
||||||
select count(*) from media_file where library_id = library.id and missing = 0
|
|
||||||
),
|
|
||||||
total_albums = (select count(*) from album where library_id = library.id and missing = 0),
|
|
||||||
total_artists = (
|
|
||||||
select count(*) from library_artist la
|
|
||||||
join artist a on la.artist_id = a.id
|
|
||||||
where la.library_id = library.id and a.missing = 0
|
|
||||||
),
|
|
||||||
total_folders = (select count(*) from folder where library_id = library.id and missing = 0 and num_audio_files > 0),
|
|
||||||
total_files = (
|
|
||||||
select ifnull(sum(num_audio_files + num_playlists + json_array_length(image_files)),0)
|
|
||||||
from folder where library_id = library.id and missing = 0
|
|
||||||
),
|
|
||||||
total_missing_files = (
|
|
||||||
select count(*) from media_file where library_id = library.id and missing = 1
|
|
||||||
),
|
|
||||||
total_size = (select ifnull(sum(size),0) from album where library_id = library.id and missing = 0);
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddLibraryStats(_ context.Context, _ *sql.Tx) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upMakeReplaygainFieldsNullable, downMakeReplaygainFieldsNullable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upMakeReplaygainFieldsNullable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
ALTER TABLE media_file ADD COLUMN rg_album_gain_new real;
|
|
||||||
ALTER TABLE media_file ADD COLUMN rg_album_peak_new real;
|
|
||||||
ALTER TABLE media_file ADD COLUMN rg_track_gain_new real;
|
|
||||||
ALTER TABLE media_file ADD COLUMN rg_track_peak_new real;
|
|
||||||
|
|
||||||
UPDATE media_file SET
|
|
||||||
rg_album_gain_new = rg_album_gain,
|
|
||||||
rg_album_peak_new = rg_album_peak,
|
|
||||||
rg_track_gain_new = rg_track_gain,
|
|
||||||
rg_track_peak_new = rg_track_peak;
|
|
||||||
|
|
||||||
ALTER TABLE media_file DROP COLUMN rg_album_gain;
|
|
||||||
ALTER TABLE media_file DROP COLUMN rg_album_peak;
|
|
||||||
ALTER TABLE media_file DROP COLUMN rg_track_gain;
|
|
||||||
ALTER TABLE media_file DROP COLUMN rg_track_peak;
|
|
||||||
|
|
||||||
ALTER TABLE media_file RENAME COLUMN rg_album_gain_new TO rg_album_gain;
|
|
||||||
ALTER TABLE media_file RENAME COLUMN rg_album_peak_new TO rg_album_peak;
|
|
||||||
ALTER TABLE media_file RENAME COLUMN rg_track_gain_new TO rg_track_gain;
|
|
||||||
ALTER TABLE media_file RENAME COLUMN rg_track_peak_new TO rg_track_peak;
|
|
||||||
`)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
notice(ctx, tx, "Fetching replaygain fields properly will require a full scan")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downMakeReplaygainFieldsNullable(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
// This code is executed when the migration is rolled back.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
update media_file set missing = 1 where folder_id = '';
|
|
||||||
update album set missing = 1 where folder_ids = '[]';
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
WITH artist_role_counters AS (
|
|
||||||
SELECT jt.atom AS artist_id,
|
|
||||||
substr(
|
|
||||||
replace(jt.path, '$.', ''),
|
|
||||||
1,
|
|
||||||
CASE WHEN instr(replace(jt.path, '$.', ''), '[') > 0
|
|
||||||
THEN instr(replace(jt.path, '$.', ''), '[') - 1
|
|
||||||
ELSE length(replace(jt.path, '$.', ''))
|
|
||||||
END
|
|
||||||
) AS role,
|
|
||||||
count(DISTINCT mf.album_id) AS album_count,
|
|
||||||
count(mf.id) AS count,
|
|
||||||
sum(mf.size) AS size
|
|
||||||
FROM media_file mf
|
|
||||||
JOIN json_tree(mf.participants) jt ON jt.key = 'id' AND jt.atom IS NOT NULL
|
|
||||||
GROUP BY jt.atom, role
|
|
||||||
),
|
|
||||||
artist_total_counters AS (
|
|
||||||
SELECT mfa.artist_id,
|
|
||||||
'total' AS role,
|
|
||||||
count(DISTINCT mf.album_id) AS album_count,
|
|
||||||
count(DISTINCT mf.id) AS count,
|
|
||||||
sum(mf.size) AS size
|
|
||||||
FROM media_file_artists mfa
|
|
||||||
JOIN media_file mf ON mfa.media_file_id = mf.id
|
|
||||||
GROUP BY mfa.artist_id
|
|
||||||
),
|
|
||||||
artist_participant_counter AS (
|
|
||||||
SELECT mfa.artist_id,
|
|
||||||
'maincredit' AS role,
|
|
||||||
count(DISTINCT mf.album_id) AS album_count,
|
|
||||||
count(DISTINCT mf.id) AS count,
|
|
||||||
sum(mf.size) AS size
|
|
||||||
FROM media_file_artists mfa
|
|
||||||
JOIN media_file mf ON mfa.media_file_id = mf.id
|
|
||||||
AND mfa.role IN ('albumartist', 'artist')
|
|
||||||
GROUP BY mfa.artist_id
|
|
||||||
),
|
|
||||||
combined_counters AS (
|
|
||||||
SELECT artist_id, role, album_count, count, size FROM artist_role_counters
|
|
||||||
UNION
|
|
||||||
SELECT artist_id, role, album_count, count, size FROM artist_total_counters
|
|
||||||
UNION
|
|
||||||
SELECT artist_id, role, album_count, count, size FROM artist_participant_counter
|
|
||||||
),
|
|
||||||
artist_counters AS (
|
|
||||||
SELECT artist_id AS id,
|
|
||||||
json_group_object(
|
|
||||||
replace(role, '"', ''),
|
|
||||||
json_object('a', album_count, 'm', count, 's', size)
|
|
||||||
) AS counters
|
|
||||||
FROM combined_counters
|
|
||||||
GROUP BY artist_id
|
|
||||||
)
|
|
||||||
UPDATE artist
|
|
||||||
SET stats = coalesce((SELECT counters FROM artist_counters ac WHERE ac.id = artist.id), '{}'),
|
|
||||||
updated_at = datetime(current_timestamp, 'localtime')
|
|
||||||
WHERE artist.id <> '';
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
|
|
||||||
-- Add indexes for MBID fields to improve lookup performance
|
|
||||||
-- Artists table
|
|
||||||
create index if not exists artist_mbz_artist_id
|
|
||||||
on artist (mbz_artist_id);
|
|
||||||
|
|
||||||
-- Albums table
|
|
||||||
create index if not exists album_mbz_album_id
|
|
||||||
on album (mbz_album_id);
|
|
||||||
|
|
||||||
-- Media files table
|
|
||||||
create index if not exists media_file_mbz_release_track_id
|
|
||||||
on media_file (mbz_release_track_id);
|
|
||||||
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
|
|
||||||
-- Remove MBID indexes
|
|
||||||
drop index if exists artist_mbz_artist_id;
|
|
||||||
drop index if exists album_mbz_album_id;
|
|
||||||
drop index if exists media_file_mbz_release_track_id;
|
|
||||||
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddMultiLibrarySupport, downAddMultiLibrarySupport)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddMultiLibrarySupport(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
-- Create user_library association table
|
|
||||||
CREATE TABLE user_library (
|
|
||||||
user_id VARCHAR(255) NOT NULL,
|
|
||||||
library_id INTEGER NOT NULL,
|
|
||||||
PRIMARY KEY (user_id, library_id),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (library_id) REFERENCES library(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
-- Create indexes for performance
|
|
||||||
CREATE INDEX idx_user_library_user_id ON user_library(user_id);
|
|
||||||
CREATE INDEX idx_user_library_library_id ON user_library(library_id);
|
|
||||||
|
|
||||||
-- Populate with existing users having access to library ID 1 (existing setup)
|
|
||||||
-- Admin users get access to all libraries, regular users get access to library 1
|
|
||||||
INSERT INTO user_library (user_id, library_id)
|
|
||||||
SELECT u.id, 1
|
|
||||||
FROM user u;
|
|
||||||
|
|
||||||
-- Add total_duration column to library table
|
|
||||||
ALTER TABLE library ADD COLUMN total_duration real DEFAULT 0;
|
|
||||||
UPDATE library SET total_duration = (
|
|
||||||
SELECT IFNULL(SUM(duration),0) from album where album.library_id = library.id and missing = 0
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Add default_new_users column to library table
|
|
||||||
ALTER TABLE library ADD COLUMN default_new_users boolean DEFAULT false;
|
|
||||||
-- Set library ID 1 (default library) as default for new users
|
|
||||||
UPDATE library SET default_new_users = true WHERE id = 1;
|
|
||||||
|
|
||||||
-- Add stats column to library_artist junction table for per-library artist statistics
|
|
||||||
ALTER TABLE library_artist ADD COLUMN stats text DEFAULT '{}';
|
|
||||||
|
|
||||||
-- Migrate existing global artist stats to per-library format in library_artist table
|
|
||||||
-- For each library_artist association, copy the artist's global stats
|
|
||||||
UPDATE library_artist
|
|
||||||
SET stats = (
|
|
||||||
SELECT COALESCE(artist.stats, '{}')
|
|
||||||
FROM artist
|
|
||||||
WHERE artist.id = library_artist.artist_id
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Remove stats column from artist table to eliminate duplication
|
|
||||||
-- Stats are now stored per-library in library_artist table
|
|
||||||
ALTER TABLE artist DROP COLUMN stats;
|
|
||||||
|
|
||||||
-- Create library_tag table for per-library tag statistics
|
|
||||||
CREATE TABLE library_tag (
|
|
||||||
tag_id VARCHAR 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),
|
|
||||||
FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (library_id) REFERENCES library(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create indexes for optimal query performance
|
|
||||||
CREATE INDEX idx_library_tag_tag_id ON library_tag(tag_id);
|
|
||||||
CREATE INDEX idx_library_tag_library_id ON library_tag(library_id);
|
|
||||||
|
|
||||||
-- Migrate existing tag stats to per-library format in library_tag table
|
|
||||||
-- For existing installations, copy current global stats to library ID 1 (default library)
|
|
||||||
INSERT INTO library_tag (tag_id, library_id, album_count, media_file_count)
|
|
||||||
SELECT t.id, 1, t.album_count, t.media_file_count
|
|
||||||
FROM tag t
|
|
||||||
WHERE EXISTS (SELECT 1 FROM library WHERE id = 1);
|
|
||||||
|
|
||||||
-- Remove global stats from tag table as they are now per-library
|
|
||||||
ALTER TABLE tag DROP COLUMN album_count;
|
|
||||||
ALTER TABLE tag DROP COLUMN media_file_count;
|
|
||||||
`)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddMultiLibrarySupport(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
_, err := tx.ExecContext(ctx, `
|
|
||||||
-- Restore stats column to artist table before removing from library_artist
|
|
||||||
ALTER TABLE artist ADD COLUMN stats text DEFAULT '{}';
|
|
||||||
|
|
||||||
-- Restore global stats by aggregating from library_artist (simplified approach)
|
|
||||||
-- In a real rollback scenario, this might need more sophisticated logic
|
|
||||||
UPDATE artist
|
|
||||||
SET stats = (
|
|
||||||
SELECT COALESCE(la.stats, '{}')
|
|
||||||
FROM library_artist la
|
|
||||||
WHERE la.artist_id = artist.id
|
|
||||||
LIMIT 1
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE library_artist DROP COLUMN IF EXISTS stats;
|
|
||||||
DROP INDEX IF EXISTS idx_user_library_library_id;
|
|
||||||
DROP INDEX IF EXISTS idx_user_library_user_id;
|
|
||||||
DROP TABLE IF EXISTS user_library;
|
|
||||||
ALTER TABLE library DROP COLUMN IF EXISTS total_duration;
|
|
||||||
ALTER TABLE library DROP COLUMN IF EXISTS default_new_users;
|
|
||||||
|
|
||||||
-- Drop library_tag table and its indexes
|
|
||||||
DROP INDEX IF EXISTS idx_library_tag_library_id;
|
|
||||||
DROP INDEX IF EXISTS idx_library_tag_tag_id;
|
|
||||||
DROP TABLE IF EXISTS library_tag;
|
|
||||||
`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE playqueue ADD COLUMN position_int integer;
|
|
||||||
UPDATE playqueue SET position_int = CAST(position as INTEGER) ;
|
|
||||||
ALTER TABLE playqueue DROP COLUMN position;
|
|
||||||
ALTER TABLE playqueue RENAME COLUMN position_int TO position;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE annotation ADD COLUMN rated_at datetime;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
CREATE TABLE scrobbles(
|
|
||||||
media_file_id VARCHAR(255) NOT NULL
|
|
||||||
REFERENCES media_file(id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE,
|
|
||||||
user_id VARCHAR(255) NOT NULL
|
|
||||||
REFERENCES user(id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE,
|
|
||||||
submission_time INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX scrobbles_date ON scrobbles (submission_time);
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
DROP TABLE scrobbles;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- Fix case-insensitive sorting for playlist names
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) collate NOCASE default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
path string default '' not null,
|
|
||||||
sync bool default false not null,
|
|
||||||
size integer default 0 not null,
|
|
||||||
rules varchar,
|
|
||||||
evaluated_at datetime,
|
|
||||||
owner_id varchar(255) not null
|
|
||||||
constraint playlist_user_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size,
|
|
||||||
rules, evaluated_at, owner_id)
|
|
||||||
select id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at,
|
|
||||||
owner_id
|
|
||||||
from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
|
|
||||||
alter table playlist_dg_tmp
|
|
||||||
rename to playlist;
|
|
||||||
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
|
|
||||||
create index playlist_created_at
|
|
||||||
on playlist (created_at);
|
|
||||||
|
|
||||||
create index playlist_updated_at
|
|
||||||
on playlist (updated_at);
|
|
||||||
|
|
||||||
create index playlist_evaluated_at
|
|
||||||
on playlist (evaluated_at);
|
|
||||||
|
|
||||||
create index playlist_size
|
|
||||||
on playlist (size);
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- Note: Downgrade loses the collation but preserves data
|
|
||||||
create table playlist_dg_tmp
|
|
||||||
(
|
|
||||||
id varchar(255) not null
|
|
||||||
primary key,
|
|
||||||
name varchar(255) default '' not null,
|
|
||||||
comment varchar(255) default '' not null,
|
|
||||||
duration real default 0 not null,
|
|
||||||
song_count integer default 0 not null,
|
|
||||||
public bool default FALSE not null,
|
|
||||||
created_at datetime,
|
|
||||||
updated_at datetime,
|
|
||||||
path string default '' not null,
|
|
||||||
sync bool default false not null,
|
|
||||||
size integer default 0 not null,
|
|
||||||
rules varchar,
|
|
||||||
evaluated_at datetime,
|
|
||||||
owner_id varchar(255) not null
|
|
||||||
constraint playlist_user_user_id_fk
|
|
||||||
references user
|
|
||||||
on update cascade on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into playlist_dg_tmp(id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size,
|
|
||||||
rules, evaluated_at, owner_id)
|
|
||||||
select id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at,
|
|
||||||
owner_id
|
|
||||||
from playlist;
|
|
||||||
|
|
||||||
drop table playlist;
|
|
||||||
|
|
||||||
alter table playlist_dg_tmp
|
|
||||||
rename to playlist;
|
|
||||||
|
|
||||||
create index playlist_name
|
|
||||||
on playlist (name);
|
|
||||||
|
|
||||||
create index playlist_created_at
|
|
||||||
on playlist (created_at);
|
|
||||||
|
|
||||||
create index playlist_updated_at
|
|
||||||
on playlist (updated_at);
|
|
||||||
|
|
||||||
create index playlist_evaluated_at
|
|
||||||
on playlist (evaluated_at);
|
|
||||||
|
|
||||||
create index playlist_size
|
|
||||||
on playlist (size);
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
CREATE TABLE IF NOT EXISTS plugin (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
path TEXT NOT NULL,
|
|
||||||
manifest JSONB NOT NULL,
|
|
||||||
config JSONB,
|
|
||||||
users JSONB,
|
|
||||||
all_users BOOL NOT NULL DEFAULT false,
|
|
||||||
libraries JSONB,
|
|
||||||
all_libraries BOOL NOT NULL DEFAULT false,
|
|
||||||
enabled BOOL NOT NULL DEFAULT false,
|
|
||||||
last_error TEXT,
|
|
||||||
sha256 TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
DROP TABLE IF EXISTS plugin;
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
ALTER TABLE album ADD COLUMN average_rating REAL NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE media_file ADD COLUMN average_rating REAL NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE artist ADD COLUMN average_rating REAL NOT NULL DEFAULT 0;
|
|
||||||
|
|
||||||
-- Populate average_rating from existing ratings
|
|
||||||
UPDATE album SET average_rating = coalesce(
|
|
||||||
(SELECT round(avg(rating), 2) FROM annotation WHERE item_id = album.id AND item_type = 'album' AND rating > 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
UPDATE media_file SET average_rating = coalesce(
|
|
||||||
(SELECT round(avg(rating), 2) FROM annotation WHERE item_id = media_file.id AND item_type = 'media_file' AND rating > 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
UPDATE artist SET average_rating = coalesce(
|
|
||||||
(SELECT round(avg(rating), 2) FROM annotation WHERE item_id = artist.id AND item_type = 'artist' AND rating > 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
ALTER TABLE artist DROP COLUMN average_rating;
|
|
||||||
ALTER TABLE media_file DROP COLUMN average_rating;
|
|
||||||
ALTER TABLE album DROP COLUMN average_rating;
|
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pressly/goose/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goose.AddMigrationContext(upAddFts5Search, downAddFts5Search)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripPunct generates a SQL expression that strips common punctuation from a column or expression.
|
|
||||||
// Used during migration to approximate the Go normalizeForFTS function for bulk-populating search_normalized.
|
|
||||||
func stripPunct(col string) string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
`REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(%s, '.', ''), '/', ''), '-', ''), '''', ''), '&', ''), ',', '')`,
|
|
||||||
col,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func upAddFts5Search(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
notice(ctx, tx, "Adding FTS5 full-text search indexes. This may take a moment on large libraries.")
|
|
||||||
|
|
||||||
// Step 1: Add search_participants and search_normalized columns to media_file, album, and artist
|
|
||||||
_, err := tx.ExecContext(ctx, `ALTER TABLE media_file ADD COLUMN search_participants TEXT NOT NULL DEFAULT ''`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding search_participants to media_file: %w", err)
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `ALTER TABLE media_file ADD COLUMN search_normalized TEXT NOT NULL DEFAULT ''`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding search_normalized to media_file: %w", err)
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `ALTER TABLE album ADD COLUMN search_participants TEXT NOT NULL DEFAULT ''`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding search_participants to album: %w", err)
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `ALTER TABLE album ADD COLUMN search_normalized TEXT NOT NULL DEFAULT ''`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding search_normalized to album: %w", err)
|
|
||||||
}
|
|
||||||
_, err = tx.ExecContext(ctx, `ALTER TABLE artist ADD COLUMN search_normalized TEXT NOT NULL DEFAULT ''`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding search_normalized to artist: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Populate search_participants from participants JSON.
|
|
||||||
// Extract all "name" values from the participants JSON structure.
|
|
||||||
// participants is a JSON object like: {"artist":[{"name":"...","id":"..."}],"albumartist":[...]}
|
|
||||||
// We use json_each + json_extract to flatten all names into a space-separated string.
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
UPDATE media_file SET search_participants = COALESCE(
|
|
||||||
(SELECT group_concat(json_extract(je2.value, '$.name'), ' ')
|
|
||||||
FROM json_each(media_file.participants) AS je1,
|
|
||||||
json_each(je1.value) AS je2
|
|
||||||
WHERE json_extract(je2.value, '$.name') IS NOT NULL),
|
|
||||||
''
|
|
||||||
)
|
|
||||||
WHERE participants IS NOT NULL AND participants != '' AND participants != '{}'
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating media_file search_participants: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
UPDATE album SET search_participants = COALESCE(
|
|
||||||
(SELECT group_concat(json_extract(je2.value, '$.name'), ' ')
|
|
||||||
FROM json_each(album.participants) AS je1,
|
|
||||||
json_each(je1.value) AS je2
|
|
||||||
WHERE json_extract(je2.value, '$.name') IS NOT NULL),
|
|
||||||
''
|
|
||||||
)
|
|
||||||
WHERE participants IS NOT NULL AND participants != '' AND participants != '{}'
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating album search_participants: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2b: Populate search_normalized using SQL REPLACE chains for common punctuation.
|
|
||||||
// The Go code will compute the precise value on next scan; this is a best-effort approximation.
|
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
|
||||||
UPDATE artist SET search_normalized = %s
|
|
||||||
WHERE name != %s`,
|
|
||||||
stripPunct("name"), stripPunct("name")))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating artist search_normalized: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
|
||||||
UPDATE album SET search_normalized = TRIM(%s || ' ' || %s)
|
|
||||||
WHERE name != %s OR COALESCE(album_artist, '') != %s`,
|
|
||||||
stripPunct("name"), stripPunct("COALESCE(album_artist, '')"),
|
|
||||||
stripPunct("name"), stripPunct("COALESCE(album_artist, '')")))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating album search_normalized: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
|
||||||
UPDATE media_file SET search_normalized =
|
|
||||||
TRIM(%s || ' ' || %s || ' ' || %s || ' ' || %s)
|
|
||||||
WHERE title != %s
|
|
||||||
OR COALESCE(album, '') != %s
|
|
||||||
OR COALESCE(artist, '') != %s
|
|
||||||
OR COALESCE(album_artist, '') != %s`,
|
|
||||||
stripPunct("title"), stripPunct("COALESCE(album, '')"),
|
|
||||||
stripPunct("COALESCE(artist, '')"), stripPunct("COALESCE(album_artist, '')"),
|
|
||||||
stripPunct("title"), stripPunct("COALESCE(album, '')"),
|
|
||||||
stripPunct("COALESCE(artist, '')"), stripPunct("COALESCE(album_artist, '')")))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating media_file search_normalized: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Create FTS5 virtual tables
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS media_file_fts USING fts5(
|
|
||||||
title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized,
|
|
||||||
content='', content_rowid='rowid',
|
|
||||||
tokenize='unicode61 remove_diacritics 2'
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating media_file_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS album_fts USING fts5(
|
|
||||||
name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized,
|
|
||||||
content='', content_rowid='rowid',
|
|
||||||
tokenize='unicode61 remove_diacritics 2'
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating album_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS artist_fts USING fts5(
|
|
||||||
name, sort_artist_name, search_normalized,
|
|
||||||
content='', content_rowid='rowid',
|
|
||||||
tokenize='unicode61 remove_diacritics 2'
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating artist_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Bulk-populate FTS5 indexes from existing data
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
INSERT INTO media_file_fts(rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized)
|
|
||||||
SELECT rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
COALESCE(disc_subtitle, ''), COALESCE(search_participants, ''),
|
|
||||||
COALESCE(search_normalized, '')
|
|
||||||
FROM media_file
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating media_file_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
INSERT INTO album_fts(rowid, name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized)
|
|
||||||
SELECT rowid, name, COALESCE(sort_album_name, ''), COALESCE(album_artist, ''),
|
|
||||||
COALESCE(search_participants, ''), COALESCE(discs, ''),
|
|
||||||
COALESCE(catalog_num, ''),
|
|
||||||
COALESCE((SELECT group_concat(json_extract(je.value, '$.value'), ' ')
|
|
||||||
FROM json_each(album.tags, '$.albumversion') AS je), ''),
|
|
||||||
COALESCE(search_normalized, '')
|
|
||||||
FROM album
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating album_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
INSERT INTO artist_fts(rowid, name, sort_artist_name, search_normalized)
|
|
||||||
SELECT rowid, name, COALESCE(sort_artist_name, ''), COALESCE(search_normalized, '')
|
|
||||||
FROM artist
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("populating artist_fts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Create triggers for media_file
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER media_file_fts_ai AFTER INSERT ON media_file BEGIN
|
|
||||||
INSERT INTO media_file_fts(rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.title, NEW.album, NEW.artist, NEW.album_artist,
|
|
||||||
NEW.sort_title, NEW.sort_album_name, NEW.sort_artist_name, NEW.sort_album_artist_name,
|
|
||||||
COALESCE(NEW.disc_subtitle, ''), COALESCE(NEW.search_participants, ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating media_file_fts insert trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER media_file_fts_ad AFTER DELETE ON media_file BEGIN
|
|
||||||
INSERT INTO media_file_fts(media_file_fts, rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.title, OLD.album, OLD.artist, OLD.album_artist,
|
|
||||||
OLD.sort_title, OLD.sort_album_name, OLD.sort_artist_name, OLD.sort_album_artist_name,
|
|
||||||
COALESCE(OLD.disc_subtitle, ''), COALESCE(OLD.search_participants, ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating media_file_fts delete trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER media_file_fts_au AFTER UPDATE ON media_file
|
|
||||||
WHEN
|
|
||||||
OLD.title IS NOT NEW.title OR
|
|
||||||
OLD.album IS NOT NEW.album OR
|
|
||||||
OLD.artist IS NOT NEW.artist OR
|
|
||||||
OLD.album_artist IS NOT NEW.album_artist OR
|
|
||||||
OLD.sort_title IS NOT NEW.sort_title OR
|
|
||||||
OLD.sort_album_name IS NOT NEW.sort_album_name OR
|
|
||||||
OLD.sort_artist_name IS NOT NEW.sort_artist_name OR
|
|
||||||
OLD.sort_album_artist_name IS NOT NEW.sort_album_artist_name OR
|
|
||||||
OLD.disc_subtitle IS NOT NEW.disc_subtitle OR
|
|
||||||
OLD.search_participants IS NOT NEW.search_participants OR
|
|
||||||
OLD.search_normalized IS NOT NEW.search_normalized
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO media_file_fts(media_file_fts, rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.title, OLD.album, OLD.artist, OLD.album_artist,
|
|
||||||
OLD.sort_title, OLD.sort_album_name, OLD.sort_artist_name, OLD.sort_album_artist_name,
|
|
||||||
COALESCE(OLD.disc_subtitle, ''), COALESCE(OLD.search_participants, ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
INSERT INTO media_file_fts(rowid, title, album, artist, album_artist,
|
|
||||||
sort_title, sort_album_name, sort_artist_name, sort_album_artist_name,
|
|
||||||
disc_subtitle, search_participants, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.title, NEW.album, NEW.artist, NEW.album_artist,
|
|
||||||
NEW.sort_title, NEW.sort_album_name, NEW.sort_artist_name, NEW.sort_album_artist_name,
|
|
||||||
COALESCE(NEW.disc_subtitle, ''), COALESCE(NEW.search_participants, ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating media_file_fts update trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 6: Create triggers for album
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER album_fts_ai AFTER INSERT ON album BEGIN
|
|
||||||
INSERT INTO album_fts(rowid, name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.name, COALESCE(NEW.sort_album_name, ''), COALESCE(NEW.album_artist, ''),
|
|
||||||
COALESCE(NEW.search_participants, ''), COALESCE(NEW.discs, ''),
|
|
||||||
COALESCE(NEW.catalog_num, ''),
|
|
||||||
COALESCE((SELECT group_concat(json_extract(je.value, '$.value'), ' ')
|
|
||||||
FROM json_each(NEW.tags, '$.albumversion') AS je), ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating album_fts insert trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER album_fts_ad AFTER DELETE ON album BEGIN
|
|
||||||
INSERT INTO album_fts(album_fts, rowid, name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.name, COALESCE(OLD.sort_album_name, ''), COALESCE(OLD.album_artist, ''),
|
|
||||||
COALESCE(OLD.search_participants, ''), COALESCE(OLD.discs, ''),
|
|
||||||
COALESCE(OLD.catalog_num, ''),
|
|
||||||
COALESCE((SELECT group_concat(json_extract(je.value, '$.value'), ' ')
|
|
||||||
FROM json_each(OLD.tags, '$.albumversion') AS je), ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating album_fts delete trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER album_fts_au AFTER UPDATE ON album
|
|
||||||
WHEN
|
|
||||||
OLD.name IS NOT NEW.name OR
|
|
||||||
OLD.sort_album_name IS NOT NEW.sort_album_name OR
|
|
||||||
OLD.album_artist IS NOT NEW.album_artist OR
|
|
||||||
OLD.search_participants IS NOT NEW.search_participants OR
|
|
||||||
OLD.discs IS NOT NEW.discs OR
|
|
||||||
OLD.catalog_num IS NOT NEW.catalog_num OR
|
|
||||||
OLD.tags IS NOT NEW.tags OR
|
|
||||||
OLD.search_normalized IS NOT NEW.search_normalized
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO album_fts(album_fts, rowid, name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.name, COALESCE(OLD.sort_album_name, ''), COALESCE(OLD.album_artist, ''),
|
|
||||||
COALESCE(OLD.search_participants, ''), COALESCE(OLD.discs, ''),
|
|
||||||
COALESCE(OLD.catalog_num, ''),
|
|
||||||
COALESCE((SELECT group_concat(json_extract(je.value, '$.value'), ' ')
|
|
||||||
FROM json_each(OLD.tags, '$.albumversion') AS je), ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
INSERT INTO album_fts(rowid, name, sort_album_name, album_artist,
|
|
||||||
search_participants, discs, catalog_num, album_version, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.name, COALESCE(NEW.sort_album_name, ''), COALESCE(NEW.album_artist, ''),
|
|
||||||
COALESCE(NEW.search_participants, ''), COALESCE(NEW.discs, ''),
|
|
||||||
COALESCE(NEW.catalog_num, ''),
|
|
||||||
COALESCE((SELECT group_concat(json_extract(je.value, '$.value'), ' ')
|
|
||||||
FROM json_each(NEW.tags, '$.albumversion') AS je), ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating album_fts update trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 7: Create triggers for artist
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER artist_fts_ai AFTER INSERT ON artist BEGIN
|
|
||||||
INSERT INTO artist_fts(rowid, name, sort_artist_name, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.name, COALESCE(NEW.sort_artist_name, ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating artist_fts insert trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER artist_fts_ad AFTER DELETE ON artist BEGIN
|
|
||||||
INSERT INTO artist_fts(artist_fts, rowid, name, sort_artist_name, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.name, COALESCE(OLD.sort_artist_name, ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating artist_fts delete trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
|
||||||
CREATE TRIGGER artist_fts_au AFTER UPDATE ON artist
|
|
||||||
WHEN
|
|
||||||
OLD.name IS NOT NEW.name OR
|
|
||||||
OLD.sort_artist_name IS NOT NEW.sort_artist_name OR
|
|
||||||
OLD.search_normalized IS NOT NEW.search_normalized
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO artist_fts(artist_fts, rowid, name, sort_artist_name, search_normalized)
|
|
||||||
VALUES ('delete', OLD.rowid, OLD.name, COALESCE(OLD.sort_artist_name, ''),
|
|
||||||
COALESCE(OLD.search_normalized, ''));
|
|
||||||
INSERT INTO artist_fts(rowid, name, sort_artist_name, search_normalized)
|
|
||||||
VALUES (NEW.rowid, NEW.name, COALESCE(NEW.sort_artist_name, ''),
|
|
||||||
COALESCE(NEW.search_normalized, ''));
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating artist_fts update trigger: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downAddFts5Search(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
for _, trigger := range []string{
|
|
||||||
"media_file_fts_ai", "media_file_fts_ad", "media_file_fts_au",
|
|
||||||
"album_fts_ai", "album_fts_ad", "album_fts_au",
|
|
||||||
"artist_fts_ai", "artist_fts_ad", "artist_fts_au",
|
|
||||||
} {
|
|
||||||
_, err := tx.ExecContext(ctx, "DROP TRIGGER IF EXISTS "+trigger)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dropping trigger %s: %w", trigger, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, table := range []string{"media_file_fts", "album_fts", "artist_fts"} {
|
|
||||||
_, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS "+table)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dropping table %s: %w", table, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We don't drop search_participants columns because SQLite doesn't support DROP COLUMN
|
|
||||||
// on older versions, and the column is harmless if left in place.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue