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