diff --git a/command.go b/command.go index 3a39474142..171918ae99 100644 --- a/command.go +++ b/command.go @@ -4725,6 +4725,66 @@ func (cmd *ClusterShardsCmd) readReply(rd *proto.Reader) error { return nil } +// ----------------------------------------- + +type RankScore struct { + Rank int64 + Score float64 +} + +type RankWithScoreCmd struct { + baseCmd + + val RankScore +} + +var _ Cmder = (*RankWithScoreCmd)(nil) + +func NewRankWithScoreCmd(ctx context.Context, args ...interface{}) *RankWithScoreCmd { + return &RankWithScoreCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *RankWithScoreCmd) SetVal(val RankScore) { + cmd.val = val +} + +func (cmd *RankWithScoreCmd) Val() RankScore { + return cmd.val +} + +func (cmd *RankWithScoreCmd) Result() (RankScore, error) { + return cmd.val, cmd.err +} + +func (cmd *RankWithScoreCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *RankWithScoreCmd) readReply(rd *proto.Reader) error { + if err := rd.ReadFixedArrayLen(2); err != nil { + return err + } + + rank, err := rd.ReadInt() + if err != nil { + return err + } + + score, err := rd.ReadFloat() + if err != nil { + return err + } + + cmd.val = RankScore{Rank: rank, Score: score} + + return nil +} + // ------------------------------------------- type ACLLogEntry struct { diff --git a/commands.go b/commands.go index 365b820f88..8593ec018f 100644 --- a/commands.go +++ b/commands.go @@ -373,6 +373,7 @@ type Cmdable interface { ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd ZRank(ctx context.Context, key, member string) *IntCmd + ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd @@ -383,6 +384,7 @@ type Cmdable interface { ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd ZRevRank(ctx context.Context, key, member string) *IntCmd + ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd ZScore(ctx context.Context, key, member string) *FloatCmd ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd @@ -499,6 +501,8 @@ type Cmdable interface { ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd ACLLog(ctx context.Context, count ...int64) *ACLLogCmd ACLLogReset(ctx context.Context) *StatusCmd + + ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd } type StatefulCmdable interface { @@ -2884,6 +2888,14 @@ func (c cmdable) ZRank(ctx context.Context, key, member string) *IntCmd { return cmd } +// ZRankWithScore according to the Redis documentation, if member does not exist +// in the sorted set or key does not exist, it will return a redis.Nil error. +func (c cmdable) ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd { + cmd := NewRankWithScoreCmd(ctx, "zrank", key, member, "withscore") + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "zrem" @@ -2924,6 +2936,8 @@ func (c cmdable) ZRevRange(ctx context.Context, key string, start, stop int64) * return cmd } +// ZRevRangeWithScores according to the Redis documentation, if member does not exist +// in the sorted set or key does not exist, it will return a redis.Nil error. func (c cmdable) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd(ctx, "zrevrange", key, start, stop, "withscores") _ = c(ctx, cmd) @@ -2974,6 +2988,12 @@ func (c cmdable) ZRevRank(ctx context.Context, key, member string) *IntCmd { return cmd } +func (c cmdable) ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd { + cmd := NewRankWithScoreCmd(ctx, "zrevrank", key, member, "withscore") + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ZScore(ctx context.Context, key, member string) *FloatCmd { cmd := NewFloatCmd(ctx, "zscore", key, member) _ = c(ctx, cmd) @@ -3891,8 +3911,37 @@ func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...inte return cmd } +// ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis. +// `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` +type ModuleLoadexConfig struct { + Path string + Conf map[string]interface{} + Args []interface{} +} + +func (c *ModuleLoadexConfig) toArgs() []interface{} { + args := make([]interface{}, 3, 3+len(c.Conf)*3+len(c.Args)*2) + args[0] = "MODULE" + args[1] = "LOADEX" + args[2] = c.Path + for k, v := range c.Conf { + args = append(args, "CONFIG", k, v) + } + for _, arg := range c.Args { + args = append(args, "ARGS", arg) + } + return args +} + +// ModuleLoadex Redis `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` command. +func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd { + cmd := NewStringCmd(ctx, conf.toArgs()...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ACLLog(ctx context.Context, count ...int64) *ACLLogCmd { - args := make([]interface{}, 0, 1) + args := make([]interface{}, 0, 2+len(count)) args = append(args, "acl", "log") if len(count) > 0 { @@ -3902,7 +3951,7 @@ func (c cmdable) ACLLog(ctx context.Context, count ...int64) *ACLLogCmd { args = append(args, count[0]) } - cmd := NewACLLogCmd(ctx, args) + cmd := NewACLLogCmd(ctx, args...) _ = c(ctx, cmd) return cmd } diff --git a/commands_test.go b/commands_test.go index 0ce56625cc..8161e5e704 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1932,6 +1932,54 @@ var _ = Describe("Commands", func() { Expect(dryRun.Val()).To(Equal("OK")) }) + It("should fail module loadex", func() { + dryRun := client.ModuleLoadex(ctx, &redis.ModuleLoadexConfig{ + Path: "/path/to/non-existent-library.so", + Conf: map[string]interface{}{ + "param1": "value1", + }, + Args: []interface{}{ + "arg1", + }, + }) + Expect(dryRun.Err()).To(HaveOccurred()) + Expect(dryRun.Err().Error()).To(Equal("ERR Error loading the extension. Please check the server logs.")) + }) + + It("converts the module loadex configuration to a slice of arguments correctly", func() { + conf := &redis.ModuleLoadexConfig{ + Path: "/path/to/your/module.so", + Conf: map[string]interface{}{ + "param1": "value1", + }, + Args: []interface{}{ + "arg1", + "arg2", + 3, + }, + } + + args := conf.ToArgs() + + // Test if the arguments are in the correct order + expectedArgs := []interface{}{ + "MODULE", + "LOADEX", + "/path/to/your/module.so", + "CONFIG", + "param1", + "value1", + "ARGS", + "arg1", + "ARGS", + "arg2", + "ARGS", + 3, + } + + Expect(args).To(Equal(expectedArgs)) + }) + It("should ACL LOG", func() { // Test without the count parameter logs := client.ACLLog(ctx) @@ -1991,7 +2039,6 @@ var _ = Describe("Commands", func() { logEntries := logCmd.Val() Expect(len(logEntries)).To(Equal(0)) }) - }) Describe("hashes", func() { @@ -4750,6 +4797,31 @@ var _ = Describe("Commands", func() { Expect(zRank.Val()).To(Equal(int64(0))) }) + It("should ZRankWithScore", func() { + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() + Expect(err).NotTo(HaveOccurred()) + + zRankWithScore := client.ZRankWithScore(ctx, "zset", "one") + Expect(zRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 0, Score: 1})) + + zRankWithScore = client.ZRankWithScore(ctx, "zset", "two") + Expect(zRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 1, Score: 2})) + + zRankWithScore = client.ZRankWithScore(ctx, "zset", "three") + Expect(zRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 2, Score: 3})) + + zRankWithScore = client.ZRankWithScore(ctx, "zset", "four") + Expect(zRankWithScore.Err()).To(HaveOccurred()) + Expect(zRankWithScore.Err()).To(Equal(redis.Nil)) + }) + It("should ZRem", func() { err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) @@ -5021,6 +5093,31 @@ var _ = Describe("Commands", func() { Expect(zRevRank.Val()).To(Equal(int64(0))) }) + It("should ZRevRankWithScore", func() { + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() + Expect(err).NotTo(HaveOccurred()) + + zRevRankWithScore := client.ZRevRankWithScore(ctx, "zset", "one") + Expect(zRevRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRevRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 2, Score: 1})) + + zRevRankWithScore = client.ZRevRankWithScore(ctx, "zset", "two") + Expect(zRevRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRevRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 1, Score: 2})) + + zRevRankWithScore = client.ZRevRankWithScore(ctx, "zset", "three") + Expect(zRevRankWithScore.Err()).NotTo(HaveOccurred()) + Expect(zRevRankWithScore.Result()).To(Equal(redis.RankScore{Rank: 0, Score: 3})) + + zRevRankWithScore = client.ZRevRankWithScore(ctx, "zset", "four") + Expect(zRevRankWithScore.Err()).To(HaveOccurred()) + Expect(zRevRankWithScore.Err()).To(Equal(redis.Nil)) + }) + It("should ZScore", func() { zAdd := client.ZAdd(ctx, "zset", redis.Z{Score: 1.001, Member: "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) diff --git a/export_test.go b/export_test.go index 19a9dddcf2..3f92983dd3 100644 --- a/export_test.go +++ b/export_test.go @@ -98,3 +98,7 @@ func (c *Ring) ShardByName(name string) *ringShard { shard, _ := c.sharding.GetByName(name) return shard } + +func (c *ModuleLoadexConfig) ToArgs() []interface{} { + return c.toArgs() +} diff --git a/main_test.go b/main_test.go index b1a1f3950a..8e87d76f86 100644 --- a/main_test.go +++ b/main_test.go @@ -314,7 +314,7 @@ func startRedis(port string, args ...string) (*redisProcess, error) { return nil, err } - baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir} + baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir, "--enable-module-command", "yes"} process, err := execCmd(redisServerBin, append(baseArgs, args...)...) if err != nil { return nil, err