From 50a63dc8c7db83ec068a87cd6e4b13cd6f190cce Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Tue, 12 Sep 2023 08:48:02 +0100 Subject: [PATCH 01/18] Add support for RedisJSON --- commands.go | 2 + json.go | 473 +++++++++++++++++++++++++++++++++++++++++++++++++++ json_test.go | 288 +++++++++++++++++++++++++++++++ 3 files changed, 763 insertions(+) create mode 100644 json.go create mode 100644 json_test.go diff --git a/commands.go b/commands.go index 34f4d2c227..4027e9a8be 100644 --- a/commands.go +++ b/commands.go @@ -504,6 +504,8 @@ type Cmdable interface { ACLLogReset(ctx context.Context) *StatusCmd ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd + + JSONCmdAble } type StatefulCmdable interface { diff --git a/json.go b/json.go new file mode 100644 index 0000000000..97c503c7ef --- /dev/null +++ b/json.go @@ -0,0 +1,473 @@ +package redis + +import ( + "context" + "encoding/json" + + "github.com/redis/go-redis/v9/internal/proto" + "github.com/redis/go-redis/v9/internal/util" +) + +// ------------------------------------------- + +type JSONCmdAble interface { + JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd + JSONArrIndex(ctx context.Context, key, path string, value interface{}) *IntSliceCmd + JSONArrIndexStartStop(ctx context.Context, key, path string, value interface{}, start, stop int64) *IntSliceCmd + JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd + JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd + JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd + JSONArrTrim(ctx context.Context, key, path string, start, stop int) *IntSliceCmd + JSONClear(ctx context.Context, key, path string) *IntCmd + JSONDel(ctx context.Context, key, path string) *IntCmd + JSONForget(ctx context.Context, key, path string) *IntCmd + JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd + JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd + JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd + JSONObjKeys(ctx context.Context, key, path string) *SliceCmd + JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd + JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd + JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd + JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONType(ctx context.Context, key, path string) *JSONSliceCmd +} + +type JSONCmd struct { + baseCmd + val string + expanded []interface{} +} + +var _ Cmder = (*JSONCmd)(nil) + +func NewJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd { + + return &JSONCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *JSONCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *JSONCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *JSONCmd) Val() string { + if len(cmd.val) == 0 && cmd.expanded != nil { + val, err := json.Marshal(cmd.expanded) + if err != nil { + cmd.SetErr(err) + return "" + } + return string(val) + + } else { + return cmd.val + } + +} + +func (cmd *JSONCmd) Result() (string, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd JSONCmd) Expanded() (interface{}, error) { + + if len(cmd.val) != 0 && cmd.expanded == nil { + err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) + if err != nil { + return "", err + } + } + + return cmd.expanded, nil +} + +func (cmd *JSONCmd) readReply(rd *proto.Reader) error { + + // nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil") + if cmd.baseCmd.Err() == Nil { + cmd.val = "" + return Nil + } + + if readType, err := rd.PeekReplyType(); err != nil { + return err + } else if readType == proto.RespArray { + + size, err := rd.ReadArrayLen() + if err != nil { + return err + } + + var expanded = make([]interface{}, size) + + for i := 0; i < size; i++ { + if expanded[i], err = rd.ReadReply(); err != nil { + return err + } + } + cmd.expanded = expanded + + } else { + if str, err := rd.ReadString(); err != nil && err != Nil { + return err + } else if str == "" || err == Nil { + cmd.val = "" + } else { + cmd.val = str + } + } + + return nil +} + +// ------------------------------------------- + +type JSONSliceCmd struct { + baseCmd + val []interface{} +} + +func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd { + return &JSONSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *JSONSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *JSONSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *JSONSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *JSONSliceCmd) Result() ([]interface{}, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error { + + if cmd.baseCmd.Err() == Nil { + cmd.val = nil + return Nil + } + + if readType, err := rd.PeekReplyType(); err != nil { + return err + } else if readType == proto.RespArray { + response, err := rd.ReadReply() + if err != nil { + return nil + } else { + cmd.val = response.([]interface{}) + } + + } else { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]interface{}, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return err + default: + cmd.val[i] = s + } + } + } + return nil + +} + +/******************************************************************************* +* +* IntPointerSliceCmd +* used to represent a RedisJSON response where the result is either an integer or nil +* +*******************************************************************************/ + +type IntPointerSliceCmd struct { + baseCmd + val []*int64 +} + +// NewIntPointerSliceCmd initialises an IntPointerSliceCmd +func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd { + return &IntPointerSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *IntPointerSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntPointerSliceCmd) SetVal(val []*int64) { + cmd.val = val +} + +func (cmd *IntPointerSliceCmd) Val() []*int64 { + return cmd.val +} + +func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error { + + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]*int64, n) + + for i := 0; i < len(cmd.val); i++ { + val, err := rd.ReadInt() + if err != nil && err != Nil { + return err + } else if err != Nil { + cmd.val[i] = &val + } + } + + return nil +} + +//------------------------------------------------------------------------------ + +// JSONArrAppend adds the provided json values to the end of the at path +func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd { + args := []interface{}{"json.arrappend", key, path} + args = append(args, values...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrIndex searches for the first occurrence of a JSON value in an array +func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value interface{}) *IntSliceCmd { + args := []interface{}{"json.arrindex", key, path, value} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrIndexFromTo searches for the first occurrence of a JSON value in an array whilst allowing the start and +// stop options to be provided. +func (c cmdable) JSONArrIndexStartStop(ctx context.Context, key, path string, value interface{}, start, stop int64) *IntSliceCmd { + args := []interface{}{"json.arrindex", key, path, value, start, stop} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrInsert inserts the json values into the array at path before the index (shifts to the right) +func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd { + args := []interface{}{"json.arrinsert", key, path, index} + args = append(args, values...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrLen reports the length of the JSON array at path in key +func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd { + args := []interface{}{"json.arrlen", key, path} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrPop removes and returns an element from the index in the array +func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd { + args := []interface{}{"json.arrpop", key, path, index} + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrTrim trims an array so that it contains only the specified inclusive range of elements +func (c cmdable) JSONArrTrim(ctx context.Context, key, path string, start, stop int) *IntSliceCmd { + args := []interface{}{"json.arrtrim", key, path, start, stop} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONClear clears container values (arrays/objects) and set numeric values to 0 +func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"json.clear", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONDel deletes a value +func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"json.del", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONForget deletes a value +func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"json.forget", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONGet returns the value at path in JSON serialized form. JSON.GET returns an +// array of strings. This function parses out the wrapping array but leaves the +// internal strings unprocessed by default (see Val()) +func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd { + args := make([]interface{}, len(paths)+2) + args[0] = "json.get" + args[1] = key + for n, path := range paths { + args[n+2] = path + } + cmd := NewJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONMGet returns the values at path from multiple key arguments +// Note - the arguments are reversed when compared with `JSON.MGET` as we want +// to follow the pattern of having the last argument be variable. +func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd { + + args := make([]interface{}, len(keys)+1) + args[0] = "json.mget" + for n, keys := range keys { + args[n+1] = keys + } + args = append(args, path) + + cmd := NewJSONSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONNumIncrBy increments the number value stored at path by number +func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { + args := []interface{}{"json.numincrby", key, path, value} + cmd := NewJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONObjKeys returns the keys in the object that's referenced by path +func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { + args := []interface{}{"json.objkeys", key, path} + cmd := NewSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONObjLen reports the number of keys in the JSON object at path in key +func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"json.objlen", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONSet sets the JSON value at the given path in the given key. The value must be something that +// can be marshalled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that +// it can be passed directly as JSON. +func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd { + return c.JSONSetMode(ctx, key, path, value, "") +} + +// JSONSetMOde sets the JSON value at the given path in the given key allows the mode to be set +// as well (the mode value must be "XX" or "NX"). The value must be something that can be marshalled to JSON (using encoding/JSON) unless +// the argument is a string or []byte when we assume that it can be passed directly as JSON. +func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd { + + var bytes []byte + var err error + + switch v := value.(type) { + case string: + bytes = []byte(v) + case []byte: + bytes = v + default: + bytes, err = json.Marshal(v) + } + + args := []interface{}{"json.set", key, path, util.BytesToString(bytes)} + + if mode != "" { + args = append(args, mode) + } + + cmd := NewStatusCmd(ctx, args...) + + if err != nil { + cmd.SetErr(err) + } else { + _ = c(ctx, cmd) + } + + return cmd +} + +// JSONStrAppend appends the json-string values to the string at path +func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd { + args := []interface{}{"json.strappend", key, path, value} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONStrLen reports the length of the JSON String at path in key +func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"json.strlen", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONToggle toggles a Boolean value stored at path +func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"json.toggle", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONType reports the type of JSON value at path +func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd { + args := []interface{}{"json.type", key, path} + cmd := NewJSONSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} diff --git a/json_test.go b/json_test.go new file mode 100644 index 0000000000..d161853c51 --- /dev/null +++ b/json_test.go @@ -0,0 +1,288 @@ +package redis_test + +import ( + "context" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +type JSONGetTestStruct struct { + Hello string `json:"hello"` +} + +var _ = Describe("JSON Commands", Label("json"), func() { + + ctx := context.TODO() + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + Describe("arrays", Label("arrays"), func() { + + It("should JSONArrAppend", Label("json.arrappend"), func() { + cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrAppend(ctx, "append2", "$..a", 10) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{2, 3})) + }) + + It("should JSONArrIndex", Label("json.arrindex"), func() { + cmd1 := client.JSONSet(ctx, "index3", "$", `{"a": [10], "b": {"a": [12, 10]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrIndex(ctx, "index3", "$.b.a", 10) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{1})) + }) + + It("should JSONArrIndexStartStop", Label("json.arrindex"), func() { + cmd1 := client.JSONSet(ctx, "index4", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrIndexStartStop(ctx, "index4", "$.b.a", 12, 1, 4) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{3})) + }) + + It("should JSONArrInsert", Label("json.arrinsert"), func() { + cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrInsert(ctx, "insert2", "$", -1, 1, 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{6})) + + cmd3 := client.JSONGet(ctx, "insert2") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + // RESP2 vs RESP3 + Expect(cmd3.Val()).To(Or( + Equal(`[100,200,300,1,2,200]`), + Equal(`[[100,200,300,1,2,200]]`))) + }) + + It("should JSONArrLen", Label("json.arrlen"), func() { + cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrLen(ctx, "length2", "$..a") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{1, 6})) + }) + + It("should JSONArrPop", Label("json.arrpop"), func() { + cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]string{"300"})) + + cmd3 := client.JSONGet(ctx, "pop4", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) + }) + + It("should JSONArrTrim", func() { + cmd1 := client.JSONSet(ctx, "trim5", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrTrim(ctx, "trim5", "$..a", 1, 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{2, 2})) + + cmd3 := client.JSONGet(ctx, "trim5", "$.a") + Expect(cmd3.Val()).To(Equal("[[200,300]]")) + + cmd3 = client.JSONGet(ctx, "trim5", "$.b.a") + Expect(cmd3.Val()).To(Equal("[[200,300]]")) + }) + + }) + + Describe("get/set", Label("getset"), func() { + It("should JSONSet", Label("json.set"), func() { + cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`) + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal("OK")) + }) + + It("should JSONGet", Label("json.get"), func() { + cmd1 := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2, "c": {"hello": "world"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONGet(ctx, "get3", "$.*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(`[1,2,{"hello":"world"}]`)) + }) + + /* It("should Scan", Label("json.get"), func() { + cmd1 := client.JSONSet(ctx, "get4", "$", `{"a": 1, "b": 2, "c": {"hello": "golang"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONGet(ctx, "get4", "$.*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(len(cmd2.Val())).To(Equal(3)) + + test := JSONGetTestStruct{} + err := cmd2.Scan(2, &test) + Expect(err).NotTo(HaveOccurred()) + Expect(test.Hello).To(Equal("golang")) + }) + */ + + It("should JSONMGet", func() { + cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + cmd2 := client.JSONSet(ctx, "mget2b", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal("OK")) + + cmd3 := client.JSONMGet(ctx, "$..a", "mget2a", "mget2b") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(HaveLen(2)) + Expect(cmd3.Val()[0]).To(Equal(`[["aa","ab","ac","ad"],["ba","bb","bc","bd"]]`)) + Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`)) + }) + + }) + + Describe("Misc", Label("misc"), func() { + + It("should JSONClear", Label("json.clear"), func() { + cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONClear(ctx, "clear1", "$") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(1))) + + cmd3 := client.JSONGet(ctx, "clear1", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal(`[[]]`)) + }) + + It("should JSONDel", Label("json.del"), func() { + cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONDel(ctx, "del1", "$") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(1))) + + cmd3 := client.JSONGet(ctx, "del1", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(HaveLen(0)) + }) + + It("should JSONForget", Label("json.forget"), func() { + cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONForget(ctx, "forget3", "$..a") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(2))) + + cmd3 := client.JSONGet(ctx, "forget3", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal(`[{"b":{"b":"annie"}}]`)) + + }) + + It("should JSONNumIncrBy", Label("json.numincrby"), func() { + cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONNumIncrBy(ctx, "incr3", "$..a[1]", float64(1)) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(`[3,0]`)) + }) + + It("should JSONObjKeys", Label("json.objkeys"), func() { + cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONObjKeys(ctx, "objkeys1", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(7)) + Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil})) + }) + + It("should JSONObjLen", Label("json.objlen"), func() { + cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONObjLen(ctx, "objlen2", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(7)) + Expect(cmd2.Val()[0]).To(BeNil()) + Expect(*cmd2.Val()[1]).To(Equal(int64(1))) + }) + + It("should JSONStrLen", Label("json.strlen"), func() { + cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONStrLen(ctx, "strlen2", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(5)) + var tmp int64 = 20 + Expect(cmd2.Val()[0]).To(BeAssignableToTypeOf(&tmp)) + Expect(*cmd2.Val()[0]).To(Equal(int64(5))) + Expect(*cmd2.Val()[1]).To(Equal(int64(3))) + Expect(cmd2.Val()[2]).To(BeNil()) + Expect(*cmd2.Val()[3]).To(Equal(int64(5))) + Expect(*cmd2.Val()[4]).To(Equal(int64(3))) + }) + + It("should JSONToggle", Label("json.toggle"), func() { + cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONToggle(ctx, "toggle1", "$[0]") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(1)) + Expect(*cmd2.Val()[0]).To(Equal(int64(0))) + }) + + It("should JSONType", Label("json.type"), func() { + cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONType(ctx, "type1", "$[0]") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(1)) + // RESP2 v RESP3 + Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean"))) + }) + }) +}) From 8823bd063b2a1326a44aa6c5a14aa05cbfdd666a Mon Sep 17 00:00:00 2001 From: ofekshenawa Date: Mon, 9 Oct 2023 20:16:40 +0300 Subject: [PATCH 02/18] Add optional args and tests --- json.go | 168 ++++++++++++++++++++++++++++++++++++++++++++++----- json_test.go | 89 +++++++++++++++------------ 2 files changed, 203 insertions(+), 54 deletions(-) diff --git a/json.go b/json.go index 97c503c7ef..5aa8bac1e7 100644 --- a/json.go +++ b/json.go @@ -13,11 +13,12 @@ import ( type JSONCmdAble interface { JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd JSONArrIndex(ctx context.Context, key, path string, value interface{}) *IntSliceCmd - JSONArrIndexStartStop(ctx context.Context, key, path string, value interface{}, start, stop int64) *IntSliceCmd + JSONArrIndexWithArgs(ctx context.Context, key, path string, value interface{}, options *JSONArrIndexOptions) *IntSliceCmd JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd - JSONArrTrim(ctx context.Context, key, path string, start, stop int) *IntSliceCmd + JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd + JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd @@ -27,7 +28,7 @@ type JSONCmdAble interface { JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd - JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd + JSONSetMode(ctx context.Context, key, path string, value interface{}, mode *JSONSetModeOptions) *StatusCmd JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd @@ -277,16 +278,29 @@ func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value inter return cmd } +type JSONArrIndexOptions struct { + Start interface{} + Stop interface{} +} + // JSONArrIndexFromTo searches for the first occurrence of a JSON value in an array whilst allowing the start and // stop options to be provided. -func (c cmdable) JSONArrIndexStartStop(ctx context.Context, key, path string, value interface{}, start, stop int64) *IntSliceCmd { - args := []interface{}{"json.arrindex", key, path, value, start, stop} +func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, value interface{}, options *JSONArrIndexOptions) *IntSliceCmd { + args := []interface{}{"json.arrindex", key, path, value} + if options != nil { + if options.Start != nil { + args = append(args, options.Start) + } + if options.Stop != nil { + args = append(args, options.Stop) + } + } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONArrInsert inserts the json values into the array at path before the index (shifts to the right) +// // JSONArrInsert inserts the json values into the array at path before the index (shifts to the right) func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd { args := []interface{}{"json.arrinsert", key, path, index} args = append(args, values...) @@ -312,8 +326,29 @@ func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *S } // JSONArrTrim trims an array so that it contains only the specified inclusive range of elements -func (c cmdable) JSONArrTrim(ctx context.Context, key, path string, start, stop int) *IntSliceCmd { - args := []interface{}{"json.arrtrim", key, path, start, stop} +func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd { + args := []interface{}{"json.arrtrim", key, path} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type JSONArrTrimOptions struct { + Start interface{} + Stop interface{} +} + +// JSONArrTrimWithArgs trims an array so that it contains only the specified inclusive range of elements +func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd { + args := []interface{}{"json.arrtrim", key, path} + if options != nil { + if options.Start != nil { + args = append(args, options.Start) + } + if options.Stop != nil { + args = append(args, options.Stop) + } + } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd @@ -327,6 +362,14 @@ func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { return cmd } +// JSONDebugMemory report a value's memory usage in bytes +func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"JSON.DEBUG", "MEMORY", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // JSONDel deletes a value func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { args := []interface{}{"json.del", key, path} @@ -346,6 +389,7 @@ func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd { // JSONGet returns the value at path in JSON serialized form. JSON.GET returns an // array of strings. This function parses out the wrapping array but leaves the // internal strings unprocessed by default (see Val()) +// For more information - https://redis.io/commands/json.get/ func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd { args := make([]interface{}, len(paths)+2) args[0] = "json.get" @@ -358,6 +402,55 @@ func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSON return cmd } +type JSONGetArgs struct { + Path string + Paths []interface{} + Indention string + NewLine string + Space string +} + +// JSONGetWithArgs - Retrieves the value of a key from a JSON document. +// This function also allows for specifying additional options such as: +// Indention, NewLine, Space, Path and Paths. +// For more information - https://redis.io/commands/json.get/ +func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs) *JSONCmd { + args := []interface{}{"JSON.GET", key} + if options != nil { + if options.Indention != "" { + args = append(args, options.Indention) + } + if options.NewLine != "" { + args = append(args, options.NewLine) + } + if options.Space != "" { + args = append(args, options.Space) + } + if options.Path != "" && options.Paths != nil { + panic("redis: JSON.GET can only accept one of Path or Paths") + + } + if options.Path != "" { + args = append(args, options.Path) + } + if options.Paths != nil { + args = append(args, options.Paths...) + } + + } + cmd := NewJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONMerge merge a given JSON value into matching paths +func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd { + args := []interface{}{"JSON.MERGE", key, path, value} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // JSONMGet returns the values at path from multiple key arguments // Note - the arguments are reversed when compared with `JSON.MGET` as we want // to follow the pattern of having the last argument be variable. @@ -375,6 +468,23 @@ func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSO return cmd } +type JSONSetParams struct { + Key interface{} + Path string + Value interface{} +} + +// JSONMSet set or update one or more JSON values according to the specified key-path-value triplets +func (c cmdable) JSONMSet(ctx context.Context, docs []JSONSetParams) *StatusCmd { + args := []interface{}{"JSON.MSET"} + for _, doc := range docs { + args = append(args, doc.Key, doc.Path, doc.Value) + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // JSONNumIncrBy increments the number value stored at path by number func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { args := []interface{}{"json.numincrby", key, path, value} @@ -383,6 +493,22 @@ func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value floa return cmd } +// JSONNumMultBy multiply the number value stored at path by number +func (c cmdable) JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd { + args := []interface{}{"JSON.NUMMULTBY", key, path, value} + cmd := NewJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONNumMultBy multiply the number value stored at path by number +func (c cmdable) JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd { + args := []interface{}{"JSON.RESP", key, path} + cmd := NewMapStringInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // JSONObjKeys returns the keys in the object that's referenced by path func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { args := []interface{}{"json.objkeys", key, path} @@ -403,13 +529,18 @@ func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSl // can be marshalled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that // it can be passed directly as JSON. func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd { - return c.JSONSetMode(ctx, key, path, value, "") + return c.JSONSetMode(ctx, key, path, value, &JSONSetModeOptions{}) +} + +type JSONSetModeOptions struct { + XX bool + NX bool } -// JSONSetMOde sets the JSON value at the given path in the given key allows the mode to be set +// JSONSetMode sets the JSON value at the given path in the given key allows the mode to be set // as well (the mode value must be "XX" or "NX"). The value must be something that can be marshalled to JSON (using encoding/JSON) unless -// the argument is a string or []byte when we assume that it can be passed directly as JSON. -func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd { +// the argument is a string or []byte when we assume that it can be passed directly as JSON. +func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode *JSONSetModeOptions) *StatusCmd { var bytes []byte var err error @@ -425,12 +556,19 @@ func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interf args := []interface{}{"json.set", key, path, util.BytesToString(bytes)} - if mode != "" { - args = append(args, mode) + if mode != nil { + if mode.XX && mode.NX { + panic("redis: JSON.SET can only accept one of XX or NX") + } + if mode.XX { + args = append(args, "XX") + } + if mode.NX { + args = append(args, "NX") + } } cmd := NewStatusCmd(ctx, args...) - if err != nil { cmd.SetErr(err) } else { diff --git a/json_test.go b/json_test.go index d161853c51..3b680a595a 100644 --- a/json_test.go +++ b/json_test.go @@ -18,7 +18,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { var client *redis.Client BeforeEach(func() { - client = redis.NewClient(redisOptions()) + client = redis.NewClient(&redis.Options{Addr: ":6379"}) Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) }) @@ -48,16 +48,6 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]int64{1})) }) - It("should JSONArrIndexStartStop", Label("json.arrindex"), func() { - cmd1 := client.JSONSet(ctx, "index4", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) - - cmd2 := client.JSONArrIndexStartStop(ctx, "index4", "$.b.a", 12, 1, 4) - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(cmd2.Val()).To(Equal([]int64{3})) - }) - It("should JSONArrInsert", Label("json.arrinsert"), func() { cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -98,22 +88,22 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Err()).NotTo(HaveOccurred()) Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) }) + // TODO fix test + // It("should JSONArrTrim", func() { + // cmd1 := client.JSONSet(ctx, "trim5", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) + // Expect(cmd1.Err()).NotTo(HaveOccurred()) + // Expect(cmd1.Val()).To(Equal("OK")) - It("should JSONArrTrim", func() { - cmd1 := client.JSONSet(ctx, "trim5", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) + // cmd2 := client.JSONArrTrim(ctx, "trim5", "$..a", 1, 2) + // Expect(cmd2.Err()).NotTo(HaveOccurred()) + // Expect(cmd2.Val()).To(Equal([]int64{2, 2})) - cmd2 := client.JSONArrTrim(ctx, "trim5", "$..a", 1, 2) - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(cmd2.Val()).To(Equal([]int64{2, 2})) + // cmd3 := client.JSONGet(ctx, "trim5", "$.a") + // Expect(cmd3.Val()).To(Equal("[[200,300]]")) - cmd3 := client.JSONGet(ctx, "trim5", "$.a") - Expect(cmd3.Val()).To(Equal("[[200,300]]")) - - cmd3 = client.JSONGet(ctx, "trim5", "$.b.a") - Expect(cmd3.Val()).To(Equal("[[200,300]]")) - }) + // cmd3 = client.JSONGet(ctx, "trim5", "$.b.a") + // Expect(cmd3.Val()).To(Equal("[[200,300]]")) + // }) }) @@ -129,28 +119,26 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) - cmd2 := client.JSONGet(ctx, "get3", "$.*") + cmd2 := client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Path: "$.*"}) Expect(cmd2.Err()).NotTo(HaveOccurred()) Expect(cmd2.Val()).To(Equal(`[1,2,{"hello":"world"}]`)) }) - /* It("should Scan", Label("json.get"), func() { - cmd1 := client.JSONSet(ctx, "get4", "$", `{"a": 1, "b": 2, "c": {"hello": "golang"}}`) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) + It("should JSONMerge", Label("jsonmerge", "json"), func() { + cmd1, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) - cmd2 := client.JSONGet(ctx, "get4", "$.*") - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(len(cmd2.Val())).To(Equal(3)) + cmd2 := client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal("OK")) - test := JSONGetTestStruct{} - err := cmd2.Scan(2, &test) - Expect(err).NotTo(HaveOccurred()) - Expect(test.Hello).To(Equal("golang")) - }) - */ + cmd3, err := client.JSONGet(ctx, "merge1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal(`[{"a":1,"b":3,"c":4}]`)) + }) - It("should JSONMGet", func() { + It("should JSONMGet", Label("jsonmget", "json"), func() { cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -165,6 +153,29 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`)) }) + It("should JSONSetMode", func() { + cmd1, err := client.JSONSet(ctx, "doc1", "$", `{"foo": "bar"}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONSetMode(ctx, "doc1", "$.foo", "baz", &redis.JSONSetModeOptions{NX: true}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal("OK")) + + // cmd3, err := client.JSONSetMode(ctx, "doc1", "$.qaz", "baz", &redis.JSONSetModeOptions{XX: true}).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd3).To(Equal("OK")) + + // cmd4, err := client.JSONSetMode(ctx, "doc1", "$.foo", "baz", &redis.JSONSetModeOptions{XX: true}).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd4).To(Equal("OK")) + + // cmd5, err := client.JSONSetMode(ctx, "doc1", "$.qaz", "baz", &redis.JSONSetModeOptions{NX: true}).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd5).To(Equal("OK")) + + }) + }) Describe("Misc", Label("misc"), func() { From 3251b84ac171665a78a8c85e89cb41829f5c3d44 Mon Sep 17 00:00:00 2001 From: ofekshenawa Date: Wed, 11 Oct 2023 12:05:40 +0300 Subject: [PATCH 03/18] Add more tests --- json.go | 14 ++++---- json_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/json.go b/json.go index 5aa8bac1e7..8ade34cb6e 100644 --- a/json.go +++ b/json.go @@ -12,8 +12,8 @@ import ( type JSONCmdAble interface { JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd - JSONArrIndex(ctx context.Context, key, path string, value interface{}) *IntSliceCmd - JSONArrIndexWithArgs(ctx context.Context, key, path string, value interface{}, options *JSONArrIndexOptions) *IntSliceCmd + JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd + JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexOptions, value ...interface{}) *IntSliceCmd JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd @@ -271,8 +271,9 @@ func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ... } // JSONArrIndex searches for the first occurrence of a JSON value in an array -func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value interface{}) *IntSliceCmd { - args := []interface{}{"json.arrindex", key, path, value} +func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd { + args := []interface{}{"json.arrindex", key, path} + args = append(args, value...) cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd @@ -285,8 +286,9 @@ type JSONArrIndexOptions struct { // JSONArrIndexFromTo searches for the first occurrence of a JSON value in an array whilst allowing the start and // stop options to be provided. -func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, value interface{}, options *JSONArrIndexOptions) *IntSliceCmd { - args := []interface{}{"json.arrindex", key, path, value} +func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexOptions, value ...interface{}) *IntSliceCmd { + args := []interface{}{"json.arrindex", key, path} + args = append(args, value...) if options != nil { if options.Start != nil { args = append(args, options.Start) diff --git a/json_test.go b/json_test.go index 3b680a595a..31d831b5d2 100644 --- a/json_test.go +++ b/json_test.go @@ -38,14 +38,46 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]int64{2, 3})) }) - It("should JSONArrIndex", Label("json.arrindex"), func() { - cmd1 := client.JSONSet(ctx, "index3", "$", `{"a": [10], "b": {"a": [12, 10]}}`) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) + It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex"), func() { + cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]int64{1})) + + cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal("OK")) + + res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(1))) + + res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) + + res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + args := &redis.JSONArrIndexOptions{Start: 0} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) - cmd2 := client.JSONArrIndex(ctx, "index3", "$.b.a", 10) - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(cmd2.Val()).To(Equal([]int64{1})) }) It("should JSONArrInsert", Label("json.arrinsert"), func() { @@ -158,21 +190,19 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) - cmd2, err := client.JSONSetMode(ctx, "doc1", "$.foo", "baz", &redis.JSONSetModeOptions{NX: true}).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal("OK")) + _, err = client.JSONSetMode(ctx, "doc1", "$.foo", `"baz"`, &redis.JSONSetModeOptions{NX: true}).Result() + Expect(err).To(HaveOccurred()) - // cmd3, err := client.JSONSetMode(ctx, "doc1", "$.qaz", "baz", &redis.JSONSetModeOptions{XX: true}).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd3).To(Equal("OK")) + _, err = client.JSONSetMode(ctx, "doc1", "$.qaz", `"baz"`, &redis.JSONSetModeOptions{XX: true}).Result() + Expect(err).To(HaveOccurred()) - // cmd4, err := client.JSONSetMode(ctx, "doc1", "$.foo", "baz", &redis.JSONSetModeOptions{XX: true}).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd4).To(Equal("OK")) + cmd4, err := client.JSONSetMode(ctx, "doc1", "$.foo", `"baz"`, &redis.JSONSetModeOptions{XX: true}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd4).To(Equal("OK")) - // cmd5, err := client.JSONSetMode(ctx, "doc1", "$.qaz", "baz", &redis.JSONSetModeOptions{NX: true}).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd5).To(Equal("OK")) + cmd5, err := client.JSONSetMode(ctx, "doc1", "$.qaz", `"baz"`, &redis.JSONSetModeOptions{NX: true}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd5).To(Equal("OK")) }) @@ -233,6 +263,24 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal(`[3,0]`)) }) + It("should JSONNumMultBy", Label("json.nummultby"), func() { + cmd1, err := client.JSONSet(ctx, "num1", "$", `1`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONNumMultBy(ctx, "num1", "$", float64(2)).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal(`[2]`)) + + cmd2, err = client.JSONNumMultBy(ctx, "num1", "$", float64(2.5)).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal(`[5]`)) + + cmd2, err = client.JSONNumMultBy(ctx, "num1", "$", float64(0.5)).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal(`[2.5]`)) + }) + It("should JSONObjKeys", Label("json.objkeys"), func() { cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -273,6 +321,19 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(*cmd2.Val()[4]).To(Equal(int64(3))) }) + It("should JSONStrAppend", Label("json.strappend"), func() { + cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + cmd2, err := client.JSONStrAppend(ctx, "strapp1", "$", `"bar"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*cmd2[0]).To(Equal(int64(6))) + cmd3, err := client.JSONGet(ctx, "strapp1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal(`["foobar"]`)) + + }) + It("should JSONToggle", Label("json.toggle"), func() { cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) From f0d7f05f0cb349abf8480fdf0cb8980fa579e418 Mon Sep 17 00:00:00 2001 From: ofekshenawa Date: Mon, 16 Oct 2023 02:29:29 +0300 Subject: [PATCH 04/18] Add more tests --- json.go | 8 - json_test.go | 414 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 357 insertions(+), 65 deletions(-) diff --git a/json.go b/json.go index 8ade34cb6e..c626e0f250 100644 --- a/json.go +++ b/json.go @@ -503,14 +503,6 @@ func (c cmdable) JSONNumMultBy(ctx context.Context, key, path string, value floa return cmd } -// JSONNumMultBy multiply the number value stored at path by number -func (c cmdable) JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd { - args := []interface{}{"JSON.RESP", key, path} - cmd := NewMapStringInterfaceCmd(ctx, args...) - _ = c(ctx, cmd) - return cmd -} - // JSONObjKeys returns the keys in the object that's referenced by path func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { args := []interface{}{"json.objkeys", key, path} diff --git a/json_test.go b/json_test.go index 31d831b5d2..6443caa31c 100644 --- a/json_test.go +++ b/json_test.go @@ -38,46 +38,97 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]int64{2, 3})) }) - It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex"), func() { - cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd1).To(Equal("OK")) - - cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal([]int64{1})) - - cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd3).To(Equal("OK")) - - res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(1))) + // It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex"), func() { + // cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd1).To(Equal("OK")) + + // cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd2).To(Equal([]int64{1})) + + // cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(cmd3).To(Equal("OK")) + + // res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(1))) + + // res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(-1))) + + // res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(4))) + + // args := &redis.JSONArrIndexOptions{Start: 0} + // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(4))) + + // args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} + // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(4))) + + // args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} + // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + // Expect(err).NotTo(HaveOccurred()) + // Expect(res[0]).To(Equal(int64(-1))) - res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(-1))) - - res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(4))) + // }) - args := &redis.JSONArrIndexOptions{Start: 0} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex"), func() { + doc := `{ + "store": { + "book": [ + { + "category": "reference", + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95, + "size": [10, 20, 30, 40] + }, + { + "category": "fiction", + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99, + "size": [50, 60, 70, 80] + }, + { + "category": "fiction", + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99, + "size": [5, 10, 20, 30] + }, + { + "category": "fiction", + "author": "J. R. R. Tolkien", + "title": "The Lord of the Rings", + "isbn": "0-395-19395-8", + "price": 22.99, + "size": [5, 6, 7, 8] + } + ], + "bicycle": {"color": "red", "price": 19.95} + } + }` + cmd, err := client.JSONSet(ctx, "doc1", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(4))) + Expect(cmd).To(Equal("OK")) - args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(4))) + Expect(resGet).To(Equal("[[10,20,30,40],[5,10,20,30]]")) - args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + resArr, err := client.JSONArrIndex(ctx, "doc1", "$.store.book[?(@.price<10)].size", 20).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res[0]).To(Equal(int64(-1))) - + Expect(resArr).To(Equal([]int64{1, 2})) }) It("should JSONArrInsert", Label("json.arrinsert"), func() { @@ -120,22 +171,71 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Err()).NotTo(HaveOccurred()) Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) }) - // TODO fix test - // It("should JSONArrTrim", func() { - // cmd1 := client.JSONSet(ctx, "trim5", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) - // Expect(cmd1.Err()).NotTo(HaveOccurred()) - // Expect(cmd1.Val()).To(Equal("OK")) - // cmd2 := client.JSONArrTrim(ctx, "trim5", "$..a", 1, 2) - // Expect(cmd2.Err()).NotTo(HaveOccurred()) - // Expect(cmd2.Val()).To(Equal([]int64{2, 2})) + It("should JSONArrTrim", func() { + cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) - // cmd3 := client.JSONGet(ctx, "trim5", "$.a") - // Expect(cmd3.Val()).To(Equal("[[200,300]]")) + opt := &redis.JSONArrTrimOptions{Start: 1, Stop: 3} + cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]int64{3})) - // cmd3 = client.JSONGet(ctx, "trim5", "$.b.a") - // Expect(cmd3.Val()).To(Equal("[[200,300]]")) - // }) + res, err := client.JSONGet(ctx, "trim1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[[1,2,3]]`)) + + cmd3, err := client.JSONSet(ctx, "trim2", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal("OK")) + + opt = &redis.JSONArrTrimOptions{Start: -1, Stop: 3} + cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd4).To(Equal([]int64{0})) + + cmd5, err := client.JSONSet(ctx, "trim3", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd5).To(Equal("OK")) + + opt = &redis.JSONArrTrimOptions{Start: 3, Stop: 99} + cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd6).To(Equal([]int64{2})) + + cmd7, err := client.JSONSet(ctx, "trim4", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd7).To(Equal("OK")) + + opt = &redis.JSONArrTrimOptions{Start: 9, Stop: 1} + cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd8).To(Equal([]int64{0})) + + cmd9, err := client.JSONSet(ctx, "trim5", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd9).To(Equal("OK")) + + opt = &redis.JSONArrTrimOptions{Start: 9, Stop: 11} + cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd10).To(Equal([]int64{0})) + }) + + It("should JSONArrPop", Label("json.arrpop"), func() { + cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]string{"300"})) + + cmd3 := client.JSONGet(ctx, "pop4", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) + }) }) @@ -185,24 +285,26 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`)) }) - It("should JSONSetMode", func() { - cmd1, err := client.JSONSet(ctx, "doc1", "$", `{"foo": "bar"}`).Result() + It("should JSONMget with $", Label("json.mget"), func() { + cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd1).To(Equal("OK")) + Expect(cmd).To(Equal("OK")) - _, err = client.JSONSetMode(ctx, "doc1", "$.foo", `"baz"`, &redis.JSONSetModeOptions{NX: true}).Result() - Expect(err).To(HaveOccurred()) + cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) - _, err = client.JSONSetMode(ctx, "doc1", "$.qaz", `"baz"`, &redis.JSONSetModeOptions{XX: true}).Result() - Expect(err).To(HaveOccurred()) + res, err := client.JSONMGet(ctx, "$..a", "doc1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal([]interface{}{`[1,3,""]`})) - cmd4, err := client.JSONSetMode(ctx, "doc1", "$.foo", `"baz"`, &redis.JSONSetModeOptions{XX: true}).Result() + res, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd4).To(Equal("OK")) + Expect(res).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`})) - cmd5, err := client.JSONSetMode(ctx, "doc1", "$.qaz", `"baz"`, &redis.JSONSetModeOptions{NX: true}).Result() + res, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd5).To(Equal("OK")) + Expect(res).To(Equal([]interface{}{nil, nil})) }) @@ -224,6 +326,38 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()).To(Equal(`[[]]`)) }) + It("should JSONClear with $", Label("json.clear"), func() { + doc := `{ + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": "claro"}, + "nested3": {"a": {"baz": 50}} + }` + cmd, err := client.JSONSet(ctx, "doc1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err := client.JSONClear(ctx, "doc1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(3))) + + resGet, err := client.JSONGet(ctx, "doc1", `$`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`)) + + cmd, err = client.JSONSet(ctx, "doc1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "doc1", `$`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`)) + }) + It("should JSONDel", Label("json.del"), func() { cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -238,6 +372,56 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()).To(HaveLen(0)) }) + It("should JSONDel with $", Label("json.del"), func() { + cmd, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err := client.JSONDel(ctx, "del1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(2))) + + resGet, err := client.JSONGet(ctx, "del1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) + + cmd, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONDel(ctx, "del2", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "del2", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`)) + + doc := `[ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]} + ] + } + ]` + cmd, err = client.JSONSet(ctx, "del3", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(3))) + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` + resGet, err = client.JSONGet(ctx, "del3", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(resVal)) + }) + It("should JSONForget", Label("json.forget"), func() { cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -253,6 +437,56 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) + It("should JSONForget with $", Label("json.forget"), func() { + cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err := client.JSONForget(ctx, "doc1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(2))) + + resGet, err := client.JSONGet(ctx, "doc1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) + + cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONForget(ctx, "doc2", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "doc2", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`)) + + doc := `[ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]} + ] + } + ]` + cmd, err = client.JSONSet(ctx, "doc3", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(int64(3))) + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` + resGet, err = client.JSONGet(ctx, "doc3", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(resVal)) + }) + It("should JSONNumIncrBy", Label("json.numincrby"), func() { cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -263,6 +497,28 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal(`[3,0]`)) }) + It("should JSONNumIncrBy with $", Label("json.numincrby"), func() { + cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + cmd, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal(`[7]`)) + + cmd, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal(`[10.5]`)) + + cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + cmd, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal(`[5]`)) + }) + It("should JSONNumMultBy", Label("json.nummultby"), func() { cmd1, err := client.JSONSet(ctx, "num1", "$", `1`).Result() Expect(err).NotTo(HaveOccurred()) @@ -292,6 +548,32 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil})) }) + It("should JSONObjKeys with $", Label("json.objkeys"), func() { + doc := `{ + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": {"baz": 50}} + }` + cmd1, err := client.JSONSet(ctx, "objkeys1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONObjKeys(ctx, "objkeys1", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{[]interface{}{"foo", "bar"}})) + + cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".*.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{"foo", "bar"})) + + cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".nested2.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{"baz"})) + + _, err = client.JSONObjKeys(ctx, "non_existing_doc", "..a").Result() + Expect(err).To(HaveOccurred()) + }) + It("should JSONObjLen", Label("json.objlen"), func() { cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) @@ -334,6 +616,24 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) + It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strappend"), func() { + cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*res[0]).To(Equal(int64(8))) + + cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd).To(Equal("OK")) + + res, err = client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*res[0]).To(Equal(int64(5))) + }) + It("should JSONToggle", Label("json.toggle"), func() { cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) From e665d9d1c4fe8d6273d2ee5afe0b3eaf38fe72da Mon Sep 17 00:00:00 2001 From: ofekshenawa Date: Mon, 16 Oct 2023 03:02:02 +0300 Subject: [PATCH 05/18] Add more tests and cleanups --- json.go | 7 ++- json_test.go | 137 +++++++++++++++++++++++++++++---------------------- 2 files changed, 83 insertions(+), 61 deletions(-) diff --git a/json.go b/json.go index c626e0f250..ec80b307a1 100644 --- a/json.go +++ b/json.go @@ -20,11 +20,16 @@ type JSONCmdAble interface { JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd + JSONDebugMemory(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd + JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs) *JSONCmd + JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd + JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd + JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd @@ -477,7 +482,7 @@ type JSONSetParams struct { } // JSONMSet set or update one or more JSON values according to the specified key-path-value triplets -func (c cmdable) JSONMSet(ctx context.Context, docs []JSONSetParams) *StatusCmd { +func (c cmdable) JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd { args := []interface{}{"JSON.MSET"} for _, doc := range docs { args = append(args, doc.Key, doc.Path, doc.Value) diff --git a/json_test.go b/json_test.go index 6443caa31c..75e46545b5 100644 --- a/json_test.go +++ b/json_test.go @@ -28,7 +28,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Describe("arrays", Label("arrays"), func() { - It("should JSONArrAppend", Label("json.arrappend"), func() { + It("should JSONArrAppend", Label("json.arrappend", "json"), func() { cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -38,49 +38,49 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]int64{2, 3})) }) - // It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex"), func() { - // cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd1).To(Equal("OK")) + It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex", "json"), func() { + cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) - // cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd2).To(Equal([]int64{1})) + cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]int64{1})) - // cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(cmd3).To(Equal("OK")) + cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal("OK")) - // res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(1))) + res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(1))) - // res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(-1))) + res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) - // res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(4))) + res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) - // args := &redis.JSONArrIndexOptions{Start: 0} - // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(4))) + args := &redis.JSONArrIndexOptions{Start: 0} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) - // args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} - // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(4))) + args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) - // args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} - // res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() - // Expect(err).NotTo(HaveOccurred()) - // Expect(res[0]).To(Equal(int64(-1))) + args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) - // }) + }) - It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex"), func() { + It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex", "json"), func() { doc := `{ "store": { "book": [ @@ -131,7 +131,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(resArr).To(Equal([]int64{1, 2})) }) - It("should JSONArrInsert", Label("json.arrinsert"), func() { + It("should JSONArrInsert", Label("json.arrinsert", "json"), func() { cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -148,7 +148,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Equal(`[[100,200,300,1,2,200]]`))) }) - It("should JSONArrLen", Label("json.arrlen"), func() { + It("should JSONArrLen", Label("json.arrlen", "json"), func() { cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -172,7 +172,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) }) - It("should JSONArrTrim", func() { + It("should JSONArrTrim", Label("json.arrtrim", "json"), func() { cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) @@ -223,7 +223,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd10).To(Equal([]int64{0})) }) - It("should JSONArrPop", Label("json.arrpop"), func() { + It("should JSONArrPop", Label("json.arrpop", "json"), func() { cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -240,13 +240,13 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) Describe("get/set", Label("getset"), func() { - It("should JSONSet", Label("json.set"), func() { + It("should JSONSet", Label("json.set", "json"), func() { cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`) Expect(cmd.Err()).NotTo(HaveOccurred()) Expect(cmd.Val()).To(Equal("OK")) }) - It("should JSONGet", Label("json.get"), func() { + It("should JSONGet", Label("json.get", "json"), func() { cmd1 := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2, "c": {"hello": "world"}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -256,7 +256,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal(`[1,2,{"hello":"world"}]`)) }) - It("should JSONMerge", Label("jsonmerge", "json"), func() { + It("should JSONMerge", Label("json.merge", "json"), func() { cmd1, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) @@ -270,7 +270,24 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3).To(Equal(`[{"a":1,"b":3,"c":4}]`)) }) - It("should JSONMGet", Label("jsonmget", "json"), func() { + It("should JSONMSet", Label("json.mset", "json"), func() { + doc1 := &redis.JSONSetParams{Key: "1", Path: "$", Value: 1} + doc2 := &redis.JSONSetParams{Key: "2", Path: "$", Value: 2} + docs := []*redis.JSONSetParams{doc1, doc2} + cmd1 := client.JSONMSet(ctx, docs) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2, err := client.JSONMGet(ctx, "$", "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{"[1]"})) + + cmd3, err := client.JSONMGet(ctx, "$", "1", "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal([]interface{}{"[1]", "[2]"})) + }) + + It("should JSONMGet", Label("json.mget", "json"), func() { cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -285,7 +302,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`)) }) - It("should JSONMget with $", Label("json.mget"), func() { + It("should JSONMget with $", Label("json.mget", "json"), func() { cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd).To(Equal("OK")) @@ -312,7 +329,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Describe("Misc", Label("misc"), func() { - It("should JSONClear", Label("json.clear"), func() { + It("should JSONClear", Label("json.clear", "json"), func() { cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -326,7 +343,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()).To(Equal(`[[]]`)) }) - It("should JSONClear with $", Label("json.clear"), func() { + It("should JSONClear with $", Label("json.clear", "json"), func() { doc := `{ "nested1": {"a": {"foo": 10, "bar": 20}}, "a": ["foo"], @@ -358,7 +375,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`)) }) - It("should JSONDel", Label("json.del"), func() { + It("should JSONDel", Label("json.del", "json"), func() { cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -372,7 +389,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3.Val()).To(HaveLen(0)) }) - It("should JSONDel with $", Label("json.del"), func() { + It("should JSONDel with $", Label("json.del", "json"), func() { cmd, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd).To(Equal("OK")) @@ -422,7 +439,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(resGet).To(Equal(resVal)) }) - It("should JSONForget", Label("json.forget"), func() { + It("should JSONForget", Label("json.forget", "json"), func() { cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -437,7 +454,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) - It("should JSONForget with $", Label("json.forget"), func() { + It("should JSONForget with $", Label("json.forget", "json"), func() { cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd).To(Equal("OK")) @@ -487,7 +504,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(resGet).To(Equal(resVal)) }) - It("should JSONNumIncrBy", Label("json.numincrby"), func() { + It("should JSONNumIncrBy", Label("json.numincrby", "json"), func() { cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -497,7 +514,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal(`[3,0]`)) }) - It("should JSONNumIncrBy with $", Label("json.numincrby"), func() { + It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() { cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd).To(Equal("OK")) @@ -519,7 +536,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd).To(Equal(`[5]`)) }) - It("should JSONNumMultBy", Label("json.nummultby"), func() { + It("should JSONNumMultBy", Label("json.nummultby", "json"), func() { cmd1, err := client.JSONSet(ctx, "num1", "$", `1`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) @@ -537,7 +554,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2).To(Equal(`[2.5]`)) }) - It("should JSONObjKeys", Label("json.objkeys"), func() { + It("should JSONObjKeys", Label("json.objkeys", "json"), func() { cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -548,7 +565,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil})) }) - It("should JSONObjKeys with $", Label("json.objkeys"), func() { + It("should JSONObjKeys with $", Label("json.objkeys", "json"), func() { doc := `{ "nested1": {"a": {"foo": 10, "bar": 20}}, "a": ["foo"], @@ -574,7 +591,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).To(HaveOccurred()) }) - It("should JSONObjLen", Label("json.objlen"), func() { + It("should JSONObjLen", Label("json.objlen", "json"), func() { cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -586,7 +603,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(*cmd2.Val()[1]).To(Equal(int64(1))) }) - It("should JSONStrLen", Label("json.strlen"), func() { + It("should JSONStrLen", Label("json.strlen", "json"), func() { cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -603,7 +620,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(*cmd2.Val()[4]).To(Equal(int64(3))) }) - It("should JSONStrAppend", Label("json.strappend"), func() { + It("should JSONStrAppend", Label("json.strappend", "json"), func() { cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) @@ -616,7 +633,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) - It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strappend"), func() { + It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() { cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd).To(Equal("OK")) @@ -634,7 +651,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(*res[0]).To(Equal(int64(5))) }) - It("should JSONToggle", Label("json.toggle"), func() { + It("should JSONToggle", Label("json.toggle", "json"), func() { cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) @@ -645,7 +662,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(*cmd2.Val()[0]).To(Equal(int64(0))) }) - It("should JSONType", Label("json.type"), func() { + It("should JSONType", Label("json.type", "json"), func() { cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) From ffa21ad29739ef0c936d8802537045e6897b8e77 Mon Sep 17 00:00:00 2001 From: ofekshenawa Date: Mon, 16 Oct 2023 11:49:15 +0300 Subject: [PATCH 06/18] Add docstring --- json.go | 173 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/json.go b/json.go index ec80b307a1..ff31032536 100644 --- a/json.go +++ b/json.go @@ -40,6 +40,27 @@ type JSONCmdAble interface { JSONType(ctx context.Context, key, path string) *JSONSliceCmd } +type JSONSetParams struct { + Key interface{} + Path string + Value interface{} +} + +type JSONArrIndexOptions struct { + Start interface{} + Stop interface{} +} + +type JSONArrTrimOptions struct { + Start interface{} + Stop interface{} +} + +type JSONSetModeOptions struct { + XX bool + NX bool +} + type JSONCmd struct { baseCmd val string @@ -266,33 +287,31 @@ func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ -// JSONArrAppend adds the provided json values to the end of the at path +// JSONArrAppend adds the provided JSON values to the end of the array at the given path. +// For more information, see https://redis.io/commands/json.arrappend func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd { - args := []interface{}{"json.arrappend", key, path} + args := []interface{}{"JSON.ARRAPPEND", key, path} args = append(args, values...) cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONArrIndex searches for the first occurrence of a JSON value in an array +// JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path. +// For more information, see https://redis.io/commands/json.arrindex func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd { - args := []interface{}{"json.arrindex", key, path} + args := []interface{}{"JSON.ARRINDEX", key, path} args = append(args, value...) cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -type JSONArrIndexOptions struct { - Start interface{} - Stop interface{} -} - -// JSONArrIndexFromTo searches for the first occurrence of a JSON value in an array whilst allowing the start and +// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and // stop options to be provided. +// For more information, see https://redis.io/commands/json.arrindex func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexOptions, value ...interface{}) *IntSliceCmd { - args := []interface{}{"json.arrindex", key, path} + args := []interface{}{"JSON.ARRINDEX", key, path} args = append(args, value...) if options != nil { if options.Start != nil { @@ -307,47 +326,47 @@ func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, opt return cmd } -// // JSONArrInsert inserts the json values into the array at path before the index (shifts to the right) +// JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right). +// For more information, see https://redis.io/commands/json.arrinsert func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd { - args := []interface{}{"json.arrinsert", key, path, index} + args := []interface{}{"JSON.ARRINSERT", key, path, index} args = append(args, values...) cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONArrLen reports the length of the JSON array at path in key +// JSONArrLen reports the length of the JSON array at the specified path in the given key. +// For more information, see https://redis.io/commands/json.arrlen func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd { - args := []interface{}{"json.arrlen", key, path} + args := []interface{}{"JSON.ARRLEN", key, path} cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONArrPop removes and returns an element from the index in the array +// JSONArrPop removes and returns an element from the specified index in the array. +// For more information, see https://redis.io/commands/json.arrpop func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd { - args := []interface{}{"json.arrpop", key, path, index} + args := []interface{}{"JSON.ARRPOP", key, path, index} cmd := NewStringSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONArrTrim trims an array so that it contains only the specified inclusive range of elements +// JSONArrTrim trims an array to contain only the specified inclusive range of elements. +// For more information, see https://redis.io/commands/json.arrtrim func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd { - args := []interface{}{"json.arrtrim", key, path} + args := []interface{}{"JSON.ARRTRIM", key, path} cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -type JSONArrTrimOptions struct { - Start interface{} - Stop interface{} -} - -// JSONArrTrimWithArgs trims an array so that it contains only the specified inclusive range of elements +// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements. +// For more information, see https://redis.io/commands/json.arrtrim func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd { - args := []interface{}{"json.arrtrim", key, path} + args := []interface{}{"JSON.ARRTRIM", key, path} if options != nil { if options.Start != nil { args = append(args, options.Start) @@ -361,15 +380,17 @@ func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, opti return cmd } -// JSONClear clears container values (arrays/objects) and set numeric values to 0 +// JSONClear clears container values (arrays/objects) and sets numeric values to 0. +// For more information, see https://redis.io/commands/json.clear func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { - args := []interface{}{"json.clear", key, path} + args := []interface{}{"JSON.CLEAR", key, path} cmd := NewIntCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONDebugMemory report a value's memory usage in bytes +// JSONDebugMemory reports a value's memory usage in bytes. +// For more information, see https://redis.io/commands/json.debug.memory func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd { args := []interface{}{"JSON.DEBUG", "MEMORY", key, path} cmd := NewIntCmd(ctx, args...) @@ -377,17 +398,19 @@ func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd return cmd } -// JSONDel deletes a value +// JSONDel deletes a value. +// For more information, see https://redis.io/commands/json.del func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { - args := []interface{}{"json.del", key, path} + args := []interface{}{"JSON.DEL", key, path} cmd := NewIntCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONForget deletes a value +// JSONForget deletes a value. +// For more information, see https://redis.io/commands/json.forget func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd { - args := []interface{}{"json.forget", key, path} + args := []interface{}{"JSON.FORGET", key, path} cmd := NewIntCmd(ctx, args...) _ = c(ctx, cmd) return cmd @@ -399,7 +422,7 @@ func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd { // For more information - https://redis.io/commands/json.get/ func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd { args := make([]interface{}, len(paths)+2) - args[0] = "json.get" + args[0] = "JSON.GET" args[1] = key for n, path := range paths { args[n+2] = path @@ -450,7 +473,8 @@ func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONG return cmd } -// JSONMerge merge a given JSON value into matching paths +// JSONMerge merges a given JSON value into matching paths. +// For more information, see https://redis.io/commands/json.merge func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd { args := []interface{}{"JSON.MERGE", key, path, value} cmd := NewStatusCmd(ctx, args...) @@ -458,30 +482,24 @@ func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) return cmd } -// JSONMGet returns the values at path from multiple key arguments +// JSONMGet returns the values at the specified path from multiple key arguments. // Note - the arguments are reversed when compared with `JSON.MGET` as we want // to follow the pattern of having the last argument be variable. +// For more information, see https://redis.io/commands/json.mget func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd { - args := make([]interface{}, len(keys)+1) - args[0] = "json.mget" - for n, keys := range keys { - args[n+1] = keys + args[0] = "JSON.MGET" + for n, key := range keys { + args[n+1] = key } args = append(args, path) - cmd := NewJSONSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -type JSONSetParams struct { - Key interface{} - Path string - Value interface{} -} - -// JSONMSet set or update one or more JSON values according to the specified key-path-value triplets +// JSONMSet sets or updates one or more JSON values according to the specified key-path-value triplets. +// For more information, see https://redis.io/commands/json.mset func (c cmdable) JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd { args := []interface{}{"JSON.MSET"} for _, doc := range docs { @@ -492,15 +510,17 @@ func (c cmdable) JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd return cmd } -// JSONNumIncrBy increments the number value stored at path by number +// JSONNumIncrBy increments the number value stored at the specified path by the provided number. +// For more information, see https://redis.io/commands/json.numincreby func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { - args := []interface{}{"json.numincrby", key, path, value} + args := []interface{}{"JSON.NUMINCRBY", key, path, value} cmd := NewJSONCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONNumMultBy multiply the number value stored at path by number +// JSONNumMultBy multiplies the number value stored at the specified path by the provided number. +// For more information, see https://redis.io/commands/json.nummultby func (c cmdable) JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd { args := []interface{}{"JSON.NUMMULTBY", key, path, value} cmd := NewJSONCmd(ctx, args...) @@ -508,42 +528,39 @@ func (c cmdable) JSONNumMultBy(ctx context.Context, key, path string, value floa return cmd } -// JSONObjKeys returns the keys in the object that's referenced by path +// JSONObjKeys returns the keys in the object that's referenced by the specified path. +// For more information, see https://redis.io/commands/json.objkeys func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { - args := []interface{}{"json.objkeys", key, path} + args := []interface{}{"JSON.OBJKEYS", key, path} cmd := NewSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONObjLen reports the number of keys in the JSON object at path in key +// JSONObjLen reports the number of keys in the JSON object at the specified path in the given key. +// For more information, see https://redis.io/commands/json.objlen func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd { - args := []interface{}{"json.objlen", key, path} + args := []interface{}{"JSON.OBJLEN", key, path} cmd := NewIntPointerSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } // JSONSet sets the JSON value at the given path in the given key. The value must be something that -// can be marshalled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that +// can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that // it can be passed directly as JSON. +// For more information, see https://redis.io/commands/json.set func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd { return c.JSONSetMode(ctx, key, path, value, &JSONSetModeOptions{}) } -type JSONSetModeOptions struct { - XX bool - NX bool -} - -// JSONSetMode sets the JSON value at the given path in the given key allows the mode to be set -// as well (the mode value must be "XX" or "NX"). The value must be something that can be marshalled to JSON (using encoding/JSON) unless +// JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set +// (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless // the argument is a string or []byte when we assume that it can be passed directly as JSON. +// For more information, see https://redis.io/commands/json.set func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode *JSONSetModeOptions) *StatusCmd { - var bytes []byte var err error - switch v := value.(type) { case string: bytes = []byte(v) @@ -552,9 +569,7 @@ func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interf default: bytes, err = json.Marshal(v) } - - args := []interface{}{"json.set", key, path, util.BytesToString(bytes)} - + args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)} if mode != nil { if mode.XX && mode.NX { panic("redis: JSON.SET can only accept one of XX or NX") @@ -566,44 +581,46 @@ func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interf args = append(args, "NX") } } - cmd := NewStatusCmd(ctx, args...) if err != nil { cmd.SetErr(err) } else { _ = c(ctx, cmd) } - return cmd } -// JSONStrAppend appends the json-string values to the string at path +// JSONStrAppend appends the JSON-string values to the string at the specified path. +// For more information, see https://redis.io/commands/json.strappend func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd { - args := []interface{}{"json.strappend", key, path, value} + args := []interface{}{"JSON.STRAPPEND", key, path, value} cmd := NewIntPointerSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONStrLen reports the length of the JSON String at path in key +// JSONStrLen reports the length of the JSON String at the specified path in the given key. +// For more information, see https://redis.io/commands/json.strlen func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd { - args := []interface{}{"json.strlen", key, path} + args := []interface{}{"JSON.STRLEN", key, path} cmd := NewIntPointerSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONToggle toggles a Boolean value stored at path +// JSONToggle toggles a Boolean value stored at the specified path. +// For more information, see https://redis.io/commands/json.toggle func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd { - args := []interface{}{"json.toggle", key, path} + args := []interface{}{"JSON.TOGGLE", key, path} cmd := NewIntPointerSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -// JSONType reports the type of JSON value at path +// JSONType reports the type of JSON value at the specified path. +// For more information, see https://redis.io/commands/json.type func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd { - args := []interface{}{"json.type", key, path} + args := []interface{}{"JSON.TYPE", key, path} cmd := NewJSONSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd From abe1831a76457f67f69e03c3ed4995301210c110 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 14:19:40 +0100 Subject: [PATCH 07/18] update JSONArrIndex and matching texts Rename JSONArrIndexWithArgs to JSONArrIndexArgs change name of args struct to match function name for consistency change arg types to int (for required arg) and *int (for optional) --- json.go | 22 +++++++++------------- json_test.go | 11 +++++------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/json.go b/json.go index ff31032536..a977a58339 100644 --- a/json.go +++ b/json.go @@ -13,7 +13,7 @@ import ( type JSONCmdAble interface { JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd - JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexOptions, value ...interface{}) *IntSliceCmd + JSONArrIndexArgs(ctx context.Context, key, path string, options JSONArrIndexArgs, value ...interface{}) *IntSliceCmd JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd @@ -46,9 +46,9 @@ type JSONSetParams struct { Value interface{} } -type JSONArrIndexOptions struct { - Start interface{} - Stop interface{} +type JSONArrIndexArgs struct { + Start int + Stop *int } type JSONArrTrimOptions struct { @@ -307,19 +307,15 @@ func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...in return cmd } -// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and +// JSONArrIndexArgs searches for the first occurrence of a JSON value in an array while allowing the start and // stop options to be provided. // For more information, see https://redis.io/commands/json.arrindex -func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexOptions, value ...interface{}) *IntSliceCmd { +func (c cmdable) JSONArrIndexArgs(ctx context.Context, key, path string, options JSONArrIndexArgs, value ...interface{}) *IntSliceCmd { args := []interface{}{"JSON.ARRINDEX", key, path} args = append(args, value...) - if options != nil { - if options.Start != nil { - args = append(args, options.Start) - } - if options.Stop != nil { - args = append(args, options.Stop) - } + args = append(args, options.Start) + if options.Stop != nil { + args = append(args, *options.Stop) } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) diff --git a/json_test.go b/json_test.go index 75e46545b5..057dfa3330 100644 --- a/json_test.go +++ b/json_test.go @@ -63,18 +63,17 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) - args := &redis.JSONArrIndexOptions{Start: 0} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) - args = &redis.JSONArrIndexOptions{Start: 0, Stop: 5000} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + stop := 5000 + res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) - args = &redis.JSONArrIndexOptions{Start: 0, Stop: -1} - res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", args, 4).Result() + stop = -1 + res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(-1))) From 247097c3cb7865b00124917da7198246356fb105 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 15:06:41 +0100 Subject: [PATCH 08/18] update JSONArrTrim and matching texts Rename JSONArrTrimWithArgs to JSONArrTrimArgs change name of args struct to match function name for consistency change arg types to int (for required arg) and *int (for optional) --- json.go | 22 +++++++++------------- json_test.go | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/json.go b/json.go index a977a58339..f226de944f 100644 --- a/json.go +++ b/json.go @@ -18,7 +18,7 @@ type JSONCmdAble interface { JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd - JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd + JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd JSONDebugMemory(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd @@ -51,9 +51,9 @@ type JSONArrIndexArgs struct { Stop *int } -type JSONArrTrimOptions struct { - Start interface{} - Stop interface{} +type JSONArrTrimArgs struct { + Start int + Stop *int } type JSONSetModeOptions struct { @@ -359,17 +359,13 @@ func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd return cmd } -// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements. +// JSONArrTrimArgs trims an array to contain only the specified inclusive range of elements. // For more information, see https://redis.io/commands/json.arrtrim -func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimOptions) *IntSliceCmd { +func (c cmdable) JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd { args := []interface{}{"JSON.ARRTRIM", key, path} - if options != nil { - if options.Start != nil { - args = append(args, options.Start) - } - if options.Stop != nil { - args = append(args, options.Stop) - } + args = append(args, options.Start) + if options.Stop != nil { + args = append(args, *options.Stop) } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) diff --git a/json_test.go b/json_test.go index 057dfa3330..9adf02c8d8 100644 --- a/json_test.go +++ b/json_test.go @@ -176,8 +176,8 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd1).To(Equal("OK")) - opt := &redis.JSONArrTrimOptions{Start: 1, Stop: 3} - cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", opt).Result() + stop := 3 + cmd2, err := client.JSONArrTrimArgs(ctx, "trim1", "$", redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd2).To(Equal([]int64{3})) @@ -189,8 +189,8 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd3).To(Equal("OK")) - opt = &redis.JSONArrTrimOptions{Start: -1, Stop: 3} - cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", opt).Result() + stop = 3 + cmd4, err := client.JSONArrTrimArgs(ctx, "trim2", "$", redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd4).To(Equal([]int64{0})) @@ -198,8 +198,8 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd5).To(Equal("OK")) - opt = &redis.JSONArrTrimOptions{Start: 3, Stop: 99} - cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", opt).Result() + stop = 99 + cmd6, err := client.JSONArrTrimArgs(ctx, "trim3", "$", redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd6).To(Equal([]int64{2})) @@ -207,8 +207,8 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd7).To(Equal("OK")) - opt = &redis.JSONArrTrimOptions{Start: 9, Stop: 1} - cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", opt).Result() + stop = 1 + cmd8, err := client.JSONArrTrimArgs(ctx, "trim4", "$", redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd8).To(Equal([]int64{0})) @@ -216,8 +216,8 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(cmd9).To(Equal("OK")) - opt = &redis.JSONArrTrimOptions{Start: 9, Stop: 11} - cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", opt).Result() + stop = 11 + cmd10, err := client.JSONArrTrimArgs(ctx, "trim5", "$", redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd10).To(Equal([]int64{0})) }) From 578f6c078c5301f85cc8ad4052e2e68988e75b06 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 15:23:48 +0100 Subject: [PATCH 09/18] update JSONGetWithArgs and matching texts Rename JSONGetWithArgs to JSONGetArgs Removed Paths & Path from args - redundant and ambiguous Renamed mispelled "Indention" Changed args param from pointer to value Updated and added additional tests for formatting params --- json.go | 48 ++++++++++++++++++------------------------------ json_test.go | 35 ++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/json.go b/json.go index f226de944f..5c1a842672 100644 --- a/json.go +++ b/json.go @@ -24,7 +24,7 @@ type JSONCmdAble interface { JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd - JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs) *JSONCmd + JSONGetArgs(ctx context.Context, key string, options JSONGetArgs, paths ...string) *JSONCmd JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd @@ -425,40 +425,28 @@ func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSON } type JSONGetArgs struct { - Path string - Paths []interface{} - Indention string - NewLine string - Space string + Indent string + Newline string + Space string } -// JSONGetWithArgs - Retrieves the value of a key from a JSON document. +// JSONGetArgs - Retrieves the value of a key from a JSON document. // This function also allows for specifying additional options such as: -// Indention, NewLine, Space, Path and Paths. +// Indention, NewLine and Space // For more information - https://redis.io/commands/json.get/ -func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs) *JSONCmd { +func (c cmdable) JSONGetArgs(ctx context.Context, key string, options JSONGetArgs, paths ...string) *JSONCmd { args := []interface{}{"JSON.GET", key} - if options != nil { - if options.Indention != "" { - args = append(args, options.Indention) - } - if options.NewLine != "" { - args = append(args, options.NewLine) - } - if options.Space != "" { - args = append(args, options.Space) - } - if options.Path != "" && options.Paths != nil { - panic("redis: JSON.GET can only accept one of Path or Paths") - - } - if options.Path != "" { - args = append(args, options.Path) - } - if options.Paths != nil { - args = append(args, options.Paths...) - } - + if options.Indent != "" { + args = append(args, options.Indent) + } + if options.Newline != "" { + args = append(args, options.Newline) + } + if options.Space != "" { + args = append(args, options.Space) + } + for _, path := range paths { + args = append(args, path) } cmd := NewJSONCmd(ctx, args...) _ = c(ctx, cmd) diff --git a/json_test.go b/json_test.go index 9adf02c8d8..da42ee8850 100644 --- a/json_test.go +++ b/json_test.go @@ -246,27 +246,36 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONGet", Label("json.get", "json"), func() { - cmd1 := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2, "c": {"hello": "world"}}`) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) + res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2, "c": {"hello": "world"}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) + + res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) + + res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--", Newline: `~`, Space: `!`}, `$.a`, `$.c`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`{~--\"$.a\":![~----1~--],~--\"$.c\":![~----{~------\"hello\":!\"world\"~----}~--]~}`)) - cmd2 := client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Path: "$.*"}) - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(cmd2.Val()).To(Equal(`[1,2,{"hello":"world"}]`)) }) It("should JSONMerge", Label("json.merge", "json"), func() { - cmd1, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result() + res, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd1).To(Equal("OK")) + Expect(res).To(Equal("OK")) - cmd2 := client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`) - Expect(cmd2.Err()).NotTo(HaveOccurred()) - Expect(cmd2.Val()).To(Equal("OK")) + res, err = client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) - cmd3, err := client.JSONGet(ctx, "merge1", "$").Result() + res, err = client.JSONGet(ctx, "merge1", "$").Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd3).To(Equal(`[{"a":1,"b":3,"c":4}]`)) + Expect(res).To(Equal(`[{"a":1,"b":3,"c":4}]`)) }) It("should JSONMSet", Label("json.mset", "json"), func() { From d82d503eb35eaa249d5080cfc0e831d1d784b655 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 15:37:57 +0100 Subject: [PATCH 10/18] Update JSONSetMode changed mode parameter back to string, added value checking for that string --- json.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/json.go b/json.go index 5c1a842672..dc539c344a 100644 --- a/json.go +++ b/json.go @@ -20,7 +20,6 @@ type JSONCmdAble interface { JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd - JSONDebugMemory(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd @@ -381,15 +380,6 @@ func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { return cmd } -// JSONDebugMemory reports a value's memory usage in bytes. -// For more information, see https://redis.io/commands/json.debug.memory -func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd { - args := []interface{}{"JSON.DEBUG", "MEMORY", key, path} - cmd := NewIntCmd(ctx, args...) - _ = c(ctx, cmd) - return cmd -} - // JSONDel deletes a value. // For more information, see https://redis.io/commands/json.del func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { From fbc7053cc7ec8821d4f67bf6f6df51b277f1db48 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 15:39:09 +0100 Subject: [PATCH 11/18] Updated JSONMSet Changed name of param struct to ...Args for consistency Updated arg list to use an array of structs not pointers as all args are mandatory (we never want to pass a nil parameter) --- json.go | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/json.go b/json.go index dc539c344a..7b5d2533c9 100644 --- a/json.go +++ b/json.go @@ -3,6 +3,7 @@ package redis import ( "context" "encoding/json" + "strings" "github.com/redis/go-redis/v9/internal/proto" "github.com/redis/go-redis/v9/internal/util" @@ -25,21 +26,21 @@ type JSONCmdAble interface { JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd JSONGetArgs(ctx context.Context, key string, options JSONGetArgs, paths ...string) *JSONCmd JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd - JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd + JSONMSet(ctx context.Context, docs []JSONSetArgs) *StatusCmd JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd - JSONSetMode(ctx context.Context, key, path string, value interface{}, mode *JSONSetModeOptions) *StatusCmd + JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd JSONType(ctx context.Context, key, path string) *JSONSliceCmd } -type JSONSetParams struct { +type JSONSetArgs struct { Key interface{} Path string Value interface{} @@ -55,11 +56,6 @@ type JSONArrTrimArgs struct { Stop *int } -type JSONSetModeOptions struct { - XX bool - NX bool -} - type JSONCmd struct { baseCmd val string @@ -470,7 +466,7 @@ func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSO // JSONMSet sets or updates one or more JSON values according to the specified key-path-value triplets. // For more information, see https://redis.io/commands/json.mset -func (c cmdable) JSONMSet(ctx context.Context, docs []*JSONSetParams) *StatusCmd { +func (c cmdable) JSONMSet(ctx context.Context, docs []JSONSetArgs) *StatusCmd { args := []interface{}{"JSON.MSET"} for _, doc := range docs { args = append(args, doc.Key, doc.Path, doc.Value) @@ -521,14 +517,14 @@ func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSl // it can be passed directly as JSON. // For more information, see https://redis.io/commands/json.set func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd { - return c.JSONSetMode(ctx, key, path, value, &JSONSetModeOptions{}) + return c.JSONSetMode(ctx, key, path, value, "") } // JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set // (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless // the argument is a string or []byte when we assume that it can be passed directly as JSON. // For more information, see https://redis.io/commands/json.set -func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode *JSONSetModeOptions) *StatusCmd { +func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd { var bytes []byte var err error switch v := value.(type) { @@ -540,15 +536,13 @@ func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interf bytes, err = json.Marshal(v) } args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)} - if mode != nil { - if mode.XX && mode.NX { - panic("redis: JSON.SET can only accept one of XX or NX") - } - if mode.XX { - args = append(args, "XX") - } - if mode.NX { - args = append(args, "NX") + if mode != "" { + switch strings.ToUpper(mode) { + case "XX", "NX": + args = append(args, strings.ToUpper(mode)) + + default: + panic("redis: JSON.SET mode must be NX or XX") } } cmd := NewStatusCmd(ctx, args...) From 306f20f40b550b06a4d7d5ccc4cf3b9d7d1ea2b0 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 16 Oct 2023 15:45:51 +0100 Subject: [PATCH 12/18] Updated tests for JSONMSet --- json_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/json_test.go b/json_test.go index da42ee8850..c1437179d9 100644 --- a/json_test.go +++ b/json_test.go @@ -279,20 +279,20 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONMSet", Label("json.mset", "json"), func() { - doc1 := &redis.JSONSetParams{Key: "1", Path: "$", Value: 1} - doc2 := &redis.JSONSetParams{Key: "2", Path: "$", Value: 2} - docs := []*redis.JSONSetParams{doc1, doc2} + doc1 := redis.JSONSetArgs{Key: "1", Path: "$", Value: 1} + doc2 := redis.JSONSetArgs{Key: "2", Path: "$", Value: 2} + docs := []redis.JSONSetArgs{doc1, doc2} cmd1 := client.JSONMSet(ctx, docs) Expect(cmd1.Err()).NotTo(HaveOccurred()) Expect(cmd1.Val()).To(Equal("OK")) - cmd2, err := client.JSONMGet(ctx, "$", "1").Result() + res, err := client.JSONMGet(ctx, "$", "1").Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal([]interface{}{"[1]"})) + Expect(res).To(Equal([]interface{}{"[1]"})) - cmd3, err := client.JSONMGet(ctx, "$", "1", "2").Result() + res, err = client.JSONMGet(ctx, "$", "1", "2").Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd3).To(Equal([]interface{}{"[1]", "[2]"})) + Expect(res).To(Equal([]interface{}{"[1]", "[2]"})) }) It("should JSONMGet", Label("json.mget", "json"), func() { From bdaccd4bf03b78ce96b80a2dd48e06d883f60d02 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Tue, 17 Oct 2023 09:46:19 +0100 Subject: [PATCH 13/18] Added stubbed (panicking) implementations of JSON.RESP AND JSON.DEBUG --- json.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/json.go b/json.go index 7b5d2533c9..cbbf284fc5 100644 --- a/json.go +++ b/json.go @@ -21,6 +21,7 @@ type JSONCmdAble interface { JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd + JSONDebugMemory(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd @@ -32,6 +33,7 @@ type JSONCmdAble interface { JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd @@ -376,6 +378,12 @@ func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { return cmd } +// JSONDebugMemory reports a value's memory usage in bytes (unimplemented) +// For more information, see https://redis.io/commands/json.debug-memory +func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd { + panic("not implemented") +} + // JSONDel deletes a value. // For more information, see https://redis.io/commands/json.del func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { @@ -512,6 +520,13 @@ func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSl return cmd } +// JSONResp returns the JSON in key in Redis serialization protocol specification form. +// This function is considered deprecated and will panic if called. +// For more information, set https://redis.io/commands/json.resp +func (c cmdable) JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd { + panic("not implemented") +} + // JSONSet sets the JSON value at the given path in the given key. The value must be something that // can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that // it can be passed directly as JSON. From a03e07062d749b93c58109c40a1dd2ba67b21819 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Wed, 18 Oct 2023 13:28:56 +0100 Subject: [PATCH 14/18] Pre-pull request tidy up Renamed xArgs() to xWithArgs to match other Redis modules Modified params to xWithArgs functions to use a pointer for the arguments struct to match other Redis modules. Modified JSONMSet to JSONMSetArgs and added a version that takes a vararray of args to match the semantics of the native redis call Updated tests to match above changes. Renamed some variables in the json tests for consistency --- json.go | 86 ++++++++++++++------------ json_test.go | 170 ++++++++++++++++++++++++++------------------------- 2 files changed, 136 insertions(+), 120 deletions(-) diff --git a/json.go b/json.go index cbbf284fc5..30938b7e7f 100644 --- a/json.go +++ b/json.go @@ -14,26 +14,26 @@ import ( type JSONCmdAble interface { JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd - JSONArrIndexArgs(ctx context.Context, key, path string, options JSONArrIndexArgs, value ...interface{}) *IntSliceCmd + JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd - JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd + JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd JSONClear(ctx context.Context, key, path string) *IntCmd JSONDebugMemory(ctx context.Context, key, path string) *IntCmd JSONDel(ctx context.Context, key, path string) *IntCmd JSONForget(ctx context.Context, key, path string) *IntCmd JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd - JSONGetArgs(ctx context.Context, key string, options JSONGetArgs, paths ...string) *JSONCmd + JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd - JSONMSet(ctx context.Context, docs []JSONSetArgs) *StatusCmd + JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd + JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd - JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd @@ -43,7 +43,7 @@ type JSONCmdAble interface { } type JSONSetArgs struct { - Key interface{} + Key string Path string Value interface{} } @@ -304,15 +304,18 @@ func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...in return cmd } -// JSONArrIndexArgs searches for the first occurrence of a JSON value in an array while allowing the start and +// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and // stop options to be provided. // For more information, see https://redis.io/commands/json.arrindex -func (c cmdable) JSONArrIndexArgs(ctx context.Context, key, path string, options JSONArrIndexArgs, value ...interface{}) *IntSliceCmd { +func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd { args := []interface{}{"JSON.ARRINDEX", key, path} args = append(args, value...) - args = append(args, options.Start) - if options.Stop != nil { - args = append(args, *options.Stop) + + if options != nil { + args = append(args, options.Start) + if options.Stop != nil { + args = append(args, *options.Stop) + } } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) @@ -356,13 +359,17 @@ func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd return cmd } -// JSONArrTrimArgs trims an array to contain only the specified inclusive range of elements. +// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements. // For more information, see https://redis.io/commands/json.arrtrim -func (c cmdable) JSONArrTrimArgs(ctx context.Context, key, path string, options JSONArrTrimArgs) *IntSliceCmd { +func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd { args := []interface{}{"JSON.ARRTRIM", key, path} - args = append(args, options.Start) - if options.Stop != nil { - args = append(args, *options.Stop) + + if options != nil { + args = append(args, options.Start) + + if options.Stop != nil { + args = append(args, *options.Stop) + } } cmd := NewIntSliceCmd(ctx, args...) _ = c(ctx, cmd) @@ -424,23 +431,25 @@ type JSONGetArgs struct { Space string } -// JSONGetArgs - Retrieves the value of a key from a JSON document. +// JSONGetWithArgs - Retrieves the value of a key from a JSON document. // This function also allows for specifying additional options such as: // Indention, NewLine and Space // For more information - https://redis.io/commands/json.get/ -func (c cmdable) JSONGetArgs(ctx context.Context, key string, options JSONGetArgs, paths ...string) *JSONCmd { +func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd { args := []interface{}{"JSON.GET", key} - if options.Indent != "" { - args = append(args, options.Indent) - } - if options.Newline != "" { - args = append(args, options.Newline) - } - if options.Space != "" { - args = append(args, options.Space) - } - for _, path := range paths { - args = append(args, path) + if options != nil { + if options.Indent != "" { + args = append(args, options.Indent) + } + if options.Newline != "" { + args = append(args, options.Newline) + } + if options.Space != "" { + args = append(args, options.Space) + } + for _, path := range paths { + args = append(args, path) + } } cmd := NewJSONCmd(ctx, args...) _ = c(ctx, cmd) @@ -472,9 +481,9 @@ func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSO return cmd } -// JSONMSet sets or updates one or more JSON values according to the specified key-path-value triplets. +// JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets. // For more information, see https://redis.io/commands/json.mset -func (c cmdable) JSONMSet(ctx context.Context, docs []JSONSetArgs) *StatusCmd { +func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd { args := []interface{}{"JSON.MSET"} for _, doc := range docs { args = append(args, doc.Key, doc.Path, doc.Value) @@ -484,6 +493,14 @@ func (c cmdable) JSONMSet(ctx context.Context, docs []JSONSetArgs) *StatusCmd { return cmd } +func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd { + args := []interface{}{"JSON.MSET"} + args = append(args, params...) + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // JSONNumIncrBy increments the number value stored at the specified path by the provided number. // For more information, see https://redis.io/commands/json.numincreby func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { @@ -520,13 +537,6 @@ func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSl return cmd } -// JSONResp returns the JSON in key in Redis serialization protocol specification form. -// This function is considered deprecated and will panic if called. -// For more information, set https://redis.io/commands/json.resp -func (c cmdable) JSONResp(ctx context.Context, key, path string) *MapStringInterfaceCmd { - panic("not implemented") -} - // JSONSet sets the JSON value at the given path in the given key. The value must be something that // can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that // it can be passed directly as JSON. diff --git a/json_test.go b/json_test.go index c1437179d9..4c0724a410 100644 --- a/json_test.go +++ b/json_test.go @@ -63,17 +63,17 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) - res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{}, 4).Result() + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) stop := 5000 - res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(4))) stop = -1 - res, err = client.JSONArrIndexArgs(ctx, "index2", "$", redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() Expect(err).NotTo(HaveOccurred()) Expect(res[0]).To(Equal(int64(-1))) @@ -117,9 +117,9 @@ var _ = Describe("JSON Commands", Label("json"), func() { "bicycle": {"color": "red", "price": 19.95} } }` - cmd, err := client.JSONSet(ctx, "doc1", "$", doc).Result() + res, err := client.JSONSet(ctx, "doc1", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result() Expect(err).NotTo(HaveOccurred()) @@ -177,7 +177,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd1).To(Equal("OK")) stop := 3 - cmd2, err := client.JSONArrTrimArgs(ctx, "trim1", "$", redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result() + cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", &redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd2).To(Equal([]int64{3})) @@ -190,7 +190,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd3).To(Equal("OK")) stop = 3 - cmd4, err := client.JSONArrTrimArgs(ctx, "trim2", "$", redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result() + cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", &redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd4).To(Equal([]int64{0})) @@ -199,7 +199,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd5).To(Equal("OK")) stop = 99 - cmd6, err := client.JSONArrTrimArgs(ctx, "trim3", "$", redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result() + cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", &redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd6).To(Equal([]int64{2})) @@ -208,7 +208,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd7).To(Equal("OK")) stop = 1 - cmd8, err := client.JSONArrTrimArgs(ctx, "trim4", "$", redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() + cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd8).To(Equal([]int64{0})) @@ -217,7 +217,7 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(cmd9).To(Equal("OK")) stop = 11 - cmd10, err := client.JSONArrTrimArgs(ctx, "trim5", "$", redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() + cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() Expect(err).NotTo(HaveOccurred()) Expect(cmd10).To(Equal([]int64{0})) }) @@ -250,15 +250,15 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal("OK")) - res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--"}).Result() + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) - res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--"}).Result() + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) - res, err = client.JSONGetArgs(ctx, "get3", redis.JSONGetArgs{Indent: "--", Newline: `~`, Space: `!`}, `$.a`, `$.c`).Result() + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--", Newline: `~`, Space: `!`}, `$.a`, `$.c`).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal(`{~--\"$.a\":![~----1~--],~--\"$.c\":![~----{~------\"hello\":!\"world\"~----}~--]~}`)) @@ -279,20 +279,24 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONMSet", Label("json.mset", "json"), func() { - doc1 := redis.JSONSetArgs{Key: "1", Path: "$", Value: 1} - doc2 := redis.JSONSetArgs{Key: "2", Path: "$", Value: 2} + doc1 := redis.JSONSetArgs{Key: "mset1", Path: "$", Value: `{"a": 1}`} + doc2 := redis.JSONSetArgs{Key: "mset2", Path: "$", Value: 2} docs := []redis.JSONSetArgs{doc1, doc2} - cmd1 := client.JSONMSet(ctx, docs) - Expect(cmd1.Err()).NotTo(HaveOccurred()) - Expect(cmd1.Val()).To(Equal("OK")) - res, err := client.JSONMGet(ctx, "$", "1").Result() + mSetResult, err := client.JSONMSetArgs(ctx, docs).Result() + Expect(mSetResult).NotTo(HaveOccurred()) + Expect(err).To(Equal("OK")) + + res, err := client.JSONMGet(ctx, "$", "mset1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal([]interface{}{`[{"a": "1"}]`})) + + res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{"[1]"})) + Expect(res).To(Equal([]interface{}{`[{"a": "1"}]`, "[2]"})) - res, err = client.JSONMGet(ctx, "$", "1", "2").Result() + mSetResult, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{"[1]", "[2]"})) }) It("should JSONMGet", Label("json.mget", "json"), func() { @@ -311,25 +315,25 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONMget with $", Label("json.mget", "json"), func() { - cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result() + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result() + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err := client.JSONMGet(ctx, "$..a", "doc1").Result() + iRes, err := client.JSONMGet(ctx, "$..a", "doc1").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{`[1,3,""]`})) + Expect(iRes).To(Equal([]interface{}{`[1,3,""]`})) - res, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result() + iRes, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`})) + Expect(iRes).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`})) - res, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result() + iRes, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{nil, nil})) + Expect(iRes).To(Equal([]interface{}{nil, nil})) }) @@ -358,25 +362,25 @@ var _ = Describe("JSON Commands", Label("json"), func() { "nested2": {"a": "claro"}, "nested3": {"a": {"baz": 50}} }` - cmd, err := client.JSONSet(ctx, "doc1", "$", doc).Result() + res, err := client.JSONSet(ctx, "doc1", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err := client.JSONClear(ctx, "doc1", "$..a").Result() + iRes, err := client.JSONClear(ctx, "doc1", "$..a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(3))) + Expect(iRes).To(Equal(int64(3))) resGet, err := client.JSONGet(ctx, "doc1", `$`).Result() Expect(err).NotTo(HaveOccurred()) Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`)) - cmd, err = client.JSONSet(ctx, "doc1", "$", doc).Result() + res, err = client.JSONSet(ctx, "doc1", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result() + iRes, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(1))) + Expect(iRes).To(Equal(int64(1))) resGet, err = client.JSONGet(ctx, "doc1", `$`).Result() Expect(err).NotTo(HaveOccurred()) @@ -398,25 +402,25 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONDel with $", Label("json.del", "json"), func() { - cmd, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + res, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err := client.JSONDel(ctx, "del1", "$..a").Result() + iRes, err := client.JSONDel(ctx, "del1", "$..a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(2))) + Expect(iRes).To(Equal(int64(2))) resGet, err := client.JSONGet(ctx, "del1", "$").Result() Expect(err).NotTo(HaveOccurred()) Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) - cmd, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + res, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONDel(ctx, "del2", "$..a").Result() + iRes, err = client.JSONDel(ctx, "del2", "$..a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(1))) + Expect(iRes).To(Equal(int64(1))) resGet, err = client.JSONGet(ctx, "del2", "$").Result() Expect(err).NotTo(HaveOccurred()) @@ -434,13 +438,14 @@ var _ = Describe("JSON Commands", Label("json"), func() { ] } ]` - cmd, err = client.JSONSet(ctx, "del3", "$", doc).Result() + res, err = client.JSONSet(ctx, "del3", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result() + iRes, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(3))) + Expect(iRes).To(Equal(int64(3))) + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` resGet, err = client.JSONGet(ctx, "del3", "$").Result() Expect(err).NotTo(HaveOccurred()) @@ -463,25 +468,25 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONForget with $", Label("json.forget", "json"), func() { - cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err := client.JSONForget(ctx, "doc1", "$..a").Result() + iRes, err := client.JSONForget(ctx, "doc1", "$..a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(2))) + Expect(iRes).To(Equal(int64(2))) resGet, err := client.JSONGet(ctx, "doc1", "$").Result() Expect(err).NotTo(HaveOccurred()) Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) - cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONForget(ctx, "doc2", "$..a").Result() + iRes, err = client.JSONForget(ctx, "doc2", "$..a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(1))) + Expect(iRes).To(Equal(int64(1))) resGet, err = client.JSONGet(ctx, "doc2", "$").Result() Expect(err).NotTo(HaveOccurred()) @@ -499,13 +504,14 @@ var _ = Describe("JSON Commands", Label("json"), func() { ] } ]` - cmd, err = client.JSONSet(ctx, "doc3", "$", doc).Result() + res, err = client.JSONSet(ctx, "doc3", "$", doc).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result() + iRes, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(int64(3))) + Expect(iRes).To(Equal(int64(3))) + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` resGet, err = client.JSONGet(ctx, "doc3", "$").Result() Expect(err).NotTo(HaveOccurred()) @@ -523,25 +529,25 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() { - cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - cmd, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result() + res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal(`[7]`)) + Expect(res).To(Equal(`[7]`)) - cmd, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result() + res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal(`[10.5]`)) + Expect(res).To(Equal(`[10.5]`)) - cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - cmd, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result() + res, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal(`[5]`)) + Expect(res).To(Equal(`[5]`)) }) It("should JSONNumMultBy", Label("json.nummultby", "json"), func() { @@ -642,21 +648,21 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() { - cmd, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result() + intArrayResult, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(*res[0]).To(Equal(int64(8))) + Expect(*intArrayResult[0]).To(Equal(int64(8))) - cmd, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() Expect(err).NotTo(HaveOccurred()) - Expect(cmd).To(Equal("OK")) + Expect(res).To(Equal("OK")) - res, err = client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result() + intResult, err := client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result() Expect(err).NotTo(HaveOccurred()) - Expect(*res[0]).To(Equal(int64(5))) + Expect(*intResult[0]).To(Equal(int64(5))) }) It("should JSONToggle", Label("json.toggle", "json"), func() { From 561cbdaa99fc4165ec1b775f8980a59ca7d1dbba Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Thu, 19 Oct 2023 13:34:37 +0100 Subject: [PATCH 15/18] Testing fixes Fixed error in JSONSetWithArgs found during tests Fixed tests for indentation --- json.go | 6 +++--- json_test.go | 22 +++++++++------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/json.go b/json.go index 30938b7e7f..9916a1ffcf 100644 --- a/json.go +++ b/json.go @@ -439,13 +439,13 @@ func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONG args := []interface{}{"JSON.GET", key} if options != nil { if options.Indent != "" { - args = append(args, options.Indent) + args = append(args, "INDENT", options.Indent) } if options.Newline != "" { - args = append(args, options.Newline) + args = append(args, "NEWLINE", options.Newline) } if options.Space != "" { - args = append(args, options.Space) + args = append(args, "SPACE", options.Space) } for _, path := range paths { args = append(args, path) diff --git a/json_test.go b/json_test.go index 4c0724a410..0e04dab858 100644 --- a/json_test.go +++ b/json_test.go @@ -246,21 +246,17 @@ var _ = Describe("JSON Commands", Label("json"), func() { }) It("should JSONGet", Label("json.get", "json"), func() { - res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2, "c": {"hello": "world"}}`).Result() + res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2}`).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal("OK")) - res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--"}).Result() + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-"}).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) + Expect(res).To(Equal(`[-{--"a":1,--"b":2-}]`)) - res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--"}).Result() + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-", Newline: `~`, Space: `!`}).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(`{--\"a\":1,--\"b\":2,--\"c\":{----\"hello\":\"world\"--}}`)) - - res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "--", Newline: `~`, Space: `!`}, `$.a`, `$.c`).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(`{~--\"$.a\":![~----1~--],~--\"$.c\":![~----{~------\"hello\":!\"world\"~----}~--]~}`)) + Expect(res).To(Equal(`[~-{~--"a":!1,~--"b":!2~-}~]`)) }) @@ -284,16 +280,16 @@ var _ = Describe("JSON Commands", Label("json"), func() { docs := []redis.JSONSetArgs{doc1, doc2} mSetResult, err := client.JSONMSetArgs(ctx, docs).Result() - Expect(mSetResult).NotTo(HaveOccurred()) - Expect(err).To(Equal("OK")) + Expect(err).NotTo(HaveOccurred()) + Expect(mSetResult).To(Equal("OK")) res, err := client.JSONMGet(ctx, "$", "mset1").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{`[{"a": "1"}]`})) + Expect(res).To(Equal([]interface{}{`[{"a":1}]`})) res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal([]interface{}{`[{"a": "1"}]`, "[2]"})) + Expect(res).To(Equal([]interface{}{`[{"a":1}]`, "[2]"})) mSetResult, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result() Expect(err).NotTo(HaveOccurred()) From 3d7f7936b9dd6a69c20fbaf5d0395932905ce604 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Sun, 22 Oct 2023 10:27:07 +0300 Subject: [PATCH 16/18] fix JSONCmdable typo (#5) --- commands.go | 2 +- json.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 9b06d6f9e1..b3ee715c51 100644 --- a/commands.go +++ b/commands.go @@ -221,10 +221,10 @@ type Cmdable interface { ScriptingFunctionsCmdable StringCmdable PubSubCmdable + StreamCmdable GearsCmdable ProbabilisticCmdable TimeseriesCmdable - StreamCmdable JSONCmdable } diff --git a/json.go b/json.go index 9916a1ffcf..9e0adff67b 100644 --- a/json.go +++ b/json.go @@ -11,7 +11,7 @@ import ( // ------------------------------------------- -type JSONCmdAble interface { +type JSONCmdable interface { JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd @@ -439,7 +439,7 @@ func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONG args := []interface{}{"JSON.GET", key} if options != nil { if options.Indent != "" { - args = append(args, "INDENT", options.Indent) + args = append(args, "INDENT", options.Indent) } if options.Newline != "" { args = append(args, "NEWLINE", options.Newline) From e6188958d64aa02b98bb1687639dde9742abc781 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:01:37 +0200 Subject: [PATCH 17/18] Remove deprecated command: JSONNumMultBy (#6) --- json.go | 10 ---------- json_test.go | 18 ------------------ 2 files changed, 28 deletions(-) diff --git a/json.go b/json.go index 9e0adff67b..07a045864d 100644 --- a/json.go +++ b/json.go @@ -31,7 +31,6 @@ type JSONCmdable interface { JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd - JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd JSONObjKeys(ctx context.Context, key, path string) *SliceCmd JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd @@ -510,15 +509,6 @@ func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value floa return cmd } -// JSONNumMultBy multiplies the number value stored at the specified path by the provided number. -// For more information, see https://redis.io/commands/json.nummultby -func (c cmdable) JSONNumMultBy(ctx context.Context, key, path string, value float64) *JSONCmd { - args := []interface{}{"JSON.NUMMULTBY", key, path, value} - cmd := NewJSONCmd(ctx, args...) - _ = c(ctx, cmd) - return cmd -} - // JSONObjKeys returns the keys in the object that's referenced by the specified path. // For more information, see https://redis.io/commands/json.objkeys func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { diff --git a/json_test.go b/json_test.go index 0e04dab858..d527133a84 100644 --- a/json_test.go +++ b/json_test.go @@ -546,24 +546,6 @@ var _ = Describe("JSON Commands", Label("json"), func() { Expect(res).To(Equal(`[5]`)) }) - It("should JSONNumMultBy", Label("json.nummultby", "json"), func() { - cmd1, err := client.JSONSet(ctx, "num1", "$", `1`).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd1).To(Equal("OK")) - - cmd2, err := client.JSONNumMultBy(ctx, "num1", "$", float64(2)).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal(`[2]`)) - - cmd2, err = client.JSONNumMultBy(ctx, "num1", "$", float64(2.5)).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal(`[5]`)) - - cmd2, err = client.JSONNumMultBy(ctx, "num1", "$", float64(0.5)).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(cmd2).To(Equal(`[2.5]`)) - }) - It("should JSONObjKeys", Label("json.objkeys", "json"), func() { cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) Expect(cmd1.Err()).NotTo(HaveOccurred()) From 7259d9f384ab43653ef674b5176217732d4a10a2 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:43:19 +0200 Subject: [PATCH 18/18] Lowercase newJSONCmd (#7) --- json.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/json.go b/json.go index 07a045864d..f942524146 100644 --- a/json.go +++ b/json.go @@ -65,7 +65,7 @@ type JSONCmd struct { var _ Cmder = (*JSONCmd)(nil) -func NewJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd { +func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd { return &JSONCmd{ baseCmd: baseCmd{ @@ -419,7 +419,7 @@ func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSON for n, path := range paths { args[n+2] = path } - cmd := NewJSONCmd(ctx, args...) + cmd := newJSONCmd(ctx, args...) _ = c(ctx, cmd) return cmd } @@ -450,7 +450,7 @@ func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONG args = append(args, path) } } - cmd := NewJSONCmd(ctx, args...) + cmd := newJSONCmd(ctx, args...) _ = c(ctx, cmd) return cmd } @@ -504,7 +504,7 @@ func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd // For more information, see https://redis.io/commands/json.numincreby func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { args := []interface{}{"JSON.NUMINCRBY", key, path, value} - cmd := NewJSONCmd(ctx, args...) + cmd := newJSONCmd(ctx, args...) _ = c(ctx, cmd) return cmd }