github.com/spline-fu/mattermost-server@v4.10.10+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) store.StoreChannel {
   402  	return store.Do(func(result *store.StoreResult) {
   403  		_, err := s.GetMaster().Exec("Update Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "RootId": postId})
   404  		if err != nil {
   405  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   406  		}
   407  	})
   408  }
   409  
   410  func (s *SqlPostStore) permanentDelete(postId string) store.StoreChannel {
   411  	return store.Do(func(result *store.StoreResult) {
   412  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   413  		if err != nil {
   414  			result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   415  		}
   416  	})
   417  }
   418  
   419  func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) store.StoreChannel {
   420  	return store.Do(func(result *store.StoreResult) {
   421  		_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   422  		if err != nil {
   423  			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)
   424  		}
   425  	})
   426  }
   427  
   428  func (s *SqlPostStore) PermanentDeleteByUser(userId string) store.StoreChannel {
   429  	return store.Do(func(result *store.StoreResult) {
   430  		// First attempt to delete all the comments for a user
   431  		if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil {
   432  			result.Err = r.Err
   433  			return
   434  		}
   435  
   436  		// Now attempt to delete all the root posts for a user.  This will also
   437  		// delete all the comments for each post.
   438  		found := true
   439  		count := 0
   440  
   441  		for found {
   442  			var ids []string
   443  			_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   444  			if err != nil {
   445  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   446  				return
   447  			} else {
   448  				found = false
   449  				for _, id := range ids {
   450  					found = true
   451  					if r := <-s.permanentDelete(id); r.Err != nil {
   452  						result.Err = r.Err
   453  						return
   454  					}
   455  				}
   456  			}
   457  
   458  			// This is a fail safe, give up if more than 10K messages
   459  			count = count + 1
   460  			if count >= 10 {
   461  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   462  				return
   463  			}
   464  		}
   465  	})
   466  }
   467  
   468  func (s *SqlPostStore) PermanentDeleteByChannel(channelId string) store.StoreChannel {
   469  	return store.Do(func(result *store.StoreResult) {
   470  		if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil {
   471  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   472  		}
   473  	})
   474  }
   475  
   476  func (s *SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) store.StoreChannel {
   477  	return store.Do(func(result *store.StoreResult) {
   478  		if limit > 1000 {
   479  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest)
   480  			return
   481  		}
   482  
   483  		// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   484  		if allowFromCache && offset == 0 && (limit == 60 || limit == 30) {
   485  			if cacheItem, ok := s.lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok {
   486  				if s.metrics != nil {
   487  					s.metrics.IncrementMemCacheHitCounter("Last Posts Cache")
   488  				}
   489  
   490  				result.Data = cacheItem.(*model.PostList)
   491  				return
   492  			} else {
   493  				if s.metrics != nil {
   494  					s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   495  				}
   496  			}
   497  		} else {
   498  			if s.metrics != nil {
   499  				s.metrics.IncrementMemCacheMissCounter("Last Posts Cache")
   500  			}
   501  		}
   502  
   503  		rpc := s.getRootPosts(channelId, offset, limit)
   504  		cpc := s.getParentsPosts(channelId, offset, limit)
   505  
   506  		if rpr := <-rpc; rpr.Err != nil {
   507  			result.Err = rpr.Err
   508  		} else if cpr := <-cpc; cpr.Err != nil {
   509  			result.Err = cpr.Err
   510  		} else {
   511  			posts := rpr.Data.([]*model.Post)
   512  			parents := cpr.Data.([]*model.Post)
   513  
   514  			list := model.NewPostList()
   515  
   516  			for _, p := range posts {
   517  				list.AddPost(p)
   518  				list.AddOrder(p.Id)
   519  			}
   520  
   521  			for _, p := range parents {
   522  				list.AddPost(p)
   523  			}
   524  
   525  			list.MakeNonNil()
   526  
   527  			// Caching only occurs on limits of 30 and 60, the common limits requested by MM clients
   528  			if offset == 0 && (limit == 60 || limit == 30) {
   529  				s.lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC)
   530  			}
   531  
   532  			result.Data = list
   533  		}
   534  	})
   535  }
   536  
   537  func (s *SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) store.StoreChannel {
   538  	return store.Do(func(result *store.StoreResult) {
   539  		if allowFromCache {
   540  			// If the last post in the channel's time is less than or equal to the time we are getting posts since,
   541  			// we can safely return no posts.
   542  			if cacheItem, ok := s.lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time {
   543  				if s.metrics != nil {
   544  					s.metrics.IncrementMemCacheHitCounter("Last Post Time")
   545  				}
   546  				list := model.NewPostList()
   547  				result.Data = list
   548  				return
   549  			} else {
   550  				if s.metrics != nil {
   551  					s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   552  				}
   553  			}
   554  		} else {
   555  			if s.metrics != nil {
   556  				s.metrics.IncrementMemCacheMissCounter("Last Post Time")
   557  			}
   558  		}
   559  
   560  		var posts []*model.Post
   561  		_, err := s.GetReplica().Select(&posts,
   562  			`(SELECT
   563  			    *
   564  			FROM
   565  			    Posts
   566  			WHERE
   567  			    (UpdateAt > :Time
   568  			        AND ChannelId = :ChannelId)
   569  			LIMIT 1000)
   570  			UNION
   571  			(SELECT
   572  			    *
   573  			FROM
   574  			    Posts
   575  			WHERE
   576  			    Id
   577  			IN
   578  			    (SELECT * FROM (SELECT
   579  			        RootId
   580  			    FROM
   581  			        Posts
   582  			    WHERE
   583  			        UpdateAt > :Time
   584  			            AND ChannelId = :ChannelId
   585  			    LIMIT 1000) temp_tab))
   586  			ORDER BY CreateAt DESC`,
   587  			map[string]interface{}{"ChannelId": channelId, "Time": time})
   588  
   589  		if err != nil {
   590  			result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   591  		} else {
   592  
   593  			list := model.NewPostList()
   594  
   595  			var latestUpdate int64 = 0
   596  
   597  			for _, p := range posts {
   598  				list.AddPost(p)
   599  				if p.UpdateAt > time {
   600  					list.AddOrder(p.Id)
   601  				}
   602  				if latestUpdate < p.UpdateAt {
   603  					latestUpdate = p.UpdateAt
   604  				}
   605  			}
   606  
   607  			s.lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC)
   608  
   609  			result.Data = list
   610  		}
   611  	})
   612  }
   613  
   614  func (s *SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   615  	return s.getPostsAround(channelId, postId, numPosts, offset, true)
   616  }
   617  
   618  func (s *SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) store.StoreChannel {
   619  	return s.getPostsAround(channelId, postId, numPosts, offset, false)
   620  }
   621  
   622  func (s *SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) store.StoreChannel {
   623  	return store.Do(func(result *store.StoreResult) {
   624  		var direction string
   625  		var sort string
   626  		if before {
   627  			direction = "<"
   628  			sort = "DESC"
   629  		} else {
   630  			direction = ">"
   631  			sort = "ASC"
   632  		}
   633  
   634  		var posts []*model.Post
   635  		var parents []*model.Post
   636  		_, err1 := s.GetReplica().Select(&posts,
   637  			`(SELECT
   638  			    *
   639  			FROM
   640  			    Posts
   641  			WHERE
   642  				(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   643  			        AND ChannelId = :ChannelId
   644  					AND DeleteAt = 0)
   645  			ORDER BY CreateAt `+sort+`
   646  			LIMIT :NumPosts
   647  			OFFSET :Offset)`,
   648  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   649  		_, err2 := s.GetReplica().Select(&parents,
   650  			`(SELECT
   651  			    *
   652  			FROM
   653  			    Posts
   654  			WHERE
   655  			    Id
   656  			IN
   657  			    (SELECT * FROM (SELECT
   658  			        RootId
   659  			    FROM
   660  			        Posts
   661  			    WHERE
   662  					(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
   663  						AND ChannelId = :ChannelId
   664  						AND DeleteAt = 0)
   665  					ORDER BY CreateAt `+sort+`
   666  					LIMIT :NumPosts
   667  					OFFSET :Offset)
   668  			    temp_tab))
   669  			ORDER BY CreateAt DESC`,
   670  			map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
   671  
   672  		if err1 != nil {
   673  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error(), http.StatusInternalServerError)
   674  		} else if err2 != nil {
   675  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error(), http.StatusInternalServerError)
   676  		} else {
   677  
   678  			list := model.NewPostList()
   679  
   680  			// We need to flip the order if we selected backwards
   681  			if before {
   682  				for _, p := range posts {
   683  					list.AddPost(p)
   684  					list.AddOrder(p.Id)
   685  				}
   686  			} else {
   687  				l := len(posts)
   688  				for i := range posts {
   689  					list.AddPost(posts[l-i-1])
   690  					list.AddOrder(posts[l-i-1].Id)
   691  				}
   692  			}
   693  
   694  			for _, p := range parents {
   695  				list.AddPost(p)
   696  			}
   697  
   698  			result.Data = list
   699  		}
   700  	})
   701  }
   702  
   703  func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel {
   704  	return store.Do(func(result *store.StoreResult) {
   705  		var posts []*model.Post
   706  		_, 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})
   707  		if err != nil {
   708  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
   709  		} else {
   710  			result.Data = posts
   711  		}
   712  	})
   713  }
   714  
   715  func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel {
   716  	return store.Do(func(result *store.StoreResult) {
   717  		var posts []*model.Post
   718  		_, err := s.GetReplica().Select(&posts,
   719  			`SELECT
   720  			    q2.*
   721  			FROM
   722  			    Posts q2
   723  			        INNER JOIN
   724  			    (SELECT DISTINCT
   725  			        q3.RootId
   726  			    FROM
   727  			        (SELECT
   728  			        RootId
   729  			    FROM
   730  			        Posts
   731  			    WHERE
   732  			        ChannelId = :ChannelId1
   733  			            AND DeleteAt = 0
   734  			    ORDER BY CreateAt DESC
   735  			    LIMIT :Limit OFFSET :Offset) q3
   736  			    WHERE q3.RootId != '') q1
   737  			    ON q1.RootId = q2.Id OR q1.RootId = q2.RootId
   738  			WHERE
   739  			    ChannelId = :ChannelId2
   740  			        AND DeleteAt = 0
   741  			ORDER BY CreateAt`,
   742  			map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
   743  		if err != nil {
   744  			result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
   745  		} else {
   746  			result.Data = posts
   747  		}
   748  	})
   749  }
   750  
   751  var specialSearchChar = []string{
   752  	"<",
   753  	">",
   754  	"+",
   755  	"-",
   756  	"(",
   757  	")",
   758  	"~",
   759  	"@",
   760  	":",
   761  }
   762  
   763  func (s *SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel {
   764  	return store.Do(func(result *store.StoreResult) {
   765  		queryParams := map[string]interface{}{
   766  			"TeamId": teamId,
   767  			"UserId": userId,
   768  		}
   769  
   770  		termMap := map[string]bool{}
   771  		terms := params.Terms
   772  
   773  		if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 {
   774  			result.Data = []*model.Post{}
   775  			return
   776  		}
   777  
   778  		searchType := "Message"
   779  		if params.IsHashtag {
   780  			searchType = "Hashtags"
   781  			for _, term := range strings.Split(terms, " ") {
   782  				termMap[strings.ToUpper(term)] = true
   783  			}
   784  		}
   785  
   786  		// these chars have special meaning and can be treated as spaces
   787  		for _, c := range specialSearchChar {
   788  			terms = strings.Replace(terms, c, " ", -1)
   789  		}
   790  
   791  		var posts []*model.Post
   792  
   793  		searchQuery := `
   794  			SELECT
   795  				*
   796  			FROM
   797  				Posts
   798  			WHERE
   799  				DeleteAt = 0
   800  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
   801  				POST_FILTER
   802  				AND ChannelId IN (
   803  					SELECT
   804  						Id
   805  					FROM
   806  						Channels,
   807  						ChannelMembers
   808  					WHERE
   809  						Id = ChannelId
   810  							AND (TeamId = :TeamId OR TeamId = '')
   811  							AND UserId = :UserId
   812  							AND DeleteAt = 0
   813  							CHANNEL_FILTER)
   814  				SEARCH_CLAUSE
   815  				ORDER BY CreateAt DESC
   816  			LIMIT 100`
   817  
   818  		if len(params.InChannels) > 1 {
   819  			inClause := ":InChannel0"
   820  			queryParams["InChannel0"] = params.InChannels[0]
   821  
   822  			for i := 1; i < len(params.InChannels); i++ {
   823  				paramName := "InChannel" + strconv.FormatInt(int64(i), 10)
   824  				inClause += ", :" + paramName
   825  				queryParams[paramName] = params.InChannels[i]
   826  			}
   827  
   828  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1)
   829  		} else if len(params.InChannels) == 1 {
   830  			queryParams["InChannel"] = params.InChannels[0]
   831  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1)
   832  		} else {
   833  			searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1)
   834  		}
   835  
   836  		if len(params.FromUsers) > 1 {
   837  			inClause := ":FromUser0"
   838  			queryParams["FromUser0"] = params.FromUsers[0]
   839  
   840  			for i := 1; i < len(params.FromUsers); i++ {
   841  				paramName := "FromUser" + strconv.FormatInt(int64(i), 10)
   842  				inClause += ", :" + paramName
   843  				queryParams[paramName] = params.FromUsers[i]
   844  			}
   845  
   846  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
   847  				AND UserId IN (
   848  					SELECT
   849  						Id
   850  					FROM
   851  						Users,
   852  						TeamMembers
   853  					WHERE
   854  						TeamMembers.TeamId = :TeamId
   855  						AND Users.Id = TeamMembers.UserId
   856  						AND Username IN (`+inClause+`))`, 1)
   857  		} else if len(params.FromUsers) == 1 {
   858  			queryParams["FromUser"] = params.FromUsers[0]
   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 = :FromUser)`, 1)
   870  		} else {
   871  			searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
   872  		}
   873  
   874  		if terms == "" {
   875  			// we've already confirmed that we have a channel or user to search for
   876  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
   877  		} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   878  			// Parse text for wildcards
   879  			if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
   880  				terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
   881  			}
   882  
   883  			if params.OrTerms {
   884  				terms = strings.Join(strings.Fields(terms), " | ")
   885  			} else {
   886  				terms = strings.Join(strings.Fields(terms), " & ")
   887  			}
   888  
   889  			searchClause := fmt.Sprintf("AND %s @@  to_tsquery(:Terms)", searchType)
   890  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   891  		} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   892  			searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
   893  			searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
   894  
   895  			if !params.OrTerms {
   896  				splitTerms := strings.Fields(terms)
   897  				for i, t := range strings.Fields(terms) {
   898  					splitTerms[i] = "+" + t
   899  				}
   900  
   901  				terms = strings.Join(splitTerms, " ")
   902  			}
   903  		}
   904  
   905  		queryParams["Terms"] = terms
   906  
   907  		list := model.NewPostList()
   908  
   909  		_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
   910  		if err != nil {
   911  			mlog.Warn(fmt.Sprintf("Query error searching posts: %v", err.Error()))
   912  			// 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.
   913  		} else {
   914  			for _, p := range posts {
   915  				if searchType == "Hashtags" {
   916  					exactMatch := false
   917  					for _, tag := range strings.Split(p.Hashtags, " ") {
   918  						if termMap[strings.ToUpper(tag)] {
   919  							exactMatch = true
   920  						}
   921  					}
   922  					if !exactMatch {
   923  						continue
   924  					}
   925  				}
   926  				list.AddPost(p)
   927  				list.AddOrder(p.Id)
   928  			}
   929  		}
   930  
   931  		list.MakeNonNil()
   932  
   933  		result.Data = list
   934  	})
   935  }
   936  
   937  func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) store.StoreChannel {
   938  	return store.Do(func(result *store.StoreResult) {
   939  		query :=
   940  			`SELECT DISTINCT
   941  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   942  			        COUNT(DISTINCT Posts.UserId) AS Value
   943  			FROM Posts`
   944  
   945  		if len(teamId) > 0 {
   946  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
   947  		} else {
   948  			query += " WHERE"
   949  		}
   950  
   951  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
   952  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
   953  			ORDER BY Name DESC
   954  			LIMIT 30`
   955  
   956  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   957  			query =
   958  				`SELECT
   959  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, 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(TO_TIMESTAMP(Posts.CreateAt / 1000))
   970  				ORDER BY Name DESC
   971  				LIMIT 30`
   972  		}
   973  
   974  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
   975  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
   976  
   977  		var rows model.AnalyticsRows
   978  		_, err := s.GetReplica().Select(
   979  			&rows,
   980  			query,
   981  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
   982  		if err != nil {
   983  			result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
   984  		} else {
   985  			result.Data = rows
   986  		}
   987  	})
   988  }
   989  
   990  func (s *SqlPostStore) AnalyticsPostCountsByDay(teamId string) store.StoreChannel {
   991  	return store.Do(func(result *store.StoreResult) {
   992  		query :=
   993  			`SELECT
   994  			        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
   995  			        COUNT(Posts.Id) AS Value
   996  			    FROM Posts`
   997  
   998  		if len(teamId) > 0 {
   999  			query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND"
  1000  		} else {
  1001  			query += " WHERE"
  1002  		}
  1003  
  1004  		query += ` Posts.CreateAt <= :EndTime
  1005  			            AND Posts.CreateAt >= :StartTime
  1006  			GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1007  			ORDER BY Name DESC
  1008  			LIMIT 30`
  1009  
  1010  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1011  			query =
  1012  				`SELECT
  1013  					TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
  1014  				FROM Posts`
  1015  
  1016  			if len(teamId) > 0 {
  1017  				query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id  AND Channels.TeamId = :TeamId AND"
  1018  			} else {
  1019  				query += " WHERE"
  1020  			}
  1021  
  1022  			query += ` Posts.CreateAt <= :EndTime
  1023  				            AND Posts.CreateAt >= :StartTime
  1024  				GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1025  				ORDER BY Name DESC
  1026  				LIMIT 30`
  1027  		}
  1028  
  1029  		end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1030  		start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1031  
  1032  		var rows model.AnalyticsRows
  1033  		_, err := s.GetReplica().Select(
  1034  			&rows,
  1035  			query,
  1036  			map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
  1037  		if err != nil {
  1038  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1039  		} else {
  1040  			result.Data = rows
  1041  		}
  1042  	})
  1043  }
  1044  
  1045  func (s *SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) store.StoreChannel {
  1046  	return store.Do(func(result *store.StoreResult) {
  1047  		query :=
  1048  			`SELECT
  1049  			    COUNT(Posts.Id) AS Value
  1050  			FROM
  1051  			    Posts,
  1052  			    Channels
  1053  			WHERE
  1054  			    Posts.ChannelId = Channels.Id`
  1055  
  1056  		if len(teamId) > 0 {
  1057  			query += " AND Channels.TeamId = :TeamId"
  1058  		}
  1059  
  1060  		if mustHaveFile {
  1061  			query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')"
  1062  		}
  1063  
  1064  		if mustHaveHashtag {
  1065  			query += " AND Posts.Hashtags != ''"
  1066  		}
  1067  
  1068  		if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil {
  1069  			result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1070  		} else {
  1071  			result.Data = v
  1072  		}
  1073  	})
  1074  }
  1075  
  1076  func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) store.StoreChannel {
  1077  	return store.Do(func(result *store.StoreResult) {
  1078  		query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId`
  1079  
  1080  		var posts []*model.Post
  1081  		_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId})
  1082  
  1083  		if err != nil {
  1084  			result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
  1085  		} else {
  1086  			result.Data = posts
  1087  		}
  1088  	})
  1089  }
  1090  
  1091  func (s *SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel {
  1092  	return store.Do(func(result *store.StoreResult) {
  1093  		keys := bytes.Buffer{}
  1094  		params := make(map[string]interface{})
  1095  		for i, postId := range postIds {
  1096  			if keys.Len() > 0 {
  1097  				keys.WriteString(",")
  1098  			}
  1099  
  1100  			key := "Post" + strconv.Itoa(i)
  1101  			keys.WriteString(":" + key)
  1102  			params[key] = postId
  1103  		}
  1104  
  1105  		query := `SELECT * FROM Posts WHERE Id in (` + keys.String() + `) and DeleteAt = 0 ORDER BY CreateAt DESC`
  1106  
  1107  		var posts []*model.Post
  1108  		_, err := s.GetReplica().Select(&posts, query, params)
  1109  
  1110  		if err != nil {
  1111  			mlog.Error(fmt.Sprint(err))
  1112  			result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1113  		} else {
  1114  			result.Data = posts
  1115  		}
  1116  	})
  1117  }
  1118  
  1119  func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) store.StoreChannel {
  1120  	return store.Do(func(result *store.StoreResult) {
  1121  		var posts []*model.PostForIndexing
  1122  		_, err1 := s.GetSearchReplica().Select(&posts,
  1123  			`SELECT
  1124  				PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
  1125  			FROM (
  1126  				SELECT
  1127  					*
  1128  				FROM
  1129  					Posts
  1130  				WHERE
  1131  					Posts.CreateAt >= :StartTime
  1132  				AND
  1133  					Posts.CreateAt < :EndTime
  1134  				ORDER BY
  1135  					CreateAt ASC
  1136  				LIMIT
  1137  					1000
  1138  				)
  1139  			AS
  1140  				PostsQuery
  1141  			LEFT JOIN
  1142  				Channels
  1143  			ON
  1144  				PostsQuery.ChannelId = Channels.Id
  1145  			LEFT JOIN
  1146  				Posts ParentPosts
  1147  			ON
  1148  				PostsQuery.RootId = ParentPosts.Id`,
  1149  			map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1150  
  1151  		if err1 != nil {
  1152  			result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err1.Error(), http.StatusInternalServerError)
  1153  		} else {
  1154  			result.Data = posts
  1155  		}
  1156  	})
  1157  }
  1158  
  1159  func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
  1160  	return store.Do(func(result *store.StoreResult) {
  1161  		var query string
  1162  		if s.DriverName() == "postgres" {
  1163  			query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1164  		} else {
  1165  			query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1166  		}
  1167  
  1168  		sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1169  		if err != nil {
  1170  			result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1171  		} else {
  1172  			rowsAffected, err1 := sqlResult.RowsAffected()
  1173  			if err1 != nil {
  1174  				result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1175  				result.Data = int64(0)
  1176  			} else {
  1177  				result.Data = rowsAffected
  1178  			}
  1179  		}
  1180  	})
  1181  }
  1182  
  1183  func (s *SqlPostStore) GetOldest() store.StoreChannel {
  1184  	return store.Do(func(result *store.StoreResult) {
  1185  		var post model.Post
  1186  		err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1187  		if err != nil {
  1188  			result.Err = model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1189  		}
  1190  
  1191  		result.Data = &post
  1192  	})
  1193  }
  1194  
  1195  func (s *SqlPostStore) determineMaxPostSize() int {
  1196  	var maxPostSize int = model.POST_MESSAGE_MAX_RUNES_V1
  1197  	var maxPostSizeBytes int32
  1198  
  1199  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1200  		// The Post.Message column in Postgres has historically been VARCHAR(4000), but
  1201  		// may be manually enlarged to support longer posts.
  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_name = 'posts'
  1209  			AND	column_name = 'message'
  1210  		`); err != nil {
  1211  			mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error())
  1212  		}
  1213  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1214  		// The Post.Message column in MySQL has historically been TEXT, with a maximum
  1215  		// limit of 65535.
  1216  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1217  			SELECT 
  1218  				COALESCE(CHARACTER_MAXIMUM_LENGTH, 0)
  1219  			FROM 
  1220  				INFORMATION_SCHEMA.COLUMNS
  1221  			WHERE 
  1222  				table_schema = DATABASE()
  1223  			AND	table_name = 'Posts'
  1224  			AND	column_name = 'Message'
  1225  			LIMIT 0, 1
  1226  		`); err != nil {
  1227  			mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error())
  1228  		}
  1229  	} else {
  1230  		mlog.Warn("No implementation found to determine the maximum supported post size")
  1231  	}
  1232  
  1233  	// Assume a worst-case representation of four bytes per rune.
  1234  	maxPostSize = int(maxPostSizeBytes) / 4
  1235  
  1236  	// To maintain backwards compatibility, don't yield a maximum post
  1237  	// size smaller than the previous limit, even though it wasn't
  1238  	// actually possible to store 4000 runes in all cases.
  1239  	if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 {
  1240  		maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1
  1241  	}
  1242  
  1243  	mlog.Info(fmt.Sprintf("Post.Message supports at most %d characters (%d bytes)", maxPostSize, maxPostSizeBytes))
  1244  
  1245  	return maxPostSize
  1246  }
  1247  
  1248  // GetMaxPostSize returns the maximum number of runes that may be stored in a post.
  1249  func (s *SqlPostStore) GetMaxPostSize() store.StoreChannel {
  1250  	return store.Do(func(result *store.StoreResult) {
  1251  		s.maxPostSizeOnce.Do(func() {
  1252  			s.maxPostSizeCached = s.determineMaxPostSize()
  1253  		})
  1254  		result.Data = s.maxPostSizeCached
  1255  	})
  1256  }