Materialize record releases to improve performance
This commit is contained in:
@@ -3,7 +3,7 @@ defmodule MusicLibrary.Collection do
|
|||||||
import MusicLibrary.Records, only: [order_alphabetically: 0]
|
import MusicLibrary.Records, only: [order_alphabetically: 0]
|
||||||
|
|
||||||
alias MusicLibrary.Records
|
alias MusicLibrary.Records
|
||||||
alias MusicLibrary.Records.{Record, SearchIndex}
|
alias MusicLibrary.Records.{Record, RecordRelease, SearchIndex}
|
||||||
alias MusicLibrary.Repo
|
alias MusicLibrary.Repo
|
||||||
|
|
||||||
def search_records(query, opts \\ []) do
|
def search_records(query, opts \\ []) do
|
||||||
@@ -121,13 +121,9 @@ defmodule MusicLibrary.Collection do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def collected_releases_query do
|
def collected_releases_query do
|
||||||
from r in fragment("records, json_each(records.release_ids)"),
|
from rr in RecordRelease,
|
||||||
where: fragment("records.purchased_at IS NOT NULL"),
|
where: not is_nil(rr.purchased_at),
|
||||||
select: %{
|
select: %{record_id: rr.record_id, cover_hash: rr.cover_hash, release_id: rr.release_id}
|
||||||
record_id: fragment("records.id"),
|
|
||||||
cover_hash: fragment("records.cover_hash"),
|
|
||||||
release_id: r.value
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp base_search do
|
defp base_search do
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
defmodule MusicLibrary.Records.RecordRelease do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
schema "record_releases" do
|
||||||
|
field :record_id, :binary_id
|
||||||
|
field :release_id, :string
|
||||||
|
field :cover_hash, :string
|
||||||
|
field :purchased_at, :utc_datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,7 @@ defmodule MusicLibrary.Wishlist do
|
|||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
|
|
||||||
alias MusicLibrary.Records
|
alias MusicLibrary.Records
|
||||||
alias MusicLibrary.Records.SearchIndex
|
alias MusicLibrary.Records.{RecordRelease, SearchIndex}
|
||||||
alias MusicLibrary.Repo
|
alias MusicLibrary.Repo
|
||||||
|
|
||||||
def search_records(query, opts \\ []) do
|
def search_records(query, opts \\ []) do
|
||||||
@@ -22,13 +22,9 @@ defmodule MusicLibrary.Wishlist do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def wishlisted_releases_query do
|
def wishlisted_releases_query do
|
||||||
from r in fragment("records, json_each(records.release_ids)"),
|
from rr in RecordRelease,
|
||||||
where: fragment("records.purchased_at IS NULL"),
|
where: is_nil(rr.purchased_at),
|
||||||
select: %{
|
select: %{record_id: rr.record_id, cover_hash: rr.cover_hash, release_id: rr.release_id}
|
||||||
record_id: fragment("records.id"),
|
|
||||||
cover_hash: fragment("records.cover_hash"),
|
|
||||||
release_id: r.value
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp base_search do
|
defp base_search do
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
defmodule MusicLibrary.Repo.Migrations.CreateRecordReleases do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute """
|
||||||
|
CREATE TABLE record_releases (
|
||||||
|
record_id TEXT NOT NULL,
|
||||||
|
release_id TEXT NOT NULL,
|
||||||
|
cover_hash TEXT,
|
||||||
|
purchased_at TEXT,
|
||||||
|
PRIMARY KEY (record_id, release_id),
|
||||||
|
FOREIGN KEY (record_id) REFERENCES records(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
|
execute """
|
||||||
|
CREATE INDEX record_releases_release_id_index ON record_releases(release_id);
|
||||||
|
"""
|
||||||
|
|
||||||
|
flush()
|
||||||
|
|
||||||
|
# Populate from existing data
|
||||||
|
execute """
|
||||||
|
INSERT INTO record_releases (record_id, release_id, cover_hash, purchased_at)
|
||||||
|
SELECT records.id, json_each.value, records.cover_hash, records.purchased_at
|
||||||
|
FROM records, json_each(records.release_ids);
|
||||||
|
"""
|
||||||
|
|
||||||
|
flush()
|
||||||
|
|
||||||
|
# Trigger: after insert on records, expand release_ids into record_releases
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER record_releases_after_insert
|
||||||
|
AFTER INSERT ON records
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO record_releases (record_id, release_id, cover_hash, purchased_at)
|
||||||
|
SELECT NEW.id, json_each.value, NEW.cover_hash, NEW.purchased_at
|
||||||
|
FROM json_each(NEW.release_ids);
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Trigger: before update on records, remove old rows
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER record_releases_before_update
|
||||||
|
BEFORE UPDATE ON records
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM record_releases WHERE record_id = OLD.id;
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Trigger: after update on records, insert new rows
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER record_releases_after_update
|
||||||
|
AFTER UPDATE ON records
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO record_releases (record_id, release_id, cover_hash, purchased_at)
|
||||||
|
SELECT NEW.id, json_each.value, NEW.cover_hash, NEW.purchased_at
|
||||||
|
FROM json_each(NEW.release_ids);
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Trigger: before delete on records, remove rows
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER record_releases_before_delete
|
||||||
|
BEFORE DELETE ON records
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM record_releases WHERE record_id = OLD.id;
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute "DROP TRIGGER IF EXISTS record_releases_after_insert"
|
||||||
|
execute "DROP TRIGGER IF EXISTS record_releases_before_update"
|
||||||
|
execute "DROP TRIGGER IF EXISTS record_releases_after_update"
|
||||||
|
execute "DROP TRIGGER IF EXISTS record_releases_before_delete"
|
||||||
|
execute "DROP TABLE IF EXISTS record_releases"
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user