github.com/lologarithm/mattermost-server@v5.3.2-0.20181002060438-c82a84ed765b+incompatible/store/sqlstore/channel_store_experimental.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package sqlstore
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"sync/atomic"
    13  
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/mattermost/gorp"
    17  	"github.com/mattermost/mattermost-server/einterfaces"
    18  	"github.com/mattermost/mattermost-server/mlog"
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/store"
    21  )
    22  
    23  // publicChannel is a subset of the metadata corresponding to public channels only.
    24  type publicChannel struct {
    25  	Id          string `json:"id"`
    26  	DeleteAt    int64  `json:"delete_at"`
    27  	TeamId      string `json:"team_id"`
    28  	DisplayName string `json:"display_name"`
    29  	Name        string `json:"name"`
    30  	Header      string `json:"header"`
    31  	Purpose     string `json:"purpose"`
    32  }
    33  
    34  type SqlChannelStoreExperimental struct {
    35  	SqlChannelStore
    36  	experimentalPublicChannelsMaterializationDisabled *uint32
    37  }
    38  
    39  func NewSqlChannelStoreExperimental(sqlStore SqlStore, metrics einterfaces.MetricsInterface, enabled bool) store.ChannelStore {
    40  	s := &SqlChannelStoreExperimental{
    41  		SqlChannelStore: *NewSqlChannelStore(sqlStore, metrics).(*SqlChannelStore),
    42  		experimentalPublicChannelsMaterializationDisabled: new(uint32),
    43  	}
    44  
    45  	if enabled {
    46  		// Forcibly log, since the default state is enabled and we want this on startup.
    47  		mlog.Info("Enabling experimental public channels materialization")
    48  		s.EnableExperimentalPublicChannelsMaterialization()
    49  	} else {
    50  		s.DisableExperimentalPublicChannelsMaterialization()
    51  	}
    52  
    53  	if s.IsExperimentalPublicChannelsMaterializationEnabled() {
    54  		for _, db := range sqlStore.GetAllConns() {
    55  			tablePublicChannels := db.AddTableWithName(publicChannel{}, "PublicChannels").SetKeys(false, "Id")
    56  			tablePublicChannels.ColMap("Id").SetMaxSize(26)
    57  			tablePublicChannels.ColMap("TeamId").SetMaxSize(26)
    58  			tablePublicChannels.ColMap("DisplayName").SetMaxSize(64)
    59  			tablePublicChannels.ColMap("Name").SetMaxSize(64)
    60  			tablePublicChannels.SetUniqueTogether("Name", "TeamId")
    61  			tablePublicChannels.ColMap("Header").SetMaxSize(1024)
    62  			tablePublicChannels.ColMap("Purpose").SetMaxSize(250)
    63  		}
    64  	}
    65  
    66  	return s
    67  }
    68  
    69  // migratePublicChannels initializes the PublicChannels table with data created before this version
    70  // of the Mattermost server kept it up-to-date.
    71  func (s SqlChannelStoreExperimental) MigratePublicChannels() error {
    72  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
    73  		return s.SqlChannelStore.MigratePublicChannels()
    74  	}
    75  
    76  	transaction, err := s.GetMaster().Begin()
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	if _, err := transaction.Exec(`
    82  		INSERT INTO PublicChannels
    83  		    (Id, DeleteAt, TeamId, DisplayName, Name, Header, Purpose)
    84  		SELECT
    85  		    c.Id, c.DeleteAt, c.TeamId, c.DisplayName, c.Name, c.Header, c.Purpose
    86  		FROM
    87  		    Channels c
    88  		LEFT JOIN
    89  		    PublicChannels pc ON (pc.Id = c.Id)
    90  		WHERE
    91  		    c.Type = 'O'
    92  		AND pc.Id IS NULL
    93  	`); err != nil {
    94  		return err
    95  	}
    96  
    97  	if err := transaction.Commit(); err != nil {
    98  		return err
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // DropPublicChannels removes the public channels table.
   105  func (s SqlChannelStoreExperimental) DropPublicChannels() error {
   106  	_, err := s.GetMaster().Exec(`
   107  		DROP TABLE IF EXISTS PublicChannels
   108  	`)
   109  	if err != nil {
   110  		return errors.Wrap(err, "failed to drop public channels table")
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  func (s SqlChannelStoreExperimental) CreateIndexesIfNotExists() {
   117  	s.SqlChannelStore.CreateIndexesIfNotExists()
   118  
   119  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   120  		return
   121  	}
   122  
   123  	s.CreateIndexIfNotExists("idx_publicchannels_team_id", "PublicChannels", "TeamId")
   124  	s.CreateIndexIfNotExists("idx_publicchannels_name", "PublicChannels", "Name")
   125  	s.CreateIndexIfNotExists("idx_publicchannels_delete_at", "PublicChannels", "DeleteAt")
   126  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   127  		s.CreateIndexIfNotExists("idx_publicchannels_name_lower", "PublicChannels", "lower(Name)")
   128  		s.CreateIndexIfNotExists("idx_publicchannels_displayname_lower", "PublicChannels", "lower(DisplayName)")
   129  	}
   130  	s.CreateFullTextIndexIfNotExists("idx_publicchannels_search_txt", "PublicChannels", "Name, DisplayName, Purpose")
   131  }
   132  
   133  func (s SqlChannelStoreExperimental) upsertPublicChannelT(transaction *gorp.Transaction, channel *model.Channel) error {
   134  	publicChannel := &publicChannel{
   135  		Id:          channel.Id,
   136  		DeleteAt:    channel.DeleteAt,
   137  		TeamId:      channel.TeamId,
   138  		DisplayName: channel.DisplayName,
   139  		Name:        channel.Name,
   140  		Header:      channel.Header,
   141  		Purpose:     channel.Purpose,
   142  	}
   143  
   144  	if channel.Type != model.CHANNEL_OPEN {
   145  		if _, err := transaction.Delete(publicChannel); err != nil {
   146  			return errors.Wrap(err, "failed to delete public channel")
   147  		}
   148  
   149  		return nil
   150  	}
   151  
   152  	if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   153  		// Leverage native upsert for MySQL, since RowsAffected returns 0 if the row exists
   154  		// but no changes were made, breaking the update-then-insert paradigm below when
   155  		// the row already exists. (Postgres 9.4 doesn't support native upsert.)
   156  		if _, err := transaction.Exec(`
   157  			INSERT INTO
   158  			    PublicChannels(Id, DeleteAt, TeamId, DisplayName, Name, Header, Purpose)
   159  			VALUES
   160  			    (:Id, :DeleteAt, :TeamId, :DisplayName, :Name, :Header, :Purpose)
   161  			ON DUPLICATE KEY UPDATE
   162  			    DeleteAt = :DeleteAt,
   163  			    TeamId = :TeamId,
   164  			    DisplayName = :DisplayName,
   165  			    Name = :Name,
   166  			    Header = :Header,
   167  			    Purpose = :Purpose;
   168  		`, map[string]interface{}{
   169  			"Id":          publicChannel.Id,
   170  			"DeleteAt":    publicChannel.DeleteAt,
   171  			"TeamId":      publicChannel.TeamId,
   172  			"DisplayName": publicChannel.DisplayName,
   173  			"Name":        publicChannel.Name,
   174  			"Header":      publicChannel.Header,
   175  			"Purpose":     publicChannel.Purpose,
   176  		}); err != nil {
   177  			return errors.Wrap(err, "failed to insert public channel")
   178  		}
   179  	} else {
   180  		count, err := transaction.Update(publicChannel)
   181  		if err != nil {
   182  			return errors.Wrap(err, "failed to update public channel")
   183  		}
   184  		if count > 0 {
   185  			return nil
   186  		}
   187  
   188  		if err := transaction.Insert(publicChannel); err != nil {
   189  			return errors.Wrap(err, "failed to insert public channel")
   190  		}
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func (s SqlChannelStoreExperimental) Save(channel *model.Channel, maxChannelsPerTeam int64) store.StoreChannel {
   197  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   198  		return s.SqlChannelStore.Save(channel, maxChannelsPerTeam)
   199  	}
   200  
   201  	return store.Do(func(result *store.StoreResult) {
   202  		if channel.DeleteAt != 0 {
   203  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest)
   204  			return
   205  		}
   206  
   207  		if channel.Type == model.CHANNEL_DIRECT {
   208  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.direct_channel.app_error", nil, "", http.StatusBadRequest)
   209  			return
   210  		}
   211  
   212  		transaction, err := s.GetMaster().Begin()
   213  		if err != nil {
   214  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   215  			return
   216  		}
   217  
   218  		*result = s.saveChannelT(transaction, channel, maxChannelsPerTeam)
   219  		if result.Err != nil {
   220  			transaction.Rollback()
   221  			return
   222  		}
   223  
   224  		// Additionally propagate the write to the PublicChannels table.
   225  		if err := s.upsertPublicChannelT(transaction, result.Data.(*model.Channel)); err != nil {
   226  			transaction.Rollback()
   227  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.upsert_public_channel.app_error", nil, err.Error(), http.StatusInternalServerError)
   228  			return
   229  		}
   230  
   231  		if err := transaction.Commit(); err != nil {
   232  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Save", "store.sql_channel.save.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   233  			return
   234  		}
   235  	})
   236  }
   237  
   238  func (s SqlChannelStoreExperimental) Update(channel *model.Channel) store.StoreChannel {
   239  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   240  		return s.SqlChannelStore.Update(channel)
   241  	}
   242  
   243  	return store.Do(func(result *store.StoreResult) {
   244  		transaction, err := s.GetMaster().Begin()
   245  		if err != nil {
   246  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   247  			return
   248  		}
   249  
   250  		*result = s.updateChannelT(transaction, channel)
   251  		if result.Err != nil {
   252  			transaction.Rollback()
   253  			return
   254  		}
   255  
   256  		// Additionally propagate the write to the PublicChannels table.
   257  		if err := s.upsertPublicChannelT(transaction, result.Data.(*model.Channel)); err != nil {
   258  			transaction.Rollback()
   259  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.upsert_public_channel.app_error", nil, err.Error(), http.StatusInternalServerError)
   260  			return
   261  		}
   262  
   263  		if err := transaction.Commit(); err != nil {
   264  			result.Err = model.NewAppError("SqlChannelStoreExperimental.Update", "store.sql_channel.update.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   265  			return
   266  		}
   267  	})
   268  }
   269  
   270  func (s SqlChannelStoreExperimental) Delete(channelId string, time int64) store.StoreChannel {
   271  	// Call the experimental version first.
   272  	return s.SetDeleteAt(channelId, time, time)
   273  }
   274  
   275  func (s SqlChannelStoreExperimental) Restore(channelId string, time int64) store.StoreChannel {
   276  	// Call the experimental version first.
   277  	return s.SetDeleteAt(channelId, 0, time)
   278  }
   279  
   280  func (s SqlChannelStoreExperimental) SetDeleteAt(channelId string, deleteAt, updateAt int64) store.StoreChannel {
   281  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   282  		return s.SqlChannelStore.SetDeleteAt(channelId, deleteAt, updateAt)
   283  	}
   284  
   285  	return store.Do(func(result *store.StoreResult) {
   286  		transaction, err := s.GetMaster().Begin()
   287  		if err != nil {
   288  			result.Err = model.NewAppError("SqlChannelStoreExperimental.SetDeleteAt", "store.sql_channel.set_delete_at.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   289  			return
   290  		}
   291  
   292  		*result = s.setDeleteAtT(transaction, channelId, deleteAt, updateAt)
   293  		if result.Err != nil {
   294  			transaction.Rollback()
   295  			return
   296  		}
   297  
   298  		// Additionally propagate the write to the PublicChannels table.
   299  		if _, err := transaction.Exec(`
   300  			UPDATE
   301  			    PublicChannels 
   302  			SET 
   303  			    DeleteAt = :DeleteAt
   304  			WHERE 
   305  			    Id = :ChannelId
   306  		`, map[string]interface{}{
   307  			"DeleteAt":  deleteAt,
   308  			"ChannelId": channelId,
   309  		}); err != nil {
   310  			transaction.Rollback()
   311  			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)
   312  			return
   313  		}
   314  
   315  		if err := transaction.Commit(); err != nil {
   316  			result.Err = model.NewAppError("SqlChannelStoreExperimental.SetDeleteAt", "store.sql_channel.set_delete_at.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   317  			return
   318  		}
   319  	})
   320  }
   321  
   322  func (s SqlChannelStoreExperimental) PermanentDeleteByTeam(teamId string) store.StoreChannel {
   323  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   324  		return s.SqlChannelStore.PermanentDeleteByTeam(teamId)
   325  	}
   326  
   327  	return store.Do(func(result *store.StoreResult) {
   328  		transaction, err := s.GetMaster().Begin()
   329  		if err != nil {
   330  			result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   331  			return
   332  		}
   333  
   334  		*result = s.permanentDeleteByTeamtT(transaction, teamId)
   335  		if result.Err != nil {
   336  			transaction.Rollback()
   337  			return
   338  		}
   339  
   340  		// Additionally propagate the deletions to the PublicChannels table.
   341  		if _, err := transaction.Exec(`
   342  			DELETE FROM
   343  			    PublicChannels 
   344  			WHERE
   345  			    TeamId = :TeamId
   346  		`, map[string]interface{}{
   347  			"TeamId": teamId,
   348  		}); err != nil {
   349  			transaction.Rollback()
   350  			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)
   351  			return
   352  		}
   353  
   354  		if err := transaction.Commit(); err != nil {
   355  			result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   356  			return
   357  		}
   358  	})
   359  }
   360  
   361  func (s SqlChannelStoreExperimental) PermanentDelete(channelId string) store.StoreChannel {
   362  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   363  		return s.SqlChannelStore.PermanentDelete(channelId)
   364  	}
   365  
   366  	return store.Do(func(result *store.StoreResult) {
   367  		transaction, err := s.GetMaster().Begin()
   368  		if err != nil {
   369  			result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   370  			return
   371  		}
   372  
   373  		*result = s.permanentDeleteT(transaction, channelId)
   374  		if result.Err != nil {
   375  			transaction.Rollback()
   376  			return
   377  		}
   378  
   379  		// Additionally propagate the deletion to the PublicChannels table.
   380  		if _, err := transaction.Exec(`
   381  			DELETE FROM
   382  			    PublicChannels 
   383  			WHERE
   384  			    Id = :ChannelId
   385  		`, map[string]interface{}{
   386  			"ChannelId": channelId,
   387  		}); err != nil {
   388  			transaction.Rollback()
   389  			result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.delete_public_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
   390  			return
   391  		}
   392  
   393  		if err := transaction.Commit(); err != nil {
   394  			result.Err = model.NewAppError("SqlChannelStoreExperimental.PermanentDelete", "store.sql_channel.permanent_delete.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
   395  			return
   396  		}
   397  	})
   398  }
   399  
   400  func (s SqlChannelStoreExperimental) GetMoreChannels(teamId string, userId string, offset int, limit int) store.StoreChannel {
   401  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   402  		return s.SqlChannelStore.GetMoreChannels(teamId, userId, offset, limit)
   403  	}
   404  
   405  	return store.Do(func(result *store.StoreResult) {
   406  		data := &model.ChannelList{}
   407  		_, err := s.GetReplica().Select(data, `
   408  			SELECT
   409  			    Channels.*
   410  			FROM
   411  			    Channels
   412  			JOIN
   413  			    PublicChannels c ON (c.Id = Channels.Id)
   414  			WHERE
   415  			    c.TeamId = :TeamId
   416  			AND c.DeleteAt = 0
   417  			AND c.Id NOT IN (
   418  			    SELECT
   419  			        c.Id
   420  			    FROM
   421  			        PublicChannels c
   422  			    JOIN
   423  			        ChannelMembers cm ON (cm.ChannelId = c.Id)
   424  			    WHERE
   425  			        c.TeamId = :TeamId
   426  			    AND cm.UserId = :UserId
   427  			    AND c.DeleteAt = 0
   428  			)
   429  			ORDER BY
   430  				c.DisplayName
   431  			LIMIT :Limit
   432  			OFFSET :Offset
   433  		`, map[string]interface{}{
   434  			"TeamId": teamId,
   435  			"UserId": userId,
   436  			"Limit":  limit,
   437  			"Offset": offset,
   438  		})
   439  
   440  		if err != nil {
   441  			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)
   442  			return
   443  		}
   444  
   445  		result.Data = data
   446  	})
   447  }
   448  
   449  func (s SqlChannelStoreExperimental) GetPublicChannelsForTeam(teamId string, offset int, limit int) store.StoreChannel {
   450  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   451  		return s.SqlChannelStore.GetPublicChannelsForTeam(teamId, offset, limit)
   452  	}
   453  
   454  	return store.Do(func(result *store.StoreResult) {
   455  		data := &model.ChannelList{}
   456  		_, err := s.GetReplica().Select(data, `
   457  			SELECT
   458  			    Channels.*
   459  			FROM
   460  			    Channels
   461  			JOIN
   462  			    PublicChannels pc ON (pc.Id = Channels.Id)
   463  			WHERE
   464  			    pc.TeamId = :TeamId
   465  			AND pc.DeleteAt = 0
   466  			ORDER BY pc.DisplayName
   467  			LIMIT :Limit
   468  			OFFSET :Offset
   469  		`, map[string]interface{}{
   470  			"TeamId": teamId,
   471  			"Limit":  limit,
   472  			"Offset": offset,
   473  		})
   474  
   475  		if err != nil {
   476  			result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsForTeam", "store.sql_channel.get_public_channels.get.app_error", nil, "teamId="+teamId+", err="+err.Error(), http.StatusInternalServerError)
   477  			return
   478  		}
   479  
   480  		result.Data = data
   481  	})
   482  }
   483  
   484  func (s SqlChannelStoreExperimental) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) store.StoreChannel {
   485  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   486  		return s.SqlChannelStore.GetPublicChannelsByIdsForTeam(teamId, channelIds)
   487  	}
   488  
   489  	return store.Do(func(result *store.StoreResult) {
   490  		props := make(map[string]interface{})
   491  		props["teamId"] = teamId
   492  
   493  		idQuery := ""
   494  
   495  		for index, channelId := range channelIds {
   496  			if len(idQuery) > 0 {
   497  				idQuery += ", "
   498  			}
   499  
   500  			props["channelId"+strconv.Itoa(index)] = channelId
   501  			idQuery += ":channelId" + strconv.Itoa(index)
   502  		}
   503  
   504  		data := &model.ChannelList{}
   505  		_, err := s.GetReplica().Select(data, `
   506  			SELECT
   507  			    Channels.*
   508  			FROM
   509  			    Channels
   510  			JOIN
   511  			    PublicChannels pc ON (pc.Id = Channels.Id)
   512  			WHERE
   513  			    pc.TeamId = :teamId
   514  			AND pc.DeleteAt = 0
   515  			AND pc.Id IN (`+idQuery+`)
   516  			ORDER BY pc.DisplayName
   517  		`, props)
   518  
   519  		if err != nil {
   520  			result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsByIdsForTeam", "store.sql_channel.get_channels_by_ids.get.app_error", nil, err.Error(), http.StatusInternalServerError)
   521  		}
   522  
   523  		if len(*data) == 0 {
   524  			result.Err = model.NewAppError("SqlChannelStore.GetPublicChannelsByIdsForTeam", "store.sql_channel.get_channels_by_ids.not_found.app_error", nil, "", http.StatusNotFound)
   525  		}
   526  
   527  		result.Data = data
   528  	})
   529  }
   530  
   531  func (s SqlChannelStoreExperimental) AutocompleteInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel {
   532  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   533  		return s.SqlChannelStore.AutocompleteInTeam(teamId, term, includeDeleted)
   534  	}
   535  
   536  	return store.Do(func(result *store.StoreResult) {
   537  		deleteFilter := "AND c.DeleteAt = 0"
   538  		if includeDeleted {
   539  			deleteFilter = ""
   540  		}
   541  
   542  		queryFormat := `
   543  			SELECT
   544  			    Channels.*
   545  			FROM
   546  			    Channels
   547  			JOIN
   548  			    PublicChannels c ON (c.Id = Channels.Id)
   549  			WHERE
   550  			    c.TeamId = :TeamId
   551  			    ` + deleteFilter + `
   552  			    %v
   553  			LIMIT 50
   554  		`
   555  
   556  		var channels model.ChannelList
   557  
   558  		if likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose"); likeClause == "" {
   559  			if _, err := s.GetReplica().Select(&channels, fmt.Sprintf(queryFormat, ""), map[string]interface{}{"TeamId": teamId}); err != nil {
   560  				result.Err = model.NewAppError("SqlChannelStore.AutocompleteInTeam", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError)
   561  			}
   562  		} else {
   563  			// Using a UNION results in index_merge and fulltext queries and is much faster than the ref
   564  			// query you would get using an OR of the LIKE and full-text clauses.
   565  			fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose")
   566  			likeQuery := fmt.Sprintf(queryFormat, "AND "+likeClause)
   567  			fulltextQuery := fmt.Sprintf(queryFormat, "AND "+fulltextClause)
   568  			query := fmt.Sprintf("(%v) UNION (%v) LIMIT 50", likeQuery, fulltextQuery)
   569  
   570  			if _, err := s.GetReplica().Select(&channels, query, map[string]interface{}{"TeamId": teamId, "LikeTerm": likeTerm, "FulltextTerm": fulltextTerm}); err != nil {
   571  				result.Err = model.NewAppError("SqlChannelStore.AutocompleteInTeam", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError)
   572  			}
   573  		}
   574  
   575  		sort.Slice(channels, func(a, b int) bool {
   576  			return strings.ToLower(channels[a].DisplayName) < strings.ToLower(channels[b].DisplayName)
   577  		})
   578  		result.Data = &channels
   579  	})
   580  }
   581  
   582  func (s SqlChannelStoreExperimental) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel {
   583  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   584  		return s.SqlChannelStore.SearchInTeam(teamId, term, includeDeleted)
   585  	}
   586  
   587  	return store.Do(func(result *store.StoreResult) {
   588  		deleteFilter := "AND c.DeleteAt = 0"
   589  		if includeDeleted {
   590  			deleteFilter = ""
   591  		}
   592  
   593  		*result = s.performSearch(`
   594  			SELECT
   595  			    Channels.*
   596  			FROM
   597  			    Channels
   598  			JOIN
   599  			    PublicChannels c ON (c.Id = Channels.Id)
   600  			WHERE
   601  			    c.TeamId = :TeamId
   602  			    `+deleteFilter+`
   603  			    SEARCH_CLAUSE
   604  			ORDER BY c.DisplayName
   605  			LIMIT 100
   606  		`, term, map[string]interface{}{
   607  			"TeamId": teamId,
   608  		})
   609  	})
   610  }
   611  
   612  func (s SqlChannelStoreExperimental) SearchMore(userId string, teamId string, term string) store.StoreChannel {
   613  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   614  		return s.SqlChannelStore.SearchMore(userId, teamId, term)
   615  	}
   616  
   617  	return store.Do(func(result *store.StoreResult) {
   618  		*result = s.performSearch(`
   619  			SELECT
   620  			    Channels.*
   621  			FROM
   622  			    Channels
   623  			JOIN
   624  			    PublicChannels c ON (c.Id = Channels.Id)
   625  			WHERE
   626  			    c.TeamId = :TeamId
   627  			AND c.DeleteAt = 0
   628  			AND c.Id NOT IN (
   629  			    SELECT
   630  			        c.Id
   631  			    FROM
   632  			        PublicChannels c
   633  			    JOIN
   634  			        ChannelMembers cm ON (cm.ChannelId = c.Id)
   635  			    WHERE
   636  			        c.TeamId = :TeamId
   637  			    AND cm.UserId = :UserId
   638  			    AND c.DeleteAt = 0
   639  		        )
   640  			SEARCH_CLAUSE
   641  			ORDER BY c.DisplayName
   642  			LIMIT 100
   643  		`, term, map[string]interface{}{
   644  			"TeamId": teamId,
   645  			"UserId": userId,
   646  		})
   647  	})
   648  }
   649  
   650  func (s SqlChannelStoreExperimental) buildLIKEClause(term string, searchColumns string) (likeClause, likeTerm string) {
   651  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   652  		return s.SqlChannelStore.buildLIKEClause(term, searchColumns)
   653  	}
   654  
   655  	likeTerm = term
   656  
   657  	// These chars must be removed from the like query.
   658  	for _, c := range ignoreLikeSearchChar {
   659  		likeTerm = strings.Replace(likeTerm, c, "", -1)
   660  	}
   661  
   662  	// These chars must be escaped in the like query.
   663  	for _, c := range escapeLikeSearchChar {
   664  		likeTerm = strings.Replace(likeTerm, c, "*"+c, -1)
   665  	}
   666  
   667  	if likeTerm == "" {
   668  		return
   669  	}
   670  
   671  	// Prepare the LIKE portion of the query.
   672  	var searchFields []string
   673  	for _, field := range strings.Split(searchColumns, ", ") {
   674  		if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   675  			searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower(%s) escape '*'", field, ":LikeTerm"))
   676  		} else {
   677  			searchFields = append(searchFields, fmt.Sprintf("%s LIKE %s escape '*'", field, ":LikeTerm"))
   678  		}
   679  	}
   680  
   681  	likeClause = fmt.Sprintf("(%s)", strings.Join(searchFields, " OR "))
   682  	likeTerm += "%"
   683  	return
   684  }
   685  
   686  func (s SqlChannelStoreExperimental) buildFulltextClause(term string, searchColumns string) (fulltextClause, fulltextTerm string) {
   687  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   688  		return s.SqlChannelStore.buildFulltextClause(term, searchColumns)
   689  	}
   690  
   691  	// Copy the terms as we will need to prepare them differently for each search type.
   692  	fulltextTerm = term
   693  
   694  	// These chars must be treated as spaces in the fulltext query.
   695  	for _, c := range spaceFulltextSearchChar {
   696  		fulltextTerm = strings.Replace(fulltextTerm, c, " ", -1)
   697  	}
   698  
   699  	// Prepare the FULLTEXT portion of the query.
   700  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   701  		fulltextTerm = strings.Replace(fulltextTerm, "|", "", -1)
   702  
   703  		splitTerm := strings.Fields(fulltextTerm)
   704  		for i, t := range strings.Fields(fulltextTerm) {
   705  			if i == len(splitTerm)-1 {
   706  				splitTerm[i] = t + ":*"
   707  			} else {
   708  				splitTerm[i] = t + ":* &"
   709  			}
   710  		}
   711  
   712  		fulltextTerm = strings.Join(splitTerm, " ")
   713  
   714  		fulltextClause = fmt.Sprintf("((%s) @@ to_tsquery(:FulltextTerm))", convertMySQLFullTextColumnsToPostgres(searchColumns))
   715  	} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
   716  		splitTerm := strings.Fields(fulltextTerm)
   717  		for i, t := range strings.Fields(fulltextTerm) {
   718  			splitTerm[i] = "+" + t + "*"
   719  		}
   720  
   721  		fulltextTerm = strings.Join(splitTerm, " ")
   722  
   723  		fulltextClause = fmt.Sprintf("MATCH(%s) AGAINST (:FulltextTerm IN BOOLEAN MODE)", searchColumns)
   724  	}
   725  
   726  	return
   727  }
   728  
   729  func (s SqlChannelStoreExperimental) performSearch(searchQuery string, term string, parameters map[string]interface{}) store.StoreResult {
   730  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   731  		return s.SqlChannelStore.performSearch(searchQuery, term, parameters)
   732  	}
   733  
   734  	result := store.StoreResult{}
   735  
   736  	likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose")
   737  	if likeTerm == "" {
   738  		// If the likeTerm is empty after preparing, then don't bother searching.
   739  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
   740  	} else {
   741  		parameters["LikeTerm"] = likeTerm
   742  		fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose")
   743  		parameters["FulltextTerm"] = fulltextTerm
   744  		searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "AND ("+likeClause+" OR "+fulltextClause+")", 1)
   745  	}
   746  
   747  	var channels model.ChannelList
   748  
   749  	if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil {
   750  		result.Err = model.NewAppError("SqlChannelStore.Search", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError)
   751  		return result
   752  	}
   753  
   754  	result.Data = &channels
   755  	return result
   756  }
   757  
   758  func (s SqlChannelStoreExperimental) EnableExperimentalPublicChannelsMaterialization() {
   759  	if !s.IsExperimentalPublicChannelsMaterializationEnabled() {
   760  		mlog.Info("Enabling experimental public channels materialization")
   761  	}
   762  
   763  	atomic.StoreUint32(s.experimentalPublicChannelsMaterializationDisabled, 0)
   764  }
   765  
   766  func (s SqlChannelStoreExperimental) DisableExperimentalPublicChannelsMaterialization() {
   767  	if s.IsExperimentalPublicChannelsMaterializationEnabled() {
   768  		mlog.Info("Disabling experimental public channels materialization")
   769  	}
   770  
   771  	atomic.StoreUint32(s.experimentalPublicChannelsMaterializationDisabled, 1)
   772  }
   773  
   774  func (s SqlChannelStoreExperimental) IsExperimentalPublicChannelsMaterializationEnabled() bool {
   775  	return atomic.LoadUint32(s.experimentalPublicChannelsMaterializationDisabled) == 0
   776  }