[MM-30831] granular data retention wireup (#17417)

* pre-checkout commit

* add API endpoints for retention policies

* allow deleting multiple teams/channels from a policy in a single request

* pre-checkout commit

* add auditing in API functions

* add permission checks

* update the store layers

* update storetest

* add check constraint on PostDuration column

* pre-checkout commit

* add query to delete posts under the scope of a granular retention policy

* add suggestions from sbishel

* allow clients to specify channels/teams when creating a new policy

* remove foreign keys referencing Channels and Teams tables

* add checks for whether teams and channels exist

* pre-checkout commit

* remove data referencing the Posts table

* pre-checkout commit

* write data store tests

* sort results of buildGetPoliciesQuery

* add missing test cases for teams

* pre-checkout commit

* add Client4 methods for data retention policy endpoints

* add uint and uint64 to app/layer_generators

* make granular policies override global policies

* fix lint errors

* pre-checkout commit

* add license to top of files

* add tests for data store layer

* add missing test cases for store layer

* run make i18n-extract

* add query to delete ChannelMemberHistory

* work in progress

* add test for old reply to old post

* fix lint error

* use COALESCE on each Posts column

* begin implementing orphaned rows worker

* split PR

* pre-checkout commit

* use RetentionPolicyWithTeamAndChannelCounts

* update app and api layers

* run make i18n-extract

* add RetentionPolicy to retrylayer_test.go

* Revert "split PR"

This reverts commit b316f03dd307a30deae931944ca7e4a1cc904605.

* fix errors caused by revert

* add suggestions from sbishel

* fix copy-paste error

* fix lint errors

* pre-checkout commit

* add function to delete orphaned rows

* use -1 for infinite retention

* remove check constraint

* copy i18n entries from master

* re-run tests with newer enterprise branch

* add team data to channel list

* add search for channels and teams in a policy

* add store tests for channel and team search

* add suggestions from mkraft

* run make einterfaces-mocks

* fix lint errors

* add suggestions from mkraft

* move removeOrphanedRows method to wireup branch

* Revert "move removeOrphanedRows method to wireup branch"

This reverts commit 94605c9b4a5378ffa44a3dec4d3f8e3306b9d33e.

* use DeleteOrphanedRows where possible

* run make i18n-extract

* use COMPLIANCE permissions

* run make migrations-bindadta

* clean up teams before test

* fix tests for TestRetentionPolicyStore

* add API endpoints for mobile

* fix lint error

* fix some of the lint errors

* move user/data_retention endpoints to data_retention.go

* Revert "fix some of the lint errors"

This reverts commit b5b2dc27566c427187db942c5c0afe319e7679c4.

* add exclude_policy_constrained parameter for /channels and /teams

* fix lint errors

* add policy_id field to GET endpoints for channels and teams

* use PolicyWithTeamID in RetentionPolicy layer

* fix lint errors

* run make i18n-extract

* update mock call in telemetry_test.go

* return status:OK in JSON instead of 204

* pre-checkout commit

* add policy_id field on channels/teams

* fix lint errors

* use sq.Eq instead of '?'

* use new subsection permissions

* update channels and teams endpoints to use new subsection permissions

* add extra search opts for channels in a policy

* fix lint errors

* allow negative post duration in patch

* remove DELETE FROM query in retention policy tests

* use *int64 for PostDuration

* re-run CI tests

* use 3-step deletion strategy for each table

* fix lint errors

* run make store-layers

* re-run CI tests

* add test with channel, team and global policies

* use common function for SQL queries

* add pagination test

* use struct for args to common SQL function

* fix lint errors

* run make i18n-extract

* check if Channels.TeamId is "" or nil

* use three OR clauses

* write separate genericRetentionPoliciesDeletion function

* add config setting for BatchSize

* add telemetry for BatchSize

* use feature flag

* add old i18n messages back in

* re-run CI tests

* update call signature in storetest

* MM-30831: Adds constant for retention default batch size.

* MM-30831: Removes comment re: optimization.

* MM-30831: Converts days to milliseconds.

* MM-30831: Reverts change to test.

* Revert "MM-30831: Reverts change to test."

This reverts commit 6d14275a1ceae682bb9e17ec69b39252b44e0c0c.

* Revert "MM-30831: Converts days to milliseconds."

This reverts commit a0cb6ec09d854a05194c1daee1c5333f260231c3.

* MM-30831: Fixes tests.

* MM-30381: Fix for change to method sig.

Co-authored-by: Max Erenberg <max.erenberg@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Martin Kraft <martin@upspin.org>
Этот коммит содержится в:
Max Erenberg
2021-06-23 07:55:12 -04:00
коммит произвёл GitHub
родитель 71ef1c5386
Коммит 58d5d51f7a
30 изменённых файлов: 1824 добавлений и 66 удалений

Просмотреть файл

@@ -299,7 +299,8 @@ func TestCreateDefaultMemberships(t *testing.T) {
timeAfterLeaving := model.GetMillis() timeAfterLeaving := model.GetMillis()
// Purging channelmemberhistory doesn't re-add user to channel // Purging channelmemberhistory doesn't re-add user to channel
_, nErr := th.App.Srv().Store.ChannelMemberHistory().PermanentDeleteBatch(timeBeforeLeaving, 1000) _, _, nErr := th.App.Srv().Store.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
0, timeBeforeLeaving, 1000, model.RetentionPolicyCursor{})
if nErr != nil { if nErr != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error()) t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error())
} }
@@ -315,7 +316,8 @@ func TestCreateDefaultMemberships(t *testing.T) {
} }
// Purging channelmemberhistory doesn't re-add user to channel // Purging channelmemberhistory doesn't re-add user to channel
_, nErr = th.App.Srv().Jobs.Store.ChannelMemberHistory().PermanentDeleteBatch(timeAfterLeaving, 1000) _, _, nErr = th.App.Srv().Store.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
0, timeAfterLeaving, 1000, model.RetentionPolicyCursor{})
if nErr != nil { if nErr != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error()) t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error())
} }

Просмотреть файл

@@ -6958,6 +6958,10 @@
"id": "ent.data_retention.reactions_batch.internal_error", "id": "ent.data_retention.reactions_batch.internal_error",
"translation": "We encountered an error permanently deleting the batch of reactions." "translation": "We encountered an error permanently deleting the batch of reactions."
}, },
{
"id": "ent.data_retention.run_failed.error",
"translation": "Data retention job failed."
},
{ {
"id": "ent.elasticsearch.aggregator_worker.create_index_job.error", "id": "ent.elasticsearch.aggregator_worker.create_index_job.error",
"translation": "Elasticsearch aggregator worker failed to create the indexing job" "translation": "Elasticsearch aggregator worker failed to create the indexing job"

Просмотреть файл

@@ -133,7 +133,8 @@ func (srv *JobServer) InitWorkers() error {
// Synchronization should be implemented by the caller. // Synchronization should be implemented by the caller.
func (workers *Workers) Start() { func (workers *Workers) Start() {
mlog.Info("Starting workers") mlog.Info("Starting workers")
if workers.DataRetention != nil && (*workers.ConfigService.Config().DataRetentionSettings.EnableMessageDeletion || *workers.ConfigService.Config().DataRetentionSettings.EnableFileDeletion) {
if workers.DataRetention != nil {
go workers.DataRetention.Run() go workers.DataRetention.Run()
} }

Просмотреть файл

@@ -206,6 +206,7 @@ const (
DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS = 365 DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS = 365
DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365 DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365
DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00" DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00"
DATA_RETENTION_SETTINGS_DEFAULT_BATCH_SIZE = 3000
PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins"
PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins"
@@ -2700,6 +2701,7 @@ type DataRetentionSettings struct {
MessageRetentionDays *int `access:"compliance_data_retention_policy"` MessageRetentionDays *int `access:"compliance_data_retention_policy"`
FileRetentionDays *int `access:"compliance_data_retention_policy"` FileRetentionDays *int `access:"compliance_data_retention_policy"`
DeletionJobStartTime *string `access:"compliance_data_retention_policy"` DeletionJobStartTime *string `access:"compliance_data_retention_policy"`
BatchSize *int `access:"compliance_data_retention_policy"`
} }
func (s *DataRetentionSettings) SetDefaults() { func (s *DataRetentionSettings) SetDefaults() {
@@ -2722,6 +2724,10 @@ func (s *DataRetentionSettings) SetDefaults() {
if s.DeletionJobStartTime == nil { if s.DeletionJobStartTime == nil {
s.DeletionJobStartTime = NewString(DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME) s.DeletionJobStartTime = NewString(DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME)
} }
if s.BatchSize == nil {
s.BatchSize = NewInt(DATA_RETENTION_SETTINGS_DEFAULT_BATCH_SIZE)
}
} }
type JobSettings struct { type JobSettings struct {

Просмотреть файл

@@ -68,6 +68,12 @@ type RetentionPolicyForChannelList struct {
TotalCount int64 `json:"total_count"` TotalCount int64 `json:"total_count"`
} }
type RetentionPolicyCursor struct {
ChannelPoliciesDone bool
TeamPoliciesDone bool
GlobalPoliciesDone bool
}
func (rp *GlobalRetentionPolicy) ToJson() []byte { func (rp *GlobalRetentionPolicy) ToJson() []byte {
b, _ := json.Marshal(rp) b, _ := json.Marshal(rp)
return b return b

Просмотреть файл

@@ -371,6 +371,9 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
} }
func (o *Post) SanitizeProps() { func (o *Post) SanitizeProps() {
if o == nil {
return
}
membersToSanitize := []string{ membersToSanitize := []string{
PROPS_ADD_CHANNEL_MEMBER, PROPS_ADD_CHANNEL_MEMBER,
} }
@@ -728,3 +731,10 @@ func (o *Post) IsFromOAuthBot() bool {
props := o.GetProps() props := o.GetProps()
return props["from_webhook"] == "true" && props["override_username"] != "" return props["from_webhook"] == "true" && props["override_username"] != ""
} }
func (o *Post) ToNilIfInvalid() *Post {
if o.Id == "" {
return nil
}
return o
}

Просмотреть файл

@@ -781,6 +781,7 @@ func (ts *TelemetryService) trackConfig() {
"message_retention_days": *cfg.DataRetentionSettings.MessageRetentionDays, "message_retention_days": *cfg.DataRetentionSettings.MessageRetentionDays,
"file_retention_days": *cfg.DataRetentionSettings.FileRetentionDays, "file_retention_days": *cfg.DataRetentionSettings.FileRetentionDays,
"deletion_job_start_time": *cfg.DataRetentionSettings.DeletionJobStartTime, "deletion_job_start_time": *cfg.DataRetentionSettings.DeletionJobStartTime,
"batch_size": *cfg.DataRetentionSettings.BatchSize,
}) })
ts.sendTelemetry(TrackConfigMessageExport, map[string]interface{}{ ts.sendTelemetry(TrackConfigMessageExport, map[string]interface{}{

Просмотреть файл

@@ -2338,6 +2338,24 @@ func (s *OpenTracingLayerChannelStore) UserBelongsToChannels(userID string, chan
return result, err return result, err
} }
func (s *OpenTracingLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelMemberHistoryStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelMemberHistoryStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) { func (s *OpenTracingLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelMemberHistoryStore.GetUsersInChannelDuring") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelMemberHistoryStore.GetUsersInChannelDuring")
@@ -2410,6 +2428,24 @@ func (s *OpenTracingLayerChannelMemberHistoryStore) PermanentDeleteBatch(endTime
return result, err return result, err
} }
func (s *OpenTracingLayerChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelMemberHistoryStore.PermanentDeleteBatchForRetentionPolicies")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, resultVar1, err := s.ChannelMemberHistoryStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, resultVar1, err
}
func (s *OpenTracingLayerClusterDiscoveryStore) Cleanup() error { func (s *OpenTracingLayerClusterDiscoveryStore) Cleanup() error {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ClusterDiscoveryStore.Cleanup") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ClusterDiscoveryStore.Cleanup")
@@ -5054,6 +5090,24 @@ func (s *OpenTracingLayerPostStore) Delete(postID string, time int64, deleteByID
return err return err
} }
func (s *OpenTracingLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { func (s *OpenTracingLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Get") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Get")
@@ -5551,6 +5605,24 @@ func (s *OpenTracingLayerPostStore) PermanentDeleteBatch(endTime int64, limit in
return result, err return result, err
} }
func (s *OpenTracingLayerPostStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDeleteBatchForRetentionPolicies")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, resultVar1, err := s.PostStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, resultVar1, err
}
func (s *OpenTracingLayerPostStore) PermanentDeleteByChannel(channelID string) error { func (s *OpenTracingLayerPostStore) PermanentDeleteByChannel(channelID string) error {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDeleteByChannel") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDeleteByChannel")
@@ -5749,6 +5821,24 @@ func (s *OpenTracingLayerPreferenceStore) DeleteCategoryAndName(category string,
return err return err
} }
func (s *OpenTracingLayerPreferenceStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PreferenceStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PreferenceStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) { func (s *OpenTracingLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PreferenceStore.Get") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PreferenceStore.Get")
@@ -5965,6 +6055,24 @@ func (s *OpenTracingLayerReactionStore) DeleteAllWithEmojiName(emojiName string)
return err return err
} }
func (s *OpenTracingLayerReactionStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ReactionStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) { func (s *OpenTracingLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetForPost") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetForPost")
@@ -6217,6 +6325,24 @@ func (s *OpenTracingLayerRetentionPolicyStore) Delete(id string) error {
return err return err
} }
func (s *OpenTracingLayerRetentionPolicyStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.RetentionPolicyStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { func (s *OpenTracingLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Get") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RetentionPolicyStore.Get")
@@ -8812,6 +8938,24 @@ func (s *OpenTracingLayerThreadStore) DeleteMembershipForUser(userId string, pos
return err return err
} }
func (s *OpenTracingLayerThreadStore) DeleteOrphanedRows(limit int) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.DeleteOrphanedRows")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ThreadStore.DeleteOrphanedRows(limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerThreadStore) Get(id string) (*model.Thread, error) { func (s *OpenTracingLayerThreadStore) Get(id string) (*model.Thread, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.Get") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.Get")
@@ -9010,6 +9154,42 @@ func (s *OpenTracingLayerThreadStore) MarkAsRead(userID string, threadID string,
return err return err
} }
func (s *OpenTracingLayerThreadStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.PermanentDeleteBatchForRetentionPolicies")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, resultVar1, err
}
func (s *OpenTracingLayerThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.PermanentDeleteBatchThreadMembershipsForRetentionPolicies")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, resultVar1, err
}
func (s *OpenTracingLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) { func (s *OpenTracingLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.Save") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.Save")

Просмотреть файл

@@ -2488,6 +2488,26 @@ func (s *RetryLayerChannelStore) UserBelongsToChannels(userID string, channelIds
} }
func (s *RetryLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.ChannelMemberHistoryStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) { func (s *RetryLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) {
tries := 0 tries := 0
@@ -2568,6 +2588,26 @@ func (s *RetryLayerChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64
} }
func (s *RetryLayerChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
tries := 0
for {
result, resultVar1, err := s.ChannelMemberHistoryStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err == nil {
return result, resultVar1, nil
}
if !isRepeatableError(err) {
return result, resultVar1, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, resultVar1, err
}
}
}
func (s *RetryLayerClusterDiscoveryStore) Cleanup() error { func (s *RetryLayerClusterDiscoveryStore) Cleanup() error {
tries := 0 tries := 0
@@ -5472,6 +5512,26 @@ func (s *RetryLayerPostStore) Delete(postID string, time int64, deleteByID strin
} }
func (s *RetryLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.PostStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { func (s *RetryLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
tries := 0 tries := 0
@@ -5990,6 +6050,26 @@ func (s *RetryLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (
} }
func (s *RetryLayerPostStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
tries := 0
for {
result, resultVar1, err := s.PostStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err == nil {
return result, resultVar1, nil
}
if !isRepeatableError(err) {
return result, resultVar1, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, resultVar1, err
}
}
}
func (s *RetryLayerPostStore) PermanentDeleteByChannel(channelID string) error { func (s *RetryLayerPostStore) PermanentDeleteByChannel(channelID string) error {
tries := 0 tries := 0
@@ -6210,6 +6290,26 @@ func (s *RetryLayerPreferenceStore) DeleteCategoryAndName(category string, name
} }
func (s *RetryLayerPreferenceStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.PreferenceStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) { func (s *RetryLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) {
tries := 0 tries := 0
@@ -6450,6 +6550,26 @@ func (s *RetryLayerReactionStore) DeleteAllWithEmojiName(emojiName string) error
} }
func (s *RetryLayerReactionStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.ReactionStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) { func (s *RetryLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) {
tries := 0 tries := 0
@@ -6730,6 +6850,26 @@ func (s *RetryLayerRetentionPolicyStore) Delete(id string) error {
} }
func (s *RetryLayerRetentionPolicyStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.RetentionPolicyStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { func (s *RetryLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
tries := 0 tries := 0
@@ -9588,6 +9728,26 @@ func (s *RetryLayerThreadStore) DeleteMembershipForUser(userId string, postID st
} }
func (s *RetryLayerThreadStore) DeleteOrphanedRows(limit int) (int64, error) {
tries := 0
for {
result, err := s.ThreadStore.DeleteOrphanedRows(limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerThreadStore) Get(id string) (*model.Thread, error) { func (s *RetryLayerThreadStore) Get(id string) (*model.Thread, error) {
tries := 0 tries := 0
@@ -9808,6 +9968,46 @@ func (s *RetryLayerThreadStore) MarkAsRead(userID string, threadID string, times
} }
func (s *RetryLayerThreadStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
tries := 0
for {
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err == nil {
return result, resultVar1, nil
}
if !isRepeatableError(err) {
return result, resultVar1, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, resultVar1, err
}
}
}
func (s *RetryLayerThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
tries := 0
for {
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
if err == nil {
return result, resultVar1, nil
}
if !isRepeatableError(err) {
return result, resultVar1, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, resultVar1, err
}
}
}
func (s *RetryLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) { func (s *RetryLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) {
tries := 0 tries := 0

Просмотреть файл

@@ -165,6 +165,46 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64
return histories, nil return histories, nil
} }
// PermanentDeleteBatchForRetentionPolicies deletes a batch of records which are affected by
// the global or a granular retention policy.
// See `genericPermanentDeleteBatchForRetentionPolicies` for details.
func (s SqlChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
builder := s.getQueryBuilder().
Select("ChannelMemberHistory.ChannelId, ChannelMemberHistory.UserId, ChannelMemberHistory.JoinTime").
From("ChannelMemberHistory")
return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{
BaseBuilder: builder,
Table: "ChannelMemberHistory",
TimeColumn: "LeaveTime",
PrimaryKeys: []string{"ChannelId", "UserId", "JoinTime"},
ChannelIDTable: "ChannelMemberHistory",
NowMillis: now,
GlobalPolicyEndTime: globalPolicyEndTime,
Limit: limit,
}, s.SqlStore, cursor)
}
// DeleteOrphanedRows removes entries from ChannelMemberHistory when a corresponding channel no longer exists.
func (s SqlChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const query = `
DELETE FROM ChannelMemberHistory WHERE (ChannelId, UserId, JoinTime) IN (
SELECT * FROM (
SELECT ChannelId, UserId, JoinTime FROM ChannelMemberHistory
LEFT JOIN Channels ON ChannelMemberHistory.ChannelId = Channels.Id
WHERE Channels.Id IS NULL
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit}
result, err := s.GetMaster().Exec(query, props)
if err != nil {
return
}
deleted, err = result.RowsAffected()
return
}
func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
var ( var (
query string query string

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -42,8 +43,34 @@ type postWithExtra struct {
func (s *SqlPostStore) ClearCaches() { func (s *SqlPostStore) ClearCaches() {
} }
func postSliceColumns() []string { func postSliceColumnsWithTypes() []struct {
return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "IsPinned", "UserId", "ChannelId", "RootId", "ParentId", "OriginalId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions", "RemoteId"} Name string
Type reflect.Kind
} {
return []struct {
Name string
Type reflect.Kind
}{
{"Id", reflect.String},
{"CreateAt", reflect.Int64},
{"UpdateAt", reflect.Int64},
{"EditAt", reflect.Int64},
{"DeleteAt", reflect.Int64},
{"IsPinned", reflect.Bool},
{"UserId", reflect.String},
{"ChannelId", reflect.String},
{"RootId", reflect.String},
{"ParentId", reflect.String},
{"OriginalId", reflect.String},
{"Message", reflect.String},
{"Type", reflect.String},
{"Props", reflect.Map},
{"Hashtags", reflect.String},
{"Filenames", reflect.Slice},
{"FileIds", reflect.Slice},
{"HasReactions", reflect.Bool},
{"RemoteId", reflect.String},
}
} }
func postToSlice(post *model.Post) []interface{} { func postToSlice(post *model.Post) []interface{} {
@@ -70,6 +97,37 @@ func postToSlice(post *model.Post) []interface{} {
} }
} }
func postSliceColumns() []string {
colInfos := postSliceColumnsWithTypes()
cols := make([]string, len(colInfos))
for i, colInfo := range colInfos {
cols[i] = colInfo.Name
}
return cols
}
func postSliceCoalesceQuery() string {
colInfos := postSliceColumnsWithTypes()
cols := make([]string, len(colInfos))
for i, colInfo := range colInfos {
var defaultValue string
switch colInfo.Type {
case reflect.String:
defaultValue = "''"
case reflect.Int64:
defaultValue = "0"
case reflect.Bool:
defaultValue = "false"
case reflect.Map:
defaultValue = "'{}'"
case reflect.Slice:
defaultValue = "'[]'"
}
cols[i] = "COALESCE(Posts." + colInfo.Name + "," + defaultValue + ") AS " + colInfo.Name
}
return strings.Join(cols, ",")
}
func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.PostStore { func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.PostStore {
s := &SqlPostStore{ s := &SqlPostStore{
SqlStore: sqlStore, SqlStore: sqlStore,
@@ -1927,6 +1985,46 @@ func (s *SqlPostStore) GetPostsBatchForIndexing(startTime int64, endTime int64,
return posts, nil return posts, nil
} }
// PermanentDeleteBatchForRetentionPolicies deletes a batch of records which are affected by
// the global or a granular retention policy.
// See `genericPermanentDeleteBatchForRetentionPolicies` for details.
func (s *SqlPostStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
builder := s.getQueryBuilder().
Select("Posts.Id").
From("Posts")
return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{
BaseBuilder: builder,
Table: "Posts",
TimeColumn: "CreateAt",
PrimaryKeys: []string{"Id"},
ChannelIDTable: "Posts",
NowMillis: now,
GlobalPolicyEndTime: globalPolicyEndTime,
Limit: limit,
}, s.SqlStore, cursor)
}
// DeleteOrphanedRows removes entries from Posts when a corresponding channel no longer exists.
func (s *SqlPostStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const query = `
DELETE FROM Posts WHERE Id IN (
SELECT * FROM (
SELECT Posts.Id FROM Posts
LEFT JOIN Channels ON Posts.ChannelId = Channels.Id
WHERE Channels.Id IS NULL
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit}
result, err := s.GetMaster().Exec(query, props)
if err != nil {
return
}
deleted, err = result.RowsAffected()
return
}
func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { func (s *SqlPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
var query string var query string
if s.DriverName() == "postgres" { if s.DriverName() == "postgres" {

Просмотреть файл

@@ -265,6 +265,28 @@ func (s SqlPreferenceStore) DeleteCategoryAndName(category string, name string)
return nil return nil
} }
// DeleteOrphanedRows removes entries from Preferences (flagged post) when a
// corresponding post no longer exists.
func (s *SqlPreferenceStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const query = `
DELETE FROM Preferences WHERE Name IN (
SELECT * FROM (
SELECT Preferences.Name FROM Preferences
LEFT JOIN Posts ON Preferences.Name = Posts.Id
WHERE Posts.Id IS NULL AND Category = :Category
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST}
result, err := s.GetMaster().Exec(query, props)
if err != nil {
return
}
deleted, err = result.RowsAffected()
return
}
func (s SqlPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) { func (s SqlPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
if limit < 0 { if limit < 0 {
// uint64 does not throw an error, it overflows if it is negative. // uint64 does not throw an error, it overflows if it is negative.

Просмотреть файл

@@ -209,6 +209,27 @@ func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) error {
return nil return nil
} }
// DeleteOrphanedRows removes entries from Reactions when a corresponding post no longer exists.
func (s *SqlReactionStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const query = `
DELETE FROM Reactions WHERE PostId IN (
SELECT * FROM (
SELECT PostId FROM Reactions
LEFT JOIN Posts ON Reactions.PostId = Posts.Id
WHERE Posts.Id IS NULL
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit}
result, err := s.GetMaster().Exec(query, props)
if err != nil {
return
}
deleted, err = result.RowsAffected()
return
}
func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
var query string var query string
if s.DriverName() == "postgres" { if s.DriverName() == "postgres" {

Просмотреть файл

@@ -5,6 +5,8 @@ package sqlstore
import ( import (
"database/sql" "database/sql"
"strconv"
"strings"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
@@ -541,6 +543,49 @@ func (s *SqlRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string)
return err return err
} }
// DeleteOrphanedRows removes entries from RetentionPoliciesChannels and RetentionPoliciesTeams
// where a channel or team no longer exists.
func (s *SqlRetentionPolicyStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const rpcDeleteQuery = `
DELETE FROM RetentionPoliciesChannels WHERE ChannelId IN (
SELECT * FROM (
SELECT ChannelId FROM RetentionPoliciesChannels
LEFT JOIN Channels ON RetentionPoliciesChannels.ChannelId = Channels.Id
WHERE Channels.Id IS NULL
LIMIT :Limit
) AS A
)`
const rptDeleteQuery = `
DELETE FROM RetentionPoliciesTeams WHERE TeamId IN (
SELECT * FROM (
SELECT TeamId FROM RetentionPoliciesTeams
LEFT JOIN Teams ON RetentionPoliciesTeams.TeamId = Teams.Id
WHERE Teams.Id IS NULL
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit}
result, err := s.GetMaster().Exec(rpcDeleteQuery, props)
if err != nil {
return
}
rpcDeleted, err := result.RowsAffected()
if err != nil {
return
}
result, err = s.GetMaster().Exec(rptDeleteQuery, props)
if err != nil {
return
}
rptDeleted, err := result.RowsAffected()
if err != nil {
return
}
deleted = rpcDeleted + rptDeleted
return
}
func (s *SqlRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForTeam, err error) { func (s *SqlRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset, limit int) (policies []*model.RetentionPolicyForTeam, err error) {
const query = ` const query = `
SELECT Teams.Id, RetentionPolicies.PostDuration SELECT Teams.Id, RetentionPolicies.PostDuration
@@ -606,3 +651,173 @@ func (s *SqlRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string)
props := map[string]interface{}{"UserId": userID} props := map[string]interface{}{"UserId": userID}
return s.GetReplica().SelectInt(query, props) return s.GetReplica().SelectInt(query, props)
} }
// RetentionPolicyBatchDeletionInfo gives information on how to delete records
// under a retention policy; see `genericPermanentDeleteBatchForRetentionPolicies`.
//
// `BaseBuilder` should already have selected the primary key(s) for the main table
// and should be joined to a table with a ChannelId column, which will be used to join
// on the Channels table.
// `Table` is the name of the table from which records are being deleted.
// `TimeColumn` is the name of the column which contains the timestamp of the record.
// `PrimaryKeys` contains the primary keys of `table`. It should be the same as the
// `From` clause in `baseBuilder`.
// `ChannelIDTable` is the table which contains the ChannelId column, it may be the
// same as `table`, or will be different if a join was used.
// `NowMillis` must be a Unix timestamp in milliseconds and is used by the granular
// policies; if `nowMillis - timestamp(record)` is greater than
// the post duration of a granular policy, than the record will be deleted.
// `GlobalPolicyEndTime` is used by the global policy; any record older than this time
// will be deleted by the global policy if it does not fall under a granular policy.
// To disable the granular policies, set `NowMillis` to 0.
// To disable the global policy, set `GlobalPolicyEndTime` to 0.
type RetentionPolicyBatchDeletionInfo struct {
BaseBuilder sq.SelectBuilder
Table string
TimeColumn string
PrimaryKeys []string
ChannelIDTable string
NowMillis int64
GlobalPolicyEndTime int64
Limit int64
}
// genericPermanentDeleteBatchForRetentionPolicies is a helper function for tables
// which need to delete records for granular and global policies.
func genericPermanentDeleteBatchForRetentionPolicies(
r RetentionPolicyBatchDeletionInfo,
s *SqlStore,
cursor model.RetentionPolicyCursor,
) (int64, model.RetentionPolicyCursor, error) {
baseBuilder := r.BaseBuilder.InnerJoin("Channels ON " + r.ChannelIDTable + ".ChannelId = Channels.Id")
scopedTimeColumn := r.Table + "." + r.TimeColumn
nowStr := strconv.FormatInt(r.NowMillis, 10)
// A record falls under the scope of a granular retention policy if:
// 1. The policy's post duration is >= 0
// 2. The record's lifespan has not exceeded the policy's post duration
const millisecondsInADay = 24 * 60 * 60 * 1000
fallsUnderGranularPolicy := sq.And{
sq.GtOrEq{"RetentionPolicies.PostDuration": 0},
sq.Expr(nowStr + " - " + scopedTimeColumn + " > RetentionPolicies.PostDuration * " + strconv.FormatInt(millisecondsInADay, 10)),
}
// If the caller wants to disable the global policy from running
if r.GlobalPolicyEndTime <= 0 {
cursor.GlobalPoliciesDone = true
}
// If the caller wants to disable the granular policies from running
if r.NowMillis <= 0 {
cursor.ChannelPoliciesDone = true
cursor.TeamPoliciesDone = true
}
var totalRowsAffected int64
// First, delete all of the records which fall under the scope of a channel-specific policy
if !cursor.ChannelPoliciesDone {
channelPoliciesBuilder := baseBuilder.
InnerJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId").
InnerJoin("RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id").
Where(fallsUnderGranularPolicy).
Limit(uint64(r.Limit))
rowsAffected, err := genericRetentionPoliciesDeletion(channelPoliciesBuilder, r, s)
if err != nil {
return 0, cursor, err
}
if rowsAffected < r.Limit {
cursor.ChannelPoliciesDone = true
}
totalRowsAffected += rowsAffected
r.Limit -= rowsAffected
}
// Next, delete all of the records which fall under the scope of a team-specific policy
if cursor.ChannelPoliciesDone && !cursor.TeamPoliciesDone {
// Channel-specific policies override team-specific policies.
teamPoliciesBuilder := baseBuilder.
LeftJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId").
InnerJoin("RetentionPoliciesTeams ON Channels.TeamId = RetentionPoliciesTeams.TeamId").
InnerJoin("RetentionPolicies ON RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id").
Where(sq.And{
sq.Eq{"RetentionPoliciesChannels.PolicyId": nil},
sq.Expr("RetentionPoliciesTeams.PolicyId = RetentionPolicies.Id"),
}).
Where(fallsUnderGranularPolicy).
Limit(uint64(r.Limit))
rowsAffected, err := genericRetentionPoliciesDeletion(teamPoliciesBuilder, r, s)
if err != nil {
return 0, cursor, err
}
if rowsAffected < r.Limit {
cursor.TeamPoliciesDone = true
}
totalRowsAffected += rowsAffected
r.Limit -= rowsAffected
}
// Finally, delete all of the records which fall under the scope of the global policy
if cursor.ChannelPoliciesDone && cursor.TeamPoliciesDone && !cursor.GlobalPoliciesDone {
// Granular policies override the global policy.
globalPolicyBuilder := baseBuilder.
LeftJoin("RetentionPoliciesChannels ON " + r.ChannelIDTable + ".ChannelId = RetentionPoliciesChannels.ChannelId").
LeftJoin("RetentionPoliciesTeams ON Channels.TeamId = RetentionPoliciesTeams.TeamId").
LeftJoin("RetentionPolicies ON RetentionPoliciesChannels.PolicyId = RetentionPolicies.Id").
Where(sq.And{
sq.Eq{"RetentionPoliciesChannels.PolicyId": nil},
sq.Eq{"RetentionPoliciesTeams.PolicyId": nil},
}).
Where(sq.Lt{scopedTimeColumn: r.GlobalPolicyEndTime}).
Limit(uint64(r.Limit))
rowsAffected, err := genericRetentionPoliciesDeletion(globalPolicyBuilder, r, s)
if err != nil {
return 0, cursor, err
}
if rowsAffected < r.Limit {
cursor.GlobalPoliciesDone = true
}
totalRowsAffected += rowsAffected
}
return totalRowsAffected, cursor, nil
}
// genericRetentionPoliciesDeletion actually executes the DELETE query using a sq.SelectBuilder
// which selects the rows to delete.
func genericRetentionPoliciesDeletion(
builder sq.SelectBuilder,
r RetentionPolicyBatchDeletionInfo,
s *SqlStore,
) (rowsAffected int64, err error) {
query, args, err := builder.ToSql()
if err != nil {
return 0, errors.Wrap(err, r.Table+"_tosql")
}
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
primaryKeysStr := "(" + strings.Join(r.PrimaryKeys, ",") + ")"
query = `
DELETE FROM ` + r.Table + ` WHERE ` + primaryKeysStr + ` IN (
` + query + `
)`
} else {
// MySQL does not support the LIMIT clause in a subquery with IN
clauses := make([]string, len(r.PrimaryKeys))
for i, key := range r.PrimaryKeys {
clauses[i] = r.Table + "." + key + " = A." + key
}
joinClause := strings.Join(clauses, " AND ")
query = `
DELETE ` + r.Table + ` FROM ` + r.Table + ` INNER JOIN (
` + query + `
) AS A ON ` + joinClause
}
result, err := s.GetMaster().Exec(query, args...)
if err != nil {
return 0, errors.Wrap(err, "failed to delete "+r.Table)
}
rowsAffected, err = result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "failed to get rows affected for "+r.Table)
}
return
}

Просмотреть файл

@@ -132,12 +132,15 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
unreadRepliesQuery := "SELECT COUNT(Posts.Id) From Posts Where Posts.RootId=ThreadMemberships.PostId AND Posts.CreateAt >= ThreadMemberships.LastViewed" unreadRepliesQuery := "SELECT COUNT(Posts.Id) From Posts Where Posts.RootId=ThreadMemberships.PostId AND Posts.CreateAt >= ThreadMemberships.LastViewed"
fetchConditions := sq.And{ fetchConditions := sq.And{
sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}, sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}, sq.Eq{"Channels.TeamId": nil}},
sq.Eq{"ThreadMemberships.UserId": userId}, sq.Eq{"ThreadMemberships.UserId": userId},
sq.Eq{"ThreadMemberships.Following": true}, sq.Eq{"ThreadMemberships.Following": true},
} }
if !opts.Deleted { if !opts.Deleted {
fetchConditions = sq.And{fetchConditions, sq.Eq{"Posts.DeleteAt": 0}} fetchConditions = sq.And{
fetchConditions,
sq.Eq{"COALESCE(Posts.DeleteAt, 0)": 0},
}
} }
pageSize := uint64(30) pageSize := uint64(30)
@@ -217,7 +220,10 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
} }
var threads []*JoinedThread var threads []*JoinedThread
query, args, _ := s.getQueryBuilder(). query, args, _ := s.getQueryBuilder().
Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt, ThreadMemberships.UnreadMentions as UnreadMentions"). Select(`Threads.*,
` + postSliceCoalesceQuery() + `,
ThreadMemberships.LastViewed as LastViewedAt,
ThreadMemberships.UnreadMentions as UnreadMentions`).
From("Threads"). From("Threads").
Column(sq.Alias(sq.Expr(unreadRepliesQuery), "UnreadReplies")). Column(sq.Alias(sq.Expr(unreadRepliesQuery), "UnreadReplies")).
LeftJoin("Posts ON Posts.Id = Threads.PostId"). LeftJoin("Posts ON Posts.Id = Threads.PostId").
@@ -309,7 +315,7 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
UnreadReplies: thread.UnreadReplies, UnreadReplies: thread.UnreadReplies,
UnreadMentions: thread.UnreadMentions, UnreadMentions: thread.UnreadMentions,
Participants: participants, Participants: participants,
Post: &thread.Post, Post: thread.Post.ToNilIfInvalid(),
}) })
} }
@@ -401,7 +407,7 @@ func (s *SqlThreadStore) GetThreadForUser(teamId string, threadMembership *model
UnreadReplies: thread.UnreadReplies, UnreadReplies: thread.UnreadReplies,
UnreadMentions: thread.UnreadMentions, UnreadMentions: thread.UnreadMentions,
Participants: users, Participants: users,
Post: &thread.Post, Post: thread.Post.ToNilIfInvalid(),
} }
return result, nil return result, nil
@@ -691,3 +697,86 @@ func (s *SqlThreadStore) GetPosts(threadId string, since int64) ([]*model.Post,
} }
return result, nil return result, nil
} }
// PermanentDeleteBatchForRetentionPolicies deletes a batch of records which are affected by
// the global or a granular retention policy.
// See `genericPermanentDeleteBatchForRetentionPolicies` for details.
func (s *SqlThreadStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
builder := s.getQueryBuilder().
Select("Threads.PostId").
From("Threads")
return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{
BaseBuilder: builder,
Table: "Threads",
TimeColumn: "LastReplyAt",
PrimaryKeys: []string{"PostId"},
ChannelIDTable: "Threads",
NowMillis: now,
GlobalPolicyEndTime: globalPolicyEndTime,
Limit: limit,
}, s.SqlStore, cursor)
}
// PermanentDeleteBatchThreadMembershipsForRetentionPolicies deletes a batch of records
// which are affected by the global or a granular retention policy.
// See `genericPermanentDeleteBatchForRetentionPolicies` for details.
func (s *SqlThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
builder := s.getQueryBuilder().
Select("ThreadMemberships.PostId").
From("ThreadMemberships").
InnerJoin("Threads ON ThreadMemberships.PostId = Threads.PostId")
return genericPermanentDeleteBatchForRetentionPolicies(RetentionPolicyBatchDeletionInfo{
BaseBuilder: builder,
Table: "ThreadMemberships",
TimeColumn: "LastUpdated",
PrimaryKeys: []string{"PostId"},
ChannelIDTable: "Threads",
NowMillis: now,
GlobalPolicyEndTime: globalPolicyEndTime,
Limit: limit,
}, s.SqlStore, cursor)
}
// DeleteOrphanedRows removes orphaned rows from Threads and ThreadMemberships
func (s *SqlThreadStore) DeleteOrphanedRows(limit int) (deleted int64, err error) {
// We need the extra level of nesting to deal with MySQL's locking
const threadsQuery = `
DELETE FROM Threads WHERE PostId IN (
SELECT * FROM (
SELECT Threads.PostId FROM Threads
LEFT JOIN Channels ON Threads.ChannelId = Channels.Id
WHERE Channels.Id IS NULL
LIMIT :Limit
) AS A
)`
// We only delete a thread membership if the entire thread no longer exists,
// not if the root post has been deleted
const threadMembershipsQuery = `
DELETE FROM ThreadMemberships WHERE PostId IN (
SELECT * FROM (
SELECT ThreadMemberships.PostId FROM ThreadMemberships
LEFT JOIN Threads ON ThreadMemberships.PostId = Threads.PostId
WHERE Threads.PostId IS NULL
LIMIT :Limit
) AS A
)`
props := map[string]interface{}{"Limit": limit}
result, err := s.GetMaster().Exec(threadsQuery, props)
if err != nil {
return
}
rpcDeleted, err := result.RowsAffected()
if err != nil {
return
}
result, err = s.GetMaster().Exec(threadMembershipsQuery, props)
if err != nil {
return
}
rptDeleted, err := result.RowsAffected()
if err != nil {
return
}
deleted = rpcDeleted + rptDeleted
return
}

Просмотреть файл

@@ -90,6 +90,7 @@ type RetentionPolicyStore interface {
GetTeamsCount(policyId string) (int64, error) GetTeamsCount(policyId string) (int64, error)
AddTeams(policyId string, teamIds []string) error AddTeams(policyId string, teamIds []string) error
RemoveTeams(policyId string, teamIds []string) error RemoveTeams(policyId string, teamIds []string) error
DeleteOrphanedRows(limit int) (int64, error)
GetTeamPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForTeam, error) GetTeamPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForTeam, error)
GetTeamPoliciesCountForUser(userID string) (int64, error) GetTeamPoliciesCountForUser(userID string) (int64, error)
GetChannelPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForChannel, error) GetChannelPoliciesForUser(userID string, offset, limit int) ([]*model.RetentionPolicyForChannel, error)
@@ -273,6 +274,8 @@ type ChannelMemberHistoryStore interface {
LogJoinEvent(userID string, channelID string, joinTime int64) error LogJoinEvent(userID string, channelID string, joinTime int64) error
LogLeaveEvent(userID string, channelID string, leaveTime int64) error LogLeaveEvent(userID string, channelID string, leaveTime int64) error
GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error)
PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error)
DeleteOrphanedRows(limit int) (deleted int64, err error)
PermanentDeleteBatch(endTime int64, limit int64) (int64, error) PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
} }
type ThreadStore interface { type ThreadStore interface {
@@ -299,6 +302,9 @@ type ThreadStore interface {
MaintainMembership(userID, postID string, opts ThreadMembershipOpts) (*model.ThreadMembership, error) MaintainMembership(userID, postID string, opts ThreadMembershipOpts) (*model.ThreadMembership, error)
CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error)
UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error
PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error)
PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error)
DeleteOrphanedRows(limit int) (deleted int64, err error)
} }
type PostStore interface { type PostStore interface {
@@ -333,6 +339,8 @@ type PostStore interface {
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
GetPostsByIds(postIds []string) ([]*model.Post, error) GetPostsByIds(postIds []string) ([]*model.Post, error)
GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, error) GetPostsBatchForIndexing(startTime int64, endTime int64, limit int) ([]*model.PostForIndexing, error)
PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error)
DeleteOrphanedRows(limit int) (deleted int64, err error)
PermanentDeleteBatch(endTime int64, limit int64) (int64, error) PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
GetOldest() (*model.Post, error) GetOldest() (*model.Post, error)
GetMaxPostSize() int GetMaxPostSize() int
@@ -572,6 +580,7 @@ type PreferenceStore interface {
DeleteCategory(userID string, category string) error DeleteCategory(userID string, category string) error
DeleteCategoryAndName(category string, name string) error DeleteCategoryAndName(category string, name string) error
PermanentDeleteByUser(userID string) error PermanentDeleteByUser(userID string) error
DeleteOrphanedRows(limit int) (deleted int64, err error)
CleanupFlagsBatch(limit int64) (int64, error) CleanupFlagsBatch(limit int64) (int64, error)
} }
@@ -646,8 +655,9 @@ type ReactionStore interface {
GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error)
GetForPostSince(postId string, since int64, excludeRemoteId string, inclDeleted bool) ([]*model.Reaction, error) GetForPostSince(postId string, since int64, excludeRemoteId string, inclDeleted bool) ([]*model.Reaction, error)
DeleteAllWithEmojiName(emojiName string) error DeleteAllWithEmojiName(emojiName string) error
PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
BulkGetForPosts(postIds []string) ([]*model.Reaction, error) BulkGetForPosts(postIds []string) ([]*model.Reaction, error)
DeleteOrphanedRows(limit int) (int64, error)
PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
} }
type JobStore interface { type JobStore interface {

Просмотреть файл

@@ -21,6 +21,7 @@ func TestChannelMemberHistoryStore(t *testing.T, ss store.Store) {
t.Run("TestGetUsersInChannelAtChannelMemberHistory", func(t *testing.T) { testGetUsersInChannelAtChannelMemberHistory(t, ss) }) t.Run("TestGetUsersInChannelAtChannelMemberHistory", func(t *testing.T) { testGetUsersInChannelAtChannelMemberHistory(t, ss) })
t.Run("TestGetUsersInChannelAtChannelMembers", func(t *testing.T) { testGetUsersInChannelAtChannelMembers(t, ss) }) t.Run("TestGetUsersInChannelAtChannelMembers", func(t *testing.T) { testGetUsersInChannelAtChannelMembers(t, ss) })
t.Run("TestPermanentDeleteBatch", func(t *testing.T) { testPermanentDeleteBatch(t, ss) }) t.Run("TestPermanentDeleteBatch", func(t *testing.T) { testPermanentDeleteBatch(t, ss) })
t.Run("TestPermanentDeleteBatchForRetentionPolicies", func(t *testing.T) { testPermanentDeleteBatchForRetentionPolicies(t, ss) })
} }
func testLogJoinEvent(t *testing.T, ss store.Store) { func testLogJoinEvent(t *testing.T, ss store.Store) {
@@ -199,7 +200,8 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
var tableDataTruncated = false var tableDataTruncated = false
for !tableDataTruncated { for !tableDataTruncated {
var count int64 var count int64
count, err = ss.ChannelMemberHistory().PermanentDeleteBatch(model.GetMillis(), 1000) count, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
0, model.GetMillis(), 1000, model.RetentionPolicyCursor{})
require.NoError(t, err, "Failed to truncate ChannelMemberHistory contents") require.NoError(t, err, "Failed to truncate ChannelMemberHistory contents")
tableDataTruncated = count == int64(0) tableDataTruncated = count == int64(0)
} }
@@ -333,7 +335,8 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
assert.Len(t, channelMembers, 2) assert.Len(t, channelMembers, 2)
// the permanent delete should delete at least one record // the permanent delete should delete at least one record
rowsDeleted, err := ss.ChannelMemberHistory().PermanentDeleteBatch(leaveTime, math.MaxInt64) rowsDeleted, _, err := ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
0, leaveTime+1, math.MaxInt64, model.RetentionPolicyCursor{})
require.NoError(t, err) require.NoError(t, err)
assert.NotEqual(t, int64(0), rowsDeleted) assert.NotEqual(t, int64(0), rowsDeleted)
@@ -343,3 +346,46 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
assert.Len(t, channelMembers, 1) assert.Len(t, channelMembers, 1)
assert.Equal(t, user2.Id, channelMembers[0].UserId) assert.Equal(t, user2.Id, channelMembers[0].UserId)
} }
func testPermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
const limit = 1000
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
})
require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
userID := model.NewId()
joinTime := int64(1000)
leaveTime := int64(1500)
err = ss.ChannelMemberHistory().LogJoinEvent(userID, channel.Id, joinTime)
require.NoError(t, err)
err = ss.ChannelMemberHistory().LogLeaveEvent(userID, channel.Id, leaveTime)
require.NoError(t, err)
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
ChannelIDs: []string{channel.Id},
})
require.NoError(t, err)
nowMillis := leaveTime + *channelPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
result, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime, leaveTime, channel.Id)
require.NoError(t, err)
require.Empty(t, result, "history should have been deleted by channel policy")
}

Просмотреть файл

@@ -1757,7 +1757,8 @@ func testChannelMembersToAdd(t *testing.T, ss store.Store) {
require.Empty(t, channelMembers) require.Empty(t, channelMembers)
// Purging ChannelMemberHistory re-returns the result // Purging ChannelMemberHistory re-returns the result
_, nErr = ss.ChannelMemberHistory().PermanentDeleteBatch(model.GetMillis()+1, 100) _, _, nErr = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
0, model.GetMillis()+1, 100, model.RetentionPolicyCursor{})
require.NoError(t, nErr) require.NoError(t, nErr)
channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false)
require.NoError(t, err) require.NoError(t, err)

Просмотреть файл

@@ -14,6 +14,27 @@ type ChannelMemberHistoryStore struct {
mock.Mock mock.Mock
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *ChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUsersInChannelDuring provides a mock function with given fields: startTime, endTime, channelID // GetUsersInChannelDuring provides a mock function with given fields: startTime, endTime, channelID
func (_m *ChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) { func (_m *ChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) {
ret := _m.Called(startTime, endTime, channelID) ret := _m.Called(startTime, endTime, channelID)
@@ -85,3 +106,31 @@ func (_m *ChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit i
return r0, r1 return r0, r1
} }
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
func (_m *ChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
var r0 int64
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
r0 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r0 = ret.Get(0).(int64)
}
var r1 model.RetentionPolicyCursor
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
r1 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r1 = ret.Get(1).(model.RetentionPolicyCursor)
}
var r2 error
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
r2 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}

Просмотреть файл

@@ -102,6 +102,27 @@ func (_m *PostStore) Delete(postID string, time int64, deleteByID string) error
return r0 return r0
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *PostStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID // Get provides a mock function with given fields: ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID
func (_m *PostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { func (_m *PostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
ret := _m.Called(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) ret := _m.Called(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID)
@@ -714,6 +735,34 @@ func (_m *PostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, er
return r0, r1 return r0, r1
} }
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
func (_m *PostStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
var r0 int64
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
r0 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r0 = ret.Get(0).(int64)
}
var r1 model.RetentionPolicyCursor
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
r1 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r1 = ret.Get(1).(model.RetentionPolicyCursor)
}
var r2 error
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
r2 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PermanentDeleteByChannel provides a mock function with given fields: channelID // PermanentDeleteByChannel provides a mock function with given fields: channelID
func (_m *PostStore) PermanentDeleteByChannel(channelID string) error { func (_m *PostStore) PermanentDeleteByChannel(channelID string) error {
ret := _m.Called(channelID) ret := _m.Called(channelID)

Просмотреть файл

@@ -77,6 +77,27 @@ func (_m *PreferenceStore) DeleteCategoryAndName(category string, name string) e
return r0 return r0
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *PreferenceStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: userID, category, name // Get provides a mock function with given fields: userID, category, name
func (_m *PreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) { func (_m *PreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) {
ret := _m.Called(userID, category, name) ret := _m.Called(userID, category, name)

Просмотреть файл

@@ -74,6 +74,27 @@ func (_m *ReactionStore) DeleteAllWithEmojiName(emojiName string) error {
return r0 return r0
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *ReactionStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetForPost provides a mock function with given fields: postID, allowFromCache // GetForPost provides a mock function with given fields: postID, allowFromCache
func (_m *ReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) { func (_m *ReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) {
ret := _m.Called(postID, allowFromCache) ret := _m.Called(postID, allowFromCache)

Просмотреть файл

@@ -56,6 +56,27 @@ func (_m *RetentionPolicyStore) Delete(id string) error {
return r0 return r0
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *RetentionPolicyStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: id // Get provides a mock function with given fields: id
func (_m *RetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { func (_m *RetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
ret := _m.Called(id) ret := _m.Called(id)

Просмотреть файл

@@ -66,6 +66,27 @@ func (_m *ThreadStore) DeleteMembershipForUser(userId string, postID string) err
return r0 return r0
} }
// DeleteOrphanedRows provides a mock function with given fields: limit
func (_m *ThreadStore) DeleteOrphanedRows(limit int) (int64, error) {
ret := _m.Called(limit)
var r0 int64
if rf, ok := ret.Get(0).(func(int) int64); ok {
r0 = rf(limit)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int) error); ok {
r1 = rf(limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: id // Get provides a mock function with given fields: id
func (_m *ThreadStore) Get(id string) (*model.Thread, error) { func (_m *ThreadStore) Get(id string) (*model.Thread, error) {
ret := _m.Called(id) ret := _m.Called(id)
@@ -292,6 +313,62 @@ func (_m *ThreadStore) MarkAsRead(userID string, threadID string, timestamp int6
return r0 return r0
} }
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
func (_m *ThreadStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
var r0 int64
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
r0 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r0 = ret.Get(0).(int64)
}
var r1 model.RetentionPolicyCursor
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
r1 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r1 = ret.Get(1).(model.RetentionPolicyCursor)
}
var r2 error
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
r2 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PermanentDeleteBatchThreadMembershipsForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
func (_m *ThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
var r0 int64
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
r0 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r0 = ret.Get(0).(int64)
}
var r1 model.RetentionPolicyCursor
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
r1 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r1 = ret.Get(1).(model.RetentionPolicyCursor)
}
var r2 error
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
r2 = rf(now, globalPolicyEndTime, limit, cursor)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Save provides a mock function with given fields: thread // Save provides a mock function with given fields: thread
func (_m *ThreadStore) Save(thread *model.Thread) (*model.Thread, error) { func (_m *ThreadStore) Save(thread *model.Thread) (*model.Thread, error) {
ret := _m.Called(thread) ret := _m.Called(thread)

Просмотреть файл

@@ -2586,16 +2586,31 @@ func testPostStoreGetPostsBatchForIndexing(t *testing.T, ss store.Store) {
} }
func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) { func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
})
require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
o1 := &model.Post{} o1 := &model.Post{}
o1.ChannelId = model.NewId() o1.ChannelId = channel.Id
o1.UserId = model.NewId() o1.UserId = model.NewId()
o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA" o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
o1.CreateAt = 1000 o1.CreateAt = 1000
o1, err := ss.Post().Save(o1) o1, err = ss.Post().Save(o1)
require.NoError(t, err) require.NoError(t, err)
o2 := &model.Post{} o2 := &model.Post{}
o2.ChannelId = model.NewId() o2.ChannelId = channel.Id
o2.UserId = model.NewId() o2.UserId = model.NewId()
o2.Message = "zz" + model.NewId() + "AAAAAAAAAAA" o2.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
o2.CreateAt = 1000 o2.CreateAt = 1000
@@ -2603,14 +2618,14 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
require.NoError(t, err) require.NoError(t, err)
o3 := &model.Post{} o3 := &model.Post{}
o3.ChannelId = model.NewId() o3.ChannelId = channel.Id
o3.UserId = model.NewId() o3.UserId = model.NewId()
o3.Message = "zz" + model.NewId() + "AAAAAAAAAAA" o3.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
o3.CreateAt = 100000 o3.CreateAt = 100000
o3, err = ss.Post().Save(o3) o3, err = ss.Post().Save(o3)
require.NoError(t, err) require.NoError(t, err)
_, err = ss.Post().PermanentDeleteBatch(2000, 1000) _, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err) require.NoError(t, err)
_, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "")
@@ -2620,7 +2635,160 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
require.Error(t, err, "Should have not found post 2 after purge") require.Error(t, err, "Should have not found post 2 after purge")
_, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") _, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "")
require.NoError(t, err, "Should have not found post 3 after purge") require.NoError(t, err, "Should have found post 3 after purge")
t.Run("with pagination", func(t *testing.T) {
for i := 0; i < 3; i++ {
_, err = ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
Message: "message",
CreateAt: 1,
})
require.NoError(t, err)
}
cursor := model.RetentionPolicyCursor{}
deleted, cursor, err := ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2, 2, cursor)
require.NoError(t, err)
require.Equal(t, int64(2), deleted)
deleted, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2, 2, cursor)
require.NoError(t, err)
require.Equal(t, int64(1), deleted)
})
t.Run("with data retention policies", func(t *testing.T) {
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
ChannelIDs: []string{channel.Id},
})
require.NoError(t, err2)
post := &model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
Message: "message",
CreateAt: 1,
}
post, err2 = ss.Post().Save(post)
require.NoError(t, err2)
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err2)
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
require.NoError(t, err2, "global policy should have been ignored due to granular policy")
nowMillis := post.CreateAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err2)
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
require.Error(t, err2, "post should have been deleted by channel policy")
// Create a team policy which is stricter than the channel policy
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(20),
},
TeamIDs: []string{team.Id},
})
require.NoError(t, err2)
post.Id = ""
post, err2 = ss.Post().Save(post)
require.NoError(t, err2)
nowMillis = post.CreateAt + *teamPolicy.PostDuration*24*60*60*1000 + 1
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err2)
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
require.NoError(t, err2, "channel policy should have overridden team policy")
// Delete channel policy and re-run team policy
err2 = ss.RetentionPolicy().RemoveChannels(channelPolicy.ID, []string{channel.Id})
require.NoError(t, err2)
err2 = ss.RetentionPolicy().Delete(channelPolicy.ID)
require.NoError(t, err2)
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err2)
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
require.Error(t, err2, "post should have been deleted by team policy")
err2 = ss.RetentionPolicy().RemoveTeams(teamPolicy.ID, []string{team.Id})
require.NoError(t, err2)
err2 = ss.RetentionPolicy().Delete(teamPolicy.ID)
require.NoError(t, err2)
})
t.Run("with channel, team and global policies", func(t *testing.T) {
c1 := &model.Channel{}
c1.TeamId = model.NewId()
c1.DisplayName = "Channel1"
c1.Name = "zz" + model.NewId() + "b"
c1.Type = model.CHANNEL_OPEN
c1, _ = ss.Channel().Save(c1, -1)
c2 := &model.Channel{}
c2.TeamId = model.NewId()
c2.DisplayName = "Channel2"
c2.Name = "zz" + model.NewId() + "b"
c2.Type = model.CHANNEL_OPEN
c2, _ = ss.Channel().Save(c2, -1)
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
ChannelIDs: []string{c1.Id},
})
require.NoError(t, err2)
defer ss.RetentionPolicy().Delete(channelPolicy.ID)
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
TeamIDs: []string{team.Id},
})
require.NoError(t, err2)
defer ss.RetentionPolicy().Delete(teamPolicy.ID)
// This one should be deleted by the channel policy
_, err2 = ss.Post().Save(&model.Post{
ChannelId: c1.Id,
UserId: model.NewId(),
Message: "message",
CreateAt: 1,
})
require.NoError(t, err2)
// This one, by the team policy
_, err2 = ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
Message: "message",
CreateAt: 1,
})
require.NoError(t, err2)
// This one, by the global policy
_, err2 = ss.Post().Save(&model.Post{
ChannelId: c2.Id,
UserId: model.NewId(),
Message: "message",
CreateAt: 1,
})
require.NoError(t, err2)
nowMillis := int64(1 + 30*24*60*60*1000 + 1)
deleted, _, err2 := ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 2, 1000, model.RetentionPolicyCursor{})
require.NoError(t, err2)
require.Equal(t, int64(3), deleted)
})
} }
func testPostStoreGetOldest(t *testing.T, ss store.Store) { func testPostStoreGetOldest(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -22,7 +22,7 @@ func TestPreferenceStore(t *testing.T, ss store.Store) {
t.Run("PreferenceDelete", func(t *testing.T) { testPreferenceDelete(t, ss) }) t.Run("PreferenceDelete", func(t *testing.T) { testPreferenceDelete(t, ss) })
t.Run("PreferenceDeleteCategory", func(t *testing.T) { testPreferenceDeleteCategory(t, ss) }) t.Run("PreferenceDeleteCategory", func(t *testing.T) { testPreferenceDeleteCategory(t, ss) })
t.Run("PreferenceDeleteCategoryAndName", func(t *testing.T) { testPreferenceDeleteCategoryAndName(t, ss) }) t.Run("PreferenceDeleteCategoryAndName", func(t *testing.T) { testPreferenceDeleteCategoryAndName(t, ss) })
t.Run("PreferenceCleanupFlagsBatch", func(t *testing.T) { testPreferenceCleanupFlagsBatch(t, ss) }) t.Run("PreferenceDeleteOrphanedRows", func(t *testing.T) { testPreferenceDeleteOrphanedRows(t, ss) })
} }
func testPreferenceSave(t *testing.T, ss store.Store) { func testPreferenceSave(t *testing.T, ss store.Store) {
@@ -330,44 +330,66 @@ func testPreferenceDeleteCategoryAndName(t *testing.T, ss store.Store) {
assert.Empty(t, preferences, "should've returned no preference") assert.Empty(t, preferences, "should've returned no preference")
} }
func testPreferenceCleanupFlagsBatch(t *testing.T, ss store.Store) { func testPreferenceDeleteOrphanedRows(t *testing.T, ss store.Store) {
const limit = 1000
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
})
require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
category := model.PREFERENCE_CATEGORY_FLAGGED_POST category := model.PREFERENCE_CATEGORY_FLAGGED_POST
userId := model.NewId() userId := model.NewId()
o1 := &model.Post{} olderPost, err := ss.Post().Save(&model.Post{
o1.ChannelId = model.NewId() ChannelId: channel.Id,
o1.UserId = userId UserId: userId,
o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA" Message: "message",
o1.CreateAt = 1000 CreateAt: 1000,
o1, err := ss.Post().Save(o1) })
require.NoError(t, err)
newerPost, err := ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: userId,
Message: "message",
CreateAt: 3000,
})
require.NoError(t, err) require.NoError(t, err)
preference1 := model.Preference{ preference1 := model.Preference{
UserId: userId, UserId: userId,
Category: category, Category: category,
Name: o1.Id, Name: olderPost.Id,
Value: "true", Value: "true",
} }
preference2 := model.Preference{ preference2 := model.Preference{
UserId: userId, UserId: userId,
Category: category, Category: category,
Name: model.NewId(), Name: newerPost.Id,
Value: "true", Value: "true",
} }
nErr := ss.Preference().Save(&model.Preferences{preference1, preference2}) nErr := ss.Preference().Save(&model.Preferences{preference1, preference2})
require.NoError(t, nErr) require.NoError(t, nErr)
_, nErr = ss.Preference().CleanupFlagsBatch(-1) _, _, nErr = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, limit, model.RetentionPolicyCursor{})
require.Error(t, nErr) assert.NoError(t, nErr)
_, nErr = ss.Preference().CleanupFlagsBatch(10000) _, nErr = ss.Preference().DeleteOrphanedRows(limit)
assert.NoError(t, nErr) assert.NoError(t, nErr)
_, nErr = ss.Preference().Get(userId, category, preference1.Name) _, nErr = ss.Preference().Get(userId, category, preference1.Name)
assert.NoError(t, nErr) assert.Error(t, nErr, "older preference should have been deleted")
_, nErr = ss.Preference().Get(userId, category, preference2.Name) _, nErr = ss.Preference().Get(userId, category, preference2.Name)
assert.Error(t, nErr) assert.NoError(t, nErr, "newer preference should not have been deleted")
} }

Просмотреть файл

@@ -522,61 +522,72 @@ func testReactionDeleteAllWithEmojiName(t *testing.T, ss store.Store, s SqlStore
} }
func testReactionStorePermanentDeleteBatch(t *testing.T, ss store.Store) { func testReactionStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
post, err1 := ss.Post().Save(&model.Post{ const limit = 1000
ChannelId: model.NewId(), team, err := ss.Team().Save(&model.Team{
UserId: model.NewId(), DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
}) })
require.NoError(t, err1) require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
olderPost, err := ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
CreateAt: 1000,
})
require.NoError(t, err)
newerPost, err := ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
CreateAt: 3000,
})
require.NoError(t, err)
// Reactions will be deleted based on the timestamp of their post. So the time at
// which a reaction was created doesn't matter.
reactions := []*model.Reaction{ reactions := []*model.Reaction{
{ {
UserId: model.NewId(), UserId: model.NewId(),
PostId: post.Id, PostId: olderPost.Id,
EmojiName: "sad", EmojiName: "sad",
CreateAt: 1000,
}, },
{ {
UserId: model.NewId(), UserId: model.NewId(),
PostId: post.Id, PostId: olderPost.Id,
EmojiName: "sad", EmojiName: "sad",
CreateAt: 1500,
}, },
{ {
UserId: model.NewId(), UserId: model.NewId(),
PostId: post.Id, PostId: newerPost.Id,
EmojiName: "sad", EmojiName: "smile",
CreateAt: 2000,
},
{
UserId: model.NewId(),
PostId: post.Id,
EmojiName: "sad",
CreateAt: 2000,
}, },
} }
// Need to hang on to a reaction to delete later in order to clear the cache, as "allowFromCache" isn't honoured any more.
var lastReaction *model.Reaction
for _, reaction := range reactions { for _, reaction := range reactions {
var nErr error _, err = ss.Reaction().Save(reaction)
lastReaction, nErr = ss.Reaction().Save(reaction) require.NoError(t, err)
require.NoError(t, nErr)
} }
returned, err := ss.Reaction().GetForPost(post.Id, false) _, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
require.Len(t, returned, 4, "expected 4 reactions")
_, err = ss.Reaction().PermanentDeleteBatch(1800, 1000)
require.NoError(t, err) require.NoError(t, err)
// This is to force a clear of the cache. _, err = ss.Reaction().DeleteOrphanedRows(limit)
_, err = ss.Reaction().Delete(lastReaction)
require.NoError(t, err) require.NoError(t, err)
returned, err = ss.Reaction().GetForPost(post.Id, false) returned, err := ss.Reaction().GetForPost(olderPost.Id, false)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, returned, 1, "expected 1 reaction. Got: %v", len(returned)) require.Len(t, returned, 0, "reactions for older post should have been deleted")
returned, err = ss.Reaction().GetForPost(newerPost.Id, false)
require.NoError(t, err)
require.Len(t, returned, 1, "reactions for newer post should not have been deleted")
} }
func testReactionBulkGetForPosts(t *testing.T, ss store.Store) { func testReactionBulkGetForPosts(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -25,6 +25,7 @@ func TestRetentionPolicyStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetTeams", func(t *testing.T) { testRetentionPolicyStoreGetTeams(t, ss, s) }) t.Run("GetTeams", func(t *testing.T) { testRetentionPolicyStoreGetTeams(t, ss, s) })
t.Run("AddTeams", func(t *testing.T) { testRetentionPolicyStoreAddTeams(t, ss, s) }) t.Run("AddTeams", func(t *testing.T) { testRetentionPolicyStoreAddTeams(t, ss, s) })
t.Run("RemoveTeams", func(t *testing.T) { testRetentionPolicyStoreRemoveTeams(t, ss, s) }) t.Run("RemoveTeams", func(t *testing.T) { testRetentionPolicyStoreRemoveTeams(t, ss, s) })
t.Run("RemoveOrphanedRows", func(t *testing.T) { testRetentionPolicyStoreRemoveOrphanedRows(t, ss, s) })
t.Run("GetPoliciesForUser", func(t *testing.T) { testRetentionPolicyStoreGetPoliciesForUser(t, ss, s) }) t.Run("GetPoliciesForUser", func(t *testing.T) { testRetentionPolicyStoreGetPoliciesForUser(t, ss, s) })
} }
@@ -656,3 +657,21 @@ func testRetentionPolicyStoreGetPoliciesForUser(t *testing.T, ss store.Store, s
require.Equal(t, int64(len(channelIDs)), count) require.Equal(t, int64(len(channelIDs)), count)
}) })
} }
func testRetentionPolicyStoreRemoveOrphanedRows(t *testing.T, ss store.Store, s SqlStore) {
teamID := createTeamsForRetentionPolicy(t, ss, 1)[0]
channelID := createChannelsForRetentionPolicy(t, ss, teamID, 1)[0]
policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1",
[]string{teamID}, []string{channelID})
err := ss.Channel().PermanentDelete(channelID)
require.NoError(t, err)
err = ss.Team().PermanentDelete(teamID)
require.NoError(t, err)
_, err = ss.RetentionPolicy().DeleteOrphanedRows(1000)
require.NoError(t, err)
policy.ChannelIDs = make([]string, 0)
policy.TeamIDs = make([]string, 0)
checkRetentionPolicyLikeThisExists(t, ss, policy)
}

Просмотреть файл

@@ -17,6 +17,12 @@ import (
func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) { func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("ThreadStorePopulation", func(t *testing.T) { testThreadStorePopulation(t, ss) }) t.Run("ThreadStorePopulation", func(t *testing.T) { testThreadStorePopulation(t, ss) })
t.Run("ThreadStorePermanentDeleteBatchForRetentionPolicies", func(t *testing.T) {
testThreadStorePermanentDeleteBatchForRetentionPolicies(t, ss)
})
t.Run("ThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies", func(t *testing.T) {
testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t, ss)
})
} }
func testThreadStorePopulation(t *testing.T, ss store.Store) { func testThreadStorePopulation(t *testing.T, ss store.Store) {
@@ -411,3 +417,185 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
require.Equal(t, int64(0), th.UnreadReplies) require.Equal(t, int64(0), th.UnreadReplies)
}) })
} }
func threadStoreCreateReply(t *testing.T, ss store.Store, channelID, postID string, createAt int64) *model.Post {
reply, err := ss.Post().Save(&model.Post{
ChannelId: channelID,
UserId: model.NewId(),
CreateAt: createAt,
RootId: postID,
ParentId: postID,
})
require.NoError(t, err)
return reply
}
func testThreadStorePermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
const limit = 1000
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
})
require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
post, err := ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
})
require.NoError(t, err)
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
thread, err := ss.Thread().Get(post.Id)
require.NoError(t, err)
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
ChannelIDs: []string{channel.Id},
})
require.NoError(t, err)
nowMillis := thread.LastReplyAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().Get(post.Id)
require.Error(t, err, "thread should have been deleted by channel policy")
// create a new thread
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
thread, err = ss.Thread().Get(post.Id)
require.NoError(t, err)
// Create a team policy which is stricter than the channel policy
teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(20),
},
TeamIDs: []string{team.Id},
})
require.NoError(t, err)
nowMillis = thread.LastReplyAt + *teamPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().Get(post.Id)
require.NoError(t, err, "channel policy should have overridden team policy")
// Delete channel policy and re-run team policy
err = ss.RetentionPolicy().Delete(channelPolicy.ID)
require.NoError(t, err)
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().Get(post.Id)
require.Error(t, err, "thread should have been deleted by team policy")
}
func testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t *testing.T, ss store.Store) {
const limit = 1000
userID := model.NewId()
createThreadMembership := func(userID, postID string) *model.ThreadMembership {
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
UpdateFollowing: true,
UpdateViewedTimestamp: false,
UpdateParticipants: false,
}
_, err := ss.Thread().MaintainMembership(userID, postID, opts)
require.NoError(t, err)
threadMembership, err := ss.Thread().GetMembershipForUser(userID, postID)
require.NoError(t, err)
return threadMembership
}
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TEAM_OPEN,
})
require.NoError(t, err)
channel, err := ss.Channel().Save(&model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel" + model.NewId(),
Type: model.CHANNEL_OPEN,
}, -1)
require.NoError(t, err)
post, err := ss.Post().Save(&model.Post{
ChannelId: channel.Id,
UserId: model.NewId(),
})
require.NoError(t, err)
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
threadMembership := createThreadMembership(userID, post.Id)
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(30),
},
ChannelIDs: []string{channel.Id},
})
require.NoError(t, err)
nowMillis := threadMembership.LastUpdated + *channelPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
require.Error(t, err, "thread membership should have been deleted by channel policy")
// create a new thread membership
threadMembership = createThreadMembership(userID, post.Id)
// Create a team policy which is stricter than the channel policy
teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
RetentionPolicy: model.RetentionPolicy{
DisplayName: "DisplayName",
PostDuration: model.NewInt64(20),
},
TeamIDs: []string{team.Id},
})
require.NoError(t, err)
nowMillis = threadMembership.LastUpdated + *teamPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
require.NoError(t, err, "channel policy should have overridden team policy")
// Delete channel policy and re-run team policy
err = ss.RetentionPolicy().Delete(channelPolicy.ID)
require.NoError(t, err)
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err)
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
require.Error(t, err, "thread membership should have been deleted by team policy")
// create a new thread membership
threadMembership = createThreadMembership(userID, post.Id)
// Delete team policy and thread
err = ss.RetentionPolicy().Delete(teamPolicy.ID)
require.NoError(t, err)
err = ss.Thread().Delete(post.Id)
require.NoError(t, err)
deleted, err := ss.Thread().DeleteOrphanedRows(1000)
require.NoError(t, err)
require.NotZero(t, deleted)
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
require.Error(t, err, "thread membership should have been deleted because thread no longer exists")
}

Просмотреть файл

@@ -2158,6 +2158,22 @@ func (s *TimerLayerChannelStore) UserBelongsToChannels(userID string, channelIds
return result, err return result, err
} }
func (s *TimerLayerChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.ChannelMemberHistoryStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelMemberHistoryStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) { func (s *TimerLayerChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -2222,6 +2238,22 @@ func (s *TimerLayerChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64
return result, err return result, err
} }
func (s *TimerLayerChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
start := timemodule.Now()
result, resultVar1, err := s.ChannelMemberHistoryStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelMemberHistoryStore.PermanentDeleteBatchForRetentionPolicies", success, elapsed)
}
return result, resultVar1, err
}
func (s *TimerLayerClusterDiscoveryStore) Cleanup() error { func (s *TimerLayerClusterDiscoveryStore) Cleanup() error {
start := timemodule.Now() start := timemodule.Now()
@@ -4586,6 +4618,22 @@ func (s *TimerLayerPostStore) Delete(postID string, time int64, deleteByID strin
return err return err
} }
func (s *TimerLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.PostStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { func (s *TimerLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -5033,6 +5081,22 @@ func (s *TimerLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (
return result, err return result, err
} }
func (s *TimerLayerPostStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
start := timemodule.Now()
result, resultVar1, err := s.PostStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.PermanentDeleteBatchForRetentionPolicies", success, elapsed)
}
return result, resultVar1, err
}
func (s *TimerLayerPostStore) PermanentDeleteByChannel(channelID string) error { func (s *TimerLayerPostStore) PermanentDeleteByChannel(channelID string) error {
start := timemodule.Now() start := timemodule.Now()
@@ -5209,6 +5273,22 @@ func (s *TimerLayerPreferenceStore) DeleteCategoryAndName(category string, name
return err return err
} }
func (s *TimerLayerPreferenceStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.PreferenceStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PreferenceStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) { func (s *TimerLayerPreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -5401,6 +5481,22 @@ func (s *TimerLayerReactionStore) DeleteAllWithEmojiName(emojiName string) error
return err return err
} }
func (s *TimerLayerReactionStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.ReactionStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ReactionStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) { func (s *TimerLayerReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -5625,6 +5721,22 @@ func (s *TimerLayerRetentionPolicyStore) Delete(id string) error {
return err return err
} }
func (s *TimerLayerRetentionPolicyStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.RetentionPolicyStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("RetentionPolicyStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) { func (s *TimerLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -7942,6 +8054,22 @@ func (s *TimerLayerThreadStore) DeleteMembershipForUser(userId string, postID st
return err return err
} }
func (s *TimerLayerThreadStore) DeleteOrphanedRows(limit int) (int64, error) {
start := timemodule.Now()
result, err := s.ThreadStore.DeleteOrphanedRows(limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.DeleteOrphanedRows", success, elapsed)
}
return result, err
}
func (s *TimerLayerThreadStore) Get(id string) (*model.Thread, error) { func (s *TimerLayerThreadStore) Get(id string) (*model.Thread, error) {
start := timemodule.Now() start := timemodule.Now()
@@ -8118,6 +8246,38 @@ func (s *TimerLayerThreadStore) MarkAsRead(userID string, threadID string, times
return err return err
} }
func (s *TimerLayerThreadStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
start := timemodule.Now()
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.PermanentDeleteBatchForRetentionPolicies", success, elapsed)
}
return result, resultVar1, err
}
func (s *TimerLayerThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
start := timemodule.Now()
result, resultVar1, err := s.ThreadStore.PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now, globalPolicyEndTime, limit, cursor)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.PermanentDeleteBatchThreadMembershipsForRetentionPolicies", success, elapsed)
}
return result, resultVar1, err
}
func (s *TimerLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) { func (s *TimerLayerThreadStore) Save(thread *model.Thread) (*model.Thread, error) {
start := timemodule.Now() start := timemodule.Now()