github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/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  	"database/sql"
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	sq "github.com/Masterminds/squirrel"
    16  	"github.com/vnforks/kid/v5/einterfaces"
    17  	"github.com/vnforks/kid/v5/mlog"
    18  	"github.com/vnforks/kid/v5/model"
    19  	"github.com/vnforks/kid/v5/store"
    20  	"github.com/vnforks/kid/v5/utils"
    21  )
    22  
    23  type SqlPostStore struct {
    24  	SqlStore
    25  	metrics           einterfaces.MetricsInterface
    26  	maxPostSizeOnce   sync.Once
    27  	maxPostSizeCached int
    28  }
    29  
    30  func (s *SqlPostStore) ClearCaches() {
    31  }
    32  
    33  func postSliceColumns() []string {
    34  	return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "UserId", "ClassId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions"}
    35  }
    36  
    37  func postToSlice(post *model.Post) []interface{} {
    38  	return []interface{}{
    39  		post.Id,
    40  		post.CreateAt,
    41  		post.UpdateAt,
    42  		post.EditAt,
    43  		post.DeleteAt,
    44  		post.UserId,
    45  		post.ClassId,
    46  		post.Message,
    47  		post.Type,
    48  		model.StringInterfaceToJson(post.Props),
    49  		post.Hashtags,
    50  		model.ArrayToJson(post.Filenames),
    51  		model.ArrayToJson(post.FileIds),
    52  		post.HasReactions,
    53  	}
    54  }
    55  
    56  func newSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
    57  	s := &SqlPostStore{
    58  		SqlStore:          sqlStore,
    59  		metrics:           metrics,
    60  		maxPostSizeCached: model.POST_MESSAGE_MAX_RUNES_V1,
    61  	}
    62  
    63  	for _, db := range sqlStore.GetAllConns() {
    64  		table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id")
    65  		table.ColMap("Id").SetMaxSize(26)
    66  		table.ColMap("UserId").SetMaxSize(26)
    67  		table.ColMap("ClassId").SetMaxSize(26)
    68  		table.ColMap("Message").SetMaxSize(model.POST_MESSAGE_MAX_BYTES_V2)
    69  		table.ColMap("Type").SetMaxSize(26)
    70  		table.ColMap("Hashtags").SetMaxSize(1000)
    71  		table.ColMap("Props").SetMaxSize(8000)
    72  		table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES)
    73  		table.ColMap("FileIds").SetMaxSize(150)
    74  	}
    75  
    76  	return s
    77  }
    78  
    79  func (s *SqlPostStore) createIndexesIfNotExists() {
    80  	s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt")
    81  	s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
    82  	s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt")
    83  	s.CreateIndexIfNotExists("idx_posts_class_id", "Posts", "ClassId")
    84  	s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
    85  
    86  	s.CreateCompositeIndexIfNotExists("idx_posts_class_id_update_at", "Posts", []string{"ClassId", "UpdateAt"})
    87  	s.CreateCompositeIndexIfNotExists("idx_posts_class_id_delete_at_create_at", "Posts", []string{"ClassId", "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) SaveMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) {
    94  	classNewPosts := make(map[string]int)
    95  	maxDateNewPosts := make(map[string]int64)
    96  	// rootIds := make(map[string]int)
    97  	// maxDateRootIds := make(map[string]int64)
    98  	for _, post := range posts {
    99  		if len(post.Id) > 0 {
   100  			return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
   101  		}
   102  		post.PreSave()
   103  		maxPostSize := s.GetMaxPostSize()
   104  		if err := post.IsValid(maxPostSize); err != nil {
   105  			return nil, err
   106  		}
   107  
   108  		currentClassCount, ok := classNewPosts[post.ClassId]
   109  		if !ok {
   110  			if post.IsJoinLeaveMessage() {
   111  				classNewPosts[post.ClassId] = 0
   112  			} else {
   113  				classNewPosts[post.ClassId] = 1
   114  			}
   115  			maxDateNewPosts[post.ClassId] = post.CreateAt
   116  		} else {
   117  			if !post.IsJoinLeaveMessage() {
   118  				classNewPosts[post.ClassId] = currentClassCount + 1
   119  			}
   120  			if post.CreateAt > maxDateNewPosts[post.ClassId] {
   121  				maxDateNewPosts[post.ClassId] = post.CreateAt
   122  			}
   123  		}
   124  
   125  	}
   126  
   127  	query := s.getQueryBuilder().Insert("Posts").Columns(postSliceColumns()...)
   128  	for _, post := range posts {
   129  		query = query.Values(postToSlice(post)...)
   130  	}
   131  	sql, args, err := query.ToSql()
   132  	if err != nil {
   133  		return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
   134  	}
   135  
   136  	if _, err := s.GetMaster().Exec(sql, args...); err != nil {
   137  		return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
   138  	}
   139  
   140  	for classId, count := range classNewPosts {
   141  		if _, err := s.GetMaster().Exec("UPDATE Classes SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count WHERE Id = :ClassId", map[string]interface{}{"LastPostAt": maxDateNewPosts[classId], "ClassId": classId, "Count": count}); err != nil {
   142  			mlog.Error("Error updating Class LastPostAt.", mlog.Err(err))
   143  		}
   144  	}
   145  
   146  	return posts, nil
   147  }
   148  
   149  func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
   150  	posts, err := s.SaveMultiple([]*model.Post{post})
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	return posts[0], nil
   155  }
   156  
   157  func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, *model.AppError) {
   158  	newPost.UpdateAt = model.GetMillis()
   159  	newPost.PreCommit()
   160  
   161  	oldPost.DeleteAt = newPost.UpdateAt
   162  	oldPost.UpdateAt = newPost.UpdateAt
   163  	oldPost.Id = model.NewId()
   164  	oldPost.PreCommit()
   165  
   166  	maxPostSize := s.GetMaxPostSize()
   167  
   168  	if err := newPost.IsValid(maxPostSize); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	if _, err := s.GetMaster().Update(newPost); err != nil {
   173  		return nil, model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError)
   174  	}
   175  
   176  	time := model.GetMillis()
   177  	s.GetMaster().Exec("UPDATE Classes SET LastPostAt = :LastPostAt  WHERE Id = :ClassId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ClassId": newPost.ClassId})
   178  
   179  	// mark the old post as deleted
   180  	s.GetMaster().Insert(oldPost)
   181  
   182  	return newPost, nil
   183  }
   184  
   185  func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) {
   186  	updateAt := model.GetMillis()
   187  	maxPostSize := s.GetMaxPostSize()
   188  	for _, post := range posts {
   189  		post.UpdateAt = updateAt
   190  		if appErr := post.IsValid(maxPostSize); appErr != nil {
   191  			return nil, appErr
   192  		}
   193  	}
   194  
   195  	tx, err := s.GetMaster().Begin()
   196  	if err != nil {
   197  		return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
   198  	}
   199  	for _, post := range posts {
   200  		if _, err = tx.Update(post); err != nil {
   201  			txErr := tx.Rollback()
   202  			if txErr != nil {
   203  				return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, txErr.Error(), http.StatusInternalServerError)
   204  			}
   205  
   206  			return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
   207  		}
   208  	}
   209  	err = tx.Commit()
   210  	if err != nil {
   211  		return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
   212  	}
   213  
   214  	return posts, nil
   215  }
   216  
   217  func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) {
   218  	posts, err := s.OverwriteMultiple([]*model.Post{post})
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	return posts[0], nil
   224  }
   225  
   226  func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
   227  	pl := model.NewPostList()
   228  
   229  	var posts []*model.Post
   230  	if _, err := s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p 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 {
   231  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   232  	}
   233  
   234  	for _, post := range posts {
   235  		pl.AddPost(post)
   236  		pl.AddOrder(post.Id)
   237  	}
   238  
   239  	return pl, nil
   240  }
   241  
   242  func (s *SqlPostStore) GetFlaggedPostsForBranch(userId, branchId string, offset int, limit int) (*model.PostList, *model.AppError) {
   243  	pl := model.NewPostList()
   244  
   245  	var posts []*model.Post
   246  
   247  	query := `
   248              SELECT
   249                  A.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN A.RootId = '' THEN A.Id ELSE A.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
   250              FROM
   251                  (SELECT
   252                      *
   253                  FROM
   254                      Posts
   255                  WHERE
   256                      Id
   257                  IN
   258                      (SELECT
   259                          Name
   260                      FROM
   261                          Preferences
   262                      WHERE
   263                          UserId = :UserId
   264                          AND Category = :Category)
   265                          AND DeleteAt = 0
   266                  ) as A
   267              INNER JOIN Classes as B
   268                  ON B.Id = A.ClassId
   269              WHERE B.BranchId = :BranchId OR B.BranchId = ''
   270              ORDER BY CreateAt DESC
   271              LIMIT :Limit OFFSET :Offset`
   272  
   273  	if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit, "BranchId": branchId}); err != nil {
   274  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForBranch", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   275  	}
   276  
   277  	for _, post := range posts {
   278  		pl.AddPost(post)
   279  		pl.AddOrder(post.Id)
   280  	}
   281  
   282  	return pl, nil
   283  }
   284  
   285  func (s *SqlPostStore) GetFlaggedPostsForClass(userId, classId string, offset int, limit int) (*model.PostList, *model.AppError) {
   286  	pl := model.NewPostList()
   287  
   288  	var posts []*model.Post
   289  	query := `
   290  		SELECT
   291  			*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
   292  		FROM Posts p
   293  		WHERE
   294  			Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category)
   295  			AND ClassId = :ClassId
   296  			AND DeleteAt = 0
   297  		ORDER BY CreateAt DESC
   298  		LIMIT :Limit OFFSET :Offset`
   299  
   300  	if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "ClassId": classId, "Offset": offset, "Limit": limit}); err != nil {
   301  		return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForClass", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   302  	}
   303  	for _, post := range posts {
   304  		pl.AddPost(post)
   305  		pl.AddOrder(post.Id)
   306  	}
   307  
   308  	return pl, nil
   309  }
   310  
   311  func (s *SqlPostStore) Get(id string, skipFetchThreads bool) (*model.PostList, *model.AppError) {
   312  	pl := model.NewPostList()
   313  
   314  	if len(id) == 0 {
   315  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest)
   316  	}
   317  
   318  	var post model.Post
   319  	postFetchQuery := "SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id = :Id AND p.DeleteAt = 0"
   320  	err := s.GetReplica().SelectOne(&post, postFetchQuery, map[string]interface{}{"Id": id})
   321  	if err != nil {
   322  		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   323  	}
   324  	pl.AddPost(&post)
   325  	pl.AddOrder(id)
   326  	// if !skipFetchThreads {
   327  
   328  	// 	if len(rootId) == 0 {
   329  	// 		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError)
   330  	// 	}
   331  
   332  	// 	var posts []*model.Post
   333  	// 	_, err = s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId})
   334  	// 	if err != nil {
   335  	// 		return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError)
   336  	// 	}
   337  
   338  	// 	for _, p := range posts {
   339  	// 		pl.AddPost(p)
   340  	// 		pl.AddOrder(p.Id)
   341  	// 	}
   342  	// }
   343  	return pl, nil
   344  }
   345  
   346  func (s *SqlPostStore) GetSingle(id string) (*model.Post, *model.AppError) {
   347  	var post model.Post
   348  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
   349  	if err != nil {
   350  		return nil, model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound)
   351  	}
   352  	return &post, nil
   353  }
   354  
   355  type etagPosts struct {
   356  	Id       string
   357  	UpdateAt int64
   358  }
   359  
   360  func (s *SqlPostStore) InvalidateLastPostTimeCache(classId string) {
   361  }
   362  
   363  func (s *SqlPostStore) GetEtag(classId string, allowFromCache bool) string {
   364  	var et etagPosts
   365  	err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ClassId = :ClassId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ClassId": classId})
   366  	var result string
   367  	if err != nil {
   368  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
   369  	} else {
   370  		result = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt)
   371  	}
   372  
   373  	return result
   374  }
   375  
   376  func (s *SqlPostStore) Delete(postId string, time int64, deleteByID string) *model.AppError {
   377  
   378  	appErr := func(errMsg string) *model.AppError {
   379  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+errMsg, http.StatusInternalServerError)
   380  	}
   381  
   382  	var post model.Post
   383  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId})
   384  	if err != nil {
   385  		return appErr(err.Error())
   386  	}
   387  
   388  	post.AddProp(model.POST_PROPS_DELETE_BY, deleteByID)
   389  
   390  	_, 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.GetProps())})
   391  	if err != nil {
   392  		return appErr(err.Error())
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  func (s *SqlPostStore) permanentDelete(postId string) *model.AppError {
   399  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId})
   400  	if err != nil {
   401  		return model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError)
   402  	}
   403  	return nil
   404  }
   405  
   406  func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) *model.AppError {
   407  	_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
   408  	if err != nil {
   409  		return model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   410  	}
   411  	return nil
   412  }
   413  
   414  func (s *SqlPostStore) PermanentDeleteByUser(userId string) *model.AppError {
   415  	// First attempt to delete all the comments for a user
   416  	if err := s.permanentDeleteAllCommentByUser(userId); err != nil {
   417  		return err
   418  	}
   419  
   420  	// Now attempt to delete all the root posts for a user. This will also
   421  	// delete all the comments for each post
   422  	found := true
   423  	count := 0
   424  
   425  	for found {
   426  		var ids []string
   427  		_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
   428  		if err != nil {
   429  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError)
   430  		}
   431  
   432  		found = false
   433  		for _, id := range ids {
   434  			found = true
   435  			if err := s.permanentDelete(id); err != nil {
   436  				return err
   437  			}
   438  		}
   439  
   440  		// This is a fail safe, give up if more than 10k messages
   441  		count++
   442  		if count >= 10 {
   443  			return model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError)
   444  		}
   445  	}
   446  
   447  	return nil
   448  }
   449  
   450  func (s *SqlPostStore) PermanentDeleteByClass(classId string) *model.AppError {
   451  	if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ClassId = :ClassId", map[string]interface{}{"ClassId": classId}); err != nil {
   452  		return model.NewAppError("SqlPostStore.PermanentDeleteByClass", "store.sql_post.permanent_delete_by_class.app_error", nil, "class_id="+classId+", "+err.Error(), http.StatusInternalServerError)
   453  	}
   454  	return nil
   455  }
   456  
   457  func (s *SqlPostStore) GetPosts(options model.GetPostsOptions, _ bool) (*model.PostList, *model.AppError) {
   458  	if options.PerPage > 1000 {
   459  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "classId="+options.ClassId, http.StatusBadRequest)
   460  	}
   461  	offset := options.PerPage * options.Page
   462  
   463  	rpc := make(chan store.StoreResult, 1)
   464  	go func() {
   465  		posts, err := s.getRootPosts(options.ClassId, offset, options.PerPage, options.SkipFetchThreads)
   466  		rpc <- store.StoreResult{Data: posts, Err: err}
   467  		close(rpc)
   468  	}()
   469  	cpc := make(chan store.StoreResult, 1)
   470  	go func() {
   471  		posts, err := s.getParentsPosts(options.ClassId, offset, options.PerPage, options.SkipFetchThreads)
   472  		cpc <- store.StoreResult{Data: posts, Err: err}
   473  		close(cpc)
   474  	}()
   475  
   476  	var err *model.AppError
   477  	list := model.NewPostList()
   478  
   479  	rpr := <-rpc
   480  	if rpr.Err != nil {
   481  		return nil, rpr.Err
   482  	}
   483  
   484  	cpr := <-cpc
   485  	if cpr.Err != nil {
   486  		return nil, cpr.Err
   487  	}
   488  
   489  	posts := rpr.Data.([]*model.Post)
   490  	parents := cpr.Data.([]*model.Post)
   491  
   492  	for _, p := range posts {
   493  		list.AddPost(p)
   494  		list.AddOrder(p.Id)
   495  	}
   496  
   497  	for _, p := range parents {
   498  		list.AddPost(p)
   499  	}
   500  
   501  	list.MakeNonNil()
   502  
   503  	return list, err
   504  }
   505  
   506  func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool) (*model.PostList, *model.AppError) {
   507  	var posts []*model.Post
   508  
   509  	replyCountQuery1 := ""
   510  	replyCountQuery2 := ""
   511  	if options.SkipFetchThreads {
   512  		replyCountQuery1 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p1.RootId = '' THEN p1.Id ELSE p1.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   513  		replyCountQuery2 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p2.RootId = '' THEN p2.Id ELSE p2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   514  	}
   515  	var query string
   516  
   517  	// union of IDs and then join to get full posts is faster in mysql
   518  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   519  		query = `SELECT *` + replyCountQuery1 + ` FROM Posts p1 JOIN (
   520  			(SELECT
   521                Id
   522  			  FROM
   523  				  Posts p2
   524  			  WHERE
   525  				  (UpdateAt > :Time
   526  					  AND ClassId = :ClassId)
   527  				  LIMIT 1000)
   528  			  UNION
   529  				  (SELECT
   530  					  Id
   531  				  FROM
   532  					  Posts p3
   533  				  WHERE
   534  					  Id
   535  				  IN
   536  					  (SELECT * FROM (SELECT
   537  						  RootId
   538  					  FROM
   539  						  Posts
   540  					  WHERE
   541  						  UpdateAt > :Time
   542  							  AND ClassId = :ClassId
   543  					  LIMIT 1000) temp_tab))
   544  			) j ON p1.Id = j.Id
   545            ORDER BY CreateAt DESC`
   546  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   547  		query = `
   548  			(SELECT
   549                         *` + replyCountQuery1 + `
   550                 FROM
   551                         Posts p1
   552                 WHERE
   553                         (UpdateAt > :Time
   554                                 AND ClassId = :ClassId)
   555                         LIMIT 1000)
   556                 UNION
   557                         (SELECT
   558                             *` + replyCountQuery2 + `
   559                         FROM
   560                             Posts p2
   561                         WHERE
   562                             Id
   563                         IN
   564                             (SELECT * FROM (SELECT
   565                                 RootId
   566                             FROM
   567                                 Posts
   568                             WHERE
   569                                 UpdateAt > :Time
   570                                                 AND ClassId = :ClassId
   571                                 LIMIT 1000) temp_tab))
   572                 ORDER BY CreateAt DESC`
   573  	}
   574  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"ClassId": options.ClassId, "Time": options.Time})
   575  
   576  	if err != nil {
   577  		return nil, model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError)
   578  	}
   579  
   580  	list := model.NewPostList()
   581  
   582  	for _, p := range posts {
   583  		list.AddPost(p)
   584  		if p.UpdateAt > options.Time {
   585  			list.AddOrder(p.Id)
   586  		}
   587  	}
   588  
   589  	return list, nil
   590  }
   591  
   592  func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   593  	return s.getPostsAround(true, options)
   594  }
   595  
   596  func (s *SqlPostStore) GetPostsAfter(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   597  	return s.getPostsAround(false, options)
   598  }
   599  
   600  func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   601  	offset := options.Page * options.PerPage
   602  	var posts, parents []*model.Post
   603  
   604  	var direction string
   605  	var sort string
   606  	if before {
   607  		direction = "<"
   608  		sort = "DESC"
   609  	} else {
   610  		direction = ">"
   611  		sort = "ASC"
   612  	}
   613  	replyCountSubQuery := s.getQueryBuilder().Select("COUNT(Posts.Id)").From("Posts").Where(sq.Expr("p.RootId = '' AND RootId = p.Id AND DeleteAt = 0"))
   614  	query := s.getQueryBuilder().Select("p.*")
   615  	if options.SkipFetchThreads {
   616  		query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
   617  	}
   618  	query = query.From("Posts p").
   619  		Where(sq.And{
   620  			sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId),
   621  			sq.Eq{"ClassId": options.ClassId},
   622  			sq.Eq{"DeleteAt": int(0)},
   623  		}).
   624  		OrderBy("CreateAt " + sort).
   625  		Limit(uint64(options.PerPage)).
   626  		Offset(uint64(offset))
   627  
   628  	queryString, args, err := query.ToSql()
   629  
   630  	if err != nil {
   631  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError)
   632  	}
   633  	_, err = s.GetMaster().Select(&posts, queryString, args...)
   634  	if err != nil {
   635  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError)
   636  	}
   637  
   638  	if len(posts) > 0 {
   639  		// rootIds := []string{}
   640  		// for _, post := range posts {
   641  		// 	rootIds = append(rootIds, post.Id)
   642  		// 	if post.RootId != "" {
   643  		// 		rootIds = append(rootIds, post.RootId)
   644  		// 	}
   645  		// }
   646  		// rootQuery := s.getQueryBuilder().Select("p.*")
   647  		// idQuery := sq.Or{
   648  		// 	sq.Eq{"Id": rootIds},
   649  		// }
   650  		// if options.SkipFetchThreads {
   651  		// 	rootQuery = rootQuery.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
   652  		// } else {
   653  		// 	idQuery = append(idQuery, sq.Eq{"RootId": rootIds}) // preserve original behaviour
   654  		// }
   655  
   656  		// rootQuery = rootQuery.From("Posts p").
   657  		// 	Where(sq.And{
   658  		// 		idQuery,
   659  		// 		sq.Eq{"ClassId": options.ClassId},
   660  		// 		sq.Eq{"DeleteAt": 0},
   661  		// 	}).
   662  		// 	OrderBy("CreateAt DESC")
   663  
   664  		// rootQueryString, rootArgs, err := rootQuery.ToSql()
   665  
   666  		// if err != nil {
   667  		// 	return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError)
   668  		// }
   669  		// _, err = s.GetMaster().Select(&parents, rootQueryString, rootArgs...)
   670  		// if err != nil {
   671  		// 	return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError)
   672  		// }
   673  	}
   674  
   675  	list := model.NewPostList()
   676  
   677  	// We need to flip the order if we selected backwards
   678  	if before {
   679  		for _, p := range posts {
   680  			list.AddPost(p)
   681  			list.AddOrder(p.Id)
   682  		}
   683  	} else {
   684  		l := len(posts)
   685  		for i := range posts {
   686  			list.AddPost(posts[l-i-1])
   687  			list.AddOrder(posts[l-i-1].Id)
   688  		}
   689  	}
   690  
   691  	for _, p := range parents {
   692  		list.AddPost(p)
   693  	}
   694  
   695  	return list, nil
   696  }
   697  
   698  func (s *SqlPostStore) GetPostIdBeforeTime(classId string, time int64) (string, *model.AppError) {
   699  	return s.getPostIdAroundTime(classId, time, true)
   700  }
   701  
   702  func (s *SqlPostStore) GetPostIdAfterTime(classId string, time int64) (string, *model.AppError) {
   703  	return s.getPostIdAroundTime(classId, time, false)
   704  }
   705  
   706  func (s *SqlPostStore) getPostIdAroundTime(classId string, time int64, before bool) (string, *model.AppError) {
   707  	var direction sq.Sqlizer
   708  	var sort string
   709  	if before {
   710  		direction = sq.Lt{"CreateAt": time}
   711  		sort = "DESC"
   712  	} else {
   713  		direction = sq.Gt{"CreateAt": time}
   714  		sort = "ASC"
   715  	}
   716  
   717  	query := s.getQueryBuilder().
   718  		Select("Id").
   719  		From("Posts").
   720  		Where(sq.And{
   721  			direction,
   722  			sq.Eq{"ClassId": classId},
   723  			sq.Eq{"DeleteAt": int(0)},
   724  		}).
   725  		// Adding ClassId and DeleteAt order columns
   726  		// to let mysql choose the "idx_posts_class_id_delete_at_create_at" index always.
   727  		// See MM-23369.
   728  		OrderBy("ClassId", "DeleteAt", "CreateAt "+sort).
   729  		Limit(1)
   730  
   731  	queryString, args, err := query.ToSql()
   732  	if err != nil {
   733  		return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError)
   734  	}
   735  
   736  	var postId string
   737  	if err := s.GetMaster().SelectOne(&postId, queryString, args...); err != nil {
   738  		if err != sql.ErrNoRows {
   739  			return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError)
   740  		}
   741  	}
   742  
   743  	return postId, nil
   744  }
   745  
   746  func (s *SqlPostStore) GetPostAfterTime(classId string, time int64) (*model.Post, *model.AppError) {
   747  	query := s.getQueryBuilder().
   748  		Select("*").
   749  		From("Posts").
   750  		Where(sq.And{
   751  			sq.Gt{"CreateAt": time},
   752  			sq.Eq{"ClassId": classId},
   753  			sq.Eq{"DeleteAt": int(0)},
   754  		}).
   755  		// Adding ClassId and DeleteAt order columns
   756  		// to let mysql choose the "idx_posts_class_id_delete_at_create_at" index always.
   757  		// See MM-23369.
   758  		OrderBy("ClassId", "DeleteAt", "CreateAt ASC").
   759  		Limit(1)
   760  
   761  	queryString, args, err := query.ToSql()
   762  	if err != nil {
   763  		return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, err.Error(), http.StatusInternalServerError)
   764  	}
   765  
   766  	var post *model.Post
   767  	if err := s.GetMaster().SelectOne(&post, queryString, args...); err != nil {
   768  		if err != sql.ErrNoRows {
   769  			return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError)
   770  		}
   771  	}
   772  
   773  	return post, nil
   774  }
   775  
   776  func (s *SqlPostStore) getRootPosts(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   777  	var posts []*model.Post
   778  	var fetchQuery string
   779  	if skipFetchThreads {
   780  		fetchQuery = "SELECT p.*, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE ClassId = :ClassId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
   781  	} else {
   782  		fetchQuery = "SELECT * FROM Posts WHERE ClassId = :ClassId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
   783  	}
   784  	mlog.Debug(fetchQuery, mlog.Any("params", map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit}))
   785  	_, err := s.GetReplica().Select(&posts, fetchQuery, map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit})
   786  	if err != nil {
   787  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError)
   788  	}
   789  	return posts, nil
   790  }
   791  
   792  func (s *SqlPostStore) getParentsPosts(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   793  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   794  		return s.getParentsPostsPostgreSQL(classId, offset, limit, skipFetchThreads)
   795  	}
   796  
   797  	// query parent Ids first
   798  	var roots []*struct {
   799  		RootId string
   800  	}
   801  	rootQuery := `
   802  		SELECT DISTINCT
   803  			q.RootId
   804  		FROM
   805  			(SELECT
   806  				RootId
   807  			FROM
   808  				Posts
   809  			WHERE
   810  				ClassId = :ClassId
   811  					AND DeleteAt = 0
   812  			ORDER BY CreateAt DESC
   813  			LIMIT :Limit OFFSET :Offset) q
   814  		WHERE q.RootId != ''`
   815  
   816  	_, err := s.GetReplica().Select(&roots, rootQuery, map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit})
   817  	if err != nil {
   818  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError)
   819  	}
   820  	if len(roots) == 0 {
   821  		return nil, nil
   822  	}
   823  	params := make(map[string]interface{})
   824  	placeholders := make([]string, len(roots))
   825  	for idx, r := range roots {
   826  		key := fmt.Sprintf(":Root%v", idx)
   827  		params[key[1:]] = r.RootId
   828  		placeholders[idx] = key
   829  	}
   830  	placeholderString := strings.Join(placeholders, ", ")
   831  	params["ClassId"] = classId
   832  	replyCountQuery := ""
   833  	whereStatement := "p.Id IN (" + placeholderString + ")"
   834  	if skipFetchThreads {
   835  		replyCountQuery = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   836  	} else {
   837  		whereStatement += " OR p.RootId IN (" + placeholderString + ")"
   838  	}
   839  	var posts []*model.Post
   840  	_, err = s.GetReplica().Select(&posts, `
   841  		SELECT p.*`+replyCountQuery+`
   842  		FROM
   843  			Posts p
   844  		WHERE
   845  			(`+whereStatement+`)
   846  				AND ClassId = :ClassId
   847  				AND DeleteAt = 0
   848  		ORDER BY CreateAt`,
   849  		params)
   850  	if err != nil {
   851  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError)
   852  	}
   853  	return posts, nil
   854  }
   855  
   856  func (s *SqlPostStore) getParentsPostsPostgreSQL(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) {
   857  	var posts []*model.Post
   858  	replyCountQuery := ""
   859  	onStatement := "q1.RootId = q2.Id"
   860  	if skipFetchThreads {
   861  		replyCountQuery = ` ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
   862  	} else {
   863  		onStatement += " OR q1.RootId = q2.RootId"
   864  	}
   865  	_, err := s.GetReplica().Select(&posts,
   866  		`SELECT q2.*`+replyCountQuery+`
   867          FROM
   868              Posts q2
   869                  INNER JOIN
   870              (SELECT DISTINCT
   871                  q3.RootId
   872              FROM
   873                  (SELECT
   874                      RootId
   875                  FROM
   876                      Posts
   877                  WHERE
   878                      ClassId = :ClassId1
   879                          AND DeleteAt = 0
   880                  ORDER BY CreateAt DESC
   881                  LIMIT :Limit OFFSET :Offset) q3
   882              WHERE q3.RootId != '') q1
   883              ON `+onStatement+`
   884          WHERE
   885              ClassId = :ClassId2
   886                  AND DeleteAt = 0
   887          ORDER BY CreateAt`,
   888  		map[string]interface{}{"ClassId1": classId, "Offset": offset, "Limit": limit, "ClassId2": classId})
   889  	if err != nil {
   890  		return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError)
   891  	}
   892  	return posts, nil
   893  }
   894  
   895  var specialSearchChar = []string{
   896  	"<",
   897  	">",
   898  	"+",
   899  	"-",
   900  	"(",
   901  	")",
   902  	"~",
   903  	"@",
   904  	":",
   905  }
   906  
   907  func (s *SqlPostStore) buildCreateDateFilterClause(params *model.SearchParams, queryParams map[string]interface{}) (string, map[string]interface{}) {
   908  	searchQuery := ""
   909  	// handle after: before: on: filters
   910  	if len(params.OnDate) > 0 {
   911  		onDateStart, onDateEnd := params.GetOnDateMillis()
   912  		queryParams["OnDateStart"] = strconv.FormatInt(onDateStart, 10)
   913  		queryParams["OnDateEnd"] = strconv.FormatInt(onDateEnd, 10)
   914  
   915  		// between `on date` start of day and end of day
   916  		searchQuery += "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd "
   917  	} else {
   918  
   919  		if len(params.ExcludedDate) > 0 {
   920  			excludedDateStart, excludedDateEnd := params.GetExcludedDateMillis()
   921  			queryParams["ExcludedDateStart"] = strconv.FormatInt(excludedDateStart, 10)
   922  			queryParams["ExcludedDateEnd"] = strconv.FormatInt(excludedDateEnd, 10)
   923  
   924  			searchQuery += "AND CreateAt NOT BETWEEN :ExcludedDateStart AND :ExcludedDateEnd "
   925  		}
   926  
   927  		if len(params.AfterDate) > 0 {
   928  			afterDate := params.GetAfterDateMillis()
   929  			queryParams["AfterDate"] = strconv.FormatInt(afterDate, 10)
   930  
   931  			// greater than `after date`
   932  			searchQuery += "AND CreateAt >= :AfterDate "
   933  		}
   934  
   935  		if len(params.BeforeDate) > 0 {
   936  			beforeDate := params.GetBeforeDateMillis()
   937  			queryParams["BeforeDate"] = strconv.FormatInt(beforeDate, 10)
   938  
   939  			// less than `before date`
   940  			searchQuery += "AND CreateAt <= :BeforeDate "
   941  		}
   942  
   943  		if len(params.ExcludedAfterDate) > 0 {
   944  			afterDate := params.GetExcludedAfterDateMillis()
   945  			queryParams["ExcludedAfterDate"] = strconv.FormatInt(afterDate, 10)
   946  
   947  			searchQuery += "AND CreateAt < :ExcludedAfterDate "
   948  		}
   949  
   950  		if len(params.ExcludedBeforeDate) > 0 {
   951  			beforeDate := params.GetExcludedBeforeDateMillis()
   952  			queryParams["ExcludedBeforeDate"] = strconv.FormatInt(beforeDate, 10)
   953  
   954  			searchQuery += "AND CreateAt > :ExcludedBeforeDate "
   955  		}
   956  	}
   957  
   958  	return searchQuery, queryParams
   959  }
   960  
   961  func (s *SqlPostStore) buildSearchClassFilterClause(classes []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byName bool) (string, map[string]interface{}) {
   962  	if len(classes) == 0 {
   963  		return "", queryParams
   964  	}
   965  
   966  	clauseSlice := []string{}
   967  	for i, class := range classes {
   968  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
   969  		clauseSlice = append(clauseSlice, ":"+paramName)
   970  		queryParams[paramName] = class
   971  	}
   972  	clause := strings.Join(clauseSlice, ", ")
   973  	if byName {
   974  		if exclusion {
   975  			return "AND Name NOT IN (" + clause + ")", queryParams
   976  		}
   977  		return "AND Name IN (" + clause + ")", queryParams
   978  	}
   979  
   980  	if exclusion {
   981  		return "AND Id NOT IN (" + clause + ")", queryParams
   982  	}
   983  	return "AND Id IN (" + clause + ")", queryParams
   984  }
   985  
   986  func (s *SqlPostStore) buildSearchUserFilterClause(users []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byUsername bool) (string, map[string]interface{}) {
   987  	if len(users) == 0 {
   988  		return "", queryParams
   989  	}
   990  	clauseSlice := []string{}
   991  	for i, user := range users {
   992  		paramName := paramPrefix + strconv.FormatInt(int64(i), 10)
   993  		clauseSlice = append(clauseSlice, ":"+paramName)
   994  		queryParams[paramName] = user
   995  	}
   996  	clause := strings.Join(clauseSlice, ", ")
   997  	if byUsername {
   998  		if exclusion {
   999  			return "AND Username NOT IN (" + clause + ")", queryParams
  1000  		}
  1001  		return "AND Username IN (" + clause + ")", queryParams
  1002  	}
  1003  	if exclusion {
  1004  		return "AND Id NOT IN (" + clause + ")", queryParams
  1005  	}
  1006  	return "AND Id IN (" + clause + ")", queryParams
  1007  }
  1008  
  1009  func (s *SqlPostStore) buildSearchPostFilterClause(fromUsers []string, excludedUsers []string, queryParams map[string]interface{}, userByUsername bool) (string, map[string]interface{}) {
  1010  	if len(fromUsers) == 0 && len(excludedUsers) == 0 {
  1011  		return "", queryParams
  1012  	}
  1013  
  1014  	filterQuery := `
  1015  		AND UserId IN (
  1016  			SELECT
  1017  				Id
  1018  			FROM
  1019  				Users,
  1020  				BranchMembers
  1021  			WHERE
  1022  				BranchMembers.BranchId = :BranchId
  1023  				AND Users.Id = BranchMembers.UserId
  1024  				FROM_USER_FILTER
  1025  				EXCLUDED_USER_FILTER)`
  1026  
  1027  	fromUserClause, queryParams := s.buildSearchUserFilterClause(fromUsers, "FromUser", false, queryParams, userByUsername)
  1028  	filterQuery = strings.Replace(filterQuery, "FROM_USER_FILTER", fromUserClause, 1)
  1029  
  1030  	excludedUserClause, queryParams := s.buildSearchUserFilterClause(excludedUsers, "ExcludedUser", true, queryParams, userByUsername)
  1031  	filterQuery = strings.Replace(filterQuery, "EXCLUDED_USER_FILTER", excludedUserClause, 1)
  1032  
  1033  	return filterQuery, queryParams
  1034  }
  1035  
  1036  func (s *SqlPostStore) Search(branchId string, userId string, params *model.SearchParams) (*model.PostList, *model.AppError) {
  1037  	return s.search(branchId, userId, params, true, true)
  1038  }
  1039  
  1040  func (s *SqlPostStore) search(branchId string, userId string, params *model.SearchParams, classesByName bool, userByUsername bool) (*model.PostList, *model.AppError) {
  1041  	queryParams := map[string]interface{}{
  1042  		"BranchId": branchId,
  1043  		"UserId":   userId,
  1044  	}
  1045  
  1046  	list := model.NewPostList()
  1047  	if params.Terms == "" && params.ExcludedTerms == "" &&
  1048  		len(params.InClasses) == 0 && len(params.ExcludedClasses) == 0 &&
  1049  		len(params.FromUsers) == 0 && len(params.ExcludedUsers) == 0 &&
  1050  		len(params.OnDate) == 0 && len(params.AfterDate) == 0 && len(params.BeforeDate) == 0 {
  1051  		return list, nil
  1052  	}
  1053  
  1054  	var posts []*model.Post
  1055  
  1056  	deletedQueryPart := "AND DeleteAt = 0"
  1057  	if params.IncludeDeletedClasses {
  1058  		deletedQueryPart = ""
  1059  	}
  1060  
  1061  	userIdPart := "AND UserId = :UserId"
  1062  	if params.SearchWithoutUserId {
  1063  		userIdPart = ""
  1064  	}
  1065  
  1066  	searchQuery := `
  1067  			SELECT
  1068  				* ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount
  1069  			FROM
  1070  				Posts q2
  1071  			WHERE
  1072  				DeleteAt = 0
  1073  				AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
  1074  				POST_FILTER
  1075  				AND ClassId IN (
  1076  					SELECT
  1077  						Id
  1078  					FROM
  1079  						Classes,
  1080  						ClassMembers
  1081  					WHERE
  1082  						Id = ClassId
  1083  							AND (BranchId = :BranchId OR BranchId = '')
  1084  							` + userIdPart + `
  1085  							` + deletedQueryPart + `
  1086  							IN_CLASS_FILTER
  1087  							EXCLUDED_CLASS_FILTER)
  1088  				CREATEDATE_CLAUSE
  1089  				SEARCH_CLAUSE
  1090  				ORDER BY CreateAt DESC
  1091  			LIMIT 100`
  1092  
  1093  	inClassClause, queryParams := s.buildSearchClassFilterClause(params.InClasses, "InClass", false, queryParams, classesByName)
  1094  	searchQuery = strings.Replace(searchQuery, "IN_CLASS_FILTER", inClassClause, 1)
  1095  
  1096  	excludedClassClause, queryParams := s.buildSearchClassFilterClause(params.ExcludedClasses, "ExcludedClass", true, queryParams, classesByName)
  1097  	searchQuery = strings.Replace(searchQuery, "EXCLUDED_CLASS_FILTER", excludedClassClause, 1)
  1098  
  1099  	postFilterClause, queryParams := s.buildSearchPostFilterClause(params.FromUsers, params.ExcludedUsers, queryParams, userByUsername)
  1100  	searchQuery = strings.Replace(searchQuery, "POST_FILTER", postFilterClause, 1)
  1101  
  1102  	createDateFilterClause, queryParams := s.buildCreateDateFilterClause(params, queryParams)
  1103  	searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", createDateFilterClause, 1)
  1104  
  1105  	termMap := map[string]bool{}
  1106  	terms := params.Terms
  1107  	excludedTerms := params.ExcludedTerms
  1108  
  1109  	searchType := "Message"
  1110  	if params.IsHashtag {
  1111  		searchType = "Hashtags"
  1112  		for _, term := range strings.Split(terms, " ") {
  1113  			termMap[strings.ToUpper(term)] = true
  1114  		}
  1115  	}
  1116  
  1117  	// these chars have special meaning and can be treated as spaces
  1118  	for _, c := range specialSearchChar {
  1119  		terms = strings.Replace(terms, c, " ", -1)
  1120  		excludedTerms = strings.Replace(excludedTerms, c, " ", -1)
  1121  	}
  1122  
  1123  	if terms == "" && excludedTerms == "" {
  1124  		// we've already confirmed that we have a class or user to search for
  1125  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
  1126  	} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1127  		// Parse text for wildcards
  1128  		if wildcard, err := regexp.Compile(`\*($| )`); err == nil {
  1129  			terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
  1130  			excludedTerms = wildcard.ReplaceAllLiteralString(excludedTerms, ":* ")
  1131  		}
  1132  
  1133  		excludeClause := ""
  1134  		if excludedTerms != "" {
  1135  			excludeClause = " & !(" + strings.Join(strings.Fields(excludedTerms), " | ") + ")"
  1136  		}
  1137  
  1138  		if params.OrTerms {
  1139  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " | ") + ")" + excludeClause
  1140  		} else {
  1141  			queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " & ") + ")" + excludeClause
  1142  		}
  1143  
  1144  		searchClause := fmt.Sprintf("AND to_tsvector('english', %s) @@  to_tsquery('english', :Terms)", searchType)
  1145  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1146  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1147  		searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
  1148  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
  1149  
  1150  		excludeClause := ""
  1151  		if excludedTerms != "" {
  1152  			excludeClause = " -(" + excludedTerms + ")"
  1153  		}
  1154  
  1155  		if params.OrTerms {
  1156  			queryParams["Terms"] = terms + excludeClause
  1157  		} else {
  1158  			splitTerms := []string{}
  1159  			for _, t := range strings.Fields(terms) {
  1160  				splitTerms = append(splitTerms, "+"+t)
  1161  			}
  1162  			queryParams["Terms"] = strings.Join(splitTerms, " ") + excludeClause
  1163  		}
  1164  	}
  1165  
  1166  	_, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams)
  1167  	if err != nil {
  1168  		mlog.Warn("Query error searching posts.", mlog.Err(err))
  1169  		// 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.
  1170  	} else {
  1171  		for _, p := range posts {
  1172  			if searchType == "Hashtags" {
  1173  				exactMatch := false
  1174  				for _, tag := range strings.Split(p.Hashtags, " ") {
  1175  					if termMap[strings.ToUpper(tag)] {
  1176  						exactMatch = true
  1177  						break
  1178  					}
  1179  				}
  1180  				if !exactMatch {
  1181  					continue
  1182  				}
  1183  			}
  1184  			list.AddPost(p)
  1185  			list.AddOrder(p.Id)
  1186  		}
  1187  	}
  1188  	list.MakeNonNil()
  1189  	return list, nil
  1190  }
  1191  
  1192  func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(branchId string) (model.AnalyticsRows, *model.AppError) {
  1193  	query :=
  1194  		`SELECT DISTINCT
  1195  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1196  		        COUNT(DISTINCT Posts.UserId) AS Value
  1197  		FROM Posts`
  1198  
  1199  	if len(branchId) > 0 {
  1200  		query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND"
  1201  	} else {
  1202  		query += " WHERE"
  1203  	}
  1204  
  1205  	query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1206  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1207  		ORDER BY Name DESC
  1208  		LIMIT 30`
  1209  
  1210  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1211  		query =
  1212  			`SELECT
  1213  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
  1214  			FROM Posts`
  1215  
  1216  		if len(branchId) > 0 {
  1217  			query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND"
  1218  		} else {
  1219  			query += " WHERE"
  1220  		}
  1221  
  1222  		query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
  1223  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1224  			ORDER BY Name DESC
  1225  			LIMIT 30`
  1226  	}
  1227  
  1228  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1229  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1230  
  1231  	var rows model.AnalyticsRows
  1232  	_, err := s.GetReplica().Select(
  1233  		&rows,
  1234  		query,
  1235  		map[string]interface{}{"BranchId": branchId, "StartTime": start, "EndTime": end})
  1236  	if err != nil {
  1237  		return nil, model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1238  	}
  1239  	return rows, nil
  1240  }
  1241  
  1242  func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCountsOptions) (model.AnalyticsRows, *model.AppError) {
  1243  
  1244  	query :=
  1245  		`SELECT
  1246  		        DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
  1247  		        COUNT(Posts.Id) AS Value
  1248  		    FROM Posts`
  1249  
  1250  	if len(options.BranchId) > 0 {
  1251  		query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND"
  1252  	} else {
  1253  		query += " WHERE"
  1254  	}
  1255  
  1256  	query += ` Posts.CreateAt <= :EndTime
  1257  		            AND Posts.CreateAt >= :StartTime
  1258  		GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
  1259  		ORDER BY Name DESC
  1260  		LIMIT 30`
  1261  
  1262  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1263  		query =
  1264  			`SELECT
  1265  				TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
  1266  			FROM Posts`
  1267  
  1268  		if len(options.BranchId) > 0 {
  1269  			query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id  AND Classes.BranchId = :BranchId AND"
  1270  		} else {
  1271  			query += " WHERE"
  1272  		}
  1273  
  1274  		query += ` Posts.CreateAt <= :EndTime
  1275  			            AND Posts.CreateAt >= :StartTime
  1276  			GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
  1277  			ORDER BY Name DESC
  1278  			LIMIT 30`
  1279  	}
  1280  
  1281  	end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
  1282  	start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
  1283  	if options.YesterdayOnly {
  1284  		start = utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -1)))
  1285  	}
  1286  
  1287  	var rows model.AnalyticsRows
  1288  	_, err := s.GetReplica().Select(
  1289  		&rows,
  1290  		query,
  1291  		map[string]interface{}{"BranchId": options.BranchId, "StartTime": start, "EndTime": end})
  1292  	if err != nil {
  1293  		return nil, model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
  1294  	}
  1295  	return rows, nil
  1296  }
  1297  
  1298  func (s *SqlPostStore) AnalyticsPostCount(branchId string, mustHaveFile bool, mustHaveHashtag bool) (int64, *model.AppError) {
  1299  	query :=
  1300  		`SELECT
  1301  			COUNT(Posts.Id) AS Value
  1302  		FROM
  1303  			Posts,
  1304  			Classes
  1305  		WHERE
  1306  			Posts.ClassId = Classes.Id`
  1307  
  1308  	if len(branchId) > 0 {
  1309  		query += " AND Classes.BranchId = :BranchId"
  1310  	}
  1311  
  1312  	if mustHaveFile {
  1313  		query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')"
  1314  	}
  1315  
  1316  	if mustHaveHashtag {
  1317  		query += " AND Posts.Hashtags != ''"
  1318  	}
  1319  
  1320  	v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"BranchId": branchId})
  1321  	if err != nil {
  1322  		return 0, model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError)
  1323  	}
  1324  
  1325  	return v, nil
  1326  }
  1327  
  1328  func (s *SqlPostStore) GetPostsCreatedAt(classId string, time int64) ([]*model.Post, *model.AppError) {
  1329  	query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ClassId = :ClassId`
  1330  
  1331  	var posts []*model.Post
  1332  	_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ClassId": classId})
  1333  
  1334  	if err != nil {
  1335  		return nil, model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError)
  1336  	}
  1337  	return posts, nil
  1338  }
  1339  
  1340  func (s *SqlPostStore) GetPostsByIds(postIds []string) ([]*model.Post, *model.AppError) {
  1341  	keys, params := MapStringsToQueryParams(postIds, "Post")
  1342  
  1343  	query := `SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id IN ` + keys + ` ORDER BY CreateAt DESC`
  1344  
  1345  	var posts []*model.Post
  1346  	_, err := s.GetReplica().Select(&posts, query, params)
  1347  
  1348  	if err != nil {
  1349  		mlog.Error("Query error getting posts.", mlog.Err(err))
  1350  		return nil, model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError)
  1351  	}
  1352  	return posts, nil
  1353  }
  1354  
  1355  func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, *model.AppError) {
  1356  	var posts []*model.PostForIndexing
  1357  	_, err := s.GetSearchReplica().Select(&posts,
  1358  		`SELECT
  1359  			PostsQuery.*, Classes.BranchId, ParentPosts.CreateAt ParentCreateAt
  1360  		FROM (
  1361  			SELECT
  1362  				*
  1363  			FROM
  1364  				Posts
  1365  			WHERE
  1366  				Posts.CreateAt >= :StartTime
  1367  			AND
  1368  				Posts.CreateAt < :EndTime
  1369  			ORDER BY
  1370  				CreateAt ASC
  1371  			LIMIT
  1372  				1000
  1373  			)
  1374  		AS
  1375  			PostsQuery
  1376  		LEFT JOIN
  1377  			Classes
  1378  		ON
  1379  			PostsQuery.ClassId = Classes.Id
  1380  		LEFT JOIN
  1381  			Posts ParentPosts
  1382  		ON
  1383  			PostsQuery.RootId = ParentPosts.Id`,
  1384  		map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit})
  1385  
  1386  	if err != nil {
  1387  		return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err.Error(), http.StatusInternalServerError)
  1388  	}
  1389  	return posts, nil
  1390  }
  1391  
  1392  func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) {
  1393  	var query string
  1394  	if s.DriverName() == "postgres" {
  1395  		query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))"
  1396  	} else {
  1397  		query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit"
  1398  	}
  1399  
  1400  	sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit})
  1401  	if err != nil {
  1402  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1403  	}
  1404  
  1405  	rowsAffected, err := sqlResult.RowsAffected()
  1406  	if err != nil {
  1407  		return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError)
  1408  	}
  1409  	return rowsAffected, nil
  1410  }
  1411  
  1412  func (s *SqlPostStore) GetOldest() (*model.Post, *model.AppError) {
  1413  	var post model.Post
  1414  	err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1")
  1415  	if err != nil {
  1416  		return nil, model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound)
  1417  	}
  1418  
  1419  	return &post, nil
  1420  }
  1421  
  1422  func (s *SqlPostStore) determineMaxPostSize() int {
  1423  	var maxPostSizeBytes int32
  1424  
  1425  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1426  		// The Post.Message column in Postgres has historically been VARCHAR(4000), but
  1427  		// may be manually enlarged to support longer posts.
  1428  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1429  			SELECT
  1430  				COALESCE(character_maximum_length, 0)
  1431  			FROM
  1432  				information_schema.columns
  1433  			WHERE
  1434  				table_name = 'posts'
  1435  			AND	column_name = 'message'
  1436  		`); err != nil {
  1437  			mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err))
  1438  		}
  1439  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1440  		// The Post.Message column in MySQL has historically been TEXT, with a maximum
  1441  		// limit of 65535.
  1442  		if err := s.GetReplica().SelectOne(&maxPostSizeBytes, `
  1443  			SELECT
  1444  				COALESCE(CHARACTER_MAXIMUM_LENGTH, 0)
  1445  			FROM
  1446  				INFORMATION_SCHEMA.COLUMNS
  1447  			WHERE
  1448  				table_schema = DATABASE()
  1449  			AND	table_name = 'Posts'
  1450  			AND	column_name = 'Message'
  1451  			LIMIT 0, 1
  1452  		`); err != nil {
  1453  			mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err))
  1454  		}
  1455  	} else {
  1456  		mlog.Warn("No implementation found to determine the maximum supported post size")
  1457  	}
  1458  
  1459  	// Assume a worst-case representation of four bytes per rune.
  1460  	maxPostSize := int(maxPostSizeBytes) / 4
  1461  
  1462  	// To maintain backwards compatibility, don't yield a maximum post
  1463  	// size smaller than the previous limit, even though it wasn't
  1464  	// actually possible to store 4000 runes in all cases.
  1465  	if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 {
  1466  		maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1
  1467  	}
  1468  
  1469  	mlog.Info("Post.Message has size restrictions", mlog.Int("max_characters", maxPostSize), mlog.Int32("max_bytes", maxPostSizeBytes))
  1470  
  1471  	return maxPostSize
  1472  }
  1473  
  1474  // GetMaxPostSize returns the maximum number of runes that may be stored in a post.
  1475  func (s *SqlPostStore) GetMaxPostSize() int {
  1476  	s.maxPostSizeOnce.Do(func() {
  1477  		s.maxPostSizeCached = s.determineMaxPostSize()
  1478  	})
  1479  	return s.maxPostSizeCached
  1480  }