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