github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/store/sqlstore/channel_member_history_store.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "database/sql" 8 9 "github.com/mattermost/mattermost-server/v5/mlog" 10 "github.com/mattermost/mattermost-server/v5/model" 11 "github.com/mattermost/mattermost-server/v5/store" 12 13 "github.com/pkg/errors" 14 ) 15 16 type SqlChannelMemberHistoryStore struct { 17 SqlStore 18 } 19 20 func newSqlChannelMemberHistoryStore(sqlStore SqlStore) store.ChannelMemberHistoryStore { 21 s := &SqlChannelMemberHistoryStore{ 22 SqlStore: sqlStore, 23 } 24 25 for _, db := range sqlStore.GetAllConns() { 26 table := db.AddTableWithName(model.ChannelMemberHistory{}, "ChannelMemberHistory").SetKeys(false, "ChannelId", "UserId", "JoinTime") 27 table.ColMap("ChannelId").SetMaxSize(26) 28 table.ColMap("UserId").SetMaxSize(26) 29 table.ColMap("JoinTime").SetNotNull(true) 30 } 31 32 return s 33 } 34 35 func (s SqlChannelMemberHistoryStore) LogJoinEvent(userId string, channelId string, joinTime int64) error { 36 channelMemberHistory := &model.ChannelMemberHistory{ 37 UserId: userId, 38 ChannelId: channelId, 39 JoinTime: joinTime, 40 } 41 42 if err := s.GetMaster().Insert(channelMemberHistory); err != nil { 43 return errors.Wrapf(err, "LogJoinEvent userId=%s channelId=%s joinTime=%d", userId, channelId, joinTime) 44 } 45 return nil 46 } 47 48 func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId string, leaveTime int64) error { 49 query := ` 50 UPDATE ChannelMemberHistory 51 SET LeaveTime = :LeaveTime 52 WHERE UserId = :UserId 53 AND ChannelId = :ChannelId 54 AND LeaveTime IS NULL` 55 56 params := map[string]interface{}{"UserId": userId, "ChannelId": channelId, "LeaveTime": leaveTime} 57 sqlResult, err := s.GetMaster().Exec(query, params) 58 if err != nil { 59 return errors.Wrapf(err, "LogLeaveEvent userId=%s channelId=%s leaveTime=%d", userId, channelId, leaveTime) 60 } 61 62 if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 { 63 // there was no join event to update - this is best effort, so no need to raise an error 64 mlog.Warn("Channel join event for user and channel not found", mlog.String("user", userId), mlog.String("channel", channelId)) 65 } 66 return nil 67 } 68 69 func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 70 useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime) 71 if err != nil { 72 return nil, errors.Wrapf(err, "hasDataAtOrBefore startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 73 } 74 75 if useChannelMemberHistory { 76 // the export period starts after the ChannelMemberHistory table was first introduced, so we can use the 77 // data from it for our export 78 channelMemberHistories, err2 := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId) 79 if err2 != nil { 80 return nil, errors.Wrapf(err2, "getFromChannelMemberHistoryTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 81 } 82 return channelMemberHistories, nil 83 } 84 85 // the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the 86 // data by assuming that anybody who has ever joined the channel in question was present during the export period. 87 // this may not always be true, but it's better than saying that somebody wasn't there when they were 88 channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId) 89 if err != nil { 90 return nil, errors.Wrapf(err, "getFromChannelMembersTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 91 } 92 return channelMemberHistories, nil 93 } 94 95 func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) { 96 type NullableCountResult struct { 97 Min sql.NullInt64 98 } 99 var result NullableCountResult 100 query := "SELECT MIN(JoinTime) AS Min FROM ChannelMemberHistory" 101 if err := s.GetReplica().SelectOne(&result, query); err != nil { 102 return false, err 103 } else if result.Min.Valid { 104 return result.Min.Int64 <= time, nil 105 } else { 106 // if the result was null, there are no rows in the table, so there is no data from before 107 return false, nil 108 } 109 } 110 111 func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 112 query := ` 113 SELECT 114 cmh.*, 115 u.Email, 116 u.Username, 117 Bots.UserId IS NOT NULL AS IsBot, 118 u.DeleteAt AS UserDeleteAt 119 FROM ChannelMemberHistory cmh 120 INNER JOIN Users u ON cmh.UserId = u.Id 121 LEFT JOIN Bots ON Bots.UserId = u.Id 122 WHERE cmh.ChannelId = :ChannelId 123 AND cmh.JoinTime <= :EndTime 124 AND (cmh.LeaveTime IS NULL OR cmh.LeaveTime >= :StartTime) 125 ORDER BY cmh.JoinTime ASC` 126 127 params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime} 128 var histories []*model.ChannelMemberHistoryResult 129 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 130 return nil, err 131 } 132 133 return histories, nil 134 } 135 136 func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 137 query := ` 138 SELECT DISTINCT 139 ch.ChannelId, 140 ch.UserId, 141 u.Email, 142 u.Username, 143 Bots.UserId IS NOT NULL AS IsBot, 144 u.DeleteAt AS UserDeleteAt 145 FROM ChannelMembers AS ch 146 INNER JOIN Users AS u ON ch.UserId = u.id 147 LEFT JOIN Bots ON Bots.UserId = u.Id 148 WHERE ch.ChannelId = :ChannelId` 149 150 params := map[string]interface{}{"ChannelId": channelId} 151 var histories []*model.ChannelMemberHistoryResult 152 if _, err := s.GetReplica().Select(&histories, query, params); err != nil { 153 return nil, err 154 } 155 // we have to fill in the join/leave times, because that data doesn't exist in the channel members table 156 for _, channelMemberHistory := range histories { 157 channelMemberHistory.JoinTime = startTime 158 channelMemberHistory.LeaveTime = model.NewInt64(endTime) 159 } 160 return histories, nil 161 } 162 163 func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { 164 var query string 165 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 166 query = 167 `DELETE FROM ChannelMemberHistory 168 WHERE ctid IN ( 169 SELECT ctid FROM ChannelMemberHistory 170 WHERE LeaveTime IS NOT NULL 171 AND LeaveTime <= :EndTime 172 LIMIT :Limit 173 );` 174 } else { 175 query = 176 `DELETE FROM ChannelMemberHistory 177 WHERE LeaveTime IS NOT NULL 178 AND LeaveTime <= :EndTime 179 LIMIT :Limit` 180 } 181 182 params := map[string]interface{}{"EndTime": endTime, "Limit": limit} 183 sqlResult, err := s.GetMaster().Exec(query, params) 184 if err != nil { 185 return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit) 186 } 187 188 rowsAffected, err := sqlResult.RowsAffected() 189 if err != nil { 190 return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit) 191 } 192 return rowsAffected, nil 193 }