diff --git a/go/api/base_client.go b/go/api/base_client.go index 26b0a5262b..a081d3dc29 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -948,3 +948,130 @@ func (client *baseClient) Del(keys []string) (Result[int64], error) { return handleLongResponse(result) } + +func (client *baseClient) Exists(keys []string) (Result[int64], error) { + result, err := client.executeCommand(C.Exists, keys) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) Expire(key string, seconds int64) (Result[bool], error) { + result, err := client.executeCommand(C.Expire, []string{key, utils.IntToString(seconds)}) + if err != nil { + return CreateNilBoolResult(), err + } + + return handleBooleanResponse(result) +} + +func (client *baseClient) ExpireWithOptions(key string, seconds int64, expireCondition ExpireCondition) (Result[bool], error) { + expireConditionStr, err := expireCondition.toString() + if err != nil { + return CreateNilBoolResult(), err + } + result, err := client.executeCommand(C.Expire, []string{key, utils.IntToString(seconds), expireConditionStr}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) ExpireAt(key string, unixTimestampInSeconds int64) (Result[bool], error) { + result, err := client.executeCommand(C.ExpireAt, []string{key, utils.IntToString(unixTimestampInSeconds)}) + if err != nil { + return CreateNilBoolResult(), err + } + + return handleBooleanResponse(result) +} + +func (client *baseClient) ExpireAtWithOptions(key string, unixTimestampInSeconds int64, expireCondition ExpireCondition) (Result[bool], error) { + expireConditionStr, err := expireCondition.toString() + if err != nil { + return CreateNilBoolResult(), err + } + result, err := client.executeCommand(C.ExpireAt, []string{key, utils.IntToString(unixTimestampInSeconds), expireConditionStr}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) PExpire(key string, milliseconds int64) (Result[bool], error) { + result, err := client.executeCommand(C.PExpire, []string{key, utils.IntToString(milliseconds)}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) PExpireWithOptions(key string, milliseconds int64, expireCondition ExpireCondition) (Result[bool], error) { + expireConditionStr, err := expireCondition.toString() + if err != nil { + return CreateNilBoolResult(), err + } + result, err := client.executeCommand(C.PExpire, []string{key, utils.IntToString(milliseconds), expireConditionStr}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) PExpireAt(key string, unixTimestampInMilliSeconds int64) (Result[bool], error) { + result, err := client.executeCommand(C.PExpireAt, []string{key, utils.IntToString(unixTimestampInMilliSeconds)}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) PExpireAtWithOptions(key string, unixTimestampInMilliSeconds int64, expireCondition ExpireCondition) (Result[bool], error) { + expireConditionStr, err := expireCondition.toString() + if err != nil { + return CreateNilBoolResult(), err + } + result, err := client.executeCommand(C.PExpireAt, []string{key, utils.IntToString(unixTimestampInMilliSeconds), expireConditionStr}) + if err != nil { + return CreateNilBoolResult(), err + } + return handleBooleanResponse(result) +} + +func (client *baseClient) ExpireTime(key string) (Result[int64], error) { + result, err := client.executeCommand(C.ExpireTime, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) PExpireTime(key string) (Result[int64], error) { + result, err := client.executeCommand(C.PExpireTime, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) TTL(key string) (Result[int64], error) { + result, err := client.executeCommand(C.TTL, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) PTTL(key string) (Result[int64], error) { + result, err := client.executeCommand(C.PTTL, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} diff --git a/go/api/command_options.go b/go/api/command_options.go index 34326e14b8..c63d01a7a3 100644 --- a/go/api/command_options.go +++ b/go/api/command_options.go @@ -120,6 +120,34 @@ const ( OnlyIfDoesNotExist ConditionalSet = "NX" ) +type ExpireCondition string + +const ( + // HasExistingExpiry only sets the key if it already exists. Equivalent to "XX" in the valkey API. + HasExistingExpiry ExpireCondition = "XX" + // HasNoExpiry only sets the key if it does not already exist. Equivalent to "NX" in the valkey API. + HasNoExpiry ExpireCondition = "NX" + // NewExpiryGreaterThanCurrent only sets the key if its greater than current. Equivalent to "GT" in the valkey API. + NewExpiryGreaterThanCurrent ExpireCondition = "GT" + // NewExpiryLessThanCurrent only sets the key if its lesser than current. Equivalent to "LT" in the valkey API. + NewExpiryLessThanCurrent ExpireCondition = "LT" +) + +func (expireCondition ExpireCondition) toString() (string, error) { + switch expireCondition { + case HasExistingExpiry: + return string(HasExistingExpiry), nil + case HasNoExpiry: + return string(HasNoExpiry), nil + case NewExpiryGreaterThanCurrent: + return string(NewExpiryGreaterThanCurrent), nil + case NewExpiryLessThanCurrent: + return string(NewExpiryLessThanCurrent), nil + default: + return "", &RequestError{"Invalid expire condition"} + } +} + // Expiry is used to configure the lifetime of a value. type Expiry struct { Type ExpiryType diff --git a/go/api/commands.go b/go/api/commands.go index 5778ec9069..c99af91f29 100644 --- a/go/api/commands.go +++ b/go/api/commands.go @@ -186,9 +186,12 @@ type StringCommands interface { // the entire operation fails. // // Note: - // When in cluster mode, all keys in keyValueMap must map to the same hash slot. - // - // See [valkey.io] for details. + // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. // // Parameters: // keyValueMap - A key-value map consisting of keys and their respective values to set. @@ -414,9 +417,12 @@ type StringCommands interface { // Valkey 7.0 and above. // // Note: - // When in cluster mode, key1 and key2 must map to the same hash slot. - // - // See [valkey.io] for details. + // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. // // Parameters: // key1 - The key that stores the first string. diff --git a/go/api/generic_commands.go b/go/api/generic_commands.go index 82620610a2..045807703e 100644 --- a/go/api/generic_commands.go +++ b/go/api/generic_commands.go @@ -13,7 +13,12 @@ type GenericBaseCommands interface { // Del removes the specified keys from the database. A key is ignored if it does not exist. // // Note: - //When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots. + // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. // // Parameters: // keys - One or more keys to delete. @@ -30,4 +35,280 @@ type GenericBaseCommands interface { // // [valkey.io]: https://valkey.io/commands/del/ Del(keys []string) (Result[int64], error) + + // Exists returns the number of keys that exist in the database + // + // Note: + // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. + // + // Parameters: + // keys - One or more keys to check if they exist. + // + // Return value: + // Returns the number of existing keys. + // + // Example: + // result, err := client.Exists([]string{"key1", "key2", "key3"}) + // result.Value(): 2 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/exists/ + Exists(keys []string) (Result[int64], error) + + // Expire sets a timeout on key. After the timeout has expired, the key will automatically be deleted + // + // If key already has an existing expire set, the time to live is updated to the new value. + // If seconds is a non-positive number, the key will be deleted rather than expired. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to expire. + // seconds - Time in seconds for the key to expire + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.Expire("key", 1) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/expire/ + Expire(key string, seconds int64) (Result[bool], error) + + // Expire sets a timeout on key. After the timeout has expired, the key will automatically be deleted + // + // If key already has an existing expire set, the time to live is updated to the new value. + // If seconds is a non-positive number, the key will be deleted rather than expired. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to expire. + // seconds - Time in seconds for the key to expire + // option - The option to set expiry - NX, XX, GT, LT + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.Expire("key", 1, api.OnlyIfDoesNotExist) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/expire/ + ExpireWithOptions(key string, seconds int64, expireCondition ExpireCondition) (Result[bool], error) + + // ExpireAt sets a timeout on key. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. + // A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. + // If key already has an existing expire set, the time to live is updated to the new value. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // If key already has an existing expire set, the time to live is updated to the new value. + // If seconds is a non-positive number, the key will be deleted rather than expired. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to expire. + // unixTimestampInSeconds - Absolute Unix timestamp + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.ExpireAt("key", time.Now().Unix()) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/expireat/ + ExpireAt(key string, unixTimestampInSeconds int64) (Result[bool], error) + + // ExpireAt sets a timeout on key. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. + // A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. + // If key already has an existing expire set, the time to live is updated to the new value. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // If key already has an existing expire set, the time to live is updated to the new value. + // If seconds is a non-positive number, the key will be deleted rather than expired. + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to expire. + // unixTimestampInSeconds - Absolute Unix timestamp + // option - The option to set expiry - NX, XX, GT, LT + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.ExpireAt("key", time.Now().Unix(), api.OnlyIfDoesNotExist) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/expireat/ + ExpireAtWithOptions(key string, unixTimestampInSeconds int64, expireCondition ExpireCondition) (Result[bool], error) + + // Sets a timeout on key in milliseconds. After the timeout has expired, the key will automatically be deleted. + // If key already has an existing expire set, the time to live is updated to the new value. + // If milliseconds is a non-positive number, the key will be deleted rather than expired + // The timeout will only be cleared by commands that delete or overwrite the contents of key. + + // Parameters: + // key - The key to set timeout on it. + // milliseconds - The timeout in milliseconds. + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.PExpire("key", int64(5 * 1000)) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pexpire/ + PExpire(key string, milliseconds int64) (Result[bool], error) + + // Sets a timeout on key in milliseconds. After the timeout has expired, the key will automatically be deleted. + // If key already has an existing expire set, the time to live is updated to the new value. + // If milliseconds is a non-positive number, the key will be deleted rather than expired + // The timeout will only be cleared by commands that delete or overwrite the contents of key. + // + // Parameters: + // key - The key to set timeout on it. + // milliseconds - The timeout in milliseconds. + // option - The option to set expiry - NX, XX, GT, LT + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.PExpire("key", int64(5 * 1000), api.OnlyIfDoesNotExist) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pexpire/ + PExpireWithOptions(key string, milliseconds int64, expireCondition ExpireCondition) (Result[bool], error) + + // Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + // January 1, 1970) instead of specifying the number of milliseconds. + // A timestamp in the past will delete the key immediately. After the timeout has + // expired, the key will automatically be deleted + // If key already has an existing expire set, the time to live is + // updated to the new value/ + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to set timeout on it. + // unixMilliseconds - The timeout in an absolute Unix timestamp. + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.PExpire("key", time.Now().Unix()*1000) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pexpireat/ + PExpireAt(key string, unixTimestampInMilliSeconds int64) (Result[bool], error) + + // Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + // January 1, 1970) instead of specifying the number of milliseconds. + // A timestamp in the past will delete the key immediately. After the timeout has + // expired, the key will automatically be deleted + // If key already has an existing expire set, the time to live is + // updated to the new value/ + // The timeout will only be cleared by commands that delete or overwrite the contents of key + // + // Parameters: + // key - The key to set timeout on it. + // unixMilliseconds - The timeout in an absolute Unix timestamp. + // option - The option to set expiry - NX, XX, GT, LT + // + // Return value: + // A Result[bool] containing true is expiry is set. + // + // Example: + // result, err := client.PExpire("key", time.Now().Unix()*1000, api.OnlyIfDoesNotExist) + // result.Value(): true + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pexpireat/ + PExpireAtWithOptions(key string, unixTimestampInMilliSeconds int64, expireCondition ExpireCondition) (Result[bool], error) + + // Expire Time returns the absolute Unix timestamp (since January 1, 1970) at which the given key + // will expire, in seconds. + // + // Parameters: + // key - The key to determine the expiration value of. + // + // Return value: + // The expiration Unix timestamp in seconds. + // -2 if key does not exist or -1 is key exists but has no associated expiration. + // + // Example: + // + // result, err := client.ExpireTime("key") + // result.Value(): 1732118030 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/expiretime/ + ExpireTime(key string) (Result[int64], error) + + // PExpire Time returns the absolute Unix timestamp (since January 1, 1970) at which the given key + // will expire, in milliseconds. + // + // Parameters: + // key - The key to determine the expiration value of. + // + // Return value: + // The expiration Unix timestamp in milliseconds. + // -2 if key does not exist or -1 is key exists but has no associated expiration. + // + // Example: + // + // result, err := client.PExpireTime("key") + // result.Value(): 33177117420000 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pexpiretime/ + PExpireTime(key string) (Result[int64], error) + + // TTL returns the remaining time to live of key that has a timeout, in seconds. + // + // Parameters: + // key - The key to return its timeout. + // + // Return value: + // Returns TTL in seconds, + // -2 if key does not exist, or -1 if key exists but has no associated expiration. + // + // Example: + // + // result, err := client.TTL("key") + // result.Value(): 3 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/ttl/ + TTL(key string) (Result[int64], error) + + // PTTL returns the remaining time to live of key that has a timeout, in milliseconds. + // + // Parameters: + // key - The key to return its timeout. + // + // Return value: + // Returns TTL in milliseconds, + // -2 if key does not exist, or -1 if key exists but has no associated expiration. + // + // Example: + // + // result, err := client.PTTL("key") + // result.Value(): 1000 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/pttl/ + PTTL(key string) (Result[int64], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 11f14d5c93..570a01ad0a 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -2503,3 +2503,646 @@ func (suite *GlideTestSuite) TestDel_MultipleKeys() { assert.True(suite.T(), result3.IsNil()) }) } + +func (suite *GlideTestSuite) TestExists() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + // Test 1: Check if an existing key returns 1 + suite.verifyOK(client.Set(key, initialValue)) + result, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), result.Value(), "The key should exist") + + // Test 2: Check if a non-existent key returns 0 + result, err = client.Exists([]string{"nonExistentKey"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), result.Value(), "The non-existent key should not exist") + + // Test 3: Multiple keys, some exist, some do not + existingKey := uuid.New().String() + testKey := uuid.New().String() + suite.verifyOK(client.Set(existingKey, value)) + suite.verifyOK(client.Set(testKey, value)) + result, err = client.Exists([]string{testKey, existingKey, "anotherNonExistentKey"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), result.Value(), "Two keys should exist") + }) +} + +func (suite *GlideTestSuite) TestExpire() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + result, err := client.Expire(key, 1) + assert.Nil(suite.T(), err, "Expected no error from Expire command") + assert.True(suite.T(), result.Value(), "Expire command should return true when expiry is set") + + time.Sleep(1500 * time.Millisecond) + + resultGet, err := client.Get(key) + assert.Nil(suite.T(), err, "Expected no error from Get command after expiry") + assert.Equal(suite.T(), "", resultGet.Value(), "Key should be expired and return empty value") + }) +} + +func (suite *GlideTestSuite) TestExpire_KeyDoesNotExist() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + // Trying to set an expiry on a non-existent key + result, err := client.Expire(key, 1) + assert.Nil(suite.T(), err) + assert.False(suite.T(), result.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireWithOptions_HasNoExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + result, err := client.ExpireWithOptions(key, 2, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), result.Value()) + + time.Sleep(2500 * time.Millisecond) + + resultGet, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", resultGet.Value()) + + result, err = client.ExpireWithOptions(key, 1, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.False(suite.T(), result.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireWithOptions_HasExistingExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + resexp, err := client.ExpireWithOptions(key, 20, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resexp.Value()) + + resultExpire, err := client.ExpireWithOptions(key, 1, api.HasExistingExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + time.Sleep(2 * time.Second) + + resultExpireTest, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + + assert.Equal(suite.T(), int64(0), resultExpireTest.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireWithOptions_NewExpiryGreaterThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + resultExpire, err := client.ExpireWithOptions(key, 2, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + resultExpire, err = client.ExpireWithOptions(key, 5, api.NewExpiryGreaterThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + time.Sleep(6 * time.Second) + resultExpireTest, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExpireTest.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireWithOptions_NewExpiryLessThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + resultExpire, err := client.ExpireWithOptions(key, 10, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + resultExpire, err = client.ExpireWithOptions(key, 5, api.NewExpiryLessThanCurrent) + assert.Nil(suite.T(), err) + + assert.True(suite.T(), resultExpire.Value()) + + resultExpire, err = client.ExpireWithOptions(key, 15, api.NewExpiryGreaterThanCurrent) + assert.Nil(suite.T(), err) + + assert.True(suite.T(), resultExpire.Value()) + + time.Sleep(16 * time.Second) + resultExpireTest, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExpireTest.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireAtWithOptions_HasNoExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + futureTimestamp := time.Now().Add(10 * time.Second).Unix() + + resultExpire, err := client.ExpireAtWithOptions(key, futureTimestamp, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + resultExpireAt, err := client.ExpireAt(key, futureTimestamp) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireAt.Value()) + resultExpireWithOptions, err := client.ExpireAtWithOptions(key, futureTimestamp+10, api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.False(suite.T(), resultExpireWithOptions.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireAtWithOptions_HasExistingExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + futureTimestamp := time.Now().Add(10 * time.Second).Unix() + resultExpireAt, err := client.ExpireAt(key, futureTimestamp) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireAt.Value()) + + resultExpireWithOptions, err := client.ExpireAtWithOptions(key, futureTimestamp+10, api.HasExistingExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + }) +} +func (suite *GlideTestSuite) TestExpireAtWithOptions_NewExpiryGreaterThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + futureTimestamp := time.Now().Add(10 * time.Second).Unix() + resultExpireAt, err := client.ExpireAt(key, futureTimestamp) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireAt.Value()) + + newFutureTimestamp := time.Now().Add(20 * time.Second).Unix() + resultExpireWithOptions, err := client.ExpireAtWithOptions(key, newFutureTimestamp, api.NewExpiryGreaterThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireAtWithOptions_NewExpiryLessThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + futureTimestamp := time.Now().Add(10 * time.Second).Unix() + resultExpireAt, err := client.ExpireAt(key, futureTimestamp) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireAt.Value()) + + newFutureTimestamp := time.Now().Add(5 * time.Second).Unix() + resultExpireWithOptions, err := client.ExpireAtWithOptions(key, newFutureTimestamp, api.NewExpiryLessThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(5 * time.Second) + resultExpireAtTest, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + + assert.Equal(suite.T(), int64(0), resultExpireAtTest.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpire() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + resultExpire, err := client.PExpire(key, 500) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + time.Sleep(600 * time.Millisecond) + resultExpireCheck, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExpireCheck.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireWithOptions_HasExistingExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + initialExpire := 500 + resultExpire, err := client.PExpire(key, int64(initialExpire)) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + newExpire := 1000 + + resultExpireWithOptions, err := client.PExpireWithOptions(key, int64(newExpire), api.HasExistingExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(1100 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireWithOptions_HasNoExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + newExpire := 500 + + resultExpireWithOptions, err := client.PExpireWithOptions(key, int64(newExpire), api.HasNoExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(600 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireWithOptions_NewExpiryGreaterThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + initialExpire := 500 + resultExpire, err := client.PExpire(key, int64(initialExpire)) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + newExpire := 1000 + + resultExpireWithOptions, err := client.PExpireWithOptions(key, int64(newExpire), api.NewExpiryGreaterThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(1100 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireWithOptions_NewExpiryLessThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + initialExpire := 500 + resultExpire, err := client.PExpire(key, int64(initialExpire)) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + newExpire := 200 + + resultExpireWithOptions, err := client.PExpireWithOptions(key, int64(newExpire), api.NewExpiryLessThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(600 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireAt() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + resultSet, err := client.Set(key, value) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultSet.Value() != "") + + expireAfterMilliseconds := time.Now().Unix() * 1000 + resultPExpireAt, err := client.PExpireAt(key, expireAfterMilliseconds) + assert.Nil(suite.T(), err) + + assert.True(suite.T(), resultPExpireAt.Value()) + + time.Sleep(6 * time.Second) + + resultpExists, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultpExists.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireAtWithOptions_HasNoExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + timestamp := time.Now().Unix() * 1000 + result, err := client.PExpireAtWithOptions(key, timestamp, api.HasNoExpiry) + + assert.Nil(suite.T(), err) + assert.True(suite.T(), result.Value()) + + time.Sleep(2 * time.Second) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireAtWithOptions_HasExistingExpiry() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + initialExpire := 500 + resultExpire, err := client.PExpire(key, int64(initialExpire)) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + newExpire := time.Now().Unix()*1000 + 1000 + + resultExpireWithOptions, err := client.PExpireAtWithOptions(key, newExpire, api.HasExistingExpiry) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(1100 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireAtWithOptions_NewExpiryGreaterThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + initialExpire := time.Now().UnixMilli() + 1000 + resultExpire, err := client.PExpireAt(key, initialExpire) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + newExpire := time.Now().UnixMilli() + 2000 + + resultExpireWithOptions, err := client.PExpireAtWithOptions(key, newExpire, api.NewExpiryGreaterThanCurrent) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(2100 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireAtWithOptions_NewExpiryLessThanCurrent() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + initialExpire := 1000 + resultExpire, err := client.PExpire(key, int64(initialExpire)) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpire.Value()) + + newExpire := time.Now().Unix()*1000 + 500 + + resultExpireWithOptions, err := client.PExpireAtWithOptions(key, newExpire, api.NewExpiryLessThanCurrent) + assert.Nil(suite.T(), err) + + assert.True(suite.T(), resultExpireWithOptions.Value()) + + time.Sleep(1100 * time.Millisecond) + resultExist, err := client.Exists([]string{key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultExist.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireTime() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + result, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), value, result.Value()) + + expireTime := time.Now().Unix() + 3 + resultExpAt, err := client.ExpireAt(key, expireTime) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpAt.Value()) + + resexptime, err := client.ExpireTime(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), expireTime, resexptime.Value()) + + time.Sleep(4 * time.Second) + + resultAfterExpiry, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", resultAfterExpiry.Value()) + }) +} + +func (suite *GlideTestSuite) TestExpireTime_KeyDoesNotExist() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + + key := uuid.New().String() + + // Call ExpireTime on a key that doesn't exist + expiryResult, err := client.ExpireTime(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(-2), expiryResult.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireTime() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + + suite.verifyOK(client.Set(key, value)) + + result, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), value, result.Value()) + + pexpireTime := time.Now().UnixMilli() + 3000 + resultExpAt, err := client.PExpireAt(key, pexpireTime) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resultExpAt.Value()) + + respexptime, err := client.PExpireTime(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), pexpireTime, respexptime.Value()) + + time.Sleep(4 * time.Second) + + resultAfterExpiry, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", resultAfterExpiry.Value()) + }) +} + +func (suite *GlideTestSuite) TestPExpireTime_KeyDoesNotExist() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + + // Call ExpireTime on a key that doesn't exist + expiryResult, err := client.PExpireTime(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(-2), expiryResult.Value()) + }) +} + +func (suite *GlideTestSuite) TestTTL_WithValidKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + resExpire, err := client.Expire(key, 1) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resExpire.Value()) + resTTL, err := client.TTL(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resTTL.Value(), int64(1)) + }) +} + +func (suite *GlideTestSuite) TestTTL_WithExpiredKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + resExpire, err := client.Expire(key, 1) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resExpire.Value()) + + time.Sleep(2 * time.Second) + + resTTL, err := client.TTL(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(-2), resTTL.Value()) + }) +} + +func (suite *GlideTestSuite) TestPTTL_WithValidKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + resExpire, err := client.Expire(key, 1) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resExpire.Value()) + + resPTTL, err := client.PTTL(key) + assert.Nil(suite.T(), err) + assert.Greater(suite.T(), resPTTL.Value(), int64(900)) + }) +} + +func (suite *GlideTestSuite) TestPTTL_WithExpiredKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + resExpire, err := client.Expire(key, 1) + assert.Nil(suite.T(), err) + assert.True(suite.T(), resExpire.Value()) + + time.Sleep(2 * time.Second) + + resPTTL, err := client.PTTL(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(-2), resPTTL.Value()) + }) +}