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