From ab5925c4def669816e2d16caf135811d0566311a Mon Sep 17 00:00:00 2001 From: Eli Yukelzon Date: Wed, 31 Mar 2021 16:51:02 +0300 Subject: [PATCH] MM-33746 Add TotalMsgCountRoot and MsgCountRoot columns (#17150) --- .circleci/config.yml | 8 ++ app/channel.go | 1 + app/post.go | 2 +- app/team.go | 5 + model/channel.go | 37 +++--- model/channel_count.go | 3 +- model/channel_member.go | 3 + model/team_member.go | 1 + scripts/mysql-migration-test.sh | 9 +- scripts/psql-migration-test.sh | 7 ++ store/opentracinglayer/opentracinglayer.go | 6 +- store/retrylayer/retrylayer.go | 10 +- store/sqlstore/channel_store.go | 92 ++++++++------ store/sqlstore/group_store.go | 1 + store/sqlstore/post_store.go | 22 +++- store/sqlstore/team_store.go | 4 +- store/sqlstore/upgrade.go | 51 ++++++++ store/sqlstore/upgrade_test.go | 138 +++++++++++++++++++++ store/store.go | 2 +- store/storetest/channel_store.go | 34 ++--- store/storetest/mocks/ChannelStore.go | 17 ++- store/timerlayer/timerlayer.go | 6 +- 22 files changed, 356 insertions(+), 103 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c5e9e62a02..4c79511674 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -320,6 +320,10 @@ jobs: mattermost/mattermost-build-server:20201119_golang-1.15.5 \ bash -c "ulimit -n 8096; make ARGS='version' run-cli && make MM_SQLSETTINGS_DATASOURCE='postgres://mmuser:mostest@postgres:5432/latest?sslmode=disable&connect_timeout=10' ARGS='version' run-cli" + echo "Ignoring known mismatch: ChannelMembers.MsgCountRoot" + docker-compose --no-ansi exec -T postgres sh -c 'exec echo "ALTER TABLE ChannelMembers DROP COLUMN MsgCountRoot;" | exec psql -U mmuser -d migrated' + docker-compose --no-ansi exec -T postgres sh -c 'exec echo "ALTER TABLE ChannelMembers DROP COLUMN MsgCountRoot;" | exec psql -U mmuser -d latest' + echo "Generating dump" docker-compose --no-ansi exec -T postgres pg_dump --schema-only -d migrated -U mmuser > migrated.sql docker-compose --no-ansi exec -T postgres pg_dump --schema-only -d latest -U mmuser > latest.sql @@ -349,9 +353,13 @@ jobs: mattermost/mattermost-build-server:20201119_golang-1.15.5 \ bash -c "ulimit -n 8096; make ARGS='version' run-cli && make MM_SQLSETTINGS_DATASOURCE='mmuser:mostest@tcp(mysql:3306)/latest?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s' ARGS='version' run-cli" + echo "Ignoring known MySQL mismatch: ChannelMembers.SchemeGuest" docker-compose --no-ansi exec -T mysql mysql -D migrated -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN SchemeGuest;" docker-compose --no-ansi exec -T mysql mysql -D latest -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN SchemeGuest;" + echo "Ignoring known MySQL mismatch: ChannelMembers.MentionCountRoot" + docker-compose --no-ansi exec -T mysql mysql -D migrated -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN MsgCountRoot;" + docker-compose --no-ansi exec -T mysql mysql -D latest -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN MsgCountRoot;" echo "Generating dump" docker-compose --no-ansi exec -T mysql mysqldump --skip-opt --no-data --compact -u root -pmostest migrated > migrated.sql diff --git a/app/channel.go b/app/channel.go index 99a692311e..279f4f752c 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1871,6 +1871,7 @@ func (a *App) GetChannelUnread(channelID, userID string) (*model.ChannelUnread, if channelUnread.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] == model.CHANNEL_MARK_UNREAD_MENTION { channelUnread.MsgCount = 0 + channelUnread.MsgCountRoot = 0 } return channelUnread, nil diff --git a/app/post.go b/app/post.go index e1f9768e86..ff8756c787 100644 --- a/app/post.go +++ b/app/post.go @@ -1411,7 +1411,7 @@ func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *m if channel.Type == model.CHANNEL_DIRECT { // In a DM channel, every post made by the other user is a mention - count, nErr := a.Srv().Store.Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id)) + count, _, nErr := a.Srv().Store.Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id)) if nErr != nil { return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError) } diff --git a/app/team.go b/app/team.go index f4942d9b3d..42f4bb4c3b 100644 --- a/app/team.go +++ b/app/team.go @@ -1133,12 +1133,14 @@ func (a *App) AddTeamMemberByInviteId(inviteId, userID string) (*model.TeamMembe func (a *App) GetTeamUnread(teamID, userID string) (*model.TeamUnread, *model.AppError) { channelUnreads, err := a.Srv().Store.Team().GetChannelUnreadsForTeam(teamID, userID) + if err != nil { return nil, model.NewAppError("GetTeamUnread", "app.team.get_unread.app_error", nil, err.Error(), http.StatusInternalServerError) } var teamUnread = &model.TeamUnread{ MsgCount: 0, + MsgCountRoot: 0, MentionCount: 0, TeamId: teamID, } @@ -1148,6 +1150,7 @@ func (a *App) GetTeamUnread(teamID, userID string) (*model.TeamUnread, *model.Ap if cu.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_MENTION { teamUnread.MsgCount += cu.MsgCount + teamUnread.MsgCountRoot += cu.MsgCountRoot } } @@ -1677,6 +1680,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string) ([]*mod if cu.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_MENTION { tu.MsgCount += cu.MsgCount + tu.MsgCountRoot += cu.MsgCountRoot } return tu @@ -1689,6 +1693,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string) ([]*mod } else { membersMap[id] = unreads(data[i], &model.TeamUnread{ MsgCount: 0, + MsgCountRoot: 0, MentionCount: 0, TeamId: id, }) diff --git a/model/channel.go b/model/channel.go index 4a62ea4140..c7a0a1bfc7 100644 --- a/model/channel.go +++ b/model/channel.go @@ -34,24 +34,25 @@ const ( ) type Channel struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - TeamId string `json:"team_id"` - Type string `json:"type"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - Header string `json:"header"` - Purpose string `json:"purpose"` - LastPostAt int64 `json:"last_post_at"` - TotalMsgCount int64 `json:"total_msg_count"` - ExtraUpdateAt int64 `json:"extra_update_at"` - CreatorId string `json:"creator_id"` - SchemeId *string `json:"scheme_id"` - Props map[string]interface{} `json:"props" db:"-"` - GroupConstrained *bool `json:"group_constrained"` - Shared *bool `json:"shared"` + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + TeamId string `json:"team_id"` + Type string `json:"type"` + DisplayName string `json:"display_name"` + Name string `json:"name"` + Header string `json:"header"` + Purpose string `json:"purpose"` + LastPostAt int64 `json:"last_post_at"` + TotalMsgCount int64 `json:"total_msg_count"` + ExtraUpdateAt int64 `json:"extra_update_at"` + CreatorId string `json:"creator_id"` + SchemeId *string `json:"scheme_id"` + Props map[string]interface{} `json:"props" db:"-"` + GroupConstrained *bool `json:"group_constrained"` + Shared *bool `json:"shared"` + TotalMsgCountRoot int64 `json:"total_msg_count_root"` } type ChannelWithTeamData struct { diff --git a/model/channel_count.go b/model/channel_count.go index 11ddeec407..6230ab3d53 100644 --- a/model/channel_count.go +++ b/model/channel_count.go @@ -14,11 +14,12 @@ import ( type ChannelCounts struct { Counts map[string]int64 `json:"counts"` + CountsRoot map[string]int64 `json:"counts_root"` UpdateTimes map[string]int64 `json:"update_times"` } func (o *ChannelCounts) Etag() string { - + // we don't include CountsRoot in ETag calculation, since it's a deriviative ids := []string{} for id := range o.Counts { ids = append(ids, id) diff --git a/model/channel_member.go b/model/channel_member.go index 567492bfd0..5e1beee2ac 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -27,6 +27,7 @@ type ChannelUnread struct { TeamId string `json:"team_id"` ChannelId string `json:"channel_id"` MsgCount int64 `json:"msg_count"` + MsgCountRoot int64 `json:"msg_count_root"` MentionCount int64 `json:"mention_count"` NotifyProps StringMap `json:"-"` } @@ -36,6 +37,7 @@ type ChannelUnreadAt struct { UserId string `json:"user_id"` ChannelId string `json:"channel_id"` MsgCount int64 `json:"msg_count"` + MsgCountRoot int64 `json:"msg_count_root"` MentionCount int64 `json:"mention_count"` LastViewedAt int64 `json:"last_viewed_at"` NotifyProps StringMap `json:"-"` @@ -54,6 +56,7 @@ type ChannelMember struct { SchemeUser bool `json:"scheme_user"` SchemeAdmin bool `json:"scheme_admin"` ExplicitRoles string `json:"explicit_roles"` + MsgCountRoot int64 `json:"msg_count_root"` } type ChannelMembers []ChannelMember diff --git a/model/team_member.go b/model/team_member.go index b74010d1ae..86187db2bc 100644 --- a/model/team_member.go +++ b/model/team_member.go @@ -33,6 +33,7 @@ type TeamMember struct { type TeamUnread struct { TeamId string `json:"team_id"` MsgCount int64 `json:"msg_count"` + MsgCountRoot int64 `json:"msg_count_root"` MentionCount int64 `json:"mention_count"` } diff --git a/scripts/mysql-migration-test.sh b/scripts/mysql-migration-test.sh index dbab53360c..90b69a8955 100755 --- a/scripts/mysql-migration-test.sh +++ b/scripts/mysql-migration-test.sh @@ -22,9 +22,12 @@ make ARGS="config set SqlSettings.DataSource 'mmuser:mostest@tcp(localhost:3306) echo "Setting up fresh db" make ARGS="version --config $TMPDIR/config.json" run-cli -echo "Ignoring known MySQL mismatch: ChannelMembers.SchemeGuest" -docker exec mattermost-mysql mysql -D migrated -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN SchemeGuest;" -docker exec mattermost-mysql mysql -D latest -uroot -pmostest -e "ALTER TABLE ChannelMembers DROP COLUMN SchemeGuest;" +for i in "ChannelMembers SchemeGuest" "ChannelMembers MsgCountRoot"; do + a=( $i ); + echo "Ignoring known MySQL mismatch: ${a[0]}.${a[1]}" + docker exec mattermost-mysql mysql -D migrated -uroot -pmostest -e "ALTER TABLE ${a[0]} DROP COLUMN ${a[1]};" + docker exec mattermost-mysql mysql -D latest -uroot -pmostest -e "ALTER TABLE ${a[0]} DROP COLUMN ${a[1]};" +done echo "Generating dump" docker exec mattermost-mysql mysqldump --skip-opt --no-data --compact -u root -pmostest migrated > $DUMPDIR/migrated.sql diff --git a/scripts/psql-migration-test.sh b/scripts/psql-migration-test.sh index e03385d50f..368eed0b45 100755 --- a/scripts/psql-migration-test.sh +++ b/scripts/psql-migration-test.sh @@ -22,6 +22,13 @@ make ARGS="config set SqlSettings.DataSource 'postgres://mmuser:mostest@localhos echo "Setting up fresh db" make ARGS="version --config $TMPDIR/config.json" run-cli +for i in "ChannelMembers MsgCountRoot"; do + a=( $i ); + echo "Ignoring known Postgres mismatch: ${a[0]}.${a[1]}" + docker exec mattermost-postgres psql -U mmuser -d migrated -c "ALTER TABLE ${a[0]} DROP COLUMN ${a[1]};" + docker exec mattermost-postgres psql -U mmuser -d latest -c "ALTER TABLE ${a[0]} DROP COLUMN ${a[1]};" +done + echo "Generating dump" docker exec mattermost-postgres pg_dump --schema-only -d migrated -U mmuser > $DUMPDIR/migrated.sql docker exec mattermost-postgres pg_dump --schema-only -d latest -U mmuser > $DUMPDIR/latest.sql diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 1fc7fa7fda..01b3ff45cc 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -625,7 +625,7 @@ func (s *OpenTracingLayerChannelStore) ClearSidebarOnTeamLeave(userId string, te return err } -func (s *OpenTracingLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, error) { +func (s *OpenTracingLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, int, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.CountPostsAfter") s.Root.Store.SetContext(newCtx) @@ -634,13 +634,13 @@ func (s *OpenTracingLayerChannelStore) CountPostsAfter(channelID string, timesta }() defer span.Finish() - result, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) + result, resultVar1, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) } - return result, err + return result, resultVar1, err } func (s *OpenTracingLayerChannelStore) CreateDirectChannel(userId *model.User, otherUserId *model.User) (*model.Channel, error) { diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 5a5ff60347..6cc8c1686b 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -664,21 +664,21 @@ func (s *RetryLayerChannelStore) ClearSidebarOnTeamLeave(userId string, teamID s } -func (s *RetryLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, error) { +func (s *RetryLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, int, error) { tries := 0 for { - result, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) + result, resultVar1, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) if err == nil { - return result, nil + return result, resultVar1, nil } if !isRepeatableError(err) { - return result, err + return result, resultVar1, err } tries++ if tries >= 3 { err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err + return result, resultVar1, err } } diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 00868ed0dd..13237a517a 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -50,6 +50,7 @@ type channelMember struct { SchemeUser sql.NullBool SchemeAdmin sql.NullBool SchemeGuest sql.NullBool + MsgCountRoot int64 } func NewChannelMemberFromModel(cm *model.ChannelMember) *channelMember { @@ -59,6 +60,7 @@ func NewChannelMemberFromModel(cm *model.ChannelMember) *channelMember { Roles: cm.ExplicitRoles, LastViewedAt: cm.LastViewedAt, MsgCount: cm.MsgCount, + MsgCountRoot: cm.MsgCountRoot, MentionCount: cm.MentionCount, NotifyProps: cm.NotifyProps, LastUpdateAt: cm.LastUpdateAt, @@ -86,10 +88,11 @@ type channelMemberWithSchemeRoles struct { ChannelSchemeDefaultGuestRole sql.NullString ChannelSchemeDefaultUserRole sql.NullString ChannelSchemeDefaultAdminRole sql.NullString + MsgCountRoot int64 } func channelMemberSliceColumns() []string { - return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MentionCount", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"} + return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MsgCountRoot", "MentionCount", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"} } func channelMemberToSlice(member *model.ChannelMember) []interface{} { @@ -99,6 +102,7 @@ func channelMemberToSlice(member *model.ChannelMember) []interface{} { resultSlice = append(resultSlice, member.ExplicitRoles) resultSlice = append(resultSlice, member.LastViewedAt) resultSlice = append(resultSlice, member.MsgCount) + resultSlice = append(resultSlice, member.MsgCountRoot) resultSlice = append(resultSlice, member.MentionCount) resultSlice = append(resultSlice, model.MapToJson(member.NotifyProps)) resultSlice = append(resultSlice, member.LastUpdateAt) @@ -230,6 +234,7 @@ func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember { Roles: strings.Join(rolesResult.roles, " "), LastViewedAt: db.LastViewedAt, MsgCount: db.MsgCount, + MsgCountRoot: db.MsgCountRoot, MentionCount: db.MentionCount, NotifyProps: db.NotifyProps, LastUpdateAt: db.LastUpdateAt, @@ -712,7 +717,10 @@ func (s SqlChannelStore) GetChannelUnread(channelId, userId string) (*model.Chan var unreadChannel model.ChannelUnread err := s.GetReplica().SelectOne(&unreadChannel, `SELECT - Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, ChannelMembers.MentionCount MentionCount, ChannelMembers.NotifyProps NotifyProps + Channels.TeamId TeamId, Channels.Id ChannelId, + (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, + (Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot, + ChannelMembers.MentionCount MentionCount, ChannelMembers.NotifyProps NotifyProps FROM Channels, ChannelMembers WHERE @@ -1181,23 +1189,25 @@ func (s SqlChannelStore) GetPublicChannelsByIdsForTeam(teamId string, channelIds } type channelIdWithCountAndUpdateAt struct { - Id string - TotalMsgCount int64 - UpdateAt int64 + Id string + TotalMsgCount int64 + TotalMsgCountRoot int64 + UpdateAt int64 } func (s SqlChannelStore) GetChannelCounts(teamId string, userId string) (*model.ChannelCounts, error) { var data []channelIdWithCountAndUpdateAt - _, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND (TeamId = :TeamId OR TeamId = '') AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId}) + _, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, TotalMsgCountRoot, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND (TeamId = :TeamId OR TeamId = '') AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId}) if err != nil { return nil, errors.Wrapf(err, "failed to get channels count with teamId=%s and userId=%s", teamId, userId) } - counts := &model.ChannelCounts{Counts: make(map[string]int64), UpdateTimes: make(map[string]int64)} + counts := &model.ChannelCounts{Counts: make(map[string]int64), CountsRoot: make(map[string]int64), UpdateTimes: make(map[string]int64)} for i := range data { v := data[i] counts.Counts[v.Id] = v.TotalMsgCount + counts.CountsRoot[v.Id] = v.TotalMsgCountRoot counts.UpdateTimes[v.Id] = v.UpdateAt } @@ -2061,12 +2071,13 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, props["UserId"] = userId var lastPostAtTimes []struct { - Id string - LastPostAt int64 - TotalMsgCount int64 + Id string + LastPostAt int64 + TotalMsgCount int64 + TotalMsgCountRoot int64 } - query := `SELECT Id, LastPostAt, TotalMsgCount FROM Channels WHERE Id IN ` + keys + query := `SELECT Id, LastPostAt, TotalMsgCount, TotalMsgCountRoot FROM Channels WHERE Id IN ` + keys // TODO: use a CTE for mysql too when version 8 becomes the minimum supported version. if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { query = `WITH c AS ( ` + query + `), @@ -2076,6 +2087,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, SET MentionCount = 0, MsgCount = greatest(cm.MsgCount, c.TotalMsgCount), + MsgCountRoot = greatest(cm.MsgCountRoot, c.TotalMsgCountRoot), LastViewedAt = greatest(cm.LastViewedAt, c.LastPostAt), LastUpdateAt = greatest(cm.LastViewedAt, c.LastPostAt) FROM c @@ -2106,6 +2118,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, } msgCountQuery := "" + msgCountQueryRoot := "" lastViewedQuery := "" for index, t := range lastPostAtTimes { @@ -2114,6 +2127,9 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, props["msgCount"+strconv.Itoa(index)] = t.TotalMsgCount msgCountQuery += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(MsgCount, :msgCount%d) ", index, index) + props["msgCountRoot"+strconv.Itoa(index)] = t.TotalMsgCountRoot + msgCountQueryRoot += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(MsgCountRoot, :msgCountRoot%d) ", index, index) + props["lastViewed"+strconv.Itoa(index)] = t.LastPostAt lastViewedQuery += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(LastViewedAt, :lastViewed%d) ", index, index) @@ -2125,6 +2141,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, SET MentionCount = 0, MsgCount = CASE ChannelId ` + msgCountQuery + ` END, + MsgCountRoot = CASE ChannelId ` + msgCountQueryRoot + ` END, LastViewedAt = CASE ChannelId ` + lastViewedQuery + ` END, LastUpdateAt = LastViewedAt WHERE @@ -2142,8 +2159,8 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, } // CountPostsAfter returns the number of posts in the given channel created after but not including the given timestamp. If given a non-empty user ID, only counts posts made by that user. -func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, userId string) (int, error) { - joinLeavePostTypes, params := MapStringsToQueryParams([]string{ +func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, userId string) (int, int, error) { + joinLeavePostTypes := []string{ // These types correspond to the ones checked by Post.IsJoinLeaveMessage model.POST_JOIN_LEAVE, model.POST_ADD_REMOVE, @@ -2155,31 +2172,25 @@ func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, user model.POST_REMOVE_FROM_CHANNEL, model.POST_ADD_TO_TEAM, model.POST_REMOVE_FROM_TEAM, - }, "PostType") - - query := ` - SELECT count(*) - FROM Posts - WHERE - ChannelId = :ChannelId - AND CreateAt > :CreateAt - AND Type NOT IN ` + joinLeavePostTypes + ` - AND DeleteAt = 0 - ` - - params["ChannelId"] = channelId - params["CreateAt"] = timestamp + } + query := s.getQueryBuilder().Select("count(*)").From("Posts").Where(sq.Eq{"ChannelId": channelId}).Where(sq.Gt{"CreateAt": timestamp}).Where(sq.NotEq{"Type": joinLeavePostTypes}).Where(sq.Eq{"DeleteAt": 0}) if userId != "" { - query += " AND UserId = :UserId" - params["UserId"] = userId + query = query.Where(sq.Eq{"UserId": userId}) } + sql, args, _ := query.ToSql() - unread, err := s.GetReplica().SelectInt(query, params) + unread, err := s.GetReplica().SelectInt(sql, args...) if err != nil { - return 0, errors.Wrap(err, "failed to count Posts") + return 0, 0, errors.Wrap(err, "failed to count Posts") } - return int(unread), nil + sql2, args2, _ := query.Where(sq.Eq{"RootId": ""}).ToSql() + + unreadRoot, err := s.GetReplica().SelectInt(sql2, args2...) + if err != nil { + return 0, 0, errors.Wrap(err, "failed to count root Posts") + } + return int(unread), int(unreadRoot), nil } // UpdateLastViewedAtPost updates a ChannelMember as if the user last read the channel at the time of the given post. @@ -2196,18 +2207,19 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s } } - unread, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "") + unread, unreadRoot, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "") if err != nil { return nil, err } params := map[string]interface{}{ - "mentions": mentionCount, - "unreadCount": unread, - "lastViewedAt": unreadDate, - "userId": userID, - "channelId": unreadPost.ChannelId, - "updatedAt": model.GetMillis(), + "mentions": mentionCount, + "unreadCount": unread, + "unreadCountRoot": unreadRoot, + "lastViewedAt": unreadDate, + "userId": userID, + "channelId": unreadPost.ChannelId, + "updatedAt": model.GetMillis(), } // msg count uses the value from channels to prevent counting on older channels where no. of messages can be high. @@ -2218,6 +2230,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s SET MentionCount = :mentions, MsgCount = (SELECT TotalMsgCount FROM Channels WHERE ID = :channelId) - :unreadCount, + MsgCountRoot = (SELECT TotalMsgCountRoot FROM Channels WHERE ID = :channelId) - :unreadCountRoot, LastViewedAt = :lastViewedAt, LastUpdateAt = :updatedAt WHERE @@ -2235,6 +2248,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s cm.UserId UserId, cm.ChannelId ChannelId, cm.MsgCount MsgCount, + cm.MsgCountRoot MsgCountRoot, cm.MentionCount MentionCount, cm.LastViewedAt LastViewedAt, cm.NotifyProps NotifyProps diff --git a/store/sqlstore/group_store.go b/store/sqlstore/group_store.go index 8e6d5910ef..899711556a 100644 --- a/store/sqlstore/group_store.go +++ b/store/sqlstore/group_store.go @@ -917,6 +917,7 @@ func (s *SqlGroupStore) ChannelMembersToRemove(channelID *string) ([]*model.Chan "ChannelMembers.UserId", "ChannelMembers.LastViewedAt", "ChannelMembers.MsgCount", + "ChannelMembers.MsgCountRoot", "ChannelMembers.MentionCount", "ChannelMembers.NotifyProps", "ChannelMembers.LastUpdateAt", diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index cfd013262f..7b49b2eac3 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -112,6 +112,7 @@ func (s *SqlPostStore) createIndexesIfNotExists() { func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) { channelNewPosts := make(map[string]int) + channelNewRootPosts := make(map[string]int) maxDateNewPosts := make(map[string]int64) rootIds := make(map[string]int) maxDateRootIds := make(map[string]int64) @@ -125,8 +126,7 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er return nil, idx, err } - currentChannelCount, ok := channelNewPosts[post.ChannelId] - if !ok { + if currentChannelCount, ok := channelNewPosts[post.ChannelId]; !ok { if post.IsJoinLeaveMessage() { channelNewPosts[post.ChannelId] = 0 } else { @@ -143,11 +143,21 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er } if post.RootId == "" { + if currentChannelCount, ok := channelNewRootPosts[post.ChannelId]; !ok { + if post.IsJoinLeaveMessage() { + channelNewRootPosts[post.ChannelId] = 0 + } else { + channelNewRootPosts[post.ChannelId] = 1 + } + } else { + if !post.IsJoinLeaveMessage() { + channelNewRootPosts[post.ChannelId] = currentChannelCount + 1 + } + } continue } - currentRootCount, ok := rootIds[post.RootId] - if !ok { + if currentRootCount, ok := rootIds[post.RootId]; !ok { rootIds[post.RootId] = 1 maxDateRootIds[post.RootId] = post.CreateAt } else { @@ -188,7 +198,9 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er } 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 { + countRoot := channelNewRootPosts[channelId] + + if _, err = s.GetMaster().Exec("UPDATE Channels SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count, TotalMsgCountRoot = TotalMsgCountRoot + :CountRoot WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": maxDateNewPosts[channelId], "ChannelId": channelId, "Count": count, "CountRoot": countRoot}); err != nil { mlog.Warn("Error updating Channel LastPostAt.", mlog.Err(err)) } } diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index ed5bbc95b8..3f798e776f 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -1177,7 +1177,7 @@ func (s SqlTeamStore) GetTeamsForUserWithPagination(userId string, page, perPage // for all the channels in all the teams except the excluded ones. func (s SqlTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId, userId string) ([]*model.ChannelUnread, error) { query, args, err := s.getQueryBuilder(). - Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.NotifyProps NotifyProps"). + Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "(Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.NotifyProps NotifyProps"). From("Channels"). Join("ChannelMembers ON Id = ChannelId"). Where(sq.Eq{"UserId": userId, "DeleteAt": 0}). @@ -1199,7 +1199,7 @@ func (s SqlTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId, userId string) // GetChannelUnreadsForTeam returns unreads msg count, mention counts and notifyProps for all the channels in a single team. func (s SqlTeamStore) GetChannelUnreadsForTeam(teamId, userId string) ([]*model.ChannelUnread, error) { query, args, err := s.getQueryBuilder(). - Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.NotifyProps NotifyProps"). + Select("Channels.TeamId TeamId", "Channels.Id ChannelId", "(Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount", "(Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot", "ChannelMembers.MentionCount MentionCount", "ChannelMembers.NotifyProps NotifyProps"). From("Channels"). Join("ChannelMembers ON Id = ChannelId"). Where(sq.Eq{"UserId": userId, "TeamId": teamId, "DeleteAt": 0}).ToSql() diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index e5dfd85865..8f6a070390 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -1011,6 +1011,57 @@ func upgradeDatabaseToVersion535(sqlStore *SqlStore) { sqlStore.CreateColumnIfNotExists("SidebarCategories", "Collapsed", "tinyint(1)", "boolean", "0") + sqlStore.CreateColumnIfNotExistsNoDefault("Channels", "TotalMsgCountRoot", "bigint", "bigint") + sqlStore.CreateColumnIfNotExistsNoDefault("Channels", "LastRootPostAt", "bigint", "bigint") + defer sqlStore.RemoveColumnIfExists("Channels", "LastRootPostAt") + + // note: setting default 0 on pre-5.0 tables causes test-db-migration script to fail, so this column will be added to ignore list + sqlStore.CreateColumnIfNotExists("ChannelMembers", "MsgCountRoot", "bigint", "bigint", "0") + sqlStore.AlterColumnDefaultIfExists("ChannelMembers", "MsgCountRoot", model.NewString("0"), model.NewString("0")) + + forceIndex := "" + if sqlStore.DriverName() == model.DATABASE_DRIVER_MYSQL { + forceIndex = "FORCE INDEX(idx_posts_channel_id)" + } + totalMsgCountRootCTE := ` + SELECT Channels.Id channelid, COALESCE(COUNT(*),0) newcount, COALESCE(MAX(Posts.CreateAt), 0) as lastpost + FROM Channels + LEFT JOIN Posts ` + forceIndex + ` ON Channels.Id = Posts.ChannelId + WHERE Posts.RootId = '' + GROUP BY Channels.Id + ` + channelsCTE := "SELECT TotalMsgCountRoot, Id, LastRootPostAt from Channels" + updateChannels := ` + WITH q AS (` + totalMsgCountRootCTE + `) + UPDATE Channels SET TotalMsgCountRoot = q.newcount, LastRootPostAt=q.lastpost + FROM q where q.channelid=Channels.Id; + ` + updateChannelMembers := ` + WITH q as (` + channelsCTE + `) + UPDATE ChannelMembers CM SET MsgCountRoot=TotalMsgCountRoot + FROM q WHERE q.id=CM.ChannelId AND LastViewedAt >= q.lastrootpostat; + ` + if sqlStore.DriverName() == model.DATABASE_DRIVER_MYSQL { + updateChannels = ` + UPDATE Channels + INNER Join (` + totalMsgCountRootCTE + `) as q + ON q.channelid=Channels.Id + SET TotalMsgCountRoot = q.newcount, LastRootPostAt=q.lastpost; + ` + updateChannelMembers = ` + UPDATE ChannelMembers CM + INNER JOIN (` + channelsCTE + `) as q + ON q.id=CM.ChannelId and LastViewedAt >= q.lastrootpostat + SET MsgCountRoot=TotalMsgCountRoot + ` + } + if _, err := sqlStore.GetMaster().Exec(updateChannels); err != nil { + mlog.Error("Error updating Channels table", mlog.Err(err)) + } + if _, err := sqlStore.GetMaster().Exec(updateChannelMembers); err != nil { + mlog.Error("Error updating ChannelMembers table", mlog.Err(err)) + } + // saveSchemaVersion(sqlStore, Version5350) // } } diff --git a/store/sqlstore/upgrade_test.go b/store/sqlstore/upgrade_test.go index e3eee3d538..ca0d165973 100644 --- a/store/sqlstore/upgrade_test.go +++ b/store/sqlstore/upgrade_test.go @@ -6,8 +6,10 @@ package sqlstore import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/store" ) @@ -103,3 +105,139 @@ func TestSaveSchemaVersion(t *testing.T) { }) }) } +func createChannelMemberWithLastViewAt(ss store.Store, channelId, userId string, lastViewAt int64) *model.ChannelMember { + m := model.ChannelMember{} + m.ChannelId = channelId + m.UserId = userId + m.LastViewedAt = lastViewAt + m.NotifyProps = model.GetDefaultChannelNotifyProps() + cm, _ := ss.Channel().SaveMember(&m) + return cm +} +func createPostWithTimestamp(ss store.Store, channelId, userId, rootId, parentId string, timestamp int64) *model.Post { + m := model.Post{} + m.CreateAt = timestamp + m.ChannelId = channelId + m.UserId = userId + m.RootId = rootId + m.ParentId = parentId + m.Message = "zz" + model.NewId() + "b" + p, _ := ss.Post().Save(&m) + return p +} + +func createChannelWithLastPostAt(ss store.Store, teamId, creatorId string, lastPostAt, msgCount, rootCount int64) (*model.Channel, error) { + m := model.Channel{} + m.TeamId = teamId + m.TotalMsgCount = msgCount + m.TotalMsgCountRoot = rootCount + m.LastPostAt = lastPostAt + m.CreatorId = creatorId + m.DisplayName = "Name" + m.Name = "zz" + model.NewId() + "b" + m.Type = model.CHANNEL_OPEN + return ss.Channel().Save(&m, -1) +} +func TestMsgCountRootMigration(t *testing.T) { + type TestCaseChannel struct { + Name string + PostTimes []int64 + ReplyTimes []int64 + MembershipsLastViewAt []int64 + ExpectedMembershipMsgCountRoot []int64 + } + type TestTableEntry struct { + name string + data []TestCaseChannel + } + testTable := []TestTableEntry{ + { + name: "test1", + data: []TestCaseChannel{ + { + Name: "channel with one post", + PostTimes: []int64{1000}, + ReplyTimes: []int64{0}, + MembershipsLastViewAt: []int64{1}, + ExpectedMembershipMsgCountRoot: []int64{0}, + }, + { + Name: "channel with one post, read", + PostTimes: []int64{1000}, + ReplyTimes: []int64{0}, + MembershipsLastViewAt: []int64{1000}, + ExpectedMembershipMsgCountRoot: []int64{1}, + }, + { + Name: "with one reply, viewed after 2nd root", + PostTimes: []int64{1000, 2000, 3000, 4000}, + ReplyTimes: []int64{1001, 0, 0, 0}, + MembershipsLastViewAt: []int64{2001}, + ExpectedMembershipMsgCountRoot: []int64{0}, + }, + { + Name: "two replies, 3 memberships", + PostTimes: []int64{1000, 2000, 3000}, + ReplyTimes: []int64{1001, 2001, 0}, + MembershipsLastViewAt: []int64{2000, 5000, 0}, + ExpectedMembershipMsgCountRoot: []int64{0, 3, 0}, + }, + }, + }, + } + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + sqlStore := ss.(*SqlStore) + team := createTeam(ss) + for _, testChannel := range testCase.data { + t.Run(testChannel.Name, func(t *testing.T) { + lastPostAt := int64(0) + for i := range testChannel.PostTimes { + if testChannel.PostTimes[i] > lastPostAt { + lastPostAt = testChannel.PostTimes[i] + } + if testChannel.ReplyTimes[i] > lastPostAt { + lastPostAt = testChannel.ReplyTimes[i] + } + } + channel, err := createChannelWithLastPostAt(ss, team.Id, model.NewId(), lastPostAt, int64(len(testChannel.PostTimes)+len(testChannel.ReplyTimes)), int64(len(testChannel.PostTimes))) + require.NoError(t, err) + var userIds []string + for _, md := range testChannel.MembershipsLastViewAt { + user := createUser(ss) + userIds = append(userIds, user.Id) + require.NotNil(t, user) + cm := createChannelMemberWithLastViewAt(ss, channel.Id, user.Id, md) + require.NotNil(t, cm) + } + for i, pt := range testChannel.PostTimes { + rt := testChannel.ReplyTimes[i] + post := createPostWithTimestamp(ss, channel.Id, model.NewId(), "", "", pt) + require.NotNil(t, post) + if rt > 0 { + reply := createPostWithTimestamp(ss, channel.Id, model.NewId(), post.Id, post.Id, rt) + require.NotNil(t, reply) + } + } + + upgradeDatabaseToVersion535(sqlStore) + + members, err := ss.Channel().GetMembersByIds(channel.Id, userIds) + require.NoError(t, err) + + for _, m := range *members { + for i, uid := range userIds { + if m.UserId == uid { + assert.Equal(t, testChannel.ExpectedMembershipMsgCountRoot[i], m.MsgCountRoot) + break + } + } + } + + }) + } + }) + }) + } +} diff --git a/store/store.go b/store/store.go index 2aa9713b57..516819f8c2 100644 --- a/store/store.go +++ b/store/store.go @@ -194,7 +194,7 @@ type ChannelStore interface { PermanentDeleteMembersByChannel(channelID string) error UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) - CountPostsAfter(channelID string, timestamp int64, userId string) (int, error) + CountPostsAfter(channelID string, timestamp int64, userId string) (int, int, error) IncrementMentionCount(channelID string, userId string, updateThreads bool) error AnalyticsTypeCount(teamID string, channelType string) (int64, error) GetMembersForUser(teamID string, userId string) (*model.ChannelMembers, error) diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 0b9220cd36..85a64bd9ee 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -327,20 +327,20 @@ func testGetChannelUnread(t *testing.T, ss store.Store) { notifyPropsModel := model.GetDefaultChannelNotifyProps() // Setup Channel 1 - c1 := &model.Channel{TeamId: m1.TeamId, Name: model.NewId(), DisplayName: "Downtown", Type: model.CHANNEL_OPEN, TotalMsgCount: 100} + c1 := &model.Channel{TeamId: m1.TeamId, Name: model.NewId(), DisplayName: "Downtown", Type: model.CHANNEL_OPEN, TotalMsgCount: 100, TotalMsgCountRoot: 99} _, nErr = ss.Channel().Save(c1, -1) require.NoError(t, nErr) - cm1 := &model.ChannelMember{ChannelId: c1.Id, UserId: m1.UserId, NotifyProps: notifyPropsModel, MsgCount: 90} + cm1 := &model.ChannelMember{ChannelId: c1.Id, UserId: m1.UserId, NotifyProps: notifyPropsModel, MsgCount: 90, MsgCountRoot: 80} _, err := ss.Channel().SaveMember(cm1) require.NoError(t, err) // Setup Channel 2 - c2 := &model.Channel{TeamId: m2.TeamId, Name: model.NewId(), DisplayName: "Cultural", Type: model.CHANNEL_OPEN, TotalMsgCount: 100} + c2 := &model.Channel{TeamId: m2.TeamId, Name: model.NewId(), DisplayName: "Cultural", Type: model.CHANNEL_OPEN, TotalMsgCount: 100, TotalMsgCountRoot: 100} _, nErr = ss.Channel().Save(c2, -1) require.NoError(t, nErr) - cm2 := &model.ChannelMember{ChannelId: c2.Id, UserId: m2.UserId, NotifyProps: notifyPropsModel, MsgCount: 90, MentionCount: 5} + cm2 := &model.ChannelMember{ChannelId: c2.Id, UserId: m2.UserId, NotifyProps: notifyPropsModel, MsgCount: 90, MsgCountRoot: 90, MentionCount: 5} _, err = ss.Channel().SaveMember(cm2) require.NoError(t, err) @@ -353,7 +353,7 @@ func testGetChannelUnread(t *testing.T, ss store.Store) { require.NotNil(t, ch.NotifyProps, "wrong props for channel 1") require.EqualValues(t, 0, ch.MentionCount, "wrong MentionCount for channel 1") require.EqualValues(t, 10, ch.MsgCount, "wrong MsgCount for channel 1") - + require.EqualValues(t, 19, ch.MsgCountRoot, "wrong MsgCountRoot for channel 1") // Check for Channel 2 ch2, nErr := ss.Channel().GetChannelUnread(c2.Id, uid) @@ -4007,19 +4007,19 @@ func testCountPostsAfter(t *testing.T, ss store.Store) { }) require.NoError(t, err) - count, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") + count, _, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") require.NoError(t, err) assert.Equal(t, 3, count) - count, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") + count, _, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") require.NoError(t, err) assert.Equal(t, 2, count) - count, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, userId1) + count, _, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, userId1) require.NoError(t, err) assert.Equal(t, 2, count) - count, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, userId1) + count, _, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, userId1) require.NoError(t, err) assert.Equal(t, 1, count) }) @@ -4044,11 +4044,11 @@ func testCountPostsAfter(t *testing.T, ss store.Store) { }) require.NoError(t, err) - count, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") + count, _, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") require.NoError(t, err) assert.Equal(t, 1, count) - count, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") + count, _, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") require.NoError(t, err) assert.Equal(t, 0, count) }) @@ -4105,19 +4105,19 @@ func testCountPostsAfter(t *testing.T, ss store.Store) { }) require.NoError(t, err) - count, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") + count, _, err := ss.Channel().CountPostsAfter(channelId, p1.CreateAt-1, "") require.NoError(t, err) assert.Equal(t, 3, count) - count, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") + count, _, err = ss.Channel().CountPostsAfter(channelId, p1.CreateAt, "") require.NoError(t, err) assert.Equal(t, 2, count) - count, err = ss.Channel().CountPostsAfter(channelId, p5.CreateAt-1, "") + count, _, err = ss.Channel().CountPostsAfter(channelId, p5.CreateAt-1, "") require.NoError(t, err) assert.Equal(t, 2, count) - count, err = ss.Channel().CountPostsAfter(channelId, p5.CreateAt, "") + count, _, err = ss.Channel().CountPostsAfter(channelId, p5.CreateAt, "") require.NoError(t, err) assert.Equal(t, 1, count) }) @@ -6345,9 +6345,9 @@ func testMaterializedPublicChannels(t *testing.T, ss store.Store, s SqlStore) { _, execerr = s.GetMaster().ExecNoTimeout(` INSERT INTO - Channels(Id, CreateAt, UpdateAt, DeleteAt, TeamId, Type, DisplayName, Name, Header, Purpose, LastPostAt, TotalMsgCount, ExtraUpdateAt, CreatorId) + Channels(Id, CreateAt, UpdateAt, DeleteAt, TeamId, Type, DisplayName, Name, Header, Purpose, LastPostAt, TotalMsgCount, ExtraUpdateAt, CreatorId, TotalMsgCountRoot) VALUES - (:Id, :CreateAt, :UpdateAt, :DeleteAt, :TeamId, :Type, :DisplayName, :Name, :Header, :Purpose, :LastPostAt, :TotalMsgCount, :ExtraUpdateAt, :CreatorId); + (:Id, :CreateAt, :UpdateAt, :DeleteAt, :TeamId, :Type, :DisplayName, :Name, :Header, :Purpose, :LastPostAt, :TotalMsgCount, :ExtraUpdateAt, :CreatorId, 0); `, map[string]interface{}{ "Id": o3.Id, "CreateAt": o3.CreateAt, diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index 7ea3911937..93e661f59e 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -140,7 +140,7 @@ func (_m *ChannelStore) ClearSidebarOnTeamLeave(userId string, teamID string) er } // CountPostsAfter provides a mock function with given fields: channelID, timestamp, userId -func (_m *ChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, error) { +func (_m *ChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, int, error) { ret := _m.Called(channelID, timestamp, userId) var r0 int @@ -150,14 +150,21 @@ func (_m *ChannelStore) CountPostsAfter(channelID string, timestamp int64, userI r0 = ret.Get(0).(int) } - var r1 error - if rf, ok := ret.Get(1).(func(string, int64, string) error); ok { + var r1 int + if rf, ok := ret.Get(1).(func(string, int64, string) int); ok { r1 = rf(channelID, timestamp, userId) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(int) } - return r0, r1 + var r2 error + if rf, ok := ret.Get(2).(func(string, int64, string) error); ok { + r2 = rf(channelID, timestamp, userId) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } // CreateDirectChannel provides a mock function with given fields: userId, otherUserId diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index d9c0ee2107..c3b70aa101 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -599,10 +599,10 @@ func (s *TimerLayerChannelStore) ClearSidebarOnTeamLeave(userId string, teamID s return err } -func (s *TimerLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, error) { +func (s *TimerLayerChannelStore) CountPostsAfter(channelID string, timestamp int64, userId string) (int, int, error) { start := timemodule.Now() - result, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) + result, resultVar1, err := s.ChannelStore.CountPostsAfter(channelID, timestamp, userId) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -612,7 +612,7 @@ func (s *TimerLayerChannelStore) CountPostsAfter(channelID string, timestamp int } s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.CountPostsAfter", success, elapsed) } - return result, err + return result, resultVar1, err } func (s *TimerLayerChannelStore) CreateDirectChannel(userId *model.User, otherUserId *model.User) (*model.Channel, error) {