github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/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/masterhung0112/hk_server/v5/model" 10 "github.com/masterhung0112/hk_server/v5/shared/mlog" 11 "github.com/masterhung0112/hk_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 func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { 233 var query string 234 if s.DriverName() == "postgres" { 235 query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))" 236 } else { 237 query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit" 238 } 239 240 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 241 if err != nil { 242 return 0, errors.Wrap(err, "failed to delete Reactions") 243 } 244 245 rowsAffected, err := sqlResult.RowsAffected() 246 if err != nil { 247 return 0, errors.Wrap(err, "unable to get rows affected for deleted Reactions") 248 } 249 return rowsAffected, nil 250 } 251 252 func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 253 params := map[string]interface{}{ 254 "UserId": reaction.UserId, 255 "PostId": reaction.PostId, 256 "EmojiName": reaction.EmojiName, 257 "CreateAt": reaction.CreateAt, 258 "UpdateAt": reaction.UpdateAt, 259 "RemoteId": reaction.RemoteId, 260 } 261 262 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 263 if _, err := transaction.Exec( 264 `INSERT INTO 265 Reactions 266 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId) 267 VALUES 268 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId) 269 ON DUPLICATE KEY UPDATE 270 UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil { 271 return err 272 } 273 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 274 if _, err := transaction.Exec( 275 `INSERT INTO 276 Reactions 277 (UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId) 278 VALUES 279 (:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId) 280 ON CONFLICT (UserId, PostId, EmojiName) 281 DO UPDATE SET UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil { 282 return err 283 } 284 } 285 return updatePostForReactionsOnInsert(transaction, reaction.PostId) 286 } 287 288 func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 289 params := map[string]interface{}{ 290 "UserId": reaction.UserId, 291 "PostId": reaction.PostId, 292 "EmojiName": reaction.EmojiName, 293 "CreateAt": reaction.CreateAt, 294 "UpdateAt": reaction.UpdateAt, 295 "DeleteAt": reaction.UpdateAt, // DeleteAt = UpdateAt 296 "RemoteId": reaction.RemoteId, 297 } 298 299 if _, err := transaction.Exec( 300 `UPDATE 301 Reactions 302 SET 303 UpdateAt = :UpdateAt, DeleteAt = :DeleteAt, RemoteId = :RemoteId 304 WHERE 305 PostId = :PostId AND 306 UserId = :UserId AND 307 EmojiName = :EmojiName`, params); err != nil { 308 return err 309 } 310 311 return updatePostForReactionsOnDelete(transaction, reaction.PostId) 312 } 313 314 const ( 315 UpdatePostHasReactionsOnDeleteQuery = `UPDATE 316 Posts 317 SET 318 UpdateAt = :UpdateAt, 319 HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId AND COALESCE(DeleteAt, 0) = 0) 320 WHERE 321 Id = :PostId` 322 ) 323 324 func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error { 325 updateAt := model.GetMillis() 326 _, err := transaction.Exec(UpdatePostHasReactionsOnDeleteQuery, map[string]interface{}{"PostId": postId, "UpdateAt": updateAt}) 327 return err 328 } 329 330 func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error { 331 _, err := transaction.Exec( 332 `UPDATE 333 Posts 334 SET 335 HasReactions = True, 336 UpdateAt = :UpdateAt 337 WHERE 338 Id = :PostId`, 339 map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 340 341 return err 342 }