ABC-79: Optimize channel autocomplete query (#8163)

* optimize channel autocomplete query

* move to new autocomplete endpoint
Этот коммит содержится в:
Chris
2018-01-31 08:26:40 -06:00
коммит произвёл Joram Wilander
родитель 0c8968fb8d
Коммит e0ee73ef99
7 изменённых файлов: 266 добавлений и 168 удалений

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

@@ -20,6 +20,7 @@ func (api *API) InitChannel() {
api.BaseRoutes.ChannelsForTeam.Handle("/deleted", api.ApiSessionRequired(getDeletedChannelsForTeam)).Methods("GET") api.BaseRoutes.ChannelsForTeam.Handle("/deleted", api.ApiSessionRequired(getDeletedChannelsForTeam)).Methods("GET")
api.BaseRoutes.ChannelsForTeam.Handle("/ids", api.ApiSessionRequired(getPublicChannelsByIdsForTeam)).Methods("POST") api.BaseRoutes.ChannelsForTeam.Handle("/ids", api.ApiSessionRequired(getPublicChannelsByIdsForTeam)).Methods("POST")
api.BaseRoutes.ChannelsForTeam.Handle("/search", api.ApiSessionRequired(searchChannelsForTeam)).Methods("POST") api.BaseRoutes.ChannelsForTeam.Handle("/search", api.ApiSessionRequired(searchChannelsForTeam)).Methods("POST")
api.BaseRoutes.ChannelsForTeam.Handle("/autocomplete", api.ApiSessionRequired(autocompleteChannelsForTeam)).Methods("GET")
api.BaseRoutes.User.Handle("/teams/{team_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequired(getChannelsForTeamForUser)).Methods("GET") api.BaseRoutes.User.Handle("/teams/{team_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequired(getChannelsForTeamForUser)).Methods("GET")
api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(getChannel)).Methods("GET") api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(getChannel)).Methods("GET")
@@ -489,6 +490,27 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques
} }
} }
func autocompleteChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) {
c.SetPermissionError(model.PERMISSION_LIST_TEAM_CHANNELS)
return
}
name := r.URL.Query().Get("name")
if channels, err := c.App.AutocompleteChannels(c.Params.TeamId, name); err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
}
func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) { func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId() c.RequireTeamId()
if c.Err != nil { if c.Err != nil {

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

@@ -1255,6 +1255,14 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod
return nil return nil
} }
func (a *App) AutocompleteChannels(teamId string, term string) (*model.ChannelList, *model.AppError) {
if result := <-a.Srv.Store.Channel().AutocompleteInTeam(teamId, term); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.ChannelList), nil
}
}
func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError) { func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError) {
if result := <-a.Srv.Store.Channel().SearchInTeam(teamId, term); result.Err != nil { if result := <-a.Srv.Store.Channel().SearchInTeam(teamId, term); result.Err != nil {
return nil, result.Err return nil, result.Err

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

@@ -7,6 +7,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"net/http" "net/http"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -81,6 +82,7 @@ func NewSqlChannelStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface)
func (s SqlChannelStore) CreateIndexesIfNotExists() { func (s SqlChannelStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_channels_team_id", "Channels", "TeamId") s.CreateIndexIfNotExists("idx_channels_team_id", "Channels", "TeamId")
s.CreateIndexIfNotExists("idx_channels_name", "Channels", "Name") s.CreateIndexIfNotExists("idx_channels_name", "Channels", "Name")
s.CreateIndexIfNotExists("idx_channels_displayname", "Channels", "DisplayName")
s.CreateIndexIfNotExists("idx_channels_update_at", "Channels", "UpdateAt") s.CreateIndexIfNotExists("idx_channels_update_at", "Channels", "UpdateAt")
s.CreateIndexIfNotExists("idx_channels_create_at", "Channels", "CreateAt") s.CreateIndexIfNotExists("idx_channels_create_at", "Channels", "CreateAt")
s.CreateIndexIfNotExists("idx_channels_delete_at", "Channels", "DeleteAt") s.CreateIndexIfNotExists("idx_channels_delete_at", "Channels", "DeleteAt")
@@ -1257,18 +1259,58 @@ func (s SqlChannelStore) GetMembersForUser(teamId string, userId string) store.S
}) })
} }
func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
queryFormat := `
SELECT
*
FROM
Channels
WHERE
TeamId = :TeamId
AND Type = 'O'
AND DeleteAt = 0
%v
LIMIT 50`
var channels model.ChannelList
if likeClause, likeTerm := s.buildLIKEClause(term); 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)
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 channels[a].DisplayName < channels[b].DisplayName
})
result.Data = &channels
})
}
func (s SqlChannelStore) SearchInTeam(teamId string, term string) store.StoreChannel { func (s SqlChannelStore) SearchInTeam(teamId string, term string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) { return store.Do(func(result *store.StoreResult) {
searchQuery := ` searchQuery := `
SELECT SELECT
* *
FROM FROM
Channels Channels
WHERE WHERE
TeamId = :TeamId TeamId = :TeamId
AND Type = 'O' AND Type = 'O'
AND DeleteAt = 0 AND DeleteAt = 0
SEARCH_CLAUSE SEARCH_CLAUSE
ORDER BY DisplayName ORDER BY DisplayName
LIMIT 100` LIMIT 100`
@@ -1305,13 +1347,8 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) s
}) })
} }
func (s SqlChannelStore) performSearch(searchQuery string, term string, parameters map[string]interface{}) store.StoreResult { func (s SqlChannelStore) buildLIKEClause(term string) (likeClause, likeTerm string) {
result := store.StoreResult{} likeTerm = term
// Copy the terms as we will need to prepare them differently for each search type.
likeTerm := term
fulltextTerm := term
searchColumns := "Name, DisplayName" searchColumns := "Name, DisplayName"
// These chars must be removed from the like query. // These chars must be removed from the like query.
@@ -1324,57 +1361,78 @@ func (s SqlChannelStore) performSearch(searchQuery string, term string, paramete
likeTerm = strings.Replace(likeTerm, c, "*"+c, -1) 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 SqlChannelStore) buildFulltextClause(term string) (fulltextClause, fulltextTerm string) {
// Copy the terms as we will need to prepare them differently for each search type.
fulltextTerm = term
searchColumns := "Name, DisplayName"
// These chars must be treated as spaces in the fulltext query. // These chars must be treated as spaces in the fulltext query.
for _, c := range spaceFulltextSearchChar { for _, c := range spaceFulltextSearchChar {
fulltextTerm = strings.Replace(fulltextTerm, c, " ", -1) fulltextTerm = strings.Replace(fulltextTerm, c, " ", -1)
} }
// Prepare the FULLTEXT portion of the query.
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
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 SqlChannelStore) performSearch(searchQuery string, term string, parameters map[string]interface{}) store.StoreResult {
result := store.StoreResult{}
likeClause, likeTerm := s.buildLIKEClause(term)
if likeTerm == "" { if likeTerm == "" {
// If the likeTerm is empty after preparing, then don't bother searching. // If the likeTerm is empty after preparing, then don't bother searching.
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
} else { } else {
// Prepare the LIKE portion of the query. parameters["LikeTerm"] = likeTerm
var searchFields []string fulltextClause, fulltextTerm := s.buildFulltextClause(term)
for _, field := range strings.Split(searchColumns, ", ") { parameters["FulltextTerm"] = fulltextTerm
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "AND ("+likeClause+" OR "+fulltextClause+")", 1)
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"))
}
}
likeSearchClause := fmt.Sprintf("(%s)", strings.Join(searchFields, " OR "))
parameters["LikeTerm"] = fmt.Sprintf("%s%%", likeTerm)
// Prepare the FULLTEXT portion of the query.
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
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, " ")
fulltextSearchClause := fmt.Sprintf("((%s) @@ to_tsquery(:FulltextTerm))", convertMySQLFullTextColumnsToPostgres(searchColumns))
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "AND ("+likeSearchClause+" OR "+fulltextSearchClause+")", 1)
} 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, " ")
fulltextSearchClause := fmt.Sprintf("MATCH(%s) AGAINST (:FulltextTerm IN BOOLEAN MODE)", searchColumns)
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", fmt.Sprintf("AND (%s OR %s)", likeSearchClause, fulltextSearchClause), 1)
}
} }
parameters["FulltextTerm"] = fulltextTerm
var channels model.ChannelList var channels model.ChannelList
if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil { if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil {

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

@@ -154,6 +154,7 @@ type ChannelStore interface {
AnalyticsTypeCount(teamId string, channelType string) StoreChannel AnalyticsTypeCount(teamId string, channelType string) StoreChannel
ExtraUpdateByUser(userId string, time int64) StoreChannel ExtraUpdateByUser(userId string, time int64) StoreChannel
GetMembersForUser(teamId string, userId string) StoreChannel GetMembersForUser(teamId string, userId string) StoreChannel
AutocompleteInTeam(teamId string, term string) StoreChannel
SearchInTeam(teamId string, term string) StoreChannel SearchInTeam(teamId string, term string) StoreChannel
SearchMore(userId string, teamId string, term string) StoreChannel SearchMore(userId string, teamId string, term string) StoreChannel
GetMembersByIds(channelId string, userIds []string) StoreChannel GetMembersByIds(channelId string, userIds []string) StoreChannel

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

@@ -1940,116 +1940,109 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
o11.Type = model.CHANNEL_OPEN o11.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&o11, -1)) store.Must(ss.Channel().Save(&o11, -1))
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "ChannelA"); result.Err != nil { for name, search := range map[string]func(teamId string, term string) store.StoreChannel{
t.Fatal(result.Err) "AutocompleteInTeam": ss.Channel().AutocompleteInTeam,
} else { "SearchInTeam": ss.Channel().SearchInTeam,
channels := result.Data.(*model.ChannelList) } {
if len(*channels) != 2 { t.Run(name, func(t *testing.T) {
t.Fatal("wrong length") if result := <-search(o1.TeamId, "ChannelA"); result.Err != nil {
} t.Fatal(result.Err)
} } else {
channels := result.Data.(*model.ChannelList)
if result := <-ss.Channel().SearchInTeam(o1.TeamId, ""); result.Err != nil { if len(*channels) != 2 {
t.Fatal(result.Err) t.Fatal("wrong length")
} else { }
channels := result.Data.(*model.ChannelList)
if len(*channels) == 0 {
t.Fatal("should not be empty")
}
}
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "blargh"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 0 {
t.Fatal("should be empty")
}
}
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "off-"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 2 {
t.Fatal("should return 2 channels, not including private channel")
}
if (*channels)[0].Name != o7.Name {
t.Fatal("wrong channel returned")
}
if (*channels)[1].Name != o6.Name {
t.Fatal("wrong channel returned")
}
}
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "off-topic"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o6.Name {
t.Fatal("wrong channel returned")
}
}
/*
// Disabling this check as it will fail on PostgreSQL as we have "liberalised" channel matching to deal with
// Full-Text Stemming Limitations.
if result := <-ss.Channel().SearchMore(m1.
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "off-topics"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 0 {
t.Fatal("should be empty")
} }
}
*/
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "town square"); result.Err != nil { if result := <-search(o1.TeamId, ""); result.Err != nil {
t.Fatal(result.Err) t.Fatal(result.Err)
} else { } else {
channels := result.Data.(*model.ChannelList) channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 { if len(*channels) == 0 {
t.Fatal("should return 1 channel") t.Fatal("should not be empty")
} }
}
if (*channels)[0].Name != o9.Name { if result := <-search(o1.TeamId, "blargh"); result.Err != nil {
t.Fatal("wrong channel returned") t.Fatal(result.Err)
} } else {
} channels := result.Data.(*model.ChannelList)
if len(*channels) != 0 {
t.Fatal("should be empty")
}
}
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "the"); result.Err != nil { if result := <-search(o1.TeamId, "off-"); result.Err != nil {
t.Fatal(result.Err) t.Fatal(result.Err)
} else { } else {
channels := result.Data.(*model.ChannelList) channels := result.Data.(*model.ChannelList)
t.Log(channels.ToJson()) if len(*channels) != 2 {
if len(*channels) != 1 { t.Fatal("should return 2 channels, not including private channel")
t.Fatal("should return 1 channel") }
}
if (*channels)[0].Name != o10.Name { if (*channels)[0].Name != o7.Name {
t.Fatal("wrong channel returned") t.Fatal("wrong channel returned")
} }
}
if result := <-ss.Channel().SearchInTeam(o1.TeamId, "Mobile"); result.Err != nil { if (*channels)[1].Name != o6.Name {
t.Fatal(result.Err) t.Fatal("wrong channel returned")
} else { }
channels := result.Data.(*model.ChannelList) }
t.Log(channels.ToJson())
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o11.Name { if result := <-search(o1.TeamId, "off-topic"); result.Err != nil {
t.Fatal("wrong channel returned") t.Fatal(result.Err)
} } else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o6.Name {
t.Fatal("wrong channel returned")
}
}
if result := <-search(o1.TeamId, "town square"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o9.Name {
t.Fatal("wrong channel returned")
}
}
if result := <-search(o1.TeamId, "the"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
t.Log(channels.ToJson())
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o10.Name {
t.Fatal("wrong channel returned")
}
}
if result := <-search(o1.TeamId, "Mobile"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
t.Log(channels.ToJson())
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o11.Name {
t.Fatal("wrong channel returned")
}
}
})
} }
} }

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

@@ -45,6 +45,22 @@ func (_m *ChannelStore) AnalyticsTypeCount(teamId string, channelType string) st
return r0 return r0
} }
// AutocompleteInTeam provides a mock function with given fields: teamId, term
func (_m *ChannelStore) AutocompleteInTeam(teamId string, term string) store.StoreChannel {
ret := _m.Called(teamId, term)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
r0 = rf(teamId, term)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// CreateDirectChannel provides a mock function with given fields: userId, otherUserId // CreateDirectChannel provides a mock function with given fields: userId, otherUserId
func (_m *ChannelStore) CreateDirectChannel(userId string, otherUserId string) store.StoreChannel { func (_m *ChannelStore) CreateDirectChannel(userId string, otherUserId string) store.StoreChannel {
ret := _m.Called(userId, otherUserId) ret := _m.Called(userId, otherUserId)

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

@@ -61,13 +61,13 @@ func (_m *UserAccessTokenStore) Get(tokenId string) store.StoreChannel {
return r0 return r0
} }
// GetAll provides a mock function with given fields: // GetAll provides a mock function with given fields: offset, limit
func (_m *UserAccessTokenStore) GetAll(page int, perPage int) store.StoreChannel { func (_m *UserAccessTokenStore) GetAll(offset int, limit int) store.StoreChannel {
ret := _m.Called(page, perPage) ret := _m.Called(offset, limit)
var r0 store.StoreChannel var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int, int) store.StoreChannel); ok { if rf, ok := ret.Get(0).(func(int, int) store.StoreChannel); ok {
r0 = rf(page, perPage) r0 = rf(offset, limit)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel) r0 = ret.Get(0).(store.StoreChannel)
@@ -109,13 +109,13 @@ func (_m *UserAccessTokenStore) GetByUser(userId string, page int, perPage int)
return r0 return r0
} }
// Search provides a mock function with given fields: // Save provides a mock function with given fields: token
func (_m *UserAccessTokenStore) Search(term string) store.StoreChannel { func (_m *UserAccessTokenStore) Save(token *model.UserAccessToken) store.StoreChannel {
ret := _m.Called(term) ret := _m.Called(token)
var r0 store.StoreChannel var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok { if rf, ok := ret.Get(0).(func(*model.UserAccessToken) store.StoreChannel); ok {
r0 = rf(term) r0 = rf(token)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel) r0 = ret.Get(0).(store.StoreChannel)
@@ -125,13 +125,13 @@ func (_m *UserAccessTokenStore) Search(term string) store.StoreChannel {
return r0 return r0
} }
// Save provides a mock function with given fields: token // Search provides a mock function with given fields: term
func (_m *UserAccessTokenStore) Save(token *model.UserAccessToken) store.StoreChannel { func (_m *UserAccessTokenStore) Search(term string) store.StoreChannel {
ret := _m.Called(token) ret := _m.Called(term)
var r0 store.StoreChannel var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.UserAccessToken) store.StoreChannel); ok { if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(token) r0 = rf(term)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel) r0 = ret.Get(0).(store.StoreChannel)