github.com/mattermost/mattermost-server/v5@v5.39.3/store/sqlstore/reaction_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 sq "github.com/Masterminds/squirrel" 8 9 "github.com/mattermost/mattermost-server/v5/model" 10 "github.com/mattermost/mattermost-server/v5/shared/mlog" 11 "github.com/mattermost/mattermost-server/v5/store" 12 13 "github.com/mattermost/gorp" 14 "github.com/pkg/errors" 15 ) 16 17 type SqlReactionStore struct { 18 *SqlStore 19 } 20 21 func newSqlReactionStore(sqlStore *SqlStore) store.ReactionStore { 22 s := &SqlReactionStore{sqlStore} 23 24 for _, db := range sqlStore.GetAllConns() { 25 table := db.AddTableWithName(model.Reaction{}, "Reactions").SetKeys(false, "PostId", "UserId", "EmojiName") 26 table.ColMap("UserId").SetMaxSize(26) 27 table.ColMap("PostId").SetMaxSize(26) 28 table.ColMap("EmojiName").SetMaxSize(64) 29 table.ColMap("RemoteId").SetMaxSize(26) 30 } 31 32 return s 33 } 34 35 func (s *SqlReactionStore) Save(reaction *model.Reaction) (*model.Reaction, error) { 36 reaction.PreSave() 37 if err := reaction.IsValid(); err != nil { 38 return nil, err 39 } 40 41 transaction, err := s.GetMaster().Begin() 42 if err != nil { 43 return nil, errors.Wrap(err, "begin_transaction") 44 } 45 defer finalizeTransaction(transaction) 46 err = s.saveReactionAndUpdatePost(transaction, reaction) 47 if err != nil { 48 // We don't consider duplicated save calls as an error 49 if !IsUniqueConstraintError(err, []string{"reactions_pkey", "PRIMARY"}) { 50 return nil, errors.Wrap(err, "failed while saving reaction or updating post") 51 } 52 } else { 53 if err := transaction.Commit(); err != nil { 54 return nil, errors.Wrap(err, "commit_transaction") 55 } 56 } 57 58 return reaction, nil 59 } 60 61 func (s *SqlReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, error) { 62 reaction.PreUpdate() 63 64 transaction, err := s.GetMaster().Begin() 65 if err != nil { 66 return nil, errors.Wrap(err, "begin_transaction") 67 } 68 defer finalizeTransaction(transaction) 69 70 if err := deleteReactionAndUpdatePost(transaction, reaction); err != nil { 71 return nil, errors.Wrap(err, "deleteReactionAndUpdatePost") 72 } 73 74 if err := transaction.Commit(); err != nil { 75 return nil, errors.Wrap(err, "commit_transaction") 76 } 77 78 return reaction, nil 79 } 80 81 // GetForPost returns all reactions associated with `postId` that are not deleted. 82 func (s *SqlReactionStore) GetForPost(postId string, allowFromCache bool) ([]*model.Reaction, error) { 83 queryString, args, err := s.getQueryBuilder(). 84 Select("UserId", "PostId", "EmojiName", "CreateAt", "COALESCE(UpdateAt, CreateAt) As UpdateAt", 85 "COALESCE(DeleteAt, 0) As DeleteAt", "RemoteId"). 86 From("Reactions"). 87 Where(sq.Eq{"PostId": postId}). 88 Where(sq.Eq{"COALESCE(DeleteAt, 0)": 0}). 89 OrderBy("CreateAt"). 90 ToSql() 91 92 if err != nil { 93 return nil, errors.Wrap(err, "reactions_getforpost_tosql") 94 } 95 96 var reactions []*model.Reaction 97 if _, err := s.GetReplica().Select(&reactions, queryString, args...); err != nil { 98 return nil, errors.Wrapf(err, "failed to get Reactions with postId=%s", postId) 99 } 100 return reactions, nil 101 } 102 103 // GetForPostSince returns all reactions associated with `postId` updated after `since`. 104 func (s *SqlReactionStore) GetForPostSince(postId string, since int64, excludeRemoteId string, inclDeleted bool) ([]*model.Reaction, error) { 105 query := s.getQueryBuilder(). 106 Select("UserId", "PostId", "EmojiName", "CreateAt", "COALESCE(UpdateAt, CreateAt) As UpdateAt", 107 "COALESCE(DeleteAt, 0) As DeleteAt", "RemoteId"). 108 From("Reactions"). 109 Where(sq.Eq{"PostId": postId}). 110 Where(sq.Gt{"UpdateAt": since}) 111 112 if excludeRemoteId != "" { 113 query = query.Where(sq.NotEq{"COALESCE(RemoteId, '')": excludeRemoteId}) 114 } 115 116 if !inclDeleted { 117 query = query.Where(sq.Eq{"COALESCE(DeleteAt, 0)": 0}) 118 } 119 120 query.OrderBy("CreateAt") 121 122 queryString, args, err := query.ToSql() 123 if err != nil { 124 return nil, errors.Wrap(err, "reactions_getforpostsince_tosql") 125 } 126 127 var reactions []*model.Reaction 128 if _, err := s.GetReplica().Select(&reactions, queryString, args...); err != nil { 129 return nil, errors.Wrapf(err, "failed to find reactions") 130 } 131 return reactions, nil 132 } 133 134 func (s *SqlReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) { 135 keys, params := MapStringsToQueryParams(postIds, "postId") 136 var reactions []*model.Reaction 137 138 if _, err := s.GetReplica().Select(&reactions, 139 `SELECT 140 UserId, 141 PostId, 142 EmojiName, 143 CreateAt, 144 COALESCE(UpdateAt, CreateAt) As UpdateAt, 145 COALESCE(DeleteAt, 0) As DeleteAt, 146 RemoteId 147 FROM 148 Reactions 149 WHERE 150 PostId IN `+keys+` AND COALESCE(DeleteAt, 0) = 0 151 ORDER BY 152 CreateAt`, params); err != nil { 153 return nil, errors.Wrap(err, "failed to get Reactions") 154 } 155 return reactions, nil 156 } 157 158 func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) error { 159 var reactions []*model.Reaction 160 now := model.GetMillis() 161 162 params := map[string]interface{}{ 163 "EmojiName": emojiName, 164 "UpdateAt": now, 165 "DeleteAt": now, 166 } 167 168 if _, err := s.GetReplica().Select(&reactions, 169 `SELECT 170 UserId, 171 PostId, 172 EmojiName, 173 CreateAt, 174 COALESCE(UpdateAt, CreateAt) As UpdateAt, 175 COALESCE(DeleteAt, 0) As DeleteAt, 176 RemoteId 177 FROM 178 Reactions 179 WHERE 180 EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params); err != nil { 181 return errors.Wrapf(err, "failed to get Reactions with emojiName=%s", emojiName) 182 } 183 184 _, err := s.GetMaster().Exec( 185 `UPDATE 186 Reactions 187 SET 188 UpdateAt = :UpdateAt, DeleteAt = :DeleteAt 189 WHERE 190 EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params) 191 if err != nil { 192 return errors.Wrapf(err, "failed to delete Reactions with emojiName=%s", emojiName) 193 } 194 195 for _, reaction := range reactions { 196 reaction := reaction 197 _, err := s.GetMaster().Exec(UpdatePostHasReactionsOnDeleteQuery, 198 map[string]interface{}{ 199 "PostId": reaction.PostId, 200 "UpdateAt": model.GetMillis(), 201 }) 202 if err != nil { 203 mlog.Warn("Unable to update Post.HasReactions while removing reactions", 204 mlog.String("post_id", reaction.PostId), 205 mlog.Err(err)) 206 } 207 } 208 209 return nil 210 } 211 212 // DeleteOrphanedRows removes entries from Reactions when a corresponding post no longer exists. 213 func (s *SqlReactionStore) DeleteOrphanedRows(limit int) (deleted int64, err error) { 214 // We need the extra level of nesting to deal with MySQL's locking 215 const query = ` 216 DELETE FROM Reactions WHERE PostId IN ( 217 SELECT * FROM ( 218 SELECT PostId FROM Reactions 219 LEFT JOIN Posts ON Reactions.PostId = Posts.Id 220 WHERE Posts.Id IS NULL 221 LIMIT :Limit 222 ) AS A 223 )` 224 props := map[string]interface{}{"Limit": limit} 225 result, err := s.GetMaster().Exec(query, props) 226 if err != nil { 227 return 228 } 229 deleted, err = result.RowsAffected() 230 return 231 } 232 233 func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { 234 var query string 235 if s.DriverName() == "postgres" { 236 query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))" 237 } else { 238 query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit" 239 } 240 241 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 242 if err != nil { 243 return 0, errors.Wrap(err, "failed to delete Reactions") 244 } 245 246 rowsAffected, err := sqlResult.RowsAffected() 247 if err != nil { 248 return 0, errors.Wrap(err, "unable to get rows affected for deleted Reactions") 249 } 250 return rowsAffected, nil 251 } 252 253 func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 254 params := map[string]interface{}{ 255 "UserId": reaction.UserId, 256 "PostId": reaction.PostId, 257 "EmojiName": reaction.EmojiName, 258 "CreateAt": reaction.CreateAt, 259 "UpdateAt": reaction.UpdateAt, 260 "RemoteId": reaction.RemoteId, 261 } 262 263 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 264 if _, err := transaction.Exec( 265 `INSERT INTO 266 Reactions 267 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId) 268 VALUES 269 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId) 270 ON DUPLICATE KEY UPDATE 271 UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil { 272 return err 273 } 274 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 275 if _, err := transaction.Exec( 276 `INSERT INTO 277 Reactions 278 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId) 279 VALUES 280 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId) 281 ON CONFLICT (UserId, PostId, EmojiName) 282 DO UPDATE SET UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil { 283 return err 284 } 285 } 286 return updatePostForReactionsOnInsert(transaction, reaction.PostId) 287 } 288 289 func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 290 params := map[string]interface{}{ 291 "UserId": reaction.UserId, 292 "PostId": reaction.PostId, 293 "EmojiName": reaction.EmojiName, 294 "CreateAt": reaction.CreateAt, 295 "UpdateAt": reaction.UpdateAt, 296 "DeleteAt": reaction.UpdateAt, // DeleteAt = UpdateAt 297 "RemoteId": reaction.RemoteId, 298 } 299 300 if _, err := transaction.Exec( 301 `UPDATE 302 Reactions 303 SET 304 UpdateAt = :UpdateAt, DeleteAt = :DeleteAt, RemoteId = :RemoteId 305 WHERE 306 PostId = :PostId AND 307 UserId = :UserId AND 308 EmojiName = :EmojiName`, params); err != nil { 309 return err 310 } 311 312 return updatePostForReactionsOnDelete(transaction, reaction.PostId) 313 } 314 315 const ( 316 UpdatePostHasReactionsOnDeleteQuery = `UPDATE 317 Posts 318 SET 319 UpdateAt = :UpdateAt, 320 HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId AND COALESCE(DeleteAt, 0) = 0) 321 WHERE 322 Id = :PostId` 323 ) 324 325 func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error { 326 updateAt := model.GetMillis() 327 _, err := transaction.Exec(UpdatePostHasReactionsOnDeleteQuery, map[string]interface{}{"PostId": postId, "UpdateAt": updateAt}) 328 return err 329 } 330 331 func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error { 332 _, err := transaction.Exec( 333 `UPDATE 334 Posts 335 SET 336 HasReactions = True, 337 UpdateAt = :UpdateAt 338 WHERE 339 Id = :PostId`, 340 map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 341 342 return err 343 }