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