diff --git a/.github/workflows/build-mbslave-container-image.yml b/.github/workflows/build-mbslave-container-image.yml
index 21347ed..b5e1f44 100644
--- a/.github/workflows/build-mbslave-container-image.yml
+++ b/.github/workflows/build-mbslave-container-image.yml
@@ -4,15 +4,17 @@ on:
push:
branches:
- main
+ - dev
tags:
- v*
pull_request:
branches:
- main
+ - dev
env:
REGISTRY: ghcr.io
- IMAGE_NAME: acoustid/mbslave
+ IMAGE_NAME: ${{ github.repository_owner }}/mbslave
jobs:
@@ -23,27 +25,38 @@ jobs:
packages: write
steps:
- - uses: actions/checkout@v2
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
- uses: docker/metadata-action@v3
+ uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=sha
- name: Log in to the Container registry
if: github.event_name != 'pull_request'
- uses: docker/login-action@v1
+ uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
- uses: docker/build-push-action@v2
+ uses: docker/build-push-action@v5
with:
file: Dockerfile.mbslave
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9ee1325..258e77a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,29 +7,54 @@ on:
jobs:
test:
runs-on: ubuntu-latest
+ container: python:${{ matrix.python_version }}
strategy:
matrix:
python_version: [ '3.8', '3.9', '3.10' ]
+ services:
+ postgresql:
+ image: postgres
+ env:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: notapassword
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
steps:
- - uses: actions/checkout@v2
- - name: Install Python
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python_version }}
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ run: |
+ apt-get update
+ apt-get install -y postgresql-client libpq-dev sudo
- name: Install Poetry
uses: snok/install-poetry@v1
+ with:
+ version: 1.8.5
+ virtualenvs-create: true
+ virtualenvs-in-project: true
- name: Install application dependencies
run: poetry install
- name: Run tests
- run: ./check.sh
+ run: |
+ poetry run mbslave init --create-user --create-database --empty
+ ./check.sh
+ env:
+ MBSLAVE_DB_HOST: postgresql
+ MBSLAVE_DB_PORT: 5432
+ MBSLAVE_DB_DB: musicbrainz
+ MBSLAVE_DB_USER: musicbrainz
+ MBSLAVE_DB_PASSWORD: reallynotapassword
+ MBSLAVE_DB_ADMIN_USER: postgres
+ MBSLAVE_DB_ADMIN_PASSWORD: notapassword
mbslave_initdb_test:
runs-on: ubuntu-latest
+ container: python:3.10
services:
postgresql:
image: postgres
- ports:
- - 5432/tcp
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: notapassword
@@ -40,21 +65,25 @@ jobs:
--health-retries 5
steps:
- name: Checkout source code
- uses: actions/checkout@v3
- - name: Install Python
- uses: actions/setup-python@v2
- with:
- python-version: '3.10'
+ uses: actions/checkout@v4
+ - name: Install dependencies
+ run: |
+ apt-get update
+ apt-get install -y postgresql-client libpq-dev sudo
- name: Install Poetry
uses: snok/install-poetry@v1
+ with:
+ version: 1.8.5
+ virtualenvs-create: true
+ virtualenvs-in-project: true
- name: Install application dependencies
run: poetry install
- name: Run `mbslave init`
run: poetry run mbslave init --create-user --create-database --empty
env:
- MBSLAVE_DB_HOST: 127.0.0.1
- MBSLAVE_DB_PORT: ${{ job.services.postgresql.ports['5432'] }}
- MBSLAVE_DB_NAME: musicbrainz
+ MBSLAVE_DB_HOST: postgresql
+ MBSLAVE_DB_PORT: 5432
+ MBSLAVE_DB_DB: musicbrainz
MBSLAVE_DB_USER: musicbrainz
MBSLAVE_DB_PASSWORD: reallynotapassword
MBSLAVE_DB_ADMIN_USER: postgres
diff --git a/.gitignore b/.gitignore
index bb6f2ac..7ee748c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@
mbdata.egg-info/
venv/
result
+/.idea/
+/.bin/
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 088e3ea..c4c4939 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,12 @@
+Version 30.0.0
+==============
+
+- Massive optimization of initial import by temporarily disabling triggers and constraints (MBS-13492).
+- Updated to MusicBrainz schema 30.
+- Added progress bars (`tqdm`) during data import.
+- Improved Docker environment with `docker-compose.dev.yml` and a more robust entrypoint script.
+- Updated dependencies.
+
Version 29.1.0
==============
diff --git a/DEVELOPMENT.rst b/DEVELOPMENT.rst
index 8b0da9b..69509b5 100644
--- a/DEVELOPMENT.rst
+++ b/DEVELOPMENT.rst
@@ -5,15 +5,50 @@ Development Guide
Development Setup
=================
-Clone the repository and setup virtualenv::
+Clone the repository and install the project using `poetry `__.
+If you don't have poetry installed, you can install it using ``pipx install poetry`` or ``pip install poetry``.
+
+::
git clone https://github.com/acoustid/mbslave.git
cd mbslave/
- virtualenv -p python3 venv
- source venv/bin/activate
- pip install poetry
poetry install
+Poetry will automatically create a virtual environment for you. You can then run commands
+within this environment using ``poetry run mbslave `` or by activating the shell with ``poetry shell``.
+For example, to see available commands:
+
+::
+
+ poetry run mbslave --help
+
+Development Database (PostgreSQL)
+=================================
+
+For development and testing, you can use the provided ``Dockerfile.postgres`` to start a pre-initialized PostgreSQL database with the MusicBrainz schema.
+
+Using Docker Compose:
+
+::
+
+ docker compose -f docker-compose.dev.yml up -d
+
+This will start a PostgreSQL container on port 5432 with:
+* Database: ``musicbrainz``
+* User: ``musicbrainz``
+* Password: ``musicbrainz``
+
+The database will be automatically initialized with the schema when the container starts.
+
+Running Tests
+=============
+
+You can run the tests using ``pytest``. Some tests require a running development database (see above).
+
+::
+
+ poetry run pytest
+
Updating SQL files and models
=============================
@@ -37,3 +72,5 @@ Release a new version
rm -rf dist/
python setup.py sdist
twine upload dist/mbslave-*.tar.gz
+
+..
diff --git a/Dockerfile.mbslave b/Dockerfile.mbslave
index 94d6cf4..90bdfb8 100644
--- a/Dockerfile.mbslave
+++ b/Dockerfile.mbslave
@@ -1,7 +1,7 @@
FROM python:3.10 AS build
RUN python3 -m venv /opt/poetry && \
- /opt/poetry/bin/pip install poetry && \
+ /opt/poetry/bin/pip install poetry==1.8.5 && \
ln -s /opt/poetry/bin/poetry /usr/local/bin/ && \
poetry --version
@@ -18,5 +18,7 @@ RUN apt-get update && \
COPY --from=build /tmp/mbdata/dist/ /tmp/dist/
RUN python -m pip install "$(ls -1 /tmp/dist/*.whl)[replication,models]" && rm -rf /tmp/dist/
-ENTRYPOINT ["/usr/bin/dumb-init", "--"]
-CMD ["mbslave", "sync", "-r"]
+COPY docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+ENTRYPOINT ["/usr/bin/dumb-init", "--", "docker-entrypoint.sh"]
diff --git a/Dockerfile.postgres b/Dockerfile.postgres
new file mode 100644
index 0000000..ba1bb7c
--- /dev/null
+++ b/Dockerfile.postgres
@@ -0,0 +1,49 @@
+FROM postgres:16
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ python3 \
+ python3-pip \
+ python3-venv \
+ postgresql-server-dev-16 \
+ gcc \
+ python3-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN python3 -m venv /opt/mbslave-venv
+ENV PATH="/opt/mbslave-venv/bin:$PATH"
+
+WORKDIR /usr/src/app
+COPY . .
+
+# Install mbslave and its dependencies
+RUN pip install .
+
+ENV POSTGRES_DB=musicbrainz
+ENV POSTGRES_USER=musicbrainz
+ENV POSTGRES_PASSWORD=musicbrainz
+
+# Create a default config file for mbslave
+RUN cat < /etc/mbslave.conf
+[database]
+host=/var/run/postgresql
+port=5432
+name=musicbrainz
+user=musicbrainz
+password=musicbrainz
+admin_user=musicbrainz
+EOF
+
+# Add initialization script
+COPY <<-'INIT_DB' /docker-entrypoint-initdb.d/01-init-mbslave.sh
+#!/bin/bash
+set -e
+
+# mbslave init expects to be able to connect to the database
+# When scripts in /docker-entrypoint-initdb.d/ are run, the server is running
+# and accepting connections.
+# We use PGUSER=postgres to connect as the superuser that exists during init.
+
+PGUSER=postgres MBSLAVE_CONFIG=/etc/mbslave.conf mbslave init --empty
+INIT_DB
+
+RUN chmod +x /docker-entrypoint-initdb.d/01-init-mbslave.sh
diff --git a/README.rst b/README.rst
index 9e4dce3..a89bc2a 100644
--- a/README.rst
+++ b/README.rst
@@ -20,7 +20,6 @@ Installation
You need to have `Python 3.x `__ installed on your system.
You can use `pipx `__ to install this package::
- sudo apt install python3 pipx
pipx install 'mbslave'
There are two ways to configure the application.
@@ -174,3 +173,5 @@ data import. You can temporarily map all schemas to e.g. "musicbrainz_NEW", impo
database there and then rename it::
echo 'BEGIN; ALTER SCHEMA musicbrainz RENAME TO musicbrainz_OLD; ALTER SCHEMA musicbrainz_NEW RENAME TO musicbrainz; COMMIT;' | mbslave psql -S
+
+..
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
new file mode 100644
index 0000000..3b59a7f
--- /dev/null
+++ b/docker-compose.dev.yml
@@ -0,0 +1,33 @@
+services:
+ db:
+ build:
+ context: .
+ dockerfile: Dockerfile.postgres
+ environment:
+ - POSTGRES_PASSWORD=musicbrainz
+ ports:
+ - "5432:5432"
+ volumes:
+ - db_data:/var/lib/postgresql/data
+ shm_size: 8gb
+ healthcheck:
+ test: [ "CMD-SHELL", "pg_isready -U musicbrainz -d musicbrainz" ]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ mbslave:
+ build:
+ context: .
+ dockerfile: Dockerfile.mbslave
+ environment:
+ - KEEP_ALIVE=true
+ volumes:
+ - mbslave_config:/etc/mbslave
+ depends_on:
+ db:
+ condition: service_healthy
+
+volumes:
+ db_data:
+ mbslave_config:
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 0000000..5dd958a
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+if [ "$KEEP_ALIVE" = "1" ] || [ "$KEEP_ALIVE" = "true" ] || [ $# -eq 0 ]; then
+ echo "KEEP_ALIVE is set or no command provided, staying up..."
+ # Keep the container running
+ tail -f /dev/null
+else
+ exec "$@"
+fi
diff --git a/mbslave/__init__.py b/mbslave/__init__.py
index 7c2f436..8cb7180 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__ = "30.0.0"
diff --git a/mbslave/replication.py b/mbslave/replication.py
index e33d68e..da8f1bc 100644
--- a/mbslave/replication.py
+++ b/mbslave/replication.py
@@ -8,7 +8,7 @@
import argparse
import logging
from io import BytesIO
-from urllib.request import urlopen
+from urllib.request import urlopen, Request
from urllib.parse import urljoin
from urllib.error import HTTPError
import tempfile
@@ -19,10 +19,22 @@
from contextlib import ExitStack
import prometheus_client
+from tqdm import tqdm
logger = logging.getLogger(__name__)
+class TqdmFileReader(object):
+ def __init__(self, fileobj, pbar):
+ self.fileobj = fileobj
+ self.pbar = pbar
+
+ def read(self, size=-1):
+ data = self.fileobj.read(size)
+ self.pbar.update(len(data))
+ return data
+
+
CURRENT_SCHEMA_SEQUENCE = prometheus_client.Gauge('mbslave_current_schema_sequence', 'Current schema sequence number')
CURRENT_REPLICATION_REQUENCE = prometheus_client.Gauge('mbslave_current_replication_sequence', 'Current replication sequence number')
PROCESSED_PACKETS = prometheus_client.Counter('mbslave_processed_packets_total', 'Total number of processed packets')
@@ -38,7 +50,7 @@ def read_env_item(obj, key, name, convert=None):
value = os.environ[name]
if name + '_FILE' in os.environ:
value = open(os.environ[name + '_FILE']).read().strip()
- if value is not None:
+ if value:
if convert is not None:
value = convert(value)
setattr(obj, key, value)
@@ -238,44 +250,119 @@ def check_table_exists(db, schema, table):
return True
+def download_url(url: str, dest_path: str) -> None:
+ headers = {}
+ file_mode = 'wb'
+ resume_size = 0
+ if os.path.exists(dest_path):
+ resume_size = os.path.getsize(dest_path)
+ headers['Range'] = 'bytes=%d-' % resume_size
+ file_mode = 'ab'
+
+ request = Request(url, headers=headers)
+ try:
+ response = urlopen(request)
+ except HTTPError as e:
+ if e.code == 416: # Range Not Satisfiable
+ # Check if the file is already complete
+ with urlopen(url) as r:
+ total_size = int(r.info().get('Content-Length', 0))
+ if resume_size >= total_size:
+ logger.info("File %s already downloaded", dest_path)
+ return
+ raise
+ raise
+
+ content_length = response.info().get('Content-Length')
+ total_size = int(content_length) if content_length else None
+
+ if response.getcode() == 206:
+ if total_size is not None:
+ total_size += resume_size
+ else:
+ file_mode = 'wb'
+ resume_size = 0
+
+ with open(dest_path, file_mode) as f:
+ with tqdm(total=total_size, unit='B', unit_scale=True, desc=os.path.basename(url), initial=resume_size, leave=False) as pbar:
+ while True:
+ chunk = response.read(1024 * 1024)
+ if not chunk:
+ break
+ f.write(chunk)
+ pbar.update(len(chunk))
+
+
def load_tar(source: str, fileobj: BytesIO, db, config, ignored_schemas, ignored_tables):
logger.info("Importing data from %s", source)
tar = tarfile.open(fileobj=fileobj, mode='r|*')
cursor = db.cursor()
- for member in tar:
- if not member.name.startswith('mbdump/'):
- continue
- name = member.name.split('/')[1].replace('_sanitised', '')
- schema, table = parse_name(config, name)
- fulltable = fqn(schema, table)
- if schema in ignored_schemas:
- logger.info("Ignoring %s", name)
- continue
- if table in ignored_tables:
- logger.info("Ignoring %s", name)
- continue
- if not check_table_exists(db, schema, table):
- logger.info("Skipping %s (table %s does not exist)", name, fulltable)
- continue
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % fulltable)
- if cursor.fetchone():
- logger.info("Skipping %s (table %s already contains data)", name, fulltable)
- continue
- logger.info("Loading %s to %s", name, fulltable)
- cursor.copy_expert('COPY {} FROM STDIN'.format(fulltable), tar.extractfile(member))
+ try:
+ cursor.execute("SET session_replication_role = 'replica'")
+ except psycopg2.Error:
+ db.rollback()
+ logger.warning("Could not set session_replication_role to 'replica', import might be slow")
+
+ try:
+ for member in tar:
+ if not member.name.startswith('mbdump/'):
+ continue
+ name = member.name.split('/')[1].replace('_sanitised', '')
+ schema, table = parse_name(config, name)
+ fulltable = fqn(schema, table)
+ if schema in ignored_schemas:
+ logger.info("Ignoring %s", name)
+ continue
+ if table in ignored_tables:
+ logger.info("Ignoring %s", name)
+ continue
+ if not check_table_exists(db, schema, table):
+ logger.info("Skipping %s (table %s does not exist)", name, fulltable)
+ continue
+ cursor.execute("SELECT 1 FROM %s LIMIT 1" % fulltable)
+ if cursor.fetchone():
+ logger.info("Skipping %s (table %s already contains data)", name, fulltable)
+ continue
+ try:
+ cursor.execute("ALTER TABLE %s DISABLE TRIGGER ALL" % fulltable)
+ except psycopg2.Error:
+ db.rollback()
+ logger.info("Loading %s to %s", name, fulltable)
+ with tqdm(total=member.size, unit='B', unit_scale=True, desc=name, leave=False) as pbar:
+ fileobj = tar.extractfile(member)
+ if fileobj is None:
+ continue
+ cursor.copy_expert('COPY {} FROM STDIN'.format(fulltable), TqdmFileReader(fileobj, pbar))
+ try:
+ cursor.execute("ALTER TABLE %s ENABLE TRIGGER ALL" % fulltable)
+ except psycopg2.Error:
+ db.rollback()
+ db.commit()
+ finally:
+ try:
+ cursor.execute("SET session_replication_role = 'origin'")
+ except psycopg2.Error:
+ db.rollback()
db.commit()
def mbslave_import_main(config, args):
db = connect_db(config, superuser=True, set_search_path=True)
+ work_dir = args.work_dir or os.getcwd()
+ if not os.path.exists(work_dir):
+ os.makedirs(work_dir)
+
for source in args.sources:
- with ExitStack() as exit_stack:
- if source.startswith('http://') or source.startswith('https://'):
- fileobj = exit_stack.enter_context(urlopen(source))
- else:
- fileobj = exit_stack.enter_context(open(source, 'rb'))
- load_tar(source, fileobj, db, config, config.schemas.ignored_schemas, config.tables.ignored_tables)
+ if source.startswith('http://') or source.startswith('https://'):
+ filename = source.split('/')[-1]
+ dest_path = os.path.join(work_dir, filename)
+ download_url(source, dest_path)
+ with open(dest_path, 'rb') as fileobj:
+ load_tar(source, fileobj, db, config, config.schemas.ignored_schemas, config.tables.ignored_tables)
+ else:
+ with open(source, 'rb') as fileobj:
+ load_tar(source, fileobj, db, config, config.schemas.ignored_schemas, config.tables.ignored_tables)
def mbslave_auto_import_main(config: Config, args) -> None:
@@ -289,7 +376,11 @@ def mbslave_auto_import_main(config: Config, args) -> None:
logger.info("Latest dump is %s", latest)
- db = connect_db(config)
+ db = connect_db(config, superuser=True)
+
+ work_dir = args.work_dir or os.getcwd()
+ if not os.path.exists(work_dir):
+ os.makedirs(work_dir)
files = [
'mbdump.tar.bz2',
@@ -300,7 +391,9 @@ def mbslave_auto_import_main(config: Config, args) -> None:
]
for file in files:
url = urljoin(base_url, latest + '/' + file)
- with urlopen(url) as fileobj:
+ dest_path = os.path.join(work_dir, file)
+ download_url(url, dest_path)
+ with open(dest_path, 'rb') as fileobj:
load_tar(url, fileobj, db, config, config.schemas.ignored_schemas, config.tables.ignored_tables)
@@ -573,15 +666,19 @@ def create_user(config: Config) -> None:
db = connect_db(config, superuser=True, no_db=True)
db.autocommit = True
cursor = db.cursor()
- cursor.execute(f"CREATE USER {config.database.user} PASSWORD %s", (config.database.password,))
+ cursor.execute("SELECT 1 FROM pg_roles WHERE rolname=%s", (config.database.user,))
+ if not cursor.fetchone():
+ cursor.execute(f"CREATE USER {config.database.user} PASSWORD %s", (config.database.password,))
def create_database(config: Config) -> None:
db = connect_db(config, superuser=True, no_db=True)
db.autocommit = True
cursor = db.cursor()
- cursor.execute(f"CREATE DATABASE {config.database.name} WITH OWNER {config.database.user}")
- cursor.execute(f"ALTER DATABASE {config.database.name} SET timezone TO 'UTC'")
+ cursor.execute("SELECT 1 FROM pg_database WHERE datname=%s", (config.database.name,))
+ if not cursor.fetchone():
+ cursor.execute(f"CREATE DATABASE {config.database.name} WITH OWNER {config.database.user}")
+ cursor.execute(f"ALTER DATABASE {config.database.name} SET timezone TO 'UTC'")
def create_schemas(config: Config) -> None:
@@ -622,6 +719,9 @@ def mbslave_init_main(config: Config, args: argparse.Namespace) -> None:
if args.create_database:
create_database(config)
+ if args.only_db:
+ return
+
create_schemas(config)
# https://github.com/metabrainz/musicbrainz-server/blob/master/admin/InitDb.pl#L254
@@ -753,11 +853,13 @@ def main():
parser_init = subparsers.add_parser('init')
parser_init.add_argument('--create-user', action='store_true')
parser_init.add_argument('--create-database', action='store_true')
+ parser_init.add_argument('--only-db', action='store_true')
parser_init.add_argument('--empty', action='store_true')
- parser_init.set_defaults(func=mbslave_init_main, create_user=False, create_database=False, empty=False)
+ parser_init.set_defaults(func=mbslave_init_main, create_user=False, create_database=False, only_db=False, empty=False)
parser_import = subparsers.add_parser('import')
parser_import.add_argument('sources', metavar='FILE_OR_URL', nargs='+', help='tar.bz2 file to import')
+ parser_import.add_argument('--work-dir', metavar='DIR', help='directory to store downloaded files')
parser_import.set_defaults(func=mbslave_import_main)
parser_auto_import = subparsers.add_parser('auto-import')
@@ -768,6 +870,7 @@ def main():
help='URL of the data dump mirror',
default='http://ftp.musicbrainz.org/pub/musicbrainz/data/fullexport/',
)
+ parser_auto_import.add_argument('--work-dir', metavar='DIR', help='directory to store downloaded files')
parser_auto_import.set_defaults(func=mbslave_auto_import_main)
parser_sync = subparsers.add_parser('sync')
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..047ccfa 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,6 @@ BEGIN
) area_hierarchy
ORDER BY descendant, parent, depth;
END;
-$$ LANGUAGE plpgsql SET search_path = musicbrainz, public;
+$$ 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..28ff024 100644
--- a/mbslave/sql/CreateReplicationTriggers2.sql
+++ b/mbslave/sql/CreateReplicationTriggers2.sql
@@ -815,6 +815,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();
diff --git a/mbslave/sql/CreateTables.sql b/mbslave/sql/CreateTables.sql
index 7f3fabe..73cca4b 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);
@@ -2907,7 +2900,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 +2967,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/DisableLastUpdatedTriggers.sql b/mbslave/sql/DisableLastUpdatedTriggers.sql
old mode 100755
new mode 100644
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..0bb2472 100644
--- a/mbslave/sql/DropFunctions.sql
+++ b/mbslave/sql/DropFunctions.sql
@@ -39,6 +39,8 @@ 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();
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..e7a9b17 100644
--- a/mbslave/sql/DropMirrorOnlyTriggers.sql
+++ b/mbslave/sql/DropMirrorOnlyTriggers.sql
@@ -17,6 +17,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 +33,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..ed646bf 100644
--- a/mbslave/sql/DropReplicationTriggers2.sql
+++ b/mbslave/sql/DropReplicationTriggers2.sql
@@ -204,6 +204,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;
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..063dab1 100644
--- a/mbslave/sql/DropTriggers.sql
+++ b/mbslave/sql/DropTriggers.sql
@@ -194,6 +194,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 +388,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/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/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/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/tests/test_docker_db.py b/mbslave/tests/test_docker_db.py
new file mode 100644
index 0000000..60ba883
--- /dev/null
+++ b/mbslave/tests/test_docker_db.py
@@ -0,0 +1,36 @@
+import psycopg2
+import pytest
+import os
+
+
+def test_db_connection():
+ # Use the default credentials from Dockerfile.postgres
+ conn = psycopg2.connect(
+ host=os.environ.get("MBSLAVE_DB_HOST", "localhost"),
+ port=int(os.environ.get("MBSLAVE_DB_PORT", "5432")),
+ dbname=os.environ.get("MBSLAVE_DB_NAME", "musicbrainz"),
+ user=os.environ.get("MBSLAVE_DB_USER", "musicbrainz"),
+ password=os.environ.get("MBSLAVE_DB_PASSWORD", "musicbrainz")
+ )
+ cur = conn.cursor()
+ cur.execute("SELECT 1")
+ assert cur.fetchone()[0] == 1
+ cur.close()
+ conn.close()
+
+
+def test_schemas_exist():
+ conn = psycopg2.connect(
+ host=os.environ.get("MBSLAVE_DB_HOST", "localhost"),
+ port=int(os.environ.get("MBSLAVE_DB_PORT", "5432")),
+ dbname=os.environ.get("MBSLAVE_DB_NAME", "musicbrainz"),
+ user=os.environ.get("MBSLAVE_DB_USER", "musicbrainz"),
+ password=os.environ.get("MBSLAVE_DB_PASSWORD", "musicbrainz")
+ )
+ cur = conn.cursor()
+ schemas = ['musicbrainz', 'statistics', 'cover_art_archive', 'event_art_archive', 'wikidocs', 'documentation', 'dbmirror2']
+ for schema in schemas:
+ cur.execute("SELECT schema_name FROM information_schema.schemata WHERE schema_name = %s", (schema,))
+ assert cur.fetchone() is not None, f"Schema {schema} does not exist"
+ cur.close()
+ conn.close()
diff --git a/poetry.lock b/poetry.lock
index 8e82936..3e0c211 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "attrs"
@@ -6,16 +6,17 @@ version = "22.1.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.5"
+groups = ["dev"]
files = [
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
]
[package.extras]
-dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
+dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
-tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
-tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
+tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
[[package]]
name = "colorama"
@@ -23,10 +24,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""}
[[package]]
name = "exceptiongroup"
@@ -34,6 +37,8 @@ version = "1.0.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
+markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.0.2-py3-none-any.whl", hash = "sha256:c22f11ec6a10d2b453871c5c5fe887436c4d1961324ce9090f2ca6ddc4180c27"},
{file = "exceptiongroup-1.0.2.tar.gz", hash = "sha256:a31cd183c3dea02e617aab5153588d5f7258a77b51f0ef41b3815ae8a0d0f695"},
@@ -48,6 +53,7 @@ version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.6.1"
+groups = ["dev"]
files = [
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
@@ -64,6 +70,7 @@ version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
optional = false
python-versions = "*"
+groups = ["dev"]
files = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
@@ -75,6 +82,7 @@ version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
@@ -86,6 +94,7 @@ version = "0.991"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"},
{file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"},
@@ -136,6 +145,7 @@ version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
optional = false
python-versions = "*"
+groups = ["dev"]
files = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@@ -147,6 +157,7 @@ version = "21.3"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
@@ -161,6 +172,7 @@ version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
@@ -176,6 +188,7 @@ version = "0.20.0"
description = "Python client for the Prometheus monitoring system."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"},
{file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"},
@@ -190,6 +203,7 @@ version = "2.9.10"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"},
{file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"},
@@ -197,6 +211,7 @@ files = [
{file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"},
{file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"},
{file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"},
+ {file = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"},
{file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"},
{file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"},
{file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"},
@@ -208,6 +223,7 @@ version = "2.9.1"
description = "Python style guide checker"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
@@ -219,6 +235,7 @@ version = "2.5.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
@@ -230,6 +247,7 @@ version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.6.8"
+groups = ["dev"]
files = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
@@ -244,6 +262,7 @@ version = "7.2.0"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
{file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
@@ -267,6 +286,7 @@ version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+groups = ["main"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@@ -278,6 +298,7 @@ version = "0.4.2"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.5"
+groups = ["dev"]
files = [
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
@@ -289,17 +310,42 @@ version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
+markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
+[[package]]
+name = "tqdm"
+version = "4.67.1"
+description = "Fast, Extensible Progress Meter"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
+ {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"]
+discord = ["requests"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
[[package]]
name = "types-psycopg2"
version = "2.9.21.2"
description = "Typing stubs for psycopg2"
optional = false
python-versions = "*"
+groups = ["dev"]
files = [
{file = "types-psycopg2-2.9.21.2.tar.gz", hash = "sha256:bff045579642ce00b4a3c8f2e401b7f96dfaa34939f10be64b0dd3b53feca57d"},
{file = "types_psycopg2-2.9.21.2-py3-none-any.whl", hash = "sha256:084558d6bc4b2cfa249b06be0fdd9a14a69d307bae5bb5809a2f14cfbaa7a23f"},
@@ -311,6 +357,7 @@ version = "1.16.21.4"
description = "Typing stubs for six"
optional = false
python-versions = "*"
+groups = ["dev"]
files = [
{file = "types-six-1.16.21.4.tar.gz", hash = "sha256:daaf1b506137d37257fad7a747857c57d5331611919d0d94b8195a06bdbc02af"},
{file = "types_six-1.16.21.4-py3-none-any.whl", hash = "sha256:3849354bd07b9274436aaa7d5d834594d8f0aa74581f88c632188c58f2abed23"},
@@ -322,12 +369,13 @@ version = "4.1.1"
description = "Backported and Experimental Type Hints for Python 3.6+"
optional = false
python-versions = ">=3.6"
+groups = ["dev"]
files = [
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
]
[metadata]
-lock-version = "2.0"
+lock-version = "2.1"
python-versions = "^3.8"
-content-hash = "b267dba7d1a750a410c81c56bd929555430d9cc1cea51684271719fba3f2cf0e"
+content-hash = "8fff0a612a1396668807525189e7cb23957ad2cf63f2be27f161730bafbf1efc"
diff --git a/pyproject.toml b/pyproject.toml
index 2a49c69..55994a1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,8 +11,9 @@ python = "^3.8"
psycopg2 = "^2.9.2"
six = "^1.16.0"
prometheus-client = "^0.20.0"
+tqdm = "^4.64.0"
-[tool.poetry.dev-dependencies]
+[tool.poetry.group.dev.dependencies]
flake8 = "^5.0.0"
psycopg2 = "^2.9.2"
sqlparse = "^0.4.2"