github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/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 "database/sql" 8 "fmt" 9 "net/http" 10 "regexp" 11 "strconv" 12 "strings" 13 "sync" 14 15 sq "github.com/Masterminds/squirrel" 16 "github.com/vnforks/kid/v5/einterfaces" 17 "github.com/vnforks/kid/v5/mlog" 18 "github.com/vnforks/kid/v5/model" 19 "github.com/vnforks/kid/v5/store" 20 "github.com/vnforks/kid/v5/utils" 21 ) 22 23 type SqlPostStore struct { 24 SqlStore 25 metrics einterfaces.MetricsInterface 26 maxPostSizeOnce sync.Once 27 maxPostSizeCached int 28 } 29 30 func (s *SqlPostStore) ClearCaches() { 31 } 32 33 func postSliceColumns() []string { 34 return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "UserId", "ClassId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions"} 35 } 36 37 func postToSlice(post *model.Post) []interface{} { 38 return []interface{}{ 39 post.Id, 40 post.CreateAt, 41 post.UpdateAt, 42 post.EditAt, 43 post.DeleteAt, 44 post.UserId, 45 post.ClassId, 46 post.Message, 47 post.Type, 48 model.StringInterfaceToJson(post.Props), 49 post.Hashtags, 50 model.ArrayToJson(post.Filenames), 51 model.ArrayToJson(post.FileIds), 52 post.HasReactions, 53 } 54 } 55 56 func newSqlPostStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) store.PostStore { 57 s := &SqlPostStore{ 58 SqlStore: sqlStore, 59 metrics: metrics, 60 maxPostSizeCached: model.POST_MESSAGE_MAX_RUNES_V1, 61 } 62 63 for _, db := range sqlStore.GetAllConns() { 64 table := db.AddTableWithName(model.Post{}, "Posts").SetKeys(false, "Id") 65 table.ColMap("Id").SetMaxSize(26) 66 table.ColMap("UserId").SetMaxSize(26) 67 table.ColMap("ClassId").SetMaxSize(26) 68 table.ColMap("Message").SetMaxSize(model.POST_MESSAGE_MAX_BYTES_V2) 69 table.ColMap("Type").SetMaxSize(26) 70 table.ColMap("Hashtags").SetMaxSize(1000) 71 table.ColMap("Props").SetMaxSize(8000) 72 table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES) 73 table.ColMap("FileIds").SetMaxSize(150) 74 } 75 76 return s 77 } 78 79 func (s *SqlPostStore) createIndexesIfNotExists() { 80 s.CreateIndexIfNotExists("idx_posts_update_at", "Posts", "UpdateAt") 81 s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt") 82 s.CreateIndexIfNotExists("idx_posts_delete_at", "Posts", "DeleteAt") 83 s.CreateIndexIfNotExists("idx_posts_class_id", "Posts", "ClassId") 84 s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId") 85 86 s.CreateCompositeIndexIfNotExists("idx_posts_class_id_update_at", "Posts", []string{"ClassId", "UpdateAt"}) 87 s.CreateCompositeIndexIfNotExists("idx_posts_class_id_delete_at_create_at", "Posts", []string{"ClassId", "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) SaveMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) { 94 classNewPosts := make(map[string]int) 95 maxDateNewPosts := make(map[string]int64) 96 // rootIds := make(map[string]int) 97 // maxDateRootIds := make(map[string]int64) 98 for _, post := range posts { 99 if len(post.Id) > 0 { 100 return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.existing.app_error", nil, "id="+post.Id, http.StatusBadRequest) 101 } 102 post.PreSave() 103 maxPostSize := s.GetMaxPostSize() 104 if err := post.IsValid(maxPostSize); err != nil { 105 return nil, err 106 } 107 108 currentClassCount, ok := classNewPosts[post.ClassId] 109 if !ok { 110 if post.IsJoinLeaveMessage() { 111 classNewPosts[post.ClassId] = 0 112 } else { 113 classNewPosts[post.ClassId] = 1 114 } 115 maxDateNewPosts[post.ClassId] = post.CreateAt 116 } else { 117 if !post.IsJoinLeaveMessage() { 118 classNewPosts[post.ClassId] = currentClassCount + 1 119 } 120 if post.CreateAt > maxDateNewPosts[post.ClassId] { 121 maxDateNewPosts[post.ClassId] = post.CreateAt 122 } 123 } 124 125 } 126 127 query := s.getQueryBuilder().Insert("Posts").Columns(postSliceColumns()...) 128 for _, post := range posts { 129 query = query.Values(postToSlice(post)...) 130 } 131 sql, args, err := query.ToSql() 132 if err != nil { 133 return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError) 134 } 135 136 if _, err := s.GetMaster().Exec(sql, args...); err != nil { 137 return nil, model.NewAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, err.Error(), http.StatusInternalServerError) 138 } 139 140 for classId, count := range classNewPosts { 141 if _, err := s.GetMaster().Exec("UPDATE Classes SET LastPostAt = GREATEST(:LastPostAt, LastPostAt), TotalMsgCount = TotalMsgCount + :Count WHERE Id = :ClassId", map[string]interface{}{"LastPostAt": maxDateNewPosts[classId], "ClassId": classId, "Count": count}); err != nil { 142 mlog.Error("Error updating Class LastPostAt.", mlog.Err(err)) 143 } 144 } 145 146 return posts, nil 147 } 148 149 func (s *SqlPostStore) Save(post *model.Post) (*model.Post, *model.AppError) { 150 posts, err := s.SaveMultiple([]*model.Post{post}) 151 if err != nil { 152 return nil, err 153 } 154 return posts[0], nil 155 } 156 157 func (s *SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, *model.AppError) { 158 newPost.UpdateAt = model.GetMillis() 159 newPost.PreCommit() 160 161 oldPost.DeleteAt = newPost.UpdateAt 162 oldPost.UpdateAt = newPost.UpdateAt 163 oldPost.Id = model.NewId() 164 oldPost.PreCommit() 165 166 maxPostSize := s.GetMaxPostSize() 167 168 if err := newPost.IsValid(maxPostSize); err != nil { 169 return nil, err 170 } 171 172 if _, err := s.GetMaster().Update(newPost); err != nil { 173 return nil, model.NewAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+newPost.Id+", "+err.Error(), http.StatusInternalServerError) 174 } 175 176 time := model.GetMillis() 177 s.GetMaster().Exec("UPDATE Classes SET LastPostAt = :LastPostAt WHERE Id = :ClassId AND LastPostAt < :LastPostAt", map[string]interface{}{"LastPostAt": time, "ClassId": newPost.ClassId}) 178 179 // mark the old post as deleted 180 s.GetMaster().Insert(oldPost) 181 182 return newPost, nil 183 } 184 185 func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) { 186 updateAt := model.GetMillis() 187 maxPostSize := s.GetMaxPostSize() 188 for _, post := range posts { 189 post.UpdateAt = updateAt 190 if appErr := post.IsValid(maxPostSize); appErr != nil { 191 return nil, appErr 192 } 193 } 194 195 tx, err := s.GetMaster().Begin() 196 if err != nil { 197 return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError) 198 } 199 for _, post := range posts { 200 if _, err = tx.Update(post); err != nil { 201 txErr := tx.Rollback() 202 if txErr != nil { 203 return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, txErr.Error(), http.StatusInternalServerError) 204 } 205 206 return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error(), http.StatusInternalServerError) 207 } 208 } 209 err = tx.Commit() 210 if err != nil { 211 return nil, model.NewAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError) 212 } 213 214 return posts, nil 215 } 216 217 func (s *SqlPostStore) Overwrite(post *model.Post) (*model.Post, *model.AppError) { 218 posts, err := s.OverwriteMultiple([]*model.Post{post}) 219 if err != nil { 220 return nil, err 221 } 222 223 return posts[0], nil 224 } 225 226 func (s *SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 227 pl := model.NewPostList() 228 229 var posts []*model.Post 230 if _, err := s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p 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 { 231 return nil, model.NewAppError("SqlPostStore.GetFlaggedPosts", "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) GetFlaggedPostsForBranch(userId, branchId string, offset int, limit int) (*model.PostList, *model.AppError) { 243 pl := model.NewPostList() 244 245 var posts []*model.Post 246 247 query := ` 248 SELECT 249 A.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN A.RootId = '' THEN A.Id ELSE A.RootId END) AND Posts.DeleteAt = 0) as ReplyCount 250 FROM 251 (SELECT 252 * 253 FROM 254 Posts 255 WHERE 256 Id 257 IN 258 (SELECT 259 Name 260 FROM 261 Preferences 262 WHERE 263 UserId = :UserId 264 AND Category = :Category) 265 AND DeleteAt = 0 266 ) as A 267 INNER JOIN Classes as B 268 ON B.Id = A.ClassId 269 WHERE B.BranchId = :BranchId OR B.BranchId = '' 270 ORDER BY CreateAt DESC 271 LIMIT :Limit OFFSET :Offset` 272 273 if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit, "BranchId": branchId}); err != nil { 274 return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForBranch", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 275 } 276 277 for _, post := range posts { 278 pl.AddPost(post) 279 pl.AddOrder(post.Id) 280 } 281 282 return pl, nil 283 } 284 285 func (s *SqlPostStore) GetFlaggedPostsForClass(userId, classId string, offset int, limit int) (*model.PostList, *model.AppError) { 286 pl := model.NewPostList() 287 288 var posts []*model.Post 289 query := ` 290 SELECT 291 *, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount 292 FROM Posts p 293 WHERE 294 Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) 295 AND ClassId = :ClassId 296 AND DeleteAt = 0 297 ORDER BY CreateAt DESC 298 LIMIT :Limit OFFSET :Offset` 299 300 if _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "ClassId": classId, "Offset": offset, "Limit": limit}); err != nil { 301 return nil, model.NewAppError("SqlPostStore.GetFlaggedPostsForClass", "store.sql_post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 302 } 303 for _, post := range posts { 304 pl.AddPost(post) 305 pl.AddOrder(post.Id) 306 } 307 308 return pl, nil 309 } 310 311 func (s *SqlPostStore) Get(id string, skipFetchThreads bool) (*model.PostList, *model.AppError) { 312 pl := model.NewPostList() 313 314 if len(id) == 0 { 315 return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id, http.StatusBadRequest) 316 } 317 318 var post model.Post 319 postFetchQuery := "SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id = :Id AND p.DeleteAt = 0" 320 err := s.GetReplica().SelectOne(&post, postFetchQuery, map[string]interface{}{"Id": id}) 321 if err != nil { 322 return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound) 323 } 324 pl.AddPost(&post) 325 pl.AddOrder(id) 326 // if !skipFetchThreads { 327 328 // if len(rootId) == 0 { 329 // return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId, http.StatusInternalServerError) 330 // } 331 332 // var posts []*model.Post 333 // _, err = s.GetReplica().Select(&posts, "SELECT *, (SELECT count(Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId}) 334 // if err != nil { 335 // return nil, model.NewAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error(), http.StatusInternalServerError) 336 // } 337 338 // for _, p := range posts { 339 // pl.AddPost(p) 340 // pl.AddOrder(p.Id) 341 // } 342 // } 343 return pl, nil 344 } 345 346 func (s *SqlPostStore) GetSingle(id string) (*model.Post, *model.AppError) { 347 var post model.Post 348 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}) 349 if err != nil { 350 return nil, model.NewAppError("SqlPostStore.GetSingle", "store.sql_post.get.app_error", nil, "id="+id+err.Error(), http.StatusNotFound) 351 } 352 return &post, nil 353 } 354 355 type etagPosts struct { 356 Id string 357 UpdateAt int64 358 } 359 360 func (s *SqlPostStore) InvalidateLastPostTimeCache(classId string) { 361 } 362 363 func (s *SqlPostStore) GetEtag(classId string, allowFromCache bool) string { 364 var et etagPosts 365 err := s.GetReplica().SelectOne(&et, "SELECT Id, UpdateAt FROM Posts WHERE ClassId = :ClassId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"ClassId": classId}) 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 return result 374 } 375 376 func (s *SqlPostStore) Delete(postId string, time int64, deleteByID string) *model.AppError { 377 378 appErr := func(errMsg string) *model.AppError { 379 return model.NewAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+errMsg, http.StatusInternalServerError) 380 } 381 382 var post model.Post 383 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": postId}) 384 if err != nil { 385 return appErr(err.Error()) 386 } 387 388 post.AddProp(model.POST_PROPS_DELETE_BY, deleteByID) 389 390 _, 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.GetProps())}) 391 if err != nil { 392 return appErr(err.Error()) 393 } 394 395 return nil 396 } 397 398 func (s *SqlPostStore) permanentDelete(postId string) *model.AppError { 399 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR RootId = :RootId", map[string]interface{}{"Id": postId, "RootId": postId}) 400 if err != nil { 401 return model.NewAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error(), http.StatusInternalServerError) 402 } 403 return nil 404 } 405 406 func (s *SqlPostStore) permanentDeleteAllCommentByUser(userId string) *model.AppError { 407 _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId}) 408 if err != nil { 409 return model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) 410 } 411 return nil 412 } 413 414 func (s *SqlPostStore) PermanentDeleteByUser(userId string) *model.AppError { 415 // First attempt to delete all the comments for a user 416 if err := s.permanentDeleteAllCommentByUser(userId); err != nil { 417 return err 418 } 419 420 // Now attempt to delete all the root posts for a user. This will also 421 // delete all the comments for each post 422 found := true 423 count := 0 424 425 for found { 426 var ids []string 427 _, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId}) 428 if err != nil { 429 return model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) 430 } 431 432 found = false 433 for _, id := range ids { 434 found = true 435 if err := s.permanentDelete(id); err != nil { 436 return err 437 } 438 } 439 440 // This is a fail safe, give up if more than 10k messages 441 count++ 442 if count >= 10 { 443 return model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId, http.StatusInternalServerError) 444 } 445 } 446 447 return nil 448 } 449 450 func (s *SqlPostStore) PermanentDeleteByClass(classId string) *model.AppError { 451 if _, err := s.GetMaster().Exec("DELETE FROM Posts WHERE ClassId = :ClassId", map[string]interface{}{"ClassId": classId}); err != nil { 452 return model.NewAppError("SqlPostStore.PermanentDeleteByClass", "store.sql_post.permanent_delete_by_class.app_error", nil, "class_id="+classId+", "+err.Error(), http.StatusInternalServerError) 453 } 454 return nil 455 } 456 457 func (s *SqlPostStore) GetPosts(options model.GetPostsOptions, _ bool) (*model.PostList, *model.AppError) { 458 if options.PerPage > 1000 { 459 return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "classId="+options.ClassId, http.StatusBadRequest) 460 } 461 offset := options.PerPage * options.Page 462 463 rpc := make(chan store.StoreResult, 1) 464 go func() { 465 posts, err := s.getRootPosts(options.ClassId, offset, options.PerPage, options.SkipFetchThreads) 466 rpc <- store.StoreResult{Data: posts, Err: err} 467 close(rpc) 468 }() 469 cpc := make(chan store.StoreResult, 1) 470 go func() { 471 posts, err := s.getParentsPosts(options.ClassId, offset, options.PerPage, options.SkipFetchThreads) 472 cpc <- store.StoreResult{Data: posts, Err: err} 473 close(cpc) 474 }() 475 476 var err *model.AppError 477 list := model.NewPostList() 478 479 rpr := <-rpc 480 if rpr.Err != nil { 481 return nil, rpr.Err 482 } 483 484 cpr := <-cpc 485 if cpr.Err != nil { 486 return nil, cpr.Err 487 } 488 489 posts := rpr.Data.([]*model.Post) 490 parents := cpr.Data.([]*model.Post) 491 492 for _, p := range posts { 493 list.AddPost(p) 494 list.AddOrder(p.Id) 495 } 496 497 for _, p := range parents { 498 list.AddPost(p) 499 } 500 501 list.MakeNonNil() 502 503 return list, err 504 } 505 506 func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool) (*model.PostList, *model.AppError) { 507 var posts []*model.Post 508 509 replyCountQuery1 := "" 510 replyCountQuery2 := "" 511 if options.SkipFetchThreads { 512 replyCountQuery1 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p1.RootId = '' THEN p1.Id ELSE p1.RootId END) AND Posts.DeleteAt = 0) as ReplyCount` 513 replyCountQuery2 = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p2.RootId = '' THEN p2.Id ELSE p2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount` 514 } 515 var query string 516 517 // union of IDs and then join to get full posts is faster in mysql 518 if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 519 query = `SELECT *` + replyCountQuery1 + ` FROM Posts p1 JOIN ( 520 (SELECT 521 Id 522 FROM 523 Posts p2 524 WHERE 525 (UpdateAt > :Time 526 AND ClassId = :ClassId) 527 LIMIT 1000) 528 UNION 529 (SELECT 530 Id 531 FROM 532 Posts p3 533 WHERE 534 Id 535 IN 536 (SELECT * FROM (SELECT 537 RootId 538 FROM 539 Posts 540 WHERE 541 UpdateAt > :Time 542 AND ClassId = :ClassId 543 LIMIT 1000) temp_tab)) 544 ) j ON p1.Id = j.Id 545 ORDER BY CreateAt DESC` 546 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 547 query = ` 548 (SELECT 549 *` + replyCountQuery1 + ` 550 FROM 551 Posts p1 552 WHERE 553 (UpdateAt > :Time 554 AND ClassId = :ClassId) 555 LIMIT 1000) 556 UNION 557 (SELECT 558 *` + replyCountQuery2 + ` 559 FROM 560 Posts p2 561 WHERE 562 Id 563 IN 564 (SELECT * FROM (SELECT 565 RootId 566 FROM 567 Posts 568 WHERE 569 UpdateAt > :Time 570 AND ClassId = :ClassId 571 LIMIT 1000) temp_tab)) 572 ORDER BY CreateAt DESC` 573 } 574 _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"ClassId": options.ClassId, "Time": options.Time}) 575 576 if err != nil { 577 return nil, model.NewAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError) 578 } 579 580 list := model.NewPostList() 581 582 for _, p := range posts { 583 list.AddPost(p) 584 if p.UpdateAt > options.Time { 585 list.AddOrder(p.Id) 586 } 587 } 588 589 return list, nil 590 } 591 592 func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 593 return s.getPostsAround(true, options) 594 } 595 596 func (s *SqlPostStore) GetPostsAfter(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 597 return s.getPostsAround(false, options) 598 } 599 600 func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) { 601 offset := options.Page * options.PerPage 602 var posts, parents []*model.Post 603 604 var direction string 605 var sort string 606 if before { 607 direction = "<" 608 sort = "DESC" 609 } else { 610 direction = ">" 611 sort = "ASC" 612 } 613 replyCountSubQuery := s.getQueryBuilder().Select("COUNT(Posts.Id)").From("Posts").Where(sq.Expr("p.RootId = '' AND RootId = p.Id AND DeleteAt = 0")) 614 query := s.getQueryBuilder().Select("p.*") 615 if options.SkipFetchThreads { 616 query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount")) 617 } 618 query = query.From("Posts p"). 619 Where(sq.And{ 620 sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId), 621 sq.Eq{"ClassId": options.ClassId}, 622 sq.Eq{"DeleteAt": int(0)}, 623 }). 624 OrderBy("CreateAt " + sort). 625 Limit(uint64(options.PerPage)). 626 Offset(uint64(offset)) 627 628 queryString, args, err := query.ToSql() 629 630 if err != nil { 631 return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError) 632 } 633 _, err = s.GetMaster().Select(&posts, queryString, args...) 634 if err != nil { 635 return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError) 636 } 637 638 if len(posts) > 0 { 639 // rootIds := []string{} 640 // for _, post := range posts { 641 // rootIds = append(rootIds, post.Id) 642 // if post.RootId != "" { 643 // rootIds = append(rootIds, post.RootId) 644 // } 645 // } 646 // rootQuery := s.getQueryBuilder().Select("p.*") 647 // idQuery := sq.Or{ 648 // sq.Eq{"Id": rootIds}, 649 // } 650 // if options.SkipFetchThreads { 651 // rootQuery = rootQuery.Column(sq.Alias(replyCountSubQuery, "ReplyCount")) 652 // } else { 653 // idQuery = append(idQuery, sq.Eq{"RootId": rootIds}) // preserve original behaviour 654 // } 655 656 // rootQuery = rootQuery.From("Posts p"). 657 // Where(sq.And{ 658 // idQuery, 659 // sq.Eq{"ClassId": options.ClassId}, 660 // sq.Eq{"DeleteAt": 0}, 661 // }). 662 // OrderBy("CreateAt DESC") 663 664 // rootQueryString, rootArgs, err := rootQuery.ToSql() 665 666 // if err != nil { 667 // return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError) 668 // } 669 // _, err = s.GetMaster().Select(&parents, rootQueryString, rootArgs...) 670 // if err != nil { 671 // return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "classId="+options.ClassId+err.Error(), http.StatusInternalServerError) 672 // } 673 } 674 675 list := model.NewPostList() 676 677 // We need to flip the order if we selected backwards 678 if before { 679 for _, p := range posts { 680 list.AddPost(p) 681 list.AddOrder(p.Id) 682 } 683 } else { 684 l := len(posts) 685 for i := range posts { 686 list.AddPost(posts[l-i-1]) 687 list.AddOrder(posts[l-i-1].Id) 688 } 689 } 690 691 for _, p := range parents { 692 list.AddPost(p) 693 } 694 695 return list, nil 696 } 697 698 func (s *SqlPostStore) GetPostIdBeforeTime(classId string, time int64) (string, *model.AppError) { 699 return s.getPostIdAroundTime(classId, time, true) 700 } 701 702 func (s *SqlPostStore) GetPostIdAfterTime(classId string, time int64) (string, *model.AppError) { 703 return s.getPostIdAroundTime(classId, time, false) 704 } 705 706 func (s *SqlPostStore) getPostIdAroundTime(classId string, time int64, before bool) (string, *model.AppError) { 707 var direction sq.Sqlizer 708 var sort string 709 if before { 710 direction = sq.Lt{"CreateAt": time} 711 sort = "DESC" 712 } else { 713 direction = sq.Gt{"CreateAt": time} 714 sort = "ASC" 715 } 716 717 query := s.getQueryBuilder(). 718 Select("Id"). 719 From("Posts"). 720 Where(sq.And{ 721 direction, 722 sq.Eq{"ClassId": classId}, 723 sq.Eq{"DeleteAt": int(0)}, 724 }). 725 // Adding ClassId and DeleteAt order columns 726 // to let mysql choose the "idx_posts_class_id_delete_at_create_at" index always. 727 // See MM-23369. 728 OrderBy("ClassId", "DeleteAt", "CreateAt "+sort). 729 Limit(1) 730 731 queryString, args, err := query.ToSql() 732 if err != nil { 733 return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError) 734 } 735 736 var postId string 737 if err := s.GetMaster().SelectOne(&postId, queryString, args...); err != nil { 738 if err != sql.ErrNoRows { 739 return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError) 740 } 741 } 742 743 return postId, nil 744 } 745 746 func (s *SqlPostStore) GetPostAfterTime(classId string, time int64) (*model.Post, *model.AppError) { 747 query := s.getQueryBuilder(). 748 Select("*"). 749 From("Posts"). 750 Where(sq.And{ 751 sq.Gt{"CreateAt": time}, 752 sq.Eq{"ClassId": classId}, 753 sq.Eq{"DeleteAt": int(0)}, 754 }). 755 // Adding ClassId and DeleteAt order columns 756 // to let mysql choose the "idx_posts_class_id_delete_at_create_at" index always. 757 // See MM-23369. 758 OrderBy("ClassId", "DeleteAt", "CreateAt ASC"). 759 Limit(1) 760 761 queryString, args, err := query.ToSql() 762 if err != nil { 763 return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, err.Error(), http.StatusInternalServerError) 764 } 765 766 var post *model.Post 767 if err := s.GetMaster().SelectOne(&post, queryString, args...); err != nil { 768 if err != sql.ErrNoRows { 769 return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError) 770 } 771 } 772 773 return post, nil 774 } 775 776 func (s *SqlPostStore) getRootPosts(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) { 777 var posts []*model.Post 778 var fetchQuery string 779 if skipFetchThreads { 780 fetchQuery = "SELECT p.*, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE ClassId = :ClassId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset" 781 } else { 782 fetchQuery = "SELECT * FROM Posts WHERE ClassId = :ClassId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset" 783 } 784 mlog.Debug(fetchQuery, mlog.Any("params", map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit})) 785 _, err := s.GetReplica().Select(&posts, fetchQuery, map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit}) 786 if err != nil { 787 return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError) 788 } 789 return posts, nil 790 } 791 792 func (s *SqlPostStore) getParentsPosts(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) { 793 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 794 return s.getParentsPostsPostgreSQL(classId, offset, limit, skipFetchThreads) 795 } 796 797 // query parent Ids first 798 var roots []*struct { 799 RootId string 800 } 801 rootQuery := ` 802 SELECT DISTINCT 803 q.RootId 804 FROM 805 (SELECT 806 RootId 807 FROM 808 Posts 809 WHERE 810 ClassId = :ClassId 811 AND DeleteAt = 0 812 ORDER BY CreateAt DESC 813 LIMIT :Limit OFFSET :Offset) q 814 WHERE q.RootId != ''` 815 816 _, err := s.GetReplica().Select(&roots, rootQuery, map[string]interface{}{"ClassId": classId, "Offset": offset, "Limit": limit}) 817 if err != nil { 818 return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError) 819 } 820 if len(roots) == 0 { 821 return nil, nil 822 } 823 params := make(map[string]interface{}) 824 placeholders := make([]string, len(roots)) 825 for idx, r := range roots { 826 key := fmt.Sprintf(":Root%v", idx) 827 params[key[1:]] = r.RootId 828 placeholders[idx] = key 829 } 830 placeholderString := strings.Join(placeholders, ", ") 831 params["ClassId"] = classId 832 replyCountQuery := "" 833 whereStatement := "p.Id IN (" + placeholderString + ")" 834 if skipFetchThreads { 835 replyCountQuery = `, (SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount` 836 } else { 837 whereStatement += " OR p.RootId IN (" + placeholderString + ")" 838 } 839 var posts []*model.Post 840 _, err = s.GetReplica().Select(&posts, ` 841 SELECT p.*`+replyCountQuery+` 842 FROM 843 Posts p 844 WHERE 845 (`+whereStatement+`) 846 AND ClassId = :ClassId 847 AND DeleteAt = 0 848 ORDER BY CreateAt`, 849 params) 850 if err != nil { 851 return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError) 852 } 853 return posts, nil 854 } 855 856 func (s *SqlPostStore) getParentsPostsPostgreSQL(classId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, *model.AppError) { 857 var posts []*model.Post 858 replyCountQuery := "" 859 onStatement := "q1.RootId = q2.Id" 860 if skipFetchThreads { 861 replyCountQuery = ` ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount` 862 } else { 863 onStatement += " OR q1.RootId = q2.RootId" 864 } 865 _, err := s.GetReplica().Select(&posts, 866 `SELECT q2.*`+replyCountQuery+` 867 FROM 868 Posts q2 869 INNER JOIN 870 (SELECT DISTINCT 871 q3.RootId 872 FROM 873 (SELECT 874 RootId 875 FROM 876 Posts 877 WHERE 878 ClassId = :ClassId1 879 AND DeleteAt = 0 880 ORDER BY CreateAt DESC 881 LIMIT :Limit OFFSET :Offset) q3 882 WHERE q3.RootId != '') q1 883 ON `+onStatement+` 884 WHERE 885 ClassId = :ClassId2 886 AND DeleteAt = 0 887 ORDER BY CreateAt`, 888 map[string]interface{}{"ClassId1": classId, "Offset": offset, "Limit": limit, "ClassId2": classId}) 889 if err != nil { 890 return nil, model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "classId="+classId+" err="+err.Error(), http.StatusInternalServerError) 891 } 892 return posts, nil 893 } 894 895 var specialSearchChar = []string{ 896 "<", 897 ">", 898 "+", 899 "-", 900 "(", 901 ")", 902 "~", 903 "@", 904 ":", 905 } 906 907 func (s *SqlPostStore) buildCreateDateFilterClause(params *model.SearchParams, queryParams map[string]interface{}) (string, map[string]interface{}) { 908 searchQuery := "" 909 // handle after: before: on: filters 910 if len(params.OnDate) > 0 { 911 onDateStart, onDateEnd := params.GetOnDateMillis() 912 queryParams["OnDateStart"] = strconv.FormatInt(onDateStart, 10) 913 queryParams["OnDateEnd"] = strconv.FormatInt(onDateEnd, 10) 914 915 // between `on date` start of day and end of day 916 searchQuery += "AND CreateAt BETWEEN :OnDateStart AND :OnDateEnd " 917 } else { 918 919 if len(params.ExcludedDate) > 0 { 920 excludedDateStart, excludedDateEnd := params.GetExcludedDateMillis() 921 queryParams["ExcludedDateStart"] = strconv.FormatInt(excludedDateStart, 10) 922 queryParams["ExcludedDateEnd"] = strconv.FormatInt(excludedDateEnd, 10) 923 924 searchQuery += "AND CreateAt NOT BETWEEN :ExcludedDateStart AND :ExcludedDateEnd " 925 } 926 927 if len(params.AfterDate) > 0 { 928 afterDate := params.GetAfterDateMillis() 929 queryParams["AfterDate"] = strconv.FormatInt(afterDate, 10) 930 931 // greater than `after date` 932 searchQuery += "AND CreateAt >= :AfterDate " 933 } 934 935 if len(params.BeforeDate) > 0 { 936 beforeDate := params.GetBeforeDateMillis() 937 queryParams["BeforeDate"] = strconv.FormatInt(beforeDate, 10) 938 939 // less than `before date` 940 searchQuery += "AND CreateAt <= :BeforeDate " 941 } 942 943 if len(params.ExcludedAfterDate) > 0 { 944 afterDate := params.GetExcludedAfterDateMillis() 945 queryParams["ExcludedAfterDate"] = strconv.FormatInt(afterDate, 10) 946 947 searchQuery += "AND CreateAt < :ExcludedAfterDate " 948 } 949 950 if len(params.ExcludedBeforeDate) > 0 { 951 beforeDate := params.GetExcludedBeforeDateMillis() 952 queryParams["ExcludedBeforeDate"] = strconv.FormatInt(beforeDate, 10) 953 954 searchQuery += "AND CreateAt > :ExcludedBeforeDate " 955 } 956 } 957 958 return searchQuery, queryParams 959 } 960 961 func (s *SqlPostStore) buildSearchClassFilterClause(classes []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byName bool) (string, map[string]interface{}) { 962 if len(classes) == 0 { 963 return "", queryParams 964 } 965 966 clauseSlice := []string{} 967 for i, class := range classes { 968 paramName := paramPrefix + strconv.FormatInt(int64(i), 10) 969 clauseSlice = append(clauseSlice, ":"+paramName) 970 queryParams[paramName] = class 971 } 972 clause := strings.Join(clauseSlice, ", ") 973 if byName { 974 if exclusion { 975 return "AND Name NOT IN (" + clause + ")", queryParams 976 } 977 return "AND Name IN (" + clause + ")", queryParams 978 } 979 980 if exclusion { 981 return "AND Id NOT IN (" + clause + ")", queryParams 982 } 983 return "AND Id IN (" + clause + ")", queryParams 984 } 985 986 func (s *SqlPostStore) buildSearchUserFilterClause(users []string, paramPrefix string, exclusion bool, queryParams map[string]interface{}, byUsername bool) (string, map[string]interface{}) { 987 if len(users) == 0 { 988 return "", queryParams 989 } 990 clauseSlice := []string{} 991 for i, user := range users { 992 paramName := paramPrefix + strconv.FormatInt(int64(i), 10) 993 clauseSlice = append(clauseSlice, ":"+paramName) 994 queryParams[paramName] = user 995 } 996 clause := strings.Join(clauseSlice, ", ") 997 if byUsername { 998 if exclusion { 999 return "AND Username NOT IN (" + clause + ")", queryParams 1000 } 1001 return "AND Username IN (" + clause + ")", queryParams 1002 } 1003 if exclusion { 1004 return "AND Id NOT IN (" + clause + ")", queryParams 1005 } 1006 return "AND Id IN (" + clause + ")", queryParams 1007 } 1008 1009 func (s *SqlPostStore) buildSearchPostFilterClause(fromUsers []string, excludedUsers []string, queryParams map[string]interface{}, userByUsername bool) (string, map[string]interface{}) { 1010 if len(fromUsers) == 0 && len(excludedUsers) == 0 { 1011 return "", queryParams 1012 } 1013 1014 filterQuery := ` 1015 AND UserId IN ( 1016 SELECT 1017 Id 1018 FROM 1019 Users, 1020 BranchMembers 1021 WHERE 1022 BranchMembers.BranchId = :BranchId 1023 AND Users.Id = BranchMembers.UserId 1024 FROM_USER_FILTER 1025 EXCLUDED_USER_FILTER)` 1026 1027 fromUserClause, queryParams := s.buildSearchUserFilterClause(fromUsers, "FromUser", false, queryParams, userByUsername) 1028 filterQuery = strings.Replace(filterQuery, "FROM_USER_FILTER", fromUserClause, 1) 1029 1030 excludedUserClause, queryParams := s.buildSearchUserFilterClause(excludedUsers, "ExcludedUser", true, queryParams, userByUsername) 1031 filterQuery = strings.Replace(filterQuery, "EXCLUDED_USER_FILTER", excludedUserClause, 1) 1032 1033 return filterQuery, queryParams 1034 } 1035 1036 func (s *SqlPostStore) Search(branchId string, userId string, params *model.SearchParams) (*model.PostList, *model.AppError) { 1037 return s.search(branchId, userId, params, true, true) 1038 } 1039 1040 func (s *SqlPostStore) search(branchId string, userId string, params *model.SearchParams, classesByName bool, userByUsername bool) (*model.PostList, *model.AppError) { 1041 queryParams := map[string]interface{}{ 1042 "BranchId": branchId, 1043 "UserId": userId, 1044 } 1045 1046 list := model.NewPostList() 1047 if params.Terms == "" && params.ExcludedTerms == "" && 1048 len(params.InClasses) == 0 && len(params.ExcludedClasses) == 0 && 1049 len(params.FromUsers) == 0 && len(params.ExcludedUsers) == 0 && 1050 len(params.OnDate) == 0 && len(params.AfterDate) == 0 && len(params.BeforeDate) == 0 { 1051 return list, nil 1052 } 1053 1054 var posts []*model.Post 1055 1056 deletedQueryPart := "AND DeleteAt = 0" 1057 if params.IncludeDeletedClasses { 1058 deletedQueryPart = "" 1059 } 1060 1061 userIdPart := "AND UserId = :UserId" 1062 if params.SearchWithoutUserId { 1063 userIdPart = "" 1064 } 1065 1066 searchQuery := ` 1067 SELECT 1068 * ,(SELECT COUNT(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount 1069 FROM 1070 Posts q2 1071 WHERE 1072 DeleteAt = 0 1073 AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%' 1074 POST_FILTER 1075 AND ClassId IN ( 1076 SELECT 1077 Id 1078 FROM 1079 Classes, 1080 ClassMembers 1081 WHERE 1082 Id = ClassId 1083 AND (BranchId = :BranchId OR BranchId = '') 1084 ` + userIdPart + ` 1085 ` + deletedQueryPart + ` 1086 IN_CLASS_FILTER 1087 EXCLUDED_CLASS_FILTER) 1088 CREATEDATE_CLAUSE 1089 SEARCH_CLAUSE 1090 ORDER BY CreateAt DESC 1091 LIMIT 100` 1092 1093 inClassClause, queryParams := s.buildSearchClassFilterClause(params.InClasses, "InClass", false, queryParams, classesByName) 1094 searchQuery = strings.Replace(searchQuery, "IN_CLASS_FILTER", inClassClause, 1) 1095 1096 excludedClassClause, queryParams := s.buildSearchClassFilterClause(params.ExcludedClasses, "ExcludedClass", true, queryParams, classesByName) 1097 searchQuery = strings.Replace(searchQuery, "EXCLUDED_CLASS_FILTER", excludedClassClause, 1) 1098 1099 postFilterClause, queryParams := s.buildSearchPostFilterClause(params.FromUsers, params.ExcludedUsers, queryParams, userByUsername) 1100 searchQuery = strings.Replace(searchQuery, "POST_FILTER", postFilterClause, 1) 1101 1102 createDateFilterClause, queryParams := s.buildCreateDateFilterClause(params, queryParams) 1103 searchQuery = strings.Replace(searchQuery, "CREATEDATE_CLAUSE", createDateFilterClause, 1) 1104 1105 termMap := map[string]bool{} 1106 terms := params.Terms 1107 excludedTerms := params.ExcludedTerms 1108 1109 searchType := "Message" 1110 if params.IsHashtag { 1111 searchType = "Hashtags" 1112 for _, term := range strings.Split(terms, " ") { 1113 termMap[strings.ToUpper(term)] = true 1114 } 1115 } 1116 1117 // these chars have special meaning and can be treated as spaces 1118 for _, c := range specialSearchChar { 1119 terms = strings.Replace(terms, c, " ", -1) 1120 excludedTerms = strings.Replace(excludedTerms, c, " ", -1) 1121 } 1122 1123 if terms == "" && excludedTerms == "" { 1124 // we've already confirmed that we have a class or user to search for 1125 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1) 1126 } else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 1127 // Parse text for wildcards 1128 if wildcard, err := regexp.Compile(`\*($| )`); err == nil { 1129 terms = wildcard.ReplaceAllLiteralString(terms, ":* ") 1130 excludedTerms = wildcard.ReplaceAllLiteralString(excludedTerms, ":* ") 1131 } 1132 1133 excludeClause := "" 1134 if excludedTerms != "" { 1135 excludeClause = " & !(" + strings.Join(strings.Fields(excludedTerms), " | ") + ")" 1136 } 1137 1138 if params.OrTerms { 1139 queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " | ") + ")" + excludeClause 1140 } else { 1141 queryParams["Terms"] = "(" + strings.Join(strings.Fields(terms), " & ") + ")" + excludeClause 1142 } 1143 1144 searchClause := fmt.Sprintf("AND to_tsvector('english', %s) @@ to_tsquery('english', :Terms)", searchType) 1145 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 1146 } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 1147 searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType) 1148 searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1) 1149 1150 excludeClause := "" 1151 if excludedTerms != "" { 1152 excludeClause = " -(" + excludedTerms + ")" 1153 } 1154 1155 if params.OrTerms { 1156 queryParams["Terms"] = terms + excludeClause 1157 } else { 1158 splitTerms := []string{} 1159 for _, t := range strings.Fields(terms) { 1160 splitTerms = append(splitTerms, "+"+t) 1161 } 1162 queryParams["Terms"] = strings.Join(splitTerms, " ") + excludeClause 1163 } 1164 } 1165 1166 _, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams) 1167 if err != nil { 1168 mlog.Warn("Query error searching posts.", mlog.Err(err)) 1169 // 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. 1170 } else { 1171 for _, p := range posts { 1172 if searchType == "Hashtags" { 1173 exactMatch := false 1174 for _, tag := range strings.Split(p.Hashtags, " ") { 1175 if termMap[strings.ToUpper(tag)] { 1176 exactMatch = true 1177 break 1178 } 1179 } 1180 if !exactMatch { 1181 continue 1182 } 1183 } 1184 list.AddPost(p) 1185 list.AddOrder(p.Id) 1186 } 1187 } 1188 list.MakeNonNil() 1189 return list, nil 1190 } 1191 1192 func (s *SqlPostStore) AnalyticsUserCountsWithPostsByDay(branchId string) (model.AnalyticsRows, *model.AppError) { 1193 query := 1194 `SELECT DISTINCT 1195 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 1196 COUNT(DISTINCT Posts.UserId) AS Value 1197 FROM Posts` 1198 1199 if len(branchId) > 0 { 1200 query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND" 1201 } else { 1202 query += " WHERE" 1203 } 1204 1205 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 1206 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 1207 ORDER BY Name DESC 1208 LIMIT 30` 1209 1210 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 1211 query = 1212 `SELECT 1213 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value 1214 FROM Posts` 1215 1216 if len(branchId) > 0 { 1217 query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND" 1218 } else { 1219 query += " WHERE" 1220 } 1221 1222 query += ` Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime 1223 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 1224 ORDER BY Name DESC 1225 LIMIT 30` 1226 } 1227 1228 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 1229 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 1230 1231 var rows model.AnalyticsRows 1232 _, err := s.GetReplica().Select( 1233 &rows, 1234 query, 1235 map[string]interface{}{"BranchId": branchId, "StartTime": start, "EndTime": end}) 1236 if err != nil { 1237 return nil, model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 1238 } 1239 return rows, nil 1240 } 1241 1242 func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCountsOptions) (model.AnalyticsRows, *model.AppError) { 1243 1244 query := 1245 `SELECT 1246 DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name, 1247 COUNT(Posts.Id) AS Value 1248 FROM Posts` 1249 1250 if len(options.BranchId) > 0 { 1251 query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND" 1252 } else { 1253 query += " WHERE" 1254 } 1255 1256 query += ` Posts.CreateAt <= :EndTime 1257 AND Posts.CreateAt >= :StartTime 1258 GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) 1259 ORDER BY Name DESC 1260 LIMIT 30` 1261 1262 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 1263 query = 1264 `SELECT 1265 TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value 1266 FROM Posts` 1267 1268 if len(options.BranchId) > 0 { 1269 query += " INNER JOIN Classes ON Posts.ClassId = Classes.Id AND Classes.BranchId = :BranchId AND" 1270 } else { 1271 query += " WHERE" 1272 } 1273 1274 query += ` Posts.CreateAt <= :EndTime 1275 AND Posts.CreateAt >= :StartTime 1276 GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) 1277 ORDER BY Name DESC 1278 LIMIT 30` 1279 } 1280 1281 end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday())) 1282 start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31))) 1283 if options.YesterdayOnly { 1284 start = utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -1))) 1285 } 1286 1287 var rows model.AnalyticsRows 1288 _, err := s.GetReplica().Select( 1289 &rows, 1290 query, 1291 map[string]interface{}{"BranchId": options.BranchId, "StartTime": start, "EndTime": end}) 1292 if err != nil { 1293 return nil, model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError) 1294 } 1295 return rows, nil 1296 } 1297 1298 func (s *SqlPostStore) AnalyticsPostCount(branchId string, mustHaveFile bool, mustHaveHashtag bool) (int64, *model.AppError) { 1299 query := 1300 `SELECT 1301 COUNT(Posts.Id) AS Value 1302 FROM 1303 Posts, 1304 Classes 1305 WHERE 1306 Posts.ClassId = Classes.Id` 1307 1308 if len(branchId) > 0 { 1309 query += " AND Classes.BranchId = :BranchId" 1310 } 1311 1312 if mustHaveFile { 1313 query += " AND (Posts.FileIds != '[]' OR Posts.Filenames != '[]')" 1314 } 1315 1316 if mustHaveHashtag { 1317 query += " AND Posts.Hashtags != ''" 1318 } 1319 1320 v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"BranchId": branchId}) 1321 if err != nil { 1322 return 0, model.NewAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError) 1323 } 1324 1325 return v, nil 1326 } 1327 1328 func (s *SqlPostStore) GetPostsCreatedAt(classId string, time int64) ([]*model.Post, *model.AppError) { 1329 query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt AND ClassId = :ClassId` 1330 1331 var posts []*model.Post 1332 _, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time, "ClassId": classId}) 1333 1334 if err != nil { 1335 return nil, model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "classId="+classId+err.Error(), http.StatusInternalServerError) 1336 } 1337 return posts, nil 1338 } 1339 1340 func (s *SqlPostStore) GetPostsByIds(postIds []string) ([]*model.Post, *model.AppError) { 1341 keys, params := MapStringsToQueryParams(postIds, "Post") 1342 1343 query := `SELECT p.*, (SELECT count(Posts.Id) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.Id IN ` + keys + ` ORDER BY CreateAt DESC` 1344 1345 var posts []*model.Post 1346 _, err := s.GetReplica().Select(&posts, query, params) 1347 1348 if err != nil { 1349 mlog.Error("Query error getting posts.", mlog.Err(err)) 1350 return nil, model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError) 1351 } 1352 return posts, nil 1353 } 1354 1355 func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, *model.AppError) { 1356 var posts []*model.PostForIndexing 1357 _, err := s.GetSearchReplica().Select(&posts, 1358 `SELECT 1359 PostsQuery.*, Classes.BranchId, ParentPosts.CreateAt ParentCreateAt 1360 FROM ( 1361 SELECT 1362 * 1363 FROM 1364 Posts 1365 WHERE 1366 Posts.CreateAt >= :StartTime 1367 AND 1368 Posts.CreateAt < :EndTime 1369 ORDER BY 1370 CreateAt ASC 1371 LIMIT 1372 1000 1373 ) 1374 AS 1375 PostsQuery 1376 LEFT JOIN 1377 Classes 1378 ON 1379 PostsQuery.ClassId = Classes.Id 1380 LEFT JOIN 1381 Posts ParentPosts 1382 ON 1383 PostsQuery.RootId = ParentPosts.Id`, 1384 map[string]interface{}{"StartTime": startTime, "EndTime": endTime, "NumPosts": limit}) 1385 1386 if err != nil { 1387 return nil, model.NewAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_batch_for_indexing.get.app_error", nil, err.Error(), http.StatusInternalServerError) 1388 } 1389 return posts, nil 1390 } 1391 1392 func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) { 1393 var query string 1394 if s.DriverName() == "postgres" { 1395 query = "DELETE from Posts WHERE Id = any (array (SELECT Id FROM Posts WHERE CreateAt < :EndTime LIMIT :Limit))" 1396 } else { 1397 query = "DELETE from Posts WHERE CreateAt < :EndTime LIMIT :Limit" 1398 } 1399 1400 sqlResult, err := s.GetMaster().Exec(query, map[string]interface{}{"EndTime": endTime, "Limit": limit}) 1401 if err != nil { 1402 return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1403 } 1404 1405 rowsAffected, err := sqlResult.RowsAffected() 1406 if err != nil { 1407 return 0, model.NewAppError("SqlPostStore.PermanentDeleteBatch", "store.sql_post.permanent_delete_batch.app_error", nil, ""+err.Error(), http.StatusInternalServerError) 1408 } 1409 return rowsAffected, nil 1410 } 1411 1412 func (s *SqlPostStore) GetOldest() (*model.Post, *model.AppError) { 1413 var post model.Post 1414 err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts ORDER BY CreateAt LIMIT 1") 1415 if err != nil { 1416 return nil, model.NewAppError("SqlPostStore.GetOldest", "store.sql_post.get.app_error", nil, err.Error(), http.StatusNotFound) 1417 } 1418 1419 return &post, nil 1420 } 1421 1422 func (s *SqlPostStore) determineMaxPostSize() int { 1423 var maxPostSizeBytes int32 1424 1425 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 1426 // The Post.Message column in Postgres has historically been VARCHAR(4000), but 1427 // may be manually enlarged to support longer posts. 1428 if err := s.GetReplica().SelectOne(&maxPostSizeBytes, ` 1429 SELECT 1430 COALESCE(character_maximum_length, 0) 1431 FROM 1432 information_schema.columns 1433 WHERE 1434 table_name = 'posts' 1435 AND column_name = 'message' 1436 `); err != nil { 1437 mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err)) 1438 } 1439 } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { 1440 // The Post.Message column in MySQL has historically been TEXT, with a maximum 1441 // limit of 65535. 1442 if err := s.GetReplica().SelectOne(&maxPostSizeBytes, ` 1443 SELECT 1444 COALESCE(CHARACTER_MAXIMUM_LENGTH, 0) 1445 FROM 1446 INFORMATION_SCHEMA.COLUMNS 1447 WHERE 1448 table_schema = DATABASE() 1449 AND table_name = 'Posts' 1450 AND column_name = 'Message' 1451 LIMIT 0, 1 1452 `); err != nil { 1453 mlog.Error("Unable to determine the maximum supported post size", mlog.Err(err)) 1454 } 1455 } else { 1456 mlog.Warn("No implementation found to determine the maximum supported post size") 1457 } 1458 1459 // Assume a worst-case representation of four bytes per rune. 1460 maxPostSize := int(maxPostSizeBytes) / 4 1461 1462 // To maintain backwards compatibility, don't yield a maximum post 1463 // size smaller than the previous limit, even though it wasn't 1464 // actually possible to store 4000 runes in all cases. 1465 if maxPostSize < model.POST_MESSAGE_MAX_RUNES_V1 { 1466 maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1 1467 } 1468 1469 mlog.Info("Post.Message has size restrictions", mlog.Int("max_characters", maxPostSize), mlog.Int32("max_bytes", maxPostSizeBytes)) 1470 1471 return maxPostSize 1472 } 1473 1474 // GetMaxPostSize returns the maximum number of runes that may be stored in a post. 1475 func (s *SqlPostStore) GetMaxPostSize() int { 1476 s.maxPostSizeOnce.Do(func() { 1477 s.maxPostSizeCached = s.determineMaxPostSize() 1478 }) 1479 return s.maxPostSizeCached 1480 }