diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 7dcb1e01fd..8e3b404aed 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -245,6 +245,8 @@ class EventContentFields: # `m.room.encryption`` algorithm field ENCRYPTION_ALGORITHM: Final = "algorithm" + TOMBSTONE_SUCCESSOR_ROOM: Final = "replacement_room" + class EventUnsignedContentFields: """Fields found inside the 'unsigned' data on events""" diff --git a/synapse/storage/databases/main/events.py b/synapse/storage/databases/main/events.py index 55b4c87f55..c628892edf 100644 --- a/synapse/storage/databases/main/events.py +++ b/synapse/storage/databases/main/events.py @@ -97,14 +97,16 @@ event_counter = Counter( # State event type/key pairs that we need to gather to fill in the # `sliding_sync_joined_rooms`/`sliding_sync_membership_snapshots` tables. -SLIDING_SYNC_RELEVANT_STATE_SET = { - # So we can fill in the `room_type` column in the `sliding_sync_joined_rooms` table +SLIDING_SYNC_RELEVANT_STATE_SET = ( + # So we can fill in the `room_type` column (EventTypes.Create, ""), - # So we can fill in the `is_encrypted` column in the `sliding_sync_joined_rooms` table + # So we can fill in the `is_encrypted` column (EventTypes.RoomEncryption, ""), - # So we can fill in the `room_name` column in the `sliding_sync_joined_rooms` table + # So we can fill in the `room_name` column (EventTypes.Name, ""), -} + # So we can fill in the `tombstone_successor_room_id` column + (EventTypes.Tombstone, ""), +) @attr.s(slots=True, auto_attribs=True) @@ -1877,11 +1879,22 @@ class PersistEventsStore: # Scrutinize JSON values if room_name is None or isinstance(room_name, str): sliding_sync_insert_map["room_name"] = room_name + elif state_key == (EventTypes.Tombstone, ""): + successor_room_id = event.content.get( + EventContentFields.TOMBSTONE_SUCCESSOR_ROOM + ) + # Scrutinize JSON values + if successor_room_id is None or isinstance(successor_room_id, str): + sliding_sync_insert_map["tombstone_successor_room_id"] = ( + successor_room_id + ) else: # We only expect to see events according to the # `SLIDING_SYNC_RELEVANT_STATE_SET`. raise AssertionError( - f"Unexpected event (we should not be fetching extra events): {state_key} {event.event_id}" + "Unexpected event (we should not be fetching extra events or this " + + "piece of code needs to be updated to handle a new event type added " + + "to `SLIDING_SYNC_RELEVANT_STATE_SET`): {state_key} {event.event_id}" ) return sliding_sync_insert_map @@ -1923,6 +1936,8 @@ class PersistEventsStore: if create_stripped_event is not None: sliding_sync_insert_map["has_known_state"] = True + # XXX: Keep this up-to-date with `SLIDING_SYNC_RELEVANT_STATE_SET` + # Find the room_type sliding_sync_insert_map["room_type"] = ( create_stripped_event.content.get(EventContentFields.ROOM_TYPE) @@ -1951,6 +1966,20 @@ class PersistEventsStore: else None ) + # Find the tombstone_successor_room_id + # Note: This isn't one of the stripped state events according to the spec + # but seems like there is no reason not to support this kind of thing. + tombstone_stripped_event = stripped_state_map.get( + (EventTypes.Tombstone, "") + ) + sliding_sync_insert_map["tombstone_successor_room_id"] = ( + tombstone_stripped_event.content.get( + EventContentFields.TOMBSTONE_SUCCESSOR_ROOM + ) + if tombstone_stripped_event is not None + else None + ) + else: # No strip state provided sliding_sync_insert_map["has_known_state"] = False diff --git a/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql b/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql index 5fac6af619..27bf460b2e 100644 --- a/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql +++ b/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql @@ -27,16 +27,32 @@ CREATE TABLE IF NOT EXISTS sliding_sync_joined_rooms( -- The `stream_ordering` of the last event according to the `bump_event_types` bump_stamp BIGINT, -- `m.room.create` -> `content.type` (current state) + -- + -- Useful for the `spaces`/`not_spaces` filter in the Sliding Sync API room_type TEXT, -- `m.room.name` -> `content.name` (current state) + -- + -- Useful for the room meta data and `room_name_like` filter in the Sliding Sync API room_name TEXT, -- `m.room.encryption` -> `content.algorithm` (current state) + -- + -- Useful for the `is_encrypted` filter in the Sliding Sync API is_encrypted BOOLEAN DEFAULT FALSE NOT NULL, - -- FIXME: Maybe we want to add `tombstone_successor_room_id` here to help with `include_old_rooms` - -- (tracked by https://github.com/element-hq/synapse/issues/17540) + -- `m.room.tombstone` -> `content.replacement_room` (according to the current state at the + -- time of the membership). + -- + -- Useful for the `include_old_rooms` functionality in the Sliding Sync API + tombstone_successor_room_id TEXT, PRIMARY KEY (room_id) ); +-- So we can purge rooms easily. +-- +-- The primary key is already `room_id` + +-- So we can sort by `stream_ordering +CREATE UNIQUE INDEX IF NOT EXISTS sliding_sync_joined_rooms_event_stream_ordering ON sliding_sync_joined_rooms(event_stream_ordering); + -- A table for storing a snapshot of room meta data (historical current state relevant -- for sliding sync) at the time of a local user's membership. Only has rows for the -- latest membership event for a given local user in a room which matches @@ -72,16 +88,25 @@ CREATE TABLE IF NOT EXISTS sliding_sync_membership_snapshots( -- no stripped state was provided for a remote invite/knock (False). has_known_state BOOLEAN DEFAULT FALSE NOT NULL, -- `m.room.create` -> `content.type` (according to the current state at the time of - -- the membership) + -- the membership). + -- + -- Useful for the `spaces`/`not_spaces` filter in the Sliding Sync API room_type TEXT, -- `m.room.name` -> `content.name` (according to the current state at the time of - -- the membership) + -- the membership). + -- + -- Useful for the room meta data and `room_name_like` filter in the Sliding Sync API room_name TEXT, -- `m.room.encryption` -> `content.algorithm` (according to the current state at the - -- time of the membership) + -- time of the membership). + -- + -- Useful for the `is_encrypted` filter in the Sliding Sync API is_encrypted BOOLEAN DEFAULT FALSE NOT NULL, - -- FIXME: Maybe we want to add `tombstone_successor_room_id` here to help with `include_old_rooms` - -- (tracked by https://github.com/element-hq/synapse/issues/17540) + -- `m.room.tombstone` -> `content.replacement_room` (according to the current state at the + -- time of the membership). + -- + -- Useful for the `include_old_rooms` functionality in the Sliding Sync API + tombstone_successor_room_id TEXT, PRIMARY KEY (room_id, user_id) ); diff --git a/tests/storage/test_events.py b/tests/storage/test_events.py index 9e5c0e2bf8..4ed7d19ac5 100644 --- a/tests/storage/test_events.py +++ b/tests/storage/test_events.py @@ -506,12 +506,14 @@ class _SlidingSyncJoinedRoomResult: room_type: Optional[str] room_name: Optional[str] is_encrypted: bool + tombstone_successor_room_id: Optional[str] @attr.s(slots=True, frozen=True, auto_attribs=True) class _SlidingSyncMembershipSnapshotResult: room_id: str user_id: str + sender: str membership_event_id: str membership: str # `event_stream_ordering` is only optional to allow easier semantics when we make @@ -524,6 +526,7 @@ class _SlidingSyncMembershipSnapshotResult: room_type: Optional[str] room_name: Optional[str] is_encrypted: bool + tombstone_successor_room_id: Optional[str] class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): @@ -566,6 +569,7 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): "room_type", "room_name", "is_encrypted", + "tombstone_successor_room_id", ), ), ), @@ -579,6 +583,7 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): room_type=row[3], room_name=row[4], is_encrypted=bool(row[5]), + tombstone_successor_room_id=row[6], ) for row in rows } @@ -601,6 +606,7 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): retcols=( "room_id", "user_id", + "sender", "membership_event_id", "membership", "event_stream_ordering", @@ -608,6 +614,7 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): "room_type", "room_name", "is_encrypted", + "tombstone_successor_room_id", ), ), ), @@ -617,13 +624,15 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): (row[0], row[1]): _SlidingSyncMembershipSnapshotResult( room_id=row[0], user_id=row[1], - membership_event_id=row[2], - membership=row[3], - event_stream_ordering=row[4], - has_known_state=bool(row[5]), - room_type=row[6], - room_name=row[7], - is_encrypted=bool(row[8]), + sender=row[2], + membership_event_id=row[3], + membership=row[4], + event_stream_ordering=row[5], + has_known_state=bool(row[6]), + room_type=row[7], + room_name=row[8], + is_encrypted=bool(row[9]), + tombstone_successor_room_id=row[10], ) for row in rows }