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  }