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