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  }