github.com/lologarithm/mattermost-server@v5.3.2-0.20181002060438-c82a84ed765b+incompatible/store/sqlstore/supplier_reactions.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "context" 8 "fmt" 9 "net/http" 10 11 "github.com/mattermost/gorp" 12 "github.com/mattermost/mattermost-server/mlog" 13 "github.com/mattermost/mattermost-server/model" 14 "github.com/mattermost/mattermost-server/store" 15 ) 16 17 func initSqlSupplierReactions(sqlStore SqlStore) { 18 for _, db := range sqlStore.GetAllConns() { 19 table := db.AddTableWithName(model.Reaction{}, "Reactions").SetKeys(false, "UserId", "PostId", "EmojiName") 20 table.ColMap("UserId").SetMaxSize(26) 21 table.ColMap("PostId").SetMaxSize(26) 22 table.ColMap("EmojiName").SetMaxSize(64) 23 } 24 } 25 26 func (s *SqlSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 27 result := store.NewSupplierResult() 28 29 reaction.PreSave() 30 if result.Err = reaction.IsValid(); result.Err != nil { 31 return result 32 } 33 34 if transaction, err := s.GetMaster().Begin(); err != nil { 35 result.Err = model.NewAppError("SqlReactionStore.Save", "store.sql_reaction.save.begin.app_error", nil, err.Error(), http.StatusInternalServerError) 36 } else { 37 err := saveReactionAndUpdatePost(transaction, reaction) 38 39 if err != nil { 40 transaction.Rollback() 41 42 // We don't consider duplicated save calls as an error 43 if !IsUniqueConstraintError(err, []string{"reactions_pkey", "PRIMARY"}) { 44 result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.save.app_error", nil, err.Error(), http.StatusBadRequest) 45 } 46 } else { 47 if err := transaction.Commit(); err != nil { 48 // don't need to rollback here since the transaction is already closed 49 result.Err = model.NewAppError("SqlPreferenceStore.Save", "store.sql_reaction.save.commit.app_error", nil, err.Error(), http.StatusInternalServerError) 50 } 51 } 52 53 if result.Err == nil { 54 result.Data = reaction 55 } 56 } 57 58 return result 59 } 60 61 func (s *SqlSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 62 result := store.NewSupplierResult() 63 64 if transaction, err := s.GetMaster().Begin(); err != nil { 65 result.Err = model.NewAppError("SqlReactionStore.Delete", "store.sql_reaction.delete.begin.app_error", nil, err.Error(), http.StatusInternalServerError) 66 } else { 67 err := deleteReactionAndUpdatePost(transaction, reaction) 68 69 if err != nil { 70 transaction.Rollback() 71 72 result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.app_error", nil, err.Error(), http.StatusInternalServerError) 73 } else if err := transaction.Commit(); err != nil { 74 // don't need to rollback here since the transaction is already closed 75 result.Err = model.NewAppError("SqlPreferenceStore.Delete", "store.sql_reaction.delete.commit.app_error", nil, err.Error(), http.StatusInternalServerError) 76 } else { 77 result.Data = reaction 78 } 79 } 80 81 return result 82 } 83 84 func (s *SqlSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 85 result := store.NewSupplierResult() 86 87 var reactions []*model.Reaction 88 89 if _, err := s.GetReplica().Select(&reactions, 90 `SELECT 91 * 92 FROM 93 Reactions 94 WHERE 95 PostId = :PostId 96 ORDER BY 97 CreateAt`, map[string]interface{}{"PostId": postId}); err != nil { 98 result.Err = model.NewAppError("SqlReactionStore.GetForPost", "store.sql_reaction.get_for_post.app_error", nil, "", http.StatusInternalServerError) 99 } else { 100 result.Data = reactions 101 } 102 103 return result 104 } 105 106 func (s *SqlSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 107 result := store.NewSupplierResult() 108 109 var reactions []*model.Reaction 110 111 if _, err := s.GetReplica().Select(&reactions, 112 `SELECT 113 * 114 FROM 115 Reactions 116 WHERE 117 EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil { 118 result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName", 119 "store.sql_reaction.delete_all_with_emoji_name.get_reactions.app_error", nil, 120 "emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError) 121 return result 122 } 123 124 if _, err := s.GetMaster().Exec( 125 `DELETE FROM 126 Reactions 127 WHERE 128 EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil { 129 result.Err = model.NewAppError("SqlReactionStore.DeleteAllWithEmojiName", 130 "store.sql_reaction.delete_all_with_emoji_name.delete_reactions.app_error", nil, 131 "emoji_name="+emojiName+", error="+err.Error(), http.StatusInternalServerError) 132 return result 133 } 134 135 for _, reaction := range reactions { 136 if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, 137 map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil { 138 mlog.Warn(fmt.Sprintf("Unable to update Post.HasReactions while removing reactions post_id=%v, error=%v", reaction.PostId, err.Error())) 139 } 140 } 141 142 return result 143 } 144 145 func (s *SqlSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { 146 result := store.NewSupplierResult() 147 148 var query string 149 if s.DriverName() == "postgres" { 150 query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))" 151 } else { 152 query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit" 153 } 154 155 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 156 if err != nil { 157 result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 158 } else { 159 rowsAffected, err1 := sqlResult.RowsAffected() 160 if err1 != nil { 161 result.Err = model.NewAppError("SqlReactionStore.PermanentDeleteBatch", "store.sql_reaction.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 162 result.Data = int64(0) 163 } else { 164 result.Data = rowsAffected 165 } 166 } 167 168 return result 169 } 170 171 func saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 172 if err := transaction.Insert(reaction); err != nil { 173 return err 174 } 175 176 return updatePostForReactionsOnInsert(transaction, reaction.PostId) 177 } 178 179 func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error { 180 if _, err := transaction.Exec( 181 `DELETE FROM 182 Reactions 183 WHERE 184 PostId = :PostId AND 185 UserId = :UserId AND 186 EmojiName = :EmojiName`, 187 map[string]interface{}{"PostId": reaction.PostId, "UserId": reaction.UserId, "EmojiName": reaction.EmojiName}); err != nil { 188 return err 189 } 190 191 return updatePostForReactionsOnDelete(transaction, reaction.PostId) 192 } 193 194 const ( 195 // Set HasReactions = true if and only if the post has reactions, update UpdateAt only if HasReactions changes 196 UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY = `UPDATE 197 Posts 198 SET 199 UpdateAt = (CASE 200 WHEN HasReactions != (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId) THEN :UpdateAt 201 ELSE UpdateAt 202 END), 203 HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId) 204 WHERE 205 Id = :PostId` 206 ) 207 208 func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error { 209 _, err := transaction.Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 210 211 return err 212 } 213 214 func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error { 215 _, err := transaction.Exec( 216 `UPDATE 217 Posts 218 SET 219 HasReactions = True, 220 UpdateAt = :UpdateAt 221 WHERE 222 Id = :PostId AND HasReactions = False`, 223 map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()}) 224 225 return err 226 }