github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/store/sqlstore/post_store.go (about)

     1  // Copyright (c) 2015-present Xenia, 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  	"sync"
    13  
    14  	sq "github.com/Masterminds/squirrel"
    15  	"github.com/xzl8028/xenia-server/einterfaces"
    16  	"github.com/xzl8028/xenia-server/mlog"
    17  	"github.com/xzl8028/xenia-server/model"
    18  	"github.com/xzl8028/xenia-server/store"
    19  	"github.com/xzl8028/xenia-server/utils"
    20  )
    21  
    22  type SqlPostStore struct {
    23  	SqlStore
    24  	metrics           einterfaces.MetricsInterface
    25  	lastPostTimeCache *utils.Cache
    26  	lastPostsCache    *utils.Cache
    27  	maxPostSizeOnce   sync.Once
    28  	maxPostSizeCached int
    29  }
    30  
    31  const (
    32  	LAST_POST_TIME_CACHE_SIZE = 25000
    33  	LAST_POST_TIME_CACHE_SEC  = 900 // 15 minutes
    34  
    35  	LAST_POSTS_CACHE_SIZE = 1000
    36  	LAST_POSTS_CACHE_SEC  = 900 // 15 minutes
    37  )
    38  
    39  func (s *SqlPostStore) ClearCaches() {
    40  	s.lastPostTimeCache.Purge()
    41  	s.lastPostsCache.Purge()
    42  
    43  	if s.metrics != nil {
    44  		s.metrics.IncrementMemCacheInvalidationCounter("Last Post Time - Purge")
    45  		s.metrics.IncrementMemCacheInvalidationCounter("Last Posts Cache - Purge")
    46  	}
    47  }
    48  
    49  func NewSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
    50  	s := &SqlPostStore{
    51  		SqlStore:          sqlStore,
    52  		metrics:           metrics,
    53  		lastPostTimeCache: utils.NewLru(LAST_POST_TIME_CACHE_SIZE),
    54  		lastPostsCache:    utils.NewLru(LAST_POSTS_CACHE_SIZE),
    55  		maxPostSizeCached: model.POST_MESSAGE_MAX_RUNES_V1,
    56  	}
    57  
    58  	for _, db := range sqlStore.GetAllConns() {
    59  		table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
    60  		table.ColMap("Id").SetMaxSize(26)
    61  		table.ColMap("UserId").SetMaxSize(26)
    62  		table.ColMap("ChannelId").SetMaxSize(26)
    63  		table.ColMap("RootId").SetMaxSize(26)
    64  		table.ColMap("ParentId").SetMaxSize(26)
    65  		table.ColMap("OriginalId").SetMaxSize(26)
    66  		table.ColMap("Message").SetMaxSize(model.POST_MESSAGE_MAX_BYTES_V2)
    67  		table.ColMap("Type").SetMaxSize(26)
    68  		table.ColMap("Hashtags").SetMaxSize(1000)
    69  		table.ColMap("Props").SetMaxSize(8000)
    70  		table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES)
    71  		table.ColMap("FileIds").SetMaxSize(150)
    72  	}
    73  
    74  	return s
    75  }
    76  
    77  func (s *SqlPostStore) CreateIndexesIfNotExists() {
    78  	s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt")
    79  	s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
    80  	s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt")
    81  	s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
    82  	s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
    83  	s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
    84  	s.CreateIndexIfNotExists("idx_posts_is_pinned", "Posts", "IsPinned")
    85  
    86  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_update_at", "Posts", []string{"ChannelId", "UpdateAt"})
    87  	s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_delete_at_create_at", "Posts", []string{"ChannelId", "DeleteAt", "CreateAt"})
    88  
    89  	s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
    90  	s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
    91  }
    92  
    93  func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
    94  	if len(post.Id) > 0 {
    95  		return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
    96  	}
    97  
    98  	maxPostSize := s.GetMaxPostSize()
    99  
   100  	post.PreSave()
   101  	if err := post.IsValid(maxPostSize); err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	if err := s.GetMaster().Insert(post); err != nil {
   106  		return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
   107  	}
   108  
   109  	time := post.UpdateAt
   110  
   111  	if post.Type != model.POST_JOIN_LEAVE && post.Type != model.POST_ADD_REMOVE &&
   112  		post.Type != model.POST_JOIN_CHANNEL && post.Type != model.POST_LEAVE_CHANNEL &&
   113  		post.Type != model.POST_JOIN_TEAM && post.Type != model.POST_LEAVE_TEAM &&
   114  		post.Type != model.POST_ADD_TO_CHANNEL && post.Type != model.POST_REMOVE_FROM_CHANNEL &&
   115  		post.Type != model.POST_ADD_TO_TEAM && post.Type != model.POST_REMOVE_FROM_TEAM {
   116  		if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + 1 WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}); err != nil {
   117  			mlog.Error(fmt.Sprintf("Error updating Channel LastPostAt: %v", err.Error()))
   118  		}
   119  	} else {
   120  		// don't update TotalMsgCount for unimportant messages so that the channel isn't marked as unread
   121  		if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}); err != nil {
   122  			mlog.Error(fmt.Sprintf("Error updating Channel LastPostAt: %v", err.Error()))
   123  		}
   124  	}
   125  
   126  	if len(post.RootId) > 0 {
   127  		if _, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": post.RootId}); err != nil {
   128  			mlog.Error(fmt.Sprintf("Error updating Post UpdateAt: %v", err.Error()))
   129  		}
   130  	}
   131  
   132  	return post, nil
   133  }
   134  
   135  func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, *model.AppError) {
   136  	newPost.UpdateAt = model.GetMillis()
   137  	newPost.PreCommit()
   138  
   139  	oldPost.DeleteAt = newPost.UpdateAt
   140  	oldPost.UpdateAt = newPost.UpdateAt
   141  	oldPost.OriginalId = oldPost.Id
   142  	oldPost.Id = model.NewId()
   143  	oldPost.PreCommit()
   144  
   145  	maxPostSize := s.GetMaxPostSize()
   146  
   147  	if err := newPost.IsValid(maxPostSize); err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	if _, err := s.GetMaster().Update(newPost); err != nil {
   152  		return nil, model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError)
   153  	}
   154  
   155  	time := model.GetMillis()
   156  	s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt  WHERE Id = :ChannelId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ChannelId": newPost.ChannelId})
   157  
   158  	if len(newPost.RootId) > 0 {
   159  		s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId AND UpdateAt < :UpdateAt", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId})
   160  	}
   161  
   162  	// mark the old post as deleted
   163  	s.GetMaster().Insert(oldPost)
   164  
   165  	return newPost, nil
   166  }
   167  
   168  func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) {
   169  	post.UpdateAt = model.GetMillis()
   170  
   171  	maxPostSize := s.GetMaxPostSize()
   172  	if appErr := post.IsValid(maxPostSize); appErr != nil {
   173  		return nil, appErr
   174  	}
   175  
   176  	if _, err := s.GetMaster().Update(post); err != nil {
   177  		return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
   178  	}
   179  
   180  	return post, nil
   181  }
   182  
   183  func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
   184  	pl := model.NewPostList()
   185  
   186  	var posts []*model.Post
   187  	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 {
   188  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   189  	}
   190  
   191  	for _, post := range posts {
   192  		pl.AddPost(post)
   193  		pl.AddOrder(post.Id)
   194  	}
   195  
   196  	return pl, nil
   197  }
   198  
   199  func (s *SqlPostStore) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) {
   200  	pl := model.NewPostList()
   201  
   202  	var posts []*model.Post
   203  
   204  	query := `
   205              SELECT
   206                  A.*
   207              FROM
   208                  (SELECT
   209                      *
   210                  FROM
   211                      Posts
   212                  WHERE
   213                      Id
   214                  IN
   215                      (SELECT
   216                          Name
   217                      FROM
   218                          Preferences
   219                      WHERE
   220                          UserId = :UserId
   221                          AND Category = :Category)
   222                          AND DeleteAt = 0
   223                  ) as A
   224              INNER JOIN Channels as B
   225                  ON B.Id = A.ChannelId
   226              WHERE B.TeamId = :TeamId OR B.TeamId = ''
   227              ORDER BY CreateAt DESC
   228              LIMIT :Limit OFFSET :Offset`
   229  
   230  	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 {
   231  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForTeam", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   232  	}
   233  
   234  	for _, post := range posts {
   235  		pl.AddPost(post)
   236  		pl.AddOrder(post.Id)
   237  	}
   238  
   239  	return pl, nil
   240  }
   241  
   242  func (s *SqlPostStore) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
   243  	pl := model.NewPostList()
   244  
   245  	var posts []*model.Post
   246  	query := `
   247  		SELECT
   248  			*
   249  		FROM Posts
   250  		WHERE
   251  			Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category)
   252  			AND ChannelId = :ChannelId
   253  			AND DeleteAt = 0
   254  		ORDER BY CreateAt DESC
   255  		LIMIT :Limit OFFSET :Offset`
   256  
   257  	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 {
   258  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForChannel", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   259  	}
   260  	for _, post := range posts {
   261  		pl.AddPost(post)
   262  		pl.AddOrder(post.Id)
   263  	}
   264  
   265  	return pl, nil
   266  }
   267  
   268  func (s *SqlPostStore) Get(id string) (*model.PostList, *model.AppError) {
   269  	pl := model.NewPostList()
   270  
   271  	if len(id) == 0 {
   272  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest)
   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  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   279  	}
   280  
   281  	pl.AddPost(&post)
   282  	pl.AddOrder(id)
   283  
   284  	rootId := post.RootId
   285  
   286  	if rootId == "" {
   287  		rootId = post.Id
   288  	}
   289  
   290  	if len(rootId) == 0 {
   291  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError)
   292  	}
   293  
   294  	var posts []*model.Post
   295  	_, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId})
   296  	if err != nil {
   297  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError)
   298  	}
   299  
   300  	for _, p := range posts {
   301  		pl.AddPost(p)
   302  		pl.AddOrder(p.Id)
   303  	}
   304  
   305  	return pl, nil
   306  }
   307  
   308  func (s *SqlPostStore) GetSingle(id string) (*model.Post, *model.AppError) {
   309  	var post model.Post
   310  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
   311  	if err != nil {
   312  		return nil, model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   313  	}
   314  	return &post, nil
   315  }
   316  
   317  
   318  func (s *SqlPostStore) SelectByMessage(message string) (*model.Post, *model.AppError) {
   319  	var post model.Post
   320  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Message = :Message AND DeleteAt = 0", map[string]interface{}{"Message":message})
   321  	if err != nil {
   322  		return nil, model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "message="+message+err.Error(), http.StatusNotFound)
   323  	}
   324  	return &post, nil
   325  }
   326  
   327  
   328  type etagPosts struct {
   329  	Id       string
   330  	UpdateAt int64
   331  }
   332  
   333  func (s *SqlPostStore) InvalidateLastPostTimeCache(channelId string) {
   334  	s.lastPostTimeCache.Remove(channelId)
   335  
   336  	// Keys are "{channelid}{limit}" and caching only occurs on limits of 30 and 60
   337  	s.lastPostsCache.Remove(channelId + "30")
   338  	s.lastPostsCache.Remove(channelId + "60")
   339  
   340  	if s.metrics != nil {
   341  		s.metrics.IncrementMemCacheInvalidationCounter("Last Post Time - Remove by Channel Id")
   342  		s.metrics.IncrementMemCacheInvalidationCounter("Last Posts Cache - Remove by Channel Id")
   343  	}
   344  }
   345  
   346  func (s *SqlPostStore) GetEtag(channelId string, allowFromCache bool) string {
   347  	if allowFromCache {
   348  		if cacheItem, ok := s.lastPostTimeCache.Get(channelId); ok {
   349  			if s.metrics != nil {
   350  				s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   351  			}
   352  			return fmt.Sprintf("%v.%v", model.CurrentVersion, cacheItem.(int64))
   353  		} else {
   354  			if s.metrics != nil {
   355  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   356  			}
   357  		}
   358  	} else {
   359  		if s.metrics != nil {
   360  			s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   361  		}
   362  	}
   363  
   364  	var et etagPosts
   365  	err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId})
   366  	var result string
   367  	if err != nil {
   368  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   369  	} else {
   370  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   371  	}
   372  
   373  	s.lastPostTimeCache.AddWithExpiresInSecs(channelId, et.UpdateAt, LAST_POST_TIME_CACHE_SEC)
   374  	return result
   375  }
   376  
   377  func (s *SqlPostStore) Delete(postId string, time int64, deleteByID string) *model.AppError {
   378  
   379  	appErr := func(errMsg string) *model.AppError {
   380  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+errMsg, http.StatusInternalServerError)
   381  	}
   382  
   383  	var post model.Post
   384  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId})
   385  	if err != nil {
   386  		return appErr(err.Error())
   387  	}
   388  
   389  	post.Props[model.POST_PROPS_DELETE_BY] = deleteByID
   390  
   391  	_, err = s.GetMaster().Exec("UPDATE Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt, Props = :Props WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "RootId": postId, "Props": model.StringInterfaceToJson(post.Props)})
   392  	if err != nil {
   393  		return appErr(err.Error())
   394  	}
   395  
   396  	return nil
   397  }
   398  
   399  func (s *SqlPostStore) permanentDelete(postId string) *model.AppError {
   400  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   401  	if err != nil {
   402  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   403  	}
   404  	return nil
   405  }
   406  
   407  func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) *model.AppError {
   408  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   409  	if err != nil {
   410  		return model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   411  	}
   412  	return nil
   413  }
   414  
   415  func (s *SqlPostStore) PermanentDeleteByUser(userId string) *model.AppError {
   416  	// First attempt to delete all the comments for a user
   417  	if err := s.permanentDeleteAllCommentByUser(userId); err != nil {
   418  		return err
   419  	}
   420  
   421  	// Now attempt to delete all the root posts for a user. This will also
   422  	// delete all the comments for each post
   423  	found := true
   424  	count := 0
   425  
   426  	for found {
   427  		var ids []string
   428  		_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   429  		if err != nil {
   430  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   431  		}
   432  
   433  		found = false
   434  		for _, id := range ids {
   435  			found = true
   436  			if err := s.permanentDelete(id); err != nil {
   437  				return err
   438  			}
   439  		}
   440  
   441  		// This is a fail safe, give up if more than 10k messages
   442  		count++
   443  		if count >= 10 {
   444  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   445  		}
   446  	}
   447  
   448  	return nil
   449  }
   450  
   451  func (s *SqlPostStore) PermanentDeleteByChannel(channelId string) *model.AppError {
   452  	if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   453  		return model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   454  	}
   455  	return nil
   456  }
   457  
   458  func (s *SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) (*model.PostList, *model.AppError) {
   459  	if limit > 1000 {
   460  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest)
   461  	}
   462  
   463  	// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   464  	if allowFromCache && offset == 0 && (limit == 60 || limit == 30) {
   465  		if cacheItem, ok := s.lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok {
   466  			if s.metrics != nil {
   467  				s.metrics.IncrementMemCacheHitCounter("Last Posts Cache")
   468  			}
   469  			return cacheItem.(*model.PostList), nil
   470  		}
   471  	}
   472  
   473  	if s.metrics != nil {
   474  		s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   475  	}
   476  
   477  	rpc := s.getRootPosts(channelId, offset, limit)
   478  	cpc := s.getParentsPosts(channelId, offset, limit)
   479  
   480  	var err *model.AppError
   481  	list := model.NewPostList()
   482  
   483  	rpr := <-rpc
   484  	if rpr.Err != nil {
   485  		return nil, rpr.Err
   486  	}
   487  
   488  	cpr := <-cpc
   489  	if cpr.Err != nil {
   490  		return nil, cpr.Err
   491  	}
   492  
   493  	posts := rpr.Data.([]*model.Post)
   494  	parents := cpr.Data.([]*model.Post)
   495  
   496  	for _, p := range posts {
   497  		list.AddPost(p)
   498  		list.AddOrder(p.Id)
   499  	}
   500  
   501  	for _, p := range parents {
   502  		list.AddPost(p)
   503  	}
   504  
   505  	list.MakeNonNil()
   506  
   507  	// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   508  	if offset == 0 && (limit == 60 || limit == 30) {
   509  		s.lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC)
   510  	}
   511  
   512  	return list, err
   513  }
   514  
   515  func (s *SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) (*model.PostList, *model.AppError) {
   516  	if allowFromCache {
   517  		// If the last post in the channel's time is less than or equal to the time we are getting posts since,
   518  		// we can safely return no posts.
   519  		if cacheItem, ok := s.lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time {
   520  			if s.metrics != nil {
   521  				s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   522  			}
   523  			list := model.NewPostList()
   524  			return list, nil
   525  		}
   526  	}
   527  
   528  	if s.metrics != nil {
   529  		s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   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  		return nil, model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   563  	}
   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  	s.lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC)
   580  
   581  	return list, nil
   582  }
   583  
   584  func (s *SqlPostStore) GetPostsBefore(channelId string, postId string, limit int, offset int) (*model.PostList, *model.AppError) {
   585  	return s.getPostsAround(channelId, postId, limit, offset, true)
   586  }
   587  
   588  func (s *SqlPostStore) GetPostsAfter(channelId string, postId string, limit int, offset int) (*model.PostList, *model.AppError) {
   589  	return s.getPostsAround(channelId, postId, limit, offset, false)
   590  }
   591  
   592  func (s *SqlPostStore) getPostsAround(channelId string, postId string, limit int, offset int, before bool) (*model.PostList, *model.AppError) {
   593  	var direction string
   594  	var sort string
   595  	if before {
   596  		direction = "<"
   597  		sort = "DESC"
   598  	} else {
   599  		direction = ">"
   600  		sort = "ASC"
   601  	}
   602  
   603  	var posts []*model.Post
   604  	var parents []*model.Post
   605  	_, err := s.GetReplica().Select(&posts,
   606  		`SELECT
   607  			*
   608  		FROM
   609  			Posts
   610  		WHERE
   611  			CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   612  				AND ChannelId = :ChannelId
   613  				AND DeleteAt = 0
   614  		ORDER BY CreateAt `+sort+`
   615  		LIMIT :Limit
   616  		OFFSET :Offset`,
   617  		map[string]interface{}{"ChannelId": channelId, "PostId": postId, "Limit": limit, "Offset": offset})
   618  	if err != nil {
   619  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   620  	}
   621  	_, err = s.GetReplica().Select(&parents,
   622  		`SELECT
   623  			q2.*
   624  		FROM
   625  			Posts q2
   626  				INNER JOIN
   627  			(SELECT DISTINCT
   628  				q3.Id, q3.RootId
   629  			FROM
   630  				(SELECT
   631  					Id, RootId
   632  				FROM
   633  					Posts
   634  				WHERE
   635  					CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   636  					AND ChannelId = :ChannelId
   637  					AND DeleteAt = 0
   638  				ORDER BY CreateAt `+sort+`
   639  				LIMIT :Limit OFFSET :Offset) q3 -- q3 contains the Id and RootId of every post in posts
   640  			) q1 -- q1 is q3 with the duplicates removed
   641  			ON q1.RootId = q2.Id -- This is the root post of a thread that appears in posts
   642  				OR q1.Id = q2.RootId -- This is a comment on a post in posts
   643  				OR (q2.RootId != '' AND q1.RootId = q2.RootId) -- This is a comment on a thread that appears in posts
   644  		WHERE
   645  			ChannelId = :ChannelId
   646  			AND DeleteAt = 0
   647  		ORDER BY CreateAt DESC`,
   648  		map[string]interface{}{"ChannelId": channelId, "PostId": postId, "Limit": limit, "Offset": offset})
   649  
   650  	if err != nil {
   651  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   652  	}
   653  	list := model.NewPostList()
   654  
   655  	// We need to flip the order if we selected backwards
   656  	if before {
   657  		for _, p := range posts {
   658  			list.AddPost(p)
   659  			list.AddOrder(p.Id)
   660  		}
   661  	} else {
   662  		l := len(posts)
   663  		for i := range posts {
   664  			list.AddPost(posts[l-i-1])
   665  			list.AddOrder(posts[l-i-1].Id)
   666  		}
   667  	}
   668  
   669  	for _, p := range parents {
   670  		list.AddPost(p)
   671  	}
   672  	return list, nil
   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  			    q2.*
   693  			FROM
   694  			    Posts q2
   695  			        INNER JOIN
   696  			    (SELECT DISTINCT
   697  			        q3.RootId
   698  			    FROM
   699  			        (SELECT
   700  						RootId
   701  					FROM
   702  						Posts
   703  					WHERE
   704  						ChannelId = :ChannelId1
   705  							AND DeleteAt = 0
   706  					ORDER BY CreateAt DESC
   707  					LIMIT :Limit OFFSET :Offset) q3
   708  			    WHERE q3.RootId != '') q1
   709  			    ON q1.RootId = q2.Id OR q1.RootId = q2.RootId
   710  			WHERE
   711  			    ChannelId = :ChannelId2
   712  			        AND DeleteAt = 0
   713  			ORDER BY CreateAt`,
   714  			map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
   715  		if err != nil {
   716  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   717  		} else {
   718  			result.Data = posts
   719  		}
   720  	})
   721  }
   722  
   723  var specialSearchChar = []string{
   724  	"<",
   725  	">",
   726  	"+",
   727  	"-",
   728  	"(",
   729  	")",
   730  	"~",
   731  	"@",
   732  	":",
   733  }
   734  
   735  func (s *SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel {
   736  	return store.Do(func(result *store.StoreResult) {
   737  		queryParams := map[string]interface{}{
   738  			"TeamId": teamId,
   739  			"UserId": userId,
   740  		}
   741  
   742  		termMap := map[string]bool{}
   743  		terms := params.Terms
   744  
   745  		if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 && len(params.OnDate) == 0 && len(params.AfterDate) == 0 && len(params.BeforeDate) == 0 {
   746  			result.Data = []*model.Post{}
   747  			return
   748  		}
   749  
   750  		searchType := "Message"
   751  		if params.IsHashtag {
   752  			searchType = "Hashtags"
   753  			for _, term := range strings.Split(terms, " ") {
   754  				termMap[strings.ToUpper(term)] = true
   755  			}
   756  		}
   757  
   758  		// these chars have special meaning and can be treated as spaces
   759  		for _, c := range specialSearchChar {
   760  			terms = strings.Replace(terms, c, " ", -1)
   761  		}
   762  
   763  		var posts []*model.Post
   764  
   765  		deletedQueryPart := "AND DeleteAt = 0"
   766  		if params.IncludeDeletedChannels {
   767  			deletedQueryPart = ""
   768  		}
   769  
   770  		userIdPart := "AND UserId = :UserId"
   771  		if params.SearchWithoutUserId {
   772  			userIdPart = ""
   773  		}
   774  
   775  		searchQuery := `
   776  			SELECT
   777  				*
   778  			FROM
   779  				Posts
   780  			WHERE
   781  				DeleteAt = 0
   782  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
   783  				POST_FILTER
   784  				AND ChannelId IN (
   785  					SELECT
   786  						Id
   787  					FROM
   788  						Channels,
   789  						ChannelMembers
   790  					WHERE
   791  						Id = ChannelId
   792  							AND (TeamId = :TeamId OR TeamId = '')
   793  							` + userIdPart + `
   794  							` + deletedQueryPart + `
   795  							CHANNEL_FILTER)
   796  				CREATEDATE_CLAUSE
   797  				SEARCH_CLAUSE
   798  				ORDER BY CreateAt DESC
   799  			LIMIT 100`
   800  
   801  		if len(params.InChannels) > 1 {
   802  			inClause := ":InChannel0"
   803  			queryParams["InChannel0"] = params.InChannels[0]
   804  
   805  			for i := 1; i < len(params.InChannels); i++ {
   806  				paramName := "InChannel" + strconv.FormatInt(int64(i), 10)
   807  				inClause += ", :" + paramName
   808  				queryParams[paramName] = params.InChannels[i]
   809  			}
   810  
   811  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1)
   812  		} else if len(params.InChannels) == 1 {
   813  			queryParams["InChannel"] = params.InChannels[0]
   814  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1)
   815  		} else {
   816  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1)
   817  		}
   818  
   819  		if len(params.FromUsers) > 1 {
   820  			inClause := ":FromUser0"
   821  			queryParams["FromUser0"] = params.FromUsers[0]
   822  
   823  			for i := 1; i < len(params.FromUsers); i++ {
   824  				paramName := "FromUser" + strconv.FormatInt(int64(i), 10)
   825  				inClause += ", :" + paramName
   826  				queryParams[paramName] = params.FromUsers[i]
   827  			}
   828  
   829  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   830  				AND UserId IN (
   831  					SELECT
   832  						Id
   833  					FROM
   834  						Users,
   835  						TeamMembers
   836  					WHERE
   837  						TeamMembers.TeamId = :TeamId
   838  						AND Users.Id = TeamMembers.UserId
   839  						AND Username IN (`+inClause+`))`, 1)
   840  		} else if len(params.FromUsers) == 1 {
   841  			queryParams["FromUser"] = params.FromUsers[0]
   842  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   843  				AND UserId IN (
   844  					SELECT
   845  						Id
   846  					FROM
   847  						Users,
   848  						TeamMembers
   849  					WHERE
   850  						TeamMembers.TeamId = :TeamId
   851  						AND Users.Id = TeamMembers.UserId
   852  						AND Username = :FromUser)`, 1)
   853  		} else {
   854  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
   855  		}
   856  
   857  		// handle after: before: on: filters
   858  		if len(params.AfterDate) > 1 || len(params.BeforeDate) > 1 || len(params.OnDate) > 1 {
   859  			if len(params.OnDate) > 1 {
   860  				onDateStart, onDateEnd := params.GetOnDateMillis()
   861  				queryParams["OnDateStart"] = strconv.FormatInt(onDateStart, 10)
   862  				queryParams["OnDateEnd"] = strconv.FormatInt(onDateEnd, 10)
   863  
   864  				// between `on date` start of day and end of day
   865  				searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd ", 1)
   866  			} else if len(params.AfterDate) > 1 && len(params.BeforeDate) > 1 {
   867  				afterDate := params.GetAfterDateMillis()
   868  				beforeDate := params.GetBeforeDateMillis()
   869  				queryParams["OnDateStart"] = strconv.FormatInt(afterDate, 10)
   870  				queryParams["OnDateEnd"] = strconv.FormatInt(beforeDate, 10)
   871  
   872  				// between clause
   873  				searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd ", 1)
   874  			} else if len(params.AfterDate) > 1 {
   875  				afterDate := params.GetAfterDateMillis()
   876  				queryParams["AfterDate"] = strconv.FormatInt(afterDate, 10)
   877  
   878  				// greater than `after date`
   879  				searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", "AND CreateAt >= :AfterDate ", 1)
   880  			} else if len(params.BeforeDate) > 1 {
   881  				beforeDate := params.GetBeforeDateMillis()
   882  				queryParams["BeforeDate"] = strconv.FormatInt(beforeDate, 10)
   883  
   884  				// less than `before date`
   885  				searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", "AND CreateAt <= :BeforeDate ", 1)
   886  			}
   887  		} else {
   888  			// no create date filters set
   889  			searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", "", 1)
   890  		}
   891  
   892  		if terms == "" {
   893  			// we've already confirmed that we have a channel or user to search for
   894  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
   895  		} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   896  			// Parse text for wildcards
   897  			if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
   898  				terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
   899  			}
   900  
   901  			if params.OrTerms {
   902  				terms = strings.Join(strings.Fields(terms), " | ")
   903  			} else {
   904  				terms = strings.Join(strings.Fields(terms), " & ")
   905  			}
   906  
   907  			searchClause := fmt.Sprintf("AND to_tsvector('english', %s) @@  to_tsquery(:Terms)", searchType)
   908  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   909  		} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   910  			searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
   911  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   912  
   913  			if !params.OrTerms {
   914  				splitTerms := strings.Fields(terms)
   915  				for i, t := range strings.Fields(terms) {
   916  					splitTerms[i] = "+" + t
   917  				}
   918  
   919  				terms = strings.Join(splitTerms, " ")
   920  			}
   921  		}
   922  
   923  		queryParams["Terms"] = terms
   924  
   925  		list := model.NewPostList()
   926  
   927  		_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
   928  		if err != nil {
   929  			mlog.Warn(fmt.Sprintf("Query error searching posts: %v", err.Error()))
   930  			// 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.
   931  		} else {
   932  			for _, p := range posts {
   933  				if searchType == "Hashtags" {
   934  					exactMatch := false
   935  					for _, tag := range strings.Split(p.Hashtags, " ") {
   936  						if termMap[strings.ToUpper(tag)] {
   937  							exactMatch = true
   938  						}
   939  					}
   940  					if !exactMatch {
   941  						continue
   942  					}
   943  				}
   944  				list.AddPost(p)
   945  				list.AddOrder(p.Id)
   946  			}
   947  		}
   948  
   949  		list.MakeNonNil()
   950  
   951  		result.Data = list
   952  	})
   953  }
   954  
   955  func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) (model.AnalyticsRows, *model.AppError) {
   956  	query :=
   957  		`SELECT DISTINCT
   958  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   959  		        COUNT(DISTINCT Posts.UserId) AS Value
   960  		FROM Posts`
   961  
   962  	if len(teamId) > 0 {
   963  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   964  	} else {
   965  		query += " WHERE"
   966  	}
   967  
   968  	query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   969  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   970  		ORDER BY Name DESC
   971  		LIMIT 30`
   972  
   973  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   974  		query =
   975  			`SELECT
   976  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
   977  			FROM Posts`
   978  
   979  		if len(teamId) > 0 {
   980  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   981  		} else {
   982  			query += " WHERE"
   983  		}
   984  
   985  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   986  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
   987  			ORDER BY Name DESC
   988  			LIMIT 30`
   989  	}
   990  
   991  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   992  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   993  
   994  	var rows model.AnalyticsRows
   995  	_, err := s.GetReplica().Select(
   996  		&rows,
   997  		query,
   998  		map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   999  	if err != nil {
  1000  		return nil, model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1001  	}
  1002  	return rows, nil
  1003  }
  1004  
  1005  func (s *SqlPostStore) AnalyticsPostCountsByDay(teamId string) (model.AnalyticsRows, *model.AppError) {
  1006  	query :=
  1007  		`SELECT
  1008  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1009  		        COUNT(Posts.Id) AS Value
  1010  		    FROM Posts`
  1011  
  1012  	if len(teamId) > 0 {
  1013  		query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1014  	} else {
  1015  		query += " WHERE"
  1016  	}
  1017  
  1018  	query += ` Posts.CreateAt <= :EndTime
  1019  		            AND Posts.CreateAt >= :StartTime
  1020  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1021  		ORDER BY Name DESC
  1022  		LIMIT 30`
  1023  
  1024  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1025  		query =
  1026  			`SELECT
  1027  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
  1028  			FROM Posts`
  1029  
  1030  		if len(teamId) > 0 {
  1031  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
  1032  		} else {
  1033  			query += " WHERE"
  1034  		}
  1035  
  1036  		query += ` Posts.CreateAt <= :EndTime
  1037  			            AND Posts.CreateAt >= :StartTime
  1038  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1039  			ORDER BY Name DESC
  1040  			LIMIT 30`
  1041  	}
  1042  
  1043  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1044  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1045  
  1046  	var rows model.AnalyticsRows
  1047  	_, err := s.GetReplica().Select(
  1048  		&rows,
  1049  		query,
  1050  		map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
  1051  	if err != nil {
  1052  		return nil, model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1053  	}
  1054  	return rows, nil
  1055  }
  1056  
  1057  func (s *SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) (int64, *model.AppError) {
  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  	v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
  1080  	if err != nil {
  1081  		return 0, model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1082  	}
  1083  
  1084  	return v, nil
  1085  }
  1086  
  1087  func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) ([]*model.Post, *model.AppError) {
  1088  	query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1089  
  1090  	var posts []*model.Post
  1091  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1092  
  1093  	if err != nil {
  1094  		return nil, model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
  1095  	}
  1096  	return posts, nil
  1097  }
  1098  
  1099  func (s *SqlPostStore) GetPostsByIds(postIds []string) ([]*model.Post, *model.AppError) {
  1100  	keys, params := MapStringsToQueryParams(postIds, "Post")
  1101  
  1102  	query := `SELECT * FROM Posts WHERE Id IN ` + keys + ` ORDER BY CreateAt DESC`
  1103  
  1104  	var posts []*model.Post
  1105  	_, err := s.GetReplica().Select(&posts, query, params)
  1106  
  1107  	if err != nil {
  1108  		mlog.Error(fmt.Sprint(err))
  1109  		return nil, model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1110  	}
  1111  	return posts, nil
  1112  }
  1113  
  1114  func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, *model.AppError) {
  1115  	var posts []*model.PostForIndexing
  1116  	_, err := s.GetSearchReplica().Select(&posts,
  1117  		`SELECT
  1118  			PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1119  		FROM (
  1120  			SELECT
  1121  				*
  1122  			FROM
  1123  				Posts
  1124  			WHERE
  1125  				Posts.CreateAt >= :StartTime
  1126  			AND
  1127  				Posts.CreateAt < :EndTime
  1128  			ORDER BY
  1129  				CreateAt ASC
  1130  			LIMIT
  1131  				1000
  1132  			)
  1133  		AS
  1134  			PostsQuery
  1135  		LEFT JOIN
  1136  			Channels
  1137  		ON
  1138  			PostsQuery.ChannelId = Channels.Id
  1139  		LEFT JOIN
  1140  			Posts ParentPosts
  1141  		ON
  1142  			PostsQuery.RootId = ParentPosts.Id`,
  1143  		map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1144  
  1145  	if err != nil {
  1146  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err.Error(), http.StatusInternalServerError)
  1147  	}
  1148  	return posts, nil
  1149  }
  1150  
  1151  func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) {
  1152  	var query string
  1153  	if s.DriverName() == "postgres" {
  1154  		query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1155  	} else {
  1156  		query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1157  	}
  1158  
  1159  	sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1160  	if err != nil {
  1161  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1162  	}
  1163  
  1164  	rowsAffected, err := sqlResult.RowsAffected()
  1165  	if err != nil {
  1166  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1167  	}
  1168  	return rowsAffected, nil
  1169  }
  1170  
  1171  func (s *SqlPostStore) GetOldest() (*model.Post, *model.AppError) {
  1172  	var post model.Post
  1173  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1174  	if err != nil {
  1175  		return nil, model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1176  	}
  1177  
  1178  	return &post, nil
  1179  }
  1180  
  1181  func (s *SqlPostStore) determineMaxPostSize() int {
  1182  	var maxPostSize int = model.POST_MESSAGE_MAX_RUNES_V1
  1183  	var maxPostSizeBytes int32
  1184  
  1185  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1186  		// The Post.Message column in Postgres has historically been VARCHAR(4000), but
  1187  		// may be manually enlarged to support longer posts.
  1188  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1189  			SELECT
  1190  				COALESCE(character_maximum_length, 0)
  1191  			FROM
  1192  				information_schema.columns
  1193  			WHERE
  1194  				table_name = 'posts'
  1195  			AND	column_name = 'message'
  1196  		`); err != nil {
  1197  			mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error())
  1198  		}
  1199  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1200  		// The Post.Message column in MySQL has historically been TEXT, with a maximum
  1201  		// limit of 65535.
  1202  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1203  			SELECT
  1204  				COALESCE(CHARACTER_MAXIMUM_LENGTH, 0)
  1205  			FROM
  1206  				INFORMATION_SCHEMA.COLUMNS
  1207  			WHERE
  1208  				table_schema = DATABASE()
  1209  			AND	table_name = 'Posts'
  1210  			AND	column_name = 'Message'
  1211  			LIMIT 0, 1
  1212  		`); err != nil {
  1213  			mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error())
  1214  		}
  1215  	} else {
  1216  		mlog.Warn("No implementation found to determine the maximum supported post size")
  1217  	}
  1218  
  1219  	// Assume a worst-case representation of four bytes per rune.
  1220  	maxPostSize = int(maxPostSizeBytes) / 4
  1221  
  1222  	// To maintain backwards compatibility, don't yield a maximum post
  1223  	// size smaller than the previous limit, even though it wasn't
  1224  	// actually possible to store 4000 runes in all cases.
  1225  	if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 {
  1226  		maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1
  1227  	}
  1228  
  1229  	mlog.Info(fmt.Sprintf("Post.Message supports at most %d characters (%d bytes)", maxPostSize, maxPostSizeBytes))
  1230  
  1231  	return maxPostSize
  1232  }
  1233  
  1234  // GetMaxPostSize returns the maximum number of runes that may be stored in a post.
  1235  func (s *SqlPostStore) GetMaxPostSize() int {
  1236  	s.maxPostSizeOnce.Do(func() {
  1237  		s.maxPostSizeCached = s.determineMaxPostSize()
  1238  	})
  1239  	return s.maxPostSizeCached
  1240  }
  1241  
  1242  func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) ([]*model.PostForExport, *model.AppError) {
  1243  	var posts []*model.PostForExport
  1244  	_, err := s.GetSearchReplica().Select(&posts, `
  1245                  SELECT
  1246                      p1.*,
  1247                      Users.Username as Username,
  1248                      Teams.Name as TeamName,
  1249                      Channels.Name as ChannelName
  1250                  FROM
  1251                      Posts p1
  1252                  INNER JOIN
  1253                      Channels ON p1.ChannelId = Channels.Id
  1254                  INNER JOIN
  1255                      Teams ON Channels.TeamId = Teams.Id
  1256                  INNER JOIN
  1257                      Users ON p1.UserId = Users.Id
  1258                  WHERE
  1259                      p1.Id > :AfterId
  1260                      AND p1.ParentId = ''
  1261                      AND p1.DeleteAt = 0
  1262  					AND Channels.DeleteAt = 0
  1263  					AND Teams.DeleteAt = 0
  1264                  ORDER BY
  1265                      p1.Id
  1266                  LIMIT
  1267                      :Limit`,
  1268  		map[string]interface{}{"Limit": limit, "AfterId": afterId})
  1269  
  1270  	if err != nil {
  1271  		return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
  1272  			nil, err.Error(), http.StatusInternalServerError)
  1273  	}
  1274  	return posts, nil
  1275  }
  1276  
  1277  func (s *SqlPostStore) GetRepliesForExport(parentId string) ([]*model.ReplyForExport, *model.AppError) {
  1278  
  1279  	var posts []*model.ReplyForExport
  1280  	_, err := s.GetSearchReplica().Select(&posts, `
  1281  			SELECT
  1282  				Posts.*,
  1283  				Users.Username as Username
  1284  			FROM
  1285  				Posts
  1286  			INNER JOIN
  1287  				Users ON Posts.UserId = Users.Id
  1288  			WHERE
  1289  				Posts.ParentId = :ParentId
  1290  				AND Posts.DeleteAt = 0
  1291  			ORDER BY
  1292  				Posts.Id`,
  1293  		map[string]interface{}{"ParentId": parentId})
  1294  
  1295  	if err != nil {
  1296  		return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1297  	}
  1298  
  1299  	return posts, nil
  1300  }
  1301  
  1302  func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) ([]*model.DirectPostForExport, *model.AppError) {
  1303  	query := s.getQueryBuilder().
  1304  		Select("p.*", "Users.Username as User").
  1305  		From("Posts p").
  1306  		Join("Channels ON p.ChannelId = Channels.Id").
  1307  		Join("Users ON p.UserId = Users.Id").
  1308  		Where(sq.And{
  1309  			sq.Gt{"p.Id": afterId},
  1310  			sq.Eq{"p.ParentId": string("")},
  1311  			sq.Eq{"p.DeleteAt": int(0)},
  1312  			sq.Eq{"Channels.DeleteAt": int(0)},
  1313  			sq.Eq{"Users.DeleteAt": int(0)},
  1314  			sq.Eq{"Channels.Type": []string{"D", "G"}},
  1315  		}).
  1316  		OrderBy("p.Id").
  1317  		Limit(uint64(limit))
  1318  
  1319  	queryString, args, err := query.ToSql()
  1320  	if err != nil {
  1321  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1322  	}
  1323  
  1324  	var posts []*model.DirectPostForExport
  1325  	if _, err = s.GetReplica().Select(&posts, queryString, args...); err != nil {
  1326  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1327  	}
  1328  	var channelIds []string
  1329  	for _, post := range posts {
  1330  		channelIds = append(channelIds, post.ChannelId)
  1331  	}
  1332  	query = s.getQueryBuilder().
  1333  		Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
  1334  		From("ChannelMembers cm").
  1335  		Join("Users u ON ( u.Id = cm.UserId )").
  1336  		Where(sq.Eq{
  1337  			"cm.ChannelId": channelIds,
  1338  		})
  1339  
  1340  	queryString, args, err = query.ToSql()
  1341  	if err != nil {
  1342  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1343  	}
  1344  
  1345  	var channelMembers []*model.ChannelMemberForExport
  1346  	if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
  1347  		return nil, model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
  1348  	}
  1349  
  1350  	// Build a map of channels and their posts
  1351  	postsChannelMap := make(map[string][]*model.DirectPostForExport)
  1352  	for _, post := range posts {
  1353  		post.ChannelMembers = &[]string{}
  1354  		postsChannelMap[post.ChannelId] = append(postsChannelMap[post.ChannelId], post)
  1355  	}
  1356  
  1357  	// Build a map of channels and their members
  1358  	channelMembersMap := make(map[string][]string)
  1359  	for _, member := range channelMembers {
  1360  		channelMembersMap[member.ChannelId] = append(channelMembersMap[member.ChannelId], member.Username)
  1361  	}
  1362  
  1363  	// Populate each post ChannelMembers extracting it from the channelMembersMap
  1364  	for channelId := range channelMembersMap {
  1365  		for _, post := range postsChannelMap[channelId] {
  1366  			*post.ChannelMembers = channelMembersMap[channelId]
  1367  		}
  1368  	}
  1369  	return posts, nil
  1370  }