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  }