MM-12342: merge the experimental channel store (#9681)

* MM-12342: merge the experimental channel store

* gofmt after upgrading to go 1.11
Этот коммит содержится в:
Jesse Hallam
2018-10-25 13:25:27 -04:00
коммит произвёл GitHub
родитель 1074a1c7ab
Коммит 937b6480d5
10 изменённых файлов: 346 добавлений и 1035 удалений

Просмотреть файл

@@ -12,6 +12,7 @@ import (
"strings"
"github.com/mattermost/gorp"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/mlog"
@@ -232,6 +233,17 @@ func (db allChannelMembers) ToMapStringString() map[string]string {
return result
}
// 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"`
}
var channelMemberCountsCache = utils.NewLru(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE)
var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
var allChannelMembersNotifyPropsForChannelCache = utils.NewLru(ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE)
@@ -278,6 +290,15 @@ func NewSqlChannelStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface)
tablem.ColMap("UserId").SetMaxSize(26)
tablem.ColMap("Roles").SetMaxSize(64)
tablem.ColMap("NotifyProps").SetMaxSize(2000)
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
@@ -299,21 +320,112 @@ func (s SqlChannelStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_channelmembers_user_id", "ChannelMembers", "UserId")
s.CreateFullTextIndexIfNotExists("idx_channel_search_txt", "Channels", "Name, DisplayName, Purpose")
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")
}
// MigratePublicChannels initializes the PublicChannels table with data created before this version
// of the Mattermost server kept it up-to-date.
func (s SqlChannelStore) MigratePublicChannels() error {
// See SqlChannelStoreExperimental
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
}
func (s SqlChannelStore) DropPublicChannels() error {
// See SqlChannelStoreExperimental
func (s SqlChannelStore) 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
}
// Save writes the (non-direct) channel channel to the database.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) Save(channel *model.Channel, maxChannelsPerTeam int64) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
if channel.DeleteAt != 0 {
@@ -338,6 +450,13 @@ func (s SqlChannelStore) Save(channel *model.Channel, maxChannelsPerTeam int64)
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("SqlChannelStore.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("SqlChannelStore.Save", "store.sql_channel.save.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
return
@@ -473,8 +592,6 @@ func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *mo
}
// Update writes the updated channel to the database.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) Update(channel *model.Channel) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
transaction, err := s.GetMaster().Begin()
@@ -489,11 +606,19 @@ func (s SqlChannelStore) Update(channel *model.Channel) store.StoreChannel {
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("SqlChannelStore.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("SqlChannelStore.Update", "store.sql_channel.update.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
})
}
func (s SqlChannelStore) updateChannelT(transaction *gorp.Transaction, channel *model.Channel) store.StoreResult {
@@ -642,22 +767,16 @@ func (s SqlChannelStore) get(id string, master bool, allowFromCache bool) store.
}
// Delete records the given deleted timestamp to the channel in question.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) Delete(channelId string, time int64) store.StoreChannel {
return s.SetDeleteAt(channelId, time, time)
}
// Restore reverts a previous deleted timestamp from the channel in question.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) Restore(channelId string, time int64) store.StoreChannel {
return s.SetDeleteAt(channelId, 0, time)
}
// SetDeleteAt records the given deleted and updated timestamp to the channel in question.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) SetDeleteAt(channelId string, deleteAt, updateAt int64) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
defer s.InvalidateChannel(channelId)
@@ -674,6 +793,23 @@ func (s SqlChannelStore) SetDeleteAt(channelId string, deleteAt, updateAt int64)
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("SqlChannelStore.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("SqlChannelStore.SetDeleteAt", "store.sql_channel.set_delete_at.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
return
@@ -694,8 +830,6 @@ func (s SqlChannelStore) setDeleteAtT(transaction *gorp.Transaction, channelId s
}
// PermanentDeleteByTeam removes all channels for the given team from the database.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) PermanentDeleteByTeam(teamId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
transaction, err := s.GetMaster().Begin()
@@ -710,6 +844,20 @@ func (s SqlChannelStore) PermanentDeleteByTeam(teamId string) store.StoreChannel
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("SqlChannelStore.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("SqlChannelStore.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
return
@@ -729,8 +877,6 @@ func (s SqlChannelStore) permanentDeleteByTeamtT(transaction *gorp.Transaction,
}
// PermanentDelete removes the given channel from the database.
//
// @see ChannelStoreExperimental for how this update propagates to the PublicChannels table.
func (s SqlChannelStore) PermanentDelete(channelId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
transaction, err := s.GetMaster().Begin()
@@ -745,6 +891,20 @@ func (s SqlChannelStore) PermanentDelete(channelId string) store.StoreChannel {
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("SqlChannelStore.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("SqlChannelStore.PermanentDelete", "store.sql_channel.permanent_delete.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError)
return
@@ -798,29 +958,38 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, includeDelete
func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
data := &model.ChannelList{}
_, err := s.GetReplica().Select(data,
`SELECT
*
_, err := s.GetReplica().Select(data, `
SELECT
Channels.*
FROM
Channels
JOIN
PublicChannels c ON (c.Id = Channels.Id)
WHERE
TeamId = :TeamId1
AND Type IN ('O')
AND DeleteAt = 0
AND Id NOT IN (SELECT
Channels.Id
FROM
Channels,
ChannelMembers
WHERE
Id = ChannelId
AND TeamId = :TeamId2
AND UserId = :UserId
AND DeleteAt = 0)
ORDER BY DisplayName
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{}{"TeamId1": teamId, "TeamId2": teamId, "UserId": userId, "Limit": limit, "Offset": offset})
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)
@@ -834,19 +1003,24 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset in
func (s SqlChannelStore) GetPublicChannelsForTeam(teamId string, offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
data := &model.ChannelList{}
_, err := s.GetReplica().Select(data,
`SELECT
*
_, err := s.GetReplica().Select(data, `
SELECT
Channels.*
FROM
Channels
JOIN
PublicChannels pc ON (pc.Id = Channels.Id)
WHERE
TeamId = :TeamId
AND Type = 'O'
AND DeleteAt = 0
ORDER BY DisplayName
pc.TeamId = :TeamId
AND pc.DeleteAt = 0
ORDER BY pc.DisplayName
LIMIT :Limit
OFFSET :Offset`,
map[string]interface{}{"TeamId": teamId, "Limit": limit, "Offset": offset})
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)
@@ -874,18 +1048,19 @@ func (s SqlChannelStore) GetPublicChannelsByIdsForTeam(teamId string, channelIds
}
data := &model.ChannelList{}
_, err := s.GetReplica().Select(data,
`SELECT
*
_, err := s.GetReplica().Select(data, `
SELECT
Channels.*
FROM
Channels
JOIN
PublicChannels pc ON (pc.Id = Channels.Id)
WHERE
TeamId = :teamId
AND Type = 'O'
AND DeleteAt = 0
AND Id IN (`+idQuery+`)
ORDER BY DisplayName`,
props)
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)
@@ -1721,33 +1896,35 @@ func (s SqlChannelStore) GetMembersForUser(teamId string, userId string) store.S
func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
deleteFilter := "AND DeleteAt = 0"
deleteFilter := "AND c.DeleteAt = 0"
if includeDeleted {
deleteFilter = ""
}
queryFormat := `
SELECT
*
Channels.*
FROM
Channels
Channels
JOIN
PublicChannels c ON (c.Id = Channels.Id)
WHERE
TeamId = :TeamId
AND Type = 'O'
` + deleteFilter + `
%v
LIMIT 50`
c.TeamId = :TeamId
` + deleteFilter + `
%v
LIMIT 50
`
var channels model.ChannelList
if likeClause, likeTerm := s.buildLIKEClause(term, "Name, DisplayName, Purpose"); likeClause == "" {
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, "Name, DisplayName, Purpose")
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)
@@ -1863,53 +2040,61 @@ func (s SqlChannelStore) autocompleteInTeamForSearchDirectMessages(userId string
func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
deleteFilter := "AND DeleteAt = 0"
deleteFilter := "AND c.DeleteAt = 0"
if includeDeleted {
deleteFilter = ""
}
searchQuery := `
SELECT
*
FROM
Channels
WHERE
TeamId = :TeamId
AND Type = 'O'
` + deleteFilter + `
SEARCH_CLAUSE
ORDER BY DisplayName
LIMIT 100`
*result = s.performSearch(searchQuery, term, map[string]interface{}{"TeamId": teamId})
*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 SqlChannelStore) SearchMore(userId string, teamId string, term string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := `
*result = s.performSearch(`
SELECT
*
Channels.*
FROM
Channels
JOIN
PublicChannels c ON (c.Id = Channels.Id)
WHERE
TeamId = :TeamId
AND Type = 'O'
AND DeleteAt = 0
AND Id NOT IN (SELECT
Channels.Id
c.TeamId = :TeamId
AND c.DeleteAt = 0
AND c.Id NOT IN (
SELECT
c.Id
FROM
Channels,
ChannelMembers
PublicChannels c
JOIN
ChannelMembers cm ON (cm.ChannelId = c.Id)
WHERE
Id = ChannelId
AND TeamId = :TeamId
AND UserId = :UserId
AND DeleteAt = 0)
SEARCH_CLAUSE
ORDER BY DisplayName
LIMIT 100`
*result = s.performSearch(searchQuery, term, map[string]interface{}{"TeamId": teamId, "UserId": userId})
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,
})
})
}
@@ -1987,13 +2172,13 @@ func (s SqlChannelStore) buildFulltextClause(term string, searchColumns string)
func (s SqlChannelStore) performSearch(searchQuery string, term string, parameters map[string]interface{}) store.StoreResult {
result := store.StoreResult{}
likeClause, likeTerm := s.buildLIKEClause(term, "Name, DisplayName, Purpose")
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, "Name, DisplayName, Purpose")
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)
}
@@ -2259,19 +2444,6 @@ func (s SqlChannelStore) resetLastPostAtT(transaction *gorp.Transaction) store.S
return result
}
func (s SqlChannelStore) EnableExperimentalPublicChannelsMaterialization() {
// See SqlChannelStoreExperimental
}
func (s SqlChannelStore) DisableExperimentalPublicChannelsMaterialization() {
// See SqlChannelStoreExperimental
}
func (s SqlChannelStore) IsExperimentalPublicChannelsMaterializationEnabled() bool {
// See SqlChannelStoreExperimental
return false
}
func (s SqlChannelStore) GetAllChannelsForExportAfter(limit int, afterId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var data []*model.ChannelForExport

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -118,13 +118,8 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.initConnection()
enableExperimentalPublicChannelsMaterialization := true
if settings.EnablePublicChannelsMaterialization != nil && !*settings.EnablePublicChannelsMaterialization {
enableExperimentalPublicChannelsMaterialization = false
}
supplier.oldStores.team = NewSqlTeamStore(supplier)
supplier.oldStores.channel = NewSqlChannelStoreExperimental(supplier, metrics, enableExperimentalPublicChannelsMaterialization)
supplier.oldStores.channel = NewSqlChannelStore(supplier, metrics)
supplier.oldStores.post = NewSqlPostStore(supplier, metrics)
supplier.oldStores.user = NewSqlUserStore(supplier, metrics)
supplier.oldStores.audit = NewSqlAuditStore(supplier)
@@ -162,7 +157,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
UpgradeDatabase(supplier)
supplier.oldStores.team.(*SqlTeamStore).CreateIndexesIfNotExists()
supplier.oldStores.channel.(*SqlChannelStoreExperimental).CreateIndexesIfNotExists()
supplier.oldStores.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
supplier.oldStores.post.(*SqlPostStore).CreateIndexesIfNotExists()
supplier.oldStores.user.(*SqlUserStore).CreateIndexesIfNotExists()
supplier.oldStores.audit.(*SqlAuditStore).CreateIndexesIfNotExists()