Introduce separate search index

Uses a fts5 virtual table, kept up to date via triggers on the records
table.
This commit is contained in:
Claudio Ortolina
2024-11-22 10:01:48 +00:00
parent be36c29f76
commit 2e2000fe01
6 changed files with 215 additions and 27 deletions
+8 -5
View File
@@ -3,7 +3,7 @@ defmodule MusicLibrary.Collection do
alias MusicLibrary.Repo
alias MusicLibrary.Records
alias MusicLibrary.Records.Record
alias MusicLibrary.Records.{Record, SearchIndex}
def search_records(query, opts \\ []) do
limit = Keyword.get(opts, :limit, 20)
@@ -18,7 +18,8 @@ defmodule MusicLibrary.Collection do
def count_records_by_format do
q =
from r in base_search(),
from r in Record,
where: not is_nil(r.purchased_at),
group_by: r.format,
order_by: [desc: count(r.id)],
select: {r.format, count(r.id)}
@@ -28,7 +29,8 @@ defmodule MusicLibrary.Collection do
def count_records_by_type do
q =
from r in base_search(),
from r in Record,
where: not is_nil(r.purchased_at),
group_by: r.type,
order_by: [desc: count(r.id)],
select: {r.type, count(r.id)}
@@ -38,7 +40,8 @@ defmodule MusicLibrary.Collection do
def get_latest_record! do
q =
from r in base_search(),
from r in Record,
where: not is_nil(r.purchased_at),
order_by: [desc: r.purchased_at],
limit: 1,
select: ^Records.essential_fields()
@@ -57,7 +60,7 @@ defmodule MusicLibrary.Collection do
end
defp base_search do
from r in Record,
from r in SearchIndex,
where: not is_nil(r.purchased_at)
end
end
+14 -10
View File
@@ -12,14 +12,15 @@ defmodule MusicLibrary.Records do
[
:id,
:type,
:artists,
:format,
:title,
:release,
:artists,
:genres,
:musicbrainz_id,
:release_ids,
:included_release_group_ids,
:cover_hash
:cover_hash,
:release
]
end
@@ -56,13 +57,16 @@ defmodule MusicLibrary.Records do
Enum.reduce(parsed_query, search_with_order, fn
{:artist, artist}, search ->
search |> where([r], like(r.artists, ^"%#{artist}%"))
search
|> where(fragment("records_search_index match 'artists : ?*'", literal(^artist)))
{:album, album}, search ->
search |> where([r], like(r.title, ^"%#{album}%"))
search
|> where(fragment("records_search_index match 'title : ?*'", literal(^album)))
{:mbid, mbid}, search ->
search |> where([r], r.musicbrainz_id == ^mbid or like(r.artists, ^"%#{mbid}%"))
search
|> where(fragment("records_search_index = '?*'", literal(^mbid)))
{:format, format}, search ->
search |> where([r], r.format == ^format)
@@ -70,12 +74,12 @@ defmodule MusicLibrary.Records do
{:type, type}, search ->
search |> where([r], r.type == ^type)
{:query, ""}, search ->
search
{:query, raw_query}, search ->
search
|> where(
[r],
like(r.title, ^"%#{raw_query}%") or like(r.artists, ^"%#{raw_query}%")
)
|> where(fragment("records_search_index = '?*'", literal(^raw_query)))
end)
end
+30
View File
@@ -0,0 +1,30 @@
defmodule MusicLibrary.Records.SearchIndex do
use Ecto.Schema
@formats [:cd, :vinyl, :blu_ray, :dvd, :multi]
@types [:album, :ep, :live, :compilation, :single, :other]
# q = from s in MusicLibrary.Records.SearchIndex,
# where: fragment("records_index = 'lex*'"), select: s.id
@primary_key {:id, :binary_id, autogenerate: false}
schema "records_search_index" do
field :type, Ecto.Enum, values: @types
field :format, Ecto.Enum, values: @formats
field :title, :string
field :musicbrainz_id, Ecto.UUID
field :genres, {:array, :string}
field :release, :string
field :purchased_at, :utc_datetime
field :cover_hash, :string
field :release_ids, {:array, :string}, default: []
field :included_release_group_ids, {:array, :string}, default: []
embeds_many :artists, Artist do
field :name, :string
field :sort_name, :string
field :disambiguation, :string
field :musicbrainz_id, Ecto.UUID
end
end
end
+2 -2
View File
@@ -3,7 +3,7 @@ defmodule MusicLibrary.Wishlist do
alias MusicLibrary.Repo
alias MusicLibrary.Records
alias MusicLibrary.Records.Record
alias MusicLibrary.Records.SearchIndex
def search_records(query, opts \\ []) do
limit = Keyword.get(opts, :limit, 20)
@@ -31,7 +31,7 @@ defmodule MusicLibrary.Wishlist do
end
defp base_search do
from r in Record,
from r in SearchIndex,
where: is_nil(r.purchased_at)
end
end