From 937b6480d534b3051cadf4a892a9aa210bec579a Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 25 Oct 2018 13:25:27 -0400 Subject: MM-12342: merge the experimental channel store (#9681) * MM-12342: merge the experimental channel store * gofmt after upgrading to go 1.11 --- store/sqlstore/channel_store_experimental.go | 778 --------------------------- 1 file changed, 778 deletions(-) delete mode 100644 store/sqlstore/channel_store_experimental.go (limited to 'store/sqlstore/channel_store_experimental.go') diff --git a/store/sqlstore/channel_store_experimental.go b/store/sqlstore/channel_store_experimental.go deleted file mode 100644 index ed1b4c06a..000000000 --- a/store/sqlstore/channel_store_experimental.go +++ /dev/null @@ -1,778 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package sqlstore - -import ( - "fmt" - "net/http" - "sort" - "strconv" - "strings" - "sync/atomic" - - "github.com/pkg/errors" - - "github.com/mattermost/gorp" - "github.com/mattermost/mattermost-server/einterfaces" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" -) - -// publicChannel is a subset of the metadata corresponding to public channels only. -type publicChannel struct { - Id string `json:"id"` - DeleteAt int64 `json:"delete_at"` - TeamId string `json:"team_id"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - Header string `json:"header"` - Purpose string `json:"purpose"` -} - -type SqlChannelStoreExperimental struct { - SqlChannelStore - experimentalPublicChannelsMaterializationDisabled *uint32 -} - -func NewSqlChannelStoreExperimental(sqlStore SqlStore, metrics einterfaces.MetricsInterface, enabled bool) store.ChannelStore { - s := &SqlChannelStoreExperimental{ - SqlChannelStore: *NewSqlChannelStore(sqlStore, metrics).(*SqlChannelStore), - experimentalPublicChannelsMaterializationDisabled: new(uint32), - } - - if enabled { - // Forcibly log, since the default state is enabled and we want this on startup. - mlog.Info("Enabling experimental public channels materialization") - s.EnableExperimentalPublicChannelsMaterialization() - } else { - s.DisableExperimentalPublicChannelsMaterialization() - } - - if s.IsExperimentalPublicChannelsMaterializationEnabled() { - for _, db := range sqlStore.GetAllConns() { - tablePublicChannels := db.AddTableWithName(publicChannel{}, "PublicChannels").SetKeys(false, "Id") - tablePublicChannels.ColMap("Id").SetMaxSize(26) - tablePublicChannels.ColMap("TeamId").SetMaxSize(26) - tablePublicChannels.ColMap("DisplayName").SetMaxSize(64) - tablePublicChannels.ColMap("Name").SetMaxSize(64) - tablePublicChannels.SetUniqueTogether("Name", "TeamId") - tablePublicChannels.ColMap("Header").SetMaxSize(1024) - tablePublicChannels.ColMap("Purpose").SetMaxSize(250) - } - } - - return s -} - -// migratePublicChannels initializes the PublicChannels table with data created before this version -// of the Mattermost server kept it up-to-date. -func (s SqlChannelStoreExperimental) MigratePublicChannels() error { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.MigratePublicChannels() - } - - transaction, err := s.GetMaster().Begin() - if err != nil { - return err - } - - if _, err := transaction.Exec(` - INSERT INTO PublicChannels - (Id, DeleteAt, TeamId, DisplayName, Name, Header, Purpose) - SELECT - c.Id, c.DeleteAt, c.TeamId, c.DisplayName, c.Name, c.Header, c.Purpose - FROM - Channels c - LEFT JOIN - PublicChannels pc ON (pc.Id = c.Id) - WHERE - c.Type = 'O' - AND pc.Id IS NULL - `); err != nil { - return err - } - - if err := transaction.Commit(); err != nil { - return err - } - - return nil -} - -// DropPublicChannels removes the public channels table. -func (s SqlChannelStoreExperimental) DropPublicChannels() error { - _, err := s.GetMaster().Exec(` - DROP TABLE IF EXISTS PublicChannels - `) - if err != nil { - return errors.Wrap(err, "failed to drop public channels table") - } - - return nil -} - -func (s SqlChannelStoreExperimental) CreateIndexesIfNotExists() { - s.SqlChannelStore.CreateIndexesIfNotExists() - - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return - } - - s.CreateIndexIfNotExists("idx_publicchannels_team_id", "PublicChannels", "TeamId") - s.CreateIndexIfNotExists("idx_publicchannels_name", "PublicChannels", "Name") - s.CreateIndexIfNotExists("idx_publicchannels_delete_at", "PublicChannels", "DeleteAt") - if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { - s.CreateIndexIfNotExists("idx_publicchannels_name_lower", "PublicChannels", "lower(Name)") - s.CreateIndexIfNotExists("idx_publicchannels_displayname_lower", "PublicChannels", "lower(DisplayName)") - } - s.CreateFullTextIndexIfNotExists("idx_publicchannels_search_txt", "PublicChannels", "Name, DisplayName, Purpose") -} - -func (s SqlChannelStoreExperimental) upsertPublicChannelT(transaction *gorp.Transaction, channel *model.Channel) error { - publicChannel := &publicChannel{ - Id: channel.Id, - DeleteAt: channel.DeleteAt, - TeamId: channel.TeamId, - DisplayName: channel.DisplayName, - Name: channel.Name, - Header: channel.Header, - Purpose: channel.Purpose, - } - - if channel.Type != model.CHANNEL_OPEN { - if _, err := transaction.Delete(publicChannel); err != nil { - return errors.Wrap(err, "failed to delete public channel") - } - - return nil - } - - if s.DriverName() == model.DATABASE_DRIVER_MYSQL { - // Leverage native upsert for MySQL, since RowsAffected returns 0 if the row exists - // but no changes were made, breaking the update-then-insert paradigm below when - // the row already exists. (Postgres 9.4 doesn't support native upsert.) - if _, err := transaction.Exec(` - INSERT INTO - PublicChannels(Id, DeleteAt, TeamId, DisplayName, Name, Header, Purpose) - VALUES - (:Id, :DeleteAt, :TeamId, :DisplayName, :Name, :Header, :Purpose) - ON DUPLICATE KEY UPDATE - DeleteAt = :DeleteAt, - TeamId = :TeamId, - DisplayName = :DisplayName, - Name = :Name, - Header = :Header, - Purpose = :Purpose; - `, map[string]interface{}{ - "Id": publicChannel.Id, - "DeleteAt": publicChannel.DeleteAt, - "TeamId": publicChannel.TeamId, - "DisplayName": publicChannel.DisplayName, - "Name": publicChannel.Name, - "Header": publicChannel.Header, - "Purpose": publicChannel.Purpose, - }); err != nil { - return errors.Wrap(err, "failed to insert public channel") - } - } else { - count, err := transaction.Update(publicChannel) - if err != nil { - return errors.Wrap(err, "failed to update public channel") - } - if count > 0 { - return nil - } - - if err := transaction.Insert(publicChannel); err != nil { - return errors.Wrap(err, "failed to insert public channel") - } - } - - return nil -} - -func (s SqlChannelStoreExperimental) Save(channel *model.Channel, maxChannelsPerTeam int64) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.Save(channel, maxChannelsPerTeam) - } - - return store.Do(func(result *store.StoreResult) { - if channel.DeleteAt != 0 { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest) - return - } - - if channel.Type == model.CHANNEL_DIRECT { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.direct_channel.app_error", nil, "", http.StatusBadRequest) - return - } - - transaction, err := s.GetMaster().Begin() - if err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - *result = s.saveChannelT(transaction, channel, maxChannelsPerTeam) - if result.Err != nil { - transaction.Rollback() - return - } - - // Additionally propagate the write to the PublicChannels table. - if err := s.upsertPublicChannelT(transaction, result.Data.(*model.Channel)); err != nil { - transaction.Rollback() - result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.upsert_public_channel.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - if err := transaction.Commit(); err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func (s SqlChannelStoreExperimental) Update(channel *model.Channel) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.Update(channel) - } - - return store.Do(func(result *store.StoreResult) { - transaction, err := s.GetMaster().Begin() - if err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - *result = s.updateChannelT(transaction, channel) - if result.Err != nil { - transaction.Rollback() - return - } - - // Additionally propagate the write to the PublicChannels table. - if err := s.upsertPublicChannelT(transaction, result.Data.(*model.Channel)); err != nil { - transaction.Rollback() - result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.upsert_public_channel.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - if err := transaction.Commit(); err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func (s SqlChannelStoreExperimental) Delete(channelId string, time int64) store.StoreChannel { - // Call the experimental version first. - return s.SetDeleteAt(channelId, time, time) -} - -func (s SqlChannelStoreExperimental) Restore(channelId string, time int64) store.StoreChannel { - // Call the experimental version first. - return s.SetDeleteAt(channelId, 0, time) -} - -func (s SqlChannelStoreExperimental) SetDeleteAt(channelId string, deleteAt, updateAt int64) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.SetDeleteAt(channelId, deleteAt, updateAt) - } - - return store.Do(func(result *store.StoreResult) { - defer s.InvalidateChannel(channelId) - - transaction, err := s.GetMaster().Begin() - if err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.SetDeleteAt", "store.sql_channel.set_delete_at.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - *result = s.setDeleteAtT(transaction, channelId, deleteAt, updateAt) - if result.Err != nil { - transaction.Rollback() - return - } - - // Additionally propagate the write to the PublicChannels table. - if _, err := transaction.Exec(` - UPDATE - PublicChannels - SET - DeleteAt = :DeleteAt - WHERE - Id = :ChannelId - `, map[string]interface{}{ - "DeleteAt": deleteAt, - "ChannelId": channelId, - }); err != nil { - transaction.Rollback() - result.Err = model.NewAppError("SqlChannelStoreExperimental.SetDeleteAt", "store.sql_channel.set_delete_at.update_public_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) - return - } - - if err := transaction.Commit(); err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.SetDeleteAt", "store.sql_channel.set_delete_at.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func (s SqlChannelStoreExperimental) PermanentDeleteByTeam(teamId string) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.PermanentDeleteByTeam(teamId) - } - - return store.Do(func(result *store.StoreResult) { - transaction, err := s.GetMaster().Begin() - if err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - *result = s.permanentDeleteByTeamtT(transaction, teamId) - if result.Err != nil { - transaction.Rollback() - return - } - - // Additionally propagate the deletions to the PublicChannels table. - if _, err := transaction.Exec(` - DELETE FROM - PublicChannels - WHERE - TeamId = :TeamId - `, map[string]interface{}{ - "TeamId": teamId, - }); err != nil { - transaction.Rollback() - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDeleteByTeamt", "store.sql_channel.permanent_delete_by_team.delete_public_channels.app_error", nil, "team_id="+teamId+", "+err.Error(), http.StatusInternalServerError) - return - } - - if err := transaction.Commit(); err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func (s SqlChannelStoreExperimental) PermanentDelete(channelId string) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.PermanentDelete(channelId) - } - - return store.Do(func(result *store.StoreResult) { - transaction, err := s.GetMaster().Begin() - if err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - *result = s.permanentDeleteT(transaction, channelId) - if result.Err != nil { - transaction.Rollback() - return - } - - // Additionally propagate the deletion to the PublicChannels table. - if _, err := transaction.Exec(` - DELETE FROM - PublicChannels - WHERE - Id = :ChannelId - `, map[string]interface{}{ - "ChannelId": channelId, - }); err != nil { - transaction.Rollback() - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.delete_public_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) - return - } - - if err := transaction.Commit(); err != nil { - result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func (s SqlChannelStoreExperimental) GetMoreChannels(teamId string, userId string, offset int, limit int) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.GetMoreChannels(teamId, userId, offset, limit) - } - - return store.Do(func(result *store.StoreResult) { - data := &model.ChannelList{} - _, err := s.GetReplica().Select(data, ` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels c ON (c.Id = Channels.Id) - WHERE - c.TeamId = :TeamId - AND c.DeleteAt = 0 - AND c.Id NOT IN ( - SELECT - c.Id - FROM - PublicChannels c - JOIN - ChannelMembers cm ON (cm.ChannelId = c.Id) - WHERE - c.TeamId = :TeamId - AND cm.UserId = :UserId - AND c.DeleteAt = 0 - ) - ORDER BY - c.DisplayName - LIMIT :Limit - OFFSET :Offset - `, map[string]interface{}{ - "TeamId": teamId, - "UserId": userId, - "Limit": limit, - "Offset": offset, - }) - - if err != nil { - result.Err = model.NewAppError("SqlChannelStore.GetMoreChannels", "store.sql_channel.get_more_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error(), http.StatusInternalServerError) - return - } - - result.Data = data - }) -} - -func (s SqlChannelStoreExperimental) GetPublicChannelsForTeam(teamId string, offset int, limit int) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.GetPublicChannelsForTeam(teamId, offset, limit) - } - - return store.Do(func(result *store.StoreResult) { - data := &model.ChannelList{} - _, err := s.GetReplica().Select(data, ` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels pc ON (pc.Id = Channels.Id) - WHERE - pc.TeamId = :TeamId - AND pc.DeleteAt = 0 - ORDER BY pc.DisplayName - LIMIT :Limit - OFFSET :Offset - `, map[string]interface{}{ - "TeamId": teamId, - "Limit": limit, - "Offset": offset, - }) - - if err != nil { - result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsForTeam", "store.sql_channel.get_public_channels.get.app_error", nil, "teamId="+teamId+", err="+err.Error(), http.StatusInternalServerError) - return - } - - result.Data = data - }) -} - -func (s SqlChannelStoreExperimental) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.GetPublicChannelsByIdsForTeam(teamId, channelIds) - } - - return store.Do(func(result *store.StoreResult) { - props := make(map[string]interface{}) - props["teamId"] = teamId - - idQuery := "" - - for index, channelId := range channelIds { - if len(idQuery) > 0 { - idQuery += ", " - } - - props["channelId"+strconv.Itoa(index)] = channelId - idQuery += ":channelId" + strconv.Itoa(index) - } - - data := &model.ChannelList{} - _, err := s.GetReplica().Select(data, ` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels pc ON (pc.Id = Channels.Id) - WHERE - pc.TeamId = :teamId - AND pc.DeleteAt = 0 - AND pc.Id IN (`+idQuery+`) - ORDER BY pc.DisplayName - `, props) - - if err != nil { - result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsByIdsForTeam", "store.sql_channel.get_channels_by_ids.get.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if len(*data) == 0 { - result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsByIdsForTeam", "store.sql_channel.get_channels_by_ids.not_found.app_error", nil, "", http.StatusNotFound) - } - - result.Data = data - }) -} - -func (s SqlChannelStoreExperimental) AutocompleteInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.AutocompleteInTeam(teamId, term, includeDeleted) - } - - return store.Do(func(result *store.StoreResult) { - deleteFilter := "AND c.DeleteAt = 0" - if includeDeleted { - deleteFilter = "" - } - - queryFormat := ` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels c ON (c.Id = Channels.Id) - WHERE - c.TeamId = :TeamId - ` + deleteFilter + ` - %v - LIMIT 50 - ` - - var channels model.ChannelList - - if likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose"); likeClause == "" { - if _, err := s.GetReplica().Select(&channels, fmt.Sprintf(queryFormat, ""), map[string]interface{}{"TeamId": teamId}); err != nil { - result.Err = model.NewAppError("SqlChannelStore.AutocompleteInTeam", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError) - } - } else { - // Using a UNION results in index_merge and fulltext queries and is much faster than the ref - // query you would get using an OR of the LIKE and full-text clauses. - fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose") - likeQuery := fmt.Sprintf(queryFormat, "AND "+likeClause) - fulltextQuery := fmt.Sprintf(queryFormat, "AND "+fulltextClause) - query := fmt.Sprintf("(%v) UNION (%v) LIMIT 50", likeQuery, fulltextQuery) - - if _, err := s.GetReplica().Select(&channels, query, map[string]interface{}{"TeamId": teamId, "LikeTerm": likeTerm, "FulltextTerm": fulltextTerm}); err != nil { - result.Err = model.NewAppError("SqlChannelStore.AutocompleteInTeam", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError) - } - } - - sort.Slice(channels, func(a, b int) bool { - return strings.ToLower(channels[a].DisplayName) < strings.ToLower(channels[b].DisplayName) - }) - result.Data = &channels - }) -} - -func (s SqlChannelStoreExperimental) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.SearchInTeam(teamId, term, includeDeleted) - } - - return store.Do(func(result *store.StoreResult) { - deleteFilter := "AND c.DeleteAt = 0" - if includeDeleted { - deleteFilter = "" - } - - *result = s.performSearch(` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels c ON (c.Id = Channels.Id) - WHERE - c.TeamId = :TeamId - `+deleteFilter+` - SEARCH_CLAUSE - ORDER BY c.DisplayName - LIMIT 100 - `, term, map[string]interface{}{ - "TeamId": teamId, - }) - }) -} - -func (s SqlChannelStoreExperimental) SearchMore(userId string, teamId string, term string) store.StoreChannel { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.SearchMore(userId, teamId, term) - } - - return store.Do(func(result *store.StoreResult) { - *result = s.performSearch(` - SELECT - Channels.* - FROM - Channels - JOIN - PublicChannels c ON (c.Id = Channels.Id) - WHERE - c.TeamId = :TeamId - AND c.DeleteAt = 0 - AND c.Id NOT IN ( - SELECT - c.Id - FROM - PublicChannels c - JOIN - ChannelMembers cm ON (cm.ChannelId = c.Id) - WHERE - c.TeamId = :TeamId - AND cm.UserId = :UserId - AND c.DeleteAt = 0 - ) - SEARCH_CLAUSE - ORDER BY c.DisplayName - LIMIT 100 - `, term, map[string]interface{}{ - "TeamId": teamId, - "UserId": userId, - }) - }) -} - -func (s SqlChannelStoreExperimental) buildLIKEClause(term string, searchColumns string) (likeClause, likeTerm string) { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.buildLIKEClause(term, searchColumns) - } - - likeTerm = term - - // These chars must be removed from the like query. - for _, c := range ignoreLikeSearchChar { - likeTerm = strings.Replace(likeTerm, c, "", -1) - } - - // These chars must be escaped in the like query. - for _, c := range escapeLikeSearchChar { - likeTerm = strings.Replace(likeTerm, c, "*"+c, -1) - } - - if likeTerm == "" { - return - } - - // Prepare the LIKE portion of the query. - var searchFields []string - for _, field := range strings.Split(searchColumns, ", ") { - if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { - searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower(%s) escape '*'", field, ":LikeTerm")) - } else { - searchFields = append(searchFields, fmt.Sprintf("%s LIKE %s escape '*'", field, ":LikeTerm")) - } - } - - likeClause = fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")) - likeTerm += "%" - return -} - -func (s SqlChannelStoreExperimental) buildFulltextClause(term string, searchColumns string) (fulltextClause, fulltextTerm string) { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.buildFulltextClause(term, searchColumns) - } - - // Copy the terms as we will need to prepare them differently for each search type. - fulltextTerm = term - - // These chars must be treated as spaces in the fulltext query. - for _, c := range spaceFulltextSearchChar { - fulltextTerm = strings.Replace(fulltextTerm, c, " ", -1) - } - - // Prepare the FULLTEXT portion of the query. - if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { - fulltextTerm = strings.Replace(fulltextTerm, "|", "", -1) - - splitTerm := strings.Fields(fulltextTerm) - for i, t := range strings.Fields(fulltextTerm) { - if i == len(splitTerm)-1 { - splitTerm[i] = t + ":*" - } else { - splitTerm[i] = t + ":* &" - } - } - - fulltextTerm = strings.Join(splitTerm, " ") - - fulltextClause = fmt.Sprintf("((%s) @@ to_tsquery(:FulltextTerm))", convertMySQLFullTextColumnsToPostgres(searchColumns)) - } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { - splitTerm := strings.Fields(fulltextTerm) - for i, t := range strings.Fields(fulltextTerm) { - splitTerm[i] = "+" + t + "*" - } - - fulltextTerm = strings.Join(splitTerm, " ") - - fulltextClause = fmt.Sprintf("MATCH(%s) AGAINST (:FulltextTerm IN BOOLEAN MODE)", searchColumns) - } - - return -} - -func (s SqlChannelStoreExperimental) performSearch(searchQuery string, term string, parameters map[string]interface{}) store.StoreResult { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - return s.SqlChannelStore.performSearch(searchQuery, term, parameters) - } - - result := store.StoreResult{} - - likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose") - if likeTerm == "" { - // If the likeTerm is empty after preparing, then don't bother searching. - searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) - } else { - parameters["LikeTerm"] = likeTerm - fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose") - parameters["FulltextTerm"] = fulltextTerm - searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "AND ("+likeClause+" OR "+fulltextClause+")", 1) - } - - var channels model.ChannelList - - if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil { - result.Err = model.NewAppError("SqlChannelStore.Search", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError) - return result - } - - result.Data = &channels - return result -} - -func (s SqlChannelStoreExperimental) EnableExperimentalPublicChannelsMaterialization() { - if !s.IsExperimentalPublicChannelsMaterializationEnabled() { - mlog.Info("Enabling experimental public channels materialization") - } - - atomic.StoreUint32(s.experimentalPublicChannelsMaterializationDisabled, 0) -} - -func (s SqlChannelStoreExperimental) DisableExperimentalPublicChannelsMaterialization() { - if s.IsExperimentalPublicChannelsMaterializationEnabled() { - mlog.Info("Disabling experimental public channels materialization") - } - - atomic.StoreUint32(s.experimentalPublicChannelsMaterializationDisabled, 1) -} - -func (s SqlChannelStoreExperimental) IsExperimentalPublicChannelsMaterializationEnabled() bool { - return atomic.LoadUint32(s.experimentalPublicChannelsMaterializationDisabled) == 0 -} -- cgit v1.2.3-1-g7c22