MM-21552: Adding SaveMultiple to posts (#13766)
* Adding SaveMultiple to posts * Improving tests * fixing i18n * Fixing tests * Improving testing on top of Save and SaveMultiple * Fixing shadow variables * Addressing some PR comments * More clear update post test * Addressing some PR comments * Addressing some PR comments and simplifying the code * Improting replies in bulk too * Fixing reply count and processing last imported replies * Adding OverwriteMultiple to posts aggregating everything in the same transaction * Adding 2 pending tests to implement * Adding tests for overwrite multiple posts * Adding tests for TeamStore.GetByNames method * Fixing shadow variables * Addressing PR comments * Extracting i18n strings * Fixing tests * Fixing tests * Adding more test cases * Using a variable instead of a fake timestamp
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
2bec92a404
Коммит
27d536b212
@@ -30,6 +30,33 @@ type SqlPostStore struct {
|
||||
func (s *SqlPostStore) ClearCaches() {
|
||||
}
|
||||
|
||||
func postSliceColumns() []string {
|
||||
return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "IsPinned", "UserId", "ChannelId", "RootId", "ParentId", "OriginalId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions"}
|
||||
}
|
||||
|
||||
func postToSlice(post *model.Post) []interface{} {
|
||||
return []interface{}{
|
||||
post.Id,
|
||||
post.CreateAt,
|
||||
post.UpdateAt,
|
||||
post.EditAt,
|
||||
post.DeleteAt,
|
||||
post.IsPinned,
|
||||
post.UserId,
|
||||
post.ChannelId,
|
||||
post.RootId,
|
||||
post.ParentId,
|
||||
post.OriginalId,
|
||||
post.Message,
|
||||
post.Type,
|
||||
model.StringInterfaceToJson(post.Props),
|
||||
post.Hashtags,
|
||||
model.ArrayToJson(post.Filenames),
|
||||
model.ArrayToJson(post.FileIds),
|
||||
post.HasReactions,
|
||||
}
|
||||
}
|
||||
|
||||
func newSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
|
||||
s := &SqlPostStore{
|
||||
SqlStore: sqlStore,
|
||||
@@ -72,48 +99,97 @@ func (s *SqlPostStore) createIndexesIfNotExists() {
|
||||
s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
|
||||
if len(post.Id) > 0 {
|
||||
return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
maxPostSize := s.GetMaxPostSize()
|
||||
|
||||
post.PreSave()
|
||||
if err := post.IsValid(maxPostSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.GetMaster().Insert(post); err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
time := post.UpdateAt
|
||||
|
||||
if !post.IsJoinLeaveMessage() {
|
||||
if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + 1 WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}); err != nil {
|
||||
mlog.Error("Error updating Channel LastPostAt.", mlog.Err(err))
|
||||
func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) {
|
||||
channelNewPosts := make(map[string]int)
|
||||
maxDateNewPosts := make(map[string]int64)
|
||||
rootIds := make(map[string]int)
|
||||
maxDateRootIds := make(map[string]int64)
|
||||
for _, post := range posts {
|
||||
if len(post.Id) > 0 {
|
||||
return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest)
|
||||
}
|
||||
} else {
|
||||
// don't update TotalMsgCount for unimportant messages so that the channel isn't marked as unread
|
||||
if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}); err != nil {
|
||||
post.PreSave()
|
||||
maxPostSize := s.GetMaxPostSize()
|
||||
if err := post.IsValid(maxPostSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentChannelCount, ok := channelNewPosts[post.ChannelId]
|
||||
if !ok {
|
||||
if post.IsJoinLeaveMessage() {
|
||||
channelNewPosts[post.ChannelId] = 0
|
||||
} else {
|
||||
channelNewPosts[post.ChannelId] = 1
|
||||
}
|
||||
maxDateNewPosts[post.ChannelId] = post.CreateAt
|
||||
} else {
|
||||
if !post.IsJoinLeaveMessage() {
|
||||
channelNewPosts[post.ChannelId] = currentChannelCount + 1
|
||||
}
|
||||
if post.CreateAt > maxDateNewPosts[post.ChannelId] {
|
||||
maxDateNewPosts[post.ChannelId] = post.CreateAt
|
||||
}
|
||||
}
|
||||
|
||||
if len(post.RootId) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
currentRootCount, ok := rootIds[post.RootId]
|
||||
if !ok {
|
||||
rootIds[post.RootId] = 1
|
||||
maxDateRootIds[post.RootId] = post.CreateAt
|
||||
} else {
|
||||
rootIds[post.RootId] = currentRootCount + 1
|
||||
if post.CreateAt > maxDateRootIds[post.RootId] {
|
||||
maxDateRootIds[post.RootId] = post.CreateAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().Insert("Posts").Columns(postSliceColumns()...)
|
||||
for _, post := range posts {
|
||||
query = query.Values(postToSlice(post)...)
|
||||
}
|
||||
sql, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Exec(sql, args...); err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for channelId, count := range channelNewPosts {
|
||||
if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": maxDateNewPosts[channelId], "ChannelId": channelId, "Count": count}); err != nil {
|
||||
mlog.Error("Error updating Channel LastPostAt.", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(post.RootId) > 0 {
|
||||
if _, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": post.RootId}); err != nil {
|
||||
for rootId := range rootIds {
|
||||
if _, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": maxDateRootIds[rootId], "RootId": rootId}); err != nil {
|
||||
mlog.Error("Error updating Post UpdateAt.", mlog.Err(err))
|
||||
}
|
||||
} else {
|
||||
if count, err := s.GetMaster().SelectInt("SELECT COUNT(*) FROM Posts WHERE RootId = :Id", map[string]interface{}{"Id": post.Id}); err != nil {
|
||||
mlog.Error("Error fetching post's thread.", mlog.Err(err))
|
||||
} else {
|
||||
post.ReplyCount = count
|
||||
}
|
||||
|
||||
for _, post := range posts {
|
||||
if len(post.RootId) == 0 {
|
||||
count, ok := rootIds[post.Id]
|
||||
if ok {
|
||||
post.ReplyCount += int64(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return post, nil
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
|
||||
posts, err := s.SaveMultiple([]*model.Post{post})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return posts[0], nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, *model.AppError) {
|
||||
@@ -149,19 +225,45 @@ func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.
|
||||
return newPost, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) {
|
||||
post.UpdateAt = model.GetMillis()
|
||||
|
||||
func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) {
|
||||
updateAt := model.GetMillis()
|
||||
maxPostSize := s.GetMaxPostSize()
|
||||
if appErr := post.IsValid(maxPostSize); appErr != nil {
|
||||
return nil, appErr
|
||||
for _, post := range posts {
|
||||
post.UpdateAt = updateAt
|
||||
if appErr := post.IsValid(maxPostSize); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Update(post); err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
tx, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
for _, post := range posts {
|
||||
if _, err = tx.Update(post); err != nil {
|
||||
txErr := tx.Rollback()
|
||||
if txErr != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, txErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return post, nil
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) {
|
||||
posts, err := s.OverwriteMultiple([]*model.Post{post})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts[0], nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -287,6 +288,33 @@ func (s SqlTeamStore) GetByName(name string) (*model.Team, *model.AppError) {
|
||||
return &team, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetByNames(names []string) ([]*model.Team, *model.AppError) {
|
||||
uniqueNames := utils.RemoveDuplicatesFromStringArray(names)
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("Teams").
|
||||
Where(sq.Eq{"Name": uniqueNames})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlTeamStore.GetByNames", "store.sql_team.get_by_names.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
teams := []*model.Team{}
|
||||
_, err = s.GetReplica().Select(&teams, queryString, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, model.NewAppError("SqlTeamStore.GetByNames", "store.sql_team.get_by_names.missing.app_error", nil, err.Error(), http.StatusNotFound)
|
||||
}
|
||||
return nil, model.NewAppError("SqlTeamStore.GetByNames", "store.sql_team.get_by_names.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
if len(teams) != len(uniqueNames) {
|
||||
return nil, model.NewAppError("SqlTeamStore.GetByNames", "store.sql_team.get_by_names.missing.app_error", nil, "", http.StatusNotFound)
|
||||
}
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
|
||||
var teams []*model.Team
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user