github.com/mattermost/mattermost-server/v5@v5.39.3/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 "fmt" 9 10 sq "github.com/Masterminds/squirrel" 11 "github.com/pkg/errors" 12 13 "github.com/mattermost/mattermost-server/v5/model" 14 "github.com/mattermost/mattermost-server/v5/shared/mlog" 15 "github.com/mattermost/mattermost-server/v5/store" 16 ) 17 18 type SqlChannelMemberHistoryStore struct { 19 *SqlStore 20 } 21 22 func newSqlChannelMemberHistoryStore(sqlStore *SqlStore) store.ChannelMemberHistoryStore { 23 s := &SqlChannelMemberHistoryStore{ 24 SqlStore: sqlStore, 25 } 26 27 for _, db := range sqlStore.GetAllConns() { 28 table := db.AddTableWithName(model.ChannelMemberHistory{}, "ChannelMemberHistory").SetKeys(false, "ChannelId", "UserId", "JoinTime") 29 table.ColMap("ChannelId").SetMaxSize(26) 30 table.ColMap("UserId").SetMaxSize(26) 31 table.ColMap("JoinTime").SetNotNull(true) 32 } 33 34 return s 35 } 36 37 func (s SqlChannelMemberHistoryStore) LogJoinEvent(userId string, channelId string, joinTime int64) error { 38 channelMemberHistory := &model.ChannelMemberHistory{ 39 UserId: userId, 40 ChannelId: channelId, 41 JoinTime: joinTime, 42 } 43 44 if err := s.GetMaster().Insert(channelMemberHistory); err != nil { 45 return errors.Wrapf(err, "LogJoinEvent userId=%s channelId=%s joinTime=%d", userId, channelId, joinTime) 46 } 47 return nil 48 } 49 50 func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId string, leaveTime int64) error { 51 query, params, err := s.getQueryBuilder(). 52 Update("ChannelMemberHistory"). 53 Set("LeaveTime", leaveTime). 54 Where(sq.And{ 55 sq.Eq{"UserId": userId}, 56 sq.Eq{"ChannelId": channelId}, 57 sq.Eq{"LeaveTime": nil}, 58 }).ToSql() 59 if err != nil { 60 return errors.Wrap(err, "channel_member_history_to_sql") 61 } 62 sqlResult, err := s.GetMaster().Exec(query, params...) 63 if err != nil { 64 return errors.Wrapf(err, "LogLeaveEvent userId=%s channelId=%s leaveTime=%d", userId, channelId, leaveTime) 65 } 66 67 if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 { 68 // there was no join event to update - this is best effort, so no need to raise an error 69 mlog.Warn("Channel join event for user and channel not found", mlog.String("user", userId), mlog.String("channel", channelId)) 70 } 71 return nil 72 } 73 74 func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 75 useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime) 76 if err != nil { 77 return nil, errors.Wrapf(err, "hasDataAtOrBefore startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 78 } 79 80 if useChannelMemberHistory { 81 // the export period starts after the ChannelMemberHistory table was first introduced, so we can use the 82 // data from it for our export 83 channelMemberHistories, err2 := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId) 84 if err2 != nil { 85 return nil, errors.Wrapf(err2, "getFromChannelMemberHistoryTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 86 } 87 return channelMemberHistories, nil 88 } 89 // the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the 90 // data by assuming that anybody who has ever joined the channel in question was present during the export period. 91 // this may not always be true, but it's better than saying that somebody wasn't there when they were 92 channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId) 93 if err != nil { 94 return nil, errors.Wrapf(err, "getFromChannelMembersTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId) 95 } 96 return channelMemberHistories, nil 97 } 98 99 func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) { 100 type NullableCountResult struct { 101 Min sql.NullInt64 102 } 103 query, _, err := s.getQueryBuilder().Select("MIN(JoinTime) as Min").From("ChannelMemberHistory").ToSql() 104 if err != nil { 105 return false, errors.Wrap(err, "channel_member_history_to_sql") 106 } 107 var result NullableCountResult 108 if err := s.GetReplica().SelectOne(&result, query); err != nil { 109 return false, err 110 } else if result.Min.Valid { 111 return result.Min.Int64 <= time, nil 112 } else { 113 // if the result was null, there are no rows in the table, so there is no data from before 114 return false, nil 115 } 116 } 117 118 func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 119 query, args, err := s.getQueryBuilder(). 120 Select("cmh.*, u.Email, u.Username, Bots.UserId IS NOT NULL AS IsBot, u.DeleteAt AS UserDeleteAt"). 121 From("ChannelMemberHistory cmh"). 122 Join("Users u ON cmh.UserId = u.Id"). 123 LeftJoin("Bots ON Bots.UserId = u.Id"). 124 Where(sq.And{ 125 sq.Eq{"cmh.ChannelId": channelId}, 126 sq.LtOrEq{"cmh.JoinTime": endTime}, 127 sq.Or{ 128 sq.Eq{"cmh.LeaveTime": nil}, 129 sq.GtOrEq{"cmh.LeaveTime": startTime}, 130 }, 131 }). 132 OrderBy("cmh.JoinTime ASC").ToSql() 133 if err != nil { 134 return nil, errors.Wrap(err, "channel_member_history_to_sql") 135 } 136 var histories []*model.ChannelMemberHistoryResult 137 if _, err := s.GetReplica().Select(&histories, query, args...); err != nil { 138 return nil, err 139 } 140 141 return histories, nil 142 } 143 144 func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) { 145 query, args, err := s.getQueryBuilder(). 146 Select("ch.ChannelId, ch.UserId, u.Email, u.Username, Bots.UserId IS NOT NULL AS IsBot, u.DeleteAt AS UserDeleteAt"). 147 Distinct(). 148 From("ChannelMembers ch"). 149 Join("Users u ON ch.UserId = u.id"). 150 LeftJoin("Bots ON Bots.UserId = u.id"). 151 Where(sq.Eq{"ch.ChannelId": channelId}).ToSql() 152 if err != nil { 153 return nil, errors.Wrap(err, "channel_member_history_to_sql") 154 } 155 156 var histories []*model.ChannelMemberHistoryResult 157 if _, err := s.GetReplica().Select(&histories, query, args...); err != nil { 158 return nil, err 159 } 160 // we have to fill in the join/leave times, because that data doesn't exist in the channel members table 161 for _, channelMemberHistory := range histories { 162 channelMemberHistory.JoinTime = startTime 163 channelMemberHistory.LeaveTime = model.NewInt64(endTime) 164 } 165 return histories, nil 166 } 167 168 // PermanentDeleteBatchForRetentionPolicies deletes a batch of records which are affected by 169 // the global or a granular retention policy. 170 // See `genericPermanentDeleteBatchForRetentionPolicies` for details. 171 func (s SqlChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) { 172 builder := s.getQueryBuilder(). 173 Select("ChannelMemberHistory.ChannelId, ChannelMemberHistory.UserId, ChannelMemberHistory.JoinTime"). 174 From("ChannelMemberHistory") 175 return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{ 176 BaseBuilder: builder, 177 Table: "ChannelMemberHistory", 178 TimeColumn: "LeaveTime", 179 PrimaryKeys: []string{"ChannelId", "UserId", "JoinTime"}, 180 ChannelIDTable: "ChannelMemberHistory", 181 NowMillis: now, 182 GlobalPolicyEndTime: globalPolicyEndTime, 183 Limit: limit, 184 }, s.SqlStore, cursor) 185 } 186 187 // DeleteOrphanedRows removes entries from ChannelMemberHistory when a corresponding channel no longer exists. 188 func (s SqlChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (deleted int64, err error) { 189 // We need the extra level of nesting to deal with MySQL's locking 190 const query = ` 191 DELETE FROM ChannelMemberHistory WHERE (ChannelId, UserId, JoinTime) IN ( 192 SELECT * FROM ( 193 SELECT ChannelId, UserId, JoinTime FROM ChannelMemberHistory 194 LEFT JOIN Channels ON ChannelMemberHistory.ChannelId = Channels.Id 195 WHERE Channels.Id IS NULL 196 LIMIT :Limit 197 ) AS A 198 )` 199 props := map[string]interface{}{"Limit": limit} 200 result, err := s.GetMaster().Exec(query, props) 201 if err != nil { 202 return 203 } 204 deleted, err = result.RowsAffected() 205 return 206 } 207 208 func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { 209 var ( 210 query string 211 args []interface{} 212 err error 213 ) 214 215 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 216 var innerSelect string 217 innerSelect, args, err = s.getQueryBuilder(). 218 Select("ctid"). 219 From("ChannelMemberHistory"). 220 Where(sq.And{ 221 sq.NotEq{"LeaveTime": nil}, 222 sq.LtOrEq{"LeaveTime": endTime}, 223 }).Limit(uint64(limit)). 224 ToSql() 225 if err != nil { 226 return 0, errors.Wrap(err, "channel_member_history_to_sql") 227 } 228 query, _, err = s.getQueryBuilder(). 229 Delete("ChannelMemberHistory"). 230 Where(fmt.Sprintf( 231 "ctid IN (%s)", innerSelect, 232 )).ToSql() 233 } else { 234 query, args, err = s.getQueryBuilder(). 235 Delete("ChannelMemberHistory"). 236 Where(sq.And{ 237 sq.NotEq{"LeaveTime": nil}, 238 sq.LtOrEq{"LeaveTime": endTime}, 239 }). 240 Limit(uint64(limit)).ToSql() 241 } 242 if err != nil { 243 return 0, errors.Wrap(err, "channel_member_history_to_sql") 244 } 245 sqlResult, err := s.GetMaster().Exec(query, args...) 246 if err != nil { 247 return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit) 248 } 249 250 rowsAffected, err := sqlResult.RowsAffected() 251 if err != nil { 252 return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit) 253 } 254 return rowsAffected, nil 255 }