mirror of
https://github.com/redis/go-redis.git
synced 2025-04-17 20:17:02 +03:00
Support new hash commands: HGETDEL, HGETEX, HSETEX (#3305)
This commit is contained in:
parent
d29aace826
commit
15059395a1
143
commands_test.go
143
commands_test.go
@ -2659,7 +2659,6 @@ var _ = Describe("Commands", func() {
|
|||||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||||
res, err := client.HPExpire(ctx, "no_such_key", 10*time.Second, "field1", "field2", "field3").Result()
|
res, err := client.HPExpire(ctx, "no_such_key", 10*time.Second, "field1", "field2", "field3").Result()
|
||||||
@ -2812,6 +2811,148 @@ var _ = Describe("Commands", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(res[0]).To(BeNumerically("~", 10*time.Second.Milliseconds(), 1))
|
Expect(res[0]).To(BeNumerically("~", 10*time.Second.Milliseconds(), 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should HGETDEL", Label("hash", "HGETDEL"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2", "f3", "val3").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Execute HGETDEL on fields f1 and f2.
|
||||||
|
res, err := client.HGetDel(ctx, "myhash", "f1", "f2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
// Expect the returned values for f1 and f2.
|
||||||
|
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||||
|
|
||||||
|
// Verify that f1 and f2 have been deleted, while f3 remains.
|
||||||
|
remaining, err := client.HMGet(ctx, "myhash", "f1", "f2", "f3").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(remaining[0]).To(BeNil())
|
||||||
|
Expect(remaining[1]).To(BeNil())
|
||||||
|
Expect(remaining[2]).To(Equal("val3"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return nil responses for HGETDEL on non-existent key", Label("hash", "HGETDEL"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
// HGETDEL on a key that does not exist.
|
||||||
|
res, err := client.HGetDel(ctx, "nonexistent", "f1", "f2").Result()
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(res).To(Equal([]string{"", ""}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// HGETEX with various TTL options
|
||||||
|
// -----------------------------
|
||||||
|
It("should HGETEX with EX option", Label("hash", "HGETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Call HGETEX with EX option and 60 seconds TTL.
|
||||||
|
opt := redis.HGetEXOptions{
|
||||||
|
ExpirationType: redis.HGetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should HGETEX with PERSIST option", Label("hash", "HGETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Call HGETEX with PERSIST (no TTL value needed).
|
||||||
|
opt := redis.HGetEXOptions{ExpirationType: redis.HGetEXExpirationPERSIST}
|
||||||
|
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should HGETEX with EXAT option", Label("hash", "HGETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Set expiration at a specific Unix timestamp (60 seconds from now).
|
||||||
|
expireAt := time.Now().Add(60 * time.Second).Unix()
|
||||||
|
opt := redis.HGetEXOptions{
|
||||||
|
ExpirationType: redis.HGetEXExpirationEXAT,
|
||||||
|
ExpirationVal: expireAt,
|
||||||
|
}
|
||||||
|
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// HSETEX with FNX/FXX options
|
||||||
|
// -----------------------------
|
||||||
|
It("should HSETEX with FNX condition", Label("hash", "HSETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
opt := redis.HSetEXOptions{
|
||||||
|
Condition: redis.HSetEXFNX,
|
||||||
|
ExpirationType: redis.HSetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val1").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(int64(1)))
|
||||||
|
|
||||||
|
opt = redis.HSetEXOptions{
|
||||||
|
Condition: redis.HSetEXFNX,
|
||||||
|
ExpirationType: redis.HSetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err = client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(int64(0)))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should HSETEX with FXX condition", Label("hash", "HSETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
err := client.HSet(ctx, "myhash", "f2", "val1").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
opt := redis.HSetEXOptions{
|
||||||
|
Condition: redis.HSetEXFXX,
|
||||||
|
ExpirationType: redis.HSetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f2", "val2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(int64(1)))
|
||||||
|
opt = redis.HSetEXOptions{
|
||||||
|
Condition: redis.HSetEXFXX,
|
||||||
|
ExpirationType: redis.HSetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err = client.HSetEXWithArgs(ctx, "myhash", &opt, "f3", "val3").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(int64(0)))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should HSETEX with multiple field operations", Label("hash", "HSETEX"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
|
|
||||||
|
opt := redis.HSetEXOptions{
|
||||||
|
ExpirationType: redis.HSetEXExpirationEX,
|
||||||
|
ExpirationVal: 60,
|
||||||
|
}
|
||||||
|
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val1", "f2", "val2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(int64(1)))
|
||||||
|
|
||||||
|
values, err := client.HMGet(ctx, "myhash", "f1", "f2").Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(values).To(Equal([]interface{}{"val1", "val2"}))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("hyperloglog", func() {
|
Describe("hyperloglog", func() {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
116
hash_commands.go
116
hash_commands.go
@ -10,13 +10,17 @@ type HashCmdable interface {
|
|||||||
HExists(ctx context.Context, key, field string) *BoolCmd
|
HExists(ctx context.Context, key, field string) *BoolCmd
|
||||||
HGet(ctx context.Context, key, field string) *StringCmd
|
HGet(ctx context.Context, key, field string) *StringCmd
|
||||||
HGetAll(ctx context.Context, key string) *MapStringStringCmd
|
HGetAll(ctx context.Context, key string) *MapStringStringCmd
|
||||||
HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
|
HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||||
|
HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||||
|
HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd
|
||||||
HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
|
HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
|
||||||
HKeys(ctx context.Context, key string) *StringSliceCmd
|
HKeys(ctx context.Context, key string) *StringSliceCmd
|
||||||
HLen(ctx context.Context, key string) *IntCmd
|
HLen(ctx context.Context, key string) *IntCmd
|
||||||
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
||||||
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
||||||
|
HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd
|
||||||
|
HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd
|
||||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||||
HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||||
@ -454,3 +458,113 @@ func (c cmdable) HPTTL(ctx context.Context, key string, fields ...string) *IntSl
|
|||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c cmdable) HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||||
|
args := []interface{}{"HGETDEL", key, "FIELDS", len(fields)}
|
||||||
|
for _, field := range fields {
|
||||||
|
args = append(args, field)
|
||||||
|
}
|
||||||
|
cmd := NewStringSliceCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cmdable) HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||||
|
args := []interface{}{"HGETEX", key, "FIELDS", len(fields)}
|
||||||
|
for _, field := range fields {
|
||||||
|
args = append(args, field)
|
||||||
|
}
|
||||||
|
cmd := NewStringSliceCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpirationType represents an expiration option for the HGETEX command.
|
||||||
|
type HGetEXExpirationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HGetEXExpirationEX HGetEXExpirationType = "EX"
|
||||||
|
HGetEXExpirationPX HGetEXExpirationType = "PX"
|
||||||
|
HGetEXExpirationEXAT HGetEXExpirationType = "EXAT"
|
||||||
|
HGetEXExpirationPXAT HGetEXExpirationType = "PXAT"
|
||||||
|
HGetEXExpirationPERSIST HGetEXExpirationType = "PERSIST"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HGetEXOptions struct {
|
||||||
|
ExpirationType HGetEXExpirationType
|
||||||
|
ExpirationVal int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cmdable) HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd {
|
||||||
|
args := []interface{}{"HGETEX", key}
|
||||||
|
if options.ExpirationType != "" {
|
||||||
|
args = append(args, string(options.ExpirationType))
|
||||||
|
if options.ExpirationType != HGetEXExpirationPERSIST {
|
||||||
|
args = append(args, options.ExpirationVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "FIELDS", len(fields))
|
||||||
|
for _, field := range fields {
|
||||||
|
args = append(args, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewStringSliceCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type HSetEXCondition string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HSetEXFNX HSetEXCondition = "FNX" // Only set the fields if none of them already exist.
|
||||||
|
HSetEXFXX HSetEXCondition = "FXX" // Only set the fields if all already exist.
|
||||||
|
)
|
||||||
|
|
||||||
|
type HSetEXExpirationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HSetEXExpirationEX HSetEXExpirationType = "EX"
|
||||||
|
HSetEXExpirationPX HSetEXExpirationType = "PX"
|
||||||
|
HSetEXExpirationEXAT HSetEXExpirationType = "EXAT"
|
||||||
|
HSetEXExpirationPXAT HSetEXExpirationType = "PXAT"
|
||||||
|
HSetEXExpirationKEEPTTL HSetEXExpirationType = "KEEPTTL"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HSetEXOptions struct {
|
||||||
|
Condition HSetEXCondition
|
||||||
|
ExpirationType HSetEXExpirationType
|
||||||
|
ExpirationVal int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cmdable) HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd {
|
||||||
|
args := []interface{}{"HSETEX", key, "FIELDS", len(fieldsAndValues) / 2}
|
||||||
|
for _, field := range fieldsAndValues {
|
||||||
|
args = append(args, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewIntCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cmdable) HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd {
|
||||||
|
args := []interface{}{"HSETEX", key}
|
||||||
|
if options.Condition != "" {
|
||||||
|
args = append(args, string(options.Condition))
|
||||||
|
}
|
||||||
|
if options.ExpirationType != "" {
|
||||||
|
args = append(args, string(options.ExpirationType))
|
||||||
|
if options.ExpirationType != HSetEXExpirationKEEPTTL {
|
||||||
|
args = append(args, options.ExpirationVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = append(args, "FIELDS", len(fieldsAndValues)/2)
|
||||||
|
for _, field := range fieldsAndValues {
|
||||||
|
args = append(args, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewIntCmd(ctx, args...)
|
||||||
|
_ = c(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user