Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit dc75fb7

Browse files
Populate rooms.creator field for easy lookup (#10697)
Part of #10566 - Fill in creator whenever we insert into the rooms table - Add background update to backfill any missing creator values
1 parent e059094 commit dc75fb7

File tree

6 files changed

+213
-4
lines changed

6 files changed

+213
-4
lines changed

changelog.d/10697.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure `rooms.creator` field is always populated for easy lookup in [MSC2716](https://github.com/matrix-org/matrix-doc/pull/2716) usage later.

synapse/api/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ class EventContentFields:
198198
# cf https://github.com/matrix-org/matrix-doc/pull/1772
199199
ROOM_TYPE = "type"
200200

201+
# The creator of the room, as used in `m.room.create` events.
202+
ROOM_CREATOR = "creator"
203+
201204
# Used on normal messages to indicate they were historically imported after the fact
202205
MSC2716_HISTORICAL = "org.matrix.msc2716.historical"
203206
# For "insertion" events to indicate what the next chunk ID should be in

synapse/handlers/federation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ async def do_invite_join(
507507
await self.store.upsert_room_on_join(
508508
room_id=room_id,
509509
room_version=room_version_obj,
510+
auth_events=auth_chain,
510511
)
511512

512513
max_stream_id = await self._persist_auth_tree(

synapse/storage/databases/main/room.py

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
from enum import Enum
2020
from typing import Any, Dict, List, Optional, Tuple
2121

22-
from synapse.api.constants import EventTypes, JoinRules
22+
from synapse.api.constants import EventContentFields, EventTypes, JoinRules
2323
from synapse.api.errors import StoreError
2424
from synapse.api.room_versions import RoomVersion, RoomVersions
25+
from synapse.events import EventBase
2526
from synapse.storage._base import SQLBaseStore, db_to_json
2627
from synapse.storage.database import DatabasePool, LoggingTransaction
2728
from synapse.storage.databases.main.search import SearchStore
@@ -1013,6 +1014,7 @@ class _BackgroundUpdates:
10131014
ADD_ROOMS_ROOM_VERSION_COLUMN = "add_rooms_room_version_column"
10141015
POPULATE_ROOM_DEPTH_MIN_DEPTH2 = "populate_room_depth_min_depth2"
10151016
REPLACE_ROOM_DEPTH_MIN_DEPTH = "replace_room_depth_min_depth"
1017+
POPULATE_ROOMS_CREATOR_COLUMN = "populate_rooms_creator_column"
10161018

10171019

10181020
_REPLACE_ROOM_DEPTH_SQL_COMMANDS = (
@@ -1054,6 +1056,11 @@ def __init__(self, database: DatabasePool, db_conn, hs):
10541056
self._background_replace_room_depth_min_depth,
10551057
)
10561058

1059+
self.db_pool.updates.register_background_update_handler(
1060+
_BackgroundUpdates.POPULATE_ROOMS_CREATOR_COLUMN,
1061+
self._background_populate_rooms_creator_column,
1062+
)
1063+
10571064
async def _background_insert_retention(self, progress, batch_size):
10581065
"""Retrieves a list of all rooms within a range and inserts an entry for each of
10591066
them into the room_retention table.
@@ -1273,7 +1280,7 @@ async def has_auth_chain_index(self, room_id: str) -> bool:
12731280
keyvalues={"room_id": room_id},
12741281
retcol="MAX(stream_ordering)",
12751282
allow_none=True,
1276-
desc="upsert_room_on_join",
1283+
desc="has_auth_chain_index_fallback",
12771284
)
12781285

12791286
return max_ordering is None
@@ -1343,14 +1350,75 @@ def process(txn: Cursor) -> None:
13431350

13441351
return 0
13451352

1353+
async def _background_populate_rooms_creator_column(
1354+
self, progress: dict, batch_size: int
1355+
):
1356+
"""Background update to go and add creator information to `rooms`
1357+
table from `current_state_events` table.
1358+
"""
1359+
1360+
last_room_id = progress.get("room_id", "")
1361+
1362+
def _background_populate_rooms_creator_column_txn(txn: LoggingTransaction):
1363+
sql = """
1364+
SELECT room_id, json FROM event_json
1365+
INNER JOIN rooms AS room USING (room_id)
1366+
INNER JOIN current_state_events AS state_event USING (room_id, event_id)
1367+
WHERE room_id > ? AND (room.creator IS NULL OR room.creator = '') AND state_event.type = 'm.room.create' AND state_event.state_key = ''
1368+
ORDER BY room_id
1369+
LIMIT ?
1370+
"""
1371+
1372+
txn.execute(sql, (last_room_id, batch_size))
1373+
room_id_to_create_event_results = txn.fetchall()
1374+
1375+
new_last_room_id = ""
1376+
for room_id, event_json in room_id_to_create_event_results:
1377+
event_dict = db_to_json(event_json)
1378+
1379+
creator = event_dict.get("content").get(EventContentFields.ROOM_CREATOR)
1380+
1381+
self.db_pool.simple_update_txn(
1382+
txn,
1383+
table="rooms",
1384+
keyvalues={"room_id": room_id},
1385+
updatevalues={"creator": creator},
1386+
)
1387+
new_last_room_id = room_id
1388+
1389+
if new_last_room_id == "":
1390+
return True
1391+
1392+
self.db_pool.updates._background_update_progress_txn(
1393+
txn,
1394+
_BackgroundUpdates.POPULATE_ROOMS_CREATOR_COLUMN,
1395+
{"room_id": new_last_room_id},
1396+
)
1397+
1398+
return False
1399+
1400+
end = await self.db_pool.runInteraction(
1401+
"_background_populate_rooms_creator_column",
1402+
_background_populate_rooms_creator_column_txn,
1403+
)
1404+
1405+
if end:
1406+
await self.db_pool.updates._end_background_update(
1407+
_BackgroundUpdates.POPULATE_ROOMS_CREATOR_COLUMN
1408+
)
1409+
1410+
return batch_size
1411+
13461412

13471413
class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore, SearchStore):
13481414
def __init__(self, database: DatabasePool, db_conn, hs):
13491415
super().__init__(database, db_conn, hs)
13501416

13511417
self.config = hs.config
13521418

1353-
async def upsert_room_on_join(self, room_id: str, room_version: RoomVersion):
1419+
async def upsert_room_on_join(
1420+
self, room_id: str, room_version: RoomVersion, auth_events: List[EventBase]
1421+
):
13541422
"""Ensure that the room is stored in the table
13551423
13561424
Called when we join a room over federation, and overwrites any room version
@@ -1361,14 +1429,32 @@ async def upsert_room_on_join(self, room_id: str, room_version: RoomVersion):
13611429
# mark the room as having an auth chain cover index.
13621430
has_auth_chain_index = await self.has_auth_chain_index(room_id)
13631431

1432+
create_event = None
1433+
for e in auth_events:
1434+
if (e.type, e.state_key) == (EventTypes.Create, ""):
1435+
create_event = e
1436+
break
1437+
1438+
if create_event is None:
1439+
# If the state doesn't have a create event then the room is
1440+
# invalid, and it would fail auth checks anyway.
1441+
raise StoreError(400, "No create event in state")
1442+
1443+
room_creator = create_event.content.get(EventContentFields.ROOM_CREATOR)
1444+
1445+
if not isinstance(room_creator, str):
1446+
# If the create event does not have a creator then the room is
1447+
# invalid, and it would fail auth checks anyway.
1448+
raise StoreError(400, "No creator defined on the create event")
1449+
13641450
await self.db_pool.simple_upsert(
13651451
desc="upsert_room_on_join",
13661452
table="rooms",
13671453
keyvalues={"room_id": room_id},
13681454
values={"room_version": room_version.identifier},
13691455
insertion_values={
13701456
"is_public": False,
1371-
"creator": "",
1457+
"creator": room_creator,
13721458
"has_auth_chain_index": has_auth_chain_index,
13731459
},
13741460
# rooms has a unique constraint on room_id, so no need to lock when doing an
@@ -1396,6 +1482,9 @@ async def maybe_store_room_on_outlier_membership(
13961482
insertion_values={
13971483
"room_version": room_version.identifier,
13981484
"is_public": False,
1485+
# We don't worry about setting the `creator` here because
1486+
# we don't process any messages in a room while a user is
1487+
# invited (only after the join).
13991488
"creator": "",
14001489
"has_auth_chain_index": has_auth_chain_index,
14011490
},
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* Copyright 2021 The Matrix.org Foundation C.I.C
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
INSERT INTO background_updates (ordering, update_name, progress_json)
17+
VALUES (6302, 'populate_rooms_creator_column', '{}');
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2021 The Matrix.org Foundation C.I.C.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the 'License');
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an 'AS IS' BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from synapse.rest import admin
16+
from synapse.rest.client import login, room
17+
from synapse.storage.databases.main.room import _BackgroundUpdates
18+
19+
from tests.unittest import HomeserverTestCase
20+
21+
22+
class RoomBackgroundUpdateStoreTestCase(HomeserverTestCase):
23+
24+
servlets = [
25+
admin.register_servlets,
26+
room.register_servlets,
27+
login.register_servlets,
28+
]
29+
30+
def prepare(self, reactor, clock, hs):
31+
self.store = hs.get_datastore()
32+
self.user_id = self.register_user("foo", "pass")
33+
self.token = self.login("foo", "pass")
34+
35+
def _generate_room(self) -> str:
36+
room_id = self.helper.create_room_as(self.user_id, tok=self.token)
37+
38+
return room_id
39+
40+
def test_background_populate_rooms_creator_column(self):
41+
"""Test that the background update to populate the rooms creator column
42+
works properly.
43+
"""
44+
45+
# Insert a room without the creator
46+
room_id = self._generate_room()
47+
self.get_success(
48+
self.store.db_pool.simple_update(
49+
table="rooms",
50+
keyvalues={"room_id": room_id},
51+
updatevalues={"creator": None},
52+
desc="test",
53+
)
54+
)
55+
56+
# Make sure the test is starting out with a room without a creator
57+
room_creator_before = self.get_success(
58+
self.store.db_pool.simple_select_one_onecol(
59+
table="rooms",
60+
keyvalues={"room_id": room_id},
61+
retcol="creator",
62+
allow_none=True,
63+
)
64+
)
65+
self.assertEqual(room_creator_before, None)
66+
67+
# Insert and run the background update.
68+
self.get_success(
69+
self.store.db_pool.simple_insert(
70+
"background_updates",
71+
{
72+
"update_name": _BackgroundUpdates.POPULATE_ROOMS_CREATOR_COLUMN,
73+
"progress_json": "{}",
74+
},
75+
)
76+
)
77+
78+
# ... and tell the DataStore that it hasn't finished all updates yet
79+
self.store.db_pool.updates._all_done = False
80+
81+
# Now let's actually drive the updates to completion
82+
while not self.get_success(
83+
self.store.db_pool.updates.has_completed_background_updates()
84+
):
85+
self.get_success(
86+
self.store.db_pool.updates.do_next_background_update(100), by=0.1
87+
)
88+
89+
# Make sure the background update filled in the room creator
90+
room_creator_after = self.get_success(
91+
self.store.db_pool.simple_select_one_onecol(
92+
table="rooms",
93+
keyvalues={"room_id": room_id},
94+
retcol="creator",
95+
allow_none=True,
96+
)
97+
)
98+
self.assertEqual(room_creator_after, self.user_id)

0 commit comments

Comments
 (0)