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