github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/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 "github.com/mattermost/mattermost-server/v5/mlog" 8 "github.com/mattermost/mattermost-server/v5/model" 9 "github.com/mattermost/mattermost-server/v5/store" 10 11 "github.com/mattermost/gorp" 12 "github.com/pkg/errors" 13 ) 14 15 type SqlReactionStore struct { 16 *SqlStore 17 } 18 19 func newSqlReactionStore(sqlStore *SqlStore) store.ReactionStore { 20 s := &SqlReactionStore{sqlStore} 21 22 for _, db := range sqlStore.GetAllConns() { 23 table := db.AddTableWithName(model.Reaction{}, "Reactions").SetKeys(false, "PostId", "UserId", "EmojiName") 24 table.ColMap("UserId").SetMaxSize(26) 25 table.ColMap("PostId").SetMaxSize(26) 26 table.ColMap("EmojiName").SetMaxSize(64) 27 } 28 29 return s 30 } 31 32 func (s *SqlReactionStore) Save(reaction *model.Reaction) (*model.Reaction, error) { 33 reaction.PreSave() 34 if err := reaction.IsValid(); err != nil { 35 return nil, err 36 } 37 38 transaction, err := s.GetMaster().Begin() 39 if err != nil { 40 return nil, errors.Wrap(err, "begin_transaction") 41 } 42 defer finalizeTransaction(transaction) 43 err = s.saveReactionAndUpdatePost(transaction, reaction) 44 if err != nil { 45 // We don't consider duplicated save calls as an error 46 if !IsUniqueConstraintError(err, []string{"reactions_pkey", "PRIMARY"}) { 47 return nil, errors.Wrap(err, "failed while saving reaction or updating post") 48 } 49 } else { 50 if err := transaction.Commit(); err != nil { 51 return nil, errors.Wrap(err, "commit_transaction") 52 } 53 } 54 55 return reaction, nil 56 } 57 58 func (s *SqlReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, error) { 59 reaction.PreUpdate() 60 61 transaction, err := s.GetMaster().Begin() 62 if err != nil { 63 return nil, errors.Wrap(err, "begin_transaction") 64 } 65 defer finalizeTransaction(transaction) 66 67 if err := deleteReactionAndUpdatePost(transaction, reaction); err != nil { 68 return nil, errors.Wrap(err, "deleteReactionAndUpdatePost") 69 } 70 71 if err := transaction.Commit(); err != nil { 72 return nil, errors.Wrap(err, "commit_transaction") 73 } 74 75 return reaction, nil 76 } 77 78 func (s *SqlReactionStore) GetForPost(postId string, allowFromCache bool) ([]*model.Reaction, error) { 79 var reactions []*model.Reaction 80 81 if _, err := s.GetReplica().Select(&reactions, 82 `SELECT 83 UserId, 84 PostId, 85 EmojiName, 86 CreateAt, 87 COALESCE(UpdateAt, CreateAt) As UpdateAt, 88 COALESCE(DeleteAt, 0) As DeleteAt 89 FROM 90 Reactions 91 WHERE 92 PostId = :PostId AND COALESCE(DeleteAt, 0) = 0 93 ORDER BY 94 CreateAt`, map[string]interface{}{"PostId": postId}); err != nil { 95 return nil, errors.Wrapf(err, "failed to get Reactions with postId=%s", postId) 96 } 97 98 return reactions, nil 99 } 100 101 func (s *SqlReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) { 102 keys, params := MapStringsToQueryParams(postIds, "postId") 103 var reactions []*model.Reaction 104 105 if _, err := s.GetReplica().Select(&reactions, 106 `SELECT 107 UserId, 108 PostId, 109 EmojiName, 110 CreateAt, 111 COALESCE(UpdateAt, CreateAt) As UpdateAt, 112 COALESCE(DeleteAt, 0) As DeleteAt 113 FROM 114 Reactions 115 WHERE 116 PostId IN `+keys+` AND COALESCE(DeleteAt, 0) = 0 117 ORDER BY 118 CreateAt`, params); err != nil { 119 return nil, errors.Wrap(err, "failed to get Reactions") 120 } 121 return reactions, nil 122 } 123 124 func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) error { 125 var reactions []*model.Reaction 126 now := model.GetMillis() 127 128 params := map[string]interface{}{ 129 "EmojiName": emojiName, 130 "UpdateAt": now, 131 "DeleteAt": now, 132 } 133 134 if _, err := s.GetReplica().Select(&reactions, 135 `SELECT 136 UserId, 137 PostId, 138 EmojiName, 139 CreateAt, 140 COALESCE(UpdateAt, CreateAt) As UpdateAt, 141 COALESCE(DeleteAt, 0) As DeleteAt 142 FROM 143 Reactions 144 WHERE 145 EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params); err != nil { 146 return errors.Wrapf(err, "failed to get Reactions with emojiName=%s", emojiName) 147 } 148 149 _, err := s.GetMaster().Exec( 150 `UPDATE 151 Reactions 152 SET 153 UpdateAt = :UpdateAt, DeleteAt = :DeleteAt 154 WHERE 155 EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params) 156 if err != nil { 157 return errors.Wrapf(err, "failed to delete Reactions with emojiName=%s", emojiName) 158 } 159 160 for _, reaction := range reactions { 161 reaction := reaction 162 _, err := s.GetMaster().Exec(UpdatePostHasReactionsOnDeleteQuery, 163 map[string]interface{}{ 164 "PostId": reaction.PostId, 165 "UpdateAt": model.GetMillis(), 166 }) 167 if err != nil { 168 mlog.Warn("Unable to update Post.HasReactions while removing reactions", 169 mlog.String("post_id", reaction.PostId), 170 mlog.Err(err)) 171 } 172 } 173 174 return nil 175 } 176 177 func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { 178 var query string 179 if s.DriverName() == "postgres" { 180 query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))" 181 } else { 182 query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit" 183 } 184 185 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 186 if err != nil { 187 return 0, errors.Wrap(err, "failed to delete Reactions") 188 } 189 190 rowsAffected, err := sqlResult.RowsAffected() 191 if err != nil { 192 return 0, errors.Wrap(err, "unable to get rows affected for deleted Reactions") 193 } 194 return rowsAffected, nil 195 } 196 197 func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 198 params := map[string]interface{}{ 199 "UserId": reaction.UserId, 200 "PostId": reaction.PostId, 201 "EmojiName": reaction.EmojiName, 202 "CreateAt": reaction.CreateAt, 203 "UpdateAt": reaction.UpdateAt, 204 } 205 206 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 207 if _, err := transaction.Exec( 208 `INSERT INTO 209 Reactions 210 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt) 211 VALUES 212 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0) 213 ON DUPLICATE KEY UPDATE 214 UpdateAt = :UpdateAt, DeleteAt = 0`, params); err != nil { 215 return err 216 } 217 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 218 if _, err := transaction.Exec( 219 `INSERT INTO 220 Reactions 221 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt) 222 VALUES 223 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0) 224 ON CONFLICT (UserId, PostId, EmojiName) 225 DO UPDATE SET UpdateAt = :UpdateAt, DeleteAt = 0`, params); err != nil { 226 return err 227 } 228 } 229 return updatePostForReactionsOnInsert(transaction, reaction.PostId) 230 } 231 232 func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 233 params := map[string]interface{}{ 234 "UserId": reaction.UserId, 235 "PostId": reaction.PostId, 236 "EmojiName": reaction.EmojiName, 237 "CreateAt": reaction.CreateAt, 238 "UpdateAt": reaction.UpdateAt, 239 "DeleteAt": reaction.UpdateAt, // DeleteAt = UpdateAt 240 } 241 242 if _, err := transaction.Exec( 243 `UPDATE 244 Reactions 245 SET 246 UpdateAt = :UpdateAt, DeleteAt = :DeleteAt 247 WHERE 248 PostId = :PostId AND 249 UserId = :UserId AND 250 EmojiName = :EmojiName`, params); err != nil { 251 return err 252 } 253 254 return updatePostForReactionsOnDelete(transaction, reaction.PostId) 255 } 256 257 const ( 258 UpdatePostHasReactionsOnDeleteQuery = `UPDATE 259 Posts 260 SET 261 UpdateAt = :UpdateAt, 262 HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId AND COALESCE(DeleteAt, 0) = 0) 263 WHERE 264 Id = :PostId` 265 ) 266 267 func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error { 268 updateAt := model.GetMillis() 269 _, err := transaction.Exec(UpdatePostHasReactionsOnDeleteQuery, map[string]interface{}{"PostId": postId, "UpdateAt": updateAt}) 270 return err 271 } 272 273 func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error { 274 _, err := transaction.Exec( 275 `UPDATE 276 Posts 277 SET 278 HasReactions = True, 279 UpdateAt = :UpdateAt 280 WHERE 281 Id = :PostId`, 282 map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 283 284 return err 285 }