diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 088e3ea..eefef2c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,13 @@ +Version 31.0.0 +============== + +- Schema change 31. + +Version 30.0.0 +============== + +- Schema change 30. + Version 29.1.0 ============== diff --git a/README.rst b/README.rst index 9e4dce3..6ee3279 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ MusicBrainz Database Mirror :target: https://badge.fury.io/py/mbslave This repository now contains a collection of scripts for managing a -replica of the MusicBrainz database. +replica of the MusicBrainz database. The main motivation for these scripts is to be able to customize your database. If you don't need such customizations, it might be @@ -36,7 +36,7 @@ There are two ways to configure the application. export MBSLAVE_CONFIG=/usr/local/etc/mbslave.conf -2. Alternativelly, you can use using environment variables:: +2. Alternatively, you can use environment variables:: export MBSLAVE_DB_HOST=127.0.0.1 export MBSLAVE_DB_PORT=5432 @@ -46,6 +46,14 @@ There are two ways to configure the application. export MBSLAVE_DB_ADMIN_USER=postgres export MBSLAVE_DB_ADMIN_PASSWORD=XXX +Be aware, that configuring a different value for `db.user` or `schemas.musicbrainz` might require to configure a +custom `search_path` for the user (or database): By default, postgres sets the search_path to `"$user", public`. +If both is `musicbrainz` everything works, if they differ, you might run into errors when running `mbslave sync` as +triggers can't lookup functions properly. One way to solve this is to set the search path for your custom user to the +schema configured for musicbrainz:: + + ALTER USER myuser SET search_path TO musicbrainz, public; + Database Setup ============== @@ -66,7 +74,7 @@ you are doing. Database Replication ==================== -You can also keep the database up-to-date by applying incrementa changes. +You can also keep the database up-to-date by applying incremental changes. You need get an API token from the `MetaBrainz website `__ and you need to either add it to `mbslave.conf` or set the ``MBSLAVE_MUSICBRAINZ_TOKEN`` environment variable. @@ -82,6 +90,22 @@ When the MusicBrainz database schema changes, the replication will stop working. This is usually announced on the `MusicBrainz blog `__. When it happens, you need to upgrade the database. +Release 2026-05-11 (31) +~~~~~~~~~~~~~~~~~~~~~~~ + +Run the upgrade scripts:: + + mbslave psql -f updates/schema-change/31.all.sql + echo 'UPDATE replication_control SET current_schema_sequence = 31;' | mbslave psql + +Release 2025-05-19 (30) +~~~~~~~~~~~~~~~~~~~~~~~ + +Run the upgrade scripts:: + + mbslave psql -f updates/schema-change/30.all.sql + echo 'UPDATE replication_control SET current_schema_sequence = 30;' | mbslave psql + Release 2024-05-13 (29) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mbslave/__init__.py b/mbslave/__init__.py index 7c2f436..e1d0aae 100644 --- a/mbslave/__init__.py +++ b/mbslave/__init__.py @@ -1,4 +1,4 @@ # Copyright (C) 2013 Lukas Lalinsky # Distributed under the MIT license, see the LICENSE file for details. -__version__ = "29.1.0" +__version__ = "31.0.0" diff --git a/mbslave/replication.py b/mbslave/replication.py index e33d68e..bbbe29e 100644 --- a/mbslave/replication.py +++ b/mbslave/replication.py @@ -686,7 +686,7 @@ def mbslave_init_main(config: Config, args: argparse.Namespace) -> None: # replication ('musicbrainz', 'ReplicationSetup.sql'), - ('dbmirror2', 'dbmirror2/ReplicationSetup.sql'), + ('dbmirror2', 'dbmirror2/dbmirror2.sql'), ] diff --git a/mbslave/sql/CreateAllReplicationTriggers2.sql b/mbslave/sql/CreateAllReplicationTriggers2.sql new file mode 100644 index 0000000..e904cf1 --- /dev/null +++ b/mbslave/sql/CreateAllReplicationTriggers2.sql @@ -0,0 +1,7 @@ +-- Helper script to create all dbmirror2 replication triggers. +\ir CreateReplicationTriggers2.sql +\ir caa/CreateReplicationTriggers2.sql +\ir documentation/CreateReplicationTriggers2.sql +\ir statistics/CreateReplicationTriggers2.sql +\ir eaa/CreateReplicationTriggers2.sql +\ir wikidocs/CreateReplicationTriggers2.sql diff --git a/mbslave/sql/CreateConstraints.sql b/mbslave/sql/CreateConstraints.sql index 5df4283..a9a3bd7 100644 --- a/mbslave/sql/CreateConstraints.sql +++ b/mbslave/sql/CreateConstraints.sql @@ -145,6 +145,7 @@ ALTER TABLE series_type ADD CONSTRAINT allowed_series_entity_type 'recording', 'release', 'release_group', + 'series', 'work' ) ); @@ -173,6 +174,15 @@ ADD CONSTRAINT group_type_implies_null_gender CHECK ( ALTER TABLE release_label ADD CHECK (catalog_number IS NOT NULL OR label IS NOT NULL); +ALTER TABLE release_label + ADD CONSTRAINT no_empty_string_catalog_number + CHECK (catalog_number != ''); + +ALTER TABLE release_label + ADD CONSTRAINT release_label_uniq + UNIQUE NULLS NOT DISTINCT (release, label, catalog_number) + DEFERRABLE INITIALLY DEFERRED; + ALTER TABLE artist ADD CONSTRAINT artist_va_check CHECK (id <> 1 OR (type = 3 AND diff --git a/mbslave/sql/CreateCustomReplicationTriggers2.sql b/mbslave/sql/CreateCustomReplicationTriggers2.sql new file mode 100644 index 0000000..8acf756 --- /dev/null +++ b/mbslave/sql/CreateCustomReplicationTriggers2.sql @@ -0,0 +1,10 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +CREATE TRIGGER sanitize_dbmirror2_editor_data + BEFORE INSERT ON dbmirror2.pending_data + FOR EACH ROW WHEN (NEW.tablename = 'musicbrainz.editor') + EXECUTE PROCEDURE sanitize_dbmirror2_editor_data(); + +COMMIT; diff --git a/mbslave/sql/CreateFKConstraints.sql b/mbslave/sql/CreateFKConstraints.sql index eef896f..4a601f6 100644 --- a/mbslave/sql/CreateFKConstraints.sql +++ b/mbslave/sql/CreateFKConstraints.sql @@ -3006,6 +3006,11 @@ ALTER TABLE medium_format FOREIGN KEY (parent) REFERENCES medium_format(id); +ALTER TABLE medium_gid_redirect + ADD CONSTRAINT medium_gid_redirect_fk_new_id + FOREIGN KEY (new_id) + REFERENCES medium(id); + ALTER TABLE medium_index ADD CONSTRAINT medium_index_fk_medium FOREIGN KEY (medium) diff --git a/mbslave/sql/CreateFunctions.sql b/mbslave/sql/CreateFunctions.sql index 285d04e..51f9823 100644 --- a/mbslave/sql/CreateFunctions.sql +++ b/mbslave/sql/CreateFunctions.sql @@ -13,7 +13,7 @@ CREATE OR REPLACE FUNCTION _median(INTEGER[]) RETURNS INTEGER AS $$ 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 SET search_path = musicbrainz, public IMMUTABLE; +$$ LANGUAGE sql IMMUTABLE; CREATE AGGREGATE median(INTEGER) ( SFUNC=array_append, @@ -50,7 +50,7 @@ BEGIN value = value || lpad(to_hex(ceil(random() * 255)::int), 2, '0'); RETURN value::uuid; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION from_hex(t text) RETURNS integer AS $$ @@ -61,7 +61,7 @@ BEGIN RETURN r.hex; END LOOP; END -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public IMMUTABLE STRICT; +$$ LANGUAGE plpgsql IMMUTABLE STRICT; -- NameSpace_URL = '6ba7b8119dad11d180b400c04fd430c8' CREATE OR REPLACE FUNCTION generate_uuid_v3(namespace varchar, name varchar) RETURNS uuid @@ -84,7 +84,7 @@ BEGIN value = value || substr(bytes, 1+2*10, 12); return value::uuid; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public IMMUTABLE STRICT; +$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION inc_ref_count(tbl varchar, row_id integer, val integer) RETURNS void AS $$ @@ -94,7 +94,7 @@ BEGIN EXECUTE 'UPDATE ' || tbl || ' SET ref_count = ref_count + ' || val || ' WHERE id = ' || row_id; RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION dec_ref_count(tbl varchar, row_id integer, val integer) RETURNS void AS $$ DECLARE @@ -109,7 +109,7 @@ BEGIN EXECUTE 'UPDATE ' || tbl || ' SET ref_count = ref_count - ' || val || ' WHERE id = ' || row_id; RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION integer_date(year SMALLINT, month SMALLINT, day SMALLINT) RETURNS INTEGER AS $$ @@ -126,7 +126,7 @@ RETURNS INTEGER AS $$ )::INTEGER END ) -$$ LANGUAGE sql SET search_path = musicbrainz, public IMMUTABLE PARALLEL SAFE; +$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; ----------------------------------------------------------------------- -- area triggers @@ -149,7 +149,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- artist triggers @@ -161,7 +161,7 @@ BEGIN INSERT INTO artist_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_artist_attribute_type_allows_text() @@ -180,7 +180,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- artist_credit triggers @@ -197,7 +197,7 @@ BEGIN -- artist_release_group. RAISE EXCEPTION 'Cannot update artist_credit_name'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- editor triggers @@ -211,7 +211,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- event triggers @@ -234,7 +234,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- event triggers @@ -246,7 +246,7 @@ BEGIN INSERT INTO event_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- instrument triggers @@ -261,7 +261,7 @@ BEGIN ) INSERT INTO link_creditable_attribute_type (attribute_type) SELECT id FROM inserted_rows; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION a_upd_instrument() RETURNS trigger AS $$ BEGIN @@ -272,7 +272,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION a_del_instrument() RETURNS trigger AS $$ BEGIN @@ -283,7 +283,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_instrument_attribute_type_allows_text() @@ -302,7 +302,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- label triggers @@ -313,7 +313,7 @@ BEGIN INSERT INTO label_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_label_attribute_type_allows_text() @@ -332,7 +332,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- medium triggers @@ -355,7 +355,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- place triggers @@ -367,7 +367,7 @@ BEGIN INSERT INTO place_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_place_attribute_type_allows_text() @@ -386,7 +386,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- recording triggers @@ -395,7 +395,7 @@ $$ LANGUAGE plpgsql SET search_path = musicbrainz, public; CREATE OR REPLACE FUNCTION median_track_length(recording_id integer) RETURNS integer AS $$ SELECT median(track.length) FROM track WHERE recording = $1; -$$ LANGUAGE sql SET search_path = musicbrainz, public; +$$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION b_upd_recording() RETURNS TRIGGER AS $$ BEGIN @@ -409,7 +409,7 @@ BEGIN NEW.last_updated = now(); RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_recording() RETURNS trigger AS $$ BEGIN @@ -417,7 +417,7 @@ BEGIN INSERT INTO recording_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_recording() RETURNS trigger AS $$ BEGIN @@ -427,14 +427,14 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_recording() RETURNS trigger AS $$ BEGIN PERFORM dec_ref_count('artist_credit', OLD.artist_credit, 1); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_recording_attribute_type_allows_text() @@ -453,7 +453,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- release triggers @@ -471,7 +471,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.release_group); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release() RETURNS trigger AS $$ BEGIN @@ -516,7 +516,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release() RETURNS trigger AS $$ BEGIN @@ -528,7 +528,40 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.release_group); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_primary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT id FROM release_group + WHERE release_group.type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_secondary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT release_group + FROM release_group_secondary_type_join + WHERE secondary_type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_group_secondary_type_join() RETURNS trigger AS $$ @@ -536,7 +569,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.release_group); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_group_secondary_type_join() RETURNS trigger AS $$ @@ -544,7 +577,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.release_group); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_label() RETURNS trigger AS $$ @@ -552,7 +585,7 @@ BEGIN INSERT INTO artist_release_pending_update VALUES (NEW.release); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_label() RETURNS trigger AS $$ @@ -562,7 +595,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_label() RETURNS trigger AS $$ @@ -570,7 +603,7 @@ BEGIN INSERT INTO artist_release_pending_update VALUES (OLD.release); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_release_attribute_type_allows_text() @@ -589,7 +622,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- release_group triggers @@ -602,7 +635,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_group() RETURNS trigger AS $$ BEGIN @@ -619,7 +652,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_group() RETURNS trigger AS $$ BEGIN @@ -627,7 +660,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION b_upd_release_group_secondary_type_join() RETURNS trigger AS $$ BEGIN @@ -639,7 +672,7 @@ BEGIN -- artist_release_group up-to-date. RAISE EXCEPTION 'Cannot update release_group_secondary_type_join'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_release_group_attribute_type_allows_text() @@ -656,7 +689,7 @@ RETURNS trigger AS $$ ELSE RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- series triggers @@ -679,7 +712,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- track triggers @@ -703,7 +736,7 @@ BEGIN ); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_track() RETURNS trigger AS $$ BEGIN @@ -749,7 +782,7 @@ BEGIN PERFORM materialise_recording_length(NEW.recording); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_track() RETURNS trigger AS $$ BEGIN @@ -769,7 +802,7 @@ BEGIN ); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- work triggers @@ -780,7 +813,7 @@ BEGIN INSERT INTO work_meta (id) VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -- Ensure attribute type allows free text if free text is added CREATE OR REPLACE FUNCTION ensure_work_attribute_type_allows_text() @@ -798,7 +831,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- alternative tracklist triggers @@ -811,7 +844,7 @@ BEGIN END IF; RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION dec_nullable_artist_credit(row_id integer) RETURNS void AS $$ BEGIN @@ -820,14 +853,14 @@ BEGIN END IF; RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_alternative_release_or_track() RETURNS trigger AS $$ BEGIN PERFORM inc_nullable_artist_credit(NEW.artist_credit); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_alternative_release_or_track() RETURNS trigger AS $$ BEGIN @@ -837,21 +870,21 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_alternative_release_or_track() RETURNS trigger AS $$ BEGIN PERFORM dec_nullable_artist_credit(OLD.artist_credit); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_alternative_medium_track() RETURNS trigger AS $$ BEGIN PERFORM inc_ref_count('alternative_track', NEW.alternative_track, 1); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_alternative_medium_track() RETURNS trigger AS $$ BEGIN @@ -861,14 +894,14 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_alternative_medium_track() RETURNS trigger AS $$ BEGIN PERFORM dec_ref_count('alternative_track', OLD.alternative_track, 1); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- lastupdate triggers @@ -879,7 +912,7 @@ BEGIN NEW.last_updated = NOW(); RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_edit() RETURNS trigger AS $$ BEGIN @@ -889,14 +922,14 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION b_ins_edit_materialize_status() RETURNS trigger AS $$ BEGIN NEW.status = (SELECT status FROM edit WHERE id = NEW.edit); RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ------------------------ -- Collection deletion and hiding triggers @@ -916,7 +949,7 @@ RETURNS trigger AS $$ RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION del_collection_sub_on_delete() RETURNS trigger AS $$ @@ -928,7 +961,7 @@ RETURNS trigger AS $$ RETURN OLD; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION del_collection_sub_on_private() RETURNS trigger AS $$ @@ -946,7 +979,7 @@ RETURNS trigger AS $$ RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION restore_collection_sub_on_public() RETURNS trigger AS $$ @@ -961,7 +994,7 @@ RETURNS trigger AS $$ RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ------------------------ -- CD Lookup @@ -1005,7 +1038,7 @@ BEGIN RETURN str::cube; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public IMMUTABLE; +$$ LANGUAGE 'plpgsql' IMMUTABLE; CREATE OR REPLACE FUNCTION create_bounding_cube(durations INTEGER[], fuzzy INTEGER) RETURNS cube AS $$ DECLARE @@ -1059,7 +1092,7 @@ BEGIN RETURN str::cube; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public IMMUTABLE; +$$ LANGUAGE 'plpgsql' IMMUTABLE; ------------------------------------------------------------------- -- Maintain musicbrainz.release_first_release_date @@ -1087,7 +1120,7 @@ BEGIN ) ORDER BY release, year NULLS LAST, month NULLS LAST, day NULLS LAST'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public STRICT; +$$ LANGUAGE 'plpgsql' STRICT; CREATE OR REPLACE FUNCTION set_release_first_release_date(release_id INTEGER) RETURNS VOID AS $$ @@ -1104,7 +1137,7 @@ BEGIN INSERT INTO artist_release_pending_update VALUES (release_id); END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public STRICT; +$$ LANGUAGE 'plpgsql' STRICT; ------------------------------------------------------------------- -- Maintain release_group_meta.first_release_date @@ -1117,9 +1150,10 @@ BEGIN first_release_date_day = first.day FROM ( SELECT rd.year, rd.month, rd.day - FROM release + FROM release_group + LEFT JOIN release ON release.release_group = release_group.id LEFT JOIN release_first_release_date rd ON (rd.release = release.id) - WHERE release.release_group = release_group_id + WHERE release_group.id = release_group_id ORDER BY rd.year NULLS LAST, rd.month NULLS LAST, @@ -1129,7 +1163,7 @@ BEGIN WHERE id = release_group_id; INSERT INTO artist_release_group_pending_update VALUES (release_group_id); END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ------------------------------------------------------------------- -- Maintain musicbrainz.recording_first_release_date @@ -1149,7 +1183,7 @@ BEGIN rd.month NULLS LAST, rd.day NULLS LAST'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public STRICT; +$$ LANGUAGE 'plpgsql' STRICT; CREATE OR REPLACE FUNCTION set_recordings_first_release_dates(recording_ids INTEGER[]) RETURNS VOID AS $$ @@ -1164,7 +1198,19 @@ BEGIN format('track.recording = any(%L)', recording_ids) ); END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public STRICT; +$$ LANGUAGE 'plpgsql' STRICT; + +CREATE OR REPLACE FUNCTION set_mediums_recordings_first_release_dates(medium_ids INTEGER[]) +RETURNS VOID AS $$ +BEGIN + PERFORM set_recordings_first_release_dates(( + SELECT array_agg(recording) + FROM track + WHERE track.medium = any(medium_ids) + )); + RETURN; +END; +$$ LANGUAGE 'plpgsql' STRICT; CREATE OR REPLACE FUNCTION set_releases_recordings_first_release_dates(release_ids INTEGER[]) RETURNS VOID AS $$ @@ -1177,7 +1223,19 @@ BEGIN )); RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public STRICT; +$$ LANGUAGE 'plpgsql' STRICT; + +CREATE OR REPLACE FUNCTION a_upd_medium_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF NEW.release IS DISTINCT FROM OLD.release THEN + PERFORM set_mediums_recordings_first_release_dates(ARRAY[OLD.id]); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_event() RETURNS TRIGGER AS $$ @@ -1196,7 +1254,7 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_event() RETURNS TRIGGER AS $$ @@ -1227,7 +1285,7 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_event() RETURNS TRIGGER AS $$ @@ -1246,13 +1304,13 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION deny_special_purpose_deletion() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'Attempted to delete a special purpose row'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ------------------------------------------------------------------- -- Ratings @@ -1284,7 +1342,7 @@ BEGIN entity_type::TEXT || '_rating_raw' ) USING entity_id; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_insert() RETURNS trigger AS $$ @@ -1297,7 +1355,7 @@ BEGIN PERFORM update_aggregate_rating(entity_type, new_entity_id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_update() RETURNS trigger AS $$ @@ -1319,7 +1377,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_aggregate_rating_for_raw_delete() RETURNS trigger AS $$ @@ -1332,7 +1390,7 @@ BEGIN PERFORM update_aggregate_rating(entity_type, old_entity_id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION delete_ratings(enttype TEXT, ids INTEGER[]) RETURNS TABLE(editor INT, rating SMALLINT) AS $$ @@ -1346,7 +1404,7 @@ BEGIN USING ids; RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ------------------------------------------------------------------- -- Prevent link attributes being used on links that don't support them @@ -1366,7 +1424,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; -------------------------------------------------------------------------------- -- Remove unused link rows when a relationship is changed @@ -1390,53 +1448,17 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; 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 SET search_path = musicbrainz, public; +BEGIN + DELETE FROM url_gid_redirect WHERE new_id = any(ids); + DELETE FROM url WHERE id = any(ids); +EXCEPTION + WHEN foreign_key_violation THEN RETURN; +END; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION remove_unused_url() RETURNS TRIGGER AS $$ @@ -1455,7 +1477,7 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION simplify_search_hints() RETURNS trigger AS $$ @@ -1474,7 +1496,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION end_date_implies_ended() RETURNS trigger AS $$ @@ -1487,7 +1509,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION end_area_implies_ended() RETURNS trigger AS $$ @@ -1498,7 +1520,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION delete_orphaned_recordings() RETURNS TRIGGER @@ -1559,15 +1581,15 @@ AS $$ RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION padded_by_whitespace(TEXT) RETURNS boolean AS $$ SELECT btrim($1) <> $1; -$$ LANGUAGE sql SET search_path = musicbrainz, public IMMUTABLE; +$$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION controlled_for_whitespace(TEXT) RETURNS boolean AS $$ SELECT NOT padded_by_whitespace($1); -$$ LANGUAGE sql IMMUTABLE SET search_path = musicbrainz, public; +$$ LANGUAGE SQL IMMUTABLE SET search_path = musicbrainz, public; 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 $$ @@ -1583,7 +1605,7 @@ BEGIN entity_type::TEXT ) USING entity_id, tag_id, count_change; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION delete_unused_aggregate_tag(entity_type taggable_entity_type, entity_id INTEGER, tag_id INTEGER) RETURNS VOID AS $$ @@ -1606,7 +1628,7 @@ BEGIN entity_type::TEXT || '_tag_raw' ) USING entity_id, tag_id; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_insert() RETURNS trigger AS $$ @@ -1620,7 +1642,7 @@ BEGIN UPDATE tag SET ref_count = ref_count + 1 WHERE id = NEW.tag; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_update() RETURNS trigger AS $$ @@ -1647,7 +1669,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION update_tag_counts_for_raw_delete() RETURNS trigger AS $$ @@ -1662,7 +1684,7 @@ BEGIN UPDATE tag SET ref_count = ref_count - 1 WHERE id = OLD.tag; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION delete_unused_tag(tag_id INT) RETURNS void AS $$ @@ -1671,7 +1693,7 @@ RETURNS void AS $$ EXCEPTION WHEN foreign_key_violation THEN RETURN; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION trg_delete_unused_tag() RETURNS trigger AS $$ @@ -1679,7 +1701,7 @@ RETURNS trigger AS $$ PERFORM delete_unused_tag(NEW.id); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION trg_delete_unused_tag_ref() RETURNS trigger AS $$ @@ -1687,7 +1709,7 @@ RETURNS trigger AS $$ PERFORM delete_unused_tag(OLD.tag); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION inserting_edits_requires_confirmed_email_address() RETURNS trigger AS $$ @@ -1702,7 +1724,7 @@ BEGIN RETURN NEW; END IF; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION deny_deprecated_links() RETURNS trigger AS $$ @@ -1713,7 +1735,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION check_has_dates() RETURNS trigger AS $$ @@ -1731,7 +1753,7 @@ BEGIN END IF; RETURN NEW; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION materialise_recording_length(recording_id INT) RETURNS void as $$ @@ -1741,14 +1763,14 @@ BEGIN WHERE recording.id = recording_id AND recording.length IS DISTINCT FROM track.median; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION track_count_matches_cdtoc(medium, int) RETURNS boolean AS $$ SELECT $1.track_count = $2 + COALESCE( (SELECT count(*) FROM track WHERE medium = $1.id AND (position = 0 OR is_data_track = true) ), 0); -$$ LANGUAGE sql SET search_path = musicbrainz, public IMMUTABLE; +$$ LANGUAGE SQL IMMUTABLE; COMMIT; @@ -1766,7 +1788,7 @@ BEGIN ); RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- Text search helpers @@ -1774,7 +1796,7 @@ $$ LANGUAGE plpgsql SET search_path = musicbrainz, public; CREATE OR REPLACE FUNCTION mb_lower(input text) RETURNS text AS $$ SELECT lower(input COLLATE musicbrainz.musicbrainz); -$$ LANGUAGE sql SET search_path = musicbrainz, public IMMUTABLE PARALLEL SAFE STRICT; +$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT; CREATE OR REPLACE FUNCTION mb_simple_tsvector(input text) RETURNS tsvector AS $$ -- The builtin 'simple' dictionary, which the mb_simple text search @@ -1782,7 +1804,7 @@ CREATE OR REPLACE FUNCTION mb_simple_tsvector(input text) RETURNS tsvector AS $$ -- for us, but internally it hardcodes DEFAULT_COLLATION_OID; therefore -- we first lowercase the input string ourselves using mb_lower. SELECT to_tsvector('musicbrainz.mb_simple', musicbrainz.mb_lower(input)); -$$ LANGUAGE sql SET search_path = musicbrainz, public IMMUTABLE PARALLEL SAFE STRICT; +$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT; ----------------------------------------------------------------------- -- Edit data helpers @@ -1817,7 +1839,7 @@ BEGIN END CASE; RETURN ''; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public IMMUTABLE PARALLEL SAFE STRICT; +$$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE STRICT; ----------------------------------------------------------------------- -- Maintain musicbrainz.artist_release @@ -1844,7 +1866,7 @@ BEGIN (CASE r.barcode WHEN '' THEN '0' ELSE r.barcode END), '[^0-9]+', '', 'g' ), 18)::BIGINT AS barcode, - left(r.name, 1)::CHAR(1) AS sort_character, + r.name, r.id FROM ( SELECT FALSE AS is_track_artist, racn.artist, r.id AS release @@ -1868,7 +1890,7 @@ BEGIN $SQL$ USING release_id; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION apply_artist_release_pending_updates() RETURNS trigger AS $$ @@ -1908,7 +1930,7 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- Maintain musicbrainz.artist_release_group @@ -1928,7 +1950,12 @@ BEGIN 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), + rgpt.child_order::SMALLINT, rg.type::SMALLINT, + array_agg( + DISTINCT rgst.child_order ORDER BY rgst.child_order) + FILTER (WHERE rgst.child_order IS NOT NULL + )::SMALLINT[], array_agg( DISTINCT st.secondary_type ORDER BY st.secondary_type) FILTER (WHERE st.secondary_type IS NOT NULL @@ -1938,7 +1965,7 @@ BEGIN rgm.first_release_date_month, rgm.first_release_date_day ), - left(rg.name, 1)::CHAR(1), + rg.name, rg.id FROM ( SELECT FALSE AS is_track_artist, rgacn.artist, rg.id AS release_group @@ -1954,15 +1981,17 @@ BEGIN 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_primary_type rgpt ON rgpt.id = rg.type LEFT JOIN release_group_secondary_type_join st ON st.release_group = rg.id + LEFT JOIN release_group_secondary_type rgst ON rgst.id = st.secondary_type $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 + GROUP BY a_rg.is_track_artist, a_rg.artist, rgm.id, rg.id, rgpt.child_order ORDER BY a_rg.artist, rg.id, a_rg.is_track_artist $SQL$ USING release_group_id; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION apply_artist_release_group_pending_updates() RETURNS trigger AS $$ @@ -2002,7 +2031,7 @@ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- Relationship triggers @@ -2019,7 +2048,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION a_upd_l_area_area_mirror() RETURNS trigger AS $$ DECLARE @@ -2043,7 +2072,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION a_del_l_area_area_mirror() RETURNS trigger AS $$ DECLARE @@ -2056,7 +2085,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION b_upd_link() RETURNS trigger AS $$ BEGIN @@ -2069,28 +2098,28 @@ BEGIN -- area_containment. RAISE EXCEPTION 'link rows are immutable'; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ 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 SET search_path = musicbrainz, public; +$$ 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 SET search_path = musicbrainz, public; +$$ 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 SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; ----------------------------------------------------------------------- -- Maintain musicbrainz.area_containment @@ -2131,7 +2160,7 @@ BEGIN $SQL$ USING part_of_area_link_type_id, descendant_area_ids; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; -- Returns a set of area_containment rows that cover the entire descendant -- hierarchy for parent_area_ids. If NULL is passed, the entire @@ -2168,7 +2197,7 @@ BEGIN $SQL$ USING part_of_area_link_type_id, parent_area_ids; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION update_area_containment_mirror( parent_ids INTEGER[], -- entity0 of area-area "part of" @@ -2216,6 +2245,49 @@ BEGIN ) area_hierarchy ORDER BY descendant, parent, depth; END; -$$ LANGUAGE plpgsql SET search_path = musicbrainz, public; +$$ LANGUAGE plpgsql; + +----------------------------------------------------------------------- +-- Editor data sanitization +----------------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION sanitize_editor(e editor) RETURNS editor AS $$ + SELECT ROW( + e.id, + e.name, + 0::INTEGER, + ''::VARCHAR(64), + NULL::VARCHAR(255), + NULL::TEXT, + e.member_since, + e.email_confirm_date, + now()::TIMESTAMP WITH TIME ZONE, + e.last_updated, + NULL::DATE, + NULL::INTEGER, + NULL::INTEGER, + '{CLEARTEXT}mb'::VARCHAR(128), + md5(e.name || ':musicbrainz.org:mb')::CHAR(32), + e.deleted + )::editor +$$ LANGUAGE sql STABLE PARALLEL SAFE; + +-- Adding `STRICT` on `sanitize_editor` would prevent inlining/optimization +-- when called via the `editor_sanitized` view. +CREATE OR REPLACE FUNCTION sanitize_editor_strict(e editor) RETURNS editor AS $$ + SELECT sanitize_editor(e) +$$ LANGUAGE sql STABLE STRICT PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION sanitize_dbmirror2_editor_data() RETURNS trigger AS $$ +BEGIN + NEW.olddata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.olddata))); + NEW.newdata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.newdata))); + IF NEW.op = 'u' AND NEW.olddata::JSONB = NEW.newdata::JSONB THEN + -- Only sanitized columns have changed. No need to log the update. + RETURN NULL; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; -- vi: set ts=4 sw=4 et : diff --git a/mbslave/sql/CreateIndexes.sql b/mbslave/sql/CreateIndexes.sql index b5c0b69..8f48262 100644 --- a/mbslave/sql/CreateIndexes.sql +++ b/mbslave/sql/CreateIndexes.sql @@ -69,16 +69,16 @@ CREATE INDEX artist_rating_raw_idx_editor ON artist_rating_raw (editor); CREATE INDEX artist_tag_raw_idx_tag ON artist_tag_raw (tag); CREATE INDEX artist_tag_raw_idx_editor ON artist_tag_raw (editor); -CREATE INDEX artist_release_nonva_idx_sort ON artist_release_nonva (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, sort_character, release); -CREATE INDEX artist_release_va_idx_sort ON artist_release_va (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, sort_character, release); +CREATE INDEX artist_release_nonva_idx_sort ON artist_release_nonva (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); +CREATE INDEX artist_release_va_idx_sort ON artist_release_va (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); CREATE UNIQUE INDEX artist_release_nonva_idx_uniq ON artist_release_nonva (release, artist); CREATE UNIQUE INDEX artist_release_va_idx_uniq ON artist_release_va (release, artist); CREATE INDEX artist_release_pending_update_idx_release ON artist_release_pending_update USING HASH (release); -CREATE INDEX artist_release_group_nonva_idx_sort ON artist_release_group_nonva (artist, unofficial, primary_type NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, sort_character, release_group); -CREATE INDEX artist_release_group_va_idx_sort ON artist_release_group_va (artist, unofficial, primary_type NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, sort_character, release_group); +CREATE INDEX artist_release_group_nonva_idx_sort ON artist_release_group_nonva (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); +CREATE INDEX artist_release_group_va_idx_sort ON artist_release_group_va (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); CREATE UNIQUE INDEX artist_release_group_nonva_idx_uniq ON artist_release_group_nonva (release_group, artist); CREATE UNIQUE INDEX artist_release_group_va_idx_uniq ON artist_release_group_va (release_group, artist); @@ -507,6 +507,8 @@ CREATE UNIQUE INDEX editor_collection_type_idx_gid ON editor_collection_type (gi CREATE UNIQUE INDEX cdtoc_idx_discid ON cdtoc (discid); CREATE INDEX cdtoc_idx_freedb_id ON cdtoc (freedb_id); +CREATE UNIQUE INDEX medium_idx_gid ON medium (gid); + CREATE INDEX medium_attribute_idx_medium ON medium_attribute (medium); CREATE UNIQUE INDEX medium_attribute_type_idx_gid ON medium_attribute_type (gid); @@ -731,6 +733,7 @@ CREATE INDEX editor_collection_gid_redirect_idx_new_id ON editor_collection_gid_ CREATE INDEX event_gid_redirect_idx_new_id ON event_gid_redirect (new_id); CREATE INDEX instrument_gid_redirect_idx_new_id ON instrument_gid_redirect (new_id); CREATE INDEX label_gid_redirect_idx_new_id ON label_gid_redirect (new_id); +CREATE INDEX medium_gid_redirect_idx_new_id ON medium_gid_redirect (new_id); CREATE INDEX place_gid_redirect_idx_new_id ON place_gid_redirect (new_id); CREATE INDEX recording_gid_redirect_idx_new_id ON recording_gid_redirect (new_id); CREATE INDEX release_gid_redirect_idx_new_id ON release_gid_redirect (new_id); diff --git a/mbslave/sql/CreateMirrorOnlyFunctions.sql b/mbslave/sql/CreateMirrorOnlyFunctions.sql index 915d6e8..4836bf6 100644 --- a/mbslave/sql/CreateMirrorOnlyFunctions.sql +++ b/mbslave/sql/CreateMirrorOnlyFunctions.sql @@ -9,7 +9,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.release_group); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_mirror() RETURNS trigger AS $$ @@ -31,7 +31,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_mirror() RETURNS trigger AS $$ @@ -40,7 +40,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.release_group); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_event_mirror() RETURNS trigger AS $$ @@ -52,7 +52,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_event_mirror() RETURNS trigger AS $$ @@ -67,7 +67,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_event_mirror() RETURNS trigger AS $$ @@ -79,7 +79,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_group_mirror() RETURNS trigger AS $$ @@ -87,7 +87,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.id); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_group_mirror() RETURNS trigger AS $$ @@ -101,7 +101,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_group_mirror() RETURNS trigger AS $$ @@ -109,7 +109,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.id); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_group_meta_mirror() RETURNS trigger AS $$ @@ -123,7 +123,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_group_secondary_type_join_mirror() RETURNS trigger AS $$ @@ -131,7 +131,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (NEW.release_group); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_group_secondary_type_join_mirror() RETURNS trigger AS $$ @@ -139,7 +139,7 @@ BEGIN INSERT INTO artist_release_group_pending_update VALUES (OLD.release_group); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_release_label_mirror() RETURNS trigger AS $$ @@ -147,7 +147,7 @@ BEGIN INSERT INTO artist_release_pending_update VALUES (NEW.release); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_release_label_mirror() RETURNS trigger AS $$ @@ -157,7 +157,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_release_label_mirror() RETURNS trigger AS $$ @@ -165,7 +165,7 @@ BEGIN INSERT INTO artist_release_pending_update VALUES (OLD.release); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_ins_track_mirror() RETURNS trigger AS $$ @@ -182,7 +182,7 @@ BEGIN ); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_upd_track_mirror() RETURNS trigger AS $$ @@ -203,7 +203,7 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION a_del_track_mirror() RETURNS trigger AS $$ @@ -220,6 +220,6 @@ BEGIN ); RETURN NULL; END; -$$ LANGUAGE 'plpgsql' SET search_path = musicbrainz, public; +$$ LANGUAGE 'plpgsql'; COMMIT; diff --git a/mbslave/sql/CreateMirrorOnlyTriggers.sql b/mbslave/sql/CreateMirrorOnlyTriggers.sql index 999542a..9875d9d 100644 --- a/mbslave/sql/CreateMirrorOnlyTriggers.sql +++ b/mbslave/sql/CreateMirrorOnlyTriggers.sql @@ -24,6 +24,9 @@ CREATE TRIGGER a_upd_l_area_area_mirror AFTER UPDATE ON l_area_area CREATE TRIGGER a_del_l_area_area_mirror AFTER DELETE ON l_area_area FOR EACH ROW EXECUTE PROCEDURE a_del_l_area_area_mirror(); +CREATE TRIGGER a_upd_medium AFTER UPDATE ON medium + FOR EACH ROW EXECUTE PROCEDURE a_upd_medium_mirror(); + CREATE TRIGGER a_ins_release_mirror AFTER INSERT ON release FOR EACH ROW EXECUTE PROCEDURE a_ins_release_mirror(); @@ -63,6 +66,12 @@ CREATE TRIGGER a_del_release_group_mirror AFTER DELETE ON release_group CREATE TRIGGER a_upd_release_group_meta_mirror AFTER UPDATE ON release_group_meta FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_meta_mirror(); +CREATE TRIGGER a_upd_release_group_primary_type_mirror AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +CREATE TRIGGER a_upd_release_group_secondary_type_mirror AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + CREATE TRIGGER a_ins_release_group_secondary_type_join_mirror AFTER INSERT ON release_group_secondary_type_join FOR EACH ROW EXECUTE PROCEDURE a_ins_release_group_secondary_type_join_mirror(); @@ -111,6 +120,18 @@ CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror AFTER UPDATE ON release_group_meta DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror AFTER INSERT OR DELETE ON release_group_secondary_type_join DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); diff --git a/mbslave/sql/CreatePrimaryKeys.sql b/mbslave/sql/CreatePrimaryKeys.sql index 8724bab..7b1fb0f 100644 --- a/mbslave/sql/CreatePrimaryKeys.sql +++ b/mbslave/sql/CreatePrimaryKeys.sql @@ -264,6 +264,7 @@ ALTER TABLE medium_attribute_type_allowed_value ADD CONSTRAINT medium_attribute_ ALTER TABLE medium_attribute_type_allowed_value_allowed_format ADD CONSTRAINT medium_attribute_type_allowed_value_allowed_format_pkey PRIMARY KEY (medium_format, medium_attribute_type_allowed_value); ALTER TABLE medium_cdtoc ADD CONSTRAINT medium_cdtoc_pkey PRIMARY KEY (id); ALTER TABLE medium_format ADD CONSTRAINT medium_format_pkey PRIMARY KEY (id); +ALTER TABLE medium_gid_redirect ADD CONSTRAINT medium_gid_redirect_pkey PRIMARY KEY (gid); ALTER TABLE medium_index ADD CONSTRAINT medium_index_pkey PRIMARY KEY (medium); ALTER TABLE mood ADD CONSTRAINT mood_pkey PRIMARY KEY (id); ALTER TABLE mood_alias ADD CONSTRAINT mood_alias_pkey PRIMARY KEY (id); diff --git a/mbslave/sql/CreateReplicationTriggers.sql b/mbslave/sql/CreateReplicationTriggers.sql index f35f62d..d989a20 100644 --- a/mbslave/sql/CreateReplicationTriggers.sql +++ b/mbslave/sql/CreateReplicationTriggers.sql @@ -815,6 +815,10 @@ CREATE TRIGGER "reptg_medium_format" AFTER INSERT OR DELETE OR UPDATE ON "medium_format" FOR EACH ROW EXECUTE PROCEDURE "recordchange" (); +CREATE TRIGGER "reptg_medium_gid_redirect" +AFTER INSERT OR DELETE OR UPDATE ON "medium_gid_redirect" +FOR EACH ROW EXECUTE PROCEDURE "recordchange" ('verbose'); + CREATE TRIGGER "reptg_medium_index" AFTER INSERT OR DELETE OR UPDATE ON "medium_index" FOR EACH ROW EXECUTE PROCEDURE "recordchange" (); diff --git a/mbslave/sql/CreateReplicationTriggers2.sql b/mbslave/sql/CreateReplicationTriggers2.sql index 1727b0a..fcc7e1b 100644 --- a/mbslave/sql/CreateReplicationTriggers2.sql +++ b/mbslave/sql/CreateReplicationTriggers2.sql @@ -143,6 +143,10 @@ CREATE TRIGGER reptg2_country_area AFTER INSERT OR DELETE OR UPDATE ON country_area FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); +CREATE TRIGGER reptg2_editor +AFTER INSERT OR DELETE OR UPDATE ON editor +FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); + CREATE TRIGGER reptg2_editor_collection_type AFTER INSERT OR DELETE OR UPDATE ON editor_collection_type FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); @@ -815,6 +819,10 @@ CREATE TRIGGER reptg2_medium_format AFTER INSERT OR DELETE OR UPDATE ON medium_format FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); +CREATE TRIGGER reptg2_medium_gid_redirect +AFTER INSERT OR DELETE OR UPDATE ON medium_gid_redirect +FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); + CREATE TRIGGER reptg2_medium_index AFTER INSERT OR DELETE OR UPDATE ON medium_index FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); @@ -911,6 +919,10 @@ CREATE TRIGGER reptg2_recording_attribute_type_allowed_value AFTER INSERT OR DELETE OR UPDATE ON recording_attribute_type_allowed_value FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); +CREATE TRIGGER reptg2_recording_first_release_date +AFTER INSERT OR DELETE OR UPDATE ON recording_first_release_date +FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); + CREATE TRIGGER reptg2_recording_gid_redirect AFTER INSERT OR DELETE OR UPDATE ON recording_gid_redirect FOR EACH ROW EXECUTE PROCEDURE dbmirror2.recordchange(); diff --git a/mbslave/sql/CreateTables.sql b/mbslave/sql/CreateTables.sql index 7f3fabe..2776bab 100644 --- a/mbslave/sql/CreateTables.sql +++ b/mbslave/sql/CreateTables.sql @@ -429,15 +429,7 @@ CREATE TABLE artist_release ( catalog_numbers TEXT[], country_code CHAR(2), barcode BIGINT, - -- Prior to adding these materialized tables, we'd order releases - -- by name only if all other attributes where equal. It's not too - -- common that an artist will have tons of releases with no dates, - -- catalog numbers, countries, or barcodes (though it can be seen - -- on some big composers). As a compromise between dropping the - -- name sorting and having to store the entire name here (which, - -- as a reminder, is duplicated for every artist on the release), - -- we only store the first character of the name for sorting. - sort_character CHAR(1) COLLATE musicbrainz NOT NULL, + name VARCHAR COLLATE musicbrainz NOT NULL, release INTEGER NOT NULL -- references release.id, CASCADE ) PARTITION BY LIST (is_track_artist); @@ -466,11 +458,12 @@ CREATE TABLE artist_release_group ( is_track_artist BOOLEAN NOT NULL, artist INTEGER NOT NULL, -- references artist.id, CASCADE unofficial BOOLEAN NOT NULL, + primary_type_child_order SMALLINT, primary_type SMALLINT, + secondary_type_child_orders SMALLINT[], secondary_types SMALLINT[], first_release_date INTEGER, - -- See comment for `artist_release.sort_character`. - sort_character CHAR(1) COLLATE musicbrainz NOT NULL, + name VARCHAR COLLATE musicbrainz NOT NULL, release_group INTEGER NOT NULL -- references release_group.id, CASCADE ) PARTITION BY LIST (is_track_artist); @@ -678,8 +671,7 @@ CREATE TABLE edit_url url INTEGER NOT NULL -- PK, references url.id CASCADE ); -CREATE TABLE editor -( +CREATE TABLE editor ( -- replicate id SERIAL, name VARCHAR(64) NOT NULL, privs INTEGER DEFAULT 0, @@ -953,7 +945,7 @@ CREATE TABLE release_first_release_date ( day SMALLINT ); -CREATE TABLE recording_first_release_date ( +CREATE TABLE recording_first_release_date ( -- replicate recording INTEGER NOT NULL, -- PK, references recording.id CASCADE year SMALLINT, month SMALLINT, @@ -1183,7 +1175,6 @@ CREATE TABLE isrc ( -- replicate (verbose) id SERIAL, recording INTEGER NOT NULL, -- references recording.id isrc CHAR(12) NOT NULL CHECK (isrc ~ E'^[A-Z]{2}[A-Z0-9]{3}[0-9]{7}$'), - source SMALLINT, edits_pending INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0), created TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); @@ -1192,7 +1183,6 @@ CREATE TABLE iswc ( -- replicate (verbose) id SERIAL NOT NULL, work INTEGER NOT NULL, -- references work.id iswc CHARACTER(15) CHECK (iswc ~ E'^T-?\\d{3}.?\\d{3}.?\\d{3}[-.]?\\d$'), - source SMALLINT, edits_pending INTEGER NOT NULL DEFAULT 0, created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() ); @@ -2907,7 +2897,8 @@ CREATE TABLE medium ( -- replicate (verbose) name VARCHAR NOT NULL DEFAULT '', edits_pending INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0), last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - track_count INTEGER NOT NULL DEFAULT 0 + track_count INTEGER NOT NULL DEFAULT 0, + gid UUID NOT NULL ); CREATE TABLE medium_attribute_type ( -- replicate (verbose) @@ -2973,6 +2964,12 @@ CREATE TABLE medium_format ( -- replicate gid uuid NOT NULL ); +CREATE TABLE medium_gid_redirect ( -- replicate (verbose) + gid UUID NOT NULL, -- PK + new_id INTEGER NOT NULL, -- references medium.id + created TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + CREATE TABLE mood ( -- replicate (verbose) id SERIAL, -- PK gid UUID NOT NULL, diff --git a/mbslave/sql/CreateTriggers.sql b/mbslave/sql/CreateTriggers.sql index 148c7f8..7f5ee5c 100644 --- a/mbslave/sql/CreateTriggers.sql +++ b/mbslave/sql/CreateTriggers.sql @@ -454,6 +454,9 @@ CREATE TRIGGER b_upd_link_type BEFORE UPDATE ON link_type CREATE TRIGGER b_upd_link_type_attribute_type BEFORE UPDATE ON link_type_attribute_type FOR EACH ROW EXECUTE PROCEDURE b_upd_last_updated_table(); +CREATE TRIGGER a_upd_medium AFTER UPDATE ON medium + FOR EACH ROW EXECUTE PROCEDURE a_upd_medium_mirror(); + CREATE TRIGGER b_upd_medium BEFORE UPDATE ON medium FOR EACH ROW EXECUTE PROCEDURE b_upd_last_updated_table(); @@ -580,6 +583,12 @@ CREATE TRIGGER a_del_release_group AFTER DELETE ON release_group CREATE TRIGGER b_upd_release_group BEFORE UPDATE ON release_group FOR EACH ROW EXECUTE PROCEDURE b_upd_last_updated_table(); +CREATE TRIGGER a_upd_release_group_primary_type AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +CREATE TRIGGER a_upd_release_group_secondary_type AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + CREATE TRIGGER a_ins_release_group_secondary_type_join AFTER INSERT ON release_group_secondary_type_join FOR EACH ROW EXECUTE PROCEDURE a_ins_release_group_secondary_type_join(); @@ -1299,6 +1308,18 @@ CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates AFTER UPDATE ON release_group_meta DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates AFTER INSERT OR DELETE ON release_group_secondary_type_join DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); diff --git a/mbslave/sql/CreateViews.sql b/mbslave/sql/CreateViews.sql index 62639f9..185e0a3 100644 --- a/mbslave/sql/CreateViews.sql +++ b/mbslave/sql/CreateViews.sql @@ -83,6 +83,20 @@ CREATE OR REPLACE VIEW release_group_series AS 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 series_series AS + SELECT entity0 AS series_part, + entity1 AS series, + lss.id AS relationship, + link_order, + lss.link, + COALESCE(text_value, '') AS text_value + FROM l_series_series lss + JOIN series s ON s.id = lss.entity1 + JOIN link l ON l.id = lss.link + JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '8da75c99-46ff-373c-9d31-276ca8fa8cc3') + 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, @@ -107,6 +121,10 @@ CREATE OR REPLACE VIEW medium_track_durations AS JOIN track ON track.medium = medium.id GROUP BY medium.id; +CREATE OR REPLACE VIEW editor_sanitized AS + SELECT (sanitize_editor(editor)).* + FROM editor; + COMMIT; -- vi: set ts=4 sw=4 et : diff --git a/mbslave/sql/DisableLastUpdatedTriggers.sql b/mbslave/sql/DisableLastUpdatedTriggers.sql old mode 100755 new mode 100644 diff --git a/mbslave/sql/DropAllReplicationTriggers2.sql b/mbslave/sql/DropAllReplicationTriggers2.sql new file mode 100644 index 0000000..22e4dc5 --- /dev/null +++ b/mbslave/sql/DropAllReplicationTriggers2.sql @@ -0,0 +1,7 @@ +-- Helper script to drop all dbmirror2 replication triggers. +\ir DropReplicationTriggers2.sql +\ir caa/DropReplicationTriggers2.sql +\ir documentation/DropReplicationTriggers2.sql +\ir statistics/DropReplicationTriggers2.sql +\ir eaa/DropReplicationTriggers2.sql +\ir wikidocs/DropReplicationTriggers2.sql diff --git a/mbslave/sql/DropCustomReplicationTriggers2.sql b/mbslave/sql/DropCustomReplicationTriggers2.sql new file mode 100644 index 0000000..9f7e319 --- /dev/null +++ b/mbslave/sql/DropCustomReplicationTriggers2.sql @@ -0,0 +1,4 @@ +-- Automatically generated, do not edit. +\unset ON_ERROR_STOP + +DROP TRIGGER IF EXISTS sanitize_dbmirror2_editor_data ON dbmirror2.pending_data; diff --git a/mbslave/sql/DropFKConstraints.sql b/mbslave/sql/DropFKConstraints.sql index 0d4eadf..5c03596 100644 --- a/mbslave/sql/DropFKConstraints.sql +++ b/mbslave/sql/DropFKConstraints.sql @@ -597,6 +597,7 @@ ALTER TABLE medium_attribute_type_allowed_value_allowed_format DROP CONSTRAINT I ALTER TABLE medium_cdtoc DROP CONSTRAINT IF EXISTS medium_cdtoc_fk_medium; ALTER TABLE medium_cdtoc DROP CONSTRAINT IF EXISTS medium_cdtoc_fk_cdtoc; ALTER TABLE medium_format DROP CONSTRAINT IF EXISTS medium_format_fk_parent; +ALTER TABLE medium_gid_redirect DROP CONSTRAINT IF EXISTS medium_gid_redirect_fk_new_id; ALTER TABLE medium_index DROP CONSTRAINT IF EXISTS medium_index_fk_medium; ALTER TABLE mood_alias DROP CONSTRAINT IF EXISTS mood_alias_fk_mood; ALTER TABLE mood_alias DROP CONSTRAINT IF EXISTS mood_alias_fk_type; diff --git a/mbslave/sql/DropFunctions.sql b/mbslave/sql/DropFunctions.sql index e37ad16..8f108bc 100644 --- a/mbslave/sql/DropFunctions.sql +++ b/mbslave/sql/DropFunctions.sql @@ -35,10 +35,13 @@ DROP FUNCTION a_upd_alternative_release_or_track(); DROP FUNCTION a_upd_edit(); DROP FUNCTION a_upd_instrument(); DROP FUNCTION a_upd_l_area_area_mirror(); +DROP FUNCTION a_upd_medium_mirror(); DROP FUNCTION a_upd_recording(); DROP FUNCTION a_upd_release(); DROP FUNCTION a_upd_release_event(); DROP FUNCTION a_upd_release_group(); +DROP FUNCTION a_upd_release_group_primary_type_mirror(); +DROP FUNCTION a_upd_release_group_secondary_type_mirror(); DROP FUNCTION a_upd_release_label(); DROP FUNCTION a_upd_track(); DROP FUNCTION apply_artist_release_group_pending_updates(); @@ -102,6 +105,10 @@ DROP FUNCTION remove_unused_links(); DROP FUNCTION remove_unused_url(); DROP FUNCTION replace_old_sub_on_add(); DROP FUNCTION restore_collection_sub_on_public(); +DROP FUNCTION sanitize_dbmirror2_editor_data(); +DROP FUNCTION sanitize_editor(e editor); +DROP FUNCTION sanitize_editor_strict(e editor); +DROP FUNCTION set_mediums_recordings_first_release_dates(medium_ids INTEGER[]); DROP FUNCTION set_recordings_first_release_dates(recording_ids INTEGER[]); DROP FUNCTION set_release_first_release_date(release_id INTEGER); DROP FUNCTION set_release_group_first_release_date(release_group_id INTEGER); diff --git a/mbslave/sql/DropIndexes.sql b/mbslave/sql/DropIndexes.sql index 4e83c1e..a9b8e00 100644 --- a/mbslave/sql/DropIndexes.sql +++ b/mbslave/sql/DropIndexes.sql @@ -403,6 +403,8 @@ DROP INDEX medium_cdtoc_idx_cdtoc; DROP INDEX medium_cdtoc_idx_medium; DROP INDEX medium_cdtoc_idx_uniq; DROP INDEX medium_format_idx_gid; +DROP INDEX medium_gid_redirect_idx_new_id; +DROP INDEX medium_idx_gid; DROP INDEX medium_idx_track_count; DROP INDEX medium_index_idx; DROP INDEX mood_alias_idx_mood; diff --git a/mbslave/sql/DropMirrorOnlyTriggers.sql b/mbslave/sql/DropMirrorOnlyTriggers.sql index 136d008..2ffd455 100644 --- a/mbslave/sql/DropMirrorOnlyTriggers.sql +++ b/mbslave/sql/DropMirrorOnlyTriggers.sql @@ -4,6 +4,7 @@ DROP TRIGGER IF EXISTS a_ins_l_area_area_mirror ON l_area_area; DROP TRIGGER IF EXISTS a_upd_l_area_area_mirror ON l_area_area; DROP TRIGGER IF EXISTS a_del_l_area_area_mirror ON l_area_area; +DROP TRIGGER IF EXISTS a_upd_medium ON medium; DROP TRIGGER IF EXISTS a_ins_release_mirror ON release; DROP TRIGGER IF EXISTS a_upd_release_mirror ON release; DROP TRIGGER IF EXISTS a_del_release_mirror ON release; @@ -17,6 +18,8 @@ DROP TRIGGER IF EXISTS a_ins_release_group_mirror ON release_group; DROP TRIGGER IF EXISTS a_upd_release_group_mirror ON release_group; DROP TRIGGER IF EXISTS a_del_release_group_mirror ON release_group; DROP TRIGGER IF EXISTS a_upd_release_group_meta_mirror ON release_group_meta; +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type_mirror ON release_group_primary_type; +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type_mirror ON release_group_secondary_type; DROP TRIGGER IF EXISTS a_ins_release_group_secondary_type_join_mirror ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS a_del_release_group_secondary_type_join_mirror ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS a_ins_release_label_mirror ON release_label; @@ -31,6 +34,8 @@ DROP TRIGGER IF EXISTS apply_artist_release_pending_updates_mirror ON release_co DROP TRIGGER IF EXISTS apply_artist_release_pending_updates_mirror ON release_first_release_date; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_meta; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_primary_type; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_secondary_type; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS apply_artist_release_pending_updates_mirror ON release_label; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON track; diff --git a/mbslave/sql/DropPrimaryKeys.sql b/mbslave/sql/DropPrimaryKeys.sql index b16b198..0edea17 100644 --- a/mbslave/sql/DropPrimaryKeys.sql +++ b/mbslave/sql/DropPrimaryKeys.sql @@ -264,6 +264,7 @@ ALTER TABLE medium_attribute_type_allowed_value DROP CONSTRAINT IF EXISTS medium ALTER TABLE medium_attribute_type_allowed_value_allowed_format DROP CONSTRAINT IF EXISTS medium_attribute_type_allowed_value_allowed_format_pkey; ALTER TABLE medium_cdtoc DROP CONSTRAINT IF EXISTS medium_cdtoc_pkey; ALTER TABLE medium_format DROP CONSTRAINT IF EXISTS medium_format_pkey; +ALTER TABLE medium_gid_redirect DROP CONSTRAINT IF EXISTS medium_gid_redirect_pkey; ALTER TABLE medium_index DROP CONSTRAINT IF EXISTS medium_index_pkey; ALTER TABLE mood DROP CONSTRAINT IF EXISTS mood_pkey; ALTER TABLE mood_alias DROP CONSTRAINT IF EXISTS mood_alias_pkey; diff --git a/mbslave/sql/DropReplicationTriggers.sql b/mbslave/sql/DropReplicationTriggers.sql index 1a48ba5..4c1fa3e 100644 --- a/mbslave/sql/DropReplicationTriggers.sql +++ b/mbslave/sql/DropReplicationTriggers.sql @@ -204,6 +204,7 @@ DROP TRIGGER IF EXISTS reptg_medium_attribute_type_allowed_value ON medium_attri DROP TRIGGER IF EXISTS reptg_medium_attribute_type_allowed_value_allowed_format ON medium_attribute_type_allowed_value_allowed_format; DROP TRIGGER IF EXISTS reptg_medium_cdtoc ON medium_cdtoc; DROP TRIGGER IF EXISTS reptg_medium_format ON medium_format; +DROP TRIGGER IF EXISTS reptg_medium_gid_redirect ON medium_gid_redirect; DROP TRIGGER IF EXISTS reptg_medium_index ON medium_index; DROP TRIGGER IF EXISTS reptg_mood ON mood; DROP TRIGGER IF EXISTS reptg_mood_alias ON mood_alias; diff --git a/mbslave/sql/DropReplicationTriggers2.sql b/mbslave/sql/DropReplicationTriggers2.sql index 7474e75..48b568c 100644 --- a/mbslave/sql/DropReplicationTriggers2.sql +++ b/mbslave/sql/DropReplicationTriggers2.sql @@ -36,6 +36,7 @@ DROP TRIGGER IF EXISTS reptg2_artist_type ON artist_type; DROP TRIGGER IF EXISTS reptg2_cdtoc ON cdtoc; DROP TRIGGER IF EXISTS reptg2_cdtoc_raw ON cdtoc_raw; DROP TRIGGER IF EXISTS reptg2_country_area ON country_area; +DROP TRIGGER IF EXISTS reptg2_editor ON editor; DROP TRIGGER IF EXISTS reptg2_editor_collection_type ON editor_collection_type; DROP TRIGGER IF EXISTS reptg2_event ON event; DROP TRIGGER IF EXISTS reptg2_event_alias ON event_alias; @@ -204,6 +205,7 @@ DROP TRIGGER IF EXISTS reptg2_medium_attribute_type_allowed_value ON medium_attr DROP TRIGGER IF EXISTS reptg2_medium_attribute_type_allowed_value_allowed_format ON medium_attribute_type_allowed_value_allowed_format; DROP TRIGGER IF EXISTS reptg2_medium_cdtoc ON medium_cdtoc; DROP TRIGGER IF EXISTS reptg2_medium_format ON medium_format; +DROP TRIGGER IF EXISTS reptg2_medium_gid_redirect ON medium_gid_redirect; DROP TRIGGER IF EXISTS reptg2_medium_index ON medium_index; DROP TRIGGER IF EXISTS reptg2_mood ON mood; DROP TRIGGER IF EXISTS reptg2_mood_alias ON mood_alias; @@ -228,6 +230,7 @@ DROP TRIGGER IF EXISTS reptg2_recording_annotation ON recording_annotation; DROP TRIGGER IF EXISTS reptg2_recording_attribute ON recording_attribute; DROP TRIGGER IF EXISTS reptg2_recording_attribute_type ON recording_attribute_type; DROP TRIGGER IF EXISTS reptg2_recording_attribute_type_allowed_value ON recording_attribute_type_allowed_value; +DROP TRIGGER IF EXISTS reptg2_recording_first_release_date ON recording_first_release_date; DROP TRIGGER IF EXISTS reptg2_recording_gid_redirect ON recording_gid_redirect; DROP TRIGGER IF EXISTS reptg2_recording_meta ON recording_meta; DROP TRIGGER IF EXISTS reptg2_recording_tag ON recording_tag; diff --git a/mbslave/sql/DropTables.sql b/mbslave/sql/DropTables.sql index 8eb5357..6d1e53e 100644 --- a/mbslave/sql/DropTables.sql +++ b/mbslave/sql/DropTables.sql @@ -268,6 +268,7 @@ DROP TABLE medium_attribute_type_allowed_value; DROP TABLE medium_attribute_type_allowed_value_allowed_format; DROP TABLE medium_cdtoc; DROP TABLE medium_format; +DROP TABLE medium_gid_redirect; DROP TABLE medium_index; DROP TABLE mood; DROP TABLE mood_alias; diff --git a/mbslave/sql/DropTriggers.sql b/mbslave/sql/DropTriggers.sql index 17e54e9..9920e57 100644 --- a/mbslave/sql/DropTriggers.sql +++ b/mbslave/sql/DropTriggers.sql @@ -152,6 +152,7 @@ DROP TRIGGER IF EXISTS b_upd_link_attribute_text_value ON link_attribute_text_va DROP TRIGGER IF EXISTS b_upd_link_attribute_type ON link_attribute_type; DROP TRIGGER IF EXISTS b_upd_link_type ON link_type; DROP TRIGGER IF EXISTS b_upd_link_type_attribute_type ON link_type_attribute_type; +DROP TRIGGER IF EXISTS a_upd_medium ON medium; DROP TRIGGER IF EXISTS b_upd_medium ON medium; DROP TRIGGER IF EXISTS b_upd_medium_cdtoc ON medium_cdtoc; DROP TRIGGER IF EXISTS b_upd_mood ON mood; @@ -194,6 +195,8 @@ DROP TRIGGER IF EXISTS a_ins_release_group ON release_group; DROP TRIGGER IF EXISTS a_upd_release_group ON release_group; DROP TRIGGER IF EXISTS a_del_release_group ON release_group; DROP TRIGGER IF EXISTS b_upd_release_group ON release_group; +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type ON release_group_primary_type; +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type ON release_group_secondary_type; DROP TRIGGER IF EXISTS a_ins_release_group_secondary_type_join ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS a_del_release_group_secondary_type_join ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS b_upd_release_group_secondary_type_join ON release_group_secondary_type_join; @@ -386,6 +389,8 @@ DROP TRIGGER IF EXISTS apply_artist_release_pending_updates ON release_country; DROP TRIGGER IF EXISTS apply_artist_release_pending_updates ON release_first_release_date; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_meta; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_primary_type; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_secondary_type; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_secondary_type_join; DROP TRIGGER IF EXISTS apply_artist_release_pending_updates ON release_label; DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON track; diff --git a/mbslave/sql/DropViews.sql b/mbslave/sql/DropViews.sql index 3b532a0..3fb3c77 100644 --- a/mbslave/sql/DropViews.sql +++ b/mbslave/sql/DropViews.sql @@ -2,10 +2,12 @@ \unset ON_ERROR_STOP DROP VIEW artist_series; +DROP VIEW editor_sanitized; DROP VIEW event_series; DROP VIEW medium_track_durations; DROP VIEW recording_series; DROP VIEW release_event; DROP VIEW release_group_series; DROP VIEW release_series; +DROP VIEW series_series; DROP VIEW work_series; diff --git a/mbslave/sql/EnableLastUpdatedTriggers.sql b/mbslave/sql/EnableLastUpdatedTriggers.sql old mode 100755 new mode 100644 diff --git a/mbslave/sql/InsertTestData.sql b/mbslave/sql/InsertTestData.sql index 0fecd6c..8914845 100644 --- a/mbslave/sql/InsertTestData.sql +++ b/mbslave/sql/InsertTestData.sql @@ -110,8 +110,8 @@ INSERT INTO release_label (id, release, label, catalog_number) INSERT INTO url (id, gid, url) VALUES (1, '9201840b-d810-4e0f-bb75-c791205f5b24', 'http://musicbrainz.org/'); -INSERT INTO medium (id, release, position, format, name) VALUES (1, 1, 1, 1, 'The First Disc'); -INSERT INTO medium (id, release, position, format, name) VALUES (2, 1, 2, 1, 'The Second Disc'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (1, 'e67d7889-d617-478f-acc2-40012aec59f5', 1, 1, 1, 'The First Disc'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (2, 'e692e77f-8670-4c1c-b880-8c3d23d14f04', 1, 2, 1, 'The Second Disc'); INSERT INTO track (id, gid, recording, medium, position, number, name, artist_credit, length) VALUES (1, '3fd2523e-1ced-4f83-8b93-c7ecf6960b32', 1, 1, 1, 1, 1, 2, 123456); @@ -157,11 +157,11 @@ INSERT INTO release_label (id, release, label, catalog_number) INSERT INTO release_label (id, release, label, catalog_number) VALUES (4, 3, 2, '82796 97772 2'); -INSERT INTO medium (id, release, position, format, name) VALUES (3, 2, 1, 1, 'A Sea of Honey'); -INSERT INTO medium (id, release, position, format, name) VALUES (4, 2, 2, 1, 'A Sky of Honey'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (3, '6ff40540-7c91-4d0d-bbdd-a199edb45e08', 2, 1, 1, 'A Sea of Honey'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (4, '76e93539-2c4a-4e24-b3ad-79657cb8185e', 2, 2, 1, 'A Sky of Honey'); -INSERT INTO medium (id, release, position, format, name) VALUES (5, 3, 1, 1, 'A Sea of Honey'); -INSERT INTO medium (id, release, position, format, name) VALUES (6, 3, 2, 1, 'A Sky of Honey'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (5, '84130ff4-50bb-45cf-a3db-454236a59bc4', 3, 1, 1, 'A Sea of Honey'); +INSERT INTO medium (id, gid, release, position, format, name) VALUES (6, '0e8c3742-21be-48ec-8672-138cd0e6d178', 3, 2, 1, 'A Sky of Honey'); INSERT INTO recording (id, gid, name, artist_credit, length, last_updated) VALUES (2, '54b9d183-7dab-42ba-94a3-7388a66604b8', 'King of the Mountain', 3, 293720, '2020-02-20 19:00:00'); @@ -242,6 +242,7 @@ INSERT INTO isrc (isrc, recording) VALUES ('DEE250800230', 2); INSERT INTO link (id, link_type, attribute_count) VALUES (1, 148, 1); INSERT INTO link (id, link_type, attribute_count) VALUES (2, 148, 2); INSERT INTO link (id, link_type, attribute_count, begin_date_year) VALUES (3, 183, 0, 2006); +INSERT INTO link (id, link_type, attribute_count) VALUES (4, 108, 0); INSERT INTO link_attribute (link, attribute_type) VALUES (1, 229); INSERT INTO link_attribute (link, attribute_type) VALUES (2, 1); @@ -256,6 +257,7 @@ INSERT INTO artist (id, gid, name, sort_name, comment) VALUES INSERT INTO event (id, gid, name, begin_date_year, begin_date_month, begin_date_day, end_date_year, end_date_month, end_date_day, time, type, cancelled, setlist, comment, ended) VALUES (59357, 'ca1d24c1-1999-46fd-8a95-3d4108df5cb2', 'BBC Open Music Prom', 2022, 9, 1, 2022, 9, 1, '19:30:00', 1, 'f', NULL, '2022, Prom 60', 't'); +INSERT INTO l_artist_artist (id, link, entity0, entity1) VALUES (1, 4, 8, 9); INSERT INTO l_artist_recording (id, link, entity0, entity1) VALUES (1, 1, 8, 2); INSERT INTO l_artist_recording (id, link, entity0, entity1, edits_pending) VALUES (2, 1, 9, 2, 1); INSERT INTO l_artist_recording (id, link, entity0, entity1) VALUES (3, 2, 8, 3); diff --git a/mbslave/sql/TruncateTables.sql b/mbslave/sql/TruncateTables.sql index db1e112..a9daad7 100644 --- a/mbslave/sql/TruncateTables.sql +++ b/mbslave/sql/TruncateTables.sql @@ -268,6 +268,7 @@ TRUNCATE TABLE medium_attribute_type_allowed_value RESTART IDENTITY CASCADE; TRUNCATE TABLE medium_attribute_type_allowed_value_allowed_format RESTART IDENTITY CASCADE; TRUNCATE TABLE medium_cdtoc RESTART IDENTITY CASCADE; TRUNCATE TABLE medium_format RESTART IDENTITY CASCADE; +TRUNCATE TABLE medium_gid_redirect RESTART IDENTITY CASCADE; TRUNCATE TABLE medium_index RESTART IDENTITY CASCADE; TRUNCATE TABLE mood RESTART IDENTITY CASCADE; TRUNCATE TABLE mood_alias RESTART IDENTITY CASCADE; diff --git a/mbslave/sql/caa/CreateMQTriggers.sql b/mbslave/sql/caa/CreateMQTriggers.sql deleted file mode 100644 index b508800..0000000 --- a/mbslave/sql/caa/CreateMQTriggers.sql +++ /dev/null @@ -1,148 +0,0 @@ -BEGIN; - -SET search_path = 'cover_art_archive'; - -CREATE OR REPLACE FUNCTION reindex_release() RETURNS trigger AS $$ - DECLARE - release_mbid UUID; - BEGIN - SELECT gid INTO release_mbid - FROM musicbrainz.release r - JOIN cover_art_archive.cover_art caa_r ON r.id = caa_r.release - WHERE r.id = NEW.id; - - IF FOUND THEN - PERFORM amqp.publish(1, 'cover-art-archive', 'index', release_mbid::text); - END IF; - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_reindex AFTER UPDATE OR INSERT -ON musicbrainz.release FOR EACH ROW -EXECUTE PROCEDURE reindex_release(); - -CREATE OR REPLACE FUNCTION reindex_artist() RETURNS trigger AS $$ - BEGIN - -- Short circuit if the name hasn't changed - IF NEW.name = OLD.name AND NEW.sort_name = OLD.sort_name THEN - RETURN NULL; - END IF; - - PERFORM amqp.publish(1, 'cover-art-archive', 'index', r.gid::text) - FROM musicbrainz.release r - JOIN cover_art_archive.cover_art caa_r ON r.id = caa_r.release - JOIN musicbrainz.artist_credit_name acn ON r.artist_credit = acn.artist_credit - WHERE acn.artist = NEW.id; - - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_reindex AFTER UPDATE -ON musicbrainz.artist FOR EACH ROW -EXECUTE PROCEDURE reindex_artist(); - -CREATE OR REPLACE FUNCTION reindex_release_via_catno() RETURNS trigger AS $$ - DECLARE - release_mbid UUID; - BEGIN - SELECT gid INTO release_mbid - FROM musicbrainz.release - JOIN musicbrainz.release_label ON release_label.release = release.id - JOIN cover_art_archive.cover_art caa_r ON release.id = caa_r.release - WHERE release.id = NEW.release; - - IF FOUND THEN - PERFORM amqp.publish(1, 'cover-art-archive', 'index', release_mbid::text); - END IF; - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_reindex AFTER UPDATE OR INSERT -ON musicbrainz.release_label FOR EACH ROW -EXECUTE PROCEDURE reindex_release_via_catno(); - -CREATE OR REPLACE FUNCTION reindex_caa() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'cover-art-archive', 'index', gid::text) - FROM musicbrainz.release - WHERE id = coalesce(( - CASE TG_OP - WHEN 'DELETE' THEN OLD.release - ELSE NEW.release - END)); - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_reindex AFTER UPDATE OR INSERT OR DELETE -ON cover_art_archive.cover_art FOR EACH ROW -EXECUTE PROCEDURE reindex_caa(); - -CREATE OR REPLACE FUNCTION reindex_caa_type() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'cover-art-archive', 'index', r.gid::text) - FROM musicbrainz.release r - JOIN cover_art_archive.cover_art ca ON r.id = ca.release - WHERE ca.id = coalesce(( - CASE TG_OP - WHEN 'DELETE' THEN OLD.id - ELSE NEW.id - END)); - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_reindex AFTER UPDATE OR INSERT OR DELETE -ON cover_art_archive.cover_art_type FOR EACH ROW -EXECUTE PROCEDURE reindex_caa_type(); - -CREATE OR REPLACE FUNCTION caa_move() RETURNS trigger AS $$ - BEGIN - IF OLD.release != NEW.release THEN - PERFORM amqp.publish(1, 'cover-art-archive', 'move', - (SELECT ca.id || E'\n' || - old_release.gid || E'\n' || - new_release.gid || E'\n' || - it.suffix || E'\n' - FROM cover_art_archive.cover_art ca - JOIN cover_art_archive.image_type it ON it.mime_type = ca.mime_type, - musicbrainz.release old_release, - musicbrainz.release new_release - WHERE ca.id = OLD.id - AND old_release.id = OLD.release - AND new_release.id = NEW.release)); - END IF; - RETURN NEW; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_move BEFORE UPDATE -ON cover_art_archive.cover_art FOR EACH ROW -EXECUTE PROCEDURE caa_move(); - -CREATE OR REPLACE FUNCTION delete_release() RETURNS trigger AS $$ - BEGIN - PERFORM - amqp.publish(1, 'cover-art-archive', 'delete', - (cover_art.id || E'\n' || OLD.gid || E'\n' || image_type.suffix)::text) - FROM cover_art_archive.cover_art - JOIN cover_art_archive.image_type ON image_type.mime_type = cover_art.mime_type - WHERE release = OLD.id; - - PERFORM amqp.publish(1, 'cover-art-archive', 'delete', - ('index.json' || E'\n' || OLD.gid)::text) - FROM musicbrainz.release - WHERE release.id = OLD.id; - - RETURN OLD; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER caa_delete BEFORE DELETE -ON musicbrainz.release FOR EACH ROW -EXECUTE PROCEDURE delete_release(); - -COMMIT; diff --git a/mbslave/sql/caa/CreateViews.sql b/mbslave/sql/caa/CreateViews.sql index c9cbac3..e005e2c 100644 --- a/mbslave/sql/caa/CreateViews.sql +++ b/mbslave/sql/caa/CreateViews.sql @@ -11,7 +11,6 @@ SELECT cover_art.*, JOIN cover_art_archive.cover_art ca_front USING (id) WHERE ca_front.release = cover_art.release AND type_id = 1 - AND mime_type != 'application/pdf' ORDER BY ca_front.ordering LIMIT 1), FALSE) AS is_front, coalesce(cover_art.id = (SELECT id FROM cover_art_archive.cover_art_type diff --git a/mbslave/sql/caa/DropMQTriggers.sql b/mbslave/sql/caa/DropMQTriggers.sql deleted file mode 100644 index b6449b2..0000000 --- a/mbslave/sql/caa/DropMQTriggers.sql +++ /dev/null @@ -1,19 +0,0 @@ -BEGIN; - -DROP TRIGGER caa_reindex ON musicbrainz.artist; -DROP TRIGGER caa_reindex ON musicbrainz.release; -DROP TRIGGER caa_reindex ON musicbrainz.release_label; -DROP TRIGGER caa_reindex ON cover_art_archive.cover_art; -DROP TRIGGER caa_reindex ON cover_art_archive.cover_art_type; -DROP TRIGGER caa_move ON cover_art_archive.cover_art; -DROP TRIGGER caa_delete ON musicbrainz.release; - -DROP FUNCTION cover_art_archive.reindex_release (); -DROP FUNCTION cover_art_archive.reindex_artist (); -DROP FUNCTION cover_art_archive.reindex_release_via_catno (); -DROP FUNCTION cover_art_archive.reindex_caa (); -DROP FUNCTION cover_art_archive.reindex_caa_type (); -DROP FUNCTION cover_art_archive.caa_move (); -DROP FUNCTION cover_art_archive.delete_release (); - -COMMIT; diff --git a/mbslave/sql/dbmirror2/README b/mbslave/sql/dbmirror2/README index 1598c4a..c8ab121 100644 --- a/mbslave/sql/dbmirror2/README +++ b/mbslave/sql/dbmirror2/README @@ -1,6 +1,6 @@ -ReplicationSetup.sql and MasterSetup.sql are copied from -https://github.com/metabrainz/dbmirror2, commit eda9923. (These are the only -two files we need, and submodules are "difficult.") +dbmirror2.sql is copied from https://github.com/metabrainz/dbmirror2, +commit 5f26872. (This is the only file we need, and submodules are +"difficult.") This is not a fork; please do not make any changes to the files here without first submitting them upstream to dbmirror2. They should exactly mirror the diff --git a/mbslave/sql/dbmirror2/RefreshColumnInfo.sql b/mbslave/sql/dbmirror2/RefreshColumnInfo.sql deleted file mode 100644 index ded9dde..0000000 --- a/mbslave/sql/dbmirror2/RefreshColumnInfo.sql +++ /dev/null @@ -1,16 +0,0 @@ -\set ON_ERROR_STOP 1 - -BEGIN; - -DO $$ -BEGIN - PERFORM 1 FROM pg_matviews - WHERE schemaname = 'dbmirror2' - AND matviewname = 'column_info'; - - IF FOUND THEN - REFRESH MATERIALIZED VIEW dbmirror2.column_info; - END IF; -END $$; - -COMMIT; diff --git a/mbslave/sql/dbmirror2/ReplicationSetup.sql b/mbslave/sql/dbmirror2/ReplicationSetup.sql deleted file mode 100644 index 659a7bf..0000000 --- a/mbslave/sql/dbmirror2/ReplicationSetup.sql +++ /dev/null @@ -1,54 +0,0 @@ --- Copyright (C) 2021 MetaBrainz Foundation --- Licensed under the GPL version 2, or (at your option) any later version: --- http://www.gnu.org/licenses/gpl-2.0.txt - -BEGIN; - --- The pending_keys tables serves two purposes: --- 1. Stores the primary keys associated with each table. --- 2. Allows quickly checking if a particular table has changed --- in the packet. -CREATE TABLE dbmirror2.pending_keys ( - tablename TEXT, - keys TEXT[] NOT NULL -); - -ALTER TABLE dbmirror2.pending_keys - ADD CONSTRAINT pending_keys_pkey - PRIMARY KEY (tablename); - -CREATE TABLE dbmirror2.pending_ts ( - xid BIGINT, - ts TIMESTAMP WITH TIME ZONE NOT NULL -); - -ALTER TABLE dbmirror2.pending_ts - ADD CONSTRAINT pending_ts_pkey - PRIMARY KEY (xid); - -CREATE TABLE dbmirror2.pending_data ( - seqid BIGSERIAL, - tablename TEXT NOT NULL CONSTRAINT tablename_exists CHECK (to_regclass(tablename) IS NOT NULL), - op "char" NOT NULL CONSTRAINT op_in_diu CHECK (op IN ('d', 'i', 'u')), - xid BIGINT NOT NULL, - -- We use JSON over JSONB because there is no need to perform - -- operations on the data; this additionally lets us store the - -- keys in column-order, which makes the packets much easier - -- to read while debugging. - olddata JSON CONSTRAINT olddata_is_null_for_inserts CHECK ((olddata IS NULL) = (op = 'i')), - newdata JSON CONSTRAINT newdata_is_null_for_deletes CHECK ((newdata IS NULL) = (op = 'd')), - oldctid TID, - trgdepth INTEGER -); - -ALTER TABLE dbmirror2.pending_data - ADD CONSTRAINT pending_data_pkey - PRIMARY KEY (seqid); - -CREATE INDEX pending_data_idx_xid_seqid - ON dbmirror2.pending_data (xid, seqid); - -CREATE INDEX pending_data_idx_oldctid_xid - ON dbmirror2.pending_data (oldctid, xid); - -COMMIT; diff --git a/mbslave/sql/dbmirror2/MasterSetup.sql b/mbslave/sql/dbmirror2/dbmirror2.sql similarity index 68% rename from mbslave/sql/dbmirror2/MasterSetup.sql rename to mbslave/sql/dbmirror2/dbmirror2.sql index d4c6481..71d28ac 100644 --- a/mbslave/sql/dbmirror2/MasterSetup.sql +++ b/mbslave/sql/dbmirror2/dbmirror2.sql @@ -4,41 +4,6 @@ BEGIN; --- The column_info view allows us to determine whether a column in a given --- table is part of its primary key, and gives us its position too. --- --- This view must be refreshed after every schema change; an event trigger --- in MasterEventTriggerSetup.sql can handle this automatically. -CREATE MATERIALIZED VIEW dbmirror2.column_info ( - table_schema, - table_name, - column_name, - position, - is_primary -) AS - SELECT - c.table_schema, - c.table_name, - c.column_name, - c.ordinal_position, - coalesce(( - SELECT TRUE - FROM information_schema.key_column_usage kcu - NATURAL JOIN information_schema.table_constraints tc - WHERE kcu.table_schema = c.table_schema - AND kcu.table_name = c.table_name - AND kcu.column_name = c.column_name - AND tc.constraint_type = 'PRIMARY KEY' - ), FALSE) AS is_primary - FROM information_schema.columns c - NATURAL JOIN information_schema.tables t - WHERE t.table_type = 'BASE TABLE' - AND t.table_schema NOT IN ('dbmirror2', 'information_schema', 'pg_catalog') -WITH DATA; - -CREATE INDEX column_info_idx - ON dbmirror2.column_info (table_schema, table_name, is_primary); - CREATE FUNCTION dbmirror2.recordchange() RETURNS trigger AS $$ DECLARE @@ -46,9 +11,6 @@ DECLARE -- pending_data.tablename and pending_keys.tablename _tablename TEXT; keys TEXT[]; - jsonquery TEXT; - olddata JSON; - newdata JSON; -- prefixed with 'x' to avoid conflict with column name in queries xoldctid TID; nextseqid BIGINT; @@ -75,29 +37,11 @@ BEGIN VALUES (txid_current(), transaction_timestamp()) ON CONFLICT DO NOTHING; - jsonquery := ( - SELECT format( - 'SELECT json_build_object(%1$s)', - array_to_string( - array_agg( - format('%1$L, ($1).%1$I', column_name) ORDER BY position - ), - ', ' - ) - ) - FROM dbmirror2.column_info - WHERE table_schema = TG_TABLE_SCHEMA AND table_name = TG_TABLE_NAME - ); - IF TG_OP != 'INSERT' THEN - EXECUTE jsonquery INTO olddata USING OLD; - xoldctid := OLD.ctid; END IF; IF TG_OP != 'DELETE' THEN - EXECUTE jsonquery INTO newdata USING NEW; - -- Detect out-of-order operations caused by cascading triggers. -- -- When row-level AFTER triggers are cascaded, the innermost trigger @@ -154,8 +98,8 @@ BEGIN _tablename, lower(left(TG_OP, 1)), txid_current(), - olddata, - newdata, + row_to_json(OLD), + row_to_json(NEW), xoldctid, pg_trigger_depth() ); @@ -164,4 +108,51 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- The pending_keys tables serves two purposes: +-- 1. Stores the primary keys associated with each table. +-- 2. Allows quickly checking if a particular table has changed +-- in the packet. +CREATE TABLE dbmirror2.pending_keys ( + tablename TEXT, + keys TEXT[] NOT NULL +); + +ALTER TABLE dbmirror2.pending_keys + ADD CONSTRAINT pending_keys_pkey + PRIMARY KEY (tablename); + +CREATE TABLE dbmirror2.pending_ts ( + xid BIGINT, + ts TIMESTAMP WITH TIME ZONE NOT NULL +); + +ALTER TABLE dbmirror2.pending_ts + ADD CONSTRAINT pending_ts_pkey + PRIMARY KEY (xid); + +CREATE TABLE dbmirror2.pending_data ( + seqid BIGSERIAL, + tablename TEXT NOT NULL CONSTRAINT tablename_exists CHECK (to_regclass(tablename) IS NOT NULL), + op "char" NOT NULL CONSTRAINT op_in_diu CHECK (op IN ('d', 'i', 'u')), + xid BIGINT NOT NULL, + -- We use JSON over JSONB because there is no need to perform + -- operations on the data; this additionally lets us store the + -- keys in column-order, which makes the packets much easier + -- to read while debugging. + olddata JSON CONSTRAINT olddata_is_null_for_inserts CHECK ((olddata IS NULL) = (op = 'i')), + newdata JSON CONSTRAINT newdata_is_null_for_deletes CHECK ((newdata IS NULL) = (op = 'd')), + oldctid TID, + trgdepth INTEGER +); + +ALTER TABLE dbmirror2.pending_data + ADD CONSTRAINT pending_data_pkey + PRIMARY KEY (seqid); + +CREATE INDEX pending_data_idx_xid_seqid + ON dbmirror2.pending_data (xid, seqid); + +CREATE INDEX pending_data_idx_oldctid_xid + ON dbmirror2.pending_data (oldctid, xid); + COMMIT; diff --git a/mbslave/sql/eaa/CreateMQTriggers.sql b/mbslave/sql/eaa/CreateMQTriggers.sql deleted file mode 100644 index 4aee8d7..0000000 --- a/mbslave/sql/eaa/CreateMQTriggers.sql +++ /dev/null @@ -1,161 +0,0 @@ -BEGIN; - -SET search_path = 'event_art_archive'; - -CREATE OR REPLACE FUNCTION reindex_event() RETURNS trigger AS $$ - DECLARE - event_mbid UUID; - BEGIN - SELECT gid INTO event_mbid - FROM musicbrainz.event e - JOIN event_art_archive.event_art ea ON e.id = ea.event - WHERE e.id = NEW.id; - - IF FOUND THEN - PERFORM amqp.publish(1, 'event-art-archive', 'index', event_mbid::text); - END IF; - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE OR INSERT -ON musicbrainz.event FOR EACH ROW -EXECUTE PROCEDURE reindex_event(); - -CREATE OR REPLACE FUNCTION reindex_artist() RETURNS trigger AS $$ - BEGIN - -- Short circuit if the name hasn't changed - IF NEW.name = OLD.name AND NEW.sort_name = OLD.sort_name THEN - RETURN NULL; - END IF; - - PERFORM amqp.publish(1, 'event-art-archive', 'index', e.gid::text) - FROM musicbrainz.event e - JOIN event_art_archive.event_art ea ON e.id = ea.event - JOIN musicbrainz.l_artist_event lae ON e.id = lae.entity1 - WHERE lae.entity0 = NEW.id; - - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE -ON musicbrainz.artist FOR EACH ROW -EXECUTE PROCEDURE reindex_artist(); - -CREATE OR REPLACE FUNCTION reindex_l_artist_event() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'event-art-archive', 'index', e.gid::text) - FROM musicbrainz.event e - JOIN event_art_archive.event_art ea ON e.id = ea.event - JOIN musicbrainz.l_artist_event lae ON e.id = lae.entity1 - WHERE lae.id = (CASE TG_OP - WHEN 'DELETE' THEN OLD.id - ELSE NEW.id - END); - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE OR INSERT OR DELETE -ON musicbrainz.l_artist_event FOR EACH ROW -EXECUTE PROCEDURE reindex_l_artist_event(); - -CREATE OR REPLACE FUNCTION reindex_place() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'event-art-archive', 'index', e.gid::text) - FROM musicbrainz.event e - JOIN event_art_archive.event_art ea ON e.id = ea.event - JOIN musicbrainz.l_event_place lep ON e.id = lep.entity0 - WHERE lep.entity1 = NEW.id; - - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE -ON musicbrainz.place FOR EACH ROW -EXECUTE PROCEDURE reindex_place(); - -CREATE OR REPLACE FUNCTION reindex_l_event_place() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'event-art-archive', 'index', e.gid::text) - FROM musicbrainz.event e - JOIN event_art_archive.event_art ea ON e.id = ea.event - JOIN musicbrainz.l_event_place lep ON e.id = lep.entity0 - WHERE lep.id = (CASE TG_OP - WHEN 'DELETE' THEN OLD.id - ELSE NEW.id - END); - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE OR INSERT OR DELETE -ON musicbrainz.l_event_place FOR EACH ROW -EXECUTE PROCEDURE reindex_l_event_place(); - -CREATE OR REPLACE FUNCTION reindex_eaa() RETURNS trigger AS $$ - BEGIN - PERFORM amqp.publish(1, 'event-art-archive', 'index', gid::text) - FROM musicbrainz.event - WHERE id = coalesce(( - CASE TG_OP - WHEN 'DELETE' THEN OLD.event - ELSE NEW.event - END)); - RETURN NULL; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_reindex AFTER UPDATE OR INSERT OR DELETE -ON event_art_archive.event_art FOR EACH ROW -EXECUTE PROCEDURE reindex_eaa(); - -CREATE OR REPLACE FUNCTION move_event() RETURNS trigger AS $$ - BEGIN - IF OLD.event != NEW.event THEN - PERFORM amqp.publish(1, 'event-art-archive', 'move', - (SELECT ea.id || E'\n' || - old_event.gid || E'\n' || - new_event.gid || E'\n' || - it.suffix || E'\n' - FROM event_art_archive.event_art ea - JOIN cover_art_archive.image_type it ON it.mime_type = ea.mime_type, - musicbrainz.event old_event, - musicbrainz.event new_event - WHERE ea.id = OLD.id - AND old_event.id = OLD.event - AND new_event.id = NEW.event)); - END IF; - RETURN NEW; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_move BEFORE UPDATE -ON event_art_archive.event_art FOR EACH ROW -EXECUTE PROCEDURE move_event(); - -CREATE OR REPLACE FUNCTION delete_event() RETURNS trigger AS $$ - BEGIN - PERFORM - amqp.publish(1, 'event-art-archive', 'delete', - (event_art.id || E'\n' || OLD.gid || E'\n' || image_type.suffix)::text) - FROM event_art_archive.event_art - JOIN cover_art_archive.image_type ON image_type.mime_type = event_art.mime_type - WHERE event = OLD.id; - - PERFORM amqp.publish(1, 'event-art-archive', 'delete', - ('index.json' || E'\n' || OLD.gid)::text) - FROM musicbrainz.event - WHERE event.id = OLD.id; - - RETURN OLD; - END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER eaa_delete BEFORE DELETE -ON musicbrainz.event FOR EACH ROW -EXECUTE PROCEDURE delete_event(); - -COMMIT; diff --git a/mbslave/sql/eaa/CreateViews.sql b/mbslave/sql/eaa/CreateViews.sql index 56f9aaa..cb87fc4 100644 --- a/mbslave/sql/eaa/CreateViews.sql +++ b/mbslave/sql/eaa/CreateViews.sql @@ -11,7 +11,6 @@ SELECT event_art.*, JOIN event_art_archive.event_art ea_front USING (id) WHERE ea_front.event = event_art.event AND type_id = 1 - AND mime_type != 'application/pdf' ORDER BY ea_front.ordering LIMIT 1), FALSE) AS is_front, array(SELECT art_type.name diff --git a/mbslave/sql/eaa/DropMQTriggers.sql b/mbslave/sql/eaa/DropMQTriggers.sql deleted file mode 100644 index 3651e68..0000000 --- a/mbslave/sql/eaa/DropMQTriggers.sql +++ /dev/null @@ -1,21 +0,0 @@ -BEGIN; - -DROP TRIGGER eaa_delete ON musicbrainz.event; -DROP TRIGGER eaa_move ON event_art_archive.event_art; -DROP TRIGGER eaa_reindex ON event_art_archive.event_art; -DROP TRIGGER eaa_reindex ON musicbrainz.artist; -DROP TRIGGER eaa_reindex ON musicbrainz.event; -DROP TRIGGER eaa_reindex ON musicbrainz.l_artist_event; -DROP TRIGGER eaa_reindex ON musicbrainz.l_event_place; -DROP TRIGGER eaa_reindex ON musicbrainz.place; - -DROP FUNCTION event_art_archive.delete_event (); -DROP FUNCTION event_art_archive.move_event (); -DROP FUNCTION event_art_archive.reindex_artist (); -DROP FUNCTION event_art_archive.reindex_eaa (); -DROP FUNCTION event_art_archive.reindex_event (); -DROP FUNCTION event_art_archive.reindex_l_artist_event (); -DROP FUNCTION event_art_archive.reindex_l_event_place (); -DROP FUNCTION event_art_archive.reindex_place (); - -COMMIT; diff --git a/mbslave/sql/updates/20240221-mbs-13492.sql b/mbslave/sql/updates/20240221-mbs-13492.sql new file mode 100644 index 0000000..6cc4950 --- /dev/null +++ b/mbslave/sql/updates/20240221-mbs-13492.sql @@ -0,0 +1,23 @@ +\set ON_ERROR_STOP 1 + +BEGIN; +SET LOCAL statement_timeout = 0; + +UPDATE editor + SET privs = privs | 8192 -- set new beginner flag + WHERE id != 4 -- avoid setting ModBot as beginner + AND NOT deleted + AND ( + member_since > NOW() - INTERVAL '2 weeks' + OR + NOT EXISTS ( + SELECT 1 + FROM edit + WHERE edit.editor = editor.id + AND edit.autoedit = 0 + AND edit.status = 2 + OFFSET 9 + ) + ); + +COMMIT; diff --git a/mbslave/sql/updates/20240726-mbs-9373.sql b/mbslave/sql/updates/20240726-mbs-9373.sql new file mode 100644 index 0000000..96c5ce1 --- /dev/null +++ b/mbslave/sql/updates/20240726-mbs-9373.sql @@ -0,0 +1,9 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +UPDATE editor + SET privs = privs | 16384 -- set new voting disabled flag + WHERE (privs & 1024) > 0; -- where editor had editing disabled flag + +COMMIT; diff --git a/mbslave/sql/updates/20241017-mbs-9253-13464.sql b/mbslave/sql/updates/20241017-mbs-9253-13464.sql new file mode 100644 index 0000000..b7af8c9 --- /dev/null +++ b/mbslave/sql/updates/20241017-mbs-9253-13464.sql @@ -0,0 +1,136 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type ON release_group_primary_type; +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type ON release_group_secondary_type; + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_primary_type; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_secondary_type; + +DROP FUNCTION get_artist_release_group_rows(integer); + +DROP INDEX artist_release_group_nonva_idx_sort; +DROP INDEX artist_release_group_va_idx_sort; + +DROP TABLE artist_release_group_nonva; +DROP TABLE artist_release_group_va; +DROP TABLE artist_release_group; + +CREATE TABLE artist_release_group ( + -- See comment for `artist_release.is_track_artist`. + is_track_artist BOOLEAN NOT NULL, + artist INTEGER NOT NULL, -- references artist.id, CASCADE + unofficial BOOLEAN NOT NULL, + primary_type_child_order SMALLINT, + primary_type SMALLINT, + secondary_type_child_orders SMALLINT[], + secondary_types SMALLINT[], + first_release_date INTEGER, + name VARCHAR COLLATE musicbrainz NOT NULL, + release_group INTEGER NOT NULL -- references release_group.id, CASCADE +) PARTITION BY LIST (is_track_artist); + +CREATE TABLE artist_release_group_nonva + PARTITION OF artist_release_group FOR VALUES IN (FALSE); + +CREATE TABLE artist_release_group_va + PARTITION OF artist_release_group FOR VALUES IN (TRUE); + +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), + rgpt.child_order::SMALLINT, + rg.type::SMALLINT, + array_agg( + DISTINCT rgst.child_order ORDER BY rgst.child_order) + FILTER (WHERE rgst.child_order IS NOT NULL + )::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 + ), + rg.name, + 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_primary_type rgpt ON rgpt.id = rg.type + LEFT JOIN release_group_secondary_type_join st ON st.release_group = rg.id + LEFT JOIN release_group_secondary_type rgst ON rgst.id = st.secondary_type + $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, rgpt.child_order + ORDER BY a_rg.artist, rg.id, a_rg.is_track_artist + $SQL$ + USING release_group_id; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_primary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT id FROM release_group + WHERE release_group.type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_secondary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT release_group + FROM release_group_secondary_type_join + WHERE secondary_type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE INDEX artist_release_group_nonva_idx_sort ON artist_release_group_nonva (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); +CREATE INDEX artist_release_group_va_idx_sort ON artist_release_group_va (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); + +CREATE UNIQUE INDEX artist_release_group_nonva_idx_uniq ON artist_release_group_nonva (release_group, artist); +CREATE UNIQUE INDEX artist_release_group_va_idx_uniq ON artist_release_group_va (release_group, artist); + +COMMIT; diff --git a/mbslave/sql/updates/20241017-mbs-9253-master_and_standalone.sql b/mbslave/sql/updates/20241017-mbs-9253-master_and_standalone.sql new file mode 100644 index 0000000..dcaa5f3 --- /dev/null +++ b/mbslave/sql/updates/20241017-mbs-9253-master_and_standalone.sql @@ -0,0 +1,37 @@ +\set ON_ERROR_STOP 1 + +SET search_path = musicbrainz; + +BEGIN; + +ALTER TABLE artist_release_group + ADD CONSTRAINT artist_release_group_fk_artist + FOREIGN KEY (artist) + REFERENCES artist(id) + ON DELETE CASCADE; + +ALTER TABLE artist_release_group + ADD CONSTRAINT artist_release_group_fk_release_group + FOREIGN KEY (release_group) + REFERENCES release_group(id) + ON DELETE CASCADE; + +CREATE TRIGGER a_upd_release_group_primary_type AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +CREATE TRIGGER a_upd_release_group_secondary_type AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +COMMIT; diff --git a/mbslave/sql/updates/20241017-mbs-9253-mirror_only.sql b/mbslave/sql/updates/20241017-mbs-9253-mirror_only.sql new file mode 100644 index 0000000..e42e50d --- /dev/null +++ b/mbslave/sql/updates/20241017-mbs-9253-mirror_only.sql @@ -0,0 +1,31 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type_mirror ON release_group_primary_type; + +CREATE TRIGGER a_upd_release_group_primary_type_mirror AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type_mirror ON release_group_secondary_type; + +CREATE TRIGGER a_upd_release_group_secondary_type_mirror AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_primary_type; + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_secondary_type; + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +COMMIT; diff --git a/mbslave/sql/updates/20241125-mbs-13832.sql b/mbslave/sql/updates/20241125-mbs-13832.sql new file mode 100644 index 0000000..5a7e1de --- /dev/null +++ b/mbslave/sql/updates/20241125-mbs-13832.sql @@ -0,0 +1,48 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +-- MBS-14014 +DROP VIEW IF EXISTS cover_art_archive.index_listing; + +-- CAA view +CREATE VIEW cover_art_archive.index_listing AS +SELECT cover_art.*, + (edit.close_time IS NOT NULL) AS approved, + coalesce(cover_art.id = (SELECT id FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.cover_art ca_front USING (id) + WHERE ca_front.release = cover_art.release + AND type_id = 1 + ORDER BY ca_front.ordering + LIMIT 1), FALSE) AS is_front, + coalesce(cover_art.id = (SELECT id FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.cover_art ca_front USING (id) + WHERE ca_front.release = cover_art.release + AND type_id = 2 + ORDER BY ca_front.ordering + LIMIT 1), FALSE) AS is_back, + array(SELECT art_type.name + FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.art_type ON cover_art_type.type_id = art_type.id + WHERE cover_art_type.id = cover_art.id) AS types +FROM cover_art_archive.cover_art +LEFT JOIN musicbrainz.edit ON edit.id = cover_art.edit; + +-- EAA view +CREATE OR REPLACE VIEW event_art_archive.index_listing AS +SELECT event_art.*, + (edit.close_time IS NOT NULL) AS approved, + coalesce(event_art.id = (SELECT id FROM event_art_archive.event_art_type + JOIN event_art_archive.event_art ea_front USING (id) + WHERE ea_front.event = event_art.event + AND type_id = 1 + ORDER BY ea_front.ordering + LIMIT 1), FALSE) AS is_front, + array(SELECT art_type.name + FROM event_art_archive.event_art_type + JOIN event_art_archive.art_type ON event_art_type.type_id = art_type.id + WHERE event_art_type.id = event_art.id) AS types +FROM event_art_archive.event_art +LEFT JOIN musicbrainz.edit ON edit.id = event_art.edit; + +COMMIT; diff --git a/mbslave/sql/updates/20250320-mbs-13768-fks.sql b/mbslave/sql/updates/20250320-mbs-13768-fks.sql new file mode 100644 index 0000000..788b9b3 --- /dev/null +++ b/mbslave/sql/updates/20250320-mbs-13768-fks.sql @@ -0,0 +1,12 @@ +\set ON_ERROR_STOP 1 + +SET search_path = musicbrainz; + +BEGIN; + +ALTER TABLE medium_gid_redirect + ADD CONSTRAINT medium_gid_redirect_fk_new_id + FOREIGN KEY (new_id) + REFERENCES medium(id); + +COMMIT; diff --git a/mbslave/sql/updates/20250320-mbs-13768.sql b/mbslave/sql/updates/20250320-mbs-13768.sql new file mode 100644 index 0000000..366593a --- /dev/null +++ b/mbslave/sql/updates/20250320-mbs-13768.sql @@ -0,0 +1,31 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +-- Medium GID + +-- Creating column +ALTER TABLE medium ADD COLUMN gid uuid; + +-- Generating GIDs +UPDATE medium SET gid = + generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'medium' || id); + +-- Adding NOT NULL constraint +ALTER TABLE medium ALTER COLUMN gid SET NOT NULL; + +-- Creating index +CREATE UNIQUE INDEX medium_idx_gid ON medium (gid); + +-- Medium GID redirect +CREATE TABLE medium_gid_redirect ( -- replicate (verbose) + gid UUID NOT NULL, -- PK + new_id INTEGER NOT NULL, -- references medium.id + created TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE medium_gid_redirect ADD CONSTRAINT medium_gid_redirect_pkey PRIMARY KEY (gid); + +CREATE INDEX medium_gid_redirect_idx_new_id ON medium_gid_redirect (new_id); + +COMMIT; diff --git a/mbslave/sql/updates/20250408-mbs-13322.sql b/mbslave/sql/updates/20250408-mbs-13322.sql new file mode 100644 index 0000000..4ed4b1c --- /dev/null +++ b/mbslave/sql/updates/20250408-mbs-13322.sql @@ -0,0 +1,15 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +CREATE OR REPLACE FUNCTION delete_unused_url(ids INTEGER[]) +RETURNS VOID AS $$ +BEGIN + DELETE FROM url_gid_redirect WHERE new_id = any(ids); + DELETE FROM url WHERE id = any(ids); +EXCEPTION + WHEN foreign_key_violation THEN RETURN; +END; +$$ LANGUAGE 'plpgsql'; + +COMMIT; diff --git a/mbslave/sql/updates/20250408-mbs-13964-all.sql b/mbslave/sql/updates/20250408-mbs-13964-all.sql new file mode 100644 index 0000000..5b2a77c --- /dev/null +++ b/mbslave/sql/updates/20250408-mbs-13964-all.sql @@ -0,0 +1,34 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +CREATE OR REPLACE FUNCTION set_mediums_recordings_first_release_dates(medium_ids INTEGER[]) +RETURNS VOID AS $$ +BEGIN + PERFORM set_recordings_first_release_dates(( + SELECT array_agg(recording) + FROM track + WHERE track.medium = any(medium_ids) + )); + RETURN; +END; +$$ LANGUAGE 'plpgsql' STRICT; + +CREATE OR REPLACE FUNCTION a_upd_medium_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF NEW.release IS DISTINCT FROM OLD.release THEN + PERFORM set_mediums_recordings_first_release_dates(ARRAY[OLD.id]); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE TRIGGER a_upd_medium AFTER UPDATE ON medium + FOR EACH ROW EXECUTE PROCEDURE a_upd_medium_mirror(); + +TRUNCATE recording_first_release_date; + +COMMIT; diff --git a/mbslave/sql/updates/20250425-mbs-13464-master_and_standalone.sql b/mbslave/sql/updates/20250425-mbs-13464-master_and_standalone.sql new file mode 100644 index 0000000..42b4259 --- /dev/null +++ b/mbslave/sql/updates/20250425-mbs-13464-master_and_standalone.sql @@ -0,0 +1,19 @@ +\set ON_ERROR_STOP 1 + +SET search_path = musicbrainz; + +BEGIN; + +ALTER TABLE artist_release + ADD CONSTRAINT artist_release_fk_artist + FOREIGN KEY (artist) + REFERENCES artist(id) + ON DELETE CASCADE; + +ALTER TABLE artist_release + ADD CONSTRAINT artist_release_fk_release + FOREIGN KEY (release) + REFERENCES release(id) + ON DELETE CASCADE; + +COMMIT; diff --git a/mbslave/sql/updates/20250425-mbs-13464.sql b/mbslave/sql/updates/20250425-mbs-13464.sql new file mode 100644 index 0000000..98f596e --- /dev/null +++ b/mbslave/sql/updates/20250425-mbs-13464.sql @@ -0,0 +1,81 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP FUNCTION get_artist_release_rows(integer); + +DROP TABLE artist_release_nonva; +DROP TABLE artist_release_va; +DROP TABLE artist_release; + +CREATE TABLE artist_release ( + is_track_artist BOOLEAN NOT NULL, + artist INTEGER NOT NULL, + first_release_date INTEGER, + catalog_numbers TEXT[], + country_code CHAR(2), + barcode BIGINT, + name VARCHAR COLLATE musicbrainz NOT NULL, + release INTEGER NOT NULL +) PARTITION BY LIST (is_track_artist); + +CREATE TABLE artist_release_nonva + PARTITION OF artist_release FOR VALUES IN (FALSE); + +CREATE TABLE artist_release_va + PARTITION OF artist_release FOR VALUES IN (TRUE); + +CREATE OR REPLACE FUNCTION get_artist_release_rows( + release_id INTEGER +) RETURNS SETOF artist_release AS $$ +BEGIN + -- PostgreSQL 12 generates a vastly more efficient plan when only + -- one release ID is passed. A condition like `r.id = any(...)` + -- can be over 200x slower, even with only one release ID in the + -- array. + RETURN QUERY EXECUTE $SQL$ + SELECT DISTINCT ON (ar.artist, r.id) + ar.is_track_artist, + ar.artist, + integer_date(rfrd.year, rfrd.month, rfrd.day) AS first_release_date, + array_agg( + DISTINCT rl.catalog_number ORDER BY rl.catalog_number + ) FILTER (WHERE rl.catalog_number IS NOT NULL)::TEXT[] AS catalog_numbers, + min(iso.code ORDER BY iso.code)::CHAR(2) AS country_code, + left(regexp_replace( + (CASE r.barcode WHEN '' THEN '0' ELSE r.barcode END), + '[^0-9]+', '', 'g' + ), 18)::BIGINT AS barcode, + r.name, + r.id + FROM ( + SELECT FALSE AS is_track_artist, racn.artist, r.id AS release + FROM release r + JOIN artist_credit_name racn ON racn.artist_credit = r.artist_credit + UNION ALL + SELECT TRUE AS is_track_artist, tacn.artist, m.release + FROM medium m + JOIN track t ON t.medium = m.id + JOIN artist_credit_name tacn ON tacn.artist_credit = t.artist_credit + ) ar + JOIN release r ON r.id = ar.release + LEFT JOIN release_first_release_date rfrd ON rfrd.release = r.id + LEFT JOIN release_label rl ON rl.release = r.id + LEFT JOIN release_country rc ON rc.release = r.id + LEFT JOIN iso_3166_1 iso ON iso.area = rc.country + $SQL$ || (CASE WHEN release_id IS NULL THEN '' ELSE 'WHERE r.id = $1' END) || + $SQL$ + GROUP BY ar.is_track_artist, ar.artist, rfrd.release, r.id + ORDER BY ar.artist, r.id, ar.is_track_artist + $SQL$ + USING release_id; +END; +$$ LANGUAGE plpgsql; + +CREATE INDEX artist_release_nonva_idx_sort ON artist_release_nonva (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); +CREATE INDEX artist_release_va_idx_sort ON artist_release_va (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); + +CREATE UNIQUE INDEX artist_release_nonva_idx_uniq ON artist_release_nonva (release, artist); +CREATE UNIQUE INDEX artist_release_va_idx_uniq ON artist_release_va (release, artist); + +COMMIT; diff --git a/mbslave/sql/updates/20250425-mbs-13966.sql b/mbslave/sql/updates/20250425-mbs-13966.sql new file mode 100644 index 0000000..3a56b05 --- /dev/null +++ b/mbslave/sql/updates/20250425-mbs-13966.sql @@ -0,0 +1,69 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +CREATE OR REPLACE FUNCTION set_release_group_first_release_date(release_group_id INTEGER) +RETURNS VOID AS $$ +BEGIN + UPDATE release_group_meta SET first_release_date_year = first.year, + first_release_date_month = first.month, + first_release_date_day = first.day + FROM ( + SELECT rd.year, rd.month, rd.day + FROM release_group + LEFT JOIN release ON release.release_group = release_group.id + LEFT JOIN release_first_release_date rd ON (rd.release = release.id) + WHERE release_group.id = release_group_id + ORDER BY + rd.year NULLS LAST, + rd.month NULLS LAST, + rd.day NULLS LAST + LIMIT 1 + ) AS first + WHERE id = release_group_id; + INSERT INTO artist_release_group_pending_update VALUES (release_group_id); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE TEMPORARY TABLE tmp_release_first_release_date_2025_q2 ( + release INTEGER NOT NULL PRIMARY KEY, + year SMALLINT, + month SMALLINT, + day SMALLINT +) ON COMMIT DROP; + +INSERT INTO tmp_release_first_release_date_2025_q2 + SELECT * FROM get_release_first_release_date_rows('TRUE'); + +UPDATE release_group_meta SET first_release_date_year = first.year, + first_release_date_month = first.month, + first_release_date_day = first.day + FROM ( + SELECT DISTINCT ON (release_group.id) + release_group.id AS release_group, rd.year, rd.month, rd.day + FROM release_group + LEFT JOIN release ON release.release_group = release_group.id + LEFT JOIN tmp_release_first_release_date_2025_q2 rd ON (rd.release = release.id) + ORDER BY + release_group.id, + rd.year NULLS LAST, + rd.month NULLS LAST, + rd.day NULLS LAST + ) AS first +WHERE id = first.release_group + AND ( + first_release_date_year IS DISTINCT FROM first.year + OR first_release_date_month IS DISTINCT FROM first.month + OR first_release_date_day IS DISTINCT FROM first.day + ); + +-- Mirrors have a `a_upd_release_group_meta_mirror` trigger which inserts +-- updated `release_group_meta` IDs into `artist_release_group_pending_update`, +-- which in turn causes the associated entries in `artist_release_group` +-- to be updated. That should be a no-op here, because the schema 30 upgrade +-- already truncates `artist_release_group` (via dropping and recreating it) +-- before this runs; but clear it anyway to avoid a pointless calculation in +-- the `apply_artist_release_group_pending_updates` function. +TRUNCATE artist_release_group_pending_update; + +COMMIT; diff --git a/mbslave/sql/updates/20260121-mbs-14092-fks.sql b/mbslave/sql/updates/20260121-mbs-14092-fks.sql new file mode 100644 index 0000000..709e947 --- /dev/null +++ b/mbslave/sql/updates/20260121-mbs-14092-fks.sql @@ -0,0 +1,21 @@ +\set ON_ERROR_STOP 1 +BEGIN; + +------------------ +-- constraints -- +------------------ + +ALTER TABLE series_type ADD CONSTRAINT allowed_series_entity_type + CHECK ( + entity_type IN ( + 'artist', + 'event', + 'recording', + 'release', + 'release_group', + 'series', + 'work' + ) + ); + +COMMIT; diff --git a/mbslave/sql/updates/20260121-mbs-14092.sql b/mbslave/sql/updates/20260121-mbs-14092.sql new file mode 100644 index 0000000..d0889d5 --- /dev/null +++ b/mbslave/sql/updates/20260121-mbs-14092.sql @@ -0,0 +1,86 @@ +\set ON_ERROR_STOP 1 +BEGIN; + +\set SERIES_PART_OF_SERIES_ID '1307' + +\set SERIES_PART_OF_SERIES_GID '''8fe04b66-fe39-40ce-a28f-76b816d3f55a''' + +-- id from link_attribute_type where name = 'number' +\set LINK_ATTRIBUTE_TYPE_NUMBER_ID '788' + +----------------------- +-- CREATE NEW VIEWS -- +----------------------- + +CREATE OR REPLACE VIEW series_series AS + SELECT entity0 AS series_part, + entity1 AS series, + lss.id AS relationship, + link_order, + lss.link, + COALESCE(text_value, '') AS text_value + FROM l_series_series lss + JOIN series s ON s.id = lss.entity1 + JOIN link l ON l.id = lss.link + JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '8da75c99-46ff-373c-9d31-276ca8fa8cc3') + LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id) + ORDER BY series, link_order; + +------------------------- +-- INSERT INITIAL DATA -- +------------------------- + +-- Part-of-series rel +-- Already exists in production, but disabled (no description) + +-- We insert the rel where it does not exist (outside prod) +INSERT INTO link_type (id, gid, entity_type0, entity_type1, entity0_cardinality, + entity1_cardinality, name, description, link_phrase, + reverse_link_phrase, long_link_phrase) VALUES + ( + :SERIES_PART_OF_SERIES_ID, + :SERIES_PART_OF_SERIES_GID, + 'series', 'series', 0, 0, 'part of', + '', + 'part of', 'has parts', 'is a part of' + ) ON CONFLICT DO NOTHING; + +-- We add the description to enable the rel +UPDATE link_type + SET description = 'Indicates that the series is part of a series.' + WHERE gid = :SERIES_PART_OF_SERIES_GID; + +-- We insert the attribute and orderable type where they do not exist (outside prod) +INSERT INTO link_attribute_type (id, parent, root, child_order, gid, name, description, last_updated) VALUES + ( + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + NULL, + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + 0, + 'a59c5830-5ec7-38fe-9a21-c7ea54f6650a', + 'number', + 'This attribute indicates the number of an entity in a series.', + '2021-05-10 11:27:11.858659+00' + ) ON CONFLICT DO NOTHING; + +INSERT INTO link_type_attribute_type (link_type, attribute_type, min, max) VALUES + ( + :SERIES_PART_OF_SERIES_ID, + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + 0, + 1 + ) ON CONFLICT DO NOTHING; + +INSERT INTO orderable_link_type (link_type, direction) VALUES + (:SERIES_PART_OF_SERIES_ID, 2) ON CONFLICT DO NOTHING; + +ALTER TABLE series_type DROP CONSTRAINT IF EXISTS allowed_series_entity_type; + +INSERT INTO series_type (id, name, entity_type, parent, child_order, description, gid) VALUES + (16, 'Series series', 'series', NULL, 6, 'A series of series.', generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'series_type16')), + (17, 'Series award', 'series', 16, 0, 'A series of series (such as podcasts or festivals) honoured by the same award.', generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'series_type17')); + +\unset SERIES_PART_OF_SERIES_GID +\unset LINK_ATTRIBUTE_TYPE_NUMBER_ID + +COMMIT; diff --git a/mbslave/sql/updates/20260212-mbs-14252.sql b/mbslave/sql/updates/20260212-mbs-14252.sql new file mode 100644 index 0000000..fe993d5 --- /dev/null +++ b/mbslave/sql/updates/20260212-mbs-14252.sql @@ -0,0 +1,8 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +ALTER TABLE isrc DROP COLUMN source CASCADE; +ALTER TABLE iswc DROP COLUMN source CASCADE; + +COMMIT; diff --git a/mbslave/sql/updates/20260422-mbs-6551-all.sql b/mbslave/sql/updates/20260422-mbs-6551-all.sql new file mode 100644 index 0000000..7f6c640 --- /dev/null +++ b/mbslave/sql/updates/20260422-mbs-6551-all.sql @@ -0,0 +1,19 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +-- Prepare data for the `no_empty_string_catalog_number` constraint +-- in admin/sql/updates/20260422-mbs-6551-master_and_standalone.sql. +UPDATE release_label SET catalog_number = NULL WHERE catalog_number = ''; + +-- This is mainly present for standalone databases. "Remove release label" +-- edits were already manually entered for the few duplicates that existed +-- in production. +DELETE FROM release_label dupe +USING release_label orig +WHERE dupe.release = orig.release + AND dupe.label IS NOT DISTINCT FROM orig.label + AND dupe.catalog_number IS NOT DISTINCT FROM orig.catalog_number + AND dupe.id > orig.id; + +COMMIT; diff --git a/mbslave/sql/updates/20260422-mbs-6551-master_and_standalone.sql b/mbslave/sql/updates/20260422-mbs-6551-master_and_standalone.sql new file mode 100644 index 0000000..b42f1bd --- /dev/null +++ b/mbslave/sql/updates/20260422-mbs-6551-master_and_standalone.sql @@ -0,0 +1,14 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +ALTER TABLE release_label + ADD CONSTRAINT no_empty_string_catalog_number + CHECK (catalog_number != ''); + +ALTER TABLE release_label + ADD CONSTRAINT release_label_uniq + UNIQUE NULLS NOT DISTINCT (release, label, catalog_number) + DEFERRABLE INITIALLY DEFERRED; + +COMMIT; diff --git a/mbslave/sql/updates/20260502-search-756-all.sql b/mbslave/sql/updates/20260502-search-756-all.sql new file mode 100644 index 0000000..bef12d2 --- /dev/null +++ b/mbslave/sql/updates/20260502-search-756-all.sql @@ -0,0 +1,48 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +CREATE OR REPLACE FUNCTION sanitize_editor(e editor) RETURNS editor AS $$ + SELECT ROW( + e.id, + e.name, + 0::INTEGER, + ''::VARCHAR(64), + NULL::VARCHAR(255), + NULL::TEXT, + e.member_since, + e.email_confirm_date, + now()::TIMESTAMP WITH TIME ZONE, + e.last_updated, + NULL::DATE, + NULL::INTEGER, + NULL::INTEGER, + '{CLEARTEXT}mb'::VARCHAR(128), + md5(e.name || ':musicbrainz.org:mb')::CHAR(32), + e.deleted + )::editor +$$ LANGUAGE sql STABLE PARALLEL SAFE; + +-- Adding `STRICT` on `sanitize_editor` would prevent inlining/optimization +-- when called via the `editor_sanitized` view. +CREATE OR REPLACE FUNCTION sanitize_editor_strict(e editor) RETURNS editor AS $$ + SELECT sanitize_editor(e) +$$ LANGUAGE sql STABLE STRICT PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION sanitize_dbmirror2_editor_data() RETURNS trigger AS $$ +BEGIN + NEW.olddata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.olddata))); + NEW.newdata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.newdata))); + IF NEW.op = 'u' AND NEW.olddata::JSONB = NEW.newdata::JSONB THEN + -- Only sanitized columns have changed. No need to log the update. + RETURN NULL; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE VIEW editor_sanitized AS + SELECT (sanitize_editor(editor)).* + FROM editor; + +COMMIT; diff --git a/mbslave/sql/updates/20260502-search-756-master_only.sql b/mbslave/sql/updates/20260502-search-756-master_only.sql new file mode 100644 index 0000000..7fdf35b --- /dev/null +++ b/mbslave/sql/updates/20260502-search-756-master_only.sql @@ -0,0 +1,118 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER sanitize_dbmirror2_editor_data + BEFORE INSERT ON dbmirror2.pending_data + FOR EACH ROW WHEN (NEW.tablename = 'musicbrainz.editor') + EXECUTE PROCEDURE sanitize_dbmirror2_editor_data(); + +COMMIT; diff --git a/mbslave/sql/updates/20260507-search-756-mirror_only.sql b/mbslave/sql/updates/20260507-search-756-mirror_only.sql new file mode 100644 index 0000000..2bfb837 --- /dev/null +++ b/mbslave/sql/updates/20260507-search-756-mirror_only.sql @@ -0,0 +1,113 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +COMMIT; diff --git a/mbslave/sql/updates/20260507-search-756-standalone_only.sql b/mbslave/sql/updates/20260507-search-756-standalone_only.sql new file mode 100644 index 0000000..db5ea48 --- /dev/null +++ b/mbslave/sql/updates/20260507-search-756-standalone_only.sql @@ -0,0 +1,155 @@ +\set ON_ERROR_STOP 1 + +BEGIN; + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_keys CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_ts CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_data CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TABLE dbmirror2.pending_keys ( + tablename TEXT, + keys TEXT[] NOT NULL +); + +ALTER TABLE dbmirror2.pending_keys + ADD CONSTRAINT pending_keys_pkey + PRIMARY KEY (tablename); + +CREATE TABLE dbmirror2.pending_ts ( + xid BIGINT, + ts TIMESTAMP WITH TIME ZONE NOT NULL +); + +ALTER TABLE dbmirror2.pending_ts + ADD CONSTRAINT pending_ts_pkey + PRIMARY KEY (xid); + +CREATE TABLE dbmirror2.pending_data ( + seqid BIGSERIAL, + tablename TEXT NOT NULL CONSTRAINT tablename_exists CHECK (to_regclass(tablename) IS NOT NULL), + op "char" NOT NULL CONSTRAINT op_in_diu CHECK (op IN ('d', 'i', 'u')), + xid BIGINT NOT NULL, + olddata JSON CONSTRAINT olddata_is_null_for_inserts CHECK ((olddata IS NULL) = (op = 'i')), + newdata JSON CONSTRAINT newdata_is_null_for_deletes CHECK ((newdata IS NULL) = (op = 'd')), + oldctid TID, + trgdepth INTEGER +); + +ALTER TABLE dbmirror2.pending_data + ADD CONSTRAINT pending_data_pkey + PRIMARY KEY (seqid); + +CREATE INDEX pending_data_idx_xid_seqid + ON dbmirror2.pending_data (xid, seqid); + +CREATE INDEX pending_data_idx_oldctid_xid + ON dbmirror2.pending_data (oldctid, xid); + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/30.all.sql b/mbslave/sql/updates/schema-change/30.all.sql new file mode 100644 index 0000000..c9d8553 --- /dev/null +++ b/mbslave/sql/updates/schema-change/30.all.sql @@ -0,0 +1,420 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20241017-mbs-9253-13464.sql +-- 20250408-mbs-13322.sql +-- 20250425-mbs-13464.sql +-- 20241125-mbs-13832.sql +-- 20250320-mbs-13768.sql +-- 20250408-mbs-13964-all.sql +-- 20250425-mbs-13966.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20241017-mbs-9253-13464.sql'; + + +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type ON release_group_primary_type; +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type ON release_group_secondary_type; + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_primary_type; +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates ON release_group_secondary_type; + +DROP FUNCTION get_artist_release_group_rows(integer); + +DROP INDEX artist_release_group_nonva_idx_sort; +DROP INDEX artist_release_group_va_idx_sort; + +DROP TABLE artist_release_group_nonva; +DROP TABLE artist_release_group_va; +DROP TABLE artist_release_group; + +CREATE TABLE artist_release_group ( + -- See comment for `artist_release.is_track_artist`. + is_track_artist BOOLEAN NOT NULL, + artist INTEGER NOT NULL, -- references artist.id, CASCADE + unofficial BOOLEAN NOT NULL, + primary_type_child_order SMALLINT, + primary_type SMALLINT, + secondary_type_child_orders SMALLINT[], + secondary_types SMALLINT[], + first_release_date INTEGER, + name VARCHAR COLLATE musicbrainz NOT NULL, + release_group INTEGER NOT NULL -- references release_group.id, CASCADE +) PARTITION BY LIST (is_track_artist); + +CREATE TABLE artist_release_group_nonva + PARTITION OF artist_release_group FOR VALUES IN (FALSE); + +CREATE TABLE artist_release_group_va + PARTITION OF artist_release_group FOR VALUES IN (TRUE); + +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), + rgpt.child_order::SMALLINT, + rg.type::SMALLINT, + array_agg( + DISTINCT rgst.child_order ORDER BY rgst.child_order) + FILTER (WHERE rgst.child_order IS NOT NULL + )::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 + ), + rg.name, + 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_primary_type rgpt ON rgpt.id = rg.type + LEFT JOIN release_group_secondary_type_join st ON st.release_group = rg.id + LEFT JOIN release_group_secondary_type rgst ON rgst.id = st.secondary_type + $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, rgpt.child_order + ORDER BY a_rg.artist, rg.id, a_rg.is_track_artist + $SQL$ + USING release_group_id; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_primary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT id FROM release_group + WHERE release_group.type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION a_upd_release_group_secondary_type_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF (NEW.child_order IS DISTINCT FROM OLD.child_order) + THEN + INSERT INTO artist_release_group_pending_update ( + SELECT release_group + FROM release_group_secondary_type_join + WHERE secondary_type = OLD.id + ); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE INDEX artist_release_group_nonva_idx_sort ON artist_release_group_nonva (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); +CREATE INDEX artist_release_group_va_idx_sort ON artist_release_group_va (artist, unofficial, primary_type_child_order NULLS FIRST, primary_type NULLS FIRST, secondary_type_child_orders NULLS FIRST, secondary_types NULLS FIRST, first_release_date NULLS LAST, name, release_group); + +CREATE UNIQUE INDEX artist_release_group_nonva_idx_uniq ON artist_release_group_nonva (release_group, artist); +CREATE UNIQUE INDEX artist_release_group_va_idx_uniq ON artist_release_group_va (release_group, artist); + +-------------------------------------------------------------------------------- +SELECT '20250408-mbs-13322.sql'; + + +CREATE OR REPLACE FUNCTION delete_unused_url(ids INTEGER[]) +RETURNS VOID AS $$ +BEGIN + DELETE FROM url_gid_redirect WHERE new_id = any(ids); + DELETE FROM url WHERE id = any(ids); +EXCEPTION + WHEN foreign_key_violation THEN RETURN; +END; +$$ LANGUAGE 'plpgsql'; + +-------------------------------------------------------------------------------- +SELECT '20250425-mbs-13464.sql'; + + +DROP FUNCTION get_artist_release_rows(integer); + +DROP TABLE artist_release_nonva; +DROP TABLE artist_release_va; +DROP TABLE artist_release; + +CREATE TABLE artist_release ( + is_track_artist BOOLEAN NOT NULL, + artist INTEGER NOT NULL, + first_release_date INTEGER, + catalog_numbers TEXT[], + country_code CHAR(2), + barcode BIGINT, + name VARCHAR COLLATE musicbrainz NOT NULL, + release INTEGER NOT NULL +) PARTITION BY LIST (is_track_artist); + +CREATE TABLE artist_release_nonva + PARTITION OF artist_release FOR VALUES IN (FALSE); + +CREATE TABLE artist_release_va + PARTITION OF artist_release FOR VALUES IN (TRUE); + +CREATE OR REPLACE FUNCTION get_artist_release_rows( + release_id INTEGER +) RETURNS SETOF artist_release AS $$ +BEGIN + -- PostgreSQL 12 generates a vastly more efficient plan when only + -- one release ID is passed. A condition like `r.id = any(...)` + -- can be over 200x slower, even with only one release ID in the + -- array. + RETURN QUERY EXECUTE $SQL$ + SELECT DISTINCT ON (ar.artist, r.id) + ar.is_track_artist, + ar.artist, + integer_date(rfrd.year, rfrd.month, rfrd.day) AS first_release_date, + array_agg( + DISTINCT rl.catalog_number ORDER BY rl.catalog_number + ) FILTER (WHERE rl.catalog_number IS NOT NULL)::TEXT[] AS catalog_numbers, + min(iso.code ORDER BY iso.code)::CHAR(2) AS country_code, + left(regexp_replace( + (CASE r.barcode WHEN '' THEN '0' ELSE r.barcode END), + '[^0-9]+', '', 'g' + ), 18)::BIGINT AS barcode, + r.name, + r.id + FROM ( + SELECT FALSE AS is_track_artist, racn.artist, r.id AS release + FROM release r + JOIN artist_credit_name racn ON racn.artist_credit = r.artist_credit + UNION ALL + SELECT TRUE AS is_track_artist, tacn.artist, m.release + FROM medium m + JOIN track t ON t.medium = m.id + JOIN artist_credit_name tacn ON tacn.artist_credit = t.artist_credit + ) ar + JOIN release r ON r.id = ar.release + LEFT JOIN release_first_release_date rfrd ON rfrd.release = r.id + LEFT JOIN release_label rl ON rl.release = r.id + LEFT JOIN release_country rc ON rc.release = r.id + LEFT JOIN iso_3166_1 iso ON iso.area = rc.country + $SQL$ || (CASE WHEN release_id IS NULL THEN '' ELSE 'WHERE r.id = $1' END) || + $SQL$ + GROUP BY ar.is_track_artist, ar.artist, rfrd.release, r.id + ORDER BY ar.artist, r.id, ar.is_track_artist + $SQL$ + USING release_id; +END; +$$ LANGUAGE plpgsql; + +CREATE INDEX artist_release_nonva_idx_sort ON artist_release_nonva (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); +CREATE INDEX artist_release_va_idx_sort ON artist_release_va (artist, first_release_date NULLS LAST, catalog_numbers NULLS LAST, country_code NULLS LAST, barcode NULLS LAST, name, release); + +CREATE UNIQUE INDEX artist_release_nonva_idx_uniq ON artist_release_nonva (release, artist); +CREATE UNIQUE INDEX artist_release_va_idx_uniq ON artist_release_va (release, artist); + +-------------------------------------------------------------------------------- +SELECT '20241125-mbs-13832.sql'; + + +-- MBS-14014 +DROP VIEW IF EXISTS cover_art_archive.index_listing; + +-- CAA view +CREATE VIEW cover_art_archive.index_listing AS +SELECT cover_art.*, + (edit.close_time IS NOT NULL) AS approved, + coalesce(cover_art.id = (SELECT id FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.cover_art ca_front USING (id) + WHERE ca_front.release = cover_art.release + AND type_id = 1 + ORDER BY ca_front.ordering + LIMIT 1), FALSE) AS is_front, + coalesce(cover_art.id = (SELECT id FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.cover_art ca_front USING (id) + WHERE ca_front.release = cover_art.release + AND type_id = 2 + ORDER BY ca_front.ordering + LIMIT 1), FALSE) AS is_back, + array(SELECT art_type.name + FROM cover_art_archive.cover_art_type + JOIN cover_art_archive.art_type ON cover_art_type.type_id = art_type.id + WHERE cover_art_type.id = cover_art.id) AS types +FROM cover_art_archive.cover_art +LEFT JOIN musicbrainz.edit ON edit.id = cover_art.edit; + +-- EAA view +CREATE OR REPLACE VIEW event_art_archive.index_listing AS +SELECT event_art.*, + (edit.close_time IS NOT NULL) AS approved, + coalesce(event_art.id = (SELECT id FROM event_art_archive.event_art_type + JOIN event_art_archive.event_art ea_front USING (id) + WHERE ea_front.event = event_art.event + AND type_id = 1 + ORDER BY ea_front.ordering + LIMIT 1), FALSE) AS is_front, + array(SELECT art_type.name + FROM event_art_archive.event_art_type + JOIN event_art_archive.art_type ON event_art_type.type_id = art_type.id + WHERE event_art_type.id = event_art.id) AS types +FROM event_art_archive.event_art +LEFT JOIN musicbrainz.edit ON edit.id = event_art.edit; + +-------------------------------------------------------------------------------- +SELECT '20250320-mbs-13768.sql'; + + +-- Medium GID + +-- Creating column +ALTER TABLE medium ADD COLUMN gid uuid; + +-- Generating GIDs +UPDATE medium SET gid = + generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'medium' || id); + +-- Adding NOT NULL constraint +ALTER TABLE medium ALTER COLUMN gid SET NOT NULL; + +-- Creating index +CREATE UNIQUE INDEX medium_idx_gid ON medium (gid); + +-- Medium GID redirect +CREATE TABLE medium_gid_redirect ( -- replicate (verbose) + gid UUID NOT NULL, -- PK + new_id INTEGER NOT NULL, -- references medium.id + created TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE medium_gid_redirect ADD CONSTRAINT medium_gid_redirect_pkey PRIMARY KEY (gid); + +CREATE INDEX medium_gid_redirect_idx_new_id ON medium_gid_redirect (new_id); + +-------------------------------------------------------------------------------- +SELECT '20250408-mbs-13964-all.sql'; + + +CREATE OR REPLACE FUNCTION set_mediums_recordings_first_release_dates(medium_ids INTEGER[]) +RETURNS VOID AS $$ +BEGIN + PERFORM set_recordings_first_release_dates(( + SELECT array_agg(recording) + FROM track + WHERE track.medium = any(medium_ids) + )); + RETURN; +END; +$$ LANGUAGE 'plpgsql' STRICT; + +CREATE OR REPLACE FUNCTION a_upd_medium_mirror() +RETURNS trigger AS $$ +BEGIN + -- DO NOT modify any replicated tables in this function; it's used + -- by a trigger on mirrors. + IF NEW.release IS DISTINCT FROM OLD.release THEN + PERFORM set_mediums_recordings_first_release_dates(ARRAY[OLD.id]); + END IF; + RETURN NULL; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE TRIGGER a_upd_medium AFTER UPDATE ON medium + FOR EACH ROW EXECUTE PROCEDURE a_upd_medium_mirror(); + +TRUNCATE recording_first_release_date; + +-------------------------------------------------------------------------------- +SELECT '20250425-mbs-13966.sql'; + + +CREATE OR REPLACE FUNCTION set_release_group_first_release_date(release_group_id INTEGER) +RETURNS VOID AS $$ +BEGIN + UPDATE release_group_meta SET first_release_date_year = first.year, + first_release_date_month = first.month, + first_release_date_day = first.day + FROM ( + SELECT rd.year, rd.month, rd.day + FROM release_group + LEFT JOIN release ON release.release_group = release_group.id + LEFT JOIN release_first_release_date rd ON (rd.release = release.id) + WHERE release_group.id = release_group_id + ORDER BY + rd.year NULLS LAST, + rd.month NULLS LAST, + rd.day NULLS LAST + LIMIT 1 + ) AS first + WHERE id = release_group_id; + INSERT INTO artist_release_group_pending_update VALUES (release_group_id); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE TEMPORARY TABLE tmp_release_first_release_date_2025_q2 ( + release INTEGER NOT NULL PRIMARY KEY, + year SMALLINT, + month SMALLINT, + day SMALLINT +) ON COMMIT DROP; + +INSERT INTO tmp_release_first_release_date_2025_q2 + SELECT * FROM get_release_first_release_date_rows('TRUE'); + +UPDATE release_group_meta SET first_release_date_year = first.year, + first_release_date_month = first.month, + first_release_date_day = first.day + FROM ( + SELECT DISTINCT ON (release_group.id) + release_group.id AS release_group, rd.year, rd.month, rd.day + FROM release_group + LEFT JOIN release ON release.release_group = release_group.id + LEFT JOIN tmp_release_first_release_date_2025_q2 rd ON (rd.release = release.id) + ORDER BY + release_group.id, + rd.year NULLS LAST, + rd.month NULLS LAST, + rd.day NULLS LAST + ) AS first +WHERE id = first.release_group + AND ( + first_release_date_year IS DISTINCT FROM first.year + OR first_release_date_month IS DISTINCT FROM first.month + OR first_release_date_day IS DISTINCT FROM first.day + ); + +-- Mirrors have a `a_upd_release_group_meta_mirror` trigger which inserts +-- updated `release_group_meta` IDs into `artist_release_group_pending_update`, +-- which in turn causes the associated entries in `artist_release_group` +-- to be updated. That should be a no-op here, because the schema 30 upgrade +-- already truncates `artist_release_group` (via dropping and recreating it) +-- before this runs; but clear it anyway to avoid a pointless calculation in +-- the `apply_artist_release_group_pending_updates` function. +TRUNCATE artist_release_group_pending_update; + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/30.master_and_standalone.sql b/mbslave/sql/updates/schema-change/30.master_and_standalone.sql new file mode 100644 index 0000000..d92cedb --- /dev/null +++ b/mbslave/sql/updates/schema-change/30.master_and_standalone.sql @@ -0,0 +1,74 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20241017-mbs-9253-master_and_standalone.sql +-- 20250425-mbs-13464-master_and_standalone.sql +-- 20250320-mbs-13768-fks.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20241017-mbs-9253-master_and_standalone.sql'; + +SET search_path = musicbrainz; + + +ALTER TABLE artist_release_group + ADD CONSTRAINT artist_release_group_fk_artist + FOREIGN KEY (artist) + REFERENCES artist(id) + ON DELETE CASCADE; + +ALTER TABLE artist_release_group + ADD CONSTRAINT artist_release_group_fk_release_group + FOREIGN KEY (release_group) + REFERENCES release_group(id) + ON DELETE CASCADE; + +CREATE TRIGGER a_upd_release_group_primary_type AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +CREATE TRIGGER a_upd_release_group_secondary_type AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +-------------------------------------------------------------------------------- +SELECT '20250425-mbs-13464-master_and_standalone.sql'; + +SET search_path = musicbrainz; + + +ALTER TABLE artist_release + ADD CONSTRAINT artist_release_fk_artist + FOREIGN KEY (artist) + REFERENCES artist(id) + ON DELETE CASCADE; + +ALTER TABLE artist_release + ADD CONSTRAINT artist_release_fk_release + FOREIGN KEY (release) + REFERENCES release(id) + ON DELETE CASCADE; + +-------------------------------------------------------------------------------- +SELECT '20250320-mbs-13768-fks.sql'; + +SET search_path = musicbrainz; + + +ALTER TABLE medium_gid_redirect + ADD CONSTRAINT medium_gid_redirect_fk_new_id + FOREIGN KEY (new_id) + REFERENCES medium(id); + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/30.mirror_only.sql b/mbslave/sql/updates/schema-change/30.mirror_only.sql new file mode 100644 index 0000000..3cf3038 --- /dev/null +++ b/mbslave/sql/updates/schema-change/30.mirror_only.sql @@ -0,0 +1,37 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20241017-mbs-9253-mirror_only.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20241017-mbs-9253-mirror_only.sql'; + + +DROP TRIGGER IF EXISTS a_upd_release_group_primary_type_mirror ON release_group_primary_type; + +CREATE TRIGGER a_upd_release_group_primary_type_mirror AFTER UPDATE ON release_group_primary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_primary_type_mirror(); + +DROP TRIGGER IF EXISTS a_upd_release_group_secondary_type_mirror ON release_group_secondary_type; + +CREATE TRIGGER a_upd_release_group_secondary_type_mirror AFTER UPDATE ON release_group_secondary_type + FOR EACH ROW EXECUTE PROCEDURE a_upd_release_group_secondary_type_mirror(); + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_primary_type; + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_primary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +DROP TRIGGER IF EXISTS apply_artist_release_group_pending_updates_mirror ON release_group_secondary_type; + +CREATE CONSTRAINT TRIGGER apply_artist_release_group_pending_updates_mirror + AFTER UPDATE ON release_group_secondary_type DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + WHEN (OLD.child_order IS DISTINCT FROM NEW.child_order) + EXECUTE PROCEDURE apply_artist_release_group_pending_updates(); + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/31.all.sql b/mbslave/sql/updates/schema-change/31.all.sql new file mode 100644 index 0000000..2de13ee --- /dev/null +++ b/mbslave/sql/updates/schema-change/31.all.sql @@ -0,0 +1,167 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20260121-mbs-14092.sql +-- 20260212-mbs-14252.sql +-- 20260422-mbs-6551-all.sql +-- 20260502-search-756-all.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20260121-mbs-14092.sql'; + +\set SERIES_PART_OF_SERIES_ID '1307' + +\set SERIES_PART_OF_SERIES_GID '''8fe04b66-fe39-40ce-a28f-76b816d3f55a''' + +-- id from link_attribute_type where name = 'number' +\set LINK_ATTRIBUTE_TYPE_NUMBER_ID '788' + +----------------------- +-- CREATE NEW VIEWS -- +----------------------- + +CREATE OR REPLACE VIEW series_series AS + SELECT entity0 AS series_part, + entity1 AS series, + lss.id AS relationship, + link_order, + lss.link, + COALESCE(text_value, '') AS text_value + FROM l_series_series lss + JOIN series s ON s.id = lss.entity1 + JOIN link l ON l.id = lss.link + JOIN link_type lt ON (lt.id = l.link_type AND lt.gid = '8da75c99-46ff-373c-9d31-276ca8fa8cc3') + LEFT OUTER JOIN link_attribute_text_value latv ON (latv.attribute_type = 788 AND latv.link = l.id) + ORDER BY series, link_order; + +------------------------- +-- INSERT INITIAL DATA -- +------------------------- + +-- Part-of-series rel +-- Already exists in production, but disabled (no description) + +-- We insert the rel where it does not exist (outside prod) +INSERT INTO link_type (id, gid, entity_type0, entity_type1, entity0_cardinality, + entity1_cardinality, name, description, link_phrase, + reverse_link_phrase, long_link_phrase) VALUES + ( + :SERIES_PART_OF_SERIES_ID, + :SERIES_PART_OF_SERIES_GID, + 'series', 'series', 0, 0, 'part of', + '', + 'part of', 'has parts', 'is a part of' + ) ON CONFLICT DO NOTHING; + +-- We add the description to enable the rel +UPDATE link_type + SET description = 'Indicates that the series is part of a series.' + WHERE gid = :SERIES_PART_OF_SERIES_GID; + +-- We insert the attribute and orderable type where they do not exist (outside prod) +INSERT INTO link_attribute_type (id, parent, root, child_order, gid, name, description, last_updated) VALUES + ( + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + NULL, + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + 0, + 'a59c5830-5ec7-38fe-9a21-c7ea54f6650a', + 'number', + 'This attribute indicates the number of an entity in a series.', + '2021-05-10 11:27:11.858659+00' + ) ON CONFLICT DO NOTHING; + +INSERT INTO link_type_attribute_type (link_type, attribute_type, min, max) VALUES + ( + :SERIES_PART_OF_SERIES_ID, + :LINK_ATTRIBUTE_TYPE_NUMBER_ID, + 0, + 1 + ) ON CONFLICT DO NOTHING; + +INSERT INTO orderable_link_type (link_type, direction) VALUES + (:SERIES_PART_OF_SERIES_ID, 2) ON CONFLICT DO NOTHING; + +ALTER TABLE series_type DROP CONSTRAINT IF EXISTS allowed_series_entity_type; + +INSERT INTO series_type (id, name, entity_type, parent, child_order, description, gid) VALUES + (16, 'Series series', 'series', NULL, 6, 'A series of series.', generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'series_type16')), + (17, 'Series award', 'series', 16, 0, 'A series of series (such as podcasts or festivals) honoured by the same award.', generate_uuid_v3('6ba7b8119dad11d180b400c04fd430c8', 'series_type17')); + +\unset SERIES_PART_OF_SERIES_GID +\unset LINK_ATTRIBUTE_TYPE_NUMBER_ID + +-------------------------------------------------------------------------------- +SELECT '20260212-mbs-14252.sql'; + + +ALTER TABLE isrc DROP COLUMN source CASCADE; +ALTER TABLE iswc DROP COLUMN source CASCADE; + +-------------------------------------------------------------------------------- +SELECT '20260422-mbs-6551-all.sql'; + + +-- Prepare data for the `no_empty_string_catalog_number` constraint +-- in admin/sql/updates/20260422-mbs-6551-master_and_standalone.sql. +UPDATE release_label SET catalog_number = NULL WHERE catalog_number = ''; + +-- This is mainly present for standalone databases. "Remove release label" +-- edits were already manually entered for the few duplicates that existed +-- in production. +DELETE FROM release_label dupe +USING release_label orig +WHERE dupe.release = orig.release + AND dupe.label IS NOT DISTINCT FROM orig.label + AND dupe.catalog_number IS NOT DISTINCT FROM orig.catalog_number + AND dupe.id > orig.id; + +-------------------------------------------------------------------------------- +SELECT '20260502-search-756-all.sql'; + + +CREATE OR REPLACE FUNCTION sanitize_editor(e editor) RETURNS editor AS $$ + SELECT ROW( + e.id, + e.name, + 0::INTEGER, + ''::VARCHAR(64), + NULL::VARCHAR(255), + NULL::TEXT, + e.member_since, + e.email_confirm_date, + now()::TIMESTAMP WITH TIME ZONE, + e.last_updated, + NULL::DATE, + NULL::INTEGER, + NULL::INTEGER, + '{CLEARTEXT}mb'::VARCHAR(128), + md5(e.name || ':musicbrainz.org:mb')::CHAR(32), + e.deleted + )::editor +$$ LANGUAGE sql STABLE PARALLEL SAFE; + +-- Adding `STRICT` on `sanitize_editor` would prevent inlining/optimization +-- when called via the `editor_sanitized` view. +CREATE OR REPLACE FUNCTION sanitize_editor_strict(e editor) RETURNS editor AS $$ + SELECT sanitize_editor(e) +$$ LANGUAGE sql STABLE STRICT PARALLEL SAFE; + +CREATE OR REPLACE FUNCTION sanitize_dbmirror2_editor_data() RETURNS trigger AS $$ +BEGIN + NEW.olddata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.olddata))); + NEW.newdata = row_to_json(sanitize_editor_strict(json_populate_record(NULL::editor, NEW.newdata))); + IF NEW.op = 'u' AND NEW.olddata::JSONB = NEW.newdata::JSONB THEN + -- Only sanitized columns have changed. No need to log the update. + RETURN NULL; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE VIEW editor_sanitized AS + SELECT (sanitize_editor(editor)).* + FROM editor; + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/31.master_and_standalone.sql b/mbslave/sql/updates/schema-change/31.master_and_standalone.sql new file mode 100644 index 0000000..c3cd5ff --- /dev/null +++ b/mbslave/sql/updates/schema-change/31.master_and_standalone.sql @@ -0,0 +1,41 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20260121-mbs-14092-fks.sql +-- 20260422-mbs-6551-master_and_standalone.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20260121-mbs-14092-fks.sql'; + +------------------ +-- constraints -- +------------------ + +ALTER TABLE series_type ADD CONSTRAINT allowed_series_entity_type + CHECK ( + entity_type IN ( + 'artist', + 'event', + 'recording', + 'release', + 'release_group', + 'series', + 'work' + ) + ); + +-------------------------------------------------------------------------------- +SELECT '20260422-mbs-6551-master_and_standalone.sql'; + + +ALTER TABLE release_label + ADD CONSTRAINT no_empty_string_catalog_number + CHECK (catalog_number != ''); + +ALTER TABLE release_label + ADD CONSTRAINT release_label_uniq + UNIQUE NULLS NOT DISTINCT (release, label, catalog_number) + DEFERRABLE INITIALLY DEFERRED; + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/31.master_only.sql b/mbslave/sql/updates/schema-change/31.master_only.sql new file mode 100644 index 0000000..d4b7202 --- /dev/null +++ b/mbslave/sql/updates/schema-change/31.master_only.sql @@ -0,0 +1,124 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20260502-search-756-master_only.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20260502-search-756-master_only.sql'; + + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER sanitize_dbmirror2_editor_data + BEFORE INSERT ON dbmirror2.pending_data + FOR EACH ROW WHEN (NEW.tablename = 'musicbrainz.editor') + EXECUTE PROCEDURE sanitize_dbmirror2_editor_data(); + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/31.mirror_only.sql b/mbslave/sql/updates/schema-change/31.mirror_only.sql new file mode 100644 index 0000000..d250a98 --- /dev/null +++ b/mbslave/sql/updates/schema-change/31.mirror_only.sql @@ -0,0 +1,119 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20260507-search-756-mirror_only.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20260507-search-756-mirror_only.sql'; + + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +COMMIT; diff --git a/mbslave/sql/updates/schema-change/31.standalone_only.sql b/mbslave/sql/updates/schema-change/31.standalone_only.sql new file mode 100644 index 0000000..f54b62b --- /dev/null +++ b/mbslave/sql/updates/schema-change/31.standalone_only.sql @@ -0,0 +1,161 @@ +-- Generated by CompileSchemaScripts.pl from: +-- 20260507-search-756-standalone_only.sql +\set ON_ERROR_STOP 1 +BEGIN; +SET search_path = musicbrainz, public; +SET LOCAL statement_timeout = 0; +-------------------------------------------------------------------------------- +SELECT '20260507-search-756-standalone_only.sql'; + + +DROP MATERIALIZED VIEW IF EXISTS dbmirror2.column_info CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_keys CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_ts CASCADE; +DROP TABLE IF EXISTS dbmirror2.pending_data CASCADE; +DROP EVENT TRIGGER IF EXISTS refresh_column_info; +DROP FUNCTION IF EXISTS dbmirror2.refresh_column_info(); + +CREATE OR REPLACE FUNCTION dbmirror2.recordchange() +RETURNS trigger AS $$ +DECLARE + -- prefixed with an underscore to disambiguate it from the column names + -- pending_data.tablename and pending_keys.tablename + _tablename TEXT; + keys TEXT[]; + -- prefixed with 'x' to avoid conflict with column name in queries + xoldctid TID; + nextseqid BIGINT; + -- out-of-order seqid + oooseqid BIGINT; + oootrgdepth INTEGER; + pdcursor NO SCROLL CURSOR (oooseqid INTEGER) FOR + SELECT seqid + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND seqid >= oooseqid + ORDER BY seqid DESC + FOR UPDATE; +BEGIN + _tablename := ( + quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) + ); + + nextseqid := nextval( + pg_get_serial_sequence('dbmirror2.pending_data', 'seqid') + ); + + INSERT INTO dbmirror2.pending_ts (xid, ts) + VALUES (txid_current(), transaction_timestamp()) + ON CONFLICT DO NOTHING; + + IF TG_OP != 'INSERT' THEN + xoldctid := OLD.ctid; + END IF; + + IF TG_OP != 'DELETE' THEN + -- Detect out-of-order operations caused by cascading triggers. + -- + -- When row-level AFTER triggers are cascaded, the innermost trigger + -- runs first. This means we may potentially see an UPDATE or DELETE + -- of a row version that hasn't been added yet. + -- + -- We detect this by storing OLD.ctid for every operation. (The ctid + -- is a tuple describing the physical location of the row version. We + -- only need this to be stable for the lifetime of the current + -- transaction.) We then check if there's a previous operation whose + -- OLD ctid equals our NEW ctid; these are then known to be out-of- + -- order. This previous operation's seqid is assigned to `oooseqid` + -- ("out-of-order seqid"). + -- + -- The order is fixed by shifting the sequence IDs from the current + -- transaction until they're corrected. The current-last operation + -- assumes `nextseqid`, the second-to-last assumes the seqid of the + -- last, and so on until `oooseqid` is unused. We then insert our new + -- operation with `oooseqid`. + -- + -- Since we're never modifying `pending_data` rows inserted by other + -- transactions, this shifting should be safe. + SELECT seqid, trgdepth INTO oooseqid, oootrgdepth + FROM dbmirror2.pending_data + WHERE xid = txid_current() + AND tablename = _tablename + AND oldctid = NEW.ctid; + + IF FOUND THEN + IF oootrgdepth IS NOT NULL AND oootrgdepth <= pg_trigger_depth() THEN + -- This should never happen! Cascading triggers are the only + -- known way for operations to arrive out of order. This + -- warning must be investigated if it's ever logged. + RAISE WARNING 'oootrgdepth (%) <= pg_trigger_depth() (%) (% ON %, OLD: %, NEW: %)', + oootrgdepth, pg_trigger_depth(), TG_OP, _tablename, OLD, NEW; + END IF; + + FOR pdrecord IN pdcursor (oooseqid := oooseqid) LOOP + UPDATE dbmirror2.pending_data + SET seqid = nextseqid + WHERE CURRENT OF pdcursor; + + nextseqid := pdrecord.seqid; + END LOOP; + + ASSERT (nextseqid = oooseqid); + END IF; + END IF; + + INSERT INTO dbmirror2.pending_data + (seqid, tablename, op, xid, olddata, newdata, oldctid, trgdepth) + VALUES ( + nextseqid, + _tablename, + lower(left(TG_OP, 1)), + txid_current(), + row_to_json(OLD), + row_to_json(NEW), + xoldctid, + pg_trigger_depth() + ); + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TABLE dbmirror2.pending_keys ( + tablename TEXT, + keys TEXT[] NOT NULL +); + +ALTER TABLE dbmirror2.pending_keys + ADD CONSTRAINT pending_keys_pkey + PRIMARY KEY (tablename); + +CREATE TABLE dbmirror2.pending_ts ( + xid BIGINT, + ts TIMESTAMP WITH TIME ZONE NOT NULL +); + +ALTER TABLE dbmirror2.pending_ts + ADD CONSTRAINT pending_ts_pkey + PRIMARY KEY (xid); + +CREATE TABLE dbmirror2.pending_data ( + seqid BIGSERIAL, + tablename TEXT NOT NULL CONSTRAINT tablename_exists CHECK (to_regclass(tablename) IS NOT NULL), + op "char" NOT NULL CONSTRAINT op_in_diu CHECK (op IN ('d', 'i', 'u')), + xid BIGINT NOT NULL, + olddata JSON CONSTRAINT olddata_is_null_for_inserts CHECK ((olddata IS NULL) = (op = 'i')), + newdata JSON CONSTRAINT newdata_is_null_for_deletes CHECK ((newdata IS NULL) = (op = 'd')), + oldctid TID, + trgdepth INTEGER +); + +ALTER TABLE dbmirror2.pending_data + ADD CONSTRAINT pending_data_pkey + PRIMARY KEY (seqid); + +CREATE INDEX pending_data_idx_xid_seqid + ON dbmirror2.pending_data (xid, seqid); + +CREATE INDEX pending_data_idx_oldctid_xid + ON dbmirror2.pending_data (oldctid, xid); + +COMMIT;