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