github.com/wgh-/mattermost-server@v4.8.0-rc2+incompatible/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  	"fmt"
     8  	"net/http"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"bytes"
    14  
    15  	l4g "github.com/alecthomas/log4go"
    16  	"github.com/mattermost/mattermost-server/einterfaces"
    17  	"github.com/mattermost/mattermost-server/model"
    18  	"github.com/mattermost/mattermost-server/store"
    19  	"github.com/mattermost/mattermost-server/utils"
    20  )
    21  
    22  type SqlPostStore struct {
    23  	SqlStore
    24  	metrics einterfaces.MetricsInterface
    25  }
    26  
    27  const (
    28  	LAST_POST_TIME_CACHE_SIZE = 25000
    29  	LAST_POST_TIME_CACHE_SEC  = 900 // 15 minutes
    30  
    31  	LAST_POSTS_CACHE_SIZE = 1000
    32  	LAST_POSTS_CACHE_SEC  = 900 // 15 minutes
    33  )
    34  
    35  var lastPostTimeCache = utils.NewLru(LAST_POST_TIME_CACHE_SIZE)
    36  var lastPostsCache = utils.NewLru(LAST_POSTS_CACHE_SIZE)
    37  
    38  func ClearPostCaches() {
    39  	lastPostTimeCache.Purge()
    40  	lastPostsCache.Purge()
    41  }
    42  
    43  func NewSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
    44  	s := &SqlPostStore{
    45  		SqlStore: sqlStore,
    46  		metrics:  metrics,
    47  	}
    48  
    49  	for _, db := range sqlStore.GetAllConns() {
    50  		table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
    51  		table.ColMap("Id").SetMaxSize(26)
    52  		table.ColMap("UserId").SetMaxSize(26)
    53  		table.ColMap("ChannelId").SetMaxSize(26)
    54  		table.ColMap("RootId").SetMaxSize(26)
    55  		table.ColMap("ParentId").SetMaxSize(26)
    56  		table.ColMap("OriginalId").SetMaxSize(26)
    57  		table.ColMap("Message").SetMaxSize(4000)
    58  		table.ColMap("Type").SetMaxSize(26)
    59  		table.ColMap("Hashtags").SetMaxSize(1000)
    60  		table.ColMap("Props").SetMaxSize(8000)
    61  		table.ColMap("Filenames").SetMaxSize(4000)
    62  		table.ColMap("FileIds").SetMaxSize(150)
    63  	}
    64  
    65  	return s
    66  }
    67  
    68  func (s SqlPostStore) CreateIndexesIfNotExists() {
    69  	s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt")
    70  	s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
    71  	s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt")
    72  	s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
    73  	s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
    74  	s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
    75  	s.CreateIndexIfNotExists("idx_posts_is_pinned", "Posts", "IsPinned")
    76  
    77  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_update_at", "Posts", []string{"ChannelId", "UpdateAt"})
    78  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_delete_at_create_at", "Posts", []string{"ChannelId", "DeleteAt", "CreateAt"})
    79  
    80  	s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
    81  	s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
    82  }
    83  
    84  func (s SqlPostStore) Save(post *model.Post) store.StoreChannel {
    85  	return store.Do(func(result *store.StoreResult) {
    86  		if len(post.Id) > 0 {
    87  			result.Err = model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
    88  			return
    89  		}
    90  
    91  		post.PreSave()
    92  		if result.Err = post.IsValid(); result.Err != nil {
    93  			return
    94  		}
    95  
    96  		if err := s.GetMaster().Insert(post); err != nil {
    97  			result.Err = model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
    98  		} else {
    99  			time := post.UpdateAt
   100  
   101  			if post.Type != model.POST_JOIN_LEAVE && post.Type != model.POST_ADD_REMOVE &&
   102  				post.Type != model.POST_JOIN_CHANNEL && post.Type != model.POST_LEAVE_CHANNEL &&
   103  				post.Type != model.POST_JOIN_TEAM && post.Type != model.POST_LEAVE_TEAM &&
   104  				post.Type != model.POST_ADD_TO_CHANNEL && post.Type != model.POST_REMOVE_FROM_CHANNEL {
   105  				s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt, TotalMsgCount = TotalMsgCount + 1 WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId})
   106  			} else {
   107  				// don't update TotalMsgCount for unimportant messages so that the channel isn't marked as unread
   108  				s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId})
   109  			}
   110  
   111  			if len(post.RootId) > 0 {
   112  				s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": post.RootId})
   113  			}
   114  
   115  			result.Data = post
   116  		}
   117  	})
   118  }
   119  
   120  func (s SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) store.StoreChannel {
   121  	return store.Do(func(result *store.StoreResult) {
   122  		newPost.UpdateAt = model.GetMillis()
   123  		newPost.PreCommit()
   124  
   125  		oldPost.DeleteAt = newPost.UpdateAt
   126  		oldPost.UpdateAt = newPost.UpdateAt
   127  		oldPost.OriginalId = oldPost.Id
   128  		oldPost.Id = model.NewId()
   129  		oldPost.PreCommit()
   130  
   131  		if result.Err = newPost.IsValid(); result.Err != nil {
   132  			return
   133  		}
   134  
   135  		if _, err := s.GetMaster().Update(newPost); err != nil {
   136  			result.Err = model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError)
   137  		} else {
   138  			time := model.GetMillis()
   139  			s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt  WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": newPost.ChannelId})
   140  
   141  			if len(newPost.RootId) > 0 {
   142  				s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId})
   143  			}
   144  
   145  			// mark the old post as deleted
   146  			s.GetMaster().Insert(oldPost)
   147  
   148  			result.Data = newPost
   149  		}
   150  	})
   151  }
   152  
   153  func (s SqlPostStore) Overwrite(post *model.Post) store.StoreChannel {
   154  	return store.Do(func(result *store.StoreResult) {
   155  		post.UpdateAt = model.GetMillis()
   156  
   157  		if result.Err = post.IsValid(); result.Err != nil {
   158  			return
   159  		}
   160  
   161  		if _, err := s.GetMaster().Update(post); err != nil {
   162  			result.Err = model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
   163  		} else {
   164  			result.Data = post
   165  		}
   166  	})
   167  }
   168  
   169  func (s SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) store.StoreChannel {
   170  	return store.Do(func(result *store.StoreResult) {
   171  		pl := model.NewPostList()
   172  
   173  		var posts []*model.Post
   174  		if _, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts 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 {
   175  			result.Err = model.NewAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   176  		} else {
   177  			for _, post := range posts {
   178  				pl.AddPost(post)
   179  				pl.AddOrder(post.Id)
   180  			}
   181  		}
   182  
   183  		result.Data = pl
   184  	})
   185  }
   186  
   187  func (s SqlPostStore) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) store.StoreChannel {
   188  	return store.Do(func(result *store.StoreResult) {
   189  		pl := model.NewPostList()
   190  
   191  		var posts []*model.Post
   192  
   193  		query := `
   194              SELECT
   195                  A.*
   196              FROM
   197                  (SELECT
   198                      *
   199                  FROM
   200                      Posts
   201                  WHERE
   202                      Id
   203                  IN
   204                      (SELECT
   205                          Name
   206                      FROM
   207                          Preferences
   208                      WHERE
   209                          UserId = :UserId
   210                          AND Category = :Category)
   211                          AND DeleteAt = 0
   212                  ) as A
   213              INNER JOIN Channels as B
   214                  ON B.Id = A.ChannelId
   215              WHERE B.TeamId = :TeamId OR B.TeamId = ''
   216              ORDER BY CreateAt DESC
   217              LIMIT :Limit OFFSET :Offset`
   218  
   219  		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 {
   220  			result.Err = model.NewAppError("SqlPostStore.GetFlaggedPostsForTeam", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   221  		} else {
   222  			for _, post := range posts {
   223  				pl.AddPost(post)
   224  				pl.AddOrder(post.Id)
   225  			}
   226  		}
   227  
   228  		result.Data = pl
   229  	})
   230  }
   231  
   232  func (s SqlPostStore) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) store.StoreChannel {
   233  	return store.Do(func(result *store.StoreResult) {
   234  		pl := model.NewPostList()
   235  
   236  		var posts []*model.Post
   237  		query := `
   238  			SELECT 
   239  				* 
   240  			FROM Posts 
   241  			WHERE 
   242  				Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) 
   243  				AND ChannelId = :ChannelId
   244  				AND DeleteAt = 0 
   245  			ORDER BY CreateAt DESC 
   246  			LIMIT :Limit OFFSET :Offset`
   247  
   248  		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 {
   249  			result.Err = model.NewAppError("SqlPostStore.GetFlaggedPostsForChannel", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   250  		} else {
   251  			for _, post := range posts {
   252  				pl.AddPost(post)
   253  				pl.AddOrder(post.Id)
   254  			}
   255  		}
   256  
   257  		result.Data = pl
   258  	})
   259  }
   260  
   261  func (s SqlPostStore) Get(id string) store.StoreChannel {
   262  	return store.Do(func(result *store.StoreResult) {
   263  		pl := model.NewPostList()
   264  
   265  		if len(id) == 0 {
   266  			result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest)
   267  			return
   268  		}
   269  
   270  		var post model.Post
   271  		err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
   272  		if err != nil {
   273  			result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   274  			return
   275  		}
   276  
   277  		pl.AddPost(&post)
   278  		pl.AddOrder(id)
   279  
   280  		rootId := post.RootId
   281  
   282  		if rootId == "" {
   283  			rootId = post.Id
   284  		}
   285  
   286  		if len(rootId) == 0 {
   287  			result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError)
   288  			return
   289  		}
   290  
   291  		var posts []*model.Post
   292  		_, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId})
   293  		if err != nil {
   294  			result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError)
   295  			return
   296  		} else {
   297  			for _, p := range posts {
   298  				pl.AddPost(p)
   299  			}
   300  		}
   301  
   302  		result.Data = pl
   303  	})
   304  }
   305  
   306  func (s SqlPostStore) GetSingle(id string) store.StoreChannel {
   307  	return store.Do(func(result *store.StoreResult) {
   308  		var post model.Post
   309  		err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
   310  		if err != nil {
   311  			result.Err = model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   312  		}
   313  
   314  		result.Data = &post
   315  	})
   316  }
   317  
   318  type etagPosts struct {
   319  	Id       string
   320  	UpdateAt int64
   321  }
   322  
   323  func (s SqlPostStore) InvalidateLastPostTimeCache(channelId string) {
   324  	lastPostTimeCache.Remove(channelId)
   325  
   326  	// Keys are "{channelid}{limit}" and caching only occurs on limits of 30 and 60
   327  	lastPostsCache.Remove(channelId + "30")
   328  	lastPostsCache.Remove(channelId + "60")
   329  }
   330  
   331  func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel {
   332  	return store.Do(func(result *store.StoreResult) {
   333  		if allowFromCache {
   334  			if cacheItem, ok := lastPostTimeCache.Get(channelId); ok {
   335  				if s.metrics != nil {
   336  					s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   337  				}
   338  				result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, cacheItem.(int64))
   339  				return
   340  			} else {
   341  				if s.metrics != nil {
   342  					s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   343  				}
   344  			}
   345  		} else {
   346  			if s.metrics != nil {
   347  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   348  			}
   349  		}
   350  
   351  		var et etagPosts
   352  		err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId})
   353  		if err != nil {
   354  			result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   355  		} else {
   356  			result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   357  		}
   358  
   359  		lastPostTimeCache.AddWithExpiresInSecs(channelId, et.UpdateAt, LAST_POST_TIME_CACHE_SEC)
   360  	})
   361  }
   362  
   363  func (s SqlPostStore) Delete(postId string, time int64) store.StoreChannel {
   364  	return store.Do(func(result *store.StoreResult) {
   365  		_, err := s.GetMaster().Exec("Update Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "RootId": postId})
   366  		if err != nil {
   367  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   368  		}
   369  	})
   370  }
   371  
   372  func (s SqlPostStore) permanentDelete(postId string) store.StoreChannel {
   373  	return store.Do(func(result *store.StoreResult) {
   374  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   375  		if err != nil {
   376  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   377  		}
   378  	})
   379  }
   380  
   381  func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) store.StoreChannel {
   382  	return store.Do(func(result *store.StoreResult) {
   383  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   384  		if err != nil {
   385  			result.Err = model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   386  		}
   387  	})
   388  }
   389  
   390  func (s SqlPostStore) PermanentDeleteByUser(userId string) store.StoreChannel {
   391  	return store.Do(func(result *store.StoreResult) {
   392  		// First attempt to delete all the comments for a user
   393  		if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil {
   394  			result.Err = r.Err
   395  			return
   396  		}
   397  
   398  		// Now attempt to delete all the root posts for a user.  This will also
   399  		// delete all the comments for each post.
   400  		found := true
   401  		count := 0
   402  
   403  		for found {
   404  			var ids []string
   405  			_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   406  			if err != nil {
   407  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   408  				return
   409  			} else {
   410  				found = false
   411  				for _, id := range ids {
   412  					found = true
   413  					if r := <-s.permanentDelete(id); r.Err != nil {
   414  						result.Err = r.Err
   415  						return
   416  					}
   417  				}
   418  			}
   419  
   420  			// This is a fail safe, give up if more than 10K messages
   421  			count = count + 1
   422  			if count >= 10 {
   423  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   424  				return
   425  			}
   426  		}
   427  	})
   428  }
   429  
   430  func (s SqlPostStore) PermanentDeleteByChannel(channelId string) store.StoreChannel {
   431  	return store.Do(func(result *store.StoreResult) {
   432  		if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   433  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   434  		}
   435  	})
   436  }
   437  
   438  func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) store.StoreChannel {
   439  	return store.Do(func(result *store.StoreResult) {
   440  		if limit > 1000 {
   441  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest)
   442  			return
   443  		}
   444  
   445  		// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   446  		if allowFromCache && offset == 0 && (limit == 60 || limit == 30) {
   447  			if cacheItem, ok := lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok {
   448  				if s.metrics != nil {
   449  					s.metrics.IncrementMemCacheHitCounter("Last Posts Cache")
   450  				}
   451  
   452  				result.Data = cacheItem.(*model.PostList)
   453  				return
   454  			} else {
   455  				if s.metrics != nil {
   456  					s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   457  				}
   458  			}
   459  		} else {
   460  			if s.metrics != nil {
   461  				s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   462  			}
   463  		}
   464  
   465  		rpc := s.getRootPosts(channelId, offset, limit)
   466  		cpc := s.getParentsPosts(channelId, offset, limit)
   467  
   468  		if rpr := <-rpc; rpr.Err != nil {
   469  			result.Err = rpr.Err
   470  		} else if cpr := <-cpc; cpr.Err != nil {
   471  			result.Err = cpr.Err
   472  		} else {
   473  			posts := rpr.Data.([]*model.Post)
   474  			parents := cpr.Data.([]*model.Post)
   475  
   476  			list := model.NewPostList()
   477  
   478  			for _, p := range posts {
   479  				list.AddPost(p)
   480  				list.AddOrder(p.Id)
   481  			}
   482  
   483  			for _, p := range parents {
   484  				list.AddPost(p)
   485  			}
   486  
   487  			list.MakeNonNil()
   488  
   489  			// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   490  			if offset == 0 && (limit == 60 || limit == 30) {
   491  				lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC)
   492  			}
   493  
   494  			result.Data = list
   495  		}
   496  	})
   497  }
   498  
   499  func (s SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) store.StoreChannel {
   500  	return store.Do(func(result *store.StoreResult) {
   501  		if allowFromCache {
   502  			// If the last post in the channel's time is less than or equal to the time we are getting posts since,
   503  			// we can safely return no posts.
   504  			if cacheItem, ok := lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time {
   505  				if s.metrics != nil {
   506  					s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   507  				}
   508  				list := model.NewPostList()
   509  				result.Data = list
   510  				return
   511  			} else {
   512  				if s.metrics != nil {
   513  					s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   514  				}
   515  			}
   516  		} else {
   517  			if s.metrics != nil {
   518  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   519  			}
   520  		}
   521  
   522  		var posts []*model.Post
   523  		_, err := s.GetReplica().Select(&posts,
   524  			`(SELECT
   525  			    *
   526  			FROM
   527  			    Posts
   528  			WHERE
   529  			    (UpdateAt > :Time
   530  			        AND ChannelId = :ChannelId)
   531  			LIMIT 1000)
   532  			UNION
   533  			(SELECT
   534  			    *
   535  			FROM
   536  			    Posts
   537  			WHERE
   538  			    Id
   539  			IN
   540  			    (SELECT * FROM (SELECT
   541  			        RootId
   542  			    FROM
   543  			        Posts
   544  			    WHERE
   545  			        UpdateAt > :Time
   546  			            AND ChannelId = :ChannelId
   547  			    LIMIT 1000) temp_tab))
   548  			ORDER BY CreateAt DESC`,
   549  			map[string]interface{}{"ChannelId": channelId, "Time": time})
   550  
   551  		if err != nil {
   552  			result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   553  		} else {
   554  
   555  			list := model.NewPostList()
   556  
   557  			var latestUpdate int64 = 0
   558  
   559  			for _, p := range posts {
   560  				list.AddPost(p)
   561  				if p.UpdateAt > time {
   562  					list.AddOrder(p.Id)
   563  				}
   564  				if latestUpdate < p.UpdateAt {
   565  					latestUpdate = p.UpdateAt
   566  				}
   567  			}
   568  
   569  			lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC)
   570  
   571  			result.Data = list
   572  		}
   573  	})
   574  }
   575  
   576  func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   577  	return s.getPostsAround(channelId, postId, numPosts, offset, true)
   578  }
   579  
   580  func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   581  	return s.getPostsAround(channelId, postId, numPosts, offset, false)
   582  }
   583  
   584  func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) store.StoreChannel {
   585  	return store.Do(func(result *store.StoreResult) {
   586  		var direction string
   587  		var sort string
   588  		if before {
   589  			direction = "<"
   590  			sort = "DESC"
   591  		} else {
   592  			direction = ">"
   593  			sort = "ASC"
   594  		}
   595  
   596  		var posts []*model.Post
   597  		var parents []*model.Post
   598  		_, err1 := s.GetReplica().Select(&posts,
   599  			`(SELECT
   600  			    *
   601  			FROM
   602  			    Posts
   603  			WHERE
   604  				(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   605  			        AND ChannelId = :ChannelId
   606  					AND DeleteAt = 0)
   607  			ORDER BY CreateAt `+sort+`
   608  			LIMIT :NumPosts
   609  			OFFSET :Offset)`,
   610  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   611  		_, err2 := s.GetReplica().Select(&parents,
   612  			`(SELECT
   613  			    *
   614  			FROM
   615  			    Posts
   616  			WHERE
   617  			    Id
   618  			IN
   619  			    (SELECT * FROM (SELECT
   620  			        RootId
   621  			    FROM
   622  			        Posts
   623  			    WHERE
   624  					(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   625  						AND ChannelId = :ChannelId
   626  						AND DeleteAt = 0)
   627  					ORDER BY CreateAt `+sort+`
   628  					LIMIT :NumPosts
   629  					OFFSET :Offset)
   630  			    temp_tab))
   631  			ORDER BY CreateAt DESC`,
   632  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   633  
   634  		if err1 != nil {
   635  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error(), http.StatusInternalServerError)
   636  		} else if err2 != nil {
   637  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error(), http.StatusInternalServerError)
   638  		} else {
   639  
   640  			list := model.NewPostList()
   641  
   642  			// We need to flip the order if we selected backwards
   643  			if before {
   644  				for _, p := range posts {
   645  					list.AddPost(p)
   646  					list.AddOrder(p.Id)
   647  				}
   648  			} else {
   649  				l := len(posts)
   650  				for i := range posts {
   651  					list.AddPost(posts[l-i-1])
   652  					list.AddOrder(posts[l-i-1].Id)
   653  				}
   654  			}
   655  
   656  			for _, p := range parents {
   657  				list.AddPost(p)
   658  			}
   659  
   660  			result.Data = list
   661  		}
   662  	})
   663  }
   664  
   665  func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel {
   666  	return store.Do(func(result *store.StoreResult) {
   667  		var posts []*model.Post
   668  		_, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
   669  		if err != nil {
   670  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   671  		} else {
   672  			result.Data = posts
   673  		}
   674  	})
   675  }
   676  
   677  func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel {
   678  	return store.Do(func(result *store.StoreResult) {
   679  		var posts []*model.Post
   680  		_, err := s.GetReplica().Select(&posts,
   681  			`SELECT
   682  			    q2.*
   683  			FROM
   684  			    Posts q2
   685  			        INNER JOIN
   686  			    (SELECT DISTINCT
   687  			        q3.RootId
   688  			    FROM
   689  			        (SELECT
   690  			        RootId
   691  			    FROM
   692  			        Posts
   693  			    WHERE
   694  			        ChannelId = :ChannelId1
   695  			            AND DeleteAt = 0
   696  			    ORDER BY CreateAt DESC
   697  			    LIMIT :Limit OFFSET :Offset) q3
   698  			    WHERE q3.RootId != '') q1
   699  			    ON q1.RootId = q2.Id OR q1.RootId = q2.RootId
   700  			WHERE
   701  			    ChannelId = :ChannelId2
   702  			        AND DeleteAt = 0
   703  			ORDER BY CreateAt`,
   704  			map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
   705  		if err != nil {
   706  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   707  		} else {
   708  			result.Data = posts
   709  		}
   710  	})
   711  }
   712  
   713  var specialSearchChar = []string{
   714  	"<",
   715  	">",
   716  	"+",
   717  	"-",
   718  	"(",
   719  	")",
   720  	"~",
   721  	"@",
   722  	":",
   723  }
   724  
   725  func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel {
   726  	return store.Do(func(result *store.StoreResult) {
   727  		queryParams := map[string]interface{}{
   728  			"TeamId": teamId,
   729  			"UserId": userId,
   730  		}
   731  
   732  		termMap := map[string]bool{}
   733  		terms := params.Terms
   734  
   735  		if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 {
   736  			result.Data = []*model.Post{}
   737  			return
   738  		}
   739  
   740  		searchType := "Message"
   741  		if params.IsHashtag {
   742  			searchType = "Hashtags"
   743  			for _, term := range strings.Split(terms, " ") {
   744  				termMap[strings.ToUpper(term)] = true
   745  			}
   746  		}
   747  
   748  		// these chars have special meaning and can be treated as spaces
   749  		for _, c := range specialSearchChar {
   750  			terms = strings.Replace(terms, c, " ", -1)
   751  		}
   752  
   753  		var posts []*model.Post
   754  
   755  		searchQuery := `
   756  			SELECT
   757  				*
   758  			FROM
   759  				Posts
   760  			WHERE
   761  				DeleteAt = 0
   762  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
   763  				POST_FILTER
   764  				AND ChannelId IN (
   765  					SELECT
   766  						Id
   767  					FROM
   768  						Channels,
   769  						ChannelMembers
   770  					WHERE
   771  						Id = ChannelId
   772  							AND (TeamId = :TeamId OR TeamId = '')
   773  							AND UserId = :UserId
   774  							AND DeleteAt = 0
   775  							CHANNEL_FILTER)
   776  				SEARCH_CLAUSE
   777  				ORDER BY CreateAt DESC
   778  			LIMIT 100`
   779  
   780  		if len(params.InChannels) > 1 {
   781  			inClause := ":InChannel0"
   782  			queryParams["InChannel0"] = params.InChannels[0]
   783  
   784  			for i := 1; i < len(params.InChannels); i++ {
   785  				paramName := "InChannel" + strconv.FormatInt(int64(i), 10)
   786  				inClause += ", :" + paramName
   787  				queryParams[paramName] = params.InChannels[i]
   788  			}
   789  
   790  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1)
   791  		} else if len(params.InChannels) == 1 {
   792  			queryParams["InChannel"] = params.InChannels[0]
   793  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1)
   794  		} else {
   795  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1)
   796  		}
   797  
   798  		if len(params.FromUsers) > 1 {
   799  			inClause := ":FromUser0"
   800  			queryParams["FromUser0"] = params.FromUsers[0]
   801  
   802  			for i := 1; i < len(params.FromUsers); i++ {
   803  				paramName := "FromUser" + strconv.FormatInt(int64(i), 10)
   804  				inClause += ", :" + paramName
   805  				queryParams[paramName] = params.FromUsers[i]
   806  			}
   807  
   808  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   809  				AND UserId IN (
   810  					SELECT
   811  						Id
   812  					FROM
   813  						Users,
   814  						TeamMembers
   815  					WHERE
   816  						TeamMembers.TeamId = :TeamId
   817  						AND Users.Id = TeamMembers.UserId
   818  						AND Username IN (`+inClause+`))`, 1)
   819  		} else if len(params.FromUsers) == 1 {
   820  			queryParams["FromUser"] = params.FromUsers[0]
   821  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   822  				AND UserId IN (
   823  					SELECT
   824  						Id
   825  					FROM
   826  						Users,
   827  						TeamMembers
   828  					WHERE
   829  						TeamMembers.TeamId = :TeamId
   830  						AND Users.Id = TeamMembers.UserId
   831  						AND Username = :FromUser)`, 1)
   832  		} else {
   833  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
   834  		}
   835  
   836  		if terms == "" {
   837  			// we've already confirmed that we have a channel or user to search for
   838  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
   839  		} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   840  			// Parse text for wildcards
   841  			if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
   842  				terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
   843  			}
   844  
   845  			if params.OrTerms {
   846  				terms = strings.Join(strings.Fields(terms), " | ")
   847  			} else {
   848  				terms = strings.Join(strings.Fields(terms), " & ")
   849  			}
   850  
   851  			searchClause := fmt.Sprintf("AND %s @@  to_tsquery(:Terms)", searchType)
   852  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   853  		} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   854  			searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
   855  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   856  
   857  			if !params.OrTerms {
   858  				splitTerms := strings.Fields(terms)
   859  				for i, t := range strings.Fields(terms) {
   860  					splitTerms[i] = "+" + t
   861  				}
   862  
   863  				terms = strings.Join(splitTerms, " ")
   864  			}
   865  		}
   866  
   867  		queryParams["Terms"] = terms
   868  
   869  		list := model.NewPostList()
   870  
   871  		_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
   872  		if err != nil {
   873  			l4g.Warn(utils.T("store.sql_post.search.warn"), err.Error())
   874  			// 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.
   875  		} else {
   876  			for _, p := range posts {
   877  				if searchType == "Hashtags" {
   878  					exactMatch := false
   879  					for _, tag := range strings.Split(p.Hashtags, " ") {
   880  						if termMap[strings.ToUpper(tag)] {
   881  							exactMatch = true
   882  						}
   883  					}
   884  					if !exactMatch {
   885  						continue
   886  					}
   887  				}
   888  				list.AddPost(p)
   889  				list.AddOrder(p.Id)
   890  			}
   891  		}
   892  
   893  		list.MakeNonNil()
   894  
   895  		result.Data = list
   896  	})
   897  }
   898  
   899  func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) store.StoreChannel {
   900  	return store.Do(func(result *store.StoreResult) {
   901  		query :=
   902  			`SELECT DISTINCT
   903  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   904  			        COUNT(DISTINCT Posts.UserId) AS Value
   905  			FROM Posts`
   906  
   907  		if len(teamId) > 0 {
   908  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   909  		} else {
   910  			query += " WHERE"
   911  		}
   912  
   913  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   914  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   915  			ORDER BY Name DESC
   916  			LIMIT 30`
   917  
   918  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   919  			query =
   920  				`SELECT
   921  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
   922  				FROM Posts`
   923  
   924  			if len(teamId) > 0 {
   925  				query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   926  			} else {
   927  				query += " WHERE"
   928  			}
   929  
   930  			query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   931  				GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
   932  				ORDER BY Name DESC
   933  				LIMIT 30`
   934  		}
   935  
   936  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   937  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   938  
   939  		var rows model.AnalyticsRows
   940  		_, err := s.GetReplica().Select(
   941  			&rows,
   942  			query,
   943  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   944  		if err != nil {
   945  			result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
   946  		} else {
   947  			result.Data = rows
   948  		}
   949  	})
   950  }
   951  
   952  func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) store.StoreChannel {
   953  	return store.Do(func(result *store.StoreResult) {
   954  		query :=
   955  			`SELECT
   956  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   957  			        COUNT(Posts.Id) AS Value
   958  			    FROM Posts`
   959  
   960  		if len(teamId) > 0 {
   961  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   962  		} else {
   963  			query += " WHERE"
   964  		}
   965  
   966  		query += ` Posts.CreateAt <= :EndTime
   967  			            AND Posts.CreateAt >= :StartTime
   968  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   969  			ORDER BY Name DESC
   970  			LIMIT 30`
   971  
   972  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   973  			query =
   974  				`SELECT
   975  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
   976  				FROM Posts`
   977  
   978  			if len(teamId) > 0 {
   979  				query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
   980  			} else {
   981  				query += " WHERE"
   982  			}
   983  
   984  			query += ` Posts.CreateAt <= :EndTime
   985  				            AND Posts.CreateAt >= :StartTime
   986  				GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
   987  				ORDER BY Name DESC
   988  				LIMIT 30`
   989  		}
   990  
   991  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   992  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   993  
   994  		var rows model.AnalyticsRows
   995  		_, err := s.GetReplica().Select(
   996  			&rows,
   997  			query,
   998  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   999  		if err != nil {
  1000  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1001  		} else {
  1002  			result.Data = rows
  1003  		}
  1004  	})
  1005  }
  1006  
  1007  func (s SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) store.StoreChannel {
  1008  	return store.Do(func(result *store.StoreResult) {
  1009  		query :=
  1010  			`SELECT
  1011  			    COUNT(Posts.Id) AS Value
  1012  			FROM
  1013  			    Posts,
  1014  			    Channels
  1015  			WHERE
  1016  			    Posts.ChannelId = Channels.Id`
  1017  
  1018  		if len(teamId) > 0 {
  1019  			query += " AND Channels.TeamId = :TeamId"
  1020  		}
  1021  
  1022  		if mustHaveFile {
  1023  			query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')"
  1024  		}
  1025  
  1026  		if mustHaveHashtag {
  1027  			query += " AND Posts.Hashtags != ''"
  1028  		}
  1029  
  1030  		if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
  1031  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1032  		} else {
  1033  			result.Data = v
  1034  		}
  1035  	})
  1036  }
  1037  
  1038  func (s SqlPostStore) GetPostsCreatedAt(channelId string, time int64) store.StoreChannel {
  1039  	return store.Do(func(result *store.StoreResult) {
  1040  		query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1041  
  1042  		var posts []*model.Post
  1043  		_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1044  
  1045  		if err != nil {
  1046  			result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
  1047  		} else {
  1048  			result.Data = posts
  1049  		}
  1050  	})
  1051  }
  1052  
  1053  func (s SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel {
  1054  	return store.Do(func(result *store.StoreResult) {
  1055  		keys := bytes.Buffer{}
  1056  		params := make(map[string]interface{})
  1057  		for i, postId := range postIds {
  1058  			if keys.Len() > 0 {
  1059  				keys.WriteString(",")
  1060  			}
  1061  
  1062  			key := "Post" + strconv.Itoa(i)
  1063  			keys.WriteString(":" + key)
  1064  			params[key] = postId
  1065  		}
  1066  
  1067  		query := `SELECT * FROM Posts WHERE Id in (` + keys.String() + `) and DeleteAt = 0 ORDER BY CreateAt DESC`
  1068  
  1069  		var posts []*model.Post
  1070  		_, err := s.GetReplica().Select(&posts, query, params)
  1071  
  1072  		if err != nil {
  1073  			l4g.Error(err)
  1074  			result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1075  		} else {
  1076  			result.Data = posts
  1077  		}
  1078  	})
  1079  }
  1080  
  1081  func (s SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) store.StoreChannel {
  1082  	return store.Do(func(result *store.StoreResult) {
  1083  		var posts []*model.PostForIndexing
  1084  		_, err1 := s.GetSearchReplica().Select(&posts,
  1085  			`SELECT
  1086  				PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1087  			FROM (
  1088  				SELECT
  1089  					*
  1090  				FROM
  1091  					Posts
  1092  				WHERE
  1093  					Posts.CreateAt >= :StartTime
  1094  				AND
  1095  					Posts.CreateAt < :EndTime
  1096  				ORDER BY
  1097  					CreateAt ASC
  1098  				LIMIT
  1099  					1000
  1100  				)
  1101  			AS
  1102  				PostsQuery
  1103  			LEFT JOIN
  1104  				Channels
  1105  			ON
  1106  				PostsQuery.ChannelId = Channels.Id
  1107  			LEFT JOIN
  1108  				Posts ParentPosts
  1109  			ON
  1110  				PostsQuery.RootId = ParentPosts.Id`,
  1111  			map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1112  
  1113  		if err1 != nil {
  1114  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err1.Error(), http.StatusInternalServerError)
  1115  		} else {
  1116  			result.Data = posts
  1117  		}
  1118  	})
  1119  }
  1120  
  1121  func (s SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
  1122  	return store.Do(func(result *store.StoreResult) {
  1123  		var query string
  1124  		if s.DriverName() == "postgres" {
  1125  			query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1126  		} else {
  1127  			query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1128  		}
  1129  
  1130  		sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1131  		if err != nil {
  1132  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1133  		} else {
  1134  			rowsAffected, err1 := sqlResult.RowsAffected()
  1135  			if err1 != nil {
  1136  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1137  				result.Data = int64(0)
  1138  			} else {
  1139  				result.Data = rowsAffected
  1140  			}
  1141  		}
  1142  	})
  1143  }
  1144  
  1145  func (s SqlPostStore) GetOldest() store.StoreChannel {
  1146  	return store.Do(func(result *store.StoreResult) {
  1147  		var post model.Post
  1148  		err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1149  		if err != nil {
  1150  			result.Err = model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1151  		}
  1152  
  1153  		result.Data = &post
  1154  	})
  1155  }