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