github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/store/sqlstore/post_store.go (about)

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