diff --git a/src/github.com/matrix-org/dendrite/roomserver/input/membership.go b/src/github.com/matrix-org/dendrite/roomserver/input/membership.go index 788b1e30a..2cf9d0d4a 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/input/membership.go +++ b/src/github.com/matrix-org/dendrite/roomserver/input/membership.go @@ -197,7 +197,7 @@ func updateToLeaveMembership( // are active for that user. We notify the consumers that the invites have // been retired using a special event, even though they could infer this // by studying the state changes in the room event stream. - retired, err := mu.SetToLeave(add.Sender()) + retired, err := mu.SetToLeave(add.Sender(), add.EventID()) if err != nil { return nil, err } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/event_state_keys_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/event_state_keys_table.go index d30e45815..b4dae8f25 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/event_state_keys_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/event_state_keys_table.go @@ -58,10 +58,22 @@ const bulkSelectEventStateKeyNIDSQL = "" + "SELECT event_state_key, event_state_key_nid FROM roomserver_event_state_keys" + " WHERE event_state_key = ANY($1)" +const selectEventStateKeySQL = "" + + "SELECT event_state_key FROM roomserver_event_state_keys" + + " WHERE event_state_key_nid = $1" + +// Bulk lookup from numeric ID to string state key for that state key. +// Takes an array of strings as the query parameter. +const bulkSelectEventStateKeySQL = "" + + "SELECT event_state_key, event_state_key_nid FROM roomserver_event_state_keys" + + " WHERE event_state_key_nid = ANY($1)" + type eventStateKeyStatements struct { insertEventStateKeyNIDStmt *sql.Stmt selectEventStateKeyNIDStmt *sql.Stmt + selectEventStateKeyStmt *sql.Stmt bulkSelectEventStateKeyNIDStmt *sql.Stmt + bulkSelectEventStateKeyStmt *sql.Stmt } func (s *eventStateKeyStatements) prepare(db *sql.DB) (err error) { @@ -72,7 +84,9 @@ func (s *eventStateKeyStatements) prepare(db *sql.DB) (err error) { return statementList{ {&s.insertEventStateKeyNIDStmt, insertEventStateKeyNIDSQL}, {&s.selectEventStateKeyNIDStmt, selectEventStateKeyNIDSQL}, + {&s.selectEventStateKeyStmt, selectEventStateKeySQL}, {&s.bulkSelectEventStateKeyNIDStmt, bulkSelectEventStateKeyNIDSQL}, + {&s.bulkSelectEventStateKeyStmt, bulkSelectEventStateKeySQL}, }.prepare(db) } @@ -114,3 +128,36 @@ func (s *eventStateKeyStatements) bulkSelectEventStateKeyNID(eventStateKeys []st } return result, nil } + +func (s *eventStateKeyStatements) selectEventStateKey(txn *sql.Tx, eventStateKeyNID types.EventStateKeyNID) (string, error) { + var eventStateKey string + stmt := s.selectEventStateKeyStmt + if txn != nil { + stmt = txn.Stmt(stmt) + } + err := stmt.QueryRow(eventStateKeyNID).Scan(&eventStateKey) + return eventStateKey, err +} + +func (s *eventStateKeyStatements) bulkSelectEventStateKey(eventStateKeyNIDs []types.EventStateKeyNID) (map[types.EventStateKeyNID]string, error) { + var nIDs pq.Int64Array + for i := range eventStateKeyNIDs { + nIDs[i] = int64(eventStateKeyNIDs[i]) + } + rows, err := s.bulkSelectEventStateKeyStmt.Query(nIDs) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make(map[types.EventStateKeyNID]string, len(eventStateKeyNIDs)) + for rows.Next() { + var stateKey string + var stateKeyNID int64 + if err := rows.Scan(&stateKey, &stateKeyNID); err != nil { + return nil, err + } + result[types.EventStateKeyNID(stateKeyNID)] = stateKey + } + return result, nil +} diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go index fa38d1881..52051af59 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go @@ -46,11 +46,16 @@ CREATE TABLE IF NOT EXISTS roomserver_membership ( -- The state the user is in within this room. -- Default value is "membershipStateLeaveOrBan" membership_nid BIGINT NOT NULL DEFAULT 1, - -- The ID of the "join" membership event. - -- This ID is updated if the join event gets updated (e.g. profile update). - -- This column is set to NULL if the user hasn't joined the room yet, e.g. - -- if the user was invited but hasn't joined yet. - event_id TEXT, + -- The numeric ID of the membership event. + -- It refers to the join membership event if the membership_nid is join (3), + -- and to the leave/ban membership event if the membership_nid is leave or + -- ban (1). + -- If the membership_nid is invite (2) and the user has been in the room + -- before, it will refer to the previous leave/ban membership event, and will + -- be equals to 0 (its default) if the user never joined the room before. + -- This NID is updated if the join event gets updated (e.g. profile update), + -- or if the user leaves/joins the room. + event_nid BIGINT NOT NULL DEFAULT 0, UNIQUE (room_nid, target_nid) ); ` @@ -62,18 +67,33 @@ const insertMembershipSQL = "" + " VALUES ($1, $2)" + " ON CONFLICT DO NOTHING" +const selectMembershipFromRoomAndTargetSQL = "" + + "SELECT membership_nid, event_nid FROM roomserver_membership" + + " WHERE room_nid = $1 AND target_nid = $2" + +const selectMembershipsFromRoomAndMembershipSQL = "" + + "SELECT event_nid FROM roomserver_membership" + + " WHERE room_nid = $1 AND membership_nid = $2" + +const selectMembershipsFromRoomSQL = "" + + "SELECT membership_nid, event_nid FROM roomserver_membership" + + " WHERE room_nid = $1" + const selectMembershipForUpdateSQL = "" + "SELECT membership_nid FROM roomserver_membership" + " WHERE room_nid = $1 AND target_nid = $2 FOR UPDATE" const updateMembershipSQL = "" + - "UPDATE roomserver_membership SET sender_nid = $3, membership_nid = $4, event_id = $5" + + "UPDATE roomserver_membership SET sender_nid = $3, membership_nid = $4, event_nid = $5" + " WHERE room_nid = $1 AND target_nid = $2" type membershipStatements struct { - insertMembershipStmt *sql.Stmt - selectMembershipForUpdateStmt *sql.Stmt - updateMembershipStmt *sql.Stmt + insertMembershipStmt *sql.Stmt + selectMembershipForUpdateStmt *sql.Stmt + selectMembershipFromRoomAndTargetStmt *sql.Stmt + selectMembershipsFromRoomAndMembershipStmt *sql.Stmt + selectMembershipsFromRoomStmt *sql.Stmt + updateMembershipStmt *sql.Stmt } func (s *membershipStatements) prepare(db *sql.DB) (err error) { @@ -85,6 +105,9 @@ func (s *membershipStatements) prepare(db *sql.DB) (err error) { return statementList{ {&s.insertMembershipStmt, insertMembershipSQL}, {&s.selectMembershipForUpdateStmt, selectMembershipForUpdateSQL}, + {&s.selectMembershipFromRoomAndTargetStmt, selectMembershipFromRoomAndTargetSQL}, + {&s.selectMembershipsFromRoomAndMembershipStmt, selectMembershipsFromRoomAndMembershipSQL}, + {&s.selectMembershipsFromRoomStmt, selectMembershipsFromRoomSQL}, {&s.updateMembershipStmt, updateMembershipSQL}, }.prepare(db) } @@ -105,17 +128,59 @@ func (s *membershipStatements) selectMembershipForUpdate( return } +func (s *membershipStatements) selectMembershipFromRoomAndTarget( + roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, +) (eventNID types.EventNID, membership membershipState, err error) { + err = s.selectMembershipFromRoomAndTargetStmt.QueryRow( + roomNID, targetUserNID, + ).Scan(&membership, &eventNID) + return +} + +func (s *membershipStatements) selectMembershipsFromRoom( + roomNID types.RoomNID, +) (eventNIDs map[types.EventNID]membershipState, err error) { + rows, err := s.selectMembershipsFromRoomStmt.Query(roomNID) + if err != nil { + return + } + + eventNIDs = make(map[types.EventNID]membershipState) + for rows.Next() { + var eNID types.EventNID + var membership membershipState + if err = rows.Scan(&membership, &eNID); err != nil { + return + } + eventNIDs[eNID] = membership + } + return +} +func (s *membershipStatements) selectMembershipsFromRoomAndMembership( + roomNID types.RoomNID, membership membershipState, +) (eventNIDs []types.EventNID, err error) { + rows, err := s.selectMembershipsFromRoomAndMembershipStmt.Query(roomNID, membership) + if err != nil { + return + } + + for rows.Next() { + var eNID types.EventNID + if err = rows.Scan(&eNID); err != nil { + return + } + eventNIDs = append(eventNIDs, eNID) + } + return +} + func (s *membershipStatements) updateMembership( txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, senderUserNID types.EventStateKeyNID, membership membershipState, - eventID string, + eventNID types.EventNID, ) error { - eID := sql.NullString{ - String: eventID, - Valid: len(eventID) > 0, - } _, err := txn.Stmt(s.updateMembershipStmt).Exec( - roomNID, targetUserNID, senderUserNID, membership, eID, + roomNID, targetUserNID, senderUserNID, membership, eventNID, ) return err } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index 57cb9ff3f..ac6a8f664 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -435,7 +435,7 @@ func (u *membershipUpdater) SetToInvite(event gomatrixserverlib.Event) (bool, er } if u.membership != membershipStateInvite { if err = u.d.statements.updateMembership( - u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, "", + u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, 0, ); err != nil { return false, err } @@ -462,9 +462,15 @@ func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpd } } + // Lookup the NID of the new join event + nIDs, err := u.d.EventNIDs([]string{eventID}) + if err != nil { + return nil, err + } + if u.membership != membershipStateJoin || isUpdate { if err = u.d.statements.updateMembership( - u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateJoin, eventID, + u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateJoin, nIDs[eventID], ); err != nil { return nil, err } @@ -474,7 +480,7 @@ func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpd } // SetToLeave implements types.MembershipUpdater -func (u *membershipUpdater) SetToLeave(senderUserID string) ([]string, error) { +func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]string, error) { senderUserNID, err := u.d.assignStateKeyNID(u.txn, senderUserID) if err != nil { return nil, err @@ -485,9 +491,16 @@ func (u *membershipUpdater) SetToLeave(senderUserID string) ([]string, error) { if err != nil { return nil, err } + + // Lookup the NID of the new leave event + nIDs, err := u.d.EventNIDs([]string{eventID}) + if err != nil { + return nil, err + } + if u.membership != membershipStateLeaveOrBan { if err = u.d.statements.updateMembership( - u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateLeaveOrBan, "", + u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateLeaveOrBan, nIDs[eventID], ); err != nil { return nil, err } @@ -495,6 +508,53 @@ func (u *membershipUpdater) SetToLeave(senderUserID string) ([]string, error) { return inviteEventIDs, nil } +// GetMembershipEvents returns an array containing the join events for all +// members in a room as requested by a given user. If the user is currently in +// the room, returns the room's current members, if not returns an empty array +// TODO: in this case, send the list of members as it was when the user left +// If the user requesting the list of members has never been in the room, returns +// nil. +// If there was an issue retrieving the events, returns an error. +func (d *Database) GetMembershipEvents(roomNID types.RoomNID, requestSenderUserID string) (events []types.Event, err error) { + txn, err := d.db.Begin() + if err != nil { + return + } + + requestSenderUserNID, err := d.assignStateKeyNID(txn, requestSenderUserID) + if err != nil { + return + } + + _, senderMembership, err := d.statements.selectMembershipFromRoomAndTarget(roomNID, requestSenderUserNID) + if err == sql.ErrNoRows { + // The user has never been a member of that room + return nil, nil + } else if err != nil { + return + } + + if senderMembership == membershipStateJoin { + // The user is still in the room: Send the current list of joined members + var joinEventNIDs []types.EventNID + joinEventNIDs, err = d.statements.selectMembershipsFromRoomAndMembership(roomNID, membershipStateJoin) + if err != nil { + return nil, err + } + + events, err = d.Events(joinEventNIDs) + } else { + // The user isn't in the room anymore + // TODO: Send the list of joined member as it was when the user left + // We cannot do this using only the memberships database, as it + // only stores the latest join event NID for a given target user. + // The solution would be to build the state of a room after before + // the leave event and extract a members list from it. + } + + return +} + type transaction struct { txn *sql.Tx } diff --git a/src/github.com/matrix-org/dendrite/roomserver/types/types.go b/src/github.com/matrix-org/dendrite/roomserver/types/types.go index df55fe2ea..e8bc99fcf 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/types/types.go +++ b/src/github.com/matrix-org/dendrite/roomserver/types/types.go @@ -198,7 +198,7 @@ type MembershipUpdater interface { SetToJoin(senderUserID string, eventID string, isUpdate bool) (inviteEventIDs []string, err error) // Set the state to leave. // Returns a list of invite event IDs that this state change retired. - SetToLeave(senderUserID string) (inviteEventIDs []string, err error) + SetToLeave(senderUserID string, eventID string) (inviteEventIDs []string, err error) // Implements Transaction so it can be committed or rolledback. Transaction }