From 2d986ef9204ff83933ba037016f3c8d3b5d1de48 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Wed, 9 Feb 2022 23:09:42 +0530 Subject: [PATCH] Migrate Channelstore categories to sqlx (#19506) Remove db:"-" tags from model.Channel as well. ```release-note NONE ``` --- model/channel.go | 4 +- store/sqlstore/channel_store.go | 90 +------- store/sqlstore/channel_store_categories.go | 256 ++++++++++----------- store/sqlstore/preference_store.go | 6 +- store/storetest/mocks/dbSelecter.go | 19 +- 5 files changed, 143 insertions(+), 232 deletions(-) diff --git a/model/channel.go b/model/channel.go index 2aca822999..6341a6d54c 100644 --- a/model/channel.go +++ b/model/channel.go @@ -51,11 +51,11 @@ type Channel struct { ExtraUpdateAt int64 `json:"extra_update_at"` CreatorId string `json:"creator_id"` SchemeId *string `json:"scheme_id"` - Props map[string]interface{} `json:"props" db:"-"` + Props map[string]interface{} `json:"props"` GroupConstrained *bool `json:"group_constrained"` Shared *bool `json:"shared"` TotalMsgCountRoot int64 `json:"total_msg_count_root"` - PolicyID *string `json:"policy_id" db:"-"` + PolicyID *string `json:"policy_id"` LastRootPostAt int64 `json:"last_root_post_at"` } diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index aaa5c0f9bf..c16b86c319 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -431,84 +431,6 @@ type publicChannel struct { Purpose string `json:"purpose"` } -// channelInternal is a struct without the db:"-" tags -// which does not work with sqlx. This would be removed once we -// move to the new migration system. -type channelInternal struct { - Id string - CreateAt int64 - UpdateAt int64 - DeleteAt int64 - TeamId string - Type model.ChannelType - DisplayName string - Name string - Header string - Purpose string - LastPostAt int64 - TotalMsgCount int64 - ExtraUpdateAt int64 - CreatorId string - SchemeId *string - Props map[string]interface{} - GroupConstrained *bool - Shared *bool - TotalMsgCountRoot int64 - PolicyId *string - LastRootPostAt int64 -} - -func (ci *channelInternal) ToModel() *model.Channel { - return &model.Channel{ - Id: ci.Id, - CreateAt: ci.CreateAt, - UpdateAt: ci.UpdateAt, - DeleteAt: ci.DeleteAt, - TeamId: ci.TeamId, - Type: ci.Type, - DisplayName: ci.DisplayName, - Name: ci.Name, - Header: ci.Header, - Purpose: ci.Purpose, - LastPostAt: ci.LastPostAt, - TotalMsgCount: ci.TotalMsgCount, - ExtraUpdateAt: ci.ExtraUpdateAt, - CreatorId: ci.CreatorId, - SchemeId: ci.SchemeId, - Props: ci.Props, - GroupConstrained: ci.GroupConstrained, - Shared: ci.Shared, - TotalMsgCountRoot: ci.TotalMsgCountRoot, - PolicyID: ci.PolicyId, - LastRootPostAt: ci.LastRootPostAt, - } -} - -type channelWithTeamDataInternal struct { - channelInternal - TeamDisplayName string - TeamName string - TeamUpdateAt int64 -} - -func (ctd *channelWithTeamDataInternal) ToModel() *model.ChannelWithTeamData { - res := &model.ChannelWithTeamData{ - TeamDisplayName: ctd.TeamDisplayName, - TeamName: ctd.TeamName, - TeamUpdateAt: ctd.TeamUpdateAt, - } - res.Channel = *ctd.channelInternal.ToModel() - return res -} - -func channelWithTeamDataSliceToModel(channels []*channelWithTeamDataInternal) model.ChannelListWithTeamData { - res := make(model.ChannelListWithTeamData, 0, len(channels)) - for _, ch := range channels { - res = append(res, ch.ToModel()) - } - return res -} - var allChannelMembersForUserCache = cache.NewLRU(cache.LRUOptions{ Size: AllChannelMembersForUserCacheSize, }) @@ -1283,13 +1205,13 @@ func (s SqlChannelStore) GetAllChannels(offset, limit int, opts store.ChannelSea return nil, errors.Wrap(err, "failed to create query") } - data := []*channelWithTeamDataInternal{} + data := model.ChannelListWithTeamData{} err = s.GetReplicaX().Select(&data, queryString, args...) if err != nil { return nil, errors.Wrap(err, "failed to get all channels") } - return channelWithTeamDataSliceToModel(data), nil + return data, nil } func (s SqlChannelStore) GetAllChannelsCount(opts store.ChannelSearchOpts) (int64, error) { @@ -1316,7 +1238,7 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo } else { selectStr = "c.*, Teams.DisplayName AS TeamDisplayName, Teams.Name AS TeamName, Teams.UpdateAt AS TeamUpdateAt" if opts.IncludePolicyID { - selectStr += ", RetentionPoliciesChannels.PolicyId" + selectStr += ", RetentionPoliciesChannels.PolicyId AS PolicyID" } } @@ -3222,7 +3144,7 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se selectStr += ", t.DisplayName AS TeamDisplayName, t.Name AS TeamName, t.UpdateAt as TeamUpdateAt" } if opts.IncludePolicyID { - selectStr += ", RetentionPoliciesChannels.PolicyId" + selectStr += ", RetentionPoliciesChannels.PolicyId AS PolicyID" } } @@ -3314,7 +3236,7 @@ func (s SqlChannelStore) SearchAllChannels(term string, opts store.ChannelSearch if err != nil { return nil, 0, errors.Wrap(err, "channel_tosql") } - channels := []*channelWithTeamDataInternal{} + channels := model.ChannelListWithTeamData{} if err2 := s.GetReplicaX().Select(&channels, queryString, args...); err2 != nil { return nil, 0, errors.Wrapf(err2, "failed to find Channels with term='%s'", term) } @@ -3335,7 +3257,7 @@ func (s SqlChannelStore) SearchAllChannels(term string, opts store.ChannelSearch totalCount = int64(len(channels)) } - return channelWithTeamDataSliceToModel(channels), totalCount, nil + return channels, totalCount, nil } // TODO: rewrite in squrrel diff --git a/store/sqlstore/channel_store_categories.go b/store/sqlstore/channel_store_categories.go index 3cbfcac182..1f31de5e3e 100644 --- a/store/sqlstore/channel_store_categories.go +++ b/store/sqlstore/channel_store_categories.go @@ -7,25 +7,24 @@ import ( "fmt" sq "github.com/Masterminds/squirrel" - "github.com/mattermost/gorp" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/store" ) -// dbSelecter is an interface used to enable some internal store methods to -// accept both transactions (*gorp.Transaction) and common db handlers (*gorp.DbMap). +// dbSelecter is an interface used to enable some internal store methods +// using both transaction and normal queries. type dbSelecter interface { - Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) + Select(i interface{}, query string, args ...interface{}) error } func (s SqlChannelStore) CreateInitialSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, error) { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return nil, errors.Wrap(err, "CreateInitialSidebarCategories: begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) if err = s.createInitialSidebarCategoriesT(transaction, userId, teamId); err != nil { return nil, errors.Wrap(err, "CreateInitialSidebarCategories: createInitialSidebarCategoriesT") @@ -43,7 +42,7 @@ func (s SqlChannelStore) CreateInitialSidebarCategories(userId, teamId string) ( return oc, nil } -func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Transaction, userId, teamId string) error { +func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *sqlxTxWrapper, userId, teamId string) error { selectQuery, selectParams, _ := s.getQueryBuilder(). Select("Type"). From("SidebarCategories"). @@ -53,8 +52,8 @@ func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Trans "Type": []model.SidebarCategoryType{model.SidebarCategoryFavorites, model.SidebarCategoryChannels, model.SidebarCategoryDirectMessages}, }).ToSql() - var existingTypes []model.SidebarCategoryType - _, err := transaction.Select(&existingTypes, selectQuery, selectParams...) + existingTypes := []model.SidebarCategoryType{} + err := transaction.Select(&existingTypes, selectQuery, selectParams...) if err != nil { return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to select existing categories") } @@ -75,7 +74,9 @@ func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Trans return errors.Wrap(err, "createInitialSidebarCategoriesT: failed to migrate favorites to sidebar") } - if err := transaction.Insert(&model.SidebarCategory{ + if _, err := transaction.NamedExec(`INSERT INTO + SidebarCategories(Id, UserId, TeamId, SortOrder, Sorting, Type, DisplayName, Muted, Collapsed) + VALUES(:Id, :UserId, :TeamId, :SortOrder, :Sorting, :Type, :DisplayName, :Muted, :Collapsed)`, &model.SidebarCategory{ DisplayName: "Favorites", // This will be retranslated by the client into the user's locale Id: favoritesCategoryId, UserId: userId, @@ -89,7 +90,9 @@ func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Trans } if !hasCategoryOfType[model.SidebarCategoryChannels] { - if err := transaction.Insert(&model.SidebarCategory{ + if _, err := transaction.NamedExec(`INSERT INTO + SidebarCategories(Id, UserId, TeamId, SortOrder, Sorting, Type, DisplayName, Muted, Collapsed) + VALUES(:Id, :UserId, :TeamId, :SortOrder, :Sorting, :Type, :DisplayName, :Muted, :Collapsed)`, &model.SidebarCategory{ DisplayName: "Channels", // This will be retranslated by the client into the user's locale Id: channelsCategoryId, UserId: userId, @@ -103,7 +106,9 @@ func (s SqlChannelStore) createInitialSidebarCategoriesT(transaction *gorp.Trans } if !hasCategoryOfType[model.SidebarCategoryDirectMessages] { - if err := transaction.Insert(&model.SidebarCategory{ + if _, err := transaction.NamedExec(`INSERT INTO + SidebarCategories(Id, UserId, TeamId, SortOrder, Sorting, Type, DisplayName, Muted, Collapsed) + VALUES(:Id, :UserId, :TeamId, :SortOrder, :Sorting, :Type, :DisplayName, :Muted, :Collapsed)`, &model.SidebarCategory{ DisplayName: "Direct Messages", // This will be retranslated by the client into the user's locale Id: directMessagesCategoryId, UserId: userId, @@ -125,9 +130,9 @@ type userMembership struct { CategoryId string } -func (s SqlChannelStore) migrateMembershipToSidebar(transaction *gorp.Transaction, runningOrder *int64, sql string, args ...interface{}) ([]userMembership, error) { - var memberships []userMembership - if _, err := transaction.Select(&memberships, sql, args...); err != nil { +func (s SqlChannelStore) migrateMembershipToSidebar(transaction *sqlxTxWrapper, runningOrder *int64, sql string, args ...interface{}) ([]userMembership, error) { + memberships := []userMembership{} + if err := transaction.Select(&memberships, sql, args...); err != nil { return nil, err } @@ -149,7 +154,7 @@ func (s SqlChannelStore) migrateMembershipToSidebar(transaction *gorp.Transactio return memberships, nil } -func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transaction, userId, teamId, favoritesCategoryId string) error { +func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *sqlxTxWrapper, userId, teamId, favoritesCategoryId string) error { favoritesQuery, favoritesParams, _ := s.getQueryBuilder(). Select("Preferences.Name"). From("Preferences"). @@ -169,13 +174,15 @@ func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transactio "Channels.Name ASC", ).ToSql() - var favoriteChannelIds []string - if _, err := transaction.Select(&favoriteChannelIds, favoritesQuery, favoritesParams...); err != nil { + favoriteChannelIds := []string{} + if err := transaction.Select(&favoriteChannelIds, favoritesQuery, favoritesParams...); err != nil { return errors.Wrap(err, "migrateFavoritesToSidebarT: unable to get favorite channel IDs") } for i, channelId := range favoriteChannelIds { - if err := transaction.Insert(&model.SidebarChannel{ + if _, err := transaction.NamedExec(`INSERT INTO + SidebarChannels(ChannelId, UserId, CategoryId, SortOrder) + VALUES(:ChannelId, :UserId, :CategoryId, :SortOrder)`, &model.SidebarChannel{ ChannelId: channelId, CategoryId: favoritesCategoryId, UserId: userId, @@ -191,12 +198,12 @@ func (s SqlChannelStore) migrateFavoritesToSidebarT(transaction *gorp.Transactio // MigrateFavoritesToSidebarChannels populates the SidebarChannels table by analyzing existing user preferences for favorites // **IMPORTANT** This function should only be called from the migration task and shouldn't be used by itself func (s SqlChannelStore) MigrateFavoritesToSidebarChannels(lastUserId string, runningOrder int64) (map[string]interface{}, error) { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return nil, err } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) sb := s. getQueryBuilder(). @@ -238,12 +245,12 @@ type sidebarCategoryForJoin struct { } func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return nil, errors.Wrap(err, "begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) categoriesWithOrder, err := s.getSidebarCategoriesT(transaction, userId, teamId) if err != nil { @@ -277,15 +284,14 @@ func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategor Type: model.SidebarCategoryCustom, Muted: newCategory.Muted, } - if err = transaction.Insert(category); err != nil { - return nil, errors.Wrap(err, "failed to save SidebarCategory") + if _, err2 := transaction.NamedExec(`INSERT INTO + SidebarCategories(Id, UserId, TeamId, SortOrder, Sorting, Type, DisplayName, Muted, Collapsed) + VALUES(:Id, :UserId, :TeamId, :SortOrder, :Sorting, :Type, :DisplayName, :Muted, :Collapsed)`, category); err2 != nil { + return nil, errors.Wrap(err2, "failed to save SidebarCategory") } if len(newCategory.Channels) > 0 { - channelIdsKeys, deleteParams := MapStringsToQueryParams(newCategory.Channels, "ChannelId") - deleteParams["UserId"] = userId - deleteParams["TeamId"] = teamId - + placeHolder, channelIdArgs := constructArrayArgs(newCategory.Channels) // Remove any channels from their previous categories and add them to the new one var deleteQuery string if s.DriverName() == model.DatabaseDriverMysql { @@ -297,9 +303,9 @@ func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategor JOIN SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id WHERE - SidebarChannels.UserId = :UserId - AND SidebarChannels.ChannelId IN ` + channelIdsKeys + ` - AND SidebarCategories.TeamId = :TeamId` + SidebarChannels.UserId = ? + AND SidebarChannels.ChannelId IN ` + placeHolder + ` + AND SidebarCategories.TeamId = ?` } else { deleteQuery = ` DELETE FROM @@ -308,36 +314,41 @@ func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategor SidebarCategories WHERE SidebarChannels.CategoryId = SidebarCategories.Id - AND SidebarChannels.UserId = :UserId - AND SidebarChannels.ChannelId IN ` + channelIdsKeys + ` - AND SidebarCategories.TeamId = :TeamId` + AND SidebarChannels.UserId = ? + AND SidebarChannels.ChannelId IN ` + placeHolder + ` + AND SidebarCategories.TeamId = ?` } - _, err = transaction.Exec(deleteQuery, deleteParams) + args := []interface{}{userId} + args = append(args, channelIdArgs...) + args = append(args, teamId) + _, err = transaction.Exec(deleteQuery, args...) if err != nil { return nil, errors.Wrap(err, "failed to delete SidebarChannels") } - var channels []interface{} + insertQuery := s.getQueryBuilder(). + Insert("SidebarChannels"). + Columns("ChannelId", "UserId", "CategoryId", "SortOrder") for i, channelID := range newCategory.Channels { - channels = append(channels, &model.SidebarChannel{ - ChannelId: channelID, - CategoryId: newCategoryId, - SortOrder: int64(i * model.MinimalSidebarSortDistance), - UserId: userId, - }) + insertQuery = insertQuery.Values(channelID, userId, newCategoryId, int64(i*model.MinimalSidebarSortDistance)) } - if err = transaction.Insert(channels...); err != nil { + sql, args, err := insertQuery.ToSql() + if err != nil { + return nil, errors.Wrap(err, "InsertSidebarChannels_Tosql") + } + + if _, err := transaction.Exec(sql, args...); err != nil { return nil, errors.Wrap(err, "failed to save SidebarChannels") } } // now we re-order the categories according to the new order - if err = s.updateSidebarCategoryOrderT(transaction, newOrder); err != nil { + if err := s.updateSidebarCategoryOrderT(transaction, newOrder); err != nil { return nil, err } - if err = transaction.Commit(); err != nil { + if err := transaction.Commit(); err != nil { return nil, errors.Wrap(err, "commit_transaction") } @@ -352,11 +363,11 @@ func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategor } func (s SqlChannelStore) completePopulatingCategoryChannels(category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return nil, errors.Wrap(err, "begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) result, err := s.completePopulatingCategoryChannelsT(transaction, category) if err != nil { @@ -399,7 +410,7 @@ func (s SqlChannelStore) completePopulatingCategoryChannelsT(db dbSelecter, cate }). Suffix(")") - var channels []string + channels := []string{} sql, args, err := s.getQueryBuilder(). Select("Id"). From("ChannelMembers"). @@ -415,7 +426,7 @@ func (s SqlChannelStore) completePopulatingCategoryChannelsT(db dbSelecter, cate return nil, errors.Wrap(err, "channel_tosql") } - if _, err = db.Select(&channels, sql, args...); err != nil { + if err := db.Select(&channels, sql, args...); err != nil { return nil, store.NewErrNotFound("ChannelMembers", "") } @@ -461,7 +472,7 @@ func (s SqlChannelStore) getSidebarCategoriesT(db dbSelecter, userId, teamId str Order: make([]string, 0), } - var categories []*sidebarCategoryForJoin + categories := []*sidebarCategoryForJoin{} query, args, err := s.getQueryBuilder(). Select("SidebarCategories.*", "SidebarChannels.ChannelId"). From("SidebarCategories"). @@ -475,7 +486,7 @@ func (s SqlChannelStore) getSidebarCategoriesT(db dbSelecter, userId, teamId str return nil, errors.Wrap(err, "sidebar_categories_tosql") } - if _, err = db.Select(&categories, query, args...); err != nil { + if err := db.Select(&categories, query, args...); err != nil { return nil, store.NewErrNotFound("SidebarCategories", fmt.Sprintf("userId=%s,teamId=%s", userId, teamId)) } @@ -509,11 +520,11 @@ func (s SqlChannelStore) getSidebarCategoriesT(db dbSelecter, userId, teamId str } func (s SqlChannelStore) GetSidebarCategories(userId, teamId string) (*model.OrderedSidebarCategories, error) { - return s.getSidebarCategoriesT(s.GetReplica(), userId, teamId) + return s.getSidebarCategoriesT(s.GetReplicaX(), userId, teamId) } func (s SqlChannelStore) GetSidebarCategoryOrder(userId, teamId string) ([]string, error) { - var ids []string + ids := []string{} sql, args, err := s.getQueryBuilder(). Select("Id"). @@ -528,43 +539,39 @@ func (s SqlChannelStore) GetSidebarCategoryOrder(userId, teamId string) ([]strin return nil, errors.Wrap(err, "sidebar_category_tosql") } - if _, err := s.GetReplica().Select(&ids, sql, args...); err != nil { + if err := s.GetReplicaX().Select(&ids, sql, args...); err != nil { return nil, store.NewErrNotFound("SidebarCategories", fmt.Sprintf("userId=%s,teamId=%s", userId, teamId)) } return ids, nil } -func (s SqlChannelStore) updateSidebarCategoryOrderT(transaction *gorp.Transaction, categoryOrder []string) error { - var newOrder []interface{} +func (s SqlChannelStore) updateSidebarCategoryOrderT(transaction *sqlxTxWrapper, categoryOrder []string) error { runningOrder := 0 for _, categoryId := range categoryOrder { - newOrder = append(newOrder, &model.SidebarCategory{ - Id: categoryId, - SortOrder: int64(runningOrder), - }) + sql, args, err := s.getQueryBuilder(). + Update("SidebarCategories"). + Set("SortOrder", runningOrder). + Where(sq.Eq{"Id": categoryId}).ToSql() + if err != nil { + return errors.Wrap(err, "updateSidebarCategoryOrderT_Tosql") + } + + if _, err := transaction.Exec(sql, args...); err != nil { + return errors.Wrap(err, "Error updating sidebar category order") + } runningOrder += model.MinimalSidebarSortDistance } - - // There's a bug in gorp where UpdateColumns messes up the stored query for any other attempt to use .Update or - // .UpdateColumns on this table, so it's okay to use here as long as we don't use those methods for SidebarCategories - // anywhere else. - if _, err := transaction.UpdateColumns(func(col *gorp.ColumnMap) bool { - return col.ColumnName == "SortOrder" - }, newOrder...); err != nil { - return errors.Wrap(err, "failed to update SidebarCategory") - } - return nil } func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []string) error { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return errors.Wrap(err, "begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) // Ensure no invalid categories are included and that no categories are left out existingOrder, err := s.GetSidebarCategoryOrder(userId, teamId) @@ -589,11 +596,11 @@ func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categ } } - if err = s.updateSidebarCategoryOrderT(transaction, categoryOrder); err != nil { + if err := s.updateSidebarCategoryOrderT(transaction, categoryOrder); err != nil { return err } - if err = transaction.Commit(); err != nil { + if err := transaction.Commit(); err != nil { return errors.Wrap(err, "commit_transaction") } @@ -602,11 +609,11 @@ func (s SqlChannelStore) UpdateSidebarCategoryOrder(userId, teamId string, categ //nolint:unparam func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, []*model.SidebarCategoryWithChannels, error) { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return nil, nil, errors.Wrap(err, "begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) updatedCategories := []*model.SidebarCategoryWithChannels{} originalCategories := []*model.SidebarCategoryWithChannels{} @@ -681,20 +688,24 @@ func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categori return nil, nil, errors.Wrap(err, "failed to delete SidebarChannels") } - var channels []interface{} runningOrder := 0 + insertQuery := s.getQueryBuilder(). + Insert("SidebarChannels"). + Columns("ChannelId", "UserId", "CategoryId", "SortOrder") for _, channelID := range category.Channels { - channels = append(channels, &model.SidebarChannel{ - ChannelId: channelID, - CategoryId: category.Id, - SortOrder: int64(runningOrder), - UserId: userId, - }) + insertQuery = insertQuery.Values(channelID, userId, category.Id, int64(runningOrder)) runningOrder += model.MinimalSidebarSortDistance } - if err = transaction.Insert(channels...); err != nil { - return nil, nil, errors.Wrap(err, "failed to save SidebarChannels") + if len(category.Channels) > 0 { + sql, args, err2 := insertQuery.ToSql() + if err2 != nil { + return nil, nil, errors.Wrap(err2, "InsertSidebarChannels_Tosql") + } + + if _, err2 := transaction.Exec(sql, args...); err2 != nil { + return nil, nil, errors.Wrap(err2, "failed to save SidebarChannels") + } } } @@ -768,11 +779,11 @@ func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categori // UpdateSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync // At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side) func (s SqlChannelStore) UpdateSidebarChannelsByPreferences(preferences model.Preferences) error { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return errors.Wrap(err, "UpdateSidebarChannelsByPreferences: begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) for _, preference := range preferences { preference := preference @@ -800,18 +811,12 @@ func (s SqlChannelStore) UpdateSidebarChannelsByPreferences(preferences model.Pr return nil } -func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Transaction, preference *model.Preference) error { +func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *sqlxTxWrapper, preference *model.Preference) error { if preference.Category != model.PreferenceCategoryFavoriteChannel { return nil } - // Delete any corresponding SidebarChannels entries in a Favorites category corresponding to this preference. This - // can't use the query builder because it uses DB-specific syntax - params := map[string]interface{}{ - "UserId": preference.UserId, - "ChannelId": preference.Name, - "CategoryType": model.SidebarCategoryFavorites, - } + // Delete any corresponding SidebarChannels entries in a Favorites category corresponding to this preference. var query string if s.DriverName() == model.DatabaseDriverMysql { query = ` @@ -822,9 +827,9 @@ func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Tr JOIN SidebarCategories ON SidebarChannels.CategoryId = SidebarCategories.Id WHERE - SidebarChannels.UserId = :UserId - AND SidebarChannels.ChannelId = :ChannelId - AND SidebarCategories.Type = :CategoryType` + SidebarChannels.UserId = ? + AND SidebarChannels.ChannelId = ? + AND SidebarCategories.Type = ?` } else { query = ` DELETE FROM @@ -833,30 +838,28 @@ func (s SqlChannelStore) removeSidebarEntriesForPreferenceT(transaction *gorp.Tr SidebarCategories WHERE SidebarChannels.CategoryId = SidebarCategories.Id - AND SidebarChannels.UserId = :UserId - AND SidebarChannels.ChannelId = :ChannelId - AND SidebarCategories.Type = :CategoryType` + AND SidebarChannels.UserId = ? + AND SidebarChannels.ChannelId = ? + AND SidebarCategories.Type = ?` } - if _, err := transaction.Exec(query, params); err != nil { + if _, err := transaction.Exec(query, preference.UserId, preference.Name, model.SidebarCategoryFavorites); err != nil { return errors.Wrap(err, "Failed to remove sidebar entries for preference") } return nil } -func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transaction, preference *model.Preference) error { +func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *sqlxTxWrapper, preference *model.Preference) error { if preference.Category != model.PreferenceCategoryFavoriteChannel { return nil } - var channel *model.Channel - if obj, err := transaction.Get(&model.Channel{}, preference.Name); err != nil { + var channel model.Channel + if err := transaction.Get(&channel, `SELECT * FROM Channels WHERE Id=?`, preference.Name); err != nil { return errors.Wrapf(err, "Failed to get favorited channel with id=%s", preference.Name) - } else if obj == nil { + } else if channel.Id == "" { return store.NewErrNotFound("Channel", preference.Name) - } else { - channel = obj.(*model.Channel) } // Get the IDs of the Favorites category/categories that the channel needs to be added to @@ -876,8 +879,8 @@ func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transa idsQuery, idsParams, _ := builder.ToSql() - var categoryIds []string - if _, err := transaction.Select(&categoryIds, idsQuery, idsParams...); err != nil { + categoryIds := []string{} + if err := transaction.Select(&categoryIds, idsQuery, idsParams...); err != nil { return errors.Wrap(err, "Failed to get Favorites sidebar categories") } @@ -919,11 +922,11 @@ func (s SqlChannelStore) addChannelToFavoritesCategoryT(transaction *gorp.Transa // DeleteSidebarChannelsByPreferences is called when the Preference table is being updated to keep SidebarCategories in sync // At the moment, it's only handling Favorites and NOT DMs/GMs (those will be handled client side) func (s SqlChannelStore) DeleteSidebarChannelsByPreferences(preferences model.Preferences) error { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return errors.Wrap(err, "DeleteSidebarChannelsByPreferences: begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) for _, preference := range preferences { preference := preference @@ -947,22 +950,17 @@ func (s SqlChannelStore) DeleteSidebarChannelsByPreferences(preferences model.Pr //nolint:unparam func (s SqlChannelStore) UpdateSidebarChannelCategoryOnMove(channel *model.Channel, newTeamId string) error { // if channel is being moved, remove it from the categories, since it's possible that there's no matching category in the new team - if _, err := s.GetMaster().Exec("DELETE FROM SidebarChannels WHERE ChannelId=:ChannelId", map[string]interface{}{"ChannelId": channel.Id}); err != nil { + if _, err := s.GetMasterX().Exec("DELETE FROM SidebarChannels WHERE ChannelId=?", channel.Id); err != nil { return errors.Wrapf(err, "failed to delete SidebarChannels with channelId=%s", channel.Id) } return nil } func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) error { - // if user leaves the team, clean his team related entries in sidebar channels and categories - params := map[string]interface{}{ - "UserId": userId, - "TeamId": teamId, - } - + // if user leaves the team, clean their team related entries in sidebar channels and categories var deleteQuery string if s.DriverName() == model.DatabaseDriverMysql { - deleteQuery = "DELETE SidebarChannels FROM SidebarChannels LEFT JOIN SidebarCategories ON SidebarCategories.Id = SidebarChannels.CategoryId WHERE SidebarCategories.TeamId=:TeamId AND SidebarCategories.UserId=:UserId" + deleteQuery = "DELETE SidebarChannels FROM SidebarChannels LEFT JOIN SidebarCategories ON SidebarCategories.Id = SidebarChannels.CategoryId WHERE SidebarCategories.TeamId=? AND SidebarCategories.UserId=?" } else { deleteQuery = ` DELETE FROM @@ -976,13 +974,13 @@ func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) error { SidebarCategories WHERE SidebarChannels.CategoryId = SidebarCategories.Id - AND SidebarCategories.TeamId = :TeamId - AND SidebarChannels.UserId = :UserId)` + AND SidebarCategories.TeamId = ? + AND SidebarChannels.UserId = ?)` } - if _, err := s.GetMaster().Exec(deleteQuery, params); err != nil { + if _, err := s.GetMasterX().Exec(deleteQuery, teamId, userId); err != nil { return errors.Wrap(err, "failed to delete from SidebarChannels") } - if _, err := s.GetMaster().Exec("DELETE FROM SidebarCategories WHERE SidebarCategories.TeamId = :TeamId AND SidebarCategories.UserId = :UserId", params); err != nil { + if _, err := s.GetMasterX().Exec("DELETE FROM SidebarCategories WHERE SidebarCategories.TeamId = ? AND SidebarCategories.UserId = ?", teamId, userId); err != nil { return errors.Wrap(err, "failed to delete from SidebarCategories") } return nil @@ -991,15 +989,15 @@ func (s SqlChannelStore) ClearSidebarOnTeamLeave(userId, teamId string) error { // DeleteSidebarCategory removes a custom category and moves any channels into it into the Channels and Direct Messages // categories respectively. Assumes that the provided user ID and team ID match the given category ID. func (s SqlChannelStore) DeleteSidebarCategory(categoryId string) error { - transaction, err := s.GetMaster().Begin() + transaction, err := s.GetMasterX().Beginx() if err != nil { return errors.Wrap(err, "begin_transaction") } - defer finalizeTransaction(transaction) + defer finalizeTransactionX(transaction) // Ensure that we're deleting a custom category - var category *model.SidebarCategory - if err = transaction.SelectOne(&category, "SELECT * FROM SidebarCategories WHERE Id = :Id", map[string]interface{}{"Id": categoryId}); err != nil { + var category model.SidebarCategory + if err = transaction.Get(&category, "SELECT * FROM SidebarCategories WHERE Id = ?", categoryId); err != nil { return errors.Wrapf(err, "failed to find SidebarCategories with id=%s", categoryId) } diff --git a/store/sqlstore/preference_store.go b/store/sqlstore/preference_store.go index 38da7a91b1..a9f5707794 100644 --- a/store/sqlstore/preference_store.go +++ b/store/sqlstore/preference_store.go @@ -5,11 +5,11 @@ package sqlstore import ( sq "github.com/Masterminds/squirrel" - "github.com/mattermost/gorp" + "github.com/pkg/errors" + "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" - "github.com/pkg/errors" ) type SqlPreferenceStore struct { @@ -67,7 +67,7 @@ func (s SqlPreferenceStore) Save(preferences model.Preferences) error { return nil } -func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *model.Preference) error { +func (s SqlPreferenceStore) save(transaction *sqlxTxWrapper, preference *model.Preference) error { preference.PreUpdate() if err := preference.IsValid(); err != nil { diff --git a/store/storetest/mocks/dbSelecter.go b/store/storetest/mocks/dbSelecter.go index 8343daf52a..52283f4414 100644 --- a/store/storetest/mocks/dbSelecter.go +++ b/store/storetest/mocks/dbSelecter.go @@ -12,27 +12,18 @@ type dbSelecter struct { } // Select provides a mock function with given fields: i, query, args -func (_m *dbSelecter) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) { +func (_m *dbSelecter) Select(i interface{}, query string, args ...interface{}) error { var _ca []interface{} _ca = append(_ca, i, query) _ca = append(_ca, args...) ret := _m.Called(_ca...) - var r0 []interface{} - if rf, ok := ret.Get(0).(func(interface{}, string, ...interface{}) []interface{}); ok { + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, string, ...interface{}) error); ok { r0 = rf(i, query, args...) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]interface{}) - } + r0 = ret.Error(0) } - var r1 error - if rf, ok := ret.Get(1).(func(interface{}, string, ...interface{}) error); ok { - r1 = rf(i, query, args...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 + return r0 }