github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/store/sqlstore/retention_policy_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 "strconv" 9 "strings" 10 11 sq "github.com/Masterminds/squirrel" 12 "github.com/go-sql-driver/mysql" 13 "github.com/lib/pq" 14 "github.com/masterhung0112/hk_server/v5/einterfaces" 15 "github.com/masterhung0112/hk_server/v5/model" 16 "github.com/masterhung0112/hk_server/v5/store" 17 "github.com/mattermost/gorp" 18 "github.com/pkg/errors" 19 ) 20 21 type SqlRetentionPolicyStore struct { 22 *SqlStore 23 metrics einterfaces.MetricsInterface 24 } 25 26 func newSqlRetentionPolicyStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.RetentionPolicyStore { 27 s := &SqlRetentionPolicyStore{ 28 SqlStore: sqlStore, 29 metrics: metrics, 30 } 31 32 for _, db := range sqlStore.GetAllConns() { 33 table := db.AddTableWithName(model.RetentionPolicy{}, "RetentionPolicies") 34 table.SetKeys(false, "Id") 35 table.ColMap("Id").SetMaxSize(26) 36 table.ColMap("DisplayName").SetMaxSize(64) 37 38 tableC := db.AddTableWithName(model.RetentionPolicyChannel{}, "RetentionPoliciesChannels") 39 tableC.SetKeys(false, "ChannelId") 40 tableC.ColMap("PolicyId").SetMaxSize(26) 41 tableC.ColMap("ChannelId").SetMaxSize(26) 42 43 tableT := db.AddTableWithName(model.RetentionPolicyTeam{}, "RetentionPoliciesTeams") 44 tableT.SetKeys(false, "TeamId") 45 tableT.ColMap("PolicyId").SetMaxSize(26) 46 tableT.ColMap("TeamId").SetMaxSize(26) 47 } 48 49 return s 50 } 51 52 func (s *SqlRetentionPolicyStore) createIndexesIfNotExists() { 53 s.CreateIndexIfNotExists("IDX_RetentionPolicies_DisplayName", "RetentionPolicies", "DisplayName") 54 s.CreateIndexIfNotExists("IDX_RetentionPoliciesChannels_PolicyId", "RetentionPoliciesChannels", "PolicyId") 55 s.CreateIndexIfNotExists("IDX_RetentionPoliciesTeams_PolicyId", "RetentionPoliciesTeams", "PolicyId") 56 s.CreateForeignKeyIfNotExists("RetentionPoliciesChannels", "PolicyId", "RetentionPolicies", "Id", true) 57 s.CreateForeignKeyIfNotExists("RetentionPoliciesTeams", "PolicyId", "RetentionPolicies", "Id", true) 58 } 59 60 // executePossiblyEmptyQuery only executes the query if it is non-empty. This helps avoid 61 // having to check for MySQL, which, unlike Postgres, does not allow empty queries. 62 func executePossiblyEmptyQuery(txn *gorp.Transaction, query string, args ...interface{}) (sql.Result, error) { 63 if query == "" { 64 return nil, nil 65 } 66 return txn.Exec(query, args...) 67 } 68 69 func (s *SqlRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { 70 // Strategy: 71 // 1. Insert new policy 72 // 2. Insert new channels into policy 73 // 3. Insert new teams into policy 74 75 if err := s.checkTeamsExist(policy.TeamIDs); err != nil { 76 return nil, err 77 } 78 if err := s.checkChannelsExist(policy.ChannelIDs); err != nil { 79 return nil, err 80 } 81 82 policy.ID = model.NewId() 83 84 policyInsertQuery, policyInsertArgs, err := s.getQueryBuilder(). 85 Insert("RetentionPolicies"). 86 Columns("Id", "DisplayName", "PostDuration"). 87 Values(policy.ID, policy.DisplayName, policy.PostDuration). 88 ToSql() 89 if err != nil { 90 return nil, err 91 } 92 93 channelsInsertQuery, channelsInsertArgs, err := s.buildInsertRetentionPoliciesChannelsQuery(policy.ID, policy.ChannelIDs) 94 if err != nil { 95 return nil, err 96 } 97 98 teamsInsertQuery, teamsInsertArgs, err := s.buildInsertRetentionPoliciesTeamsQuery(policy.ID, policy.TeamIDs) 99 if err != nil { 100 return nil, err 101 } 102 103 policySelectQuery, policySelectProps := s.buildGetPolicyQuery(policy.ID) 104 105 txn, err := s.GetMaster().Begin() 106 if err != nil { 107 return nil, err 108 } 109 defer finalizeTransaction(txn) 110 // Create a new policy in RetentionPolicies 111 if _, err = txn.Exec(policyInsertQuery, policyInsertArgs...); err != nil { 112 return nil, err 113 } 114 // Insert the channel IDs into RetentionPoliciesChannels 115 if _, err = executePossiblyEmptyQuery(txn, channelsInsertQuery, channelsInsertArgs...); err != nil { 116 return nil, err 117 } 118 // Insert the team IDs into RetentionPoliciesTeams 119 if _, err = executePossiblyEmptyQuery(txn, teamsInsertQuery, teamsInsertArgs...); err != nil { 120 return nil, err 121 } 122 // Select the new policy (with team/channel counts) which we just created 123 var newPolicy model.RetentionPolicyWithTeamAndChannelCounts 124 if err = txn.SelectOne(&newPolicy, policySelectQuery, policySelectProps); err != nil { 125 return nil, err 126 } 127 if err = txn.Commit(); err != nil { 128 return nil, err 129 } 130 return &newPolicy, nil 131 } 132 133 func (s *SqlRetentionPolicyStore) checkTeamsExist(teamIDs []string) error { 134 if len(teamIDs) > 0 { 135 teamsSelectQuery, teamsSelectArgs, err := s.getQueryBuilder(). 136 Select("Id"). 137 From("Teams"). 138 Where(sq.Eq{"Id": teamIDs}). 139 ToSql() 140 if err != nil { 141 return err 142 } 143 var rows []*string 144 _, err = s.GetReplica().Select(&rows, teamsSelectQuery, teamsSelectArgs...) 145 if err != nil { 146 return err 147 } 148 if len(rows) == len(teamIDs) { 149 return nil 150 } 151 retrievedIDs := make(map[string]bool) 152 for _, teamID := range rows { 153 retrievedIDs[*teamID] = true 154 } 155 for _, teamID := range teamIDs { 156 if _, ok := retrievedIDs[teamID]; !ok { 157 return store.NewErrNotFound("Team", teamID) 158 } 159 } 160 } 161 return nil 162 } 163 164 func (s *SqlRetentionPolicyStore) checkChannelsExist(channelIDs []string) error { 165 if len(channelIDs) > 0 { 166 channelsSelectQuery, channelsSelectArgs, err := s.getQueryBuilder(). 167 Select("Id"). 168 From("Channels"). 169 Where(sq.Eq{"Id": channelIDs}). 170 ToSql() 171 if err != nil { 172 return err 173 } 174 var rows []*string 175 _, err = s.GetReplica().Select(&rows, channelsSelectQuery, channelsSelectArgs...) 176 if err != nil { 177 return err 178 } 179 if len(rows) == len(channelIDs) { 180 return nil 181 } 182 retrievedIDs := make(map[string]bool) 183 for _, channelID := range rows { 184 retrievedIDs[*channelID] = true 185 } 186 for _, channelID := range channelIDs { 187 if _, ok := retrievedIDs[channelID]; !ok { 188 return store.NewErrNotFound("Channel", channelID) 189 } 190 } 191 } 192 return nil 193 } 194 195 func (s *SqlRetentionPolicyStore) buildInsertRetentionPoliciesChannelsQuery(policyID string, channelIDs []string) (query string, args []interface{}, err error) { 196 if len(channelIDs) > 0 { 197 builder := s.getQueryBuilder(). 198 Insert("RetentionPoliciesChannels"). 199 Columns("PolicyId", "ChannelId") 200 for _, channelID := range channelIDs { 201 builder = builder.Values(policyID, channelID) 202 } 203 query, args, err = builder.ToSql() 204 } 205 return 206 } 207 208 func (s *SqlRetentionPolicyStore) buildInsertRetentionPoliciesTeamsQuery(policyID string, teamIDs []string) (query string, args []interface{}, err error) { 209 if len(teamIDs) > 0 { 210 builder := s.getQueryBuilder(). 211 Insert("RetentionPoliciesTeams"). 212 Columns("PolicyId", "TeamId") 213 for _, teamID := range teamIDs { 214 builder = builder.Values(policyID, teamID) 215 } 216 query, args, err = builder.ToSql() 217 } 218 return 219 } 220 221 func (s *SqlRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { 222 // Strategy: 223 // 1. Update policy attributes 224 // 2. Delete existing channels from policy 225 // 3. Insert new channels into policy 226 // 4. Delete existing teams from policy 227 // 5. Insert new teams into policy 228 // 6. Read new policy 229 230 var err error 231 if err = s.checkTeamsExist(patch.TeamIDs); err != nil { 232 return nil, err 233 } 234 if err = s.checkChannelsExist(patch.ChannelIDs); err != nil { 235 return nil, err 236 } 237 238 policyUpdateQuery := "" 239 policyUpdateArgs := []interface{}{} 240 if patch.DisplayName != "" || patch.PostDuration != nil { 241 builder := s.getQueryBuilder().Update("RetentionPolicies") 242 if patch.DisplayName != "" { 243 builder = builder.Set("DisplayName", patch.DisplayName) 244 } 245 if patch.PostDuration != nil { 246 builder = builder.Set("PostDuration", *patch.PostDuration) 247 } 248 policyUpdateQuery, policyUpdateArgs, err = builder. 249 Where(sq.Eq{"Id": patch.ID}). 250 ToSql() 251 if err != nil { 252 return nil, err 253 } 254 } 255 256 channelsDeleteQuery := "" 257 channelsDeleteArgs := []interface{}{} 258 channelsInsertQuery := "" 259 channelsInsertArgs := []interface{}{} 260 if patch.ChannelIDs != nil { 261 channelsDeleteQuery, channelsDeleteArgs, err = s.getQueryBuilder(). 262 Delete("RetentionPoliciesChannels"). 263 Where(sq.Eq{"PolicyId": patch.ID}). 264 ToSql() 265 if err != nil { 266 return nil, err 267 } 268 269 channelsInsertQuery, channelsInsertArgs, err = s.buildInsertRetentionPoliciesChannelsQuery(patch.ID, patch.ChannelIDs) 270 if err != nil { 271 return nil, err 272 } 273 } 274 275 teamsDeleteQuery := "" 276 teamsDeleteArgs := []interface{}{} 277 teamsInsertQuery := "" 278 teamsInsertArgs := []interface{}{} 279 if patch.TeamIDs != nil { 280 teamsDeleteQuery, teamsDeleteArgs, err = s.getQueryBuilder(). 281 Delete("RetentionPoliciesTeams"). 282 Where(sq.Eq{"PolicyId": patch.ID}). 283 ToSql() 284 if err != nil { 285 return nil, err 286 } 287 288 teamsInsertQuery, teamsInsertArgs, err = s.buildInsertRetentionPoliciesTeamsQuery(patch.ID, patch.TeamIDs) 289 if err != nil { 290 return nil, err 291 } 292 } 293 294 policySelectQuery, policySelectProps := s.buildGetPolicyQuery(patch.ID) 295 296 txn, err := s.GetMaster().Begin() 297 if err != nil { 298 return nil, err 299 } 300 defer finalizeTransaction(txn) 301 // Update the fields of the policy in RetentionPolicies 302 if _, err = executePossiblyEmptyQuery(txn, policyUpdateQuery, policyUpdateArgs...); err != nil { 303 return nil, err 304 } 305 // Remove all channels from the policy in RetentionPoliciesChannels 306 if _, err = executePossiblyEmptyQuery(txn, channelsDeleteQuery, channelsDeleteArgs...); err != nil { 307 return nil, err 308 } 309 // Insert the new channels for the policy in RetentionPoliciesChannels 310 if _, err = executePossiblyEmptyQuery(txn, channelsInsertQuery, channelsInsertArgs...); err != nil { 311 return nil, err 312 } 313 // Remove all teams from the policy in RetentionPoliciesTeams 314 if _, err = executePossiblyEmptyQuery(txn, teamsDeleteQuery, teamsDeleteArgs...); err != nil { 315 return nil, err 316 } 317 // Insert the new teams for the policy in RetentionPoliciesTeams 318 if _, err = executePossiblyEmptyQuery(txn, teamsInsertQuery, teamsInsertArgs...); err != nil { 319 return nil, err 320 } 321 // Select the policy which we just updated 322 var newPolicy model.RetentionPolicyWithTeamAndChannelCounts 323 if err = txn.SelectOne(&newPolicy, policySelectQuery, policySelectProps); err != nil { 324 return nil, err 325 } 326 if err = txn.Commit(); err != nil { 327 return nil, err 328 } 329 return &newPolicy, nil 330 } 331 332 func (s *SqlRetentionPolicyStore) buildGetPolicyQuery(id string) (query string, props map[string]interface{}) { 333 return s.buildGetPoliciesQuery(id, 0, 1) 334 } 335 336 // buildGetPoliciesQuery builds a query to select information for the policy with the specified 337 // ID, or, if `id` is the empty string, from all policies. The results returned will be sorted by 338 // policy display name and ID. 339 func (s *SqlRetentionPolicyStore) buildGetPoliciesQuery(id string, offset, limit int) (query string, props map[string]interface{}) { 340 props = map[string]interface{}{"Offset": offset, "Limit": limit} 341 whereIdEqualsPolicyId := "" 342 if id != "" { 343 whereIdEqualsPolicyId = "WHERE RetentionPolicies.Id = :PolicyId" 344 props["PolicyId"] = id 345 } 346 query = ` 347 SELECT RetentionPolicies.Id, 348 RetentionPolicies.DisplayName, 349 RetentionPolicies.PostDuration, 350 A.Count AS ChannelCount, 351 B.Count AS TeamCount 352 FROM RetentionPolicies 353 INNER JOIN ( 354 SELECT RetentionPolicies.Id, 355 COUNT(RetentionPoliciesChannels.ChannelId) AS Count 356 FROM RetentionPolicies 357 LEFT JOIN RetentionPoliciesChannels ON RetentionPolicies.Id = RetentionPoliciesChannels.PolicyId 358 ` + whereIdEqualsPolicyId + ` 359 GROUP BY RetentionPolicies.Id 360 ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id 361 LIMIT :Limit 362 OFFSET :Offset 363 ) AS A ON RetentionPolicies.Id = A.Id 364 INNER JOIN ( 365 SELECT RetentionPolicies.Id, 366 COUNT(RetentionPoliciesTeams.TeamId) AS Count 367 FROM RetentionPolicies 368 LEFT JOIN RetentionPoliciesTeams ON RetentionPolicies.Id = RetentionPoliciesTeams.PolicyId 369 ` + whereIdEqualsPolicyId + ` 370 GROUP BY RetentionPolicies.Id 371 ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id 372 LIMIT :Limit 373 OFFSET :Offset 374 ) AS B ON RetentionPolicies.Id = B.Id 375 ORDER BY RetentionPolicies.DisplayName, RetentionPolicies.Id` 376 return 377 } 378 379 func (s *SqlRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { 380 query, props := s.buildGetPolicyQuery(id) 381 var policy model.RetentionPolicyWithTeamAndChannelCounts 382 if err := s.GetReplica().SelectOne(&policy, query, props); err != nil { 383 return nil, err 384 } 385 return &policy, nil 386 } 387 388 func (s *SqlRetentionPolicyStore) GetAll(offset, limit int) (policies []*model.RetentionPolicyWithTeamAndChannelCounts, err error) { 389 query, props := s.buildGetPoliciesQuery("", offset, limit) 390 _, err = s.GetReplica().Select(&policies, query, props) 391 return 392 } 393 394 func (s *SqlRetentionPolicyStore) GetCount() (int64, error) { 395 return s.GetReplica().SelectInt("SELECT COUNT(*) FROM RetentionPolicies") 396 } 397 398 func (s *SqlRetentionPolicyStore) Delete(id string) error { 399 builder := s.getQueryBuilder(). 400 Delete("RetentionPolicies"). 401 Where(sq.Eq{"Id": id}) 402 result, err := builder.RunWith(s.GetMaster()).Exec() 403 if err != nil { 404 return err 405 } 406 numRowsAffected, err := result.RowsAffected() 407 if err != nil { 408 return err 409 } else if numRowsAffected == 0 { 410 return errors.New("policy not found") 411 } 412 return nil 413 } 414 415 func (s *SqlRetentionPolicyStore) GetChannels(policyId string, offset, limit int) (channels model.ChannelListWithTeamData, err error) { 416 const query = ` 417 SELECT Channels.*, 418 Teams.DisplayName AS TeamDisplayName, 419 Teams.Name AS TeamName, 420 Teams.UpdateAt AS TeamUpdateAt 421 FROM RetentionPoliciesChannels 422 INNER JOIN Channels ON RetentionPoliciesChannels.ChannelId = Channels.Id 423 INNER JOIN Teams ON Channels.TeamId = Teams.Id 424 WHERE RetentionPoliciesChannels.PolicyId = :PolicyId 425 ORDER BY Channels.DisplayName, Channels.Id 426 LIMIT :Limit 427 OFFSET :Offset` 428 props := map[string]interface{}{"PolicyId": policyId, "Limit": limit, "Offset": offset} 429 _, err = s.GetReplica().Select(&channels, query, props) 430 for _, channel := range channels { 431 channel.PolicyID = model.NewString(policyId) 432 } 433 return 434 } 435 436 func (s *SqlRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) { 437 const query = ` 438 SELECT COUNT(*) 439 FROM RetentionPolicies 440 INNER JOIN RetentionPoliciesChannels ON RetentionPolicies.Id = RetentionPoliciesChannels.PolicyId 441 WHERE RetentionPolicies.Id = :PolicyId` 442 props := map[string]interface{}{"PolicyId": policyId} 443 return s.GetReplica().SelectInt(query, props) 444 } 445 446 func (s *SqlRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error { 447 if len(channelIds) == 0 { 448 return nil 449 } 450 if err := s.checkChannelsExist(channelIds); err != nil { 451 return err 452 } 453 builder := s.getQueryBuilder(). 454 Insert("RetentionPoliciesChannels"). 455 Columns("policyId", "channelId") 456 for _, channelId := range channelIds { 457 builder = builder.Values(policyId, channelId) 458 } 459 _, err := builder.RunWith(s.GetMaster()).Exec() 460 if err != nil { 461 switch dbErr := err.(type) { 462 case *pq.Error: 463 if dbErr.Code == PGForeignKeyViolationErrorCode { 464 return store.NewErrNotFound("RetentionPolicy", policyId) 465 } 466 case *mysql.MySQLError: 467 if dbErr.Number == MySQLForeignKeyViolationErrorCode { 468 return store.NewErrNotFound("RetentionPolicy", policyId) 469 } 470 } 471 } 472 return err 473 } 474 475 func (s *SqlRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error { 476 if len(channelIds) == 0 { 477 return nil 478 } 479 builder := s.getQueryBuilder(). 480 Delete("RetentionPoliciesChannels"). 481 Where(sq.And{ 482 sq.Eq{"PolicyId": policyId}, 483 sq.Eq{"ChannelId": channelIds}, 484 }) 485 _, err := builder.RunWith(s.GetMaster()).Exec() 486 return err 487 } 488 489 func (s *SqlRetentionPolicyStore) GetTeams(policyId string, offset, limit int) (teams []*model.Team, err error) { 490 const query = ` 491 SELECT Teams.* FROM RetentionPoliciesTeams 492 INNER JOIN Teams ON RetentionPoliciesTeams.TeamId = Teams.Id 493 WHERE RetentionPoliciesTeams.PolicyId = :PolicyId 494 ORDER BY Teams.DisplayName, Teams.Id 495 LIMIT :Limit 496 OFFSET :Offset` 497 props := map[string]interface{}{"PolicyId": policyId, "Limit": limit, "Offset": offset} 498 _, err = s.GetReplica().Select(&teams, query, props) 499 for _, team := range teams { 500 team.PolicyID = &policyId 501 } 502 return 503 } 504 505 func (s *SqlRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) { 506 const query = ` 507 SELECT COUNT(*) 508 FROM RetentionPolicies 509 INNER JOIN RetentionPoliciesTeams ON RetentionPolicies.Id = RetentionPoliciesTeams.PolicyId 510 WHERE RetentionPolicies.Id = :PolicyId` 511 props := map[string]interface{}{"PolicyId": policyId} 512 return s.GetReplica().SelectInt(query, props) 513 } 514 515 func (s *SqlRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error { 516 if len(teamIds) == 0 { 517 return nil 518 } 519 if err := s.checkTeamsExist(teamIds); err != nil { 520 return err 521 } 522 builder := s.getQueryBuilder(). 523 Insert("RetentionPoliciesTeams"). 524 Columns("PolicyId", "TeamId") 525 for _, teamId := range teamIds { 526 builder = builder.Values(policyId, teamId) 527 } 528 _, err := builder.RunWith(s.GetMaster()).Exec() 529 return err 530 } 531 532 func (s *SqlRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error { 533 if len(teamIds) == 0 { 534 return nil 535 } 536 builder := s.getQueryBuilder(). 537 Delete("RetentionPoliciesTeams"). 538 Where(sq.And{ 539 sq.Eq{"PolicyId": policyId}, 540 sq.Eq{"TeamId": teamIds}, 541 }) 542 _, err := builder.RunWith(s.GetMaster()).Exec() 543 return err 544 } 545 546 // DeleteOrphanedRows removes entries from RetentionPoliciesChannels and RetentionPoliciesTeams 547 // where a channel or team no longer exists. 548 func (s *SqlRetentionPolicyStore) DeleteOrphanedRows(limit int) (deleted int64, err error) { 549 // We need the extra level of nesting to deal with MySQL's locking 550 const rpcDeleteQuery = ` 551 DELETE FROM RetentionPoliciesChannels WHERE ChannelId IN ( 552 SELECT * FROM ( 553 SELECT ChannelId FROM RetentionPoliciesChannels 554 LEFT JOIN Channels ON RetentionPoliciesChannels.ChannelId = Channels.Id 555 WHERE Channels.Id IS NULL 556 LIMIT :Limit 557 ) AS A 558 )` 559 const rptDeleteQuery = ` 560 DELETE FROM RetentionPoliciesTeams WHERE TeamId IN ( 561 SELECT * FROM ( 562 SELECT TeamId FROM RetentionPoliciesTeams 563 LEFT JOIN Teams ON RetentionPoliciesTeams.TeamId = Teams.Id 564 WHERE Teams.Id IS NULL 565 LIMIT :Limit 566 ) AS A 567 )` 568 props := map[string]interface{}{"Limit": limit} 569 result, err := s.GetMaster().Exec(rpcDeleteQuery, props) 570 if err != nil { 571 return 572 } 573 rpcDeleted, err := result.RowsAffected() 574 if err != nil { 575 return 576 } 577 result, err = s.GetMaster().Exec(rptDeleteQuery, props) 578 if err != nil { 579 return 580 } 581 rptDeleted, err := result.RowsAffected() 582 if err != nil { 583 return 584 } 585 deleted = rpcDeleted + rptDeleted 586 return 587 } 588 589 func (s *SqlRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForTeam, err error) { 590 const query = ` 591 SELECT Teams.Id, RetentionPolicies.PostDuration 592 FROM Users 593 INNER JOIN TeamMembers ON Users.Id = TeamMembers.UserId 594 INNER JOIN Teams ON TeamMembers.TeamId = Teams.Id 595 INNER JOIN RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId 596 INNER JOIN RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id 597 WHERE Users.Id = :UserId 598 AND TeamMembers.DeleteAt = 0 599 AND Teams.DeleteAt = 0 600 ORDER BY Teams.Id 601 LIMIT :Limit 602 OFFSET :Offset` 603 props := map[string]interface{}{"UserId": userID, "Limit": limit, "Offset": offset} 604 _, err = s.GetReplica().Select(&policies, query, props) 605 return 606 } 607 608 func (s *SqlRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) { 609 const query = ` 610 SELECT COUNT(*) 611 FROM Users 612 INNER JOIN TeamMembers ON Users.Id = TeamMembers.UserId 613 INNER JOIN Teams ON TeamMembers.TeamId = Teams.Id 614 INNER JOIN RetentionPoliciesTeams ON Teams.Id = RetentionPoliciesTeams.TeamId 615 INNER JOIN RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id 616 WHERE Users.Id = :UserId 617 AND TeamMembers.DeleteAt = 0 618 AND Teams.DeleteAt = 0` 619 props := map[string]interface{}{"UserId": userID} 620 return s.GetReplica().SelectInt(query, props) 621 } 622 623 func (s *SqlRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForChannel, err error) { 624 const query = ` 625 SELECT Channels.Id, RetentionPolicies.PostDuration 626 FROM Users 627 INNER JOIN ChannelMembers ON Users.Id = ChannelMembers.UserId 628 INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id 629 INNER JOIN RetentionPoliciesChannels ON Channels.Id = RetentionPoliciesChannels.ChannelId 630 INNER JOIN RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id 631 WHERE Users.Id = :UserId 632 AND Channels.DeleteAt = 0 633 ORDER BY Channels.Id 634 LIMIT :Limit 635 OFFSET :Offset` 636 props := map[string]interface{}{"UserId": userID, "Limit": limit, "Offset": offset} 637 _, err = s.GetReplica().Select(&policies, query, props) 638 return 639 } 640 641 func (s *SqlRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) { 642 const query = ` 643 SELECT COUNT(*) 644 FROM Users 645 INNER JOIN ChannelMembers ON Users.Id = ChannelMembers.UserId 646 INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id 647 INNER JOIN RetentionPoliciesChannels ON Channels.Id = RetentionPoliciesChannels.ChannelId 648 INNER JOIN RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id 649 WHERE Users.Id = :UserId 650 AND Channels.DeleteAt = 0` 651 props := map[string]interface{}{"UserId": userID} 652 return s.GetReplica().SelectInt(query, props) 653 } 654 655 // RetentionPolicyBatchDeletionInfo gives information on how to delete records 656 // under a retention policy; see `genericPermanentDeleteBatchForRetentionPolicies`. 657 // 658 // `BaseBuilder` should already have selected the primary key(s) for the main table 659 // and should be joined to a table with a ChannelId column, which will be used to join 660 // on the Channels table. 661 // `Table` is the name of the table from which records are being deleted. 662 // `TimeColumn` is the name of the column which contains the timestamp of the record. 663 // `PrimaryKeys` contains the primary keys of `table`. It should be the same as the 664 // `From` clause in `baseBuilder`. 665 // `ChannelIDTable` is the table which contains the ChannelId column, it may be the 666 // same as `table`, or will be different if a join was used. 667 // `NowMillis` must be a Unix timestamp in milliseconds and is used by the granular 668 // policies; if `nowMillis - timestamp(record)` is greater than 669 // the post duration of a granular policy, than the record will be deleted. 670 // `GlobalPolicyEndTime` is used by the global policy; any record older than this time 671 // will be deleted by the global policy if it does not fall under a granular policy. 672 // To disable the granular policies, set `NowMillis` to 0. 673 // To disable the global policy, set `GlobalPolicyEndTime` to 0. 674 type RetentionPolicyBatchDeletionInfo struct { 675 BaseBuilder sq.SelectBuilder 676 Table string 677 TimeColumn string 678 PrimaryKeys []string 679 ChannelIDTable string 680 NowMillis int64 681 GlobalPolicyEndTime int64 682 Limit int64 683 } 684 685 // genericPermanentDeleteBatchForRetentionPolicies is a helper function for tables 686 // which need to delete records for granular and global policies. 687 func genericPermanentDeleteBatchForRetentionPolicies( 688 r RetentionPolicyBatchDeletionInfo, 689 s *SqlStore, 690 cursor model.RetentionPolicyCursor, 691 ) (int64, model.RetentionPolicyCursor, error) { 692 baseBuilder := r.BaseBuilder.InnerJoin("Channels ON " + r.ChannelIDTable + ".ChannelId = Channels.Id") 693 694 scopedTimeColumn := r.Table + "." + r.TimeColumn 695 nowStr := strconv.FormatInt(r.NowMillis, 10) 696 // A record falls under the scope of a granular retention policy if: 697 // 1. The policy's post duration is >= 0 698 // 2. The record's lifespan has not exceeded the policy's post duration 699 const millisecondsInADay = 24 * 60 * 60 * 1000 700 fallsUnderGranularPolicy := sq.And{ 701 sq.GtOrEq{"RetentionPolicies.PostDuration": 0}, 702 sq.Expr(nowStr + " - " + scopedTimeColumn + " > RetentionPolicies.PostDuration * " + strconv.FormatInt(millisecondsInADay, 10)), 703 } 704 705 // If the caller wants to disable the global policy from running 706 if r.GlobalPolicyEndTime <= 0 { 707 cursor.GlobalPoliciesDone = true 708 } 709 // If the caller wants to disable the granular policies from running 710 if r.NowMillis <= 0 { 711 cursor.ChannelPoliciesDone = true 712 cursor.TeamPoliciesDone = true 713 } 714 715 var totalRowsAffected int64 716 717 // First, delete all of the records which fall under the scope of a channel-specific policy 718 if !cursor.ChannelPoliciesDone { 719 channelPoliciesBuilder := baseBuilder. 720 InnerJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId"). 721 InnerJoin("RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id"). 722 Where(fallsUnderGranularPolicy). 723 Limit(uint64(r.Limit)) 724 rowsAffected, err := genericRetentionPoliciesDeletion(channelPoliciesBuilder, r, s) 725 if err != nil { 726 return 0, cursor, err 727 } 728 if rowsAffected < r.Limit { 729 cursor.ChannelPoliciesDone = true 730 } 731 totalRowsAffected += rowsAffected 732 r.Limit -= rowsAffected 733 } 734 735 // Next, delete all of the records which fall under the scope of a team-specific policy 736 if cursor.ChannelPoliciesDone && !cursor.TeamPoliciesDone { 737 // Channel-specific policies override team-specific policies. 738 teamPoliciesBuilder := baseBuilder. 739 LeftJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId"). 740 InnerJoin("RetentionPoliciesTeams ON Channels.TeamId = RetentionPoliciesTeams.TeamId"). 741 InnerJoin("RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id"). 742 Where(sq.And{ 743 sq.Eq{"RetentionPoliciesChannels.PolicyId": nil}, 744 sq.Expr("RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id"), 745 }). 746 Where(fallsUnderGranularPolicy). 747 Limit(uint64(r.Limit)) 748 rowsAffected, err := genericRetentionPoliciesDeletion(teamPoliciesBuilder, r, s) 749 if err != nil { 750 return 0, cursor, err 751 } 752 if rowsAffected < r.Limit { 753 cursor.TeamPoliciesDone = true 754 } 755 totalRowsAffected += rowsAffected 756 r.Limit -= rowsAffected 757 } 758 759 // Finally, delete all of the records which fall under the scope of the global policy 760 if cursor.ChannelPoliciesDone && cursor.TeamPoliciesDone && !cursor.GlobalPoliciesDone { 761 // Granular policies override the global policy. 762 globalPolicyBuilder := baseBuilder. 763 LeftJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId"). 764 LeftJoin("RetentionPoliciesTeams ON Channels.TeamId = RetentionPoliciesTeams.TeamId"). 765 LeftJoin("RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id"). 766 Where(sq.And{ 767 sq.Eq{"RetentionPoliciesChannels.PolicyId": nil}, 768 sq.Eq{"RetentionPoliciesTeams.PolicyId": nil}, 769 }). 770 Where(sq.Lt{scopedTimeColumn: r.GlobalPolicyEndTime}). 771 Limit(uint64(r.Limit)) 772 rowsAffected, err := genericRetentionPoliciesDeletion(globalPolicyBuilder, r, s) 773 if err != nil { 774 return 0, cursor, err 775 } 776 if rowsAffected < r.Limit { 777 cursor.GlobalPoliciesDone = true 778 } 779 totalRowsAffected += rowsAffected 780 } 781 782 return totalRowsAffected, cursor, nil 783 } 784 785 // genericRetentionPoliciesDeletion actually executes the DELETE query using a sq.SelectBuilder 786 // which selects the rows to delete. 787 func genericRetentionPoliciesDeletion( 788 builder sq.SelectBuilder, 789 r RetentionPolicyBatchDeletionInfo, 790 s *SqlStore, 791 ) (rowsAffected int64, err error) { 792 query, args, err := builder.ToSql() 793 if err != nil { 794 return 0, errors.Wrap(err, r.Table+"_tosql") 795 } 796 if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { 797 primaryKeysStr := "(" + strings.Join(r.PrimaryKeys, ",") + ")" 798 query = ` 799 DELETE FROM ` + r.Table + ` WHERE ` + primaryKeysStr + ` IN ( 800 ` + query + ` 801 )` 802 } else { 803 // MySQL does not support the LIMIT clause in a subquery with IN 804 clauses := make([]string, len(r.PrimaryKeys)) 805 for i, key := range r.PrimaryKeys { 806 clauses[i] = r.Table + "." + key + " = A." + key 807 } 808 joinClause := strings.Join(clauses, " AND ") 809 query = ` 810 DELETE ` + r.Table + ` FROM ` + r.Table + ` INNER JOIN ( 811 ` + query + ` 812 ) AS A ON ` + joinClause 813 } 814 result, err := s.GetMaster().Exec(query, args...) 815 if err != nil { 816 return 0, errors.Wrap(err, "failed to delete "+r.Table) 817 } 818 rowsAffected, err = result.RowsAffected() 819 if err != nil { 820 return 0, errors.Wrap(err, "failed to get rows affected for "+r.Table) 821 } 822 return 823 }