-- Generated by CompileSchemaScripts.pl from:
-- 20210406-mbs-11457.sql
-- 20210419-mbs-11456.sql
-- 20210526-a_upd_release_event.sql
-- 20210606-mbs-11682.sql
-- 20220207-mbs-12224-mirror.sql
-- 20220218-mbs-12208.sql
-- 20220302-mbs-12225.sql
-- 20220314-mbs-12252.sql
-- 20220314-mbs-12253.sql
-- 20220314-mbs-12254.sql
-- 20220314-mbs-12255.sql
-- 20220328-mbs-12250-mirror.sql
-- 20220322-mbs-12256-mirror.sql
-- 20220324-mbs-12200.sql
-- 20220408-immutable-link-tables.sql
-- 20220408-mbs-12249.sql
-- 20220412-mbs-12190.sql
-- 20220426-mbs-12131.sql
\set ON_ERROR_STOP 1
BEGIN;
SET search_path = musicbrainz, public;
SET LOCAL statement_timeout = 0;
--------------------------------------------------------------------------------
SELECT '20210406-mbs-11457.sql';


-- I) Replace s.ordering_attribute with the only possible value, 788

CREATE OR REPLACE VIEW event_series AS
    SELECT entity0 AS event,
           entity1 AS series,
           lrs.id AS relationship,
           link_order,
           lrs.link,
           COALESCE(text_value, '') AS text_value
    FROM l_event_series lrs
    JOIN series s ON s.id = lrs.entity1
    JOIN link l ON l.id = lrs.link
    JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '707d947d-9563-328a-9a7d-0c5b9c3a9791')
    LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id)
    ORDER BY series, link_order;

CREATE OR REPLACE VIEW recording_series AS
    SELECT entity0 AS recording,
           entity1 AS series,
           lrs.id AS relationship,
           link_order,
           lrs.link,
           COALESCE(text_value, '') AS text_value
    FROM l_recording_series lrs
    JOIN series s ON s.id = lrs.entity1
    JOIN link l ON l.id = lrs.link
    JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = 'ea6f0698-6782-30d6-b16d-293081b66774')
    LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id)
    ORDER BY series, link_order;

CREATE OR REPLACE VIEW release_series AS
    SELECT entity0 AS release,
           entity1 AS series,
           lrs.id AS relationship,
           link_order,
           lrs.link,
           COALESCE(text_value, '') AS text_value
    FROM l_release_series lrs
    JOIN series s ON s.id = lrs.entity1
    JOIN link l ON l.id = lrs.link
    JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '3fa29f01-8e13-3e49-9b0a-ad212aa2f81d')
    LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id)
    ORDER BY series, link_order;

CREATE OR REPLACE VIEW release_group_series AS
    SELECT entity0 AS release_group,
           entity1 AS series,
           lrgs.id AS relationship,
           link_order,
           lrgs.link,
           COALESCE(text_value, '') AS text_value
    FROM l_release_group_series lrgs
    JOIN series s ON s.id = lrgs.entity1
    JOIN link l ON l.id = lrgs.link
    JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '01018437-91d8-36b9-bf89-3f885d53b5bd')
    LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id)
    ORDER BY series, link_order;

CREATE OR REPLACE VIEW work_series AS
    SELECT entity1 AS work,
           entity0 AS series,
           lsw.id AS relationship,
           link_order,
           lsw.link,
           COALESCE(text_value, '') AS text_value
    FROM l_series_work lsw
    JOIN series s ON s.id = lsw.entity0
    JOIN link l ON l.id = lsw.link
    JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = 'b0d44366-cdf0-3acb-bee6-0f65a77a6ef0')
    LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id)
    ORDER BY series, link_order;

-- II) Drop the ordering_attribute column

ALTER TABLE series DROP COLUMN ordering_attribute CASCADE;

--------------------------------------------------------------------------------
SELECT '20210419-mbs-11456.sql';


-- Artist credit GID

-- Creating column
ALTER TABLE artist_credit ADD COLUMN gid uuid;

-- Generating GIDs
UPDATE artist_credit SET gid =
    generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'artist_credit' || id);

-- Adding NOT NULL constraint
ALTER TABLE artist_credit ALTER COLUMN gid SET NOT NULL;

-- Creating index
CREATE UNIQUE INDEX artist_credit_idx_gid ON artist_credit (gid);

-- Artist credit GID redirect
CREATE TABLE artist_credit_gid_redirect ( -- replicate (verbose)
    gid                 UUID NOT NULL, -- PK
    new_id              INTEGER NOT NULL, -- references artist_credit.id
    created             TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

ALTER TABLE artist_credit_gid_redirect ADD CONSTRAINT artist_credit_gid_redirect_pkey PRIMARY KEY (gid);

CREATE INDEX artist_credit_gid_redirect_idx_new_id ON artist_credit_gid_redirect (new_id);

--------------------------------------------------------------------------------
SELECT '20210526-a_upd_release_event.sql';


CREATE OR REPLACE FUNCTION a_upd_release_event()
RETURNS TRIGGER AS $$
BEGIN
  IF (
    NEW.release != OLD.release OR
    NEW.date_year IS DISTINCT FROM OLD.date_year OR
    NEW.date_month IS DISTINCT FROM OLD.date_month OR
    NEW.date_day IS DISTINCT FROM OLD.date_day
  ) THEN
    PERFORM set_release_first_release_date(OLD.release);
    IF NEW.release != OLD.release THEN
        PERFORM set_release_first_release_date(NEW.release);
    END IF;

    PERFORM set_release_group_first_release_date(release_group)
    FROM release
    WHERE release.id IN (NEW.release, OLD.release);

    PERFORM set_releases_recordings_first_release_dates(ARRAY[NEW.release, OLD.release]);
  END IF;

  IF TG_TABLE_NAME = 'release_country' THEN
    IF NEW.country != OLD.country THEN
      INSERT INTO artist_release_pending_update VALUES (OLD.release);
    END IF;
  END IF;

  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

--------------------------------------------------------------------------------
SELECT '20210606-mbs-11682.sql';


CREATE OR REPLACE FUNCTION apply_artist_release_pending_updates()
RETURNS trigger AS $$
DECLARE
    release_ids INTEGER[];
    release_id INTEGER;
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.
    WITH pending AS (
        DELETE FROM artist_release_pending_update
        RETURNING release
    )
    SELECT array_agg(DISTINCT release)
    INTO release_ids
    FROM pending;

    IF coalesce(array_length(release_ids, 1), 0) > 0 THEN
        -- If the user hasn't generated `artist_release`, then we
        -- shouldn't update or insert to it. MBS determines whether to
        -- use this table based on it being non-empty, so a partial
        -- table would manifest as partial data on the website and
        -- webservice.
        PERFORM 1 FROM artist_release LIMIT 1;
        IF FOUND THEN
            DELETE FROM artist_release WHERE release = any(release_ids);

            FOREACH release_id IN ARRAY release_ids LOOP
                -- We handle each release ID separately because the
                -- `get_artist_release_rows` query can be planned much
                -- more efficiently that way.
                INSERT INTO artist_release
                SELECT * FROM get_artist_release_rows(release_id);
            END LOOP;
        END IF;
    END IF;

    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION apply_artist_release_group_pending_updates()
RETURNS trigger AS $$
DECLARE
    release_group_ids INTEGER[];
    release_group_id INTEGER;
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.
    WITH pending AS (
        DELETE FROM artist_release_group_pending_update
        RETURNING release_group
    )
    SELECT array_agg(DISTINCT release_group)
    INTO release_group_ids
    FROM pending;

    IF coalesce(array_length(release_group_ids, 1), 0) > 0 THEN
        -- If the user hasn't generated `artist_release_group`, then we
        -- shouldn't update or insert to it. MBS determines whether to
        -- use this table based on it being non-empty, so a partial
        -- table would manifest as partial data on the website and
        -- webservice.
        PERFORM 1 FROM artist_release_group LIMIT 1;
        IF FOUND THEN
            DELETE FROM artist_release_group WHERE release_group = any(release_group_ids);

            FOREACH release_group_id IN ARRAY release_group_ids LOOP
                -- We handle each release group ID separately because
                -- the `get_artist_release_group_rows` query can be
                -- planned much more efficiently that way.
                INSERT INTO artist_release_group
                SELECT * FROM get_artist_release_group_rows(release_group_id);
            END LOOP;
        END IF;
    END IF;

    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

--------------------------------------------------------------------------------
SELECT '20220207-mbs-12224-mirror.sql';


CREATE TYPE taggable_entity_type AS ENUM (
    'area',
    'artist',
    'event',
    'instrument',
    'label',
    'place',
    'recording',
    'release',
    'release_group',
    'series',
    'work'
);

CREATE OR REPLACE FUNCTION update_aggregate_tag_count(entity_type taggable_entity_type, entity_id INTEGER, tag_id INTEGER, count_change SMALLINT)
RETURNS VOID AS $$
BEGIN
  -- Insert-or-update the aggregate vote count for the given (entity_id, tag_id).
  EXECUTE format(
    $SQL$
      INSERT INTO %1$I AS agg (%2$I, tag, count)
           VALUES ($1, $2, $3)
      ON CONFLICT (%2$I, tag) DO UPDATE SET count = agg.count + $3
    $SQL$,
    entity_type::TEXT || '_tag',
    entity_type::TEXT
  ) USING entity_id, tag_id, count_change;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION delete_unused_aggregate_tag(entity_type taggable_entity_type, entity_id INTEGER, tag_id INTEGER)
RETURNS VOID AS $$
BEGIN
  -- Delete the aggregate tag row for (entity_id, tag_id) if no raw tag pair
  -- exists for the same.
  --
  -- Note that an aggregate vote count of 0 doesn't imply there are no raw
  -- tags; it's a sum of all the votes, so it can also mean that there's a
  -- downvote for every upvote.
  EXECUTE format(
    $SQL$
      DELETE FROM %1$I
            WHERE %2$I = $1
              AND tag = $2
              AND NOT EXISTS (SELECT 1 FROM %3$I WHERE %2$I = $1 AND tag = $2)
    $SQL$,
    entity_type::TEXT || '_tag',
    entity_type::TEXT,
    entity_type::TEXT || '_tag_raw'
  ) USING entity_id, tag_id;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_insert()
RETURNS trigger AS $$
DECLARE
  entity_type taggable_entity_type;
  new_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::taggable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type::TEXT) INTO new_entity_id USING NEW;
  PERFORM update_aggregate_tag_count(entity_type, new_entity_id, NEW.tag, (CASE WHEN NEW.is_upvote THEN 1 ELSE -1 END)::SMALLINT);
  UPDATE tag SET ref_count = ref_count + 1 WHERE id = NEW.tag;
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_update()
RETURNS trigger AS $$
DECLARE
  entity_type taggable_entity_type;
  new_entity_id INTEGER;
  old_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::taggable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type) INTO new_entity_id USING NEW;
  EXECUTE format('SELECT ($1).%s', entity_type) INTO old_entity_id USING OLD;
  IF (old_entity_id = new_entity_id AND OLD.tag = NEW.tag AND OLD.is_upvote != NEW.is_upvote) THEN
    -- Case 1: only the vote changed.
    PERFORM update_aggregate_tag_count(entity_type, old_entity_id, OLD.tag, (CASE WHEN OLD.is_upvote THEN -2 ELSE 2 END)::SMALLINT);
  ELSIF (old_entity_id != new_entity_id OR OLD.tag != NEW.tag OR OLD.is_upvote != NEW.is_upvote) THEN
    -- Case 2: the entity, tag, or vote changed.
    PERFORM update_aggregate_tag_count(entity_type, old_entity_id, OLD.tag, (CASE WHEN OLD.is_upvote THEN -1 ELSE 1 END)::SMALLINT);
    PERFORM update_aggregate_tag_count(entity_type, new_entity_id, NEW.tag, (CASE WHEN NEW.is_upvote THEN 1 ELSE -1 END)::SMALLINT);
    PERFORM delete_unused_aggregate_tag(entity_type, old_entity_id, OLD.tag);
  END IF;
  IF OLD.tag != NEW.tag THEN
    UPDATE tag SET ref_count = ref_count - 1 WHERE id = OLD.tag;
    UPDATE tag SET ref_count = ref_count + 1 WHERE id = NEW.tag;
  END IF;
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_delete()
RETURNS trigger AS $$
DECLARE
  entity_type taggable_entity_type;
  old_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::taggable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type::TEXT) INTO old_entity_id USING OLD;
  PERFORM update_aggregate_tag_count(entity_type, old_entity_id, OLD.tag, (CASE WHEN OLD.is_upvote THEN -1 ELSE 1 END)::SMALLINT);
  PERFORM delete_unused_aggregate_tag(entity_type, old_entity_id, OLD.tag);
  UPDATE tag SET ref_count = ref_count - 1 WHERE id = OLD.tag;
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

--------------------------------------------------------------------------------
SELECT '20220218-mbs-12208.sql';


CREATE OR REPLACE FUNCTION get_artist_release_group_rows(
    release_group_id INTEGER
) RETURNS SETOF artist_release_group AS $$
BEGIN
    -- PostgreSQL 12 generates a vastly more efficient plan when only
    -- one release group ID is passed. A condition like
    -- `rg.id = any(...)` can be over 200x slower, even with only one
    -- release group ID in the array.
    RETURN QUERY EXECUTE $SQL$
        SELECT DISTINCT ON (a_rg.artist, rg.id)
            a_rg.is_track_artist,
            a_rg.artist,
            -- Withdrawn releases were once official by definition
            bool_and(r.status IS NOT NULL AND r.status != 1 AND r.status != 5),
            rg.type::SMALLINT,
            array_agg(
                DISTINCT st.secondary_type ORDER BY st.secondary_type)
                FILTER (WHERE st.secondary_type IS NOT NULL
            )::SMALLINT[],
            integer_date(
                rgm.first_release_date_year,
                rgm.first_release_date_month,
                rgm.first_release_date_day
            ),
            left(rg.name, 1)::CHAR(1),
            rg.id
        FROM (
            SELECT FALSE AS is_track_artist, rgacn.artist, rg.id AS release_group
            FROM release_group rg
            JOIN artist_credit_name rgacn ON rgacn.artist_credit = rg.artist_credit
            UNION ALL
            SELECT TRUE AS is_track_artist, tacn.artist, r.release_group
            FROM release r
            JOIN medium m ON m.release = r.id
            JOIN track t ON t.medium = m.id
            JOIN artist_credit_name tacn ON tacn.artist_credit = t.artist_credit
        ) a_rg
        JOIN release_group rg ON rg.id = a_rg.release_group
        LEFT JOIN release r ON r.release_group = rg.id
        JOIN release_group_meta rgm ON rgm.id = rg.id
        LEFT JOIN release_group_secondary_type_join st ON st.release_group = rg.id
    $SQL$ || (CASE WHEN release_group_id IS NULL THEN '' ELSE 'WHERE rg.id = $1' END) ||
    $SQL$
        GROUP BY a_rg.is_track_artist, a_rg.artist, rgm.id, rg.id
        ORDER BY a_rg.artist, rg.id, a_rg.is_track_artist
    $SQL$
    USING release_group_id;
END;
$$ LANGUAGE plpgsql;

-- We update the table for any existing RGs containing withdrawn releases
-- with this one-off script; the updated function will keep it up after this.
DO $$
DECLARE
    release_group_ids INTEGER[];
    release_group_id INTEGER;
BEGIN
    SELECT array_agg(DISTINCT rg.id)
    INTO release_group_ids
    FROM release_group rg
    JOIN release r ON rg.id = r.release_group
    WHERE r.status = 5; -- Withdrawn

    IF coalesce(array_length(release_group_ids, 1), 0) > 0 THEN
        -- If the user hasn't generated `artist_release_group`, then we
        -- shouldn't update or insert to it. MBS determines whether to
        -- use this table based on it being non-empty, so a partial
        -- table would manifest as partial data on the website and
        -- webservice.
        PERFORM 1 FROM artist_release_group LIMIT 1;
        IF FOUND THEN
            DELETE FROM artist_release_group WHERE release_group = any(release_group_ids);

            FOREACH release_group_id IN ARRAY release_group_ids LOOP
                -- We handle each release group ID separately because
                -- the `get_artist_release_group_rows` query can be
                -- planned much more efficiently that way.
                INSERT INTO artist_release_group
                SELECT * FROM get_artist_release_group_rows(release_group_id);
            END LOOP;
        END IF;
    END IF;
END $$;

--------------------------------------------------------------------------------
SELECT '20220302-mbs-12225.sql';


-- The internal comment had the word "slaves" changed to "mirrors."
-- The function is otherwise unchanged.
CREATE OR REPLACE FUNCTION set_recordings_first_release_dates(recording_ids INTEGER[])
RETURNS VOID AS $$
BEGIN
  -- DO NOT modify any replicated tables in this function; it's used
  -- by a trigger on mirrors.
  DELETE FROM recording_first_release_date
  WHERE recording = ANY(recording_ids);

  INSERT INTO recording_first_release_date
  SELECT * FROM get_recording_first_release_date_rows(
    format('track.recording = any(%L)', recording_ids)
  );
END;
$$ LANGUAGE 'plpgsql' STRICT;

-- The internal comment had the word "slaves" changed to "mirrors."
-- The function is otherwise unchanged.
CREATE OR REPLACE FUNCTION set_release_first_release_date(release_id INTEGER)
RETURNS VOID AS $$
BEGIN
  -- DO NOT modify any replicated tables in this function; it's used
  -- by a trigger on mirrors.
  DELETE FROM release_first_release_date
  WHERE release = release_id;

  INSERT INTO release_first_release_date
  SELECT * FROM get_release_first_release_date_rows(
    format('release = %L', release_id)
  );

  INSERT INTO artist_release_pending_update VALUES (release_id);
END;
$$ LANGUAGE 'plpgsql' STRICT;

--------------------------------------------------------------------------------
SELECT '20220314-mbs-12252.sql';


CREATE TABLE edit_genre
(
    edit                INTEGER NOT NULL, -- PK, references edit.id
    genre               INTEGER NOT NULL  -- PK, references genre.id CASCADE
);

ALTER TABLE edit_genre ADD CONSTRAINT edit_genre_pkey PRIMARY KEY (edit, genre);

CREATE INDEX edit_genre_idx ON edit_genre (genre);

--------------------------------------------------------------------------------
SELECT '20220314-mbs-12253.sql';


CREATE TABLE l_area_genre ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references area.id
    entity1             INTEGER NOT NULL, -- references genre.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_artist_genre ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references artist.id
    entity1             INTEGER NOT NULL, -- references genre.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_event_genre ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references event.id
    entity1             INTEGER NOT NULL, -- references genre.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_genre ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references genre.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_instrument ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references instrument.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_label ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references label.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_place ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references place.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_recording ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references recording.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_release ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references release.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_release_group ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references release_group.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_series ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references series.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_url ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references url.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_work ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references work.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);


ALTER TABLE l_area_genre ADD CONSTRAINT l_area_genre_pkey PRIMARY KEY (id);
ALTER TABLE l_artist_genre ADD CONSTRAINT l_artist_genre_pkey PRIMARY KEY (id);
ALTER TABLE l_event_genre ADD CONSTRAINT l_event_genre_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_genre ADD CONSTRAINT l_genre_genre_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_instrument ADD CONSTRAINT l_genre_instrument_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_label ADD CONSTRAINT l_genre_label_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_place ADD CONSTRAINT l_genre_place_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_recording ADD CONSTRAINT l_genre_recording_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_release ADD CONSTRAINT l_genre_release_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_release_group ADD CONSTRAINT l_genre_release_group_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_series ADD CONSTRAINT l_genre_series_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_url ADD CONSTRAINT l_genre_url_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_work ADD CONSTRAINT l_genre_work_pkey PRIMARY KEY (id);

CREATE OR REPLACE FUNCTION delete_unused_url(ids INTEGER[])
RETURNS VOID AS $$
DECLARE
  clear_up INTEGER[];
BEGIN
  SELECT ARRAY(
    SELECT id FROM url url_row WHERE id = any(ids)
    EXCEPT
    SELECT url FROM edit_url JOIN edit ON (edit.id = edit_url.edit) WHERE edit.status = 1
    EXCEPT
    SELECT entity1 FROM l_area_url
    EXCEPT
    SELECT entity1 FROM l_artist_url
    EXCEPT
    SELECT entity1 FROM l_event_url
    EXCEPT
    SELECT entity1 FROM l_genre_url
    EXCEPT
    SELECT entity1 FROM l_instrument_url
    EXCEPT
    SELECT entity1 FROM l_label_url
    EXCEPT
    SELECT entity1 FROM l_place_url
    EXCEPT
    SELECT entity1 FROM l_recording_url
    EXCEPT
    SELECT entity1 FROM l_release_url
    EXCEPT
    SELECT entity1 FROM l_release_group_url
    EXCEPT
    SELECT entity1 FROM l_series_url
    EXCEPT
    SELECT entity1 FROM l_url_url
    EXCEPT
    SELECT entity0 FROM l_url_url
    EXCEPT
    SELECT entity0 FROM l_url_work
  ) INTO clear_up;

  DELETE FROM url_gid_redirect WHERE new_id = any(clear_up);
  DELETE FROM url WHERE id = any(clear_up);
END;
$$ LANGUAGE 'plpgsql';


CREATE UNIQUE INDEX l_area_genre_idx_uniq ON l_area_genre (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_artist_genre_idx_uniq ON l_artist_genre (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_event_genre_idx_uniq ON l_event_genre (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_genre_idx_uniq ON l_genre_genre (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_instrument_idx_uniq ON l_genre_instrument (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_label_idx_uniq ON l_genre_label (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_place_idx_uniq ON l_genre_place (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_recording_idx_uniq ON l_genre_recording (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_release_idx_uniq ON l_genre_release (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_release_group_idx_uniq ON l_genre_release_group (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_series_idx_uniq ON l_genre_series (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_url_idx_uniq ON l_genre_url (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_work_idx_uniq ON l_genre_work (entity0, entity1, link, link_order);

CREATE INDEX l_area_genre_idx_entity1 ON l_area_genre (entity1);
CREATE INDEX l_artist_genre_idx_entity1 ON l_artist_genre (entity1);
CREATE INDEX l_event_genre_idx_entity1 ON l_event_genre (entity1);
CREATE INDEX l_genre_genre_idx_entity1 ON l_genre_genre (entity1);
CREATE INDEX l_genre_instrument_idx_entity1 ON l_genre_instrument (entity1);
CREATE INDEX l_genre_label_idx_entity1 ON l_genre_label (entity1);
CREATE INDEX l_genre_place_idx_entity1 ON l_genre_place (entity1);
CREATE INDEX l_genre_recording_idx_entity1 ON l_genre_recording (entity1);
CREATE INDEX l_genre_release_idx_entity1 ON l_genre_release (entity1);
CREATE INDEX l_genre_release_group_idx_entity1 ON l_genre_release_group (entity1);
CREATE INDEX l_genre_series_idx_entity1 ON l_genre_series (entity1);
CREATE INDEX l_genre_url_idx_entity1 ON l_genre_url (entity1);
CREATE INDEX l_genre_work_idx_entity1 ON l_genre_work (entity1);


CREATE TABLE documentation.l_area_genre_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_area_genre.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_artist_genre_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_artist_genre.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_event_genre_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_event_genre.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_genre_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_genre.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_instrument_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_instrument.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_label_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_label.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_place_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_place.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_recording_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_recording.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_release_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_release.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_release_group_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_release_group.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_series_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_series.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_url_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_url.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_work_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_work.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);


ALTER TABLE documentation.l_area_genre_example ADD CONSTRAINT l_area_genre_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_artist_genre_example ADD CONSTRAINT l_artist_genre_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_event_genre_example ADD CONSTRAINT l_event_genre_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_genre_example ADD CONSTRAINT l_genre_genre_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_instrument_example ADD CONSTRAINT l_genre_instrument_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_label_example ADD CONSTRAINT l_genre_label_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_place_example ADD CONSTRAINT l_genre_place_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_recording_example ADD CONSTRAINT l_genre_recording_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_release_example ADD CONSTRAINT l_genre_release_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_release_group_example ADD CONSTRAINT l_genre_release_group_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_series_example ADD CONSTRAINT l_genre_series_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_url_example ADD CONSTRAINT l_genre_url_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_work_example ADD CONSTRAINT l_genre_work_example_pkey PRIMARY KEY (id);

--------------------------------------------------------------------------------
SELECT '20220314-mbs-12254.sql';


CREATE TABLE genre_annotation ( -- replicate (verbose)
    genre       INTEGER NOT NULL, -- PK, references genre.id
    annotation  INTEGER NOT NULL -- PK, references annotation.id
);

ALTER TABLE genre_annotation ADD CONSTRAINT genre_annotation_pkey PRIMARY KEY (genre, annotation);

--------------------------------------------------------------------------------
SELECT '20220314-mbs-12255.sql';


CREATE TABLE genre_alias_type ( -- replicate
    id                  SERIAL, -- PK,
    name                TEXT NOT NULL,
    parent              INTEGER, -- references genre_alias_type.id
    child_order         INTEGER NOT NULL DEFAULT 0,
    description         TEXT,
    gid                 uuid NOT NULL
);

ALTER TABLE genre_alias_type ADD CONSTRAINT genre_alias_type_pkey PRIMARY KEY (id);

CREATE UNIQUE INDEX genre_alias_type_idx_gid ON genre_alias_type (gid);

-- generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'genre_type' || id);
INSERT INTO genre_alias_type (id, gid, name)
    VALUES (1, '61e89fea-acce-3908-a590-d999dc627ac9', 'Genre name'),
           (2, '5d81fc72-598a-3a9d-a85a-a471c6ba84dc', 'Search hint');

-- We drop and recreate the table to standardise it
-- rather than adding a ton of rows to it out of the standard order.
-- This is empty in production and mirrors but might not be on standalone
CREATE TEMPORARY TABLE tmp_genre_alias
    ON COMMIT DROP
    AS
    SELECT * FROM genre_alias;

DROP TABLE genre_alias;

CREATE TABLE genre_alias ( -- replicate (verbose)
    id                  SERIAL, --PK
    genre               INTEGER NOT NULL, -- references genre.id
    name                VARCHAR NOT NULL,
    locale              TEXT,
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    type                INTEGER, -- references genre_alias_type.id
    sort_name           VARCHAR NOT NULL,
    begin_date_year     SMALLINT,
    begin_date_month    SMALLINT,
    begin_date_day      SMALLINT,
    end_date_year       SMALLINT,
    end_date_month      SMALLINT,
    end_date_day        SMALLINT,
    primary_for_locale  BOOLEAN NOT NULL DEFAULT false,
    ended               BOOLEAN NOT NULL DEFAULT FALSE
      CHECK (
        (
          -- If any end date fields are not null, then ended must be true
          (end_date_year IS NOT NULL OR
           end_date_month IS NOT NULL OR
           end_date_day IS NOT NULL) AND
          ended = TRUE
        ) OR (
          -- Otherwise, all end date fields must be null
          (end_date_year IS NULL AND
           end_date_month IS NULL AND
           end_date_day IS NULL)
        )
      ),
    CONSTRAINT primary_check CHECK ((locale IS NULL AND primary_for_locale IS FALSE) OR (locale IS NOT NULL)),
    CONSTRAINT search_hints_are_empty
      CHECK (
        (type <> 2) OR (
          type = 2 AND sort_name = name AND
          begin_date_year IS NULL AND begin_date_month IS NULL AND begin_date_day IS NULL AND
          end_date_year IS NULL AND end_date_month IS NULL AND end_date_day IS NULL AND
          primary_for_locale IS FALSE AND locale IS NULL
        )
      )
);

ALTER TABLE genre_alias ADD CONSTRAINT genre_alias_pkey PRIMARY KEY (id);

CREATE INDEX genre_alias_idx_genre ON genre_alias (genre);
CREATE UNIQUE INDEX genre_alias_idx_primary ON genre_alias (genre, locale) WHERE primary_for_locale = TRUE AND locale IS NOT NULL;

INSERT INTO genre_alias (id, genre, name, locale, edits_pending, last_updated, type, sort_name)
SELECT id, genre, name, locale, edits_pending, last_updated, 1, name -- sortname = name, type = genre name
FROM tmp_genre_alias;

--------------------------------------------------------------------------------
SELECT '20220328-mbs-12250-mirror.sql';


CREATE SCHEMA IF NOT EXISTS dbmirror2;

--------------------------------------------------------------------------------
SELECT '20220322-mbs-12256-mirror.sql';


UPDATE artist_meta
   SET rating = agg.rating
  FROM (
      SELECT artist,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM artist_rating_raw
    GROUP BY artist
  ) agg
 WHERE id = agg.artist;

UPDATE event_meta
   SET rating = agg.rating
  FROM (
      SELECT event,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM event_rating_raw
    GROUP BY event
  ) agg
 WHERE id = agg.event;

UPDATE label_meta
   SET rating = agg.rating
  FROM (
      SELECT label,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM label_rating_raw
    GROUP BY label
  ) agg
 WHERE id = agg.label;

UPDATE place_meta
   SET rating = agg.rating
  FROM (
      SELECT place,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM place_rating_raw
    GROUP BY place
  ) agg
 WHERE id = agg.place;

UPDATE recording_meta
   SET rating = agg.rating
  FROM (
      SELECT recording,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM recording_rating_raw
    GROUP BY recording
  ) agg
 WHERE id = agg.recording;

UPDATE release_group_meta
   SET rating = agg.rating
  FROM (
      SELECT release_group,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM release_group_rating_raw
    GROUP BY release_group
  ) agg
 WHERE id = agg.release_group;

UPDATE work_meta
   SET rating = agg.rating
  FROM (
      SELECT work,
             trunc((sum(rating)::REAL /
                    count(rating)::REAL) +
                   0.5::REAL)::SMALLINT AS rating
        FROM work_rating_raw
    GROUP BY work
  ) agg
 WHERE id = agg.work;

CREATE TYPE ratable_entity_type AS ENUM (
    'artist',
    'event',
    'label',
    'place',
    'recording',
    'release_group',
    'work'
);

CREATE OR REPLACE FUNCTION update_aggregate_rating(entity_type ratable_entity_type, entity_id INTEGER)
RETURNS VOID AS $$
BEGIN
  -- update the aggregate rating for the given entity_id.
  EXECUTE format(
    $SQL$
      UPDATE %2$I
         SET rating = agg.rating,
             rating_count = nullif(agg.rating_count, 0)
        FROM (
          SELECT count(rating)::INTEGER AS rating_count,
                 -- trunc(x + 0.5) is used because round() on REAL values
                 -- rounds to the nearest even number.
                 trunc((sum(rating)::REAL /
                        count(rating)::REAL) +
                       0.5::REAL)::SMALLINT AS rating
            FROM %3$I
           WHERE %1$I = $1
        ) agg
       WHERE id = $1
    $SQL$,
    entity_type::TEXT,
    entity_type::TEXT || '_meta',
    entity_type::TEXT || '_rating_raw'
  ) USING entity_id;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_insert()
RETURNS trigger AS $$
DECLARE
  entity_type ratable_entity_type;
  new_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::ratable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type::TEXT) INTO new_entity_id USING NEW;
  PERFORM update_aggregate_rating(entity_type, new_entity_id);
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_update()
RETURNS trigger AS $$
DECLARE
  entity_type ratable_entity_type;
  new_entity_id INTEGER;
  old_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::ratable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type) INTO new_entity_id USING NEW;
  EXECUTE format('SELECT ($1).%s', entity_type) INTO old_entity_id USING OLD;
  IF (old_entity_id = new_entity_id AND OLD.rating != NEW.rating) THEN
    -- Case 1: only the rating changed.
    PERFORM update_aggregate_rating(entity_type, old_entity_id);
  ELSIF (old_entity_id != new_entity_id OR OLD.rating != NEW.rating) THEN
    -- Case 2: the entity or rating changed.
    PERFORM update_aggregate_rating(entity_type, old_entity_id);
    PERFORM update_aggregate_rating(entity_type, new_entity_id);
  END IF;
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_delete()
RETURNS trigger AS $$
DECLARE
  entity_type ratable_entity_type;
  old_entity_id INTEGER;
BEGIN
  entity_type := TG_ARGV[0]::ratable_entity_type;
  EXECUTE format('SELECT ($1).%s', entity_type::TEXT) INTO old_entity_id USING OLD;
  PERFORM update_aggregate_rating(entity_type, old_entity_id);
  RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

--------------------------------------------------------------------------------
SELECT '20220324-mbs-12200.sql';


-- Redefined to remove `INSERT INTO release_coverart (id) VALUES (NEW.id);`.
CREATE OR REPLACE FUNCTION a_ins_release() RETURNS trigger AS $$
BEGIN
    -- increment ref_count of the name
    PERFORM inc_ref_count('artist_credit', NEW.artist_credit, 1);
    -- increment release_count of the parent release group
    UPDATE release_group_meta SET release_count = release_count + 1 WHERE id = NEW.release_group;
    -- add new release_meta
    INSERT INTO release_meta (id) VALUES (NEW.id);
    INSERT INTO artist_release_pending_update VALUES (NEW.id);
    INSERT INTO artist_release_group_pending_update VALUES (NEW.release_group);
    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

DROP TABLE release_coverart;

ALTER TABLE release_meta DROP COLUMN amazon_store;

--------------------------------------------------------------------------------
SELECT '20220408-immutable-link-tables.sql';


CREATE OR REPLACE FUNCTION deny_deprecated_links()
RETURNS trigger AS $$
BEGIN
  IF (SELECT is_deprecated FROM link_type WHERE id = NEW.link_type)
  THEN
    RAISE EXCEPTION 'Attempt to create a relationship with a deprecated type';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION b_upd_link() RETURNS trigger AS $$
BEGIN
    -- Like artist credits, links are shared across many entities
    -- (relationships) and so are immutable: they can only be inserted
    -- or deleted.
    --
    -- This helps ensure the data integrity of relationships and other
    -- materialized tables that rely on their immutability, like
    -- area_containment.
    RAISE EXCEPTION 'link rows are immutable';
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION b_upd_link_attribute() RETURNS trigger AS $$
BEGIN
    -- Refer to b_upd_link.
    RAISE EXCEPTION 'link_attribute rows are immutable';
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION b_upd_link_attribute_credit() RETURNS trigger AS $$
BEGIN
    -- Refer to b_upd_link.
    RAISE EXCEPTION 'link_attribute_credit rows are immutable';
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION b_upd_link_attribute_text_value() RETURNS trigger AS $$
BEGIN
    -- Refer to b_upd_link.
    RAISE EXCEPTION 'link_attribute_text_value rows are immutable';
END;
$$ LANGUAGE 'plpgsql';

--------------------------------------------------------------------------------
SELECT '20220408-mbs-12249.sql';


CREATE TABLE area_containment (
    descendant          INTEGER NOT NULL, -- PK, references area.id
    parent              INTEGER NOT NULL, -- PK, references area.id
    depth               SMALLINT NOT NULL
);

CREATE OR REPLACE FUNCTION a_ins_l_area_area_mirror() RETURNS trigger AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.
    IF (SELECT link_type FROM link WHERE id = NEW.link) = part_of_area_link_type_id THEN
        PERFORM update_area_containment_mirror(ARRAY[NEW.entity0], ARRAY[NEW.entity1]);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION a_upd_l_area_area_mirror() RETURNS trigger AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
    old_lt_id INTEGER;
    new_lt_id INTEGER;
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.
    SELECT link_type INTO old_lt_id FROM link WHERE id = OLD.link;
    SELECT link_type INTO new_lt_id FROM link WHERE id = NEW.link;
    IF (
        (
            old_lt_id = part_of_area_link_type_id AND
            new_lt_id = part_of_area_link_type_id AND
            (OLD.entity0 != NEW.entity0 OR OLD.entity1 != NEW.entity1)
        ) OR
        (old_lt_id = part_of_area_link_type_id) != (new_lt_id = part_of_area_link_type_id)
    ) THEN
        PERFORM update_area_containment_mirror(ARRAY[OLD.entity0, NEW.entity0], ARRAY[OLD.entity1, NEW.entity1]);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION a_del_l_area_area_mirror() RETURNS trigger AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.
    IF (SELECT link_type FROM link WHERE id = OLD.link) = part_of_area_link_type_id THEN
        PERFORM update_area_containment_mirror(ARRAY[OLD.entity0], ARRAY[OLD.entity1]);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION get_area_parent_hierarchy_rows(
    descendant_area_ids INTEGER[]
) RETURNS SETOF area_containment AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
BEGIN
    RETURN QUERY EXECUTE $SQL$
        WITH RECURSIVE area_parent_hierarchy(descendant, parent, path, cycle) AS (
            SELECT entity1, entity0, ARRAY[ROW(entity1, entity0)], FALSE
              FROM l_area_area laa
              JOIN link ON laa.link = link.id
             WHERE link.link_type = $1
    $SQL$ || (CASE WHEN descendant_area_ids IS NULL THEN '' ELSE 'AND entity1 = any($2)' END) ||
    $SQL$
             UNION ALL
            SELECT descendant, entity0, path || ROW(descendant, entity0), ROW(descendant, entity0) = any(path)
              FROM l_area_area laa
              JOIN link ON laa.link = link.id
              JOIN area_parent_hierarchy ON area_parent_hierarchy.parent = laa.entity1
             WHERE link.link_type = $1
               AND descendant != entity0
               AND NOT cycle
        )
        SELECT descendant, parent, array_length(path, 1)::SMALLINT
          FROM area_parent_hierarchy
    $SQL$
    USING part_of_area_link_type_id, descendant_area_ids;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION get_area_descendant_hierarchy_rows(
    parent_area_ids INTEGER[]
) RETURNS SETOF area_containment AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
BEGIN
    RETURN QUERY EXECUTE $SQL$
        WITH RECURSIVE area_descendant_hierarchy(descendant, parent, path, cycle) AS (
            SELECT entity1, entity0, ARRAY[ROW(entity1, entity0)], FALSE
              FROM l_area_area laa
              JOIN link ON laa.link = link.id
             WHERE link.link_type = $1
    $SQL$ || (CASE WHEN parent_area_ids IS NULL THEN '' ELSE 'AND entity0 = any($2)' END) ||
    $SQL$
             UNION ALL
            SELECT entity1, parent, path || ROW(entity1, parent), ROW(entity1, parent) = any(path)
              FROM l_area_area laa
              JOIN link ON laa.link = link.id
              JOIN area_descendant_hierarchy ON area_descendant_hierarchy.descendant = laa.entity0
             WHERE link.link_type = $1
               AND parent != entity1
               AND NOT cycle
        )
        SELECT descendant, parent, array_length(path, 1)::SMALLINT
          FROM area_descendant_hierarchy
    $SQL$
    USING part_of_area_link_type_id, parent_area_ids;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION update_area_containment_mirror(
    parent_ids INTEGER[], -- entity0 of area-area "part of"
    descendant_ids INTEGER[] -- entity1
) RETURNS VOID AS $$
DECLARE
    part_of_area_link_type_id CONSTANT SMALLINT := 356;
    descendant_ids_to_update INTEGER[];
    parent_ids_to_update INTEGER[];
BEGIN
    -- DO NOT modify any replicated tables in this function; it's used
    -- by a trigger on mirrors.

    SELECT array_agg(descendant)
      INTO descendant_ids_to_update
      FROM area_containment
     WHERE parent = any(parent_ids);

    SELECT array_agg(parent)
      INTO parent_ids_to_update
      FROM area_containment
     WHERE descendant = any(descendant_ids);

    -- For INSERTS/UPDATES, include the new IDs that aren't present in
    -- area_containment yet.
    descendant_ids_to_update := descendant_ids_to_update || descendant_ids;
    parent_ids_to_update := parent_ids_to_update || parent_ids;

    DELETE FROM area_containment
     WHERE descendant = any(descendant_ids_to_update);

    DELETE FROM area_containment
     WHERE parent = any(parent_ids_to_update);

    -- Update the parents of all descendants of parent_ids.
    -- Update the descendants of all parents of descendant_ids.

    INSERT INTO area_containment
    SELECT DISTINCT ON (descendant, parent)
        descendant, parent, depth
      FROM (
          SELECT * FROM get_area_parent_hierarchy_rows(descendant_ids_to_update)
          UNION ALL
          SELECT * FROM get_area_descendant_hierarchy_rows(parent_ids_to_update)
      ) area_hierarchy
     ORDER BY descendant, parent, depth;
END;
$$ LANGUAGE plpgsql;

-- Note: when passing NULL, it doesn't matter whether we use
-- get_area_parent_hierarchy_rows vs. get_area_descendant_hierarchy_rows
-- to build the entire table.
INSERT INTO area_containment
SELECT DISTINCT ON (descendant, parent)
    descendant,
    parent,
    depth
 FROM get_area_parent_hierarchy_rows(NULL)
ORDER BY descendant, parent, depth;

ALTER TABLE area_containment ADD CONSTRAINT area_containment_pkey PRIMARY KEY (descendant, parent);

CREATE INDEX area_containment_idx_parent ON area_containment (parent);

--------------------------------------------------------------------------------
SELECT '20220412-mbs-12190.sql';


-- Tables

CREATE TABLE edit_mood
(
    edit                INTEGER NOT NULL, -- PK, references edit.id
    mood                INTEGER NOT NULL  -- PK, references mood.id CASCADE
);

CREATE TABLE l_area_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references area.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_artist_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references artist.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_event_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references event.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_genre_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references genre.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_instrument_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references instrument.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_label_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references label.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_mood ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references mood.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_place ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references place.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_recording ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references recording.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_release ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references release.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_release_group ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references release_group.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_series ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references series.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_url ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references url.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE l_mood_work ( -- replicate
    id                  SERIAL,
    link                INTEGER NOT NULL, -- references link.id
    entity0             INTEGER NOT NULL, -- references mood.id
    entity1             INTEGER NOT NULL, -- references work.id
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    link_order          INTEGER NOT NULL DEFAULT 0 CHECK (link_order >= 0),
    entity0_credit      TEXT NOT NULL DEFAULT '',
    entity1_credit      TEXT NOT NULL DEFAULT ''
);

CREATE TABLE mood ( -- replicate (verbose)
    id                  SERIAL, -- PK
    gid                 UUID NOT NULL,
    name                VARCHAR NOT NULL,
    comment             VARCHAR(255) NOT NULL DEFAULT '',
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >=0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE mood_alias_type ( -- replicate
    id                  SERIAL, -- PK,
    name                TEXT NOT NULL,
    parent              INTEGER, -- references mood_alias_type.id
    child_order         INTEGER NOT NULL DEFAULT 0,
    description         TEXT,
    gid                 uuid NOT NULL
);

CREATE TABLE mood_alias ( -- replicate (verbose)
    id                  SERIAL, --PK
    mood                INTEGER NOT NULL, -- references mood.id
    name                VARCHAR NOT NULL,
    locale              TEXT,
    edits_pending       INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
    last_updated        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    type                INTEGER, -- references mood_alias_type.id
    sort_name           VARCHAR NOT NULL,
    begin_date_year     SMALLINT,
    begin_date_month    SMALLINT,
    begin_date_day      SMALLINT,
    end_date_year       SMALLINT,
    end_date_month      SMALLINT,
    end_date_day        SMALLINT,
    primary_for_locale  BOOLEAN NOT NULL DEFAULT false,
    ended               BOOLEAN NOT NULL DEFAULT FALSE
      CHECK (
        (
          -- If any end date fields are not null, then ended must be true
          (end_date_year IS NOT NULL OR
           end_date_month IS NOT NULL OR
           end_date_day IS NOT NULL) AND
          ended = TRUE
        ) OR (
          -- Otherwise, all end date fields must be null
          (end_date_year IS NULL AND
           end_date_month IS NULL AND
           end_date_day IS NULL)
        )
      ),
    CONSTRAINT primary_check CHECK ((locale IS NULL AND primary_for_locale IS FALSE) OR (locale IS NOT NULL)),
    CONSTRAINT search_hints_are_empty
      CHECK (
        (type <> 2) OR (
          type = 2 AND sort_name = name AND
          begin_date_year IS NULL AND begin_date_month IS NULL AND begin_date_day IS NULL AND
          end_date_year IS NULL AND end_date_month IS NULL AND end_date_day IS NULL AND
          primary_for_locale IS FALSE AND locale IS NULL
        )
      )
);

CREATE TABLE mood_annotation ( -- replicate (verbose)
    mood        INTEGER NOT NULL, -- PK, references mood.id
    annotation  INTEGER NOT NULL -- PK, references annotation.id
);


-- Indexes

CREATE INDEX edit_mood_idx ON edit_mood (mood);

CREATE UNIQUE INDEX l_area_mood_idx_uniq ON l_area_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_artist_mood_idx_uniq ON l_artist_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_event_mood_idx_uniq ON l_event_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_genre_mood_idx_uniq ON l_genre_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_instrument_mood_idx_uniq ON l_instrument_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_label_mood_idx_uniq ON l_label_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_mood_idx_uniq ON l_mood_mood (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_place_idx_uniq ON l_mood_place (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_recording_idx_uniq ON l_mood_recording (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_release_idx_uniq ON l_mood_release (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_release_group_idx_uniq ON l_mood_release_group (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_series_idx_uniq ON l_mood_series (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_url_idx_uniq ON l_mood_url (entity0, entity1, link, link_order);
CREATE UNIQUE INDEX l_mood_work_idx_uniq ON l_mood_work (entity0, entity1, link, link_order);

CREATE INDEX l_area_mood_idx_entity1 ON l_area_mood (entity1);
CREATE INDEX l_artist_mood_idx_entity1 ON l_artist_mood (entity1);
CREATE INDEX l_event_mood_idx_entity1 ON l_event_mood (entity1);
CREATE INDEX l_genre_mood_idx_entity1 ON l_genre_mood (entity1);
CREATE INDEX l_instrument_mood_idx_entity1 ON l_instrument_mood (entity1);
CREATE INDEX l_label_mood_idx_entity1 ON l_label_mood (entity1);
CREATE INDEX l_mood_mood_idx_entity1 ON l_mood_mood (entity1);
CREATE INDEX l_mood_place_idx_entity1 ON l_mood_place (entity1);
CREATE INDEX l_mood_recording_idx_entity1 ON l_mood_recording (entity1);
CREATE INDEX l_mood_release_idx_entity1 ON l_mood_release (entity1);
CREATE INDEX l_mood_release_group_idx_entity1 ON l_mood_release_group (entity1);
CREATE INDEX l_mood_series_idx_entity1 ON l_mood_series (entity1);
CREATE INDEX l_mood_url_idx_entity1 ON l_mood_url (entity1);
CREATE INDEX l_mood_work_idx_entity1 ON l_mood_work (entity1);

CREATE UNIQUE INDEX mood_idx_gid ON mood (gid);
CREATE UNIQUE INDEX mood_idx_name ON mood (LOWER(name));

CREATE INDEX mood_alias_idx_mood ON mood_alias (mood);
CREATE UNIQUE INDEX mood_alias_idx_primary ON mood_alias (mood, locale) WHERE primary_for_locale = TRUE AND locale IS NOT NULL;

CREATE UNIQUE INDEX mood_alias_type_idx_gid ON mood_alias_type (gid);

-- generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'mood_type' || id);
INSERT INTO mood_alias_type (id, gid, name)
    VALUES (1, '4df5b403-3059-36f8-a96f-cf04313dc007', 'Mood name'),
           (2, 'ccd867f1-81ba-3520-89a5-0b0d7a5f6f74', 'Search hint');


-- PKs

ALTER TABLE edit_mood ADD CONSTRAINT edit_mood_pkey PRIMARY KEY (edit, mood);
ALTER TABLE l_area_mood ADD CONSTRAINT l_area_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_artist_mood ADD CONSTRAINT l_artist_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_event_mood ADD CONSTRAINT l_event_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_genre_mood ADD CONSTRAINT l_genre_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_instrument_mood ADD CONSTRAINT l_instrument_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_label_mood ADD CONSTRAINT l_label_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_mood ADD CONSTRAINT l_mood_mood_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_place ADD CONSTRAINT l_mood_place_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_recording ADD CONSTRAINT l_mood_recording_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_release ADD CONSTRAINT l_mood_release_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_release_group ADD CONSTRAINT l_mood_release_group_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_series ADD CONSTRAINT l_mood_series_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_url ADD CONSTRAINT l_mood_url_pkey PRIMARY KEY (id);
ALTER TABLE l_mood_work ADD CONSTRAINT l_mood_work_pkey PRIMARY KEY (id);
ALTER TABLE mood ADD CONSTRAINT mood_pkey PRIMARY KEY (id);
ALTER TABLE mood_alias ADD CONSTRAINT mood_alias_pkey PRIMARY KEY (id);
ALTER TABLE mood_alias_type ADD CONSTRAINT mood_alias_type_pkey PRIMARY KEY (id);
ALTER TABLE mood_annotation ADD CONSTRAINT mood_annotation_pkey PRIMARY KEY (mood, annotation);


-- Functions

CREATE OR REPLACE FUNCTION delete_unused_url(ids INTEGER[])
RETURNS VOID AS $$
DECLARE
  clear_up INTEGER[];
BEGIN
  SELECT ARRAY(
    SELECT id FROM url url_row WHERE id = any(ids)
    EXCEPT
    SELECT url FROM edit_url JOIN edit ON (edit.id = edit_url.edit) WHERE edit.status = 1
    EXCEPT
    SELECT entity1 FROM l_area_url
    EXCEPT
    SELECT entity1 FROM l_artist_url
    EXCEPT
    SELECT entity1 FROM l_event_url
    EXCEPT
    SELECT entity1 FROM l_genre_url
    EXCEPT
    SELECT entity1 FROM l_instrument_url
    EXCEPT
    SELECT entity1 FROM l_label_url
    EXCEPT
    SELECT entity1 FROM l_mood_url
    EXCEPT
    SELECT entity1 FROM l_place_url
    EXCEPT
    SELECT entity1 FROM l_recording_url
    EXCEPT
    SELECT entity1 FROM l_release_url
    EXCEPT
    SELECT entity1 FROM l_release_group_url
    EXCEPT
    SELECT entity1 FROM l_series_url
    EXCEPT
    SELECT entity1 FROM l_url_url
    EXCEPT
    SELECT entity0 FROM l_url_url
    EXCEPT
    SELECT entity0 FROM l_url_work
  ) INTO clear_up;

  DELETE FROM url_gid_redirect WHERE new_id = any(clear_up);
  DELETE FROM url WHERE id = any(clear_up);
END;
$$ LANGUAGE 'plpgsql';


-- Examples

CREATE TABLE documentation.l_area_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_area_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_artist_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_artist_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_event_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_event_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_genre_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_genre_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_instrument_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_instrument_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_label_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_label_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_mood_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_mood.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_place_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_place.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_recording_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_recording.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_release_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_release.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_release_group_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_release_group.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_series_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_series.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_url_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_url.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

CREATE TABLE documentation.l_mood_work_example ( -- replicate (verbose)
  id INTEGER NOT NULL, -- PK, references musicbrainz.l_mood_work.id
  published BOOLEAN NOT NULL,
  name TEXT NOT NULL
);

ALTER TABLE documentation.l_area_mood_example ADD CONSTRAINT l_area_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_artist_mood_example ADD CONSTRAINT l_artist_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_event_mood_example ADD CONSTRAINT l_event_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_genre_mood_example ADD CONSTRAINT l_genre_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_instrument_mood_example ADD CONSTRAINT l_instrument_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_label_mood_example ADD CONSTRAINT l_label_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_mood_example ADD CONSTRAINT l_mood_mood_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_place_example ADD CONSTRAINT l_mood_place_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_recording_example ADD CONSTRAINT l_mood_recording_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_release_example ADD CONSTRAINT l_mood_release_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_release_group_example ADD CONSTRAINT l_mood_release_group_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_series_example ADD CONSTRAINT l_mood_series_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_url_example ADD CONSTRAINT l_mood_url_example_pkey PRIMARY KEY (id);
ALTER TABLE documentation.l_mood_work_example ADD CONSTRAINT l_mood_work_example_pkey PRIMARY KEY (id);

--------------------------------------------------------------------------------
SELECT '20220426-mbs-12131.sql';


DROP AGGREGATE IF EXISTS median(anyelement);
DROP FUNCTION IF EXISTS _median(anyarray);

CREATE OR REPLACE FUNCTION _median(INTEGER[]) RETURNS INTEGER AS $$
  WITH q AS (
      SELECT val
      FROM unnest($1) val
      WHERE VAL IS NOT NULL
      ORDER BY val
  )
  SELECT val
  FROM q
  LIMIT 1
  -- Subtracting (n + 1) % 2 creates a left bias
  OFFSET greatest(0, floor((select count(*) FROM q) / 2.0) - ((select count(*) + 1 FROM q) % 2));
$$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE AGGREGATE median(INTEGER) (
  SFUNC=array_append,
  STYPE=INTEGER[],
  FINALFUNC=_median,
  INITCOND='{}'
);

DROP AGGREGATE IF EXISTS array_accum(anyelement);

COMMIT;
