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 }