github.com/mattermost/mattermost-server/v5@v5.39.3/store/sqlstore/post_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  	"context"
     8  	"database/sql"
     9  	"fmt"
    10  	"reflect"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  
    16  	sq "github.com/Masterminds/squirrel"
    17  	"github.com/mattermost/gorp"
    18  	"github.com/pkg/errors"
    19  
    20  	"github.com/mattermost/mattermost-server/v5/einterfaces"
    21  	"github.com/mattermost/mattermost-server/v5/model"
    22  	"github.com/mattermost/mattermost-server/v5/shared/mlog"
    23  	"github.com/mattermost/mattermost-server/v5/store"
    24  	"github.com/mattermost/mattermost-server/v5/store/searchlayer"
    25  	"github.com/mattermost/mattermost-server/v5/utils"
    26  )
    27  
    28  type SqlPostStore struct {
    29  	*SqlStore
    30  	metrics           einterfaces.MetricsInterface
    31  	maxPostSizeOnce   sync.Once
    32  	maxPostSizeCached int
    33  }
    34  
    35  type postWithExtra struct {
    36  	ThreadReplyCount   int64
    37  	IsFollowing        *bool
    38  	ThreadParticipants model.StringArray
    39  	model.Post
    40  }
    41  
    42  func (s *SqlPostStore) ClearCaches() {
    43  }
    44  
    45  func postSliceColumnsWithTypes() []struct {
    46  	Name string
    47  	Type reflect.Kind
    48  } {
    49  	return []struct {
    50  		Name string
    51  		Type reflect.Kind
    52  	}{
    53  		{"Id", reflect.String},
    54  		{"CreateAt", reflect.Int64},
    55  		{"UpdateAt", reflect.Int64},
    56  		{"EditAt", reflect.Int64},
    57  		{"DeleteAt", reflect.Int64},
    58  		{"IsPinned", reflect.Bool},
    59  		{"UserId", reflect.String},
    60  		{"ChannelId", reflect.String},
    61  		{"RootId", reflect.String},
    62  		{"ParentId", reflect.String},
    63  		{"OriginalId", reflect.String},
    64  		{"Message", reflect.String},
    65  		{"Type", reflect.String},
    66  		{"Props", reflect.Map},
    67  		{"Hashtags", reflect.String},
    68  		{"Filenames", reflect.Slice},
    69  		{"FileIds", reflect.Slice},
    70  		{"HasReactions", reflect.Bool},
    71  		{"RemoteId", reflect.String},
    72  	}
    73  }
    74  
    75  func postToSlice(post *model.Post) []interface{} {
    76  	return []interface{}{
    77  		post.Id,
    78  		post.CreateAt,
    79  		post.UpdateAt,
    80  		post.EditAt,
    81  		post.DeleteAt,
    82  		post.IsPinned,
    83  		post.UserId,
    84  		post.ChannelId,
    85  		post.RootId,
    86  		post.ParentId,
    87  		post.OriginalId,
    88  		post.Message,
    89  		post.Type,
    90  		model.StringInterfaceToJson(post.Props),
    91  		post.Hashtags,
    92  		model.ArrayToJson(post.Filenames),
    93  		model.ArrayToJson(post.FileIds),
    94  		post.HasReactions,
    95  		post.RemoteId,
    96  	}
    97  }
    98  
    99  func postSliceColumns() []string {
   100  	colInfos := postSliceColumnsWithTypes()
   101  	cols := make([]string, len(colInfos))
   102  	for i, colInfo := range colInfos {
   103  		cols[i] = colInfo.Name
   104  	}
   105  	return cols
   106  }
   107  
   108  func postSliceCoalesceQuery() string {
   109  	colInfos := postSliceColumnsWithTypes()
   110  	cols := make([]string, len(colInfos))
   111  	for i, colInfo := range colInfos {
   112  		var defaultValue string
   113  		switch colInfo.Type {
   114  		case reflect.String:
   115  			defaultValue = "''"
   116  		case reflect.Int64:
   117  			defaultValue = "0"
   118  		case reflect.Bool:
   119  			defaultValue = "false"
   120  		case reflect.Map:
   121  			defaultValue = "'{}'"
   122  		case reflect.Slice:
   123  			defaultValue = "'[]'"
   124  		}
   125  		cols[i] = "COALESCE(Posts." + colInfo.Name + "," + defaultValue + ") AS " + colInfo.Name
   126  	}
   127  	return strings.Join(cols, ",")
   128  }
   129  
   130  func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
   131  	s := &SqlPostStore{
   132  		SqlStore:          sqlStore,
   133  		metrics:           metrics,
   134  		maxPostSizeCached: model.POST_MESSAGE_MAX_RUNES_V1,
   135  	}
   136  
   137  	for _, db := range sqlStore.GetAllConns() {
   138  		table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
   139  		table.ColMap("Id").SetMaxSize(26)
   140  		table.ColMap("UserId").SetMaxSize(26)
   141  		table.ColMap("ChannelId").SetMaxSize(26)
   142  		table.ColMap("RootId").SetMaxSize(26)
   143  		table.ColMap("ParentId").SetMaxSize(26)
   144  		table.ColMap("OriginalId").SetMaxSize(26)
   145  		table.ColMap("Message").SetMaxSize(model.POST_MESSAGE_MAX_BYTES_V2)
   146  		table.ColMap("Type").SetMaxSize(26)
   147  		table.ColMap("Hashtags").SetMaxSize(1000)
   148  		table.ColMap("Props").SetMaxSize(8000)
   149  		table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES)
   150  		table.ColMap("FileIds").SetMaxSize(300)
   151  		table.ColMap("RemoteId").SetMaxSize(26)
   152  	}
   153  
   154  	return s
   155  }
   156  
   157  func (s *SqlPostStore) createIndexesIfNotExists() {
   158  	s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt")
   159  	s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
   160  	s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt")
   161  	s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
   162  	s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
   163  	s.CreateIndexIfNotExists("idx_posts_is_pinned", "Posts", "IsPinned")
   164  
   165  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_update_at", "Posts", []string{"ChannelId", "UpdateAt"})
   166  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_delete_at_create_at", "Posts", []string{"ChannelId", "DeleteAt", "CreateAt"})
   167  
   168  	s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
   169  	s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
   170  }
   171  
   172  func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
   173  	channelNewPosts := make(map[string]int)
   174  	channelNewRootPosts := make(map[string]int)
   175  	maxDateNewPosts := make(map[string]int64)
   176  	rootIds := make(map[string]int)
   177  	maxDateRootIds := make(map[string]int64)
   178  	for idx, post := range posts {
   179  		if post.Id != "" && !post.IsRemote() {
   180  			return nil, idx, store.NewErrInvalidInput("Post", "id", post.Id)
   181  		}
   182  		post.PreSave()
   183  		maxPostSize := s.GetMaxPostSize()
   184  		if err := post.IsValid(maxPostSize); err != nil {
   185  			return nil, idx, err
   186  		}
   187  
   188  		if currentChannelCount, ok := channelNewPosts[post.ChannelId]; !ok {
   189  			if post.IsJoinLeaveMessage() {
   190  				channelNewPosts[post.ChannelId] = 0
   191  			} else {
   192  				channelNewPosts[post.ChannelId] = 1
   193  			}
   194  			maxDateNewPosts[post.ChannelId] = post.CreateAt
   195  		} else {
   196  			if !post.IsJoinLeaveMessage() {
   197  				channelNewPosts[post.ChannelId] = currentChannelCount + 1
   198  			}
   199  			if post.CreateAt > maxDateNewPosts[post.ChannelId] {
   200  				maxDateNewPosts[post.ChannelId] = post.CreateAt
   201  			}
   202  		}
   203  
   204  		if post.RootId == "" {
   205  			if currentChannelCount, ok := channelNewRootPosts[post.ChannelId]; !ok {
   206  				if post.IsJoinLeaveMessage() {
   207  					channelNewRootPosts[post.ChannelId] = 0
   208  				} else {
   209  					channelNewRootPosts[post.ChannelId] = 1
   210  				}
   211  			} else {
   212  				if !post.IsJoinLeaveMessage() {
   213  					channelNewRootPosts[post.ChannelId] = currentChannelCount + 1
   214  				}
   215  			}
   216  			continue
   217  		}
   218  
   219  		if currentRootCount, ok := rootIds[post.RootId]; !ok {
   220  			rootIds[post.RootId] = 1
   221  			maxDateRootIds[post.RootId] = post.CreateAt
   222  		} else {
   223  			rootIds[post.RootId] = currentRootCount + 1
   224  			if post.CreateAt > maxDateRootIds[post.RootId] {
   225  				maxDateRootIds[post.RootId] = post.CreateAt
   226  			}
   227  		}
   228  	}
   229  
   230  	builder := s.getQueryBuilder().Insert("Posts").Columns(postSliceColumns()...)
   231  	for _, post := range posts {
   232  		builder = builder.Values(postToSlice(post)...)
   233  	}
   234  	query, args, err := builder.ToSql()
   235  	if err != nil {
   236  		return nil, -1, errors.Wrap(err, "post_tosql")
   237  	}
   238  
   239  	transaction, err := s.GetMaster().Begin()
   240  	if err != nil {
   241  		return posts, -1, errors.Wrap(err, "begin_transaction")
   242  	}
   243  
   244  	defer finalizeTransaction(transaction)
   245  
   246  	if _, err = transaction.Exec(query, args...); err != nil {
   247  		return nil, -1, errors.Wrap(err, "failed to save Post")
   248  	}
   249  
   250  	if err = s.updateThreadsFromPosts(transaction, posts); err != nil {
   251  		mlog.Warn("Error updating posts, thread update failed", mlog.Err(err))
   252  	}
   253  
   254  	if err = transaction.Commit(); err != nil {
   255  		// don't need to rollback here since the transaction is already closed
   256  		return posts, -1, errors.Wrap(err, "commit_transaction")
   257  	}
   258  
   259  	for channelId, count := range channelNewPosts {
   260  		countRoot := channelNewRootPosts[channelId]
   261  
   262  		if _, err = s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count, TotalMsgCountRoot = TotalMsgCountRoot + :CountRoot WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": maxDateNewPosts[channelId], "ChannelId": channelId, "Count": count, "CountRoot": countRoot}); err != nil {
   263  			mlog.Warn("Error updating Channel LastPostAt.", mlog.Err(err))
   264  		}
   265  	}
   266  
   267  	for rootId := range rootIds {
   268  		if _, err = s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": maxDateRootIds[rootId], "RootId": rootId}); err != nil {
   269  			mlog.Warn("Error updating Post UpdateAt.", mlog.Err(err))
   270  		}
   271  	}
   272  
   273  	var unknownRepliesPosts []*model.Post
   274  	for _, post := range posts {
   275  		if post.RootId == "" {
   276  			count, ok := rootIds[post.Id]
   277  			if ok {
   278  				post.ReplyCount += int64(count)
   279  			}
   280  		} else {
   281  			unknownRepliesPosts = append(unknownRepliesPosts, post)
   282  		}
   283  	}
   284  
   285  	if len(unknownRepliesPosts) > 0 {
   286  		if err := s.populateReplyCount(unknownRepliesPosts); err != nil {
   287  			mlog.Warn("Unable to populate the reply count in some posts.", mlog.Err(err))
   288  		}
   289  	}
   290  
   291  	return posts, -1, nil
   292  }
   293  
   294  func (s *SqlPostStore) Save(post *model.Post) (*model.Post, error) {
   295  	posts, _, err := s.SaveMultiple([]*model.Post{post})
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	return posts[0], nil
   300  }
   301  
   302  func (s *SqlPostStore) populateReplyCount(posts []*model.Post) error {
   303  	rootIds := []string{}
   304  	for _, post := range posts {
   305  		rootIds = append(rootIds, post.RootId)
   306  	}
   307  	countList := []struct {
   308  		RootId string
   309  		Count  int64
   310  	}{}
   311  	query := s.getQueryBuilder().Select("RootId, COUNT(Id) AS Count").From("Posts").Where(sq.Eq{"RootId": rootIds}).Where(sq.Eq{"DeleteAt": 0}).GroupBy("RootId")
   312  
   313  	queryString, args, err := query.ToSql()
   314  	if err != nil {
   315  		return errors.Wrap(err, "post_tosql")
   316  	}
   317  	_, err = s.GetMaster().Select(&countList, queryString, args...)
   318  	if err != nil {
   319  		return errors.Wrap(err, "failed to count Posts")
   320  	}
   321  
   322  	counts := map[string]int64{}
   323  	for _, count := range countList {
   324  		counts[count.RootId] = count.Count
   325  	}
   326  
   327  	for _, post := range posts {
   328  		count, ok := counts[post.RootId]
   329  		if !ok {
   330  			post.ReplyCount = 0
   331  		}
   332  		post.ReplyCount = count
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, error) {
   339  	newPost.UpdateAt = model.GetMillis()
   340  	newPost.PreCommit()
   341  
   342  	oldPost.DeleteAt = newPost.UpdateAt
   343  	oldPost.UpdateAt = newPost.UpdateAt
   344  	oldPost.OriginalId = oldPost.Id
   345  	oldPost.Id = model.NewId()
   346  	oldPost.PreCommit()
   347  
   348  	maxPostSize := s.GetMaxPostSize()
   349  
   350  	if err := newPost.IsValid(maxPostSize); err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	if _, err := s.GetMaster().Update(newPost); err != nil {
   355  		return nil, errors.Wrapf(err, "failed to update Post with id=%s", newPost.Id)
   356  	}
   357  
   358  	time := model.GetMillis()
   359  	s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt  WHERE Id = :ChannelId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ChannelId": newPost.ChannelId})
   360  
   361  	if newPost.RootId != "" {
   362  		s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId AND UpdateAt < :UpdateAt", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId})
   363  		s.GetMaster().Exec("UPDATE Threads SET LastReplyAt = :UpdateAt WHERE PostId = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId})
   364  	}
   365  
   366  	// mark the old post as deleted
   367  	s.GetMaster().Insert(oldPost)
   368  
   369  	return newPost, nil
   370  }
   371  
   372  func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
   373  	updateAt := model.GetMillis()
   374  	maxPostSize := s.GetMaxPostSize()
   375  	for idx, post := range posts {
   376  		post.UpdateAt = updateAt
   377  		if appErr := post.IsValid(maxPostSize); appErr != nil {
   378  			return nil, idx, appErr
   379  		}
   380  	}
   381  
   382  	tx, err := s.GetMaster().Begin()
   383  	if err != nil {
   384  		return nil, -1, errors.Wrap(err, "begin_transaction")
   385  	}
   386  	for idx, post := range posts {
   387  		if _, err = tx.Update(post); err != nil {
   388  			txErr := tx.Rollback()
   389  			if txErr != nil {
   390  				return nil, idx, errors.Wrap(txErr, "rollback_transaction")
   391  			}
   392  
   393  			return nil, idx, errors.Wrap(err, "failed to update Post")
   394  		}
   395  		if post.RootId != "" {
   396  			tx.Exec("UPDATE Threads SET LastReplyAt = :UpdateAt WHERE PostId = :RootId", map[string]interface{}{"UpdateAt": updateAt, "RootId": post.Id})
   397  		}
   398  	}
   399  	err = tx.Commit()
   400  	if err != nil {
   401  		return nil, -1, errors.Wrap(err, "commit_transaction")
   402  	}
   403  
   404  	return posts, -1, nil
   405  }
   406  
   407  func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, error) {
   408  	posts, _, err := s.OverwriteMultiple([]*model.Post{post})
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	return posts[0], nil
   414  }
   415  
   416  func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, error) {
   417  	pl := model.NewPostList()
   418  
   419  	var posts []*model.Post
   420  	if _, err := s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit}); err != nil {
   421  		return nil, errors.Wrap(err, "failed to find Posts")
   422  	}
   423  
   424  	for _, post := range posts {
   425  		pl.AddPost(post)
   426  		pl.AddOrder(post.Id)
   427  	}
   428  
   429  	return pl, nil
   430  }
   431  
   432  func (s *SqlPostStore) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, error) {
   433  	pl := model.NewPostList()
   434  
   435  	var posts []*model.Post
   436  
   437  	query := `
   438              SELECT
   439                  A.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN A.RootId = '' THEN A.Id ELSE A.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
   440              FROM
   441                  (SELECT
   442                      *
   443                  FROM
   444                      Posts
   445                  WHERE
   446                      Id
   447                  IN
   448                      (SELECT
   449                          Name
   450                      FROM
   451                          Preferences
   452                      WHERE
   453                          UserId = :UserId
   454                          AND Category = :Category)
   455                          AND DeleteAt = 0
   456                  ) as A
   457              INNER JOIN Channels as B
   458                  ON B.Id = A.ChannelId
   459              WHERE B.TeamId = :TeamId OR B.TeamId = ''
   460              ORDER BY CreateAt DESC
   461              LIMIT :Limit OFFSET :Offset`
   462  
   463  	if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit, "TeamId": teamId}); err != nil {
   464  		return nil, errors.Wrap(err, "failed to find Posts")
   465  	}
   466  
   467  	for _, post := range posts {
   468  		pl.AddPost(post)
   469  		pl.AddOrder(post.Id)
   470  	}
   471  
   472  	return pl, nil
   473  }
   474  
   475  func (s *SqlPostStore) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, error) {
   476  	pl := model.NewPostList()
   477  
   478  	var posts []*model.Post
   479  	query := `
   480  		SELECT
   481  			*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
   482  		FROM Posts p
   483  		WHERE
   484  			Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category)
   485  			AND ChannelId = :ChannelId
   486  			AND DeleteAt = 0
   487  		ORDER BY CreateAt DESC
   488  		LIMIT :Limit OFFSET :Offset`
   489  
   490  	if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
   491  		return nil, errors.Wrap(err, "failed to find Posts")
   492  	}
   493  	for _, post := range posts {
   494  		pl.AddPost(post)
   495  		pl.AddOrder(post.Id)
   496  	}
   497  
   498  	return pl, nil
   499  }
   500  func (s *SqlPostStore) getPostWithCollapsedThreads(id, userID string, extended bool) (*model.PostList, error) {
   501  	if id == "" {
   502  		return nil, store.NewErrInvalidInput("Post", "id", id)
   503  	}
   504  
   505  	var columns []string
   506  	for _, c := range postSliceColumns() {
   507  		columns = append(columns, "Posts."+c)
   508  	}
   509  	columns = append(columns,
   510  		"COALESCE(Threads.ReplyCount, 0) as ThreadReplyCount",
   511  		"COALESCE(Threads.LastReplyAt, 0) as LastReplyAt",
   512  		"COALESCE(Threads.Participants, '[]') as ThreadParticipants",
   513  		"COALESCE(ThreadMemberships.Following, false) as IsFollowing",
   514  	)
   515  	var post postWithExtra
   516  
   517  	postFetchQuery, args, _ := s.getQueryBuilder().
   518  		Select(columns...).
   519  		From("Posts").
   520  		LeftJoin("Threads ON Threads.PostId = Id").
   521  		LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Id AND ThreadMemberships.UserId = ?", userID).
   522  		Where(sq.Eq{"DeleteAt": 0}).
   523  		Where(sq.Eq{"Id": id}).ToSql()
   524  
   525  	err := s.GetReplica().SelectOne(&post, postFetchQuery, args...)
   526  	if err != nil {
   527  		if err == sql.ErrNoRows {
   528  			return nil, store.NewErrNotFound("Post", id)
   529  		}
   530  
   531  		return nil, errors.Wrapf(err, "failed to get Post with id=%s", id)
   532  	}
   533  
   534  	var posts []*model.Post
   535  	_, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE Posts.RootId = :RootId AND DeleteAt = 0", map[string]interface{}{"RootId": id})
   536  	if err != nil {
   537  		return nil, errors.Wrapf(err, "failed to find Posts for thread %s", id)
   538  	}
   539  
   540  	list, err := s.prepareThreadedResponse([]*postWithExtra{&post}, extended, false)
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	for _, p := range posts {
   545  		list.AddPost(p)
   546  		list.AddOrder(p.Id)
   547  	}
   548  	return list, nil
   549  }
   550  
   551  func (s *SqlPostStore) Get(ctx context.Context, id string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
   552  	if collapsedThreads {
   553  		return s.getPostWithCollapsedThreads(id, userID, collapsedThreadsExtended)
   554  	}
   555  	pl := model.NewPostList()
   556  
   557  	if id == "" {
   558  		return nil, store.NewErrInvalidInput("Post", "id", id)
   559  	}
   560  
   561  	var post model.Post
   562  	postFetchQuery := "SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id = :Id AND p.DeleteAt = 0"
   563  	err := s.DBFromContext(ctx).SelectOne(&post, postFetchQuery, map[string]interface{}{"Id": id})
   564  	if err != nil {
   565  		if err == sql.ErrNoRows {
   566  			return nil, store.NewErrNotFound("Post", id)
   567  		}
   568  
   569  		return nil, errors.Wrapf(err, "failed to get Post with id=%s", id)
   570  	}
   571  	pl.AddPost(&post)
   572  	pl.AddOrder(id)
   573  	if !skipFetchThreads {
   574  		rootId := post.RootId
   575  
   576  		if rootId == "" {
   577  			rootId = post.Id
   578  		}
   579  
   580  		if rootId == "" {
   581  			return nil, errors.Wrapf(err, "invalid rootId with value=%s", rootId)
   582  		}
   583  
   584  		var posts []*model.Post
   585  		_, err = s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId})
   586  		if err != nil {
   587  			return nil, errors.Wrap(err, "failed to find Posts")
   588  		}
   589  
   590  		for _, p := range posts {
   591  			pl.AddPost(p)
   592  			pl.AddOrder(p.Id)
   593  		}
   594  	}
   595  	return pl, nil
   596  }
   597  
   598  func (s *SqlPostStore) GetSingle(id string, inclDeleted bool) (*model.Post, error) {
   599  	query := s.getQueryBuilder().
   600  		Select("p.*").
   601  		From("Posts p").
   602  		Where(sq.Eq{"p.Id": id})
   603  
   604  	replyCountSubQuery := s.getQueryBuilder().
   605  		Select("COUNT(Posts.Id)").
   606  		From("Posts").
   607  		Where(sq.Expr("Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0"))
   608  
   609  	if !inclDeleted {
   610  		query = query.Where(sq.Eq{"p.DeleteAt": 0})
   611  	}
   612  	query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
   613  
   614  	queryString, args, err := query.ToSql()
   615  	if err != nil {
   616  		return nil, errors.Wrap(err, "getsingleincldeleted_tosql")
   617  	}
   618  
   619  	var post model.Post
   620  	err = s.GetReplica().SelectOne(&post, queryString, args...)
   621  	if err != nil {
   622  		if err == sql.ErrNoRows {
   623  			return nil, store.NewErrNotFound("Post", id)
   624  		}
   625  
   626  		return nil, errors.Wrapf(err, "failed to get Post with id=%s", id)
   627  	}
   628  	return &post, nil
   629  }
   630  
   631  type etagPosts struct {
   632  	Id       string
   633  	UpdateAt int64
   634  }
   635  
   636  //nolint:unparam
   637  func (s *SqlPostStore) InvalidateLastPostTimeCache(channelId string) {
   638  }
   639  
   640  //nolint:unparam
   641  func (s *SqlPostStore) GetEtag(channelId string, allowFromCache, collapsedThreads bool) string {
   642  	q := s.getQueryBuilder().Select("Id", "UpdateAt").From("Posts").Where(sq.Eq{"ChannelId": channelId}).OrderBy("UpdateAt DESC").Limit(1)
   643  	if collapsedThreads {
   644  		q.Where(sq.Eq{"RootId": ""})
   645  	}
   646  	sql, args, _ := q.ToSql()
   647  
   648  	var et etagPosts
   649  	err := s.GetReplica().SelectOne(&et, sql, args...)
   650  	var result string
   651  	if err != nil {
   652  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   653  	} else {
   654  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   655  	}
   656  
   657  	return result
   658  }
   659  
   660  func (s *SqlPostStore) Delete(postId string, time int64, deleteByID string) error {
   661  	var post model.Post
   662  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId})
   663  	if err != nil {
   664  		if err == sql.ErrNoRows {
   665  			return store.NewErrNotFound("Post", postId)
   666  		}
   667  
   668  		return errors.Wrapf(err, "failed to delete Post with id=%s", postId)
   669  	}
   670  
   671  	post.AddProp(model.POST_PROPS_DELETE_BY, deleteByID)
   672  
   673  	_, err = s.GetMaster().Exec("UPDATE Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt, Props = :Props WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "RootId": postId, "Props": model.StringInterfaceToJson(post.GetProps())})
   674  	if err != nil {
   675  		return errors.Wrap(err, "failed to update Posts")
   676  	}
   677  
   678  	return s.cleanupThreads(post.Id, post.RootId, false)
   679  }
   680  
   681  func (s *SqlPostStore) permanentDelete(postId string) error {
   682  	var post model.Post
   683  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId})
   684  	if err != nil && err != sql.ErrNoRows {
   685  		return errors.Wrapf(err, "failed to get Post with id=%s", postId)
   686  	}
   687  	if err = s.cleanupThreads(post.Id, post.RootId, true); err != nil {
   688  		return errors.Wrapf(err, "failed to cleanup threads for Post with id=%s", postId)
   689  	}
   690  
   691  	if _, err = s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId}); err != nil {
   692  		return errors.Wrapf(err, "failed to delete Post with id=%s", postId)
   693  	}
   694  
   695  	return nil
   696  }
   697  
   698  type postIds struct {
   699  	Id     string
   700  	RootId string
   701  	UserId string
   702  }
   703  
   704  func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) error {
   705  	results := []postIds{}
   706  	_, err := s.GetMaster().Select(&results, "Select Id, RootId FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   707  	if err != nil {
   708  		return errors.Wrapf(err, "failed to fetch Posts with userId=%s", userId)
   709  	}
   710  
   711  	for _, ids := range results {
   712  		if err = s.cleanupThreads(ids.Id, ids.RootId, true); err != nil {
   713  			return err
   714  		}
   715  	}
   716  
   717  	_, err = s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   718  	if err != nil {
   719  		return errors.Wrapf(err, "failed to delete Posts with userId=%s", userId)
   720  	}
   721  	return nil
   722  }
   723  
   724  func (s *SqlPostStore) PermanentDeleteByUser(userId string) error {
   725  	// First attempt to delete all the comments for a user
   726  	if err := s.permanentDeleteAllCommentByUser(userId); err != nil {
   727  		return err
   728  	}
   729  
   730  	// Now attempt to delete all the root posts for a user. This will also
   731  	// delete all the comments for each post
   732  	found := true
   733  	count := 0
   734  
   735  	for found {
   736  		var ids []string
   737  		_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   738  		if err != nil {
   739  			return errors.Wrapf(err, "failed to find Posts with userId=%s", userId)
   740  		}
   741  
   742  		found = false
   743  		for _, id := range ids {
   744  			found = true
   745  			if err = s.permanentDelete(id); err != nil {
   746  				return err
   747  			}
   748  		}
   749  
   750  		// This is a fail safe, give up if more than 10k messages
   751  		count++
   752  		if count >= 10 {
   753  			return errors.Wrapf(err, "too many Posts to delete with userId=%s", userId)
   754  		}
   755  	}
   756  
   757  	return nil
   758  }
   759  
   760  func (s *SqlPostStore) PermanentDeleteByChannel(channelId string) error {
   761  	results := []postIds{}
   762  	_, err := s.GetMaster().Select(&results, "SELECT Id, RootId, UserId FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
   763  	if err != nil {
   764  		return errors.Wrapf(err, "failed to fetch Posts with channelId=%s", channelId)
   765  	}
   766  
   767  	for _, ids := range results {
   768  		if err = s.cleanupThreads(ids.Id, ids.RootId, true); err != nil {
   769  			return err
   770  		}
   771  	}
   772  
   773  	if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   774  		return errors.Wrapf(err, "failed to delete Posts with channelId=%s", channelId)
   775  	}
   776  	return nil
   777  }
   778  
   779  func (s *SqlPostStore) prepareThreadedResponse(posts []*postWithExtra, extended, reversed bool) (*model.PostList, error) {
   780  	list := model.NewPostList()
   781  	var userIds []string
   782  	userIdMap := map[string]bool{}
   783  	for _, thread := range posts {
   784  		for _, participantId := range thread.ThreadParticipants {
   785  			if _, ok := userIdMap[participantId]; !ok {
   786  				userIdMap[participantId] = true
   787  				userIds = append(userIds, participantId)
   788  			}
   789  		}
   790  	}
   791  	var users []*model.User
   792  	if extended {
   793  		var err error
   794  		users, err = s.User().GetProfileByIds(context.Background(), userIds, &store.UserGetByIdsOpts{}, true)
   795  		if err != nil {
   796  			return nil, err
   797  		}
   798  	} else {
   799  		for _, userId := range userIds {
   800  			users = append(users, &model.User{Id: userId})
   801  		}
   802  	}
   803  	processPost := func(p *postWithExtra) error {
   804  		p.Post.ReplyCount = p.ThreadReplyCount
   805  		if p.IsFollowing != nil {
   806  			p.Post.IsFollowing = model.NewBool(*p.IsFollowing)
   807  		}
   808  		for _, th := range p.ThreadParticipants {
   809  			var participant *model.User
   810  			for _, u := range users {
   811  				if u.Id == th {
   812  					participant = u
   813  					break
   814  				}
   815  			}
   816  			if participant == nil {
   817  				return errors.New("cannot find thread participant with id=" + th)
   818  			}
   819  			p.Post.Participants = append(p.Post.Participants, participant)
   820  		}
   821  		return nil
   822  	}
   823  
   824  	l := len(posts)
   825  	for i := range posts {
   826  		idx := i
   827  		// We need to flip the order if we selected backwards
   828  
   829  		if reversed {
   830  			idx = l - i - 1
   831  		}
   832  		if err := processPost(posts[idx]); err != nil {
   833  			return nil, err
   834  		}
   835  		list.AddPost(&posts[idx].Post)
   836  		list.AddOrder(posts[idx].Id)
   837  	}
   838  
   839  	return list, nil
   840  }
   841  
   842  func (s *SqlPostStore) getPostsCollapsedThreads(options model.GetPostsOptions) (*model.PostList, error) {
   843  	var columns []string
   844  	for _, c := range postSliceColumns() {
   845  		columns = append(columns, "Posts."+c)
   846  	}
   847  	columns = append(columns,
   848  		"COALESCE(Threads.ReplyCount, 0) as ThreadReplyCount",
   849  		"COALESCE(Threads.LastReplyAt, 0) as LastReplyAt",
   850  		"COALESCE(Threads.Participants, '[]') as ThreadParticipants",
   851  		"COALESCE(ThreadMemberships.Following, false) as IsFollowing",
   852  	)
   853  	var posts []*postWithExtra
   854  	offset := options.PerPage * options.Page
   855  
   856  	postFetchQuery, args, _ := s.getQueryBuilder().
   857  		Select(columns...).
   858  		From("Posts").
   859  		LeftJoin("Threads ON Threads.PostId = Id").
   860  		LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Id AND ThreadMemberships.UserId = ?", options.UserId).
   861  		Where(sq.Eq{"DeleteAt": 0}).
   862  		Where(sq.Eq{"Posts.ChannelId": options.ChannelId}).
   863  		Where(sq.Eq{"RootId": ""}).
   864  		Limit(uint64(options.PerPage)).
   865  		Offset(uint64(offset)).
   866  		OrderBy("CreateAt DESC").ToSql()
   867  
   868  	_, err := s.GetReplica().Select(&posts, postFetchQuery, args...)
   869  
   870  	if err != nil {
   871  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
   872  	}
   873  
   874  	return s.prepareThreadedResponse(posts, options.CollapsedThreadsExtended, false)
   875  }
   876  
   877  func (s *SqlPostStore) GetPosts(options model.GetPostsOptions, _ bool) (*model.PostList, error) {
   878  	if options.PerPage > 1000 {
   879  		return nil, store.NewErrInvalidInput("Post", "<options.PerPage>", options.PerPage)
   880  	}
   881  	if options.CollapsedThreads {
   882  		return s.getPostsCollapsedThreads(options)
   883  	}
   884  	offset := options.PerPage * options.Page
   885  
   886  	rpc := make(chan store.StoreResult, 1)
   887  	go func() {
   888  		posts, err := s.getRootPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
   889  		rpc <- store.StoreResult{Data: posts, NErr: err}
   890  		close(rpc)
   891  	}()
   892  	cpc := make(chan store.StoreResult, 1)
   893  	go func() {
   894  		posts, err := s.getParentsPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
   895  		cpc <- store.StoreResult{Data: posts, NErr: err}
   896  		close(cpc)
   897  	}()
   898  
   899  	list := model.NewPostList()
   900  
   901  	rpr := <-rpc
   902  	if rpr.NErr != nil {
   903  		return nil, rpr.NErr
   904  	}
   905  
   906  	cpr := <-cpc
   907  	if cpr.NErr != nil {
   908  		return nil, cpr.NErr
   909  	}
   910  
   911  	posts := rpr.Data.([]*model.Post)
   912  	parents := cpr.Data.([]*model.Post)
   913  
   914  	for _, p := range posts {
   915  		list.AddPost(p)
   916  		list.AddOrder(p.Id)
   917  	}
   918  
   919  	for _, p := range parents {
   920  		list.AddPost(p)
   921  	}
   922  
   923  	list.MakeNonNil()
   924  
   925  	return list, nil
   926  }
   927  
   928  func (s *SqlPostStore) getPostsSinceCollapsedThreads(options model.GetPostsSinceOptions) (*model.PostList, error) {
   929  	var columns []string
   930  	for _, c := range postSliceColumns() {
   931  		columns = append(columns, "Posts."+c)
   932  	}
   933  	columns = append(columns,
   934  		"COALESCE(Threads.ReplyCount, 0) as ThreadReplyCount",
   935  		"COALESCE(Threads.LastReplyAt, 0) as LastReplyAt",
   936  		"COALESCE(Threads.Participants, '[]') as ThreadParticipants",
   937  		"COALESCE(ThreadMemberships.Following, false) as IsFollowing",
   938  	)
   939  	var posts []*postWithExtra
   940  
   941  	postFetchQuery, args, _ := s.getQueryBuilder().
   942  		Select(columns...).
   943  		From("Posts").
   944  		LeftJoin("Threads ON Threads.PostId = Id").
   945  		LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Id AND ThreadMemberships.UserId = ?", options.UserId).
   946  		Where(sq.Eq{"DeleteAt": 0}).
   947  		Where(sq.Eq{"Posts.ChannelId": options.ChannelId}).
   948  		Where(sq.Gt{"UpdateAt": options.Time}).
   949  		Where(sq.Eq{"RootId": ""}).
   950  		OrderBy("CreateAt DESC").ToSql()
   951  
   952  	_, err := s.GetReplica().Select(&posts, postFetchQuery, args...)
   953  
   954  	if err != nil {
   955  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
   956  	}
   957  	return s.prepareThreadedResponse(posts, options.CollapsedThreadsExtended, false)
   958  }
   959  
   960  //nolint:unparam
   961  func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool) (*model.PostList, error) {
   962  	if options.CollapsedThreads {
   963  		return s.getPostsSinceCollapsedThreads(options)
   964  	}
   965  
   966  	var posts []*model.Post
   967  
   968  	order := "DESC"
   969  	if options.SortAscending {
   970  		order = "ASC"
   971  	}
   972  
   973  	replyCountQuery1 := ""
   974  	replyCountQuery2 := ""
   975  	if options.SkipFetchThreads {
   976  		replyCountQuery1 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p1.RootId = '' THEN p1.Id ELSE p1.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   977  		replyCountQuery2 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN cte.RootId = '' THEN cte.Id ELSE cte.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   978  	}
   979  	var query string
   980  
   981  	// union of IDs and then join to get full posts is faster in mysql
   982  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   983  		query = `SELECT *` + replyCountQuery1 + ` FROM Posts p1 JOIN (
   984  			(SELECT
   985                Id
   986  			  FROM
   987  				  Posts p2
   988  			  WHERE
   989  				  (UpdateAt > :Time
   990  					  AND ChannelId = :ChannelId)
   991  				  LIMIT 1000)
   992  			  UNION
   993  				  (SELECT
   994  					  Id
   995  				  FROM
   996  					  Posts p3
   997  				  WHERE
   998  					  Id
   999  				  IN
  1000  					  (SELECT * FROM (SELECT
  1001  						  RootId
  1002  					  FROM
  1003  						  Posts
  1004  					  WHERE
  1005  						  UpdateAt > :Time
  1006  							  AND ChannelId = :ChannelId
  1007  					  LIMIT 1000) temp_tab))
  1008  			) j ON p1.Id = j.Id
  1009            ORDER BY CreateAt ` + order
  1010  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1011  		query = `WITH cte AS (SELECT
  1012  		       *
  1013  		FROM
  1014  		       Posts
  1015  		WHERE
  1016  		       UpdateAt > :Time AND ChannelId = :ChannelId
  1017  		       LIMIT 1000)
  1018  		(SELECT *` + replyCountQuery2 + ` FROM cte)
  1019  		UNION
  1020  		(SELECT *` + replyCountQuery1 + ` FROM Posts p1 WHERE id in (SELECT rootid FROM cte))
  1021  		ORDER BY CreateAt ` + order
  1022  	}
  1023  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"ChannelId": options.ChannelId, "Time": options.Time})
  1024  
  1025  	if err != nil {
  1026  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
  1027  	}
  1028  
  1029  	list := model.NewPostList()
  1030  
  1031  	for _, p := range posts {
  1032  		list.AddPost(p)
  1033  		if p.UpdateAt > options.Time {
  1034  			list.AddOrder(p.Id)
  1035  		}
  1036  	}
  1037  
  1038  	return list, nil
  1039  }
  1040  
  1041  func (s *SqlPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
  1042  	query := `
  1043  		SELECT EXISTS (SELECT 1
  1044  				FROM
  1045  					Posts
  1046  				WHERE
  1047  					UpdateAt >= :Time
  1048  					AND
  1049  					ChannelId = :ChannelId
  1050  					AND
  1051  					UserId = :UserId
  1052  					AND
  1053  					Type = :Type
  1054  				LIMIT 1)`
  1055  
  1056  	var exist bool
  1057  	err := s.GetReplica().SelectOne(&exist, query, map[string]interface{}{
  1058  		"ChannelId": options.ChannelId,
  1059  		"Time":      options.Time,
  1060  		"UserId":    userId,
  1061  		"Type":      model.POST_AUTO_RESPONDER,
  1062  	})
  1063  
  1064  	if err != nil {
  1065  		return false, errors.Wrapf(err,
  1066  			"failed to check if autoresponse posts in channelId=%s for userId=%s since %s", options.ChannelId, userId, model.GetTimeForMillis(options.Time))
  1067  	}
  1068  
  1069  	return exist, nil
  1070  }
  1071  
  1072  func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
  1073  	query := s.getQueryBuilder().
  1074  		Select("*").
  1075  		From("Posts").
  1076  		Where(sq.Or{sq.Gt{"UpdateAt": cursor.LastPostUpdateAt}, sq.And{sq.Eq{"UpdateAt": cursor.LastPostUpdateAt}, sq.Gt{"Id": cursor.LastPostId}}}).
  1077  		OrderBy("UpdateAt", "Id").
  1078  		Limit(uint64(limit))
  1079  
  1080  	if options.ChannelId != "" {
  1081  		query = query.Where(sq.Eq{"ChannelId": options.ChannelId})
  1082  	}
  1083  
  1084  	if !options.IncludeDeleted {
  1085  		query = query.Where(sq.Eq{"DeleteAt": 0})
  1086  	}
  1087  
  1088  	if options.ExcludeRemoteId != "" {
  1089  		query = query.Where(sq.NotEq{"COALESCE(Posts.RemoteId,'')": options.ExcludeRemoteId})
  1090  	}
  1091  
  1092  	queryString, args, err := query.ToSql()
  1093  	if err != nil {
  1094  		return nil, cursor, errors.Wrap(err, "getpostssinceforsync_tosql")
  1095  	}
  1096  
  1097  	var posts []*model.Post
  1098  	_, err = s.GetReplica().Select(&posts, queryString, args...)
  1099  	if err != nil {
  1100  		return nil, cursor, errors.Wrapf(err, "error getting Posts with channelId=%s", options.ChannelId)
  1101  	}
  1102  
  1103  	if len(posts) != 0 {
  1104  		cursor.LastPostUpdateAt = posts[len(posts)-1].UpdateAt
  1105  		cursor.LastPostId = posts[len(posts)-1].Id
  1106  	}
  1107  	return posts, cursor, nil
  1108  }
  1109  
  1110  func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, error) {
  1111  	return s.getPostsAround(true, options)
  1112  }
  1113  
  1114  func (s *SqlPostStore) GetPostsAfter(options model.GetPostsOptions) (*model.PostList, error) {
  1115  	return s.getPostsAround(false, options)
  1116  }
  1117  
  1118  func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions) (*model.PostList, error) {
  1119  	if options.Page < 0 {
  1120  		return nil, store.NewErrInvalidInput("Post", "<options.Page>", options.Page)
  1121  	}
  1122  
  1123  	if options.PerPage < 0 {
  1124  		return nil, store.NewErrInvalidInput("Post", "<options.PerPage>", options.PerPage)
  1125  	}
  1126  
  1127  	offset := options.Page * options.PerPage
  1128  	var posts []*postWithExtra
  1129  	var parents []*model.Post
  1130  
  1131  	var direction string
  1132  	var sort string
  1133  	if before {
  1134  		direction = "<"
  1135  		sort = "DESC"
  1136  	} else {
  1137  		direction = ">"
  1138  		sort = "ASC"
  1139  	}
  1140  	table := "Posts p"
  1141  	// We force MySQL to use the right index to prevent it from accidentally
  1142  	// using the index_merge_intersection optimization.
  1143  	// See MM-27575.
  1144  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1145  		table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
  1146  	}
  1147  	columns := []string{"p.*"}
  1148  	if options.CollapsedThreads {
  1149  		columns = append(columns,
  1150  			"COALESCE(Threads.ReplyCount, 0) as ThreadReplyCount",
  1151  			"COALESCE(Threads.LastReplyAt, 0) as LastReplyAt",
  1152  			"COALESCE(Threads.Participants, '[]') as ThreadParticipants",
  1153  			"COALESCE(ThreadMemberships.Following, false) as IsFollowing",
  1154  		)
  1155  	}
  1156  	query := s.getQueryBuilder().Select(columns...)
  1157  	replyCountSubQuery := s.getQueryBuilder().Select("COUNT(Posts.Id)").From("Posts").Where(sq.Expr("Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0"))
  1158  
  1159  	conditions := sq.And{
  1160  		sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId),
  1161  		sq.Eq{"p.ChannelId": options.ChannelId},
  1162  		sq.Eq{"DeleteAt": int(0)},
  1163  	}
  1164  	if options.CollapsedThreads {
  1165  		conditions = append(conditions, sq.Eq{"RootId": ""})
  1166  		query = query.LeftJoin("Threads ON Threads.PostId = p.Id").LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = p.Id AND ThreadMemberships.UserId=?", options.UserId)
  1167  	} else {
  1168  		query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
  1169  	}
  1170  	query = query.From(table).
  1171  		Where(conditions).
  1172  		// Adding ChannelId and DeleteAt order columns
  1173  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
  1174  		// See MM-24170.
  1175  		OrderBy("p.ChannelId", "DeleteAt", "CreateAt "+sort).
  1176  		Limit(uint64(options.PerPage)).
  1177  		Offset(uint64(offset))
  1178  
  1179  	queryString, args, err := query.ToSql()
  1180  	if err != nil {
  1181  		return nil, errors.Wrap(err, "post_tosql")
  1182  	}
  1183  	_, err = s.GetMaster().Select(&posts, queryString, args...)
  1184  	if err != nil {
  1185  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
  1186  	}
  1187  
  1188  	if !options.CollapsedThreads && len(posts) > 0 {
  1189  		rootIds := []string{}
  1190  		for _, post := range posts {
  1191  			rootIds = append(rootIds, post.Id)
  1192  			if post.RootId != "" {
  1193  				rootIds = append(rootIds, post.RootId)
  1194  			}
  1195  		}
  1196  		rootQuery := s.getQueryBuilder().Select("p.*")
  1197  		idQuery := sq.Or{
  1198  			sq.Eq{"Id": rootIds},
  1199  		}
  1200  		rootQuery = rootQuery.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
  1201  		if !options.SkipFetchThreads {
  1202  			idQuery = append(idQuery, sq.Eq{"RootId": rootIds}) // preserve original behaviour
  1203  		}
  1204  
  1205  		rootQuery = rootQuery.From("Posts p").
  1206  			Where(sq.And{
  1207  				idQuery,
  1208  				sq.Eq{"ChannelId": options.ChannelId},
  1209  				sq.Eq{"DeleteAt": 0},
  1210  			}).
  1211  			OrderBy("CreateAt DESC")
  1212  
  1213  		rootQueryString, rootArgs, nErr := rootQuery.ToSql()
  1214  
  1215  		if nErr != nil {
  1216  			return nil, errors.Wrap(nErr, "post_tosql")
  1217  		}
  1218  		_, nErr = s.GetMaster().Select(&parents, rootQueryString, rootArgs...)
  1219  		if nErr != nil {
  1220  			return nil, errors.Wrapf(nErr, "failed to find Posts with channelId=%s", options.ChannelId)
  1221  		}
  1222  	}
  1223  
  1224  	list, err := s.prepareThreadedResponse(posts, options.CollapsedThreadsExtended, !before)
  1225  	if err != nil {
  1226  		return nil, err
  1227  	}
  1228  
  1229  	for _, p := range parents {
  1230  		list.AddPost(p)
  1231  	}
  1232  
  1233  	return list, nil
  1234  }
  1235  
  1236  func (s *SqlPostStore) GetPostIdBeforeTime(channelId string, time int64, collapsedThreads bool) (string, error) {
  1237  	return s.getPostIdAroundTime(channelId, time, true, collapsedThreads)
  1238  }
  1239  
  1240  func (s *SqlPostStore) GetPostIdAfterTime(channelId string, time int64, collapsedThreads bool) (string, error) {
  1241  	return s.getPostIdAroundTime(channelId, time, false, collapsedThreads)
  1242  }
  1243  
  1244  func (s *SqlPostStore) getPostIdAroundTime(channelId string, time int64, before bool, collapsedThreads bool) (string, error) {
  1245  	var direction sq.Sqlizer
  1246  	var sort string
  1247  	if before {
  1248  		direction = sq.Lt{"CreateAt": time}
  1249  		sort = "DESC"
  1250  	} else {
  1251  		direction = sq.Gt{"CreateAt": time}
  1252  		sort = "ASC"
  1253  	}
  1254  
  1255  	table := "Posts"
  1256  	// We force MySQL to use the right index to prevent it from accidentally
  1257  	// using the index_merge_intersection optimization.
  1258  	// See MM-27575.
  1259  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1260  		table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
  1261  	}
  1262  
  1263  	conditions := sq.And{
  1264  		direction,
  1265  		sq.Eq{"ChannelId": channelId},
  1266  		sq.Eq{"DeleteAt": int(0)},
  1267  	}
  1268  	if collapsedThreads {
  1269  		conditions = sq.And{conditions, sq.Eq{"RootId": ""}}
  1270  	}
  1271  	query := s.getQueryBuilder().
  1272  		Select("Id").
  1273  		From(table).
  1274  		Where(conditions).
  1275  		// Adding ChannelId and DeleteAt order columns
  1276  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
  1277  		// See MM-23369.
  1278  		OrderBy("ChannelId", "DeleteAt", "CreateAt "+sort).
  1279  		Limit(1)
  1280  
  1281  	queryString, args, err := query.ToSql()
  1282  	if err != nil {
  1283  		return "", errors.Wrap(err, "post_tosql")
  1284  	}
  1285  
  1286  	var postId string
  1287  	if err := s.GetMaster().SelectOne(&postId, queryString, args...); err != nil {
  1288  		if err != sql.ErrNoRows {
  1289  			return "", errors.Wrapf(err, "failed to get Post id with channelId=%s", channelId)
  1290  		}
  1291  	}
  1292  
  1293  	return postId, nil
  1294  }
  1295  
  1296  func (s *SqlPostStore) GetPostAfterTime(channelId string, time int64, collapsedThreads bool) (*model.Post, error) {
  1297  	table := "Posts"
  1298  	// We force MySQL to use the right index to prevent it from accidentally
  1299  	// using the index_merge_intersection optimization.
  1300  	// See MM-27575.
  1301  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1302  		table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
  1303  	}
  1304  	conditions := sq.And{
  1305  		sq.Gt{"CreateAt": time},
  1306  		sq.Eq{"ChannelId": channelId},
  1307  		sq.Eq{"DeleteAt": int(0)},
  1308  	}
  1309  	if collapsedThreads {
  1310  		conditions = sq.And{conditions, sq.Eq{"RootId": ""}}
  1311  	}
  1312  	query := s.getQueryBuilder().
  1313  		Select("*").
  1314  		From(table).
  1315  		Where(conditions).
  1316  		// Adding ChannelId and DeleteAt order columns
  1317  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
  1318  		// See MM-23369.
  1319  		OrderBy("ChannelId", "DeleteAt", "CreateAt ASC").
  1320  		Limit(1)
  1321  
  1322  	queryString, args, err := query.ToSql()
  1323  	if err != nil {
  1324  		return nil, errors.Wrap(err, "post_tosql")
  1325  	}
  1326  
  1327  	var post *model.Post
  1328  	if err := s.GetMaster().SelectOne(&post, queryString, args...); err != nil {
  1329  		if err != sql.ErrNoRows {
  1330  			return nil, errors.Wrapf(err, "failed to get Post with channelId=%s", channelId)
  1331  		}
  1332  	}
  1333  
  1334  	return post, nil
  1335  }
  1336  
  1337  func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
  1338  	var posts []*model.Post
  1339  	var fetchQuery string
  1340  	if skipFetchThreads {
  1341  		fetchQuery = "SELECT p.*, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
  1342  	} else {
  1343  		fetchQuery = "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
  1344  	}
  1345  	_, err := s.GetReplica().Select(&posts, fetchQuery, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
  1346  	if err != nil {
  1347  		return nil, errors.Wrap(err, "failed to find Posts")
  1348  	}
  1349  	return posts, nil
  1350  }
  1351  
  1352  func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
  1353  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1354  		return s.getParentsPostsPostgreSQL(channelId, offset, limit, skipFetchThreads)
  1355  	}
  1356  
  1357  	// query parent Ids first
  1358  	var roots []*struct {
  1359  		RootId string
  1360  	}
  1361  	rootQuery := `
  1362  		SELECT DISTINCT
  1363  			q.RootId
  1364  		FROM
  1365  			(SELECT
  1366  				RootId
  1367  			FROM
  1368  				Posts
  1369  			WHERE
  1370  				ChannelId = :ChannelId
  1371  					AND DeleteAt = 0
  1372  			ORDER BY CreateAt DESC
  1373  			LIMIT :Limit OFFSET :Offset) q
  1374  		WHERE q.RootId != ''`
  1375  
  1376  	_, err := s.GetReplica().Select(&roots, rootQuery, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
  1377  	if err != nil {
  1378  		return nil, errors.Wrap(err, "failed to find Posts")
  1379  	}
  1380  	if len(roots) == 0 {
  1381  		return nil, nil
  1382  	}
  1383  	params := make(map[string]interface{})
  1384  	placeholders := make([]string, len(roots))
  1385  	for idx, r := range roots {
  1386  		key := fmt.Sprintf(":Root%v", idx)
  1387  		params[key[1:]] = r.RootId
  1388  		placeholders[idx] = key
  1389  	}
  1390  	placeholderString := strings.Join(placeholders, ", ")
  1391  	params["ChannelId"] = channelId
  1392  	replyCountQuery := ""
  1393  	whereStatement := "p.Id IN (" + placeholderString + ")"
  1394  	if skipFetchThreads {
  1395  		replyCountQuery = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
  1396  	} else {
  1397  		whereStatement += " OR p.RootId IN (" + placeholderString + ")"
  1398  	}
  1399  	var posts []*model.Post
  1400  	_, err = s.GetReplica().Select(&posts, `
  1401  		SELECT p.*`+replyCountQuery+`
  1402  		FROM
  1403  			Posts p
  1404  		WHERE
  1405  			(`+whereStatement+`)
  1406  				AND ChannelId = :ChannelId
  1407  				AND DeleteAt = 0
  1408  		ORDER BY CreateAt`,
  1409  		params)
  1410  	if err != nil {
  1411  		return nil, errors.Wrap(err, "failed to find Posts")
  1412  	}
  1413  	return posts, nil
  1414  }
  1415  
  1416  func (s *SqlPostStore) getParentsPostsPostgreSQL(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
  1417  	var posts []*model.Post
  1418  	replyCountQuery := ""
  1419  	onStatement := "q1.RootId = q2.Id"
  1420  	if skipFetchThreads {
  1421  		replyCountQuery = ` ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
  1422  	} else {
  1423  		onStatement += " OR q1.RootId = q2.RootId"
  1424  	}
  1425  	_, err := s.GetReplica().Select(&posts,
  1426  		`SELECT q2.*`+replyCountQuery+`
  1427          FROM
  1428              Posts q2
  1429                  INNER JOIN
  1430              (SELECT DISTINCT
  1431                  q3.RootId
  1432              FROM
  1433                  (SELECT
  1434                      RootId
  1435                  FROM
  1436                      Posts
  1437                  WHERE
  1438                      ChannelId = :ChannelId1
  1439                          AND DeleteAt = 0
  1440                  ORDER BY CreateAt DESC
  1441                  LIMIT :Limit OFFSET :Offset) q3
  1442              WHERE q3.RootId != '') q1
  1443              ON `+onStatement+`
  1444          WHERE
  1445              ChannelId = :ChannelId2
  1446                  AND DeleteAt = 0
  1447          ORDER BY CreateAt`,
  1448  		map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
  1449  	if err != nil {
  1450  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", channelId)
  1451  	}
  1452  	return posts, nil
  1453  }
  1454  
  1455  var specialSearchChar = []string{
  1456  	"<",
  1457  	">",
  1458  	"+",
  1459  	"-",
  1460  	"(",
  1461  	")",
  1462  	"~",
  1463  	"@",
  1464  	":",
  1465  }
  1466  
  1467  func (s *SqlPostStore) buildCreateDateFilterClause(params *model.SearchParams, queryParams map[string]interface{}) (string, map[string]interface{}) {
  1468  	searchQuery := ""
  1469  	// handle after: before: on: filters
  1470  	if params.OnDate != "" {
  1471  		onDateStart, onDateEnd := params.GetOnDateMillis()
  1472  		queryParams["OnDateStart"] = strconv.FormatInt(onDateStart, 10)
  1473  		queryParams["OnDateEnd"] = strconv.FormatInt(onDateEnd, 10)
  1474  
  1475  		// between `on date` start of day and end of day
  1476  		searchQuery += "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd "
  1477  	} else {
  1478  
  1479  		if params.ExcludedDate != "" {
  1480  			excludedDateStart, excludedDateEnd := params.GetExcludedDateMillis()
  1481  			queryParams["ExcludedDateStart"] = strconv.FormatInt(excludedDateStart, 10)
  1482  			queryParams["ExcludedDateEnd"] = strconv.FormatInt(excludedDateEnd, 10)
  1483  
  1484  			searchQuery += "AND CreateAt NOT BETWEEN :ExcludedDateStart AND :ExcludedDateEnd "
  1485  		}
  1486  
  1487  		if params.AfterDate != "" {
  1488  			afterDate := params.GetAfterDateMillis()
  1489  			queryParams["AfterDate"] = strconv.FormatInt(afterDate, 10)
  1490  
  1491  			// greater than `after date`
  1492  			searchQuery += "AND CreateAt >= :AfterDate "
  1493  		}
  1494  
  1495  		if params.BeforeDate != "" {
  1496  			beforeDate := params.GetBeforeDateMillis()
  1497  			queryParams["BeforeDate"] = strconv.FormatInt(beforeDate, 10)
  1498  
  1499  			// less than `before date`
  1500  			searchQuery += "AND CreateAt <= :BeforeDate "
  1501  		}
  1502  
  1503  		if params.ExcludedAfterDate != "" {
  1504  			afterDate := params.GetExcludedAfterDateMillis()
  1505  			queryParams["ExcludedAfterDate"] = strconv.FormatInt(afterDate, 10)
  1506  
  1507  			searchQuery += "AND CreateAt < :ExcludedAfterDate "
  1508  		}
  1509  
  1510  		if params.ExcludedBeforeDate != "" {
  1511  			beforeDate := params.GetExcludedBeforeDateMillis()
  1512  			queryParams["ExcludedBeforeDate"] = strconv.FormatInt(beforeDate, 10)
  1513  
  1514  			searchQuery += "AND CreateAt > :ExcludedBeforeDate "
  1515  		}
  1516  	}
  1517  
  1518  	return searchQuery, queryParams
  1519  }
  1520  
  1521  func (s *SqlPostStore) buildSearchChannelFilterClause(channels []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byName bool) (string, map[string]interface{}) {
  1522  	if len(channels) == 0 {
  1523  		return "", queryParams
  1524  	}
  1525  
  1526  	clauseSlice := []string{}
  1527  	for i, channel := range channels {
  1528  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
  1529  		clauseSlice = append(clauseSlice, ":"+paramName)
  1530  		queryParams[paramName] = channel
  1531  	}
  1532  	clause := strings.Join(clauseSlice, ", ")
  1533  	if byName {
  1534  		if exclusion {
  1535  			return "AND Name NOT IN (" + clause + ")", queryParams
  1536  		}
  1537  		return "AND Name IN (" + clause + ")", queryParams
  1538  	}
  1539  
  1540  	if exclusion {
  1541  		return "AND Id NOT IN (" + clause + ")", queryParams
  1542  	}
  1543  	return "AND Id IN (" + clause + ")", queryParams
  1544  }
  1545  
  1546  func (s *SqlPostStore) buildSearchUserFilterClause(users []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byUsername bool) (string, map[string]interface{}) {
  1547  	if len(users) == 0 {
  1548  		return "", queryParams
  1549  	}
  1550  	clauseSlice := []string{}
  1551  	for i, user := range users {
  1552  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
  1553  		clauseSlice = append(clauseSlice, ":"+paramName)
  1554  		queryParams[paramName] = user
  1555  	}
  1556  	clause := strings.Join(clauseSlice, ", ")
  1557  	if byUsername {
  1558  		if exclusion {
  1559  			return "AND Username NOT IN (" + clause + ")", queryParams
  1560  		}
  1561  		return "AND Username IN (" + clause + ")", queryParams
  1562  	}
  1563  	if exclusion {
  1564  		return "AND Id NOT IN (" + clause + ")", queryParams
  1565  	}
  1566  	return "AND Id IN (" + clause + ")", queryParams
  1567  }
  1568  
  1569  func (s *SqlPostStore) buildSearchPostFilterClause(fromUsers []string, excludedUsers []string, queryParams map[string]interface{}, userByUsername bool) (string, map[string]interface{}) {
  1570  	if len(fromUsers) == 0 && len(excludedUsers) == 0 {
  1571  		return "", queryParams
  1572  	}
  1573  
  1574  	filterQuery := `
  1575  		AND UserId IN (
  1576  			SELECT
  1577  				Id
  1578  			FROM
  1579  				Users,
  1580  				TeamMembers
  1581  			WHERE
  1582  				TeamMembers.TeamId = :TeamId
  1583  				AND Users.Id = TeamMembers.UserId
  1584  				FROM_USER_FILTER
  1585  				EXCLUDED_USER_FILTER)`
  1586  
  1587  	fromUserClause, queryParams := s.buildSearchUserFilterClause(fromUsers, "FromUser", false, queryParams, userByUsername)
  1588  	filterQuery = strings.Replace(filterQuery, "FROM_USER_FILTER", fromUserClause, 1)
  1589  
  1590  	excludedUserClause, queryParams := s.buildSearchUserFilterClause(excludedUsers, "ExcludedUser", true, queryParams, userByUsername)
  1591  	filterQuery = strings.Replace(filterQuery, "EXCLUDED_USER_FILTER", excludedUserClause, 1)
  1592  
  1593  	return filterQuery, queryParams
  1594  }
  1595  
  1596  func (s *SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) (*model.PostList, error) {
  1597  	return s.search(teamId, userId, params, true, true)
  1598  }
  1599  
  1600  func (s *SqlPostStore) search(teamId string, userId string, params *model.SearchParams, channelsByName bool, userByUsername bool) (*model.PostList, error) {
  1601  	queryParams := map[string]interface{}{
  1602  		"TeamId": teamId,
  1603  		"UserId": userId,
  1604  	}
  1605  
  1606  	list := model.NewPostList()
  1607  	if params.Terms == "" && params.ExcludedTerms == "" &&
  1608  		len(params.InChannels) == 0 && len(params.ExcludedChannels) == 0 &&
  1609  		len(params.FromUsers) == 0 && len(params.ExcludedUsers) == 0 &&
  1610  		params.OnDate == "" && params.AfterDate == "" && params.BeforeDate == "" {
  1611  		return list, nil
  1612  	}
  1613  
  1614  	var posts []*model.Post
  1615  
  1616  	deletedQueryPart := "AND DeleteAt = 0"
  1617  	if params.IncludeDeletedChannels {
  1618  		deletedQueryPart = ""
  1619  	}
  1620  
  1621  	userIdPart := "AND UserId = :UserId"
  1622  	if params.SearchWithoutUserId {
  1623  		userIdPart = ""
  1624  	}
  1625  
  1626  	searchQuery := `
  1627  			SELECT
  1628  				* ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
  1629  			FROM
  1630  				Posts q2
  1631  			WHERE
  1632  				DeleteAt = 0
  1633  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
  1634  				POST_FILTER
  1635  				AND ChannelId IN (
  1636  					SELECT
  1637  						Id
  1638  					FROM
  1639  						Channels,
  1640  						ChannelMembers
  1641  					WHERE
  1642  						Id = ChannelId
  1643  							AND (TeamId = :TeamId OR TeamId = '')
  1644  							` + userIdPart + `
  1645  							` + deletedQueryPart + `
  1646  							IN_CHANNEL_FILTER
  1647  							EXCLUDED_CHANNEL_FILTER)
  1648  				CREATEDATE_CLAUSE
  1649  				SEARCH_CLAUSE
  1650  				ORDER BY CreateAt DESC
  1651  			LIMIT 100`
  1652  
  1653  	inChannelClause, queryParams := s.buildSearchChannelFilterClause(params.InChannels, "InChannel", false, queryParams, channelsByName)
  1654  	searchQuery = strings.Replace(searchQuery, "IN_CHANNEL_FILTER", inChannelClause, 1)
  1655  
  1656  	excludedChannelClause, queryParams := s.buildSearchChannelFilterClause(params.ExcludedChannels, "ExcludedChannel", true, queryParams, channelsByName)
  1657  	searchQuery = strings.Replace(searchQuery, "EXCLUDED_CHANNEL_FILTER", excludedChannelClause, 1)
  1658  
  1659  	postFilterClause, queryParams := s.buildSearchPostFilterClause(params.FromUsers, params.ExcludedUsers, queryParams, userByUsername)
  1660  	searchQuery = strings.Replace(searchQuery, "POST_FILTER", postFilterClause, 1)
  1661  
  1662  	createDateFilterClause, queryParams := s.buildCreateDateFilterClause(params, queryParams)
  1663  	searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", createDateFilterClause, 1)
  1664  
  1665  	termMap := map[string]bool{}
  1666  	terms := params.Terms
  1667  	excludedTerms := params.ExcludedTerms
  1668  
  1669  	searchType := "Message"
  1670  	if params.IsHashtag {
  1671  		searchType = "Hashtags"
  1672  		for _, term := range strings.Split(terms, " ") {
  1673  			termMap[strings.ToUpper(term)] = true
  1674  		}
  1675  	}
  1676  
  1677  	// these chars have special meaning and can be treated as spaces
  1678  	for _, c := range specialSearchChar {
  1679  		terms = strings.Replace(terms, c, " ", -1)
  1680  		excludedTerms = strings.Replace(excludedTerms, c, " ", -1)
  1681  	}
  1682  
  1683  	if terms == "" && excludedTerms == "" {
  1684  		// we've already confirmed that we have a channel or user to search for
  1685  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
  1686  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1687  		// Parse text for wildcards
  1688  		if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
  1689  			terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
  1690  			excludedTerms = wildcard.ReplaceAllLiteralString(excludedTerms, ":* ")
  1691  		}
  1692  
  1693  		excludeClause := ""
  1694  		if excludedTerms != "" {
  1695  			excludeClause = " & !(" + strings.Join(strings.Fields(excludedTerms), " | ") + ")"
  1696  		}
  1697  
  1698  		if params.OrTerms {
  1699  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " | ") + ")" + excludeClause
  1700  		} else if strings.HasPrefix(terms, `"`) && strings.HasSuffix(terms, `"`) {
  1701  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " <-> ") + ")" + excludeClause
  1702  		} else {
  1703  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " & ") + ")" + excludeClause
  1704  		}
  1705  
  1706  		searchClause := fmt.Sprintf("AND to_tsvector('english', %s) @@  to_tsquery('english', :Terms)", searchType)
  1707  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1708  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1709  		if searchType == "Message" {
  1710  			var err error
  1711  			terms, err = removeMysqlStopWordsFromTerms(terms)
  1712  			if err != nil {
  1713  				return nil, errors.Wrap(err, "failed to remove Mysql stop-words from terms")
  1714  			}
  1715  
  1716  			if terms == "" {
  1717  				return list, nil
  1718  			}
  1719  		}
  1720  
  1721  		searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
  1722  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1723  
  1724  		excludeClause := ""
  1725  		if excludedTerms != "" {
  1726  			excludeClause = " -(" + excludedTerms + ")"
  1727  		}
  1728  
  1729  		if params.OrTerms {
  1730  			queryParams["Terms"] = terms + excludeClause
  1731  		} else {
  1732  			splitTerms := []string{}
  1733  			for _, t := range strings.Fields(terms) {
  1734  				splitTerms = append(splitTerms, "+"+t)
  1735  			}
  1736  			queryParams["Terms"] = strings.Join(splitTerms, " ") + excludeClause
  1737  		}
  1738  	}
  1739  
  1740  	_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
  1741  	if err != nil {
  1742  		mlog.Warn("Query error searching posts.", mlog.Err(err))
  1743  		// Don't return the error to the caller as it is of no use to the user. Instead return an empty set of search results.
  1744  	} else {
  1745  		for _, p := range posts {
  1746  			if searchType == "Hashtags" {
  1747  				exactMatch := false
  1748  				for _, tag := range strings.Split(p.Hashtags, " ") {
  1749  					if termMap[strings.ToUpper(tag)] {
  1750  						exactMatch = true
  1751  						break
  1752  					}
  1753  				}
  1754  				if !exactMatch {
  1755  					continue
  1756  				}
  1757  			}
  1758  			list.AddPost(p)
  1759  			list.AddOrder(p.Id)
  1760  		}
  1761  	}
  1762  	list.MakeNonNil()
  1763  	return list, nil
  1764  }
  1765  
  1766  func removeMysqlStopWordsFromTerms(terms string) (string, error) {
  1767  	stopWords := make([]string, len(searchlayer.MySQLStopWords))
  1768  	copy(stopWords, searchlayer.MySQLStopWords)
  1769  	re, err := regexp.Compile(fmt.Sprintf(`^(%s)$`, strings.Join(stopWords, "|")))
  1770  	if err != nil {
  1771  		return "", err
  1772  	}
  1773  
  1774  	newTerms := make([]string, 0)
  1775  	separatedTerms := strings.Fields(terms)
  1776  	for _, term := range separatedTerms {
  1777  		term = strings.TrimSpace(term)
  1778  		if term = re.ReplaceAllString(term, ""); term != "" {
  1779  			newTerms = append(newTerms, term)
  1780  		}
  1781  	}
  1782  	return strings.Join(newTerms, " "), nil
  1783  }
  1784  
  1785  func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) (model.AnalyticsRows, error) {
  1786  	query :=
  1787  		`SELECT DISTINCT
  1788  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1789  		        COUNT(DISTINCT Posts.UserId) AS Value
  1790  		FROM Posts`
  1791  
  1792  	if teamId != "" {
  1793  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1794  	} else {
  1795  		query += " WHERE"
  1796  	}
  1797  
  1798  	query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1799  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1800  		ORDER BY Name DESC
  1801  		LIMIT 30`
  1802  
  1803  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1804  		query =
  1805  			`SELECT
  1806  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
  1807  			FROM Posts`
  1808  
  1809  		if teamId != "" {
  1810  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1811  		} else {
  1812  			query += " WHERE"
  1813  		}
  1814  
  1815  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1816  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1817  			ORDER BY Name DESC
  1818  			LIMIT 30`
  1819  	}
  1820  
  1821  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1822  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1823  
  1824  	var rows model.AnalyticsRows
  1825  	_, err := s.GetReplica().Select(
  1826  		&rows,
  1827  		query,
  1828  		map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
  1829  	if err != nil {
  1830  		return nil, errors.Wrapf(err, "failed to find Posts with teamId=%s", teamId)
  1831  	}
  1832  	return rows, nil
  1833  }
  1834  
  1835  func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCountsOptions) (model.AnalyticsRows, error) {
  1836  
  1837  	query :=
  1838  		`SELECT
  1839  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1840  		        COUNT(Posts.Id) AS Value
  1841  		    FROM Posts`
  1842  
  1843  	if options.BotsOnly {
  1844  		query += " INNER JOIN Bots ON Posts.UserId = Bots.Userid"
  1845  	}
  1846  
  1847  	if options.TeamId != "" {
  1848  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1849  	} else {
  1850  		query += " WHERE"
  1851  	}
  1852  
  1853  	query += ` Posts.CreateAt <= :EndTime
  1854  		            AND Posts.CreateAt >= :StartTime
  1855  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1856  		ORDER BY Name DESC
  1857  		LIMIT 30`
  1858  
  1859  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1860  		query =
  1861  			`SELECT
  1862  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
  1863  			FROM Posts`
  1864  
  1865  		if options.BotsOnly {
  1866  			query += " INNER JOIN Bots ON Posts.UserId = Bots.Userid"
  1867  		}
  1868  
  1869  		if options.TeamId != "" {
  1870  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
  1871  		} else {
  1872  			query += " WHERE"
  1873  		}
  1874  
  1875  		query += ` Posts.CreateAt <= :EndTime
  1876  			            AND Posts.CreateAt >= :StartTime
  1877  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1878  			ORDER BY Name DESC
  1879  			LIMIT 30`
  1880  	}
  1881  
  1882  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1883  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1884  	if options.YesterdayOnly {
  1885  		start = utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -1)))
  1886  	}
  1887  
  1888  	var rows model.AnalyticsRows
  1889  	_, err := s.GetReplica().Select(
  1890  		&rows,
  1891  		query,
  1892  		map[string]interface{}{"TeamId": options.TeamId, "StartTime": start, "EndTime": end})
  1893  	if err != nil {
  1894  		return nil, errors.Wrapf(err, "failed to find Posts with teamId=%s", options.TeamId)
  1895  	}
  1896  	return rows, nil
  1897  }
  1898  
  1899  func (s *SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) (int64, error) {
  1900  	query := s.getQueryBuilder().
  1901  		Select("COUNT(p.Id) AS Value").
  1902  		From("Posts p")
  1903  
  1904  	if teamId != "" {
  1905  		query = query.
  1906  			Join("Channels c ON (c.Id = p.ChannelId)").
  1907  			Where(sq.Eq{"c.TeamId": teamId})
  1908  	}
  1909  
  1910  	if mustHaveFile {
  1911  		query = query.Where(sq.Or{sq.NotEq{"p.FileIds": "[]"}, sq.NotEq{"p.Filenames": "[]"}})
  1912  	}
  1913  
  1914  	if mustHaveHashtag {
  1915  		query = query.Where(sq.NotEq{"p.Hashtags": ""})
  1916  	}
  1917  
  1918  	queryString, args, err := query.ToSql()
  1919  	if err != nil {
  1920  		return 0, errors.Wrap(err, "post_tosql")
  1921  	}
  1922  
  1923  	v, err := s.GetReplica().SelectInt(queryString, args...)
  1924  	if err != nil {
  1925  		return 0, errors.Wrap(err, "failed to count Posts")
  1926  	}
  1927  
  1928  	return v, nil
  1929  }
  1930  
  1931  func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) ([]*model.Post, error) {
  1932  	query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1933  
  1934  	var posts []*model.Post
  1935  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1936  
  1937  	if err != nil {
  1938  		return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", channelId)
  1939  	}
  1940  	return posts, nil
  1941  }
  1942  
  1943  func (s *SqlPostStore) GetPostsByIds(postIds []string) ([]*model.Post, error) {
  1944  	keys, params := MapStringsToQueryParams(postIds, "Post")
  1945  
  1946  	query := `SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id IN ` + keys + ` ORDER BY CreateAt DESC`
  1947  
  1948  	var posts []*model.Post
  1949  	_, err := s.GetReplica().Select(&posts, query, params)
  1950  
  1951  	if err != nil {
  1952  		return nil, errors.Wrap(err, "failed to find Posts")
  1953  	}
  1954  	return posts, nil
  1955  }
  1956  
  1957  func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, error) {
  1958  	var posts []*model.PostForIndexing
  1959  	_, err := s.GetSearchReplica().Select(&posts,
  1960  		`SELECT
  1961  			PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1962  		FROM (
  1963  			SELECT
  1964  				*
  1965  			FROM
  1966  				Posts
  1967  			WHERE
  1968  				Posts.CreateAt >= :StartTime
  1969  			AND
  1970  				Posts.CreateAt < :EndTime
  1971  			ORDER BY
  1972  				CreateAt ASC
  1973  			LIMIT
  1974  				1000
  1975  			)
  1976  		AS
  1977  			PostsQuery
  1978  		LEFT JOIN
  1979  			Channels
  1980  		ON
  1981  			PostsQuery.ChannelId = Channels.Id
  1982  		LEFT JOIN
  1983  			Posts ParentPosts
  1984  		ON
  1985  			PostsQuery.RootId = ParentPosts.Id`,
  1986  		map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1987  
  1988  	if err != nil {
  1989  		return nil, errors.Wrap(err, "failed to find Posts")
  1990  	}
  1991  	return posts, nil
  1992  }
  1993  
  1994  // PermanentDeleteBatchForRetentionPolicies deletes a batch of records which are affected by
  1995  // the global or a granular retention policy.
  1996  // See `genericPermanentDeleteBatchForRetentionPolicies` for details.
  1997  func (s *SqlPostStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
  1998  	builder := s.getQueryBuilder().
  1999  		Select("Posts.Id").
  2000  		From("Posts")
  2001  	return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{
  2002  		BaseBuilder:         builder,
  2003  		Table:               "Posts",
  2004  		TimeColumn:          "CreateAt",
  2005  		PrimaryKeys:         []string{"Id"},
  2006  		ChannelIDTable:      "Posts",
  2007  		NowMillis:           now,
  2008  		GlobalPolicyEndTime: globalPolicyEndTime,
  2009  		Limit:               limit,
  2010  	}, s.SqlStore, cursor)
  2011  }
  2012  
  2013  // DeleteOrphanedRows removes entries from Posts when a corresponding channel no longer exists.
  2014  func (s *SqlPostStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
  2015  	// We need the extra level of nesting to deal with MySQL's locking
  2016  	const query = `
  2017  	DELETE FROM Posts WHERE Id IN (
  2018  		SELECT * FROM (
  2019  			SELECT Posts.Id FROM Posts
  2020  			LEFT JOIN Channels ON Posts.ChannelId = Channels.Id
  2021  			WHERE Channels.Id IS NULL
  2022  			LIMIT :Limit
  2023  		) AS A
  2024  	)`
  2025  	props := map[string]interface{}{"Limit": limit}
  2026  	result, err := s.GetMaster().Exec(query, props)
  2027  	if err != nil {
  2028  		return
  2029  	}
  2030  	deleted, err = result.RowsAffected()
  2031  	return
  2032  }
  2033  
  2034  func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
  2035  	var query string
  2036  	if s.DriverName() == "postgres" {
  2037  		query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  2038  	} else {
  2039  		query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  2040  	}
  2041  
  2042  	sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  2043  	if err != nil {
  2044  		return 0, errors.Wrap(err, "failed to delete Posts")
  2045  	}
  2046  
  2047  	rowsAffected, err := sqlResult.RowsAffected()
  2048  	if err != nil {
  2049  		return 0, errors.Wrap(err, "failed to delete Posts")
  2050  	}
  2051  	return rowsAffected, nil
  2052  }
  2053  
  2054  func (s *SqlPostStore) GetOldest() (*model.Post, error) {
  2055  	var post model.Post
  2056  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  2057  	if err != nil {
  2058  		if err == sql.ErrNoRows {
  2059  			return nil, store.NewErrNotFound("Post", "none")
  2060  		}
  2061  
  2062  		return nil, errors.Wrap(err, "failed to get oldest Post")
  2063  	}
  2064  
  2065  	return &post, nil
  2066  }
  2067  
  2068  func (s *SqlPostStore) determineMaxPostSize() int {
  2069  	var maxPostSizeBytes int32
  2070  
  2071  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  2072  		// The Post.Message column in Postgres has historically been VARCHAR(4000), but
  2073  		// may be manually enlarged to support longer posts.
  2074  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  2075  			SELECT
  2076  				COALESCE(character_maximum_length, 0)
  2077  			FROM
  2078  				information_schema.columns
  2079  			WHERE
  2080  				table_name = 'posts'
  2081  			AND	column_name = 'message'
  2082  		`); err != nil {
  2083  			mlog.Warn("Unable to determine the maximum supported post size", mlog.Err(err))
  2084  		}
  2085  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  2086  		// The Post.Message column in MySQL has historically been TEXT, with a maximum
  2087  		// limit of 65535.
  2088  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  2089  			SELECT
  2090  				COALESCE(CHARACTER_MAXIMUM_LENGTH, 0)
  2091  			FROM
  2092  				INFORMATION_SCHEMA.COLUMNS
  2093  			WHERE
  2094  				table_schema = DATABASE()
  2095  			AND	table_name = 'Posts'
  2096  			AND	column_name = 'Message'
  2097  			LIMIT 0, 1
  2098  		`); err != nil {
  2099  			mlog.Warn("Unable to determine the maximum supported post size", mlog.Err(err))
  2100  		}
  2101  	} else {
  2102  		mlog.Warn("No implementation found to determine the maximum supported post size")
  2103  	}
  2104  
  2105  	// Assume a worst-case representation of four bytes per rune.
  2106  	maxPostSize := int(maxPostSizeBytes) / 4
  2107  
  2108  	// To maintain backwards compatibility, don't yield a maximum post
  2109  	// size smaller than the previous limit, even though it wasn't
  2110  	// actually possible to store 4000 runes in all cases.
  2111  	if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 {
  2112  		maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1
  2113  	}
  2114  
  2115  	mlog.Info("Post.Message has size restrictions", mlog.Int("max_characters", maxPostSize), mlog.Int32("max_bytes", maxPostSizeBytes))
  2116  
  2117  	return maxPostSize
  2118  }
  2119  
  2120  // GetMaxPostSize returns the maximum number of runes that may be stored in a post.
  2121  func (s *SqlPostStore) GetMaxPostSize() int {
  2122  	s.maxPostSizeOnce.Do(func() {
  2123  		s.maxPostSizeCached = s.determineMaxPostSize()
  2124  	})
  2125  	return s.maxPostSizeCached
  2126  }
  2127  
  2128  func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) ([]*model.PostForExport, error) {
  2129  	for {
  2130  		var rootIds []string
  2131  		_, err := s.GetReplica().Select(&rootIds,
  2132  			`SELECT
  2133  				Id
  2134  			FROM
  2135  				Posts
  2136  			WHERE
  2137  				Id > :AfterId
  2138  				AND RootId = ''
  2139  				AND DeleteAt = 0
  2140  			ORDER BY Id
  2141  			LIMIT :Limit`,
  2142  			map[string]interface{}{"Limit": limit, "AfterId": afterId})
  2143  		if err != nil {
  2144  			return nil, errors.Wrap(err, "failed to find Posts")
  2145  		}
  2146  
  2147  		var postsForExport []*model.PostForExport
  2148  		if len(rootIds) == 0 {
  2149  			return postsForExport, nil
  2150  		}
  2151  
  2152  		keys, params := MapStringsToQueryParams(rootIds, "PostId")
  2153  		_, err = s.GetSearchReplica().Select(&postsForExport, `
  2154  			SELECT
  2155  				p1.*,
  2156  				Users.Username as Username,
  2157  				Teams.Name as TeamName,
  2158  				Channels.Name as ChannelName
  2159  			FROM
  2160  				(Select * FROM Posts WHERE Id IN `+keys+`) p1
  2161  			INNER JOIN
  2162  				Channels ON p1.ChannelId = Channels.Id
  2163  			INNER JOIN
  2164  				Teams ON Channels.TeamId = Teams.Id
  2165  			INNER JOIN
  2166  				Users ON p1.UserId = Users.Id
  2167  			WHERE
  2168  				Channels.DeleteAt = 0
  2169  				AND Teams.DeleteAt = 0
  2170  			ORDER BY
  2171  				p1.Id`,
  2172  			params)
  2173  		if err != nil {
  2174  			return nil, errors.Wrap(err, "failed to find Posts")
  2175  		}
  2176  
  2177  		if len(postsForExport) == 0 {
  2178  			// All of the posts were in channels or teams that were deleted.
  2179  			// Update the afterId and try again.
  2180  			afterId = rootIds[len(rootIds)-1]
  2181  			continue
  2182  		}
  2183  
  2184  		return postsForExport, nil
  2185  	}
  2186  }
  2187  
  2188  func (s *SqlPostStore) GetRepliesForExport(rootId string) ([]*model.ReplyForExport, error) {
  2189  	var posts []*model.ReplyForExport
  2190  	_, err := s.GetSearchReplica().Select(&posts, `
  2191  			SELECT
  2192  				Posts.*,
  2193  				Users.Username as Username
  2194  			FROM
  2195  				Posts
  2196  			INNER JOIN
  2197  				Users ON Posts.UserId = Users.Id
  2198  			WHERE
  2199  				Posts.RootId = :RootId
  2200  				AND Posts.DeleteAt = 0
  2201  			ORDER BY
  2202  				Posts.Id`,
  2203  		map[string]interface{}{"RootId": rootId})
  2204  
  2205  	if err != nil {
  2206  		return nil, errors.Wrap(err, "failed to find Posts")
  2207  	}
  2208  
  2209  	return posts, nil
  2210  }
  2211  
  2212  func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) ([]*model.DirectPostForExport, error) {
  2213  	query := s.getQueryBuilder().
  2214  		Select("p.*", "Users.Username as User").
  2215  		From("Posts p").
  2216  		Join("Channels ON p.ChannelId = Channels.Id").
  2217  		Join("Users ON p.UserId = Users.Id").
  2218  		Where(sq.And{
  2219  			sq.Gt{"p.Id": afterId},
  2220  			sq.Eq{"p.ParentId": string("")},
  2221  			sq.Eq{"p.DeleteAt": int(0)},
  2222  			sq.Eq{"Channels.DeleteAt": int(0)},
  2223  			sq.Eq{"Users.DeleteAt": int(0)},
  2224  			sq.Eq{"Channels.Type": []string{"D", "G"}},
  2225  		}).
  2226  		OrderBy("p.Id").
  2227  		Limit(uint64(limit))
  2228  
  2229  	queryString, args, err := query.ToSql()
  2230  	if err != nil {
  2231  		return nil, errors.Wrap(err, "post_tosql")
  2232  	}
  2233  
  2234  	var posts []*model.DirectPostForExport
  2235  	if _, err = s.GetReplica().Select(&posts, queryString, args...); err != nil {
  2236  		return nil, errors.Wrap(err, "failed to find Posts")
  2237  	}
  2238  	var channelIds []string
  2239  	for _, post := range posts {
  2240  		channelIds = append(channelIds, post.ChannelId)
  2241  	}
  2242  	query = s.getQueryBuilder().
  2243  		Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
  2244  		From("ChannelMembers cm").
  2245  		Join("Users u ON ( u.Id = cm.UserId )").
  2246  		Where(sq.Eq{
  2247  			"cm.ChannelId": channelIds,
  2248  		})
  2249  
  2250  	queryString, args, err = query.ToSql()
  2251  	if err != nil {
  2252  		return nil, errors.Wrap(err, "post_tosql")
  2253  	}
  2254  
  2255  	var channelMembers []*model.ChannelMemberForExport
  2256  	if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
  2257  		return nil, errors.Wrap(err, "failed to find ChannelMembers")
  2258  	}
  2259  
  2260  	// Build a map of channels and their posts
  2261  	postsChannelMap := make(map[string][]*model.DirectPostForExport)
  2262  	for _, post := range posts {
  2263  		post.ChannelMembers = &[]string{}
  2264  		postsChannelMap[post.ChannelId] = append(postsChannelMap[post.ChannelId], post)
  2265  	}
  2266  
  2267  	// Build a map of channels and their members
  2268  	channelMembersMap := make(map[string][]string)
  2269  	for _, member := range channelMembers {
  2270  		channelMembersMap[member.ChannelId] = append(channelMembersMap[member.ChannelId], member.Username)
  2271  	}
  2272  
  2273  	// Populate each post ChannelMembers extracting it from the channelMembersMap
  2274  	for channelId := range channelMembersMap {
  2275  		for _, post := range postsChannelMap[channelId] {
  2276  			*post.ChannelMembers = channelMembersMap[channelId]
  2277  		}
  2278  	}
  2279  	return posts, nil
  2280  }
  2281  
  2282  //nolint:unparam
  2283  func (s *SqlPostStore) SearchPostsInTeamForUser(paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) {
  2284  	// Since we don't support paging for DB search, we just return nothing for later pages
  2285  	if page > 0 {
  2286  		return model.MakePostSearchResults(model.NewPostList(), nil), nil
  2287  	}
  2288  
  2289  	if err := model.IsSearchParamsListValid(paramsList); err != nil {
  2290  		return nil, err
  2291  	}
  2292  
  2293  	var wg sync.WaitGroup
  2294  
  2295  	pchan := make(chan store.StoreResult, len(paramsList))
  2296  
  2297  	for _, params := range paramsList {
  2298  		// remove any unquoted term that contains only non-alphanumeric chars
  2299  		// ex: abcd "**" && abc     >>     abcd "**" abc
  2300  		params.Terms = removeNonAlphaNumericUnquotedTerms(params.Terms, " ")
  2301  
  2302  		wg.Add(1)
  2303  
  2304  		go func(params *model.SearchParams) {
  2305  			defer wg.Done()
  2306  			postList, err := s.search(teamId, userId, params, false, false)
  2307  			pchan <- store.StoreResult{Data: postList, NErr: err}
  2308  		}(params)
  2309  	}
  2310  
  2311  	wg.Wait()
  2312  	close(pchan)
  2313  
  2314  	posts := model.NewPostList()
  2315  
  2316  	for result := range pchan {
  2317  		if result.NErr != nil {
  2318  			return nil, result.NErr
  2319  		}
  2320  		data := result.Data.(*model.PostList)
  2321  		posts.Extend(data)
  2322  	}
  2323  
  2324  	posts.SortByCreateAt()
  2325  
  2326  	return model.MakePostSearchResults(posts, nil), nil
  2327  }
  2328  
  2329  func (s *SqlPostStore) GetOldestEntityCreationTime() (int64, error) {
  2330  	query := s.getQueryBuilder().Select("MIN(min_createat) min_createat").
  2331  		Suffix(`FROM (
  2332  					(SELECT MIN(createat) min_createat FROM Posts)
  2333  					UNION
  2334  					(SELECT MIN(createat) min_createat FROM Users)
  2335  					UNION
  2336  					(SELECT MIN(createat) min_createat FROM Channels)
  2337  				) entities`)
  2338  	queryString, _, err := query.ToSql()
  2339  	if err != nil {
  2340  		return -1, errors.Wrap(err, "post_tosql")
  2341  	}
  2342  	row := s.GetReplica().Db.QueryRow(queryString)
  2343  	var oldest int64
  2344  	if err := row.Scan(&oldest); err != nil {
  2345  		return -1, errors.Wrap(err, "unable to scan oldest entity creation time")
  2346  	}
  2347  	return oldest, nil
  2348  }
  2349  
  2350  func (s *SqlPostStore) cleanupThreads(postId, rootId string, permanent bool) error {
  2351  	if permanent {
  2352  		if _, err := s.GetMaster().Exec("DELETE FROM Threads WHERE PostId = :Id", map[string]interface{}{"Id": postId}); err != nil {
  2353  			return errors.Wrap(err, "failed to delete Threads")
  2354  		}
  2355  		if _, err := s.GetMaster().Exec("DELETE FROM ThreadMemberships WHERE PostId = :Id", map[string]interface{}{"Id": postId}); err != nil {
  2356  			return errors.Wrap(err, "failed to delete ThreadMemberships")
  2357  		}
  2358  		return nil
  2359  	}
  2360  	if rootId != "" {
  2361  		thread, err := s.Thread().Get(rootId)
  2362  		if err != nil {
  2363  			var nfErr *store.ErrNotFound
  2364  			if !errors.As(err, &nfErr) {
  2365  				return errors.Wrap(err, "failed to get a thread")
  2366  			}
  2367  		}
  2368  		if thread != nil {
  2369  			thread.ReplyCount -= 1
  2370  			if _, err = s.Thread().Update(thread); err != nil {
  2371  				return errors.Wrap(err, "failed to update thread")
  2372  			}
  2373  		}
  2374  	}
  2375  	return nil
  2376  }
  2377  
  2378  func (s *SqlPostStore) updateThreadsFromPosts(transaction *gorp.Transaction, posts []*model.Post) error {
  2379  	postsByRoot := map[string][]*model.Post{}
  2380  	var rootIds []string
  2381  	for _, post := range posts {
  2382  		// skip if post is not a part of a thread
  2383  		if post.RootId == "" {
  2384  			continue
  2385  		}
  2386  		rootIds = append(rootIds, post.RootId)
  2387  		postsByRoot[post.RootId] = append(postsByRoot[post.RootId], post)
  2388  	}
  2389  	if len(rootIds) == 0 {
  2390  		return nil
  2391  	}
  2392  	threadsByRootsSql, threadsByRootsArgs, _ := s.getQueryBuilder().Select("*").From("Threads").Where(sq.Eq{"PostId": rootIds}).ToSql()
  2393  	var threadsByRoots []*model.Thread
  2394  	if _, err := transaction.Select(&threadsByRoots, threadsByRootsSql, threadsByRootsArgs...); err != nil {
  2395  		return err
  2396  	}
  2397  
  2398  	threadByRoot := map[string]*model.Thread{}
  2399  	for _, thread := range threadsByRoots {
  2400  		threadByRoot[thread.PostId] = thread
  2401  	}
  2402  
  2403  	for rootId, posts := range postsByRoot {
  2404  		if thread, found := threadByRoot[rootId]; !found {
  2405  			// calculate participants
  2406  			var participants model.StringArray
  2407  			if _, err := transaction.Select(&participants, "SELECT DISTINCT UserId FROM Posts WHERE RootId=:RootId OR Id=:RootId", map[string]interface{}{"RootId": rootId}); err != nil {
  2408  				return err
  2409  			}
  2410  			// calculate reply count
  2411  			count, err := transaction.SelectInt("SELECT COUNT(Id) FROM Posts WHERE RootId=:RootId And DeleteAt=0", map[string]interface{}{"RootId": rootId})
  2412  			if err != nil {
  2413  				return err
  2414  			}
  2415  			// calculate last reply at
  2416  			lastReplyAt, err := transaction.SelectInt("SELECT COALESCE(MAX(Posts.CreateAt), 0) FROM Posts WHERE RootID=:RootId and DeleteAt=0", map[string]interface{}{"RootId": rootId})
  2417  			if err != nil {
  2418  				return err
  2419  			}
  2420  			// no metadata entry, create one
  2421  			if err := transaction.Insert(&model.Thread{
  2422  				PostId:       rootId,
  2423  				ChannelId:    posts[0].ChannelId,
  2424  				ReplyCount:   count,
  2425  				LastReplyAt:  lastReplyAt,
  2426  				Participants: participants,
  2427  			}); err != nil {
  2428  				return err
  2429  			}
  2430  		} else {
  2431  			// metadata exists, update it
  2432  			for _, post := range posts {
  2433  				thread.ReplyCount += 1
  2434  				if !thread.Participants.Contains(post.UserId) {
  2435  					thread.Participants = append(thread.Participants, post.UserId)
  2436  				}
  2437  				if post.CreateAt > thread.LastReplyAt {
  2438  					thread.LastReplyAt = post.CreateAt
  2439  				}
  2440  			}
  2441  			if _, err := transaction.Update(thread); err != nil {
  2442  				return err
  2443  			}
  2444  		}
  2445  	}
  2446  	return nil
  2447  }