-- Rooms, events and room state: the roomserver tables. -- -- Events are keyed by their event ID rather than by a numeric surrogate. -- An event ID is 44 bytes of base64 and appears in prev_events, -- auth_events, state snapshots and client requests alike, so a surrogate -- would have to be translated at every one of those boundaries; the -- indexes below are what make the text key cheap enough. -- -- Timestamps are milliseconds since the Unix epoch, as everywhere else in -- Warp. CREATE TABLE IF NOT EXISTS roomserver_rooms ( room_id TEXT NOT NULL PRIMARY KEY, -- Fixed at creation. Nothing can read an event without it: the room -- version decides the event format, the redaction algorithm and the -- authorisation rules. room_version TEXT NOT NULL, creator TEXT NOT NULL, is_published BOOLEAN NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, -- The denormalised room listing columns. Everything below is derivable -- from the room's current state and is kept here anyway, because the -- public room directory orders every published room by member count -- and pages the result: that cannot be done by reading each room's -- state in turn. The roomserver recomputes them when the state behind -- them changes; joined_members the store counts for itself. name TEXT NOT NULL DEFAULT '', topic TEXT NOT NULL DEFAULT '', avatar_url TEXT NOT NULL DEFAULT '', canonical_alias TEXT NOT NULL DEFAULT '', join_rule TEXT NOT NULL DEFAULT '', history_visibility TEXT NOT NULL DEFAULT '', guest_access TEXT NOT NULL DEFAULT '', room_type TEXT NOT NULL DEFAULT '', joined_members BIGINT NOT NULL DEFAULT 0 ); -- The public room directory's own ordering: published rooms, biggest first. CREATE INDEX IF NOT EXISTS roomserver_rooms_published_idx ON roomserver_rooms (is_published, joined_members); -- One counter row per stream, incremented inside the write transaction. -- -- Neither AUTOINCREMENT nor a sequence would do: SQLite and PostgreSQL -- spell those differently, and a PostgreSQL sequence is explicitly not -- transactional, so a rolled-back write would leave a hole in the stream -- that /sync would have to be taught to skip. An UPDATE on this row takes -- a row lock in both engines and gives out positions with no gaps. CREATE TABLE IF NOT EXISTS roomserver_sequences ( name TEXT NOT NULL PRIMARY KEY, value BIGINT NOT NULL ); INSERT INTO roomserver_sequences (name, value) VALUES ('stream_ordering', 0) ON CONFLICT (name) DO NOTHING; INSERT INTO roomserver_sequences (name, value) VALUES ('state_snapshot', 0) ON CONFLICT (name) DO NOTHING; -- A state snapshot is the complete state of a room at one point, stored -- once and shared by every event it applies to. Consecutive messages in a -- room whose state is not changing all point at the same snapshot, so the -- cost is one snapshot per state change rather than one per event. CREATE TABLE IF NOT EXISTS roomserver_state_snapshots ( snapshot_id BIGINT NOT NULL PRIMARY KEY, room_id TEXT NOT NULL REFERENCES roomserver_rooms (room_id) ON DELETE CASCADE, -- SHA-256 over the sorted (type, state_key, event_id) triples: the -- identity of the snapshot's contents, which is what lets two -- identical states share a row. state_hash TEXT NOT NULL, UNIQUE (room_id, state_hash) ); CREATE TABLE IF NOT EXISTS roomserver_state_entries ( snapshot_id BIGINT NOT NULL REFERENCES roomserver_state_snapshots (snapshot_id) ON DELETE CASCADE, event_type TEXT NOT NULL, state_key TEXT NOT NULL, event_id TEXT NOT NULL, PRIMARY KEY (snapshot_id, event_type, state_key) ); CREATE TABLE IF NOT EXISTS roomserver_events ( event_id TEXT NOT NULL PRIMARY KEY, room_id TEXT NOT NULL REFERENCES roomserver_rooms (room_id) ON DELETE CASCADE, -- This server's own monotonic position for the event: the order it was -- persisted in, which is the order clients are shown it in. stream_ordering BIGINT NOT NULL, event_type TEXT NOT NULL, -- NULL for a message event. The empty string is a valid state key, so -- "no state key" cannot be spelled as ''. state_key TEXT, sender TEXT NOT NULL, depth BIGINT NOT NULL, origin_server_ts BIGINT NOT NULL, -- The event as canonical JSON, byte for byte as it was signed. Every -- other column here is a projection of it, written out to be indexed. event_json TEXT NOT NULL, -- The state of the room as of this event's prev_events, 0 for the -- create event. Keeping it per event is what lets the state after a -- forward extremity be worked out, which state resolution needs when a -- room has more than one. state_before_id BIGINT NOT NULL DEFAULT 0, -- The redaction that applies to this event, or ''. The JSON above is -- rewritten to its redacted form at the same time; this records which -- event did it so that unsigned.redacted_because can be filled in. redacted_by TEXT NOT NULL DEFAULT '' ); CREATE UNIQUE INDEX IF NOT EXISTS roomserver_events_stream_idx ON roomserver_events (stream_ordering); -- Paging a room's timeline, which is what /messages and /sync do. CREATE INDEX IF NOT EXISTS roomserver_events_room_stream_idx ON roomserver_events (room_id, stream_ordering); -- The forward extremities: the events with no children, and so the -- prev_events of whatever is sent next. CREATE TABLE IF NOT EXISTS roomserver_extremities ( room_id TEXT NOT NULL REFERENCES roomserver_rooms (room_id) ON DELETE CASCADE, event_id TEXT NOT NULL, PRIMARY KEY (room_id, event_id) ); -- The room's resolved current state: the state after its extremities. -- -- It is derivable from the events, and is stored anyway because every -- authorisation decision reads it. The membership column is a denormalised -- copy of content.membership on m.room.member rows, so that "the rooms -- @alice is joined to" is an index lookup rather than a scan that parses -- JSON. CREATE TABLE IF NOT EXISTS roomserver_current_state ( room_id TEXT NOT NULL REFERENCES roomserver_rooms (room_id) ON DELETE CASCADE, event_type TEXT NOT NULL, state_key TEXT NOT NULL, event_id TEXT NOT NULL, membership TEXT NOT NULL DEFAULT '', PRIMARY KEY (room_id, event_type, state_key) ); CREATE INDEX IF NOT EXISTS roomserver_current_state_membership_idx ON roomserver_current_state (event_type, state_key, membership); -- Client transaction IDs, so that a retried request returns the event the -- first attempt created instead of sending a second one. CREATE TABLE IF NOT EXISTS roomserver_transactions ( localpart TEXT NOT NULL, device_id TEXT NOT NULL, txn_id TEXT NOT NULL, event_id TEXT NOT NULL, room_id TEXT NOT NULL, sent_at_ms BIGINT NOT NULL, PRIMARY KEY (localpart, device_id, txn_id) ); -- Local room aliases. Remote aliases are never stored: they are resolved -- over federation every time, because the other server may have moved them. CREATE TABLE IF NOT EXISTS roomserver_aliases ( alias TEXT NOT NULL PRIMARY KEY, room_id TEXT NOT NULL REFERENCES roomserver_rooms (room_id) ON DELETE CASCADE, creator TEXT NOT NULL, created_at_ms BIGINT NOT NULL ); CREATE INDEX IF NOT EXISTS roomserver_aliases_room_idx ON roomserver_aliases (room_id);