diff --git a/temporal/Taskfile.yml b/temporal/Taskfile.yml index 3303a904..9d617e94 100644 --- a/temporal/Taskfile.yml +++ b/temporal/Taskfile.yml @@ -38,4 +38,4 @@ tasks: status: - type mockery cmds: - - go install github.com/vektra/mockery/v2@v2.38.0 + - go install github.com/vektra/mockery/v2@v2.53.6 diff --git a/temporal/internal/driver/redisv9/keyvalue.go b/temporal/internal/driver/redisv9/keyvalue.go index ead0d6f6..4d4b9fd4 100644 --- a/temporal/internal/driver/redisv9/keyvalue.go +++ b/temporal/internal/driver/redisv9/keyvalue.go @@ -472,6 +472,14 @@ func (r *RedisV9) SetIfNotExist(ctx context.Context, key, value string, expirati return res.Val(), nil } +func (r *RedisV9) SetIfExist(ctx context.Context, key, value string, expiration time.Duration) (bool, error) { + if key == "" { + return false, temperr.KeyEmpty + } + + return r.client.SetXX(ctx, key, value, expiration).Result() +} + func fetchKeysWithCursor(ctx context.Context, client redis.UniversalClient, pattern string, diff --git a/temporal/keyvalue/keyvalue_test.go b/temporal/keyvalue/keyvalue_test.go index 0e2c7d3a..305fcc32 100644 --- a/temporal/keyvalue/keyvalue_test.go +++ b/temporal/keyvalue/keyvalue_test.go @@ -14,6 +14,7 @@ import ( "github.com/TykTechnologies/storage/temporal/model" "github.com/TykTechnologies/storage/temporal/temperr" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestKeyValue_Set(t *testing.T) { @@ -1351,3 +1352,51 @@ func TestKeyValue_SetIfNotExist(t *testing.T) { } } } + +func TestKeyValue_SetIfExist(t *testing.T) { + connectors := testutil.TestConnectors(t) + defer testutil.CloseConnectors(t, connectors) + + for _, connector := range connectors { + t.Run(connector.Type(), func(t *testing.T) { + ctx := context.Background() + kv, err := NewKeyValue(connector) + require.Nil(t, err) + + flusher, err := flusher.NewFlusher(connector) + assert.Nil(t, err) + defer assert.Nil(t, flusher.FlushAll(ctx)) + + t.Run("does not set key if key does not exist", func(t *testing.T) { + const key = "non_existent_key" + ok, err := kv.SetIfExist(t.Context(), key, "value", 0) + assert.False(t, ok) + assert.NoError(t, err) + + _, err = kv.Get(t.Context(), key) + assert.ErrorIs(t, err, temperr.KeyNotFound) + }) + + t.Run("overrides key of already exists", func(t *testing.T) { + const key = "existent_key" + + err := kv.Set(t.Context(), key, "value1", 0) + assert.NoError(t, err) + + ok, err := kv.SetIfExist(t.Context(), key, "value2", 0) + assert.True(t, ok) + assert.NoError(t, err) + + v, err := kv.Get(t.Context(), key) + assert.Equal(t, "value2", v) + assert.NoError(t, err) + }) + + t.Run("returns err if empty key was given", func(t *testing.T) { + ok, err := kv.SetIfExist(t.Context(), "", "1", 0) + assert.False(t, ok) + assert.ErrorIs(t, err, temperr.KeyEmpty) + }) + }) + } +} diff --git a/temporal/model/types.go b/temporal/model/types.go index 95697508..bf579b5e 100644 --- a/temporal/model/types.go +++ b/temporal/model/types.go @@ -58,6 +58,9 @@ type KeyValue interface { // SetIfNotExist sets the string value of a key if the key does not exist. // Returns true if the key was set, false otherwise. SetIfNotExist(ctx context.Context, key, value string, expiration time.Duration) (bool, error) + // SetIfExist sets the string value of a key if the key exists. + // Returns true if the key was set, false otherwise. + SetIfExist(ctx context.Context, key, value string, expiration time.Duration) (bool, error) // Delete removes the specified keys Delete(ctx context.Context, key string) error // Increment atomically increments the integer value of a key by one diff --git a/temporal/tempmocks/connector.go b/temporal/tempmocks/connector.go index 6484af5d..b85e7448 100644 --- a/temporal/tempmocks/connector.go +++ b/temporal/tempmocks/connector.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks @@ -67,7 +67,7 @@ func (_m *Connector) Ping(_a0 context.Context) error { return r0 } -// Type provides a mock function with given fields: +// Type provides a mock function with no fields func (_m *Connector) Type() string { ret := _m.Called() diff --git a/temporal/tempmocks/flusher.go b/temporal/tempmocks/flusher.go index ec1507a4..88a3ca09 100644 --- a/temporal/tempmocks/flusher.go +++ b/temporal/tempmocks/flusher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/key_value.go b/temporal/tempmocks/key_value.go index 10ce09ae..1dc63ba2 100644 --- a/temporal/tempmocks/key_value.go +++ b/temporal/tempmocks/key_value.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks @@ -373,6 +373,34 @@ func (_m *KeyValue) Set(ctx context.Context, key string, value string, ttl time. return r0 } +// SetIfExist provides a mock function with given fields: ctx, key, value, expiration +func (_m *KeyValue) SetIfExist(ctx context.Context, key string, value string, expiration time.Duration) (bool, error) { + ret := _m.Called(ctx, key, value, expiration) + + if len(ret) == 0 { + panic("no return value specified for SetIfExist") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, time.Duration) (bool, error)); ok { + return rf(ctx, key, value, expiration) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, time.Duration) bool); ok { + r0 = rf(ctx, key, value, expiration) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, time.Duration) error); ok { + r1 = rf(ctx, key, value, expiration) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // SetIfNotExist provides a mock function with given fields: ctx, key, value, expiration func (_m *KeyValue) SetIfNotExist(ctx context.Context, key string, value string, expiration time.Duration) (bool, error) { ret := _m.Called(ctx, key, value, expiration) diff --git a/temporal/tempmocks/list.go b/temporal/tempmocks/list.go index c881bb95..ed564452 100644 --- a/temporal/tempmocks/list.go +++ b/temporal/tempmocks/list.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/message.go b/temporal/tempmocks/message.go index b35b6c57..3239f314 100644 --- a/temporal/tempmocks/message.go +++ b/temporal/tempmocks/message.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks @@ -9,7 +9,7 @@ type Message struct { mock.Mock } -// Channel provides a mock function with given fields: +// Channel provides a mock function with no fields func (_m *Message) Channel() (string, error) { ret := _m.Called() @@ -37,7 +37,7 @@ func (_m *Message) Channel() (string, error) { return r0, r1 } -// Payload provides a mock function with given fields: +// Payload provides a mock function with no fields func (_m *Message) Payload() (string, error) { ret := _m.Called() @@ -65,7 +65,7 @@ func (_m *Message) Payload() (string, error) { return r0, r1 } -// Type provides a mock function with given fields: +// Type provides a mock function with no fields func (_m *Message) Type() string { ret := _m.Called() diff --git a/temporal/tempmocks/option.go b/temporal/tempmocks/option.go index 427a90da..182a1842 100644 --- a/temporal/tempmocks/option.go +++ b/temporal/tempmocks/option.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/queue.go b/temporal/tempmocks/queue.go index 765e6a0b..fb46f8bd 100644 --- a/temporal/tempmocks/queue.go +++ b/temporal/tempmocks/queue.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/set.go b/temporal/tempmocks/set.go index f014dd33..b809658e 100644 --- a/temporal/tempmocks/set.go +++ b/temporal/tempmocks/set.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/sorted_set.go b/temporal/tempmocks/sorted_set.go index 10cc4aff..5c5bd984 100644 --- a/temporal/tempmocks/sorted_set.go +++ b/temporal/tempmocks/sorted_set.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks diff --git a/temporal/tempmocks/subscription.go b/temporal/tempmocks/subscription.go index bde98dd1..a1fffb62 100644 --- a/temporal/tempmocks/subscription.go +++ b/temporal/tempmocks/subscription.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.53.6. DO NOT EDIT. package mocks @@ -14,7 +14,7 @@ type Subscription struct { mock.Mock } -// Close provides a mock function with given fields: +// Close provides a mock function with no fields func (_m *Subscription) Close() error { ret := _m.Called()