github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/store/sqlstore/channel_member_history_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  
    10  	sq "github.com/Masterminds/squirrel"
    11  	"github.com/pkg/errors"
    12  
    13  	"github.com/mattermost/mattermost-server/v5/mlog"
    14  	"github.com/mattermost/mattermost-server/v5/model"
    15  	"github.com/mattermost/mattermost-server/v5/store"
    16  )
    17  
    18  type SqlChannelMemberHistoryStore struct {
    19  	*SqlStore
    20  }
    21  
    22  func newSqlChannelMemberHistoryStore(sqlStore *SqlStore) store.ChannelMemberHistoryStore {
    23  	s := &SqlChannelMemberHistoryStore{
    24  		SqlStore: sqlStore,
    25  	}
    26  
    27  	for _, db := range sqlStore.GetAllConns() {
    28  		table := db.AddTableWithName(model.ChannelMemberHistory{}, "ChannelMemberHistory").SetKeys(false, "ChannelId", "UserId", "JoinTime")
    29  		table.ColMap("ChannelId").SetMaxSize(26)
    30  		table.ColMap("UserId").SetMaxSize(26)
    31  		table.ColMap("JoinTime").SetNotNull(true)
    32  	}
    33  
    34  	return s
    35  }
    36  
    37  func (s SqlChannelMemberHistoryStore) LogJoinEvent(userId string, channelId string, joinTime int64) error {
    38  	channelMemberHistory := &model.ChannelMemberHistory{
    39  		UserId:    userId,
    40  		ChannelId: channelId,
    41  		JoinTime:  joinTime,
    42  	}
    43  
    44  	if err := s.GetMaster().Insert(channelMemberHistory); err != nil {
    45  		return errors.Wrapf(err, "LogJoinEvent userId=%s channelId=%s joinTime=%d", userId, channelId, joinTime)
    46  	}
    47  	return nil
    48  }
    49  
    50  func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId string, leaveTime int64) error {
    51  	query, params, err := s.getQueryBuilder().
    52  		Update("ChannelMemberHistory").
    53  		Set("LeaveTime", leaveTime).
    54  		Where(sq.And{
    55  			sq.Eq{"UserId": userId},
    56  			sq.Eq{"ChannelId": channelId},
    57  			sq.Eq{"LeaveTime": nil},
    58  		}).ToSql()
    59  	if err != nil {
    60  		return errors.Wrap(err, "channel_member_history_to_sql")
    61  	}
    62  	sqlResult, err := s.GetMaster().Exec(query, params...)
    63  	if err != nil {
    64  		return errors.Wrapf(err, "LogLeaveEvent userId=%s channelId=%s leaveTime=%d", userId, channelId, leaveTime)
    65  	}
    66  
    67  	if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 {
    68  		// there was no join event to update - this is best effort, so no need to raise an error
    69  		mlog.Warn("Channel join event for user and channel not found", mlog.String("user", userId), mlog.String("channel", channelId))
    70  	}
    71  	return nil
    72  }
    73  
    74  func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) {
    75  	useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime)
    76  	if err != nil {
    77  		return nil, errors.Wrapf(err, "hasDataAtOrBefore startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId)
    78  	}
    79  
    80  	if useChannelMemberHistory {
    81  		// the export period starts after the ChannelMemberHistory table was first introduced, so we can use the
    82  		// data from it for our export
    83  		channelMemberHistories, err2 := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId)
    84  		if err2 != nil {
    85  			return nil, errors.Wrapf(err2, "getFromChannelMemberHistoryTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId)
    86  		}
    87  		return channelMemberHistories, nil
    88  	}
    89  	// the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the
    90  	// data by assuming that anybody who has ever joined the channel in question was present during the export period.
    91  	// this may not always be true, but it's better than saying that somebody wasn't there when they were
    92  	channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId)
    93  	if err != nil {
    94  		return nil, errors.Wrapf(err, "getFromChannelMembersTable startTime=%d endTime=%d channelId=%s", startTime, endTime, channelId)
    95  	}
    96  	return channelMemberHistories, nil
    97  }
    98  
    99  func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) {
   100  	type NullableCountResult struct {
   101  		Min sql.NullInt64
   102  	}
   103  	query, _, err := s.getQueryBuilder().Select("MIN(JoinTime) as Min").From("ChannelMemberHistory").ToSql()
   104  	if err != nil {
   105  		return false, errors.Wrap(err, "channel_member_history_to_sql")
   106  	}
   107  	var result NullableCountResult
   108  	if err := s.GetReplica().SelectOne(&result, query); err != nil {
   109  		return false, err
   110  	} else if result.Min.Valid {
   111  		return result.Min.Int64 <= time, nil
   112  	} else {
   113  		// if the result was null, there are no rows in the table, so there is no data from before
   114  		return false, nil
   115  	}
   116  }
   117  
   118  func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) {
   119  	query, args, err := s.getQueryBuilder().
   120  		Select("cmh.*, u.Email, u.Username, Bots.UserId IS NOT NULL AS IsBot, u.DeleteAt AS UserDeleteAt").
   121  		From("ChannelMemberHistory cmh").
   122  		Join("Users u ON cmh.UserId = u.Id").
   123  		LeftJoin("Bots ON Bots.UserId = u.Id").
   124  		Where(sq.And{
   125  			sq.Eq{"cmh.ChannelId": channelId},
   126  			sq.LtOrEq{"cmh.JoinTime": endTime},
   127  			sq.Or{
   128  				sq.Eq{"cmh.LeaveTime": nil},
   129  				sq.GtOrEq{"cmh.LeaveTime": startTime},
   130  			},
   131  		}).
   132  		OrderBy("cmh.JoinTime ASC").ToSql()
   133  	if err != nil {
   134  		return nil, errors.Wrap(err, "channel_member_history_to_sql")
   135  	}
   136  	var histories []*model.ChannelMemberHistoryResult
   137  	if _, err := s.GetReplica().Select(&histories, query, args...); err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	return histories, nil
   142  }
   143  
   144  func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistoryResult, error) {
   145  	query, args, err := s.getQueryBuilder().
   146  		Select("ch.ChannelId, ch.UserId, u.Email, u.Username, Bots.UserId IS NOT NULL AS IsBot, u.DeleteAt AS UserDeleteAt").
   147  		Distinct().
   148  		From("ChannelMembers ch").
   149  		Join("Users u ON ch.UserId = u.id").
   150  		LeftJoin("Bots ON Bots.UserId = u.id").
   151  		Where(sq.Eq{"ch.ChannelId": channelId}).ToSql()
   152  	if err != nil {
   153  		return nil, errors.Wrap(err, "channel_member_history_to_sql")
   154  	}
   155  
   156  	var histories []*model.ChannelMemberHistoryResult
   157  	if _, err := s.GetReplica().Select(&histories, query, args...); err != nil {
   158  		return nil, err
   159  	}
   160  	// we have to fill in the join/leave times, because that data doesn't exist in the channel members table
   161  	for _, channelMemberHistory := range histories {
   162  		channelMemberHistory.JoinTime = startTime
   163  		channelMemberHistory.LeaveTime = model.NewInt64(endTime)
   164  	}
   165  	return histories, nil
   166  }
   167  
   168  func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
   169  	var (
   170  		query string
   171  		args  []interface{}
   172  		err   error
   173  	)
   174  
   175  	if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   176  		var innerSelect string
   177  		innerSelect, args, err = s.getQueryBuilder().
   178  			Select("ctid").
   179  			From("ChannelMemberHistory").
   180  			Where(sq.And{
   181  				sq.NotEq{"LeaveTime": nil},
   182  				sq.LtOrEq{"LeaveTime": endTime},
   183  			}).Limit(uint64(limit)).
   184  			ToSql()
   185  		if err != nil {
   186  			return 0, errors.Wrap(err, "channel_member_history_to_sql")
   187  		}
   188  		query, _, err = s.getQueryBuilder().
   189  			Delete("ChannelMemberHistory").
   190  			Where(fmt.Sprintf(
   191  				"ctid IN (%s)", innerSelect,
   192  			)).ToSql()
   193  	} else {
   194  		query, args, err = s.getQueryBuilder().
   195  			Delete("ChannelMemberHistory").
   196  			Where(sq.And{
   197  				sq.NotEq{"LeaveTime": nil},
   198  				sq.LtOrEq{"LeaveTime": endTime},
   199  			}).
   200  			Limit(uint64(limit)).ToSql()
   201  	}
   202  	if err != nil {
   203  		return 0, errors.Wrap(err, "channel_member_history_to_sql")
   204  	}
   205  	sqlResult, err := s.GetMaster().Exec(query, args...)
   206  	if err != nil {
   207  		return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit)
   208  	}
   209  
   210  	rowsAffected, err := sqlResult.RowsAffected()
   211  	if err != nil {
   212  		return 0, errors.Wrapf(err, "PermanentDeleteBatch endTime=%d limit=%d", endTime, limit)
   213  	}
   214  	return rowsAffected, nil
   215  }