# Copyright 2020 New Vector Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # We create a new table called `local_current_membership` that stores the latest # membership state of local users in rooms, which helps track leaves/bans/etc # even if the server has left the room (and so has deleted the room from # `current_state_events`). This will also include outstanding invites for local # users for rooms the server isn't in. # # If the server isn't and hasn't been in the room then it will only include # outsstanding invites, and not e.g. pre-emptive bans of local users. # # If the server later rejoins a room `local_current_membership` can simply be # replaced with the new current state of the room (which results in the # equivalent behaviour as if the server had remained in the room). from synapse.config.homeserver import HomeServerConfig from synapse.storage.database import LoggingTransaction from synapse.storage.engines import BaseDatabaseEngine def run_upgrade( cur: LoggingTransaction, database_engine: BaseDatabaseEngine, config: HomeServerConfig, ) -> None: # We need to do the insert in `run_upgrade` section as we don't have access # to `config` in `run_create`. # This upgrade may take a bit of time for large servers (e.g. one minute for # matrix.org) but means we avoid a lots of book keeping required to do it as # a background update. # We check if the `current_state_events.membership` is up to date by # checking if the relevant background update has finished. If it has # finished we can avoid doing a join against `room_memberships`, which # speesd things up. cur.execute( """SELECT 1 FROM background_updates WHERE update_name = 'current_state_events_membership' """ ) current_state_membership_up_to_date = not bool(cur.fetchone()) # Cheekily drop and recreate indices, as that is faster. cur.execute("DROP INDEX local_current_membership_idx") cur.execute("DROP INDEX local_current_membership_room_idx") if current_state_membership_up_to_date: sql = """ INSERT INTO local_current_membership (room_id, user_id, event_id, membership) SELECT c.room_id, state_key AS user_id, event_id, c.membership FROM current_state_events AS c WHERE type = 'm.room.member' AND c.membership IS NOT NULL AND state_key LIKE ? """ else: # We can't rely on the membership column, so we need to join against # `room_memberships`. sql = """ INSERT INTO local_current_membership (room_id, user_id, event_id, membership) SELECT c.room_id, state_key AS user_id, event_id, r.membership FROM current_state_events AS c INNER JOIN room_memberships AS r USING (event_id) WHERE type = 'm.room.member' AND state_key LIKE ? """ cur.execute(sql, ("%:" + config.server.server_name,)) cur.execute( "CREATE UNIQUE INDEX local_current_membership_idx ON local_current_membership(user_id, room_id)" ) cur.execute( "CREATE INDEX local_current_membership_room_idx ON local_current_membership(room_id)" ) def run_create(cur: LoggingTransaction, database_engine: BaseDatabaseEngine) -> None: cur.execute( """ CREATE TABLE local_current_membership ( room_id TEXT NOT NULL, user_id TEXT NOT NULL, event_id TEXT NOT NULL, membership TEXT NOT NULL )""" ) cur.execute( "CREATE UNIQUE INDEX local_current_membership_idx ON local_current_membership(user_id, room_id)" ) cur.execute( "CREATE INDEX local_current_membership_room_idx ON local_current_membership(room_id)" )