github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/store/sqlstore/supplier_reactions.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 = 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  	err := store.WithDeadlockRetry(func() error {
    60  		transaction, err := s.GetMaster().Begin()
    61  		if err != nil {
    62  			return errors.Wrap(err, "begin_transaction")
    63  		}
    64  		defer finalizeTransaction(transaction)
    65  
    66  		if err := deleteReactionAndUpdatePost(transaction, reaction); err != nil {
    67  			return errors.Wrap(err, "deleteReactionAndUpdatePost")
    68  		}
    69  
    70  		if err := transaction.Commit(); err != nil {
    71  			return errors.Wrap(err, "commit_transaction")
    72  		}
    73  		return nil
    74  	})
    75  	if err != nil {
    76  		return nil, errors.Wrap(err, "failed to delete reaction")
    77  	}
    78  
    79  	return reaction, nil
    80  }
    81  
    82  func (s *SqlReactionStore) GetForPost(postId string, allowFromCache bool) ([]*model.Reaction, error) {
    83  	var reactions []*model.Reaction
    84  
    85  	if _, err := s.GetReplica().Select(&reactions,
    86  		`SELECT
    87  				*
    88  			FROM
    89  				Reactions
    90  			WHERE
    91  				PostId = :PostId
    92  			ORDER BY
    93  				CreateAt`, map[string]interface{}{"PostId": postId}); err != nil {
    94  		return nil, errors.Wrapf(err, "failed to get Reactions with postId=%s", postId)
    95  	}
    96  
    97  	return reactions, nil
    98  }
    99  
   100  func (s *SqlReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
   101  	keys, params := MapStringsToQueryParams(postIds, "postId")
   102  	var reactions []*model.Reaction
   103  
   104  	if _, err := s.GetReplica().Select(&reactions, `SELECT
   105  				*
   106  			FROM
   107  				Reactions
   108  			WHERE
   109  				PostId IN `+keys+`
   110  			ORDER BY
   111  				CreateAt`, params); err != nil {
   112  		return nil, errors.Wrap(err, "failed to get Reactions")
   113  	}
   114  	return reactions, nil
   115  }
   116  
   117  func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) error {
   118  	var reactions []*model.Reaction
   119  
   120  	if _, err := s.GetReplica().Select(&reactions,
   121  		`SELECT
   122  				*
   123  			FROM
   124  				Reactions
   125  			WHERE
   126  				EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName}); err != nil {
   127  		return errors.Wrapf(err, "failed to get Reactions with emojiName=%s", emojiName)
   128  	}
   129  
   130  	err := store.WithDeadlockRetry(func() error {
   131  		_, err := s.GetMaster().Exec(
   132  			`DELETE FROM
   133  				Reactions
   134  			WHERE
   135  				EmojiName = :EmojiName`, map[string]interface{}{"EmojiName": emojiName})
   136  		return err
   137  	})
   138  	if err != nil {
   139  		return errors.Wrapf(err, "failed to delete Reactions with emojiName=%s", emojiName)
   140  	}
   141  
   142  	for _, reaction := range reactions {
   143  		reaction := reaction
   144  		err := store.WithDeadlockRetry(func() error {
   145  			_, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY,
   146  				map[string]interface{}{
   147  					"PostId":   reaction.PostId,
   148  					"UpdateAt": model.GetMillis(),
   149  				})
   150  			return err
   151  		})
   152  		if err != nil {
   153  			mlog.Warn("Unable to update Post.HasReactions while removing reactions",
   154  				mlog.String("post_id", reaction.PostId),
   155  				mlog.Err(err))
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
   163  	var query string
   164  	if s.DriverName() == "postgres" {
   165  		query = "DELETE from Reactions WHERE CreateAt = any (array (SELECT CreateAt FROM Reactions WHERE CreateAt < :EndTime LIMIT :Limit))"
   166  	} else {
   167  		query = "DELETE from Reactions WHERE CreateAt < :EndTime LIMIT :Limit"
   168  	}
   169  
   170  	sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
   171  	if err != nil {
   172  		return 0, errors.Wrap(err, "failed to delete Reactions")
   173  	}
   174  
   175  	rowsAffected, err := sqlResult.RowsAffected()
   176  	if err != nil {
   177  		return 0, errors.Wrap(err, "unable to get rows affected for deleted Reactions")
   178  	}
   179  	return rowsAffected, nil
   180  }
   181  
   182  func saveReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error {
   183  	if err := transaction.Insert(reaction); err != nil {
   184  		return err
   185  	}
   186  
   187  	return updatePostForReactionsOnInsert(transaction, reaction.PostId)
   188  }
   189  
   190  func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.Reaction) error {
   191  	if _, err := transaction.Exec(
   192  		`DELETE FROM
   193  			Reactions
   194  		WHERE
   195  			PostId = :PostId AND
   196  			UserId = :UserId AND
   197  			EmojiName = :EmojiName`,
   198  		map[string]interface{}{"PostId": reaction.PostId, "UserId": reaction.UserId, "EmojiName": reaction.EmojiName}); err != nil {
   199  		return err
   200  	}
   201  
   202  	return updatePostForReactionsOnDelete(transaction, reaction.PostId)
   203  }
   204  
   205  const (
   206  	UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY = `UPDATE
   207  			Posts
   208  		SET
   209  			UpdateAt = :UpdateAt,
   210  			HasReactions = (SELECT count(0) > 0 FROM Reactions WHERE PostId = :PostId)
   211  		WHERE
   212  			Id = :PostId`
   213  )
   214  
   215  func updatePostForReactionsOnDelete(transaction *gorp.Transaction, postId string) error {
   216  	updateAt := model.GetMillis()
   217  	_, err := transaction.Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, map[string]interface{}{"PostId": postId, "UpdateAt": updateAt})
   218  	return err
   219  }
   220  
   221  func updatePostForReactionsOnInsert(transaction *gorp.Transaction, postId string) error {
   222  	_, err := transaction.Exec(
   223  		`UPDATE
   224  			Posts
   225  		SET
   226  			HasReactions = True,
   227  			UpdateAt = :UpdateAt
   228  		WHERE
   229  			Id = :PostId`,
   230  		map[string]interface{}{"PostId": postId, "UpdateAt": model.GetMillis()})
   231  
   232  	return err
   233  }