From d5b24e383d32d4bb5ff9d49a204e74934168d014 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Wed, 9 Feb 2022 19:21:47 +0530 Subject: [PATCH] Migrate ChannelStore.UpdateLastViewedAt to squirrel (#19439) Fixes https://github.com/mattermost/mattermost-server/issues/19332 ```release-note NONE ``` Co-authored-by: Mattermod --- store/sqlstore/channel_store.go | 112 ++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index f33180bf72..aaa5c0f9bf 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -2431,7 +2431,6 @@ func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) error { return nil } -// TODO: convert to squirrel (https://github.com/mattermost/mattermost-server/issues/19332) func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) { var threadsToUpdate []string now := model.GetMillis() @@ -2443,38 +2442,50 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, } } - keys, props := MapStringsToQueryParams(channelIds, "Channel") - props["UserId"] = userId - - var lastPostAtTimes []struct { + lastPostAtTimes := []struct { Id string LastPostAt int64 TotalMsgCount int64 TotalMsgCountRoot int64 - } + }{} + + // We use the question placeholder format for both databases, because + // we replace that with the dollar format later on. + // It's needed to support the prefix CTE query. See: https://github.com/Masterminds/squirrel/issues/285. + query := sq.StatementBuilder.PlaceholderFormat(sq.Question). + Select("Id, LastPostAt, TotalMsgCount, TotalMsgCountRoot"). + From("Channels"). + Where(sq.Eq{"Id": channelIds}) - 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.DatabaseDriverPostgres { - query = `WITH c AS ( ` + query + `), - updated AS ( - UPDATE - ChannelMembers cm - SET - MentionCount = 0, - MentionCountRoot = 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 - WHERE cm.UserId = :UserId - AND c.Id=cm.ChannelId -) - SELECT Id, LastPostAt FROM c` + with := query.Prefix("WITH c AS (").Suffix(") ,") + update := sq.StatementBuilder.PlaceholderFormat(sq.Question). + Update("ChannelMembers cm"). + Set("MentionCount", 0). + Set("MentionCountRoot", 0). + Set("MsgCount", sq.Expr("greatest(cm.MsgCount, c.TotalMsgCount)")). + Set("MsgCountRoot", sq.Expr("greatest(cm.MsgCountRoot, c.TotalMsgCountRoot)")). + Set("LastViewedAt", sq.Expr("greatest(cm.LastViewedAt, c.LastPostAt)")). + Set("LastUpdateAt", sq.Expr("greatest(cm.LastViewedAt, c.LastPostAt)")). + SuffixExpr(sq.Expr("FROM c WHERE cm.UserId = ? AND c.Id = cm.ChannelId", userId)) + updateWrap := update.Prefix("updated AS (").Suffix(")") + query = with.SuffixExpr(updateWrap).Suffix("SELECT Id, LastPostAt FROM c") } - _, err := s.GetMaster().Select(&lastPostAtTimes, query, props) + sql, args, err := query.ToSql() + if err != nil { + return nil, errors.Wrap(err, "UpdateLastViewedAt_CTE_Tosql") + } + + if s.DriverName() == model.DatabaseDriverPostgres { + sql, err = sq.Dollar.ReplacePlaceholders(sql) + if err != nil { + return nil, errors.Wrap(err, "UpdateLastViewedAt_ReplacePlaceholders") + } + } + + err = s.GetMasterX().Select(&lastPostAtTimes, sql, args...) if err != nil { return nil, errors.Wrapf(err, "failed to find ChannelMembers data with userId=%s and channelId in %v", userId, channelIds) } @@ -2494,39 +2505,42 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, return times, nil } - msgCountQuery := "" - msgCountQueryRoot := "" - lastViewedQuery := "" + var msgCountQuery, msgCountQueryRoot, lastViewedQuery = sq.Case("ChannelId"), sq.Case("ChannelId"), sq.Case("ChannelId") - for index, t := range lastPostAtTimes { + for _, t := range lastPostAtTimes { times[t.Id] = t.LastPostAt - props["msgCount"+strconv.Itoa(index)] = t.TotalMsgCount - msgCountQuery += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(MsgCount, :msgCount%d) ", index, index) + msgCountQuery = msgCountQuery.When( + sq.Expr("?", t.Id), + sq.Expr("GREATEST(MsgCount, ?)", t.TotalMsgCount)) - props["msgCountRoot"+strconv.Itoa(index)] = t.TotalMsgCountRoot - msgCountQueryRoot += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(MsgCountRoot, :msgCountRoot%d) ", index, index) + msgCountQueryRoot = msgCountQueryRoot.When( + sq.Expr("?", t.Id), + sq.Expr("GREATEST(MsgCountRoot, ?)", t.TotalMsgCountRoot)) - props["lastViewed"+strconv.Itoa(index)] = t.LastPostAt - lastViewedQuery += fmt.Sprintf("WHEN :channelId%d THEN GREATEST(LastViewedAt, :lastViewed%d) ", index, index) - - props["channelId"+strconv.Itoa(index)] = t.Id + lastViewedQuery = lastViewedQuery.When( + sq.Expr("?", t.Id), + sq.Expr("GREATEST(LastViewedAt, ?)", t.LastPostAt)) } - updateQuery := `UPDATE - ChannelMembers - SET - MentionCount = 0, - MentionCountRoot = 0, - MsgCount = CASE ChannelId ` + msgCountQuery + ` END, - MsgCountRoot = CASE ChannelId ` + msgCountQueryRoot + ` END, - LastViewedAt = CASE ChannelId ` + lastViewedQuery + ` END, - LastUpdateAt = LastViewedAt - WHERE - UserId = :UserId - AND ChannelId IN ` + keys + updateQuery := s.getQueryBuilder().Update("ChannelMembers"). + Set("MentionCount", 0). + Set("MentionCountRoot", 0). + Set("MsgCount", msgCountQuery). + Set("MsgCountRoot", msgCountQueryRoot). + Set("LastViewedAt", lastViewedQuery). + Set("LastUpdateAt", sq.Expr("LastViewedAt")). + Where(sq.Eq{ + "UserId": userId, + "ChannelId": channelIds, + }) - if _, err := s.GetMaster().Exec(updateQuery, props); err != nil { + sql, args, err = updateQuery.ToSql() + if err != nil { + return nil, errors.Wrap(err, "UpdateLastViewedAt_Update_Tosql") + } + + if _, err := s.GetMasterX().Exec(sql, args...); err != nil { return nil, errors.Wrapf(err, "failed to update ChannelMembers with userId=%s and channelId in %v", userId, channelIds) }