github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+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 lastPostsCache.Remove(channelId) 326 } 327 328 func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel { 329 return store.Do(func(result *store.StoreResult) { 330 if allowFromCache { 331 if cacheItem, ok := lastPostTimeCache.Get(channelId); ok { 332 if s.metrics != nil { 333 s.metrics.IncrementMemCacheHitCounter("Last Post Time") 334 } 335 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, cacheItem.(int64)) 336 return 337 } else { 338 if s.metrics != nil { 339 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 340 } 341 } 342 } else { 343 if s.metrics != nil { 344 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 345 } 346 } 347 348 var et etagPosts 349 err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ChannelId = :ChannelId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ChannelId": channelId}) 350 if err != nil { 351 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis()) 352 } else { 353 result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, et.UpdateAt) 354 } 355 356 lastPostTimeCache.AddWithExpiresInSecs(channelId, et.UpdateAt, LAST_POST_TIME_CACHE_SEC) 357 }) 358 } 359 360 func (s SqlPostStore) Delete(postId string, time int64) store.StoreChannel { 361 return store.Do(func(result *store.StoreResult) { 362 _, 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}) 363 if err != nil { 364 result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError) 365 } 366 }) 367 } 368 369 func (s SqlPostStore) permanentDelete(postId string) store.StoreChannel { 370 return store.Do(func(result *store.StoreResult) { 371 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId}) 372 if err != nil { 373 result.Err = model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError) 374 } 375 }) 376 } 377 378 func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) store.StoreChannel { 379 return store.Do(func(result *store.StoreResult) { 380 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId}) 381 if err != nil { 382 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) 383 } 384 }) 385 } 386 387 func (s SqlPostStore) PermanentDeleteByUser(userId string) store.StoreChannel { 388 return store.Do(func(result *store.StoreResult) { 389 // First attempt to delete all the comments for a user 390 if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil { 391 result.Err = r.Err 392 return 393 } 394 395 // Now attempt to delete all the root posts for a user. This will also 396 // delete all the comments for each post. 397 found := true 398 count := 0 399 400 for found { 401 var ids []string 402 _, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId}) 403 if err != nil { 404 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) 405 return 406 } else { 407 found = false 408 for _, id := range ids { 409 found = true 410 if r := <-s.permanentDelete(id); r.Err != nil { 411 result.Err = r.Err 412 return 413 } 414 } 415 } 416 417 // This is a fail safe, give up if more than 10K messages 418 count = count + 1 419 if count >= 10 { 420 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError) 421 return 422 } 423 } 424 }) 425 } 426 427 func (s SqlPostStore) PermanentDeleteByChannel(channelId string) store.StoreChannel { 428 return store.Do(func(result *store.StoreResult) { 429 if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId}); err != nil { 430 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByChannel", "store.sql_post.permanent_delete_by_channel.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) 431 } 432 }) 433 } 434 435 func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) store.StoreChannel { 436 return store.Do(func(result *store.StoreResult) { 437 if limit > 1000 { 438 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId, http.StatusBadRequest) 439 return 440 } 441 442 if allowFromCache && offset == 0 && limit == 60 { 443 if cacheItem, ok := lastPostsCache.Get(channelId); ok { 444 if s.metrics != nil { 445 s.metrics.IncrementMemCacheHitCounter("Last Posts Cache") 446 } 447 448 result.Data = cacheItem.(*model.PostList) 449 return 450 } else { 451 if s.metrics != nil { 452 s.metrics.IncrementMemCacheMissCounter("Last Posts Cache") 453 } 454 } 455 } else { 456 if s.metrics != nil { 457 s.metrics.IncrementMemCacheMissCounter("Last Posts Cache") 458 } 459 } 460 461 rpc := s.getRootPosts(channelId, offset, limit) 462 cpc := s.getParentsPosts(channelId, offset, limit) 463 464 if rpr := <-rpc; rpr.Err != nil { 465 result.Err = rpr.Err 466 } else if cpr := <-cpc; cpr.Err != nil { 467 result.Err = cpr.Err 468 } else { 469 posts := rpr.Data.([]*model.Post) 470 parents := cpr.Data.([]*model.Post) 471 472 list := model.NewPostList() 473 474 for _, p := range posts { 475 list.AddPost(p) 476 list.AddOrder(p.Id) 477 } 478 479 for _, p := range parents { 480 list.AddPost(p) 481 } 482 483 list.MakeNonNil() 484 485 if offset == 0 && limit == 60 { 486 lastPostsCache.AddWithExpiresInSecs(channelId, list, LAST_POSTS_CACHE_SEC) 487 } 488 489 result.Data = list 490 } 491 }) 492 } 493 494 func (s SqlPostStore) GetPostsSince(channelId string, time int64, allowFromCache bool) store.StoreChannel { 495 return store.Do(func(result *store.StoreResult) { 496 if allowFromCache { 497 // If the last post in the channel's time is less than or equal to the time we are getting posts since, 498 // we can safely return no posts. 499 if cacheItem, ok := lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) <= time { 500 if s.metrics != nil { 501 s.metrics.IncrementMemCacheHitCounter("Last Post Time") 502 } 503 list := model.NewPostList() 504 result.Data = list 505 return 506 } else { 507 if s.metrics != nil { 508 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 509 } 510 } 511 } else { 512 if s.metrics != nil { 513 s.metrics.IncrementMemCacheMissCounter("Last Post Time") 514 } 515 } 516 517 var posts []*model.Post 518 _, err := s.GetReplica().Select(&posts, 519 `(SELECT 520 * 521 FROM 522 Posts 523 WHERE 524 (UpdateAt > :Time 525 AND ChannelId = :ChannelId) 526 LIMIT 1000) 527 UNION 528 (SELECT 529 * 530 FROM 531 Posts 532 WHERE 533 Id 534 IN 535 (SELECT * FROM (SELECT 536 RootId 537 FROM 538 Posts 539 WHERE 540 UpdateAt > :Time 541 AND ChannelId = :ChannelId 542 LIMIT 1000) temp_tab)) 543 ORDER BY CreateAt DESC`, 544 map[string]interface{}{"ChannelId": channelId, "Time": time}) 545 546 if err != nil { 547 result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 548 } else { 549 550 list := model.NewPostList() 551 552 var latestUpdate int64 = 0 553 554 for _, p := range posts { 555 list.AddPost(p) 556 if p.UpdateAt > time { 557 list.AddOrder(p.Id) 558 } 559 if latestUpdate < p.UpdateAt { 560 latestUpdate = p.UpdateAt 561 } 562 } 563 564 lastPostTimeCache.AddWithExpiresInSecs(channelId, latestUpdate, LAST_POST_TIME_CACHE_SEC) 565 566 result.Data = list 567 } 568 }) 569 } 570 571 func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) store.StoreChannel { 572 return s.getPostsAround(channelId, postId, numPosts, offset, true) 573 } 574 575 func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) store.StoreChannel { 576 return s.getPostsAround(channelId, postId, numPosts, offset, false) 577 } 578 579 func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) store.StoreChannel { 580 return store.Do(func(result *store.StoreResult) { 581 var direction string 582 var sort string 583 if before { 584 direction = "<" 585 sort = "DESC" 586 } else { 587 direction = ">" 588 sort = "ASC" 589 } 590 591 var posts []*model.Post 592 var parents []*model.Post 593 _, err1 := s.GetReplica().Select(&posts, 594 `(SELECT 595 * 596 FROM 597 Posts 598 WHERE 599 (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId) 600 AND ChannelId = :ChannelId 601 AND DeleteAt = 0) 602 ORDER BY CreateAt `+sort+` 603 LIMIT :NumPosts 604 OFFSET :Offset)`, 605 map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset}) 606 _, err2 := s.GetReplica().Select(&parents, 607 `(SELECT 608 * 609 FROM 610 Posts 611 WHERE 612 Id 613 IN 614 (SELECT * FROM (SELECT 615 RootId 616 FROM 617 Posts 618 WHERE 619 (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId) 620 AND ChannelId = :ChannelId 621 AND DeleteAt = 0) 622 ORDER BY CreateAt `+sort+` 623 LIMIT :NumPosts 624 OFFSET :Offset) 625 temp_tab)) 626 ORDER BY CreateAt DESC`, 627 map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset}) 628 629 if err1 != nil { 630 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error(), http.StatusInternalServerError) 631 } else if err2 != nil { 632 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error(), http.StatusInternalServerError) 633 } else { 634 635 list := model.NewPostList() 636 637 // We need to flip the order if we selected backwards 638 if before { 639 for _, p := range posts { 640 list.AddPost(p) 641 list.AddOrder(p.Id) 642 } 643 } else { 644 l := len(posts) 645 for i := range posts { 646 list.AddPost(posts[l-i-1]) 647 list.AddOrder(posts[l-i-1].Id) 648 } 649 } 650 651 for _, p := range parents { 652 list.AddPost(p) 653 } 654 655 result.Data = list 656 } 657 }) 658 } 659 660 func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel { 661 return store.Do(func(result *store.StoreResult) { 662 var posts []*model.Post 663 _, 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}) 664 if err != nil { 665 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 666 } else { 667 result.Data = posts 668 } 669 }) 670 } 671 672 func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel { 673 return store.Do(func(result *store.StoreResult) { 674 var posts []*model.Post 675 _, err := s.GetReplica().Select(&posts, 676 `SELECT 677 q2.* 678 FROM 679 Posts q2 680 INNER JOIN 681 (SELECT DISTINCT 682 q3.RootId 683 FROM 684 (SELECT 685 RootId 686 FROM 687 Posts 688 WHERE 689 ChannelId = :ChannelId1 690 AND DeleteAt = 0 691 ORDER BY CreateAt DESC 692 LIMIT :Limit OFFSET :Offset) q3 693 WHERE q3.RootId != '') q1 694 ON q1.RootId = q2.Id OR q1.RootId = q2.RootId 695 WHERE 696 ChannelId = :ChannelId2 697 AND DeleteAt = 0 698 ORDER BY CreateAt`, 699 map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId}) 700 if err != nil { 701 result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError) 702 } else { 703 result.Data = posts 704 } 705 }) 706 } 707 708 var specialSearchChar = []string{ 709 "<", 710 ">", 711 "+", 712 "-", 713 "(", 714 ")", 715 "~", 716 "@", 717 ":", 718 } 719 720 func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) store.StoreChannel { 721 return store.Do(func(result *store.StoreResult) { 722 queryParams := map[string]interface{}{ 723 "TeamId": teamId, 724 "UserId": userId, 725 } 726 727 termMap := map[string]bool{} 728 terms := params.Terms 729 730 if terms == "" && len(params.InChannels) == 0 && len(params.FromUsers) == 0 { 731 result.Data = []*model.Post{} 732 return 733 } 734 735 searchType := "Message" 736 if params.IsHashtag { 737 searchType = "Hashtags" 738 for _, term := range strings.Split(terms, " ") { 739 termMap[strings.ToUpper(term)] = true 740 } 741 } 742 743 // these chars have special meaning and can be treated as spaces 744 for _, c := range specialSearchChar { 745 terms = strings.Replace(terms, c, " ", -1) 746 } 747 748 var posts []*model.Post 749 750 searchQuery := ` 751 SELECT 752 * 753 FROM 754 Posts 755 WHERE 756 DeleteAt = 0 757 AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%' 758 POST_FILTER 759 AND ChannelId IN ( 760 SELECT 761 Id 762 FROM 763 Channels, 764 ChannelMembers 765 WHERE 766 Id = ChannelId 767 AND (TeamId = :TeamId OR TeamId = '') 768 AND UserId = :UserId 769 AND DeleteAt = 0 770 CHANNEL_FILTER) 771 SEARCH_CLAUSE 772 ORDER BY CreateAt DESC 773 LIMIT 100` 774 775 if len(params.InChannels) > 1 { 776 inClause := ":InChannel0" 777 queryParams["InChannel0"] = params.InChannels[0] 778 779 for i := 1; i < len(params.InChannels); i++ { 780 paramName := "InChannel" + strconv.FormatInt(int64(i), 10) 781 inClause += ", :" + paramName 782 queryParams[paramName] = params.InChannels[i] 783 } 784 785 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name IN ("+inClause+")", 1) 786 } else if len(params.InChannels) == 1 { 787 queryParams["InChannel"] = params.InChannels[0] 788 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1) 789 } else { 790 searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1) 791 } 792 793 if len(params.FromUsers) > 1 { 794 inClause := ":FromUser0" 795 queryParams["FromUser0"] = params.FromUsers[0] 796 797 for i := 1; i < len(params.FromUsers); i++ { 798 paramName := "FromUser" + strconv.FormatInt(int64(i), 10) 799 inClause += ", :" + paramName 800 queryParams[paramName] = params.FromUsers[i] 801 } 802 803 searchQuery = strings.Replace(searchQuery, "POST_FILTER", ` 804 AND UserId IN ( 805 SELECT 806 Id 807 FROM 808 Users, 809 TeamMembers 810 WHERE 811 TeamMembers.TeamId = :TeamId 812 AND Users.Id = TeamMembers.UserId 813 AND Username IN (`+inClause+`))`, 1) 814 } else if len(params.FromUsers) == 1 { 815 queryParams["FromUser"] = params.FromUsers[0] 816 searchQuery = strings.Replace(searchQuery, "POST_FILTER", ` 817 AND UserId IN ( 818 SELECT 819 Id 820 FROM 821 Users, 822 TeamMembers 823 WHERE 824 TeamMembers.TeamId = :TeamId 825 AND Users.Id = TeamMembers.UserId 826 AND Username = :FromUser)`, 1) 827 } else { 828 searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1) 829 } 830 831 if terms == "" { 832 // we've already confirmed that we have a channel or user to search for 833 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) 834 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 835 // Parse text for wildcards 836 if wildcard, err := regexp.Compile(`\*($| )`); err == nil { 837 terms = wildcard.ReplaceAllLiteralString(terms, ":* ") 838 } 839 840 if params.OrTerms { 841 terms = strings.Join(strings.Fields(terms), " | ") 842 } else { 843 terms = strings.Join(strings.Fields(terms), " & ") 844 } 845 846 searchClause := fmt.Sprintf("AND %s @@ to_tsquery(:Terms)", searchType) 847 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 848 } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 849 searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType) 850 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 851 852 if !params.OrTerms { 853 splitTerms := strings.Fields(terms) 854 for i, t := range strings.Fields(terms) { 855 splitTerms[i] = "+" + t 856 } 857 858 terms = strings.Join(splitTerms, " ") 859 } 860 } 861 862 queryParams["Terms"] = terms 863 864 list := model.NewPostList() 865 866 _, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams) 867 if err != nil { 868 l4g.Warn(utils.T("store.sql_post.search.warn"), err.Error()) 869 // 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. 870 } else { 871 for _, p := range posts { 872 if searchType == "Hashtags" { 873 exactMatch := false 874 for _, tag := range strings.Split(p.Hashtags, " ") { 875 if termMap[strings.ToUpper(tag)] { 876 exactMatch = true 877 } 878 } 879 if !exactMatch { 880 continue 881 } 882 } 883 list.AddPost(p) 884 list.AddOrder(p.Id) 885 } 886 } 887 888 list.MakeNonNil() 889 890 result.Data = list 891 }) 892 } 893 894 func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) store.StoreChannel { 895 return store.Do(func(result *store.StoreResult) { 896 query := 897 `SELECT DISTINCT 898 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 899 COUNT(DISTINCT Posts.UserId) AS Value 900 FROM Posts` 901 902 if len(teamId) > 0 { 903 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 904 } else { 905 query += " WHERE" 906 } 907 908 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 909 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 910 ORDER BY Name DESC 911 LIMIT 30` 912 913 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 914 query = 915 `SELECT 916 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value 917 FROM Posts` 918 919 if len(teamId) > 0 { 920 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 921 } else { 922 query += " WHERE" 923 } 924 925 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 926 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 927 ORDER BY Name DESC 928 LIMIT 30` 929 } 930 931 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 932 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 933 934 var rows model.AnalyticsRows 935 _, err := s.GetReplica().Select( 936 &rows, 937 query, 938 map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end}) 939 if err != nil { 940 result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 941 } else { 942 result.Data = rows 943 } 944 }) 945 } 946 947 func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) store.StoreChannel { 948 return store.Do(func(result *store.StoreResult) { 949 query := 950 `SELECT 951 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 952 COUNT(Posts.Id) AS Value 953 FROM Posts` 954 955 if len(teamId) > 0 { 956 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 957 } else { 958 query += " WHERE" 959 } 960 961 query += ` Posts.CreateAt <= :EndTime 962 AND Posts.CreateAt >= :StartTime 963 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 964 ORDER BY Name DESC 965 LIMIT 30` 966 967 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 968 query = 969 `SELECT 970 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value 971 FROM Posts` 972 973 if len(teamId) > 0 { 974 query += " INNER JOIN Channels ON Posts.ChannelId = Channels.Id AND Channels.TeamId = :TeamId AND" 975 } else { 976 query += " WHERE" 977 } 978 979 query += ` Posts.CreateAt <= :EndTime 980 AND Posts.CreateAt >= :StartTime 981 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 982 ORDER BY Name DESC 983 LIMIT 30` 984 } 985 986 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 987 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 988 989 var rows model.AnalyticsRows 990 _, err := s.GetReplica().Select( 991 &rows, 992 query, 993 map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end}) 994 if err != nil { 995 result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 996 } else { 997 result.Data = rows 998 } 999 }) 1000 } 1001 1002 func (s SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) store.StoreChannel { 1003 return store.Do(func(result *store.StoreResult) { 1004 query := 1005 `SELECT 1006 COUNT(Posts.Id) AS Value 1007 FROM 1008 Posts, 1009 Channels 1010 WHERE 1011 Posts.ChannelId = Channels.Id` 1012 1013 if len(teamId) > 0 { 1014 query += " AND Channels.TeamId = :TeamId" 1015 } 1016 1017 if mustHaveFile { 1018 query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')" 1019 } 1020 1021 if mustHaveHashtag { 1022 query += " AND Posts.Hashtags != ''" 1023 } 1024 1025 if v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId}); err != nil { 1026 result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError) 1027 } else { 1028 result.Data = v 1029 } 1030 }) 1031 } 1032 1033 func (s SqlPostStore) GetPostsCreatedAt(channelId string, time int64) store.StoreChannel { 1034 return store.Do(func(result *store.StoreResult) { 1035 query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ChannelId = :ChannelId` 1036 1037 var posts []*model.Post 1038 _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ChannelId": channelId}) 1039 1040 if err != nil { 1041 result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError) 1042 } else { 1043 result.Data = posts 1044 } 1045 }) 1046 } 1047 1048 func (s SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel { 1049 return store.Do(func(result *store.StoreResult) { 1050 keys := bytes.Buffer{} 1051 params := make(map[string]interface{}) 1052 for i, postId := range postIds { 1053 if keys.Len() > 0 { 1054 keys.WriteString(",") 1055 } 1056 1057 key := "Post" + strconv.Itoa(i) 1058 keys.WriteString(":" + key) 1059 params[key] = postId 1060 } 1061 1062 query := `SELECT * FROM Posts WHERE Id in (` + keys.String() + `) and DeleteAt = 0 ORDER BY CreateAt DESC` 1063 1064 var posts []*model.Post 1065 _, err := s.GetReplica().Select(&posts, query, params) 1066 1067 if err != nil { 1068 l4g.Error(err) 1069 result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError) 1070 } else { 1071 result.Data = posts 1072 } 1073 }) 1074 } 1075 1076 func (s SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) store.StoreChannel { 1077 return store.Do(func(result *store.StoreResult) { 1078 var posts []*model.PostForIndexing 1079 _, err1 := s.GetSearchReplica().Select(&posts, 1080 `SELECT 1081 PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt 1082 FROM ( 1083 SELECT 1084 * 1085 FROM 1086 Posts 1087 WHERE 1088 Posts.CreateAt >= :StartTime 1089 AND 1090 Posts.CreateAt < :EndTime 1091 ORDER BY 1092 CreateAt ASC 1093 LIMIT 1094 1000 1095 ) 1096 AS 1097 PostsQuery 1098 LEFT JOIN 1099 Channels 1100 ON 1101 PostsQuery.ChannelId = Channels.Id 1102 LEFT JOIN 1103 Posts ParentPosts 1104 ON 1105 PostsQuery.RootId = ParentPosts.Id`, 1106 map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit}) 1107 1108 if err1 != nil { 1109 result.Err = model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err1.Error(), http.StatusInternalServerError) 1110 } else { 1111 result.Data = posts 1112 } 1113 }) 1114 } 1115 1116 func (s SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel { 1117 return store.Do(func(result *store.StoreResult) { 1118 var query string 1119 if s.DriverName() == "postgres" { 1120 query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))" 1121 } else { 1122 query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit" 1123 } 1124 1125 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 1126 if err != nil { 1127 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1128 } else { 1129 rowsAffected, err1 := sqlResult.RowsAffected() 1130 if err1 != nil { 1131 result.Err = model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1132 result.Data = int64(0) 1133 } else { 1134 result.Data = rowsAffected 1135 } 1136 } 1137 }) 1138 } 1139 1140 func (s SqlPostStore) GetOldest() store.StoreChannel { 1141 return store.Do(func(result *store.StoreResult) { 1142 var post model.Post 1143 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1") 1144 if err != nil { 1145 result.Err = model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound) 1146 } 1147 1148 result.Data = &post 1149 }) 1150 }