github.com/mad-app/mattermost-server@v5.11.1+incompatible/store/sqlstore/post_store.go (about)

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