github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+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  	lastPostsCache.Remove(channelId)
   326  }
   327  
   328  func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel {
   329  	return store.Do(func(result *store.StoreResult) {
   330  		if allowFromCache {
   331  			if cacheItem, ok := lastPostTimeCache.Get(channelId); ok {
   332  				if s.metrics != nil {
   333  					s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   334  				}
   335  				result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, cacheItem.(int64))
   336  				return
   337  			} else {
   338  				if s.metrics != nil {
   339  					s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   340  				}
   341  			}
   342  		} else {
   343  			if s.metrics != nil {
   344  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   345  			}
   346  		}
   347  
   348  		var et etagPosts
   349  		err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId})
   350  		if err != nil {
   351  			result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   352  		} else {
   353  			result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   354  		}
   355  
   356  		lastPostTimeCache.AddWithExpiresInSecs(channelId, et.UpdateAt, LAST_POST_TIME_CACHE_SEC)
   357  	})
   358  }
   359  
   360  func (s SqlPostStore) Delete(postId string, time int64) store.StoreChannel {
   361  	return store.Do(func(result *store.StoreResult) {
   362  		_, 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})
   363  		if err != nil {
   364  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   365  		}
   366  	})
   367  }
   368  
   369  func (s SqlPostStore) permanentDelete(postId string) store.StoreChannel {
   370  	return store.Do(func(result *store.StoreResult) {
   371  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   372  		if err != nil {
   373  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   374  		}
   375  	})
   376  }
   377  
   378  func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) store.StoreChannel {
   379  	return store.Do(func(result *store.StoreResult) {
   380  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   381  		if err != nil {
   382  			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)
   383  		}
   384  	})
   385  }
   386  
   387  func (s SqlPostStore) PermanentDeleteByUser(userId string) store.StoreChannel {
   388  	return store.Do(func(result *store.StoreResult) {
   389  		// First attempt to delete all the comments for a user
   390  		if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil {
   391  			result.Err = r.Err
   392  			return
   393  		}
   394  
   395  		// Now attempt to delete all the root posts for a user.  This will also
   396  		// delete all the comments for each post.
   397  		found := true
   398  		count := 0
   399  
   400  		for found {
   401  			var ids []string
   402  			_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   403  			if err != nil {
   404  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   405  				return
   406  			} else {
   407  				found = false
   408  				for _, id := range ids {
   409  					found = true
   410  					if r := <-s.permanentDelete(id); r.Err != nil {
   411  						result.Err = r.Err
   412  						return
   413  					}
   414  				}
   415  			}
   416  
   417  			// This is a fail safe, give up if more than 10K messages
   418  			count = count + 1
   419  			if count >= 10 {
   420  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   421  				return
   422  			}
   423  		}
   424  	})
   425  }
   426  
   427  func (s SqlPostStore) PermanentDeleteByChannel(channelId string) store.StoreChannel {
   428  	return store.Do(func(result *store.StoreResult) {
   429  		if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   430  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   431  		}
   432  	})
   433  }
   434  
   435  func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) store.StoreChannel {
   436  	return store.Do(func(result *store.StoreResult) {
   437  		if limit > 1000 {
   438  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest)
   439  			return
   440  		}
   441  
   442  		if allowFromCache && offset == 0 && limit == 60 {
   443  			if cacheItem, ok := lastPostsCache.Get(channelId); ok {
   444  				if s.metrics != nil {
   445  					s.metrics.IncrementMemCacheHitCounter("Last Posts Cache")
   446  				}
   447  
   448  				result.Data = cacheItem.(*model.PostList)
   449  				return
   450  			} else {
   451  				if s.metrics != nil {
   452  					s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   453  				}
   454  			}
   455  		} else {
   456  			if s.metrics != nil {
   457  				s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   458  			}
   459  		}
   460  
   461  		rpc := s.getRootPosts(channelId, offset, limit)
   462  		cpc := s.getParentsPosts(channelId, offset, limit)
   463  
   464  		if rpr := <-rpc; rpr.Err != nil {
   465  			result.Err = rpr.Err
   466  		} else if cpr := <-cpc; cpr.Err != nil {
   467  			result.Err = cpr.Err
   468  		} else {
   469  			posts := rpr.Data.([]*model.Post)
   470  			parents := cpr.Data.([]*model.Post)
   471  
   472  			list := model.NewPostList()
   473  
   474  			for _, p := range posts {
   475  				list.AddPost(p)
   476  				list.AddOrder(p.Id)
   477  			}
   478  
   479  			for _, p := range parents {
   480  				list.AddPost(p)
   481  			}
   482  
   483  			list.MakeNonNil()
   484  
   485  			if offset == 0 && limit == 60 {
   486  				lastPostsCache.AddWithExpiresInSecs(channelId, list, LAST_POSTS_CACHE_SEC)
   487  			}
   488  
   489  			result.Data = list
   490  		}
   491  	})
   492  }
   493  
   494  func (s SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) store.StoreChannel {
   495  	return store.Do(func(result *store.StoreResult) {
   496  		if allowFromCache {
   497  			// If the last post in the channel's time is less than or equal to the time we are getting posts since,
   498  			// we can safely return no posts.
   499  			if cacheItem, ok := lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time {
   500  				if s.metrics != nil {
   501  					s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   502  				}
   503  				list := model.NewPostList()
   504  				result.Data = list
   505  				return
   506  			} else {
   507  				if s.metrics != nil {
   508  					s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   509  				}
   510  			}
   511  		} else {
   512  			if s.metrics != nil {
   513  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   514  			}
   515  		}
   516  
   517  		var posts []*model.Post
   518  		_, err := s.GetReplica().Select(&posts,
   519  			`(SELECT
   520  			    *
   521  			FROM
   522  			    Posts
   523  			WHERE
   524  			    (UpdateAt > :Time
   525  			        AND ChannelId = :ChannelId)
   526  			LIMIT 1000)
   527  			UNION
   528  			(SELECT
   529  			    *
   530  			FROM
   531  			    Posts
   532  			WHERE
   533  			    Id
   534  			IN
   535  			    (SELECT * FROM (SELECT
   536  			        RootId
   537  			    FROM
   538  			        Posts
   539  			    WHERE
   540  			        UpdateAt > :Time
   541  			            AND ChannelId = :ChannelId
   542  			    LIMIT 1000) temp_tab))
   543  			ORDER BY CreateAt DESC`,
   544  			map[string]interface{}{"ChannelId": channelId, "Time": time})
   545  
   546  		if err != nil {
   547  			result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   548  		} else {
   549  
   550  			list := model.NewPostList()
   551  
   552  			var latestUpdate int64 = 0
   553  
   554  			for _, p := range posts {
   555  				list.AddPost(p)
   556  				if p.UpdateAt > time {
   557  					list.AddOrder(p.Id)
   558  				}
   559  				if latestUpdate < p.UpdateAt {
   560  					latestUpdate = p.UpdateAt
   561  				}
   562  			}
   563  
   564  			lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC)
   565  
   566  			result.Data = list
   567  		}
   568  	})
   569  }
   570  
   571  func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   572  	return s.getPostsAround(channelId, postId, numPosts, offset, true)
   573  }
   574  
   575  func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   576  	return s.getPostsAround(channelId, postId, numPosts, offset, false)
   577  }
   578  
   579  func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) store.StoreChannel {
   580  	return store.Do(func(result *store.StoreResult) {
   581  		var direction string
   582  		var sort string
   583  		if before {
   584  			direction = "<"
   585  			sort = "DESC"
   586  		} else {
   587  			direction = ">"
   588  			sort = "ASC"
   589  		}
   590  
   591  		var posts []*model.Post
   592  		var parents []*model.Post
   593  		_, err1 := s.GetReplica().Select(&posts,
   594  			`(SELECT
   595  			    *
   596  			FROM
   597  			    Posts
   598  			WHERE
   599  				(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   600  			        AND ChannelId = :ChannelId
   601  					AND DeleteAt = 0)
   602  			ORDER BY CreateAt `+sort+`
   603  			LIMIT :NumPosts
   604  			OFFSET :Offset)`,
   605  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   606  		_, err2 := s.GetReplica().Select(&parents,
   607  			`(SELECT
   608  			    *
   609  			FROM
   610  			    Posts
   611  			WHERE
   612  			    Id
   613  			IN
   614  			    (SELECT * FROM (SELECT
   615  			        RootId
   616  			    FROM
   617  			        Posts
   618  			    WHERE
   619  					(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   620  						AND ChannelId = :ChannelId
   621  						AND DeleteAt = 0)
   622  					ORDER BY CreateAt `+sort+`
   623  					LIMIT :NumPosts
   624  					OFFSET :Offset)
   625  			    temp_tab))
   626  			ORDER BY CreateAt DESC`,
   627  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   628  
   629  		if err1 != nil {
   630  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error(), http.StatusInternalServerError)
   631  		} else if err2 != nil {
   632  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error(), http.StatusInternalServerError)
   633  		} else {
   634  
   635  			list := model.NewPostList()
   636  
   637  			// We need to flip the order if we selected backwards
   638  			if before {
   639  				for _, p := range posts {
   640  					list.AddPost(p)
   641  					list.AddOrder(p.Id)
   642  				}
   643  			} else {
   644  				l := len(posts)
   645  				for i := range posts {
   646  					list.AddPost(posts[l-i-1])
   647  					list.AddOrder(posts[l-i-1].Id)
   648  				}
   649  			}
   650  
   651  			for _, p := range parents {
   652  				list.AddPost(p)
   653  			}
   654  
   655  			result.Data = list
   656  		}
   657  	})
   658  }
   659  
   660  func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel {
   661  	return store.Do(func(result *store.StoreResult) {
   662  		var posts []*model.Post
   663  		_, 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})
   664  		if err != nil {
   665  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   666  		} else {
   667  			result.Data = posts
   668  		}
   669  	})
   670  }
   671  
   672  func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel {
   673  	return store.Do(func(result *store.StoreResult) {
   674  		var posts []*model.Post
   675  		_, err := s.GetReplica().Select(&posts,
   676  			`SELECT
   677  			    q2.*
   678  			FROM
   679  			    Posts q2
   680  			        INNER JOIN
   681  			    (SELECT DISTINCT
   682  			        q3.RootId
   683  			    FROM
   684  			        (SELECT
   685  			        RootId
   686  			    FROM
   687  			        Posts
   688  			    WHERE
   689  			        ChannelId = :ChannelId1
   690  			            AND DeleteAt = 0
   691  			    ORDER BY CreateAt DESC
   692  			    LIMIT :Limit OFFSET :Offset) q3
   693  			    WHERE q3.RootId != '') q1
   694  			    ON q1.RootId = q2.Id OR q1.RootId = q2.RootId
   695  			WHERE
   696  			    ChannelId = :ChannelId2
   697  			        AND DeleteAt = 0
   698  			ORDER BY CreateAt`,
   699  			map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
   700  		if err != nil {
   701  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   702  		} else {
   703  			result.Data = posts
   704  		}
   705  	})
   706  }
   707  
   708  var specialSearchChar = []string{
   709  	"<",
   710  	">",
   711  	"+",
   712  	"-",
   713  	"(",
   714  	")",
   715  	"~",
   716  	"@",
   717  	":",
   718  }
   719  
   720  func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel {
   721  	return store.Do(func(result *store.StoreResult) {
   722  		queryParams := map[string]interface{}{
   723  			"TeamId": teamId,
   724  			"UserId": userId,
   725  		}
   726  
   727  		termMap := map[string]bool{}
   728  		terms := params.Terms
   729  
   730  		if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 {
   731  			result.Data = []*model.Post{}
   732  			return
   733  		}
   734  
   735  		searchType := "Message"
   736  		if params.IsHashtag {
   737  			searchType = "Hashtags"
   738  			for _, term := range strings.Split(terms, " ") {
   739  				termMap[strings.ToUpper(term)] = true
   740  			}
   741  		}
   742  
   743  		// these chars have special meaning and can be treated as spaces
   744  		for _, c := range specialSearchChar {
   745  			terms = strings.Replace(terms, c, " ", -1)
   746  		}
   747  
   748  		var posts []*model.Post
   749  
   750  		searchQuery := `
   751  			SELECT
   752  				*
   753  			FROM
   754  				Posts
   755  			WHERE
   756  				DeleteAt = 0
   757  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
   758  				POST_FILTER
   759  				AND ChannelId IN (
   760  					SELECT
   761  						Id
   762  					FROM
   763  						Channels,
   764  						ChannelMembers
   765  					WHERE
   766  						Id = ChannelId
   767  							AND (TeamId = :TeamId OR TeamId = '')
   768  							AND UserId = :UserId
   769  							AND DeleteAt = 0
   770  							CHANNEL_FILTER)
   771  				SEARCH_CLAUSE
   772  				ORDER BY CreateAt DESC
   773  			LIMIT 100`
   774  
   775  		if len(params.InChannels) > 1 {
   776  			inClause := ":InChannel0"
   777  			queryParams["InChannel0"] = params.InChannels[0]
   778  
   779  			for i := 1; i < len(params.InChannels); i++ {
   780  				paramName := "InChannel" + strconv.FormatInt(int64(i), 10)
   781  				inClause += ", :" + paramName
   782  				queryParams[paramName] = params.InChannels[i]
   783  			}
   784  
   785  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1)
   786  		} else if len(params.InChannels) == 1 {
   787  			queryParams["InChannel"] = params.InChannels[0]
   788  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1)
   789  		} else {
   790  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1)
   791  		}
   792  
   793  		if len(params.FromUsers) > 1 {
   794  			inClause := ":FromUser0"
   795  			queryParams["FromUser0"] = params.FromUsers[0]
   796  
   797  			for i := 1; i < len(params.FromUsers); i++ {
   798  				paramName := "FromUser" + strconv.FormatInt(int64(i), 10)
   799  				inClause += ", :" + paramName
   800  				queryParams[paramName] = params.FromUsers[i]
   801  			}
   802  
   803  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   804  				AND UserId IN (
   805  					SELECT
   806  						Id
   807  					FROM
   808  						Users,
   809  						TeamMembers
   810  					WHERE
   811  						TeamMembers.TeamId = :TeamId
   812  						AND Users.Id = TeamMembers.UserId
   813  						AND Username IN (`+inClause+`))`, 1)
   814  		} else if len(params.FromUsers) == 1 {
   815  			queryParams["FromUser"] = params.FromUsers[0]
   816  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   817  				AND UserId IN (
   818  					SELECT
   819  						Id
   820  					FROM
   821  						Users,
   822  						TeamMembers
   823  					WHERE
   824  						TeamMembers.TeamId = :TeamId
   825  						AND Users.Id = TeamMembers.UserId
   826  						AND Username = :FromUser)`, 1)
   827  		} else {
   828  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
   829  		}
   830  
   831  		if terms == "" {
   832  			// we've already confirmed that we have a channel or user to search for
   833  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
   834  		} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   835  			// Parse text for wildcards
   836  			if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
   837  				terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
   838  			}
   839  
   840  			if params.OrTerms {
   841  				terms = strings.Join(strings.Fields(terms), " | ")
   842  			} else {
   843  				terms = strings.Join(strings.Fields(terms), " & ")
   844  			}
   845  
   846  			searchClause := fmt.Sprintf("AND %s @@  to_tsquery(:Terms)", searchType)
   847  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   848  		} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   849  			searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
   850  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   851  
   852  			if !params.OrTerms {
   853  				splitTerms := strings.Fields(terms)
   854  				for i, t := range strings.Fields(terms) {
   855  					splitTerms[i] = "+" + t
   856  				}
   857  
   858  				terms = strings.Join(splitTerms, " ")
   859  			}
   860  		}
   861  
   862  		queryParams["Terms"] = terms
   863  
   864  		list := model.NewPostList()
   865  
   866  		_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
   867  		if err != nil {
   868  			l4g.Warn(utils.T("store.sql_post.search.warn"), err.Error())
   869  			// 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.
   870  		} else {
   871  			for _, p := range posts {
   872  				if searchType == "Hashtags" {
   873  					exactMatch := false
   874  					for _, tag := range strings.Split(p.Hashtags, " ") {
   875  						if termMap[strings.ToUpper(tag)] {
   876  							exactMatch = true
   877  						}
   878  					}
   879  					if !exactMatch {
   880  						continue
   881  					}
   882  				}
   883  				list.AddPost(p)
   884  				list.AddOrder(p.Id)
   885  			}
   886  		}
   887  
   888  		list.MakeNonNil()
   889  
   890  		result.Data = list
   891  	})
   892  }
   893  
   894  func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) store.StoreChannel {
   895  	return store.Do(func(result *store.StoreResult) {
   896  		query :=
   897  			`SELECT DISTINCT
   898  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   899  			        COUNT(DISTINCT Posts.UserId) AS Value
   900  			FROM Posts`
   901  
   902  		if len(teamId) > 0 {
   903  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   904  		} else {
   905  			query += " WHERE"
   906  		}
   907  
   908  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   909  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   910  			ORDER BY Name DESC
   911  			LIMIT 30`
   912  
   913  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   914  			query =
   915  				`SELECT
   916  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
   917  				FROM Posts`
   918  
   919  			if len(teamId) > 0 {
   920  				query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   921  			} else {
   922  				query += " WHERE"
   923  			}
   924  
   925  			query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   926  				GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
   927  				ORDER BY Name DESC
   928  				LIMIT 30`
   929  		}
   930  
   931  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   932  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   933  
   934  		var rows model.AnalyticsRows
   935  		_, err := s.GetReplica().Select(
   936  			&rows,
   937  			query,
   938  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   939  		if err != nil {
   940  			result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
   941  		} else {
   942  			result.Data = rows
   943  		}
   944  	})
   945  }
   946  
   947  func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) store.StoreChannel {
   948  	return store.Do(func(result *store.StoreResult) {
   949  		query :=
   950  			`SELECT
   951  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   952  			        COUNT(Posts.Id) AS Value
   953  			    FROM Posts`
   954  
   955  		if len(teamId) > 0 {
   956  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   957  		} else {
   958  			query += " WHERE"
   959  		}
   960  
   961  		query += ` Posts.CreateAt <= :EndTime
   962  			            AND Posts.CreateAt >= :StartTime
   963  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   964  			ORDER BY Name DESC
   965  			LIMIT 30`
   966  
   967  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   968  			query =
   969  				`SELECT
   970  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
   971  				FROM Posts`
   972  
   973  			if len(teamId) > 0 {
   974  				query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
   975  			} else {
   976  				query += " WHERE"
   977  			}
   978  
   979  			query += ` Posts.CreateAt <= :EndTime
   980  				            AND Posts.CreateAt >= :StartTime
   981  				GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
   982  				ORDER BY Name DESC
   983  				LIMIT 30`
   984  		}
   985  
   986  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   987  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   988  
   989  		var rows model.AnalyticsRows
   990  		_, err := s.GetReplica().Select(
   991  			&rows,
   992  			query,
   993  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   994  		if err != nil {
   995  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
   996  		} else {
   997  			result.Data = rows
   998  		}
   999  	})
  1000  }
  1001  
  1002  func (s SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) store.StoreChannel {
  1003  	return store.Do(func(result *store.StoreResult) {
  1004  		query :=
  1005  			`SELECT
  1006  			    COUNT(Posts.Id) AS Value
  1007  			FROM
  1008  			    Posts,
  1009  			    Channels
  1010  			WHERE
  1011  			    Posts.ChannelId = Channels.Id`
  1012  
  1013  		if len(teamId) > 0 {
  1014  			query += " AND Channels.TeamId = :TeamId"
  1015  		}
  1016  
  1017  		if mustHaveFile {
  1018  			query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')"
  1019  		}
  1020  
  1021  		if mustHaveHashtag {
  1022  			query += " AND Posts.Hashtags != ''"
  1023  		}
  1024  
  1025  		if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
  1026  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1027  		} else {
  1028  			result.Data = v
  1029  		}
  1030  	})
  1031  }
  1032  
  1033  func (s SqlPostStore) GetPostsCreatedAt(channelId string, time int64) store.StoreChannel {
  1034  	return store.Do(func(result *store.StoreResult) {
  1035  		query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1036  
  1037  		var posts []*model.Post
  1038  		_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1039  
  1040  		if err != nil {
  1041  			result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
  1042  		} else {
  1043  			result.Data = posts
  1044  		}
  1045  	})
  1046  }
  1047  
  1048  func (s SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel {
  1049  	return store.Do(func(result *store.StoreResult) {
  1050  		keys := bytes.Buffer{}
  1051  		params := make(map[string]interface{})
  1052  		for i, postId := range postIds {
  1053  			if keys.Len() > 0 {
  1054  				keys.WriteString(",")
  1055  			}
  1056  
  1057  			key := "Post" + strconv.Itoa(i)
  1058  			keys.WriteString(":" + key)
  1059  			params[key] = postId
  1060  		}
  1061  
  1062  		query := `SELECT * FROM Posts WHERE Id in (` + keys.String() + `) and DeleteAt = 0 ORDER BY CreateAt DESC`
  1063  
  1064  		var posts []*model.Post
  1065  		_, err := s.GetReplica().Select(&posts, query, params)
  1066  
  1067  		if err != nil {
  1068  			l4g.Error(err)
  1069  			result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1070  		} else {
  1071  			result.Data = posts
  1072  		}
  1073  	})
  1074  }
  1075  
  1076  func (s SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) store.StoreChannel {
  1077  	return store.Do(func(result *store.StoreResult) {
  1078  		var posts []*model.PostForIndexing
  1079  		_, err1 := s.GetSearchReplica().Select(&posts,
  1080  			`SELECT
  1081  				PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1082  			FROM (
  1083  				SELECT
  1084  					*
  1085  				FROM
  1086  					Posts
  1087  				WHERE
  1088  					Posts.CreateAt >= :StartTime
  1089  				AND
  1090  					Posts.CreateAt < :EndTime
  1091  				ORDER BY
  1092  					CreateAt ASC
  1093  				LIMIT
  1094  					1000
  1095  				)
  1096  			AS
  1097  				PostsQuery
  1098  			LEFT JOIN
  1099  				Channels
  1100  			ON
  1101  				PostsQuery.ChannelId = Channels.Id
  1102  			LEFT JOIN
  1103  				Posts ParentPosts
  1104  			ON
  1105  				PostsQuery.RootId = ParentPosts.Id`,
  1106  			map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1107  
  1108  		if err1 != nil {
  1109  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err1.Error(), http.StatusInternalServerError)
  1110  		} else {
  1111  			result.Data = posts
  1112  		}
  1113  	})
  1114  }
  1115  
  1116  func (s SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
  1117  	return store.Do(func(result *store.StoreResult) {
  1118  		var query string
  1119  		if s.DriverName() == "postgres" {
  1120  			query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1121  		} else {
  1122  			query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1123  		}
  1124  
  1125  		sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1126  		if err != nil {
  1127  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1128  		} else {
  1129  			rowsAffected, err1 := sqlResult.RowsAffected()
  1130  			if err1 != nil {
  1131  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1132  				result.Data = int64(0)
  1133  			} else {
  1134  				result.Data = rowsAffected
  1135  			}
  1136  		}
  1137  	})
  1138  }
  1139  
  1140  func (s SqlPostStore) GetOldest() store.StoreChannel {
  1141  	return store.Do(func(result *store.StoreResult) {
  1142  		var post model.Post
  1143  		err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1144  		if err != nil {
  1145  			result.Err = model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1146  		}
  1147  
  1148  		result.Data = &post
  1149  	})
  1150  }