diff --git a/db/migration/20200319211049_merge_search_into_main_tables.go b/db/migration/20200319211049_merge_search_into_main_tables.go new file mode 100644 index 000000000..ca0b0a334 --- /dev/null +++ b/db/migration/20200319211049_merge_search_into_main_tables.go @@ -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 +} diff --git a/model/album.go b/model/album.go index a8dfca2ad..2bc263e7b 100644 --- a/model/album.go +++ b/model/album.go @@ -15,6 +15,7 @@ type Album struct { SongCount int `json:"songCount"` Duration float32 `json:"duration"` Genre string `json:"genre"` + FullText string `json:"fullText"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` diff --git a/model/artist.go b/model/artist.go index ea5040ace..b9171447b 100644 --- a/model/artist.go +++ b/model/artist.go @@ -6,6 +6,7 @@ type Artist struct { ID string `json:"id" orm:"column(id)"` Name string `json:"name"` AlbumCount int `json:"albumCount" orm:"column(album_count)"` + FullText string `json:"fullText"` // Annotations PlayCount int `json:"-" orm:"-"` diff --git a/model/mediafile.go b/model/mediafile.go index 244b5c115..b93cf13d6 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -23,6 +23,7 @@ type MediaFile struct { Duration float32 `json:"duration"` BitRate int `json:"bitRate"` Genre string `json:"genre"` + FullText string `json:"fullText"` Compilation bool `json:"compilation"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 3427543cc..4a9f73a7e 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -39,11 +39,9 @@ func (r *albumRepository) Exists(id string) (bool, 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) - if err != nil { - return err - } - return r.index(a.ID, a.Name, a.Artist, a.AlbumArtist) + return err } func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder { diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go index 4f4fbaaa9..efb983524 100644 --- a/persistence/artist_repository.go +++ b/persistence/artist_repository.go @@ -52,11 +52,9 @@ func (r *artistRepository) getIndexKey(a *model.Artist) string { } func (r *artistRepository) Put(a *model.Artist) error { + a.FullText = r.getFullText(a.Name) _, err := r.put(a.ID, a) - if err != nil { - return err - } - return r.index(a.ID, a.Name) + return err } func (r *artistRepository) Get(id string) (*model.Artist, error) { diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index e8c6c3739..5c5bdce4b 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -37,11 +37,9 @@ func (r mediaFileRepository) Exists(id string) (bool, 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) - if err != nil { - return err - } - return r.index(m.ID, m.Title, m.Album, m.Artist, m.AlbumArtist) + return err } func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) SelectBuilder { diff --git a/persistence/persistence.go b/persistence/persistence.go index e292e3b29..df8963570 100644 --- a/persistence/persistence.go +++ b/persistence/persistence.go @@ -119,18 +119,6 @@ func (s *SQLStore) GC(ctx context.Context) error { if err != nil { 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() if err != nil { return err diff --git a/persistence/persistence_suite_test.go b/persistence/persistence_suite_test.go index 0e5e4e958..3567e7ad9 100644 --- a/persistence/persistence_suite_test.go +++ b/persistence/persistence_suite_test.go @@ -31,8 +31,8 @@ func TestPersistence(t *testing.T) { } var ( - artistKraftwerk = model.Artist{ID: "2", Name: "Kraftwerk", AlbumCount: 1} - artistBeatles = model.Artist{ID: "3", Name: "The Beatles", AlbumCount: 2} + artistKraftwerk = model.Artist{ID: "2", Name: "Kraftwerk", AlbumCount: 1, FullText: "kraftwerk"} + artistBeatles = model.Artist{ID: "3", Name: "The Beatles", AlbumCount: 2, FullText: "the beatles"} testArtists = model.Artists{ artistKraftwerk, artistBeatles, @@ -40,9 +40,9 @@ 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} - 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} - albumRadioactivity = model.Album{ID: "3", Name: "Radioactivity", Artist: "Kraftwerk", ArtistID: "2", Genre: "Electronic", CoverArtId: "3", CoverArtPath: P("/kraft/radio/radio.mp3"), SongCount: 2} + 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, 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, FullText: "radioactivity kraftwerk"} testAlbums = model.Albums{ albumSgtPeppers, albumAbbeyRoad, @@ -51,10 +51,10 @@ 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")} - 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")} - songRadioactivity = model.MediaFile{ID: "3", Title: "Radioactivity", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Album: "Radioactivity", Genre: "Electronic", Path: P("/kraft/radio/radio.mp3")} - songAntenna = model.MediaFile{ID: "4", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "3", Genre: "Electronic", Path: P("/kraft/radio/antenna.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"), 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"), 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"), FullText: "antenna kraftwerk"} testSongs = model.MediaFiles{ songDayInALife, songComeTogether, diff --git a/persistence/sql_search.go b/persistence/sql_search.go index 9f5bf9612..e9c2e88c5 100644 --- a/persistence/sql_search.go +++ b/persistence/sql_search.go @@ -4,34 +4,15 @@ import ( "strings" . "github.com/Masterminds/squirrel" - "github.com/deluan/navidrome/log" "github.com/kennygrant/sanitize" ) -const searchTable = "search" - -func (r sqlRepository) index(id string, text ...string) error { +func (r sqlRepository) getFullText(text ...string) string { sanitizedText := strings.Builder{} for _, txt := range text { sanitizedText.WriteString(strings.TrimSpace(sanitize.Accents(strings.ToLower(txt))) + " ") } - - 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 + return strings.TrimSpace(sanitizedText.String()) } 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 { sq = sq.OrderBy(orderBys...) } - sq = sq.Join("search").Where("search.id = " + r.tableName + ".id") parts := strings.Split(q, " ") for _, part := range parts { sq = sq.Where(Or{ @@ -55,15 +35,3 @@ func (r sqlRepository) doSearch(q string, offset, size int, results interface{}, err := r.queryAll(sq, results) 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 -}