github.com/wgh-/mattermost-server@v4.8.0-rc2+incompatible/store/sqlstore/post_store.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 "regexp" 10 "strconv" 11 "strings" 12 13 "bytes" 14 15 l4g "github.com/alecthomas/log4go" 16 "github.com/mattermost/mattermost-server/einterfaces" 17 "github.com/mattermost/mattermost-server/model" 18 "github.com/mattermost/mattermost-server/store" 19 "github.com/mattermost/mattermost-server/utils" 20 ) 21 22 type SqlPostStore struct { 23 SqlStore 24 metrics einterfaces.MetricsInterface 25 } 26 27 const ( 28 LAST_POST_TIME_CACHE_SIZE = 25000 29 LAST_POST_TIME_CACHE_SEC = 900 // 15 minutes 30 31 LAST_POSTS_CACHE_SIZE = 1000 32 LAST_POSTS_CACHE_SEC = 900 // 15 minutes 33 ) 34 35 var lastPostTimeCache = utils.NewLru(LAST_POST_TIME_CACHE_SIZE) 36 var lastPostsCache = utils.NewLru(LAST_POSTS_CACHE_SIZE) 37 38 func ClearPostCaches() { 39 lastPostTimeCache.Purge() 40 lastPostsCache.Purge() 41 } 42 43 func NewSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore { 44 s := &SqlPostStore{ 45 SqlStore: sqlStore, 46 metrics: metrics, 47 } 48 49 for _, db := range sqlStore.GetAllConns() { 50 table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id") 51 table.ColMap("Id").SetMaxSize(26) 52 table.ColMap("UserId").SetMaxSize(26) 53 table.ColMap("ChannelId").SetMaxSize(26) 54 table.ColMap("RootId").SetMaxSize(26) 55 table.ColMap("ParentId").SetMaxSize(26) 56 table.ColMap("OriginalId").SetMaxSize(26) 57 table.ColMap("Message").SetMaxSize(4000) 58 table.ColMap("Type").SetMaxSize(26) 59 table.ColMap("Hashtags").SetMaxSize(1000) 60 table.ColMap("Props").SetMaxSize(8000) 61 table.ColMap("Filenames").SetMaxSize(4000) 62 table.ColMap("FileIds").SetMaxSize(150) 63 } 64 65 return s 66 } 67 68 func (s SqlPostStore) CreateIndexesIfNotExists() { 69 s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt") 70 s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt") 71 s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt") 72 s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId") 73 s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId") 74 s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId") 75 s.CreateIndexIfNotExists("idx_posts_is_pinned", "Posts", "IsPinned") 76 77 s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_update_at", "Posts", []string{"ChannelId", "UpdateAt"}) 78 s.CreateCompositeIndexIfNotExists("idx_posts_channel_id_delete_at_create_at", "Posts", []string{"ChannelId", "DeleteAt", "CreateAt"}) 79 80 s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message") 81 s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags") 82 } 83 84 func (s SqlPostStore) Save(post *model.Post) store.StoreChannel { 85 return store.Do(func(result *store.StoreResult) { 86 if len(post.Id) > 0 { 87 result.Err = model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest) 88 return 89 } 90 91 post.PreSave() 92 if result.Err = post.IsValid(); result.Err != nil { 93 return 94 } 95 96 if err := s.GetMaster().Insert(post); err != nil { 97 result.Err = model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError) 98 } else { 99 time := post.UpdateAt 100 101 if post.Type != model.POST_JOIN_LEAVE && post.Type != model.POST_ADD_REMOVE && 102 post.Type != model.POST_JOIN_CHANNEL && post.Type != model.POST_LEAVE_CHANNEL && 103 post.Type != model.POST_JOIN_TEAM && post.Type != model.POST_LEAVE_TEAM && 104 post.Type != model.POST_ADD_TO_CHANNEL && post.Type != model.POST_REMOVE_FROM_CHANNEL { 105 s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt, TotalMsgCount = TotalMsgCount + 1 WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}) 106 } else { 107 // don't update TotalMsgCount for unimportant messages so that the channel isn't marked as unread 108 s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId}) 109 } 110 111 if len(post.RootId) > 0 { 112 s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": post.RootId}) 113 } 114 115 result.Data = post 116 } 117 }) 118 } 119 120 func (s SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) store.StoreChannel { 121 return store.Do(func(result *store.StoreResult) { 122 newPost.UpdateAt = model.GetMillis() 123 newPost.PreCommit() 124 125 oldPost.DeleteAt = newPost.UpdateAt 126 oldPost.UpdateAt = newPost.UpdateAt 127 oldPost.OriginalId = oldPost.Id 128 oldPost.Id = model.NewId() 129 oldPost.PreCommit() 130 131 if result.Err = newPost.IsValid(); result.Err != nil { 132 return 133 } 134 135 if _, err := s.GetMaster().Update(newPost); err != nil { 136 result.Err = model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError) 137 } else { 138 time := model.GetMillis() 139 s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": newPost.ChannelId}) 140 141 if len(newPost.RootId) > 0 { 142 s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": newPost.RootId}) 143 } 144 145 // mark the old post as deleted 146 s.GetMaster().Insert(oldPost) 147 148 result.Data = newPost 149 } 150 }) 151 } 152 153 func (s SqlPostStore) Overwrite(post *model.Post) store.StoreChannel { 154 return store.Do(func(result *store.StoreResult) { 155 post.UpdateAt = model.GetMillis() 156 157 if result.Err = post.IsValid(); result.Err != nil { 158 return 159 } 160 161 if _, err := s.GetMaster().Update(post); err != nil { 162 result.Err = model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError) 163 } else { 164 result.Data = post 165 } 166 }) 167 } 168 169 func (s SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) store.StoreChannel { 170 return store.Do(func(result *store.StoreResult) { 171 pl := model.NewPostList() 172 173 var posts []*model.Post 174 if _, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit}); err != nil { 175 result.Err = model.NewAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 176 } else { 177 for _, post := range posts { 178 pl.AddPost(post) 179 pl.AddOrder(post.Id) 180 } 181 } 182 183 result.Data = pl 184 }) 185 } 186 187 func (s SqlPostStore) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) store.StoreChannel { 188 return store.Do(func(result *store.StoreResult) { 189 pl := model.NewPostList() 190 191 var posts []*model.Post 192 193 query := ` 194 SELECT 195 A.* 196 FROM 197 (SELECT 198 * 199 FROM 200 Posts 201 WHERE 202 Id 203 IN 204 (SELECT 205 Name 206 FROM 207 Preferences 208 WHERE 209 UserId = :UserId 210 AND Category = :Category) 211 AND DeleteAt = 0 212 ) as A 213 INNER JOIN Channels as B 214 ON B.Id = A.ChannelId 215 WHERE B.TeamId = :TeamId OR B.TeamId = '' 216 ORDER BY CreateAt DESC 217 LIMIT :Limit OFFSET :Offset` 218 219 if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit, "TeamId": teamId}); err != nil { 220 result.Err = model.NewAppError("SqlPostStore.GetFlaggedPostsForTeam", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 221 } else { 222 for _, post := range posts { 223 pl.AddPost(post) 224 pl.AddOrder(post.Id) 225 } 226 } 227 228 result.Data = pl 229 }) 230 } 231 232 func (s SqlPostStore) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) store.StoreChannel { 233 return store.Do(func(result *store.StoreResult) { 234 pl := model.NewPostList() 235 236 var posts []*model.Post 237 query := ` 238 SELECT 239 * 240 FROM Posts 241 WHERE 242 Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) 243 AND ChannelId = :ChannelId 244 AND DeleteAt = 0 245 ORDER BY CreateAt DESC 246 LIMIT :Limit OFFSET :Offset` 247 248 if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil { 249 result.Err = model.NewAppError("SqlPostStore.GetFlaggedPostsForChannel", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 250 } else { 251 for _, post := range posts { 252 pl.AddPost(post) 253 pl.AddOrder(post.Id) 254 } 255 } 256 257 result.Data = pl 258 }) 259 } 260 261 func (s SqlPostStore) Get(id string) store.StoreChannel { 262 return store.Do(func(result *store.StoreResult) { 263 pl := model.NewPostList() 264 265 if len(id) == 0 { 266 result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest) 267 return 268 } 269 270 var post model.Post 271 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}) 272 if err != nil { 273 result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound) 274 return 275 } 276 277 pl.AddPost(&post) 278 pl.AddOrder(id) 279 280 rootId := post.RootId 281 282 if rootId == "" { 283 rootId = post.Id 284 } 285 286 if len(rootId) == 0 { 287 result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError) 288 return 289 } 290 291 var posts []*model.Post 292 _, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId}) 293 if err != nil { 294 result.Err = model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError) 295 return 296 } else { 297 for _, p := range posts { 298 pl.AddPost(p) 299 } 300 } 301 302 result.Data = pl 303 }) 304 } 305 306 func (s SqlPostStore) GetSingle(id string) store.StoreChannel { 307 return store.Do(func(result *store.StoreResult) { 308 var post model.Post 309 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}) 310 if err != nil { 311 result.Err = model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound) 312 } 313 314 result.Data = &post 315 }) 316 } 317 318 type etagPosts struct { 319 Id string 320 UpdateAt int64 321 } 322 323 func (s SqlPostStore) InvalidateLastPostTimeCache(channelId string) { 324 lastPostTimeCache.Remove(channelId) 325 326 // Keys are "{channelid}{limit}" and caching only occurs on limits of 30 and 60 327 lastPostsCache.Remove(channelId + "30") 328 lastPostsCache.Remove(channelId + "60") 329 } 330 331 func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel { 332 return store.Do(func(result *store.StoreResult) { 333 if allowFromCache { 334 if cacheItem, ok := lastPostTimeCache.Get(channelId); ok { 335 if s.metrics != nil { 336 s.metrics.IncrementMemCacheHitCounter("Last Post Time") 337 } 338 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, cacheItem.(int64)) 339 return 340 } else { 341 if s.metrics != nil { 342 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 343 } 344 } 345 } else { 346 if s.metrics != nil { 347 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 348 } 349 } 350 351 var et etagPosts 352 err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId}) 353 if err != nil { 354 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis()) 355 } else { 356 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt) 357 } 358 359 lastPostTimeCache.AddWithExpiresInSecs(channelId, et.UpdateAt, LAST_POST_TIME_CACHE_SEC) 360 }) 361 } 362 363 func (s SqlPostStore) Delete(postId string, time int64) store.StoreChannel { 364 return store.Do(func(result *store.StoreResult) { 365 _, err := s.GetMaster().Exec("Update Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "RootId": postId}) 366 if err != nil { 367 result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError) 368 } 369 }) 370 } 371 372 func (s SqlPostStore) permanentDelete(postId string) store.StoreChannel { 373 return store.Do(func(result *store.StoreResult) { 374 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId}) 375 if err != nil { 376 result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError) 377 } 378 }) 379 } 380 381 func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) store.StoreChannel { 382 return store.Do(func(result *store.StoreResult) { 383 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId}) 384 if err != nil { 385 result.Err = model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) 386 } 387 }) 388 } 389 390 func (s SqlPostStore) PermanentDeleteByUser(userId string) store.StoreChannel { 391 return store.Do(func(result *store.StoreResult) { 392 // First attempt to delete all the comments for a user 393 if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil { 394 result.Err = r.Err 395 return 396 } 397 398 // Now attempt to delete all the root posts for a user. This will also 399 // delete all the comments for each post. 400 found := true 401 count := 0 402 403 for found { 404 var ids []string 405 _, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId}) 406 if err != nil { 407 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) 408 return 409 } else { 410 found = false 411 for _, id := range ids { 412 found = true 413 if r := <-s.permanentDelete(id); r.Err != nil { 414 result.Err = r.Err 415 return 416 } 417 } 418 } 419 420 // This is a fail safe, give up if more than 10K messages 421 count = count + 1 422 if count >= 10 { 423 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError) 424 return 425 } 426 } 427 }) 428 } 429 430 func (s SqlPostStore) PermanentDeleteByChannel(channelId string) store.StoreChannel { 431 return store.Do(func(result *store.StoreResult) { 432 if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil { 433 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) 434 } 435 }) 436 } 437 438 func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) store.StoreChannel { 439 return store.Do(func(result *store.StoreResult) { 440 if limit > 1000 { 441 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest) 442 return 443 } 444 445 // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients 446 if allowFromCache && offset == 0 && (limit == 60 || limit == 30) { 447 if cacheItem, ok := lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok { 448 if s.metrics != nil { 449 s.metrics.IncrementMemCacheHitCounter("Last Posts Cache") 450 } 451 452 result.Data = cacheItem.(*model.PostList) 453 return 454 } else { 455 if s.metrics != nil { 456 s.metrics.IncrementMemCacheMissCounter("Last Posts Cache") 457 } 458 } 459 } else { 460 if s.metrics != nil { 461 s.metrics.IncrementMemCacheMissCounter("Last Posts Cache") 462 } 463 } 464 465 rpc := s.getRootPosts(channelId, offset, limit) 466 cpc := s.getParentsPosts(channelId, offset, limit) 467 468 if rpr := <-rpc; rpr.Err != nil { 469 result.Err = rpr.Err 470 } else if cpr := <-cpc; cpr.Err != nil { 471 result.Err = cpr.Err 472 } else { 473 posts := rpr.Data.([]*model.Post) 474 parents := cpr.Data.([]*model.Post) 475 476 list := model.NewPostList() 477 478 for _, p := range posts { 479 list.AddPost(p) 480 list.AddOrder(p.Id) 481 } 482 483 for _, p := range parents { 484 list.AddPost(p) 485 } 486 487 list.MakeNonNil() 488 489 // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients 490 if offset == 0 && (limit == 60 || limit == 30) { 491 lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC) 492 } 493 494 result.Data = list 495 } 496 }) 497 } 498 499 func (s SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) store.StoreChannel { 500 return store.Do(func(result *store.StoreResult) { 501 if allowFromCache { 502 // If the last post in the channel's time is less than or equal to the time we are getting posts since, 503 // we can safely return no posts. 504 if cacheItem, ok := lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time { 505 if s.metrics != nil { 506 s.metrics.IncrementMemCacheHitCounter("Last Post Time") 507 } 508 list := model.NewPostList() 509 result.Data = list 510 return 511 } else { 512 if s.metrics != nil { 513 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 514 } 515 } 516 } else { 517 if s.metrics != nil { 518 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 519 } 520 } 521 522 var posts []*model.Post 523 _, err := s.GetReplica().Select(&posts, 524 `(SELECT 525 * 526 FROM 527 Posts 528 WHERE 529 (UpdateAt > :Time 530 AND ChannelId = :ChannelId) 531 LIMIT 1000) 532 UNION 533 (SELECT 534 * 535 FROM 536 Posts 537 WHERE 538 Id 539 IN 540 (SELECT * FROM (SELECT 541 RootId 542 FROM 543 Posts 544 WHERE 545 UpdateAt > :Time 546 AND ChannelId = :ChannelId 547 LIMIT 1000) temp_tab)) 548 ORDER BY CreateAt DESC`, 549 map[string]interface{}{"ChannelId": channelId, "Time": time}) 550 551 if err != nil { 552 result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 553 } else { 554 555 list := model.NewPostList() 556 557 var latestUpdate int64 = 0 558 559 for _, p := range posts { 560 list.AddPost(p) 561 if p.UpdateAt > time { 562 list.AddOrder(p.Id) 563 } 564 if latestUpdate < p.UpdateAt { 565 latestUpdate = p.UpdateAt 566 } 567 } 568 569 lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC) 570 571 result.Data = list 572 } 573 }) 574 } 575 576 func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) store.StoreChannel { 577 return s.getPostsAround(channelId, postId, numPosts, offset, true) 578 } 579 580 func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) store.StoreChannel { 581 return s.getPostsAround(channelId, postId, numPosts, offset, false) 582 } 583 584 func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) store.StoreChannel { 585 return store.Do(func(result *store.StoreResult) { 586 var direction string 587 var sort string 588 if before { 589 direction = "<" 590 sort = "DESC" 591 } else { 592 direction = ">" 593 sort = "ASC" 594 } 595 596 var posts []*model.Post 597 var parents []*model.Post 598 _, err1 := s.GetReplica().Select(&posts, 599 `(SELECT 600 * 601 FROM 602 Posts 603 WHERE 604 (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId) 605 AND ChannelId = :ChannelId 606 AND DeleteAt = 0) 607 ORDER BY CreateAt `+sort+` 608 LIMIT :NumPosts 609 OFFSET :Offset)`, 610 map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset}) 611 _, err2 := s.GetReplica().Select(&parents, 612 `(SELECT 613 * 614 FROM 615 Posts 616 WHERE 617 Id 618 IN 619 (SELECT * FROM (SELECT 620 RootId 621 FROM 622 Posts 623 WHERE 624 (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId) 625 AND ChannelId = :ChannelId 626 AND DeleteAt = 0) 627 ORDER BY CreateAt `+sort+` 628 LIMIT :NumPosts 629 OFFSET :Offset) 630 temp_tab)) 631 ORDER BY CreateAt DESC`, 632 map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset}) 633 634 if err1 != nil { 635 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error(), http.StatusInternalServerError) 636 } else if err2 != nil { 637 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error(), http.StatusInternalServerError) 638 } else { 639 640 list := model.NewPostList() 641 642 // We need to flip the order if we selected backwards 643 if before { 644 for _, p := range posts { 645 list.AddPost(p) 646 list.AddOrder(p.Id) 647 } 648 } else { 649 l := len(posts) 650 for i := range posts { 651 list.AddPost(posts[l-i-1]) 652 list.AddOrder(posts[l-i-1].Id) 653 } 654 } 655 656 for _, p := range parents { 657 list.AddPost(p) 658 } 659 660 result.Data = list 661 } 662 }) 663 } 664 665 func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel { 666 return store.Do(func(result *store.StoreResult) { 667 var posts []*model.Post 668 _, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}) 669 if err != nil { 670 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 671 } else { 672 result.Data = posts 673 } 674 }) 675 } 676 677 func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel { 678 return store.Do(func(result *store.StoreResult) { 679 var posts []*model.Post 680 _, err := s.GetReplica().Select(&posts, 681 `SELECT 682 q2.* 683 FROM 684 Posts q2 685 INNER JOIN 686 (SELECT DISTINCT 687 q3.RootId 688 FROM 689 (SELECT 690 RootId 691 FROM 692 Posts 693 WHERE 694 ChannelId = :ChannelId1 695 AND DeleteAt = 0 696 ORDER BY CreateAt DESC 697 LIMIT :Limit OFFSET :Offset) q3 698 WHERE q3.RootId != '') q1 699 ON q1.RootId = q2.Id OR q1.RootId = q2.RootId 700 WHERE 701 ChannelId = :ChannelId2 702 AND DeleteAt = 0 703 ORDER BY CreateAt`, 704 map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId}) 705 if err != nil { 706 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError) 707 } else { 708 result.Data = posts 709 } 710 }) 711 } 712 713 var specialSearchChar = []string{ 714 "<", 715 ">", 716 "+", 717 "-", 718 "(", 719 ")", 720 "~", 721 "@", 722 ":", 723 } 724 725 func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel { 726 return store.Do(func(result *store.StoreResult) { 727 queryParams := map[string]interface{}{ 728 "TeamId": teamId, 729 "UserId": userId, 730 } 731 732 termMap := map[string]bool{} 733 terms := params.Terms 734 735 if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 { 736 result.Data = []*model.Post{} 737 return 738 } 739 740 searchType := "Message" 741 if params.IsHashtag { 742 searchType = "Hashtags" 743 for _, term := range strings.Split(terms, " ") { 744 termMap[strings.ToUpper(term)] = true 745 } 746 } 747 748 // these chars have special meaning and can be treated as spaces 749 for _, c := range specialSearchChar { 750 terms = strings.Replace(terms, c, " ", -1) 751 } 752 753 var posts []*model.Post 754 755 searchQuery := ` 756 SELECT 757 * 758 FROM 759 Posts 760 WHERE 761 DeleteAt = 0 762 AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%' 763 POST_FILTER 764 AND ChannelId IN ( 765 SELECT 766 Id 767 FROM 768 Channels, 769 ChannelMembers 770 WHERE 771 Id = ChannelId 772 AND (TeamId = :TeamId OR TeamId = '') 773 AND UserId = :UserId 774 AND DeleteAt = 0 775 CHANNEL_FILTER) 776 SEARCH_CLAUSE 777 ORDER BY CreateAt DESC 778 LIMIT 100` 779 780 if len(params.InChannels) > 1 { 781 inClause := ":InChannel0" 782 queryParams["InChannel0"] = params.InChannels[0] 783 784 for i := 1; i < len(params.InChannels); i++ { 785 paramName := "InChannel" + strconv.FormatInt(int64(i), 10) 786 inClause += ", :" + paramName 787 queryParams[paramName] = params.InChannels[i] 788 } 789 790 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1) 791 } else if len(params.InChannels) == 1 { 792 queryParams["InChannel"] = params.InChannels[0] 793 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1) 794 } else { 795 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1) 796 } 797 798 if len(params.FromUsers) > 1 { 799 inClause := ":FromUser0" 800 queryParams["FromUser0"] = params.FromUsers[0] 801 802 for i := 1; i < len(params.FromUsers); i++ { 803 paramName := "FromUser" + strconv.FormatInt(int64(i), 10) 804 inClause += ", :" + paramName 805 queryParams[paramName] = params.FromUsers[i] 806 } 807 808 searchQuery = strings.Replace(searchQuery, "POST_FILTER", ` 809 AND UserId IN ( 810 SELECT 811 Id 812 FROM 813 Users, 814 TeamMembers 815 WHERE 816 TeamMembers.TeamId = :TeamId 817 AND Users.Id = TeamMembers.UserId 818 AND Username IN (`+inClause+`))`, 1) 819 } else if len(params.FromUsers) == 1 { 820 queryParams["FromUser"] = params.FromUsers[0] 821 searchQuery = strings.Replace(searchQuery, "POST_FILTER", ` 822 AND UserId IN ( 823 SELECT 824 Id 825 FROM 826 Users, 827 TeamMembers 828 WHERE 829 TeamMembers.TeamId = :TeamId 830 AND Users.Id = TeamMembers.UserId 831 AND Username = :FromUser)`, 1) 832 } else { 833 searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1) 834 } 835 836 if terms == "" { 837 // we've already confirmed that we have a channel or user to search for 838 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) 839 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 840 // Parse text for wildcards 841 if wildcard, err := regexp.Compile(`\*($| )`); err == nil { 842 terms = wildcard.ReplaceAllLiteralString(terms, ":* ") 843 } 844 845 if params.OrTerms { 846 terms = strings.Join(strings.Fields(terms), " | ") 847 } else { 848 terms = strings.Join(strings.Fields(terms), " & ") 849 } 850 851 searchClause := fmt.Sprintf("AND %s @@ to_tsquery(:Terms)", searchType) 852 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 853 } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 854 searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType) 855 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 856 857 if !params.OrTerms { 858 splitTerms := strings.Fields(terms) 859 for i, t := range strings.Fields(terms) { 860 splitTerms[i] = "+" + t 861 } 862 863 terms = strings.Join(splitTerms, " ") 864 } 865 } 866 867 queryParams["Terms"] = terms 868 869 list := model.NewPostList() 870 871 _, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams) 872 if err != nil { 873 l4g.Warn(utils.T("store.sql_post.search.warn"), err.Error()) 874 // Don't return the error to the caller as it is of no use to the user. Instead return an empty set of search results. 875 } else { 876 for _, p := range posts { 877 if searchType == "Hashtags" { 878 exactMatch := false 879 for _, tag := range strings.Split(p.Hashtags, " ") { 880 if termMap[strings.ToUpper(tag)] { 881 exactMatch = true 882 } 883 } 884 if !exactMatch { 885 continue 886 } 887 } 888 list.AddPost(p) 889 list.AddOrder(p.Id) 890 } 891 } 892 893 list.MakeNonNil() 894 895 result.Data = list 896 }) 897 } 898 899 func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) store.StoreChannel { 900 return store.Do(func(result *store.StoreResult) { 901 query := 902 `SELECT DISTINCT 903 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 904 COUNT(DISTINCT Posts.UserId) AS Value 905 FROM Posts` 906 907 if len(teamId) > 0 { 908 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 909 } else { 910 query += " WHERE" 911 } 912 913 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 914 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 915 ORDER BY Name DESC 916 LIMIT 30` 917 918 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 919 query = 920 `SELECT 921 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value 922 FROM Posts` 923 924 if len(teamId) > 0 { 925 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 926 } else { 927 query += " WHERE" 928 } 929 930 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 931 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 932 ORDER BY Name DESC 933 LIMIT 30` 934 } 935 936 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 937 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 938 939 var rows model.AnalyticsRows 940 _, err := s.GetReplica().Select( 941 &rows, 942 query, 943 map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end}) 944 if err != nil { 945 result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 946 } else { 947 result.Data = rows 948 } 949 }) 950 } 951 952 func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) store.StoreChannel { 953 return store.Do(func(result *store.StoreResult) { 954 query := 955 `SELECT 956 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 957 COUNT(Posts.Id) AS Value 958 FROM Posts` 959 960 if len(teamId) > 0 { 961 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 962 } else { 963 query += " WHERE" 964 } 965 966 query += ` Posts.CreateAt <= :EndTime 967 AND Posts.CreateAt >= :StartTime 968 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 969 ORDER BY Name DESC 970 LIMIT 30` 971 972 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 973 query = 974 `SELECT 975 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value 976 FROM Posts` 977 978 if len(teamId) > 0 { 979 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 980 } else { 981 query += " WHERE" 982 } 983 984 query += ` Posts.CreateAt <= :EndTime 985 AND Posts.CreateAt >= :StartTime 986 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 987 ORDER BY Name DESC 988 LIMIT 30` 989 } 990 991 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 992 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 993 994 var rows model.AnalyticsRows 995 _, err := s.GetReplica().Select( 996 &rows, 997 query, 998 map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end}) 999 if err != nil { 1000 result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 1001 } else { 1002 result.Data = rows 1003 } 1004 }) 1005 } 1006 1007 func (s SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) store.StoreChannel { 1008 return store.Do(func(result *store.StoreResult) { 1009 query := 1010 `SELECT 1011 COUNT(Posts.Id) AS Value 1012 FROM 1013 Posts, 1014 Channels 1015 WHERE 1016 Posts.ChannelId = Channels.Id` 1017 1018 if len(teamId) > 0 { 1019 query += " AND Channels.TeamId = :TeamId" 1020 } 1021 1022 if mustHaveFile { 1023 query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')" 1024 } 1025 1026 if mustHaveHashtag { 1027 query += " AND Posts.Hashtags != ''" 1028 } 1029 1030 if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil { 1031 result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError) 1032 } else { 1033 result.Data = v 1034 } 1035 }) 1036 } 1037 1038 func (s SqlPostStore) GetPostsCreatedAt(channelId string, time int64) store.StoreChannel { 1039 return store.Do(func(result *store.StoreResult) { 1040 query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId` 1041 1042 var posts []*model.Post 1043 _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId}) 1044 1045 if err != nil { 1046 result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 1047 } else { 1048 result.Data = posts 1049 } 1050 }) 1051 } 1052 1053 func (s SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel { 1054 return store.Do(func(result *store.StoreResult) { 1055 keys := bytes.Buffer{} 1056 params := make(map[string]interface{}) 1057 for i, postId := range postIds { 1058 if keys.Len() > 0 { 1059 keys.WriteString(",") 1060 } 1061 1062 key := "Post" + strconv.Itoa(i) 1063 keys.WriteString(":" + key) 1064 params[key] = postId 1065 } 1066 1067 query := `SELECT * FROM Posts WHERE Id in (` + keys.String() + `) and DeleteAt = 0 ORDER BY CreateAt DESC` 1068 1069 var posts []*model.Post 1070 _, err := s.GetReplica().Select(&posts, query, params) 1071 1072 if err != nil { 1073 l4g.Error(err) 1074 result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError) 1075 } else { 1076 result.Data = posts 1077 } 1078 }) 1079 } 1080 1081 func (s SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) store.StoreChannel { 1082 return store.Do(func(result *store.StoreResult) { 1083 var posts []*model.PostForIndexing 1084 _, err1 := s.GetSearchReplica().Select(&posts, 1085 `SELECT 1086 PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt 1087 FROM ( 1088 SELECT 1089 * 1090 FROM 1091 Posts 1092 WHERE 1093 Posts.CreateAt >= :StartTime 1094 AND 1095 Posts.CreateAt < :EndTime 1096 ORDER BY 1097 CreateAt ASC 1098 LIMIT 1099 1000 1100 ) 1101 AS 1102 PostsQuery 1103 LEFT JOIN 1104 Channels 1105 ON 1106 PostsQuery.ChannelId = Channels.Id 1107 LEFT JOIN 1108 Posts ParentPosts 1109 ON 1110 PostsQuery.RootId = ParentPosts.Id`, 1111 map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit}) 1112 1113 if err1 != nil { 1114 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err1.Error(), http.StatusInternalServerError) 1115 } else { 1116 result.Data = posts 1117 } 1118 }) 1119 } 1120 1121 func (s SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel { 1122 return store.Do(func(result *store.StoreResult) { 1123 var query string 1124 if s.DriverName() == "postgres" { 1125 query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))" 1126 } else { 1127 query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit" 1128 } 1129 1130 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 1131 if err != nil { 1132 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1133 } else { 1134 rowsAffected, err1 := sqlResult.RowsAffected() 1135 if err1 != nil { 1136 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1137 result.Data = int64(0) 1138 } else { 1139 result.Data = rowsAffected 1140 } 1141 } 1142 }) 1143 } 1144 1145 func (s SqlPostStore) GetOldest() store.StoreChannel { 1146 return store.Do(func(result *store.StoreResult) { 1147 var post model.Post 1148 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1") 1149 if err != nil { 1150 result.Err = model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound) 1151 } 1152 1153 result.Data = &post 1154 }) 1155 }