mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-05 13:37:38 +03:00
refactor: drop search table, integrated full_text into main tables
This commit is contained in:
parent
8cdd4e317d
commit
32fbf2e9eb
10 changed files with 62 additions and 67 deletions
42
db/migration/20200319211049_merge_search_into_main_tables.go
Normal file
42
db/migration/20200319211049_merge_search_into_main_tables.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
goose.AddMigration(Up20200319211049, Down20200319211049)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Up20200319211049(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
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(tx, "A full rescan will be performed!")
|
||||||
|
return forceFullRescan(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Down20200319211049(tx *sql.Tx) error {
|
||||||
|
// This code is executed when the migration is rolled back.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ type Album struct {
|
||||||
SongCount int `json:"songCount"`
|
SongCount int `json:"songCount"`
|
||||||
Duration float32 `json:"duration"`
|
Duration float32 `json:"duration"`
|
||||||
Genre string `json:"genre"`
|
Genre string `json:"genre"`
|
||||||
|
FullText string `json:"fullText"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ type Artist struct {
|
||||||
ID string `json:"id" orm:"column(id)"`
|
ID string `json:"id" orm:"column(id)"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AlbumCount int `json:"albumCount" orm:"column(album_count)"`
|
AlbumCount int `json:"albumCount" orm:"column(album_count)"`
|
||||||
|
FullText string `json:"fullText"`
|
||||||
|
|
||||||
// Annotations
|
// Annotations
|
||||||
PlayCount int `json:"-" orm:"-"`
|
PlayCount int `json:"-" orm:"-"`
|
||||||
|
|
|
@ -23,6 +23,7 @@ type MediaFile struct {
|
||||||
Duration float32 `json:"duration"`
|
Duration float32 `json:"duration"`
|
||||||
BitRate int `json:"bitRate"`
|
BitRate int `json:"bitRate"`
|
||||||
Genre string `json:"genre"`
|
Genre string `json:"genre"`
|
||||||
|
FullText string `json:"fullText"`
|
||||||
Compilation bool `json:"compilation"`
|
Compilation bool `json:"compilation"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|
|
@ -39,11 +39,9 @@ func (r *albumRepository) Exists(id string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *albumRepository) Put(a *model.Album) error {
|
func (r *albumRepository) Put(a *model.Album) error {
|
||||||
|
a.FullText = r.getFullText(a.Name, a.Artist, a.AlbumArtist)
|
||||||
_, err := r.put(a.ID, a)
|
_, err := r.put(a.ID, a)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.index(a.ID, a.Name, a.Artist, a.AlbumArtist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder {
|
func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder {
|
||||||
|
|
|
@ -52,11 +52,9 @@ func (r *artistRepository) getIndexKey(a *model.Artist) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *artistRepository) Put(a *model.Artist) error {
|
func (r *artistRepository) Put(a *model.Artist) error {
|
||||||
|
a.FullText = r.getFullText(a.Name)
|
||||||
_, err := r.put(a.ID, a)
|
_, err := r.put(a.ID, a)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.index(a.ID, a.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *artistRepository) Get(id string) (*model.Artist, error) {
|
func (r *artistRepository) Get(id string) (*model.Artist, error) {
|
||||||
|
|
|
@ -37,11 +37,9 @@ func (r mediaFileRepository) Exists(id string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r mediaFileRepository) Put(m *model.MediaFile) error {
|
func (r mediaFileRepository) Put(m *model.MediaFile) error {
|
||||||
|
m.FullText = r.getFullText(m.Title, m.Album, m.Artist, m.AlbumArtist)
|
||||||
_, err := r.put(m.ID, m)
|
_, err := r.put(m.ID, m)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.index(m.ID, m.Title, m.Album, m.Artist, m.AlbumArtist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) SelectBuilder {
|
func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) SelectBuilder {
|
||||||
|
|
|
@ -119,18 +119,6 @@ func (s *SQLStore) GC(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.MediaFile(ctx).(*mediaFileRepository).cleanSearchIndex()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Album(ctx).(*albumRepository).cleanSearchIndex()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Artist(ctx).(*artistRepository).cleanSearchIndex()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.MediaFile(ctx).(*mediaFileRepository).cleanAnnotations()
|
err = s.MediaFile(ctx).(*mediaFileRepository).cleanAnnotations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -31,8 +31,8 @@ func TestPersistence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
artistKraftwerk = model.Artist{ID: "2", Name: "Kraftwerk", AlbumCount: 1}
|
artistKraftwerk = model.Artist{ID: "2", Name: "Kraftwerk", AlbumCount: 1, FullText: "kraftwerk"}
|
||||||
artistBeatles = model.Artist{ID: "3", Name: "The Beatles", AlbumCount: 2}
|
artistBeatles = model.Artist{ID: "3", Name: "The Beatles", AlbumCount: 2, FullText: "the beatles"}
|
||||||
testArtists = model.Artists{
|
testArtists = model.Artists{
|
||||||
artistKraftwerk,
|
artistKraftwerk,
|
||||||
artistBeatles,
|
artistBeatles,
|
||||||
|
@ -40,9 +40,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
albumSgtPeppers = model.Album{ID: "1", Name: "Sgt Peppers", Artist: "The Beatles", ArtistID: "3", Genre: "Rock", CoverArtId: "1", CoverArtPath: P("/beatles/1/sgt/a day.mp3"), SongCount: 1, Year: 1967}
|
albumSgtPeppers = model.Album{ID: "1", Name: "Sgt Peppers", Artist: "The Beatles", ArtistID: "3", Genre: "Rock", CoverArtId: "1", CoverArtPath: P("/beatles/1/sgt/a day.mp3"), SongCount: 1, Year: 1967, FullText: "sgt peppers the beatles"}
|
||||||
albumAbbeyRoad = model.Album{ID: "2", Name: "Abbey Road", Artist: "The Beatles", ArtistID: "3", Genre: "Rock", CoverArtId: "2", CoverArtPath: P("/beatles/1/come together.mp3"), SongCount: 1, Year: 1969}
|
albumAbbeyRoad = model.Album{ID: "2", Name: "Abbey Road", Artist: "The Beatles", ArtistID: "3", Genre: "Rock", CoverArtId: "2", CoverArtPath: P("/beatles/1/come together.mp3"), SongCount: 1, Year: 1969, FullText: "abbey road the beatles"}
|
||||||
albumRadioactivity = model.Album{ID: "3", Name: "Radioactivity", Artist: "Kraftwerk", ArtistID: "2", Genre: "Electronic", CoverArtId: "3", CoverArtPath: P("/kraft/radio/radio.mp3"), SongCount: 2}
|
albumRadioactivity = model.Album{ID: "3", Name: "Radioactivity", Artist: "Kraftwerk", ArtistID: "2", Genre: "Electronic", CoverArtId: "3", CoverArtPath: P("/kraft/radio/radio.mp3"), SongCount: 2, FullText: "radioactivity kraftwerk"}
|
||||||
testAlbums = model.Albums{
|
testAlbums = model.Albums{
|
||||||
albumSgtPeppers,
|
albumSgtPeppers,
|
||||||
albumAbbeyRoad,
|
albumAbbeyRoad,
|
||||||
|
@ -51,10 +51,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
songDayInALife = model.MediaFile{ID: "1", Title: "A Day In A Life", ArtistID: "3", Artist: "The Beatles", AlbumID: "1", Album: "Sgt Peppers", Genre: "Rock", Path: P("/beatles/1/sgt/a day.mp3")}
|
songDayInALife = model.MediaFile{ID: "1", Title: "A Day In A Life", ArtistID: "3", Artist: "The Beatles", AlbumID: "1", Album: "Sgt Peppers", Genre: "Rock", Path: P("/beatles/1/sgt/a day.mp3"), FullText: "a day in a life sgt peppers the beatles"}
|
||||||
songComeTogether = model.MediaFile{ID: "2", Title: "Come Together", ArtistID: "3", Artist: "The Beatles", AlbumID: "2", Album: "Abbey Road", Genre: "Rock", Path: P("/beatles/1/come together.mp3")}
|
songComeTogether = model.MediaFile{ID: "2", Title: "Come Together", ArtistID: "3", Artist: "The Beatles", AlbumID: "2", Album: "Abbey Road", Genre: "Rock", Path: P("/beatles/1/come together.mp3"), FullText: "come together abbey road the beatles"}
|
||||||
songRadioactivity = model.MediaFile{ID: "3", Title: "Radioactivity", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Album: "Radioactivity", Genre: "Electronic", Path: P("/kraft/radio/radio.mp3")}
|
songRadioactivity = model.MediaFile{ID: "3", Title: "Radioactivity", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Album: "Radioactivity", Genre: "Electronic", Path: P("/kraft/radio/radio.mp3"), FullText: "radioactivity radioactivity kraftwerk"}
|
||||||
songAntenna = model.MediaFile{ID: "4", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Genre: "Electronic", Path: P("/kraft/radio/antenna.mp3")}
|
songAntenna = model.MediaFile{ID: "4", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Genre: "Electronic", Path: P("/kraft/radio/antenna.mp3"), FullText: "antenna kraftwerk"}
|
||||||
testSongs = model.MediaFiles{
|
testSongs = model.MediaFiles{
|
||||||
songDayInALife,
|
songDayInALife,
|
||||||
songComeTogether,
|
songComeTogether,
|
||||||
|
|
|
@ -4,34 +4,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
. "github.com/Masterminds/squirrel"
|
. "github.com/Masterminds/squirrel"
|
||||||
"github.com/deluan/navidrome/log"
|
|
||||||
"github.com/kennygrant/sanitize"
|
"github.com/kennygrant/sanitize"
|
||||||
)
|
)
|
||||||
|
|
||||||
const searchTable = "search"
|
func (r sqlRepository) getFullText(text ...string) string {
|
||||||
|
|
||||||
func (r sqlRepository) index(id string, text ...string) error {
|
|
||||||
sanitizedText := strings.Builder{}
|
sanitizedText := strings.Builder{}
|
||||||
for _, txt := range text {
|
for _, txt := range text {
|
||||||
sanitizedText.WriteString(strings.TrimSpace(sanitize.Accents(strings.ToLower(txt))) + " ")
|
sanitizedText.WriteString(strings.TrimSpace(sanitize.Accents(strings.ToLower(txt))) + " ")
|
||||||
}
|
}
|
||||||
|
return strings.TrimSpace(sanitizedText.String())
|
||||||
values := map[string]interface{}{
|
|
||||||
"id": id,
|
|
||||||
"item_type": r.tableName,
|
|
||||||
"full_text": strings.TrimSpace(sanitizedText.String()),
|
|
||||||
}
|
|
||||||
update := Update(searchTable).Where(Eq{"id": id}).SetMap(values)
|
|
||||||
count, err := r.executeSQL(update)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
insert := Insert(searchTable).SetMap(values)
|
|
||||||
_, err = r.executeSQL(insert)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sqlRepository) doSearch(q string, offset, size int, results interface{}, orderBys ...string) error {
|
func (r sqlRepository) doSearch(q string, offset, size int, results interface{}, orderBys ...string) error {
|
||||||
|
@ -44,7 +25,6 @@ func (r sqlRepository) doSearch(q string, offset, size int, results interface{},
|
||||||
if len(orderBys) > 0 {
|
if len(orderBys) > 0 {
|
||||||
sq = sq.OrderBy(orderBys...)
|
sq = sq.OrderBy(orderBys...)
|
||||||
}
|
}
|
||||||
sq = sq.Join("search").Where("search.id = " + r.tableName + ".id")
|
|
||||||
parts := strings.Split(q, " ")
|
parts := strings.Split(q, " ")
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
sq = sq.Where(Or{
|
sq = sq.Where(Or{
|
||||||
|
@ -55,15 +35,3 @@ func (r sqlRepository) doSearch(q string, offset, size int, results interface{},
|
||||||
err := r.queryAll(sq, results)
|
err := r.queryAll(sq, results)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sqlRepository) cleanSearchIndex() error {
|
|
||||||
del := Delete(searchTable).Where(Eq{"item_type": r.tableName}).Where("id not in (select id from " + r.tableName + ")")
|
|
||||||
c, err := r.executeSQL(del)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c > 0 {
|
|
||||||
log.Debug(r.ctx, "Clean-up search index", "table", r.tableName, "totalDeleted", c)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue