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