Membership events retrieval + update on leave

This commit is contained in:
Brendan Abolivier 2017-08-18 15:22:36 +01:00
parent 68aa5bce29
commit f03bb7cf39
No known key found for this signature in database
GPG key ID: 8EF1500759F70623
5 changed files with 193 additions and 21 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}