github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/store/sqlstore/post_store.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sqlstore
     5  
     6  import (
     7  	"database/sql"
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	sq "github.com/Masterminds/squirrel"
    16  	"github.com/mattermost/mattermost-server/v5/einterfaces"
    17  	"github.com/mattermost/mattermost-server/v5/mlog"
    18  	"github.com/mattermost/mattermost-server/v5/model"
    19  	"github.com/mattermost/mattermost-server/v5/store"
    20  	"github.com/mattermost/mattermost-server/v5/utils"
    21  )
    22  
    23  type SqlPostStore struct {
    24  	SqlStore
    25  	metrics           einterfaces.MetricsInterface
    26  	maxPostSizeOnce   sync.Once
    27  	maxPostSizeCached int
    28  }
    29  
    30  func (s *SqlPostStore) ClearCaches() {
    31  }
    32  
    33  func postSliceColumns() []string {
    34  	return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "IsPinned", "UserId", "ChannelId", "RootId", "ParentId", "OriginalId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions"}
    35  }
    36  
    37  func postToSlice(post *model.Post) []interface{} {
    38  	return []interface{}{
    39  		post.Id,
    40  		post.CreateAt,
    41  		post.UpdateAt,
    42  		post.EditAt,
    43  		post.DeleteAt,
    44  		post.IsPinned,
    45  		post.UserId,
    46  		post.ChannelId,
    47  		post.RootId,
    48  		post.ParentId,
    49  		post.OriginalId,
    50  		post.Message,
    51  		post.Type,
    52  		model.StringInterfaceToJson(post.Props),
    53  		post.Hashtags,
    54  		model.ArrayToJson(post.Filenames),
    55  		model.ArrayToJson(post.FileIds),
    56  		post.HasReactions,
    57  	}
    58  }
    59  
    60  func newSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
    61  	s := &SqlPostStore{
    62  		SqlStore:          sqlStore,
    63  		metrics:           metrics,
    64  		maxPostSizeCached: model.POST_MESSAGE_MAX_RUNES_V1,
    65  	}
    66  
    67  	for _, db := range sqlStore.GetAllConns() {
    68  		table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
    69  		table.ColMap("Id").SetMaxSize(26)
    70  		table.ColMap("UserId").SetMaxSize(26)
    71  		table.ColMap("ChannelId").SetMaxSize(26)
    72  		table.ColMap("RootId").SetMaxSize(26)
    73  		table.ColMap("ParentId").SetMaxSize(26)
    74  		table.ColMap("OriginalId").SetMaxSize(26)
    75  		table.ColMap("Message").SetMaxSize(model.POST_MESSAGE_MAX_BYTES_V2)
    76  		table.ColMap("Type").SetMaxSize(26)
    77  		table.ColMap("Hashtags").SetMaxSize(1000)
    78  		table.ColMap("Props").SetMaxSize(8000)
    79  		table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES)
    80  		table.ColMap("FileIds").SetMaxSize(150)
    81  	}
    82  
    83  	return s
    84  }
    85  
    86  func (s *SqlPostStore) createIndexesIfNotExists() {
    87  	s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt")
    88  	s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
    89  	s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt")
    90  	s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
    91  	s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
    92  	s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
    93  	s.CreateIndexIfNotExists("idx_posts_is_pinned", "Posts", "IsPinned")
    94  
    95  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_update_at", "Posts", []string{"ChannelId", "UpdateAt"})
    96  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_delete_at_create_at", "Posts", []string{"ChannelId", "DeleteAt", "CreateAt"})
    97  
    98  	s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
    99  	s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
   100  }
   101  
   102  func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, *model.AppError) {
   103  	channelNewPosts := make(map[string]int)
   104  	maxDateNewPosts := make(map[string]int64)
   105  	rootIds := make(map[string]int)
   106  	maxDateRootIds := make(map[string]int64)
   107  	for idx, post := range posts {
   108  		if len(post.Id) > 0 {
   109  			return nil, idx, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
   110  		}
   111  		post.PreSave()
   112  		maxPostSize := s.GetMaxPostSize()
   113  		if err := post.IsValid(maxPostSize); err != nil {
   114  			return nil, idx, err
   115  		}
   116  
   117  		currentChannelCount, ok := channelNewPosts[post.ChannelId]
   118  		if !ok {
   119  			if post.IsJoinLeaveMessage() {
   120  				channelNewPosts[post.ChannelId] = 0
   121  			} else {
   122  				channelNewPosts[post.ChannelId] = 1
   123  			}
   124  			maxDateNewPosts[post.ChannelId] = post.CreateAt
   125  		} else {
   126  			if !post.IsJoinLeaveMessage() {
   127  				channelNewPosts[post.ChannelId] = currentChannelCount + 1
   128  			}
   129  			if post.CreateAt > maxDateNewPosts[post.ChannelId] {
   130  				maxDateNewPosts[post.ChannelId] = post.CreateAt
   131  			}
   132  		}
   133  
   134  		if len(post.RootId) == 0 {
   135  			continue
   136  		}
   137  
   138  		currentRootCount, ok := rootIds[post.RootId]
   139  		if !ok {
   140  			rootIds[post.RootId] = 1
   141  			maxDateRootIds[post.RootId] = post.CreateAt
   142  		} else {
   143  			rootIds[post.RootId] = currentRootCount + 1
   144  			if post.CreateAt > maxDateRootIds[post.RootId] {
   145  				maxDateRootIds[post.RootId] = post.CreateAt
   146  			}
   147  		}
   148  	}
   149  
   150  	query := s.getQueryBuilder().Insert("Posts").Columns(postSliceColumns()...)
   151  	for _, post := range posts {
   152  		query = query.Values(postToSlice(post)...)
   153  	}
   154  	sql, args, err := query.ToSql()
   155  	if err != nil {
   156  		return nil, -1, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
   157  	}
   158  
   159  	if _, err := s.GetMaster().Exec(sql, args...); err != nil {
   160  		return nil, -1, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
   161  	}
   162  
   163  	for channelId, count := range channelNewPosts {
   164  		if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": maxDateNewPosts[channelId], "ChannelId": channelId, "Count": count}); err != nil {
   165  			mlog.Error("Error updating Channel LastPostAt.", mlog.Err(err))
   166  		}
   167  	}
   168  
   169  	for rootId := range rootIds {
   170  		if _, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": maxDateRootIds[rootId], "RootId": rootId}); err != nil {
   171  			mlog.Error("Error updating Post UpdateAt.", mlog.Err(err))
   172  		}
   173  	}
   174  
   175  	unknownRepliesPosts := []*model.Post{}
   176  	for _, post := range posts {
   177  		if len(post.RootId) == 0 {
   178  			count, ok := rootIds[post.Id]
   179  			if ok {
   180  				post.ReplyCount += int64(count)
   181  			}
   182  		} else {
   183  			unknownRepliesPosts = append(unknownRepliesPosts, post)
   184  		}
   185  	}
   186  
   187  	if len(unknownRepliesPosts) > 0 {
   188  		if err := s.populateReplyCount(unknownRepliesPosts); err != nil {
   189  			mlog.Error("Unable to populate the reply count in some posts.", mlog.Err(err))
   190  		}
   191  	}
   192  
   193  	return posts, -1, nil
   194  }
   195  
   196  func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
   197  	posts, _, err := s.SaveMultiple([]*model.Post{post})
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	return posts[0], nil
   202  }
   203  
   204  func (s *SqlPostStore) populateReplyCount(posts []*model.Post) *model.AppError {
   205  	rootIds := []string{}
   206  	for _, post := range posts {
   207  		rootIds = append(rootIds, post.RootId)
   208  	}
   209  	countList := []struct {
   210  		RootId string
   211  		Count  int64
   212  	}{}
   213  	query := s.getQueryBuilder().Select("RootId, COUNT(Id) AS Count").From("Posts").Where(sq.Eq{"RootId": rootIds}).Where(sq.Eq{"DeleteAt": 0}).GroupBy("RootId")
   214  
   215  	queryString, args, err := query.ToSql()
   216  	if err != nil {
   217  		return model.NewAppError("SqlPostStore.populateReplyCount", "store.sql_post.populate_reply_count.app_error", nil, err.Error(), http.StatusInternalServerError)
   218  	}
   219  	_, err = s.GetMaster().Select(&countList, queryString, args...)
   220  	if err != nil {
   221  		return model.NewAppError("SqlPostStore.populateReplyCount", "store.sql_post.populate_reply_count.app_error", nil, err.Error(), http.StatusInternalServerError)
   222  	}
   223  
   224  	counts := map[string]int64{}
   225  	for _, count := range countList {
   226  		counts[count.RootId] = count.Count
   227  	}
   228  
   229  	for _, post := range posts {
   230  		count, ok := counts[post.RootId]
   231  		if !ok {
   232  			post.ReplyCount = 0
   233  		}
   234  		post.ReplyCount = count
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, *model.AppError) {
   241  	newPost.UpdateAt = model.GetMillis()
   242  	newPost.PreCommit()
   243  
   244  	oldPost.DeleteAt = newPost.UpdateAt
   245  	oldPost.UpdateAt = newPost.UpdateAt
   246  	oldPost.OriginalId = oldPost.Id
   247  	oldPost.Id = model.NewId()
   248  	oldPost.PreCommit()
   249  
   250  	maxPostSize := s.GetMaxPostSize()
   251  
   252  	if err := newPost.IsValid(maxPostSize); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if _, err := s.GetMaster().Update(newPost); err != nil {
   257  		return nil, model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError)
   258  	}
   259  
   260  	time := model.GetMillis()
   261  	s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt  WHERE Id = :ChannelId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ChannelId": newPost.ChannelId})
   262  
   263  	if len(newPost.RootId) > 0 {
   264  		s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId AND UpdateAt < :UpdateAt", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId})
   265  	}
   266  
   267  	// mark the old post as deleted
   268  	s.GetMaster().Insert(oldPost)
   269  
   270  	return newPost, nil
   271  }
   272  
   273  func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, *model.AppError) {
   274  	updateAt := model.GetMillis()
   275  	maxPostSize := s.GetMaxPostSize()
   276  	for idx, post := range posts {
   277  		post.UpdateAt = updateAt
   278  		if appErr := post.IsValid(maxPostSize); appErr != nil {
   279  			return nil, idx, appErr
   280  		}
   281  	}
   282  
   283  	tx, err := s.GetMaster().Begin()
   284  	if err != nil {
   285  		return nil, -1, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
   286  	}
   287  	for idx, post := range posts {
   288  		if _, err = tx.Update(post); err != nil {
   289  			txErr := tx.Rollback()
   290  			if txErr != nil {
   291  				return nil, idx, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, txErr.Error(), http.StatusInternalServerError)
   292  			}
   293  
   294  			return nil, idx, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
   295  		}
   296  	}
   297  	err = tx.Commit()
   298  	if err != nil {
   299  		return nil, -1, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
   300  	}
   301  
   302  	return posts, -1, nil
   303  }
   304  
   305  func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) {
   306  	posts, _, err := s.OverwriteMultiple([]*model.Post{post})
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  
   311  	return posts[0], nil
   312  }
   313  
   314  func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
   315  	pl := model.NewPostList()
   316  
   317  	var posts []*model.Post
   318  	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 {
   319  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   320  	}
   321  
   322  	for _, post := range posts {
   323  		pl.AddPost(post)
   324  		pl.AddOrder(post.Id)
   325  	}
   326  
   327  	return pl, nil
   328  }
   329  
   330  func (s *SqlPostStore) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) {
   331  	pl := model.NewPostList()
   332  
   333  	var posts []*model.Post
   334  
   335  	query := `
   336              SELECT
   337                  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
   338              FROM
   339                  (SELECT
   340                      *
   341                  FROM
   342                      Posts
   343                  WHERE
   344                      Id
   345                  IN
   346                      (SELECT
   347                          Name
   348                      FROM
   349                          Preferences
   350                      WHERE
   351                          UserId = :UserId
   352                          AND Category = :Category)
   353                          AND DeleteAt = 0
   354                  ) as A
   355              INNER JOIN Channels as B
   356                  ON B.Id = A.ChannelId
   357              WHERE B.TeamId = :TeamId OR B.TeamId = ''
   358              ORDER BY CreateAt DESC
   359              LIMIT :Limit OFFSET :Offset`
   360  
   361  	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 {
   362  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForTeam", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   363  	}
   364  
   365  	for _, post := range posts {
   366  		pl.AddPost(post)
   367  		pl.AddOrder(post.Id)
   368  	}
   369  
   370  	return pl, nil
   371  }
   372  
   373  func (s *SqlPostStore) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
   374  	pl := model.NewPostList()
   375  
   376  	var posts []*model.Post
   377  	query := `
   378  		SELECT
   379  			*, (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
   380  		FROM Posts p
   381  		WHERE
   382  			Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category)
   383  			AND ChannelId = :ChannelId
   384  			AND DeleteAt = 0
   385  		ORDER BY CreateAt DESC
   386  		LIMIT :Limit OFFSET :Offset`
   387  
   388  	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 {
   389  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForChannel", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   390  	}
   391  	for _, post := range posts {
   392  		pl.AddPost(post)
   393  		pl.AddOrder(post.Id)
   394  	}
   395  
   396  	return pl, nil
   397  }
   398  
   399  func (s *SqlPostStore) Get(id string, skipFetchThreads bool) (*model.PostList, *model.AppError) {
   400  	pl := model.NewPostList()
   401  
   402  	if len(id) == 0 {
   403  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest)
   404  	}
   405  
   406  	var post model.Post
   407  	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"
   408  	err := s.GetReplica().SelectOne(&post, postFetchQuery, map[string]interface{}{"Id": id})
   409  	if err != nil {
   410  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   411  	}
   412  	pl.AddPost(&post)
   413  	pl.AddOrder(id)
   414  	if !skipFetchThreads {
   415  		rootId := post.RootId
   416  
   417  		if rootId == "" {
   418  			rootId = post.Id
   419  		}
   420  
   421  		if len(rootId) == 0 {
   422  			return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError)
   423  		}
   424  
   425  		var posts []*model.Post
   426  		_, 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})
   427  		if err != nil {
   428  			return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError)
   429  		}
   430  
   431  		for _, p := range posts {
   432  			pl.AddPost(p)
   433  			pl.AddOrder(p.Id)
   434  		}
   435  	}
   436  	return pl, nil
   437  }
   438  
   439  func (s *SqlPostStore) GetSingle(id string) (*model.Post, *model.AppError) {
   440  	var post model.Post
   441  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
   442  	if err != nil {
   443  		return nil, model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   444  	}
   445  	return &post, nil
   446  }
   447  
   448  type etagPosts struct {
   449  	Id       string
   450  	UpdateAt int64
   451  }
   452  
   453  func (s *SqlPostStore) InvalidateLastPostTimeCache(channelId string) {
   454  }
   455  
   456  func (s *SqlPostStore) GetEtag(channelId string, allowFromCache bool) string {
   457  	var et etagPosts
   458  	err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId})
   459  	var result string
   460  	if err != nil {
   461  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   462  	} else {
   463  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   464  	}
   465  
   466  	return result
   467  }
   468  
   469  func (s *SqlPostStore) Delete(postId string, time int64, deleteByID string) *model.AppError {
   470  
   471  	appErr := func(errMsg string) *model.AppError {
   472  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+errMsg, http.StatusInternalServerError)
   473  	}
   474  
   475  	var post model.Post
   476  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId})
   477  	if err != nil {
   478  		return appErr(err.Error())
   479  	}
   480  
   481  	post.AddProp(model.POST_PROPS_DELETE_BY, deleteByID)
   482  
   483  	_, 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())})
   484  	if err != nil {
   485  		return appErr(err.Error())
   486  	}
   487  
   488  	return nil
   489  }
   490  
   491  func (s *SqlPostStore) permanentDelete(postId string) *model.AppError {
   492  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   493  	if err != nil {
   494  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   495  	}
   496  	return nil
   497  }
   498  
   499  func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) *model.AppError {
   500  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   501  	if err != nil {
   502  		return model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   503  	}
   504  	return nil
   505  }
   506  
   507  func (s *SqlPostStore) PermanentDeleteByUser(userId string) *model.AppError {
   508  	// First attempt to delete all the comments for a user
   509  	if err := s.permanentDeleteAllCommentByUser(userId); err != nil {
   510  		return err
   511  	}
   512  
   513  	// Now attempt to delete all the root posts for a user. This will also
   514  	// delete all the comments for each post
   515  	found := true
   516  	count := 0
   517  
   518  	for found {
   519  		var ids []string
   520  		_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   521  		if err != nil {
   522  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   523  		}
   524  
   525  		found = false
   526  		for _, id := range ids {
   527  			found = true
   528  			if err := s.permanentDelete(id); err != nil {
   529  				return err
   530  			}
   531  		}
   532  
   533  		// This is a fail safe, give up if more than 10k messages
   534  		count++
   535  		if count >= 10 {
   536  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   537  		}
   538  	}
   539  
   540  	return nil
   541  }
   542  
   543  func (s *SqlPostStore) PermanentDeleteByChannel(channelId string) *model.AppError {
   544  	if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   545  		return model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   546  	}
   547  	return nil
   548  }
   549  
   550  func (s *SqlPostStore) GetPosts(options model.GetPostsOptions, _ bool) (*model.PostList, *model.AppError) {
   551  	if options.PerPage > 1000 {
   552  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+options.ChannelId, http.StatusBadRequest)
   553  	}
   554  	offset := options.PerPage * options.Page
   555  
   556  	rpc := make(chan store.StoreResult, 1)
   557  	go func() {
   558  		posts, err := s.getRootPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
   559  		rpc <- store.StoreResult{Data: posts, Err: err}
   560  		close(rpc)
   561  	}()
   562  	cpc := make(chan store.StoreResult, 1)
   563  	go func() {
   564  		posts, err := s.getParentsPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
   565  		cpc <- store.StoreResult{Data: posts, Err: err}
   566  		close(cpc)
   567  	}()
   568  
   569  	var err *model.AppError
   570  	list := model.NewPostList()
   571  
   572  	rpr := <-rpc
   573  	if rpr.Err != nil {
   574  		return nil, rpr.Err
   575  	}
   576  
   577  	cpr := <-cpc
   578  	if cpr.Err != nil {
   579  		return nil, cpr.Err
   580  	}
   581  
   582  	posts := rpr.Data.([]*model.Post)
   583  	parents := cpr.Data.([]*model.Post)
   584  
   585  	for _, p := range posts {
   586  		list.AddPost(p)
   587  		list.AddOrder(p.Id)
   588  	}
   589  
   590  	for _, p := range parents {
   591  		list.AddPost(p)
   592  	}
   593  
   594  	list.MakeNonNil()
   595  
   596  	return list, err
   597  }
   598  
   599  func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool) (*model.PostList, *model.AppError) {
   600  	var posts []*model.Post
   601  
   602  	replyCountQuery1 := ""
   603  	replyCountQuery2 := ""
   604  	if options.SkipFetchThreads {
   605  		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`
   606  		replyCountQuery2 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p2.RootId = '' THEN p2.Id ELSE p2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   607  	}
   608  	var query string
   609  
   610  	// union of IDs and then join to get full posts is faster in mysql
   611  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   612  		query = `SELECT *` + replyCountQuery1 + ` FROM Posts p1 JOIN (
   613  			(SELECT
   614                Id
   615  			  FROM
   616  				  Posts p2
   617  			  WHERE
   618  				  (UpdateAt > :Time
   619  					  AND ChannelId = :ChannelId)
   620  				  LIMIT 1000)
   621  			  UNION
   622  				  (SELECT
   623  					  Id
   624  				  FROM
   625  					  Posts p3
   626  				  WHERE
   627  					  Id
   628  				  IN
   629  					  (SELECT * FROM (SELECT
   630  						  RootId
   631  					  FROM
   632  						  Posts
   633  					  WHERE
   634  						  UpdateAt > :Time
   635  							  AND ChannelId = :ChannelId
   636  					  LIMIT 1000) temp_tab))
   637  			) j ON p1.Id = j.Id
   638            ORDER BY CreateAt DESC`
   639  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   640  		query = `
   641  			(SELECT
   642                         *` + replyCountQuery1 + `
   643                 FROM
   644                         Posts p1
   645                 WHERE
   646                         (UpdateAt > :Time
   647                                 AND ChannelId = :ChannelId)
   648                         LIMIT 1000)
   649                 UNION
   650                         (SELECT
   651                             *` + replyCountQuery2 + `
   652                         FROM
   653                             Posts p2
   654                         WHERE
   655                             Id
   656                         IN
   657                             (SELECT * FROM (SELECT
   658                                 RootId
   659                             FROM
   660                                 Posts
   661                             WHERE
   662                                 UpdateAt > :Time
   663                                                 AND ChannelId = :ChannelId
   664                                 LIMIT 1000) temp_tab))
   665                 ORDER BY CreateAt DESC`
   666  	}
   667  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"ChannelId": options.ChannelId, "Time": options.Time})
   668  
   669  	if err != nil {
   670  		return nil, model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+options.ChannelId+err.Error(), http.StatusInternalServerError)
   671  	}
   672  
   673  	list := model.NewPostList()
   674  
   675  	for _, p := range posts {
   676  		list.AddPost(p)
   677  		if p.UpdateAt > options.Time {
   678  			list.AddOrder(p.Id)
   679  		}
   680  	}
   681  
   682  	return list, nil
   683  }
   684  
   685  func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   686  	return s.getPostsAround(true, options)
   687  }
   688  
   689  func (s *SqlPostStore) GetPostsAfter(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   690  	return s.getPostsAround(false, options)
   691  }
   692  
   693  func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   694  	if options.Page < 0 || options.PerPage < 0 {
   695  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil,
   696  			fmt.Sprintf("Page=%d and PerPage=%d must be non-negative", options.Page, options.PerPage), http.StatusBadRequest)
   697  	}
   698  
   699  	offset := options.Page * options.PerPage
   700  	var posts, parents []*model.Post
   701  
   702  	var direction string
   703  	var sort string
   704  	if before {
   705  		direction = "<"
   706  		sort = "DESC"
   707  	} else {
   708  		direction = ">"
   709  		sort = "ASC"
   710  	}
   711  	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"))
   712  	query := s.getQueryBuilder().Select("p.*")
   713  	query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
   714  	query = query.From("Posts p").
   715  		Where(sq.And{
   716  			sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId),
   717  			sq.Eq{"ChannelId": options.ChannelId},
   718  			sq.Eq{"DeleteAt": int(0)},
   719  		}).
   720  		// Adding ChannelId and DeleteAt order columns
   721  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
   722  		// See MM-24170.
   723  		OrderBy("ChannelId", "DeleteAt", "CreateAt "+sort).
   724  		Limit(uint64(options.PerPage)).
   725  		Offset(uint64(offset))
   726  
   727  	queryString, args, err := query.ToSql()
   728  	if err != nil {
   729  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+options.ChannelId+err.Error(), http.StatusInternalServerError)
   730  	}
   731  	_, err = s.GetMaster().Select(&posts, queryString, args...)
   732  	if err != nil {
   733  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+options.ChannelId+err.Error(), http.StatusInternalServerError)
   734  	}
   735  
   736  	if len(posts) > 0 {
   737  		rootIds := []string{}
   738  		for _, post := range posts {
   739  			rootIds = append(rootIds, post.Id)
   740  			if post.RootId != "" {
   741  				rootIds = append(rootIds, post.RootId)
   742  			}
   743  		}
   744  		rootQuery := s.getQueryBuilder().Select("p.*")
   745  		idQuery := sq.Or{
   746  			sq.Eq{"Id": rootIds},
   747  		}
   748  		rootQuery = rootQuery.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
   749  		if !options.SkipFetchThreads {
   750  			idQuery = append(idQuery, sq.Eq{"RootId": rootIds}) // preserve original behaviour
   751  		}
   752  
   753  		rootQuery = rootQuery.From("Posts p").
   754  			Where(sq.And{
   755  				idQuery,
   756  				sq.Eq{"ChannelId": options.ChannelId},
   757  				sq.Eq{"DeleteAt": 0},
   758  			}).
   759  			OrderBy("CreateAt DESC")
   760  
   761  		rootQueryString, rootArgs, err := rootQuery.ToSql()
   762  
   763  		if err != nil {
   764  			return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+options.ChannelId+err.Error(), http.StatusInternalServerError)
   765  		}
   766  		_, err = s.GetMaster().Select(&parents, rootQueryString, rootArgs...)
   767  		if err != nil {
   768  			return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+options.ChannelId+err.Error(), http.StatusInternalServerError)
   769  		}
   770  	}
   771  
   772  	list := model.NewPostList()
   773  
   774  	// We need to flip the order if we selected backwards
   775  	if before {
   776  		for _, p := range posts {
   777  			list.AddPost(p)
   778  			list.AddOrder(p.Id)
   779  		}
   780  	} else {
   781  		l := len(posts)
   782  		for i := range posts {
   783  			list.AddPost(posts[l-i-1])
   784  			list.AddOrder(posts[l-i-1].Id)
   785  		}
   786  	}
   787  
   788  	for _, p := range parents {
   789  		list.AddPost(p)
   790  	}
   791  
   792  	return list, nil
   793  }
   794  
   795  func (s *SqlPostStore) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) {
   796  	return s.getPostIdAroundTime(channelId, time, true)
   797  }
   798  
   799  func (s *SqlPostStore) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) {
   800  	return s.getPostIdAroundTime(channelId, time, false)
   801  }
   802  
   803  func (s *SqlPostStore) getPostIdAroundTime(channelId string, time int64, before bool) (string, *model.AppError) {
   804  	var direction sq.Sqlizer
   805  	var sort string
   806  	if before {
   807  		direction = sq.Lt{"CreateAt": time}
   808  		sort = "DESC"
   809  	} else {
   810  		direction = sq.Gt{"CreateAt": time}
   811  		sort = "ASC"
   812  	}
   813  
   814  	query := s.getQueryBuilder().
   815  		Select("Id").
   816  		From("Posts").
   817  		Where(sq.And{
   818  			direction,
   819  			sq.Eq{"ChannelId": channelId},
   820  			sq.Eq{"DeleteAt": int(0)},
   821  		}).
   822  		// Adding ChannelId and DeleteAt order columns
   823  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
   824  		// See MM-23369.
   825  		OrderBy("ChannelId", "DeleteAt", "CreateAt "+sort).
   826  		Limit(1)
   827  
   828  	queryString, args, err := query.ToSql()
   829  	if err != nil {
   830  		return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError)
   831  	}
   832  
   833  	var postId string
   834  	if err := s.GetMaster().SelectOne(&postId, queryString, args...); err != nil {
   835  		if err != sql.ErrNoRows {
   836  			return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   837  		}
   838  	}
   839  
   840  	return postId, nil
   841  }
   842  
   843  func (s *SqlPostStore) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
   844  	query := s.getQueryBuilder().
   845  		Select("*").
   846  		From("Posts").
   847  		Where(sq.And{
   848  			sq.Gt{"CreateAt": time},
   849  			sq.Eq{"ChannelId": channelId},
   850  			sq.Eq{"DeleteAt": int(0)},
   851  		}).
   852  		// Adding ChannelId and DeleteAt order columns
   853  		// to let mysql choose the "idx_posts_channel_id_delete_at_create_at" index always.
   854  		// See MM-23369.
   855  		OrderBy("ChannelId", "DeleteAt", "CreateAt ASC").
   856  		Limit(1)
   857  
   858  	queryString, args, err := query.ToSql()
   859  	if err != nil {
   860  		return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, err.Error(), http.StatusInternalServerError)
   861  	}
   862  
   863  	var post *model.Post
   864  	if err := s.GetMaster().SelectOne(&post, queryString, args...); err != nil {
   865  		if err != sql.ErrNoRows {
   866  			return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   867  		}
   868  	}
   869  
   870  	return post, nil
   871  }
   872  
   873  func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   874  	var posts []*model.Post
   875  	var fetchQuery string
   876  	if skipFetchThreads {
   877  		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"
   878  	} else {
   879  		fetchQuery = "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
   880  	}
   881  	_, err := s.GetReplica().Select(&posts, fetchQuery, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
   882  	if err != nil {
   883  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   884  	}
   885  	return posts, nil
   886  }
   887  
   888  func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   889  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   890  		return s.getParentsPostsPostgreSQL(channelId, offset, limit, skipFetchThreads)
   891  	}
   892  
   893  	// query parent Ids first
   894  	var roots []*struct {
   895  		RootId string
   896  	}
   897  	rootQuery := `
   898  		SELECT DISTINCT
   899  			q.RootId
   900  		FROM
   901  			(SELECT
   902  				RootId
   903  			FROM
   904  				Posts
   905  			WHERE
   906  				ChannelId = :ChannelId
   907  					AND DeleteAt = 0
   908  			ORDER BY CreateAt DESC
   909  			LIMIT :Limit OFFSET :Offset) q
   910  		WHERE q.RootId != ''`
   911  
   912  	_, err := s.GetReplica().Select(&roots, rootQuery, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
   913  	if err != nil {
   914  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   915  	}
   916  	if len(roots) == 0 {
   917  		return nil, nil
   918  	}
   919  	params := make(map[string]interface{})
   920  	placeholders := make([]string, len(roots))
   921  	for idx, r := range roots {
   922  		key := fmt.Sprintf(":Root%v", idx)
   923  		params[key[1:]] = r.RootId
   924  		placeholders[idx] = key
   925  	}
   926  	placeholderString := strings.Join(placeholders, ", ")
   927  	params["ChannelId"] = channelId
   928  	replyCountQuery := ""
   929  	whereStatement := "p.Id IN (" + placeholderString + ")"
   930  	if skipFetchThreads {
   931  		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`
   932  	} else {
   933  		whereStatement += " OR p.RootId IN (" + placeholderString + ")"
   934  	}
   935  	var posts []*model.Post
   936  	_, err = s.GetReplica().Select(&posts, `
   937  		SELECT p.*`+replyCountQuery+`
   938  		FROM
   939  			Posts p
   940  		WHERE
   941  			(`+whereStatement+`)
   942  				AND ChannelId = :ChannelId
   943  				AND DeleteAt = 0
   944  		ORDER BY CreateAt`,
   945  		params)
   946  	if err != nil {
   947  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   948  	}
   949  	return posts, nil
   950  }
   951  
   952  func (s *SqlPostStore) getParentsPostsPostgreSQL(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   953  	var posts []*model.Post
   954  	replyCountQuery := ""
   955  	onStatement := "q1.RootId = q2.Id"
   956  	if skipFetchThreads {
   957  		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`
   958  	} else {
   959  		onStatement += " OR q1.RootId = q2.RootId"
   960  	}
   961  	_, err := s.GetReplica().Select(&posts,
   962  		`SELECT q2.*`+replyCountQuery+`
   963          FROM
   964              Posts q2
   965                  INNER JOIN
   966              (SELECT DISTINCT
   967                  q3.RootId
   968              FROM
   969                  (SELECT
   970                      RootId
   971                  FROM
   972                      Posts
   973                  WHERE
   974                      ChannelId = :ChannelId1
   975                          AND DeleteAt = 0
   976                  ORDER BY CreateAt DESC
   977                  LIMIT :Limit OFFSET :Offset) q3
   978              WHERE q3.RootId != '') q1
   979              ON `+onStatement+`
   980          WHERE
   981              ChannelId = :ChannelId2
   982                  AND DeleteAt = 0
   983          ORDER BY CreateAt`,
   984  		map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
   985  	if err != nil {
   986  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   987  	}
   988  	return posts, nil
   989  }
   990  
   991  var specialSearchChar = []string{
   992  	"<",
   993  	">",
   994  	"+",
   995  	"-",
   996  	"(",
   997  	")",
   998  	"~",
   999  	"@",
  1000  	":",
  1001  }
  1002  
  1003  func (s *SqlPostStore) buildCreateDateFilterClause(params *model.SearchParams, queryParams map[string]interface{}) (string, map[string]interface{}) {
  1004  	searchQuery := ""
  1005  	// handle after: before: on: filters
  1006  	if len(params.OnDate) > 0 {
  1007  		onDateStart, onDateEnd := params.GetOnDateMillis()
  1008  		queryParams["OnDateStart"] = strconv.FormatInt(onDateStart, 10)
  1009  		queryParams["OnDateEnd"] = strconv.FormatInt(onDateEnd, 10)
  1010  
  1011  		// between `on date` start of day and end of day
  1012  		searchQuery += "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd "
  1013  	} else {
  1014  
  1015  		if len(params.ExcludedDate) > 0 {
  1016  			excludedDateStart, excludedDateEnd := params.GetExcludedDateMillis()
  1017  			queryParams["ExcludedDateStart"] = strconv.FormatInt(excludedDateStart, 10)
  1018  			queryParams["ExcludedDateEnd"] = strconv.FormatInt(excludedDateEnd, 10)
  1019  
  1020  			searchQuery += "AND CreateAt NOT BETWEEN :ExcludedDateStart AND :ExcludedDateEnd "
  1021  		}
  1022  
  1023  		if len(params.AfterDate) > 0 {
  1024  			afterDate := params.GetAfterDateMillis()
  1025  			queryParams["AfterDate"] = strconv.FormatInt(afterDate, 10)
  1026  
  1027  			// greater than `after date`
  1028  			searchQuery += "AND CreateAt >= :AfterDate "
  1029  		}
  1030  
  1031  		if len(params.BeforeDate) > 0 {
  1032  			beforeDate := params.GetBeforeDateMillis()
  1033  			queryParams["BeforeDate"] = strconv.FormatInt(beforeDate, 10)
  1034  
  1035  			// less than `before date`
  1036  			searchQuery += "AND CreateAt <= :BeforeDate "
  1037  		}
  1038  
  1039  		if len(params.ExcludedAfterDate) > 0 {
  1040  			afterDate := params.GetExcludedAfterDateMillis()
  1041  			queryParams["ExcludedAfterDate"] = strconv.FormatInt(afterDate, 10)
  1042  
  1043  			searchQuery += "AND CreateAt < :ExcludedAfterDate "
  1044  		}
  1045  
  1046  		if len(params.ExcludedBeforeDate) > 0 {
  1047  			beforeDate := params.GetExcludedBeforeDateMillis()
  1048  			queryParams["ExcludedBeforeDate"] = strconv.FormatInt(beforeDate, 10)
  1049  
  1050  			searchQuery += "AND CreateAt > :ExcludedBeforeDate "
  1051  		}
  1052  	}
  1053  
  1054  	return searchQuery, queryParams
  1055  }
  1056  
  1057  func (s *SqlPostStore) buildSearchChannelFilterClause(channels []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byName bool) (string, map[string]interface{}) {
  1058  	if len(channels) == 0 {
  1059  		return "", queryParams
  1060  	}
  1061  
  1062  	clauseSlice := []string{}
  1063  	for i, channel := range channels {
  1064  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
  1065  		clauseSlice = append(clauseSlice, ":"+paramName)
  1066  		queryParams[paramName] = channel
  1067  	}
  1068  	clause := strings.Join(clauseSlice, ", ")
  1069  	if byName {
  1070  		if exclusion {
  1071  			return "AND Name NOT IN (" + clause + ")", queryParams
  1072  		}
  1073  		return "AND Name IN (" + clause + ")", queryParams
  1074  	}
  1075  
  1076  	if exclusion {
  1077  		return "AND Id NOT IN (" + clause + ")", queryParams
  1078  	}
  1079  	return "AND Id IN (" + clause + ")", queryParams
  1080  }
  1081  
  1082  func (s *SqlPostStore) buildSearchUserFilterClause(users []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byUsername bool) (string, map[string]interface{}) {
  1083  	if len(users) == 0 {
  1084  		return "", queryParams
  1085  	}
  1086  	clauseSlice := []string{}
  1087  	for i, user := range users {
  1088  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
  1089  		clauseSlice = append(clauseSlice, ":"+paramName)
  1090  		queryParams[paramName] = user
  1091  	}
  1092  	clause := strings.Join(clauseSlice, ", ")
  1093  	if byUsername {
  1094  		if exclusion {
  1095  			return "AND Username NOT IN (" + clause + ")", queryParams
  1096  		}
  1097  		return "AND Username IN (" + clause + ")", queryParams
  1098  	}
  1099  	if exclusion {
  1100  		return "AND Id NOT IN (" + clause + ")", queryParams
  1101  	}
  1102  	return "AND Id IN (" + clause + ")", queryParams
  1103  }
  1104  
  1105  func (s *SqlPostStore) buildSearchPostFilterClause(fromUsers []string, excludedUsers []string, queryParams map[string]interface{}, userByUsername bool) (string, map[string]interface{}) {
  1106  	if len(fromUsers) == 0 && len(excludedUsers) == 0 {
  1107  		return "", queryParams
  1108  	}
  1109  
  1110  	filterQuery := `
  1111  		AND UserId IN (
  1112  			SELECT
  1113  				Id
  1114  			FROM
  1115  				Users,
  1116  				TeamMembers
  1117  			WHERE
  1118  				TeamMembers.TeamId = :TeamId
  1119  				AND Users.Id = TeamMembers.UserId
  1120  				FROM_USER_FILTER
  1121  				EXCLUDED_USER_FILTER)`
  1122  
  1123  	fromUserClause, queryParams := s.buildSearchUserFilterClause(fromUsers, "FromUser", false, queryParams, userByUsername)
  1124  	filterQuery = strings.Replace(filterQuery, "FROM_USER_FILTER", fromUserClause, 1)
  1125  
  1126  	excludedUserClause, queryParams := s.buildSearchUserFilterClause(excludedUsers, "ExcludedUser", true, queryParams, userByUsername)
  1127  	filterQuery = strings.Replace(filterQuery, "EXCLUDED_USER_FILTER", excludedUserClause, 1)
  1128  
  1129  	return filterQuery, queryParams
  1130  }
  1131  
  1132  func (s *SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) (*model.PostList, *model.AppError) {
  1133  	return s.search(teamId, userId, params, true, true)
  1134  }
  1135  
  1136  func (s *SqlPostStore) search(teamId string, userId string, params *model.SearchParams, channelsByName bool, userByUsername bool) (*model.PostList, *model.AppError) {
  1137  	queryParams := map[string]interface{}{
  1138  		"TeamId": teamId,
  1139  		"UserId": userId,
  1140  	}
  1141  
  1142  	list := model.NewPostList()
  1143  	if params.Terms == "" && params.ExcludedTerms == "" &&
  1144  		len(params.InChannels) == 0 && len(params.ExcludedChannels) == 0 &&
  1145  		len(params.FromUsers) == 0 && len(params.ExcludedUsers) == 0 &&
  1146  		len(params.OnDate) == 0 && len(params.AfterDate) == 0 && len(params.BeforeDate) == 0 {
  1147  		return list, nil
  1148  	}
  1149  
  1150  	var posts []*model.Post
  1151  
  1152  	deletedQueryPart := "AND DeleteAt = 0"
  1153  	if params.IncludeDeletedChannels {
  1154  		deletedQueryPart = ""
  1155  	}
  1156  
  1157  	userIdPart := "AND UserId = :UserId"
  1158  	if params.SearchWithoutUserId {
  1159  		userIdPart = ""
  1160  	}
  1161  
  1162  	searchQuery := `
  1163  			SELECT
  1164  				* ,(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
  1165  			FROM
  1166  				Posts q2
  1167  			WHERE
  1168  				DeleteAt = 0
  1169  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
  1170  				POST_FILTER
  1171  				AND ChannelId IN (
  1172  					SELECT
  1173  						Id
  1174  					FROM
  1175  						Channels,
  1176  						ChannelMembers
  1177  					WHERE
  1178  						Id = ChannelId
  1179  							AND (TeamId = :TeamId OR TeamId = '')
  1180  							` + userIdPart + `
  1181  							` + deletedQueryPart + `
  1182  							IN_CHANNEL_FILTER
  1183  							EXCLUDED_CHANNEL_FILTER)
  1184  				CREATEDATE_CLAUSE
  1185  				SEARCH_CLAUSE
  1186  				ORDER BY CreateAt DESC
  1187  			LIMIT 100`
  1188  
  1189  	inChannelClause, queryParams := s.buildSearchChannelFilterClause(params.InChannels, "InChannel", false, queryParams, channelsByName)
  1190  	searchQuery = strings.Replace(searchQuery, "IN_CHANNEL_FILTER", inChannelClause, 1)
  1191  
  1192  	excludedChannelClause, queryParams := s.buildSearchChannelFilterClause(params.ExcludedChannels, "ExcludedChannel", true, queryParams, channelsByName)
  1193  	searchQuery = strings.Replace(searchQuery, "EXCLUDED_CHANNEL_FILTER", excludedChannelClause, 1)
  1194  
  1195  	postFilterClause, queryParams := s.buildSearchPostFilterClause(params.FromUsers, params.ExcludedUsers, queryParams, userByUsername)
  1196  	searchQuery = strings.Replace(searchQuery, "POST_FILTER", postFilterClause, 1)
  1197  
  1198  	createDateFilterClause, queryParams := s.buildCreateDateFilterClause(params, queryParams)
  1199  	searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", createDateFilterClause, 1)
  1200  
  1201  	termMap := map[string]bool{}
  1202  	terms := params.Terms
  1203  	excludedTerms := params.ExcludedTerms
  1204  
  1205  	searchType := "Message"
  1206  	if params.IsHashtag {
  1207  		searchType = "Hashtags"
  1208  		for _, term := range strings.Split(terms, " ") {
  1209  			termMap[strings.ToUpper(term)] = true
  1210  		}
  1211  	}
  1212  
  1213  	// these chars have special meaning and can be treated as spaces
  1214  	for _, c := range specialSearchChar {
  1215  		terms = strings.Replace(terms, c, " ", -1)
  1216  		excludedTerms = strings.Replace(excludedTerms, c, " ", -1)
  1217  	}
  1218  
  1219  	if terms == "" && excludedTerms == "" {
  1220  		// we've already confirmed that we have a channel or user to search for
  1221  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
  1222  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1223  		// Parse text for wildcards
  1224  		if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
  1225  			terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
  1226  			excludedTerms = wildcard.ReplaceAllLiteralString(excludedTerms, ":* ")
  1227  		}
  1228  
  1229  		excludeClause := ""
  1230  		if excludedTerms != "" {
  1231  			excludeClause = " & !(" + strings.Join(strings.Fields(excludedTerms), " | ") + ")"
  1232  		}
  1233  
  1234  		if params.OrTerms {
  1235  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " | ") + ")" + excludeClause
  1236  		} else {
  1237  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " & ") + ")" + excludeClause
  1238  		}
  1239  
  1240  		searchClause := fmt.Sprintf("AND to_tsvector('english', %s) @@  to_tsquery('english', :Terms)", searchType)
  1241  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1242  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1243  		searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
  1244  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1245  
  1246  		excludeClause := ""
  1247  		if excludedTerms != "" {
  1248  			excludeClause = " -(" + excludedTerms + ")"
  1249  		}
  1250  
  1251  		if params.OrTerms {
  1252  			queryParams["Terms"] = terms + excludeClause
  1253  		} else {
  1254  			splitTerms := []string{}
  1255  			for _, t := range strings.Fields(terms) {
  1256  				splitTerms = append(splitTerms, "+"+t)
  1257  			}
  1258  			queryParams["Terms"] = strings.Join(splitTerms, " ") + excludeClause
  1259  		}
  1260  	}
  1261  
  1262  	_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
  1263  	if err != nil {
  1264  		mlog.Warn("Query error searching posts.", mlog.Err(err))
  1265  		// 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.
  1266  	} else {
  1267  		for _, p := range posts {
  1268  			if searchType == "Hashtags" {
  1269  				exactMatch := false
  1270  				for _, tag := range strings.Split(p.Hashtags, " ") {
  1271  					if termMap[strings.ToUpper(tag)] {
  1272  						exactMatch = true
  1273  						break
  1274  					}
  1275  				}
  1276  				if !exactMatch {
  1277  					continue
  1278  				}
  1279  			}
  1280  			list.AddPost(p)
  1281  			list.AddOrder(p.Id)
  1282  		}
  1283  	}
  1284  	list.MakeNonNil()
  1285  	return list, nil
  1286  }
  1287  
  1288  func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) (model.AnalyticsRows, *model.AppError) {
  1289  	query :=
  1290  		`SELECT DISTINCT
  1291  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1292  		        COUNT(DISTINCT Posts.UserId) AS Value
  1293  		FROM Posts`
  1294  
  1295  	if len(teamId) > 0 {
  1296  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1297  	} else {
  1298  		query += " WHERE"
  1299  	}
  1300  
  1301  	query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1302  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1303  		ORDER BY Name DESC
  1304  		LIMIT 30`
  1305  
  1306  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1307  		query =
  1308  			`SELECT
  1309  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
  1310  			FROM Posts`
  1311  
  1312  		if len(teamId) > 0 {
  1313  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1314  		} else {
  1315  			query += " WHERE"
  1316  		}
  1317  
  1318  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1319  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1320  			ORDER BY Name DESC
  1321  			LIMIT 30`
  1322  	}
  1323  
  1324  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1325  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1326  
  1327  	var rows model.AnalyticsRows
  1328  	_, err := s.GetReplica().Select(
  1329  		&rows,
  1330  		query,
  1331  		map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
  1332  	if err != nil {
  1333  		return nil, model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1334  	}
  1335  	return rows, nil
  1336  }
  1337  
  1338  func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCountsOptions) (model.AnalyticsRows, *model.AppError) {
  1339  
  1340  	query :=
  1341  		`SELECT
  1342  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1343  		        COUNT(Posts.Id) AS Value
  1344  		    FROM Posts`
  1345  
  1346  	if options.BotsOnly {
  1347  		query += " INNER JOIN Bots ON Posts.UserId = Bots.Userid"
  1348  	}
  1349  
  1350  	if len(options.TeamId) > 0 {
  1351  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1352  	} else {
  1353  		query += " WHERE"
  1354  	}
  1355  
  1356  	query += ` Posts.CreateAt <= :EndTime
  1357  		            AND Posts.CreateAt >= :StartTime
  1358  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1359  		ORDER BY Name DESC
  1360  		LIMIT 30`
  1361  
  1362  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1363  		query =
  1364  			`SELECT
  1365  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
  1366  			FROM Posts`
  1367  
  1368  		if options.BotsOnly {
  1369  			query += " INNER JOIN Bots ON Posts.UserId = Bots.Userid"
  1370  		}
  1371  
  1372  		if len(options.TeamId) > 0 {
  1373  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
  1374  		} else {
  1375  			query += " WHERE"
  1376  		}
  1377  
  1378  		query += ` Posts.CreateAt <= :EndTime
  1379  			            AND Posts.CreateAt >= :StartTime
  1380  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1381  			ORDER BY Name DESC
  1382  			LIMIT 30`
  1383  	}
  1384  
  1385  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1386  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1387  	if options.YesterdayOnly {
  1388  		start = utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -1)))
  1389  	}
  1390  
  1391  	var rows model.AnalyticsRows
  1392  	_, err := s.GetReplica().Select(
  1393  		&rows,
  1394  		query,
  1395  		map[string]interface{}{"TeamId": options.TeamId, "StartTime": start, "EndTime": end})
  1396  	if err != nil {
  1397  		return nil, model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1398  	}
  1399  	return rows, nil
  1400  }
  1401  
  1402  func (s *SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) (int64, *model.AppError) {
  1403  	query :=
  1404  		`SELECT
  1405  			COUNT(Posts.Id) AS Value
  1406  		FROM
  1407  			Posts,
  1408  			Channels
  1409  		WHERE
  1410  			Posts.ChannelId = Channels.Id`
  1411  
  1412  	if len(teamId) > 0 {
  1413  		query += " AND Channels.TeamId = :TeamId"
  1414  	}
  1415  
  1416  	if mustHaveFile {
  1417  		query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')"
  1418  	}
  1419  
  1420  	if mustHaveHashtag {
  1421  		query += " AND Posts.Hashtags != ''"
  1422  	}
  1423  
  1424  	v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
  1425  	if err != nil {
  1426  		return 0, model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1427  	}
  1428  
  1429  	return v, nil
  1430  }
  1431  
  1432  func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) ([]*model.Post, *model.AppError) {
  1433  	query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1434  
  1435  	var posts []*model.Post
  1436  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1437  
  1438  	if err != nil {
  1439  		return nil, model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
  1440  	}
  1441  	return posts, nil
  1442  }
  1443  
  1444  func (s *SqlPostStore) GetPostsByIds(postIds []string) ([]*model.Post, *model.AppError) {
  1445  	keys, params := MapStringsToQueryParams(postIds, "Post")
  1446  
  1447  	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`
  1448  
  1449  	var posts []*model.Post
  1450  	_, err := s.GetReplica().Select(&posts, query, params)
  1451  
  1452  	if err != nil {
  1453  		mlog.Error("Query error getting posts.", mlog.Err(err))
  1454  		return nil, model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1455  	}
  1456  	return posts, nil
  1457  }
  1458  
  1459  func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, *model.AppError) {
  1460  	var posts []*model.PostForIndexing
  1461  	_, err := s.GetSearchReplica().Select(&posts,
  1462  		`SELECT
  1463  			PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1464  		FROM (
  1465  			SELECT
  1466  				*
  1467  			FROM
  1468  				Posts
  1469  			WHERE
  1470  				Posts.CreateAt >= :StartTime
  1471  			AND
  1472  				Posts.CreateAt < :EndTime
  1473  			ORDER BY
  1474  				CreateAt ASC
  1475  			LIMIT
  1476  				1000
  1477  			)
  1478  		AS
  1479  			PostsQuery
  1480  		LEFT JOIN
  1481  			Channels
  1482  		ON
  1483  			PostsQuery.ChannelId = Channels.Id
  1484  		LEFT JOIN
  1485  			Posts ParentPosts
  1486  		ON
  1487  			PostsQuery.RootId = ParentPosts.Id`,
  1488  		map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1489  
  1490  	if err != nil {
  1491  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err.Error(), http.StatusInternalServerError)
  1492  	}
  1493  	return posts, nil
  1494  }
  1495  
  1496  func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) {
  1497  	var query string
  1498  	if s.DriverName() == "postgres" {
  1499  		query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1500  	} else {
  1501  		query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1502  	}
  1503  
  1504  	sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1505  	if err != nil {
  1506  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1507  	}
  1508  
  1509  	rowsAffected, err := sqlResult.RowsAffected()
  1510  	if err != nil {
  1511  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1512  	}
  1513  	return rowsAffected, nil
  1514  }
  1515  
  1516  func (s *SqlPostStore) GetOldest() (*model.Post, *model.AppError) {
  1517  	var post model.Post
  1518  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1519  	if err != nil {
  1520  		return nil, model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1521  	}
  1522  
  1523  	return &post, nil
  1524  }
  1525  
  1526  func (s *SqlPostStore) determineMaxPostSize() int {
  1527  	var maxPostSizeBytes int32
  1528  
  1529  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1530  		// The Post.Message column in Postgres has historically been VARCHAR(4000), but
  1531  		// may be manually enlarged to support longer posts.
  1532  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1533  			SELECT
  1534  				COALESCE(character_maximum_length, 0)
  1535  			FROM
  1536  				information_schema.columns
  1537  			WHERE
  1538  				table_name = 'posts'
  1539  			AND	column_name = 'message'
  1540  		`); err != nil {
  1541  			mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err))
  1542  		}
  1543  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1544  		// The Post.Message column in MySQL has historically been TEXT, with a maximum
  1545  		// limit of 65535.
  1546  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1547  			SELECT
  1548  				COALESCE(CHARACTER_MAXIMUM_LENGTH, 0)
  1549  			FROM
  1550  				INFORMATION_SCHEMA.COLUMNS
  1551  			WHERE
  1552  				table_schema = DATABASE()
  1553  			AND	table_name = 'Posts'
  1554  			AND	column_name = 'Message'
  1555  			LIMIT 0, 1
  1556  		`); err != nil {
  1557  			mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err))
  1558  		}
  1559  	} else {
  1560  		mlog.Warn("No implementation found to determine the maximum supported post size")
  1561  	}
  1562  
  1563  	// Assume a worst-case representation of four bytes per rune.
  1564  	maxPostSize := int(maxPostSizeBytes) / 4
  1565  
  1566  	// To maintain backwards compatibility, don't yield a maximum post
  1567  	// size smaller than the previous limit, even though it wasn't
  1568  	// actually possible to store 4000 runes in all cases.
  1569  	if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 {
  1570  		maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1
  1571  	}
  1572  
  1573  	mlog.Info("Post.Message has size restrictions", mlog.Int("max_characters", maxPostSize), mlog.Int32("max_bytes", maxPostSizeBytes))
  1574  
  1575  	return maxPostSize
  1576  }
  1577  
  1578  // GetMaxPostSize returns the maximum number of runes that may be stored in a post.
  1579  func (s *SqlPostStore) GetMaxPostSize() int {
  1580  	s.maxPostSizeOnce.Do(func() {
  1581  		s.maxPostSizeCached = s.determineMaxPostSize()
  1582  	})
  1583  	return s.maxPostSizeCached
  1584  }
  1585  
  1586  func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) ([]*model.PostForExport, *model.AppError) {
  1587  	for {
  1588  		var rootIds []string
  1589  		_, err := s.GetReplica().Select(&rootIds,
  1590  			`SELECT
  1591  				Id
  1592  			FROM
  1593  				Posts
  1594  			WHERE
  1595  				Id > :AfterId
  1596  				AND RootId = ''
  1597  				AND DeleteAt = 0
  1598  			ORDER BY Id
  1599  			LIMIT :Limit`,
  1600  			map[string]interface{}{"Limit": limit, "AfterId": afterId})
  1601  		if err != nil {
  1602  			return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
  1603  				nil, err.Error(), http.StatusInternalServerError)
  1604  		}
  1605  
  1606  		var postsForExport []*model.PostForExport
  1607  		if len(rootIds) == 0 {
  1608  			return postsForExport, nil
  1609  		}
  1610  
  1611  		keys, params := MapStringsToQueryParams(rootIds, "PostId")
  1612  		_, err = s.GetSearchReplica().Select(&postsForExport, `
  1613  			SELECT
  1614  				p1.*,
  1615  				Users.Username as Username,
  1616  				Teams.Name as TeamName,
  1617  				Channels.Name as ChannelName
  1618  			FROM
  1619  				(Select * FROM Posts WHERE Id IN `+keys+`) p1
  1620  			INNER JOIN
  1621  				Channels ON p1.ChannelId = Channels.Id
  1622  			INNER JOIN
  1623  				Teams ON Channels.TeamId = Teams.Id
  1624  			INNER JOIN
  1625  				Users ON p1.UserId = Users.Id
  1626  			WHERE
  1627  				Channels.DeleteAt = 0
  1628  				AND Teams.DeleteAt = 0
  1629  			ORDER BY
  1630  				p1.Id`,
  1631  			params)
  1632  		if err != nil {
  1633  			return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
  1634  				nil, err.Error(), http.StatusInternalServerError)
  1635  		}
  1636  
  1637  		if len(postsForExport) == 0 {
  1638  			// All of the posts were in channels or teams that were deleted.
  1639  			// Update the afterId and try again.
  1640  			afterId = rootIds[len(rootIds)-1]
  1641  			continue
  1642  		}
  1643  
  1644  		return postsForExport, nil
  1645  	}
  1646  }
  1647  
  1648  func (s *SqlPostStore) GetRepliesForExport(rootId string) ([]*model.ReplyForExport, *model.AppError) {
  1649  	var posts []*model.ReplyForExport
  1650  	_, err := s.GetSearchReplica().Select(&posts, `
  1651  			SELECT
  1652  				Posts.*,
  1653  				Users.Username as Username
  1654  			FROM
  1655  				Posts
  1656  			INNER JOIN
  1657  				Users ON Posts.UserId = Users.Id
  1658  			WHERE
  1659  				Posts.RootId = :RootId
  1660  				AND Posts.DeleteAt = 0
  1661  			ORDER BY
  1662  				Posts.Id`,
  1663  		map[string]interface{}{"RootId": rootId})
  1664  
  1665  	if err != nil {
  1666  		return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1667  	}
  1668  
  1669  	return posts, nil
  1670  }
  1671  
  1672  func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) ([]*model.DirectPostForExport, *model.AppError) {
  1673  	query := s.getQueryBuilder().
  1674  		Select("p.*", "Users.Username as User").
  1675  		From("Posts p").
  1676  		Join("Channels ON p.ChannelId = Channels.Id").
  1677  		Join("Users ON p.UserId = Users.Id").
  1678  		Where(sq.And{
  1679  			sq.Gt{"p.Id": afterId},
  1680  			sq.Eq{"p.ParentId": string("")},
  1681  			sq.Eq{"p.DeleteAt": int(0)},
  1682  			sq.Eq{"Channels.DeleteAt": int(0)},
  1683  			sq.Eq{"Users.DeleteAt": int(0)},
  1684  			sq.Eq{"Channels.Type": []string{"D", "G"}},
  1685  		}).
  1686  		OrderBy("p.Id").
  1687  		Limit(uint64(limit))
  1688  
  1689  	queryString, args, err := query.ToSql()
  1690  	if err != nil {
  1691  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1692  	}
  1693  
  1694  	var posts []*model.DirectPostForExport
  1695  	if _, err = s.GetReplica().Select(&posts, queryString, args...); err != nil {
  1696  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1697  	}
  1698  	var channelIds []string
  1699  	for _, post := range posts {
  1700  		channelIds = append(channelIds, post.ChannelId)
  1701  	}
  1702  	query = s.getQueryBuilder().
  1703  		Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
  1704  		From("ChannelMembers cm").
  1705  		Join("Users u ON ( u.Id = cm.UserId )").
  1706  		Where(sq.Eq{
  1707  			"cm.ChannelId": channelIds,
  1708  		})
  1709  
  1710  	queryString, args, err = query.ToSql()
  1711  	if err != nil {
  1712  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1713  	}
  1714  
  1715  	var channelMembers []*model.ChannelMemberForExport
  1716  	if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
  1717  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1718  	}
  1719  
  1720  	// Build a map of channels and their posts
  1721  	postsChannelMap := make(map[string][]*model.DirectPostForExport)
  1722  	for _, post := range posts {
  1723  		post.ChannelMembers = &[]string{}
  1724  		postsChannelMap[post.ChannelId] = append(postsChannelMap[post.ChannelId], post)
  1725  	}
  1726  
  1727  	// Build a map of channels and their members
  1728  	channelMembersMap := make(map[string][]string)
  1729  	for _, member := range channelMembers {
  1730  		channelMembersMap[member.ChannelId] = append(channelMembersMap[member.ChannelId], member.Username)
  1731  	}
  1732  
  1733  	// Populate each post ChannelMembers extracting it from the channelMembersMap
  1734  	for channelId := range channelMembersMap {
  1735  		for _, post := range postsChannelMap[channelId] {
  1736  			*post.ChannelMembers = channelMembersMap[channelId]
  1737  		}
  1738  	}
  1739  	return posts, nil
  1740  }
  1741  
  1742  func (s *SqlPostStore) SearchPostsInTeamForUser(paramsList []*model.SearchParams, userId, teamId string, isOrSearch, includeDeletedChannels bool, page, perPage int) (*model.PostSearchResults, *model.AppError) {
  1743  	// Since we don't support paging for DB search, we just return nothing for later pages
  1744  	if page > 0 {
  1745  		return model.MakePostSearchResults(model.NewPostList(), nil), nil
  1746  	}
  1747  
  1748  	var wg sync.WaitGroup
  1749  
  1750  	pchan := make(chan store.StoreResult, len(paramsList))
  1751  
  1752  	for _, params := range paramsList {
  1753  		// remove any unquoted term that contains only non-alphanumeric chars
  1754  		// ex: abcd "**" && abc     >>     abcd "**" abc
  1755  		params.Terms = removeNonAlphaNumericUnquotedTerms(params.Terms, " ")
  1756  		params.IncludeDeletedChannels = includeDeletedChannels
  1757  		params.OrTerms = isOrSearch
  1758  
  1759  		wg.Add(1)
  1760  
  1761  		go func(params *model.SearchParams) {
  1762  			defer wg.Done()
  1763  			postList, err := s.search(teamId, userId, params, false, false)
  1764  			pchan <- store.StoreResult{Data: postList, Err: err}
  1765  		}(params)
  1766  	}
  1767  
  1768  	wg.Wait()
  1769  	close(pchan)
  1770  
  1771  	posts := model.NewPostList()
  1772  
  1773  	for result := range pchan {
  1774  		if result.Err != nil {
  1775  			return nil, result.Err
  1776  		}
  1777  		data := result.Data.(*model.PostList)
  1778  		posts.Extend(data)
  1779  	}
  1780  
  1781  	posts.SortByCreateAt()
  1782  
  1783  	return model.MakePostSearchResults(posts, nil), nil
  1784  }
  1785  
  1786  func (s *SqlPostStore) GetOldestEntityCreationTime() (int64, *model.AppError) {
  1787  	query := s.getQueryBuilder().Select("MIN(min_createat) min_createat").
  1788  		Suffix(`FROM (
  1789  					(SELECT MIN(createat) min_createat FROM Posts)
  1790  					UNION
  1791  					(SELECT MIN(createat) min_createat FROM Users)
  1792  					UNION
  1793  					(SELECT MIN(createat) min_createat FROM Channels)
  1794  				) entities`)
  1795  	queryString, _, err := query.ToSql()
  1796  	if err != nil {
  1797  		return -1, model.NewAppError("SqlPostStore.GetOldestEntityCreationTime",
  1798  			"store.sql_post.get_oldest_entity_creation_time.app_error", nil, err.Error(), http.StatusInternalServerError)
  1799  	}
  1800  	row := s.GetReplica().Db.QueryRow(queryString)
  1801  	var oldest int64
  1802  	if err := row.Scan(&oldest); err != nil {
  1803  		return -1, model.NewAppError("SqlPostStore.GetOldestEntityCreationTime",
  1804  			"store.sql_post.get_oldest_entity_creation_time.app_error", nil, err.Error(), http.StatusInternalServerError)
  1805  	}
  1806  	return oldest, nil
  1807  }