From f92b99260763b70d2d8f39a88f6732eab71aca75 Mon Sep 17 00:00:00 2001 From: Brandur Date: Thu, 26 Dec 2024 17:23:14 -0700 Subject: [PATCH] Add missing doc for `JobRow.UniqueStates` + reveal `rivertype.UniqueOptsByStateDefault()` Two small ones related to unique job insertion: * Adding a missing doc comment on `JobRow.UniqueStates`. * As discussed in #704, reveal a way for end users to get the default set of unique job states via `rivertype.UniqueOptsByStateDefault()`. Similar to `rivertype.JobStates()`, this is revealed as a function so that it's not possible to accidentally mutate a global slice. (Go: readonly variables would sure be pretty nice.) --- insert_opts.go | 4 ++++ internal/dbunique/db_unique.go | 18 ++++-------------- internal/dbunique/db_unique_test.go | 10 +++++----- rivertype/river_type.go | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/insert_opts.go b/insert_opts.go index 0efba19a..d4982cc1 100644 --- a/insert_opts.go +++ b/insert_opts.go @@ -145,6 +145,10 @@ type UniqueOpts struct { // // ByState: []rivertype.JobState{rivertype.JobStateAvailable, rivertype.JobStateCompleted, rivertype.JobStatePending, rivertype.JobStateRunning, rivertype.JobStateRetryable, rivertype.JobStateScheduled} // + // Or more succinctly: + // + // ByState: rivertype.UniqueOptsByStateDefault() + // // With this setting, any jobs of the same kind that have been completed or // discarded, but not yet cleaned out by the system, will still prevent a // duplicate unique job from being inserted. For example, with the default diff --git a/internal/dbunique/db_unique.go b/internal/dbunique/db_unique.go index e4ccd010..bd503d39 100644 --- a/internal/dbunique/db_unique.go +++ b/internal/dbunique/db_unique.go @@ -14,19 +14,9 @@ import ( "github.com/riverqueue/river/rivertype" ) -// When a job has specified unique options, but has not set the ByState -// parameter explicitly, this is the set of default states that are used to -// determine uniqueness. So for example, a new unique job may be inserted even -// if another job already exists, as long as that other job is set `cancelled` -// or `discarded`. -var defaultUniqueStates = []rivertype.JobState{ //nolint:gochecknoglobals - rivertype.JobStateAvailable, - rivertype.JobStateCompleted, - rivertype.JobStatePending, - rivertype.JobStateRetryable, - rivertype.JobStateRunning, - rivertype.JobStateScheduled, -} +// Default job states for UniqueOpts.ByState. Stored here to a variable so we +// don't have to reallocate a slice over and over again. +var uniqueOptsByStateDefault = rivertype.UniqueOptsByStateDefault() //nolint:gochecknoglobals var jobStateBitPositions = map[rivertype.JobState]uint{ //nolint:gochecknoglobals rivertype.JobStateAvailable: 7, @@ -56,7 +46,7 @@ func (o *UniqueOpts) IsEmpty() bool { } func (o *UniqueOpts) StateBitmask() byte { - states := defaultUniqueStates + states := uniqueOptsByStateDefault if len(o.ByState) > 0 { states = o.ByState } diff --git a/internal/dbunique/db_unique_test.go b/internal/dbunique/db_unique_test.go index e2ad3037..e9a2404a 100644 --- a/internal/dbunique/db_unique_test.go +++ b/internal/dbunique/db_unique_test.go @@ -223,7 +223,7 @@ func TestUniqueKey(t *testing.T) { encodedArgs, err := json.Marshal(args) require.NoError(t, err) - states := defaultUniqueStates + states := uniqueOptsByStateDefault if len(tt.uniqueOpts.ByState) > 0 { states = tt.uniqueOpts.ByState } @@ -258,9 +258,9 @@ func TestUniqueKey(t *testing.T) { func TestDefaultUniqueStatesSorted(t *testing.T) { t.Parallel() - states := slices.Clone(defaultUniqueStates) + states := slices.Clone(uniqueOptsByStateDefault) slices.Sort(states) - require.Equal(t, states, defaultUniqueStates, "Default unique states should be sorted") + require.Equal(t, states, uniqueOptsByStateDefault, "Default unique states should be sorted") } func TestUniqueOptsIsEmpty(t *testing.T) { @@ -289,7 +289,7 @@ func TestUniqueOptsStateBitmask(t *testing.T) { t.Parallel() emptyOpts := &UniqueOpts{} - require.Equal(t, UniqueStatesToBitmask(defaultUniqueStates), emptyOpts.StateBitmask(), "Empty unique options should have default bitmask") + require.Equal(t, UniqueStatesToBitmask(uniqueOptsByStateDefault), emptyOpts.StateBitmask(), "Empty unique options should have default bitmask") otherStates := []rivertype.JobState{rivertype.JobStateAvailable, rivertype.JobStateCompleted} nonEmptyOpts := &UniqueOpts{ @@ -301,7 +301,7 @@ func TestUniqueOptsStateBitmask(t *testing.T) { func TestUniqueStatesToBitmask(t *testing.T) { t.Parallel() - bitmask := UniqueStatesToBitmask(defaultUniqueStates) + bitmask := UniqueStatesToBitmask(uniqueOptsByStateDefault) require.Equal(t, byte(0b11110101), bitmask, "Default unique states should be all set except cancelled and discarded") for state, position := range jobStateBitPositions { diff --git a/rivertype/river_type.go b/rivertype/river_type.go index ebe93e7b..525605ee 100644 --- a/rivertype/river_type.go +++ b/rivertype/river_type.go @@ -129,6 +129,9 @@ type JobRow struct { // opts configuration. UniqueKey []byte + // UniqueStates is the set of states where uniqueness is enforced for this + // job. Equivalent to the default set of unique states unless + // UniqueOpts.ByState was assigned a custom value. UniqueStates []JobState } @@ -303,3 +306,19 @@ type Queue struct { // deleted from the table by a maintenance process. UpdatedAt time.Time } + +// UniqueOptsByStateDefault is the set of job states that are used to determine +// uniqueness unless unique job states have been overridden with +// UniqueOpts.ByState. So for example, with this default set a new unique job +// may be inserted even if another job already exists, as long as that other job +// is set `cancelled` or `discarded`. +func UniqueOptsByStateDefault() []JobState { + return []JobState{ + JobStateAvailable, + JobStateCompleted, + JobStatePending, + JobStateRetryable, + JobStateRunning, + JobStateScheduled, + } +}