1 package redis_test
2
3 import (
4 "context"
5 "encoding/json"
6 "time"
7
8 . "github.com/bsm/ginkgo/v2"
9 . "github.com/bsm/gomega"
10
11 "github.com/redis/go-redis/v9"
12 )
13
14 type JSONGetTestStruct struct {
15 Hello string `json:"hello"`
16 }
17
18 var _ = Describe("JSON Commands", Label("json"), func() {
19 ctx := context.TODO()
20 var client *redis.Client
21
22 setupRedisClient := func(protocolVersion int) *redis.Client {
23 return redis.NewClient(&redis.Options{
24 Addr: "localhost:6379",
25 DB: 0,
26 Protocol: protocolVersion,
27 UnstableResp3: true,
28 })
29 }
30
31 AfterEach(func() {
32 if client != nil {
33 client.FlushDB(ctx)
34 client.Close()
35 }
36 })
37
38 protocols := []int{2, 3}
39 for _, protocol := range protocols {
40 BeforeEach(func() {
41 client = setupRedisClient(protocol)
42 Expect(client.FlushAll(ctx).Err()).NotTo(HaveOccurred())
43 })
44
45 Describe("arrays", Label("arrays"), func() {
46 It("should JSONArrAppend", Label("json.arrappend", "json"), func() {
47 cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`)
48 Expect(cmd1.Err()).NotTo(HaveOccurred())
49 Expect(cmd1.Val()).To(Equal("OK"))
50
51 cmd2 := client.JSONArrAppend(ctx, "append2", "$..a", 10)
52 Expect(cmd2.Err()).NotTo(HaveOccurred())
53 Expect(cmd2.Val()).To(Equal([]int64{2, 3}))
54 })
55
56 It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex", "json"), func() {
57 cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result()
58 Expect(err).NotTo(HaveOccurred())
59 Expect(cmd1).To(Equal("OK"))
60
61 cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result()
62 Expect(err).NotTo(HaveOccurred())
63 Expect(cmd2).To(Equal([]int64{1}))
64
65 cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result()
66 Expect(err).NotTo(HaveOccurred())
67 Expect(cmd3).To(Equal("OK"))
68
69 res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result()
70 Expect(err).NotTo(HaveOccurred())
71 Expect(res[0]).To(Equal(int64(1)))
72
73 res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result()
74 Expect(err).NotTo(HaveOccurred())
75 Expect(res[0]).To(Equal(int64(-1)))
76
77 res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result()
78 Expect(err).NotTo(HaveOccurred())
79 Expect(res[0]).To(Equal(int64(4)))
80
81 res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{}, 4).Result()
82 Expect(err).NotTo(HaveOccurred())
83 Expect(res[0]).To(Equal(int64(4)))
84
85 stop := 5000
86 res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
87 Expect(err).NotTo(HaveOccurred())
88 Expect(res[0]).To(Equal(int64(4)))
89
90 stop = -1
91 res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
92 Expect(err).NotTo(HaveOccurred())
93 Expect(res[0]).To(Equal(int64(-1)))
94 })
95
96 It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex", "json"), func() {
97 doc := `{
98 "store": {
99 "book": [
100 {
101 "category": "reference",
102 "author": "Nigel Rees",
103 "title": "Sayings of the Century",
104 "price": 8.95,
105 "size": [10, 20, 30, 40]
106 },
107 {
108 "category": "fiction",
109 "author": "Evelyn Waugh",
110 "title": "Sword of Honour",
111 "price": 12.99,
112 "size": [50, 60, 70, 80]
113 },
114 {
115 "category": "fiction",
116 "author": "Herman Melville",
117 "title": "Moby Dick",
118 "isbn": "0-553-21311-3",
119 "price": 8.99,
120 "size": [5, 10, 20, 30]
121 },
122 {
123 "category": "fiction",
124 "author": "J. R. R. Tolkien",
125 "title": "The Lord of the Rings",
126 "isbn": "0-395-19395-8",
127 "price": 22.99,
128 "size": [5, 6, 7, 8]
129 }
130 ],
131 "bicycle": {"color": "red", "price": 19.95}
132 }
133 }`
134 res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
135 Expect(err).NotTo(HaveOccurred())
136 Expect(res).To(Equal("OK"))
137
138 resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result()
139 Expect(err).NotTo(HaveOccurred())
140 Expect(resGet).To(Equal("[[10,20,30,40],[5,10,20,30]]"))
141
142 resArr, err := client.JSONArrIndex(ctx, "doc1", "$.store.book[?(@.price<10)].size", 20).Result()
143 Expect(err).NotTo(HaveOccurred())
144 Expect(resArr).To(Equal([]int64{1, 2}))
145
146 _, err = client.JSONGet(ctx, "this-key-does-not-exist", "$").Result()
147 Expect(err).To(HaveOccurred())
148 Expect(err).To(BeIdenticalTo(redis.Nil))
149 })
150
151 It("should JSONArrInsert", Label("json.arrinsert", "json"), func() {
152 cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`)
153 Expect(cmd1.Err()).NotTo(HaveOccurred())
154 Expect(cmd1.Val()).To(Equal("OK"))
155
156 cmd2 := client.JSONArrInsert(ctx, "insert2", "$", -1, 1, 2)
157 Expect(cmd2.Err()).NotTo(HaveOccurred())
158 Expect(cmd2.Val()).To(Equal([]int64{6}))
159
160 cmd3 := client.JSONGet(ctx, "insert2")
161 Expect(cmd3.Err()).NotTo(HaveOccurred())
162
163 Expect(cmd3.Val()).To(Or(
164 Equal(`[100,200,300,1,2,200]`),
165 Equal(`[[100,200,300,1,2,200]]`)))
166 })
167
168 It("should JSONArrLen", Label("json.arrlen", "json"), func() {
169 cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`)
170 Expect(cmd1.Err()).NotTo(HaveOccurred())
171 Expect(cmd1.Val()).To(Equal("OK"))
172
173 cmd2 := client.JSONArrLen(ctx, "length2", "$..a")
174 Expect(cmd2.Err()).NotTo(HaveOccurred())
175 Expect(cmd2.Val()).To(Equal([]int64{1, 6}))
176 })
177
178 It("should JSONArrPop", Label("json.arrpop"), func() {
179 cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
180 Expect(cmd1.Err()).NotTo(HaveOccurred())
181 Expect(cmd1.Val()).To(Equal("OK"))
182
183 cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
184 Expect(cmd2.Err()).NotTo(HaveOccurred())
185 Expect(cmd2.Val()).To(Equal([]string{"300"}))
186
187 cmd3 := client.JSONGet(ctx, "pop4", "$")
188 Expect(cmd3.Err()).NotTo(HaveOccurred())
189 Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
190 })
191
192 It("should JSONArrTrim", Label("json.arrtrim", "json"), func() {
193 cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result()
194 Expect(err).NotTo(HaveOccurred())
195 Expect(cmd1).To(Equal("OK"))
196
197 stop := 3
198 cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", &redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result()
199 Expect(err).NotTo(HaveOccurred())
200 Expect(cmd2).To(Equal([]int64{3}))
201
202 res, err := client.JSONGet(ctx, "trim1", "$").Result()
203 Expect(err).NotTo(HaveOccurred())
204 Expect(res).To(Equal(`[[1,2,3]]`))
205
206 cmd3, err := client.JSONSet(ctx, "trim2", "$", `[0,1,2,3,4]`).Result()
207 Expect(err).NotTo(HaveOccurred())
208 Expect(cmd3).To(Equal("OK"))
209
210 stop = 3
211 cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", &redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result()
212 Expect(err).NotTo(HaveOccurred())
213 Expect(cmd4).To(Equal([]int64{0}))
214
215 cmd5, err := client.JSONSet(ctx, "trim3", "$", `[0,1,2,3,4]`).Result()
216 Expect(err).NotTo(HaveOccurred())
217 Expect(cmd5).To(Equal("OK"))
218
219 stop = 99
220 cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", &redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result()
221 Expect(err).NotTo(HaveOccurred())
222 Expect(cmd6).To(Equal([]int64{2}))
223
224 cmd7, err := client.JSONSet(ctx, "trim4", "$", `[0,1,2,3,4]`).Result()
225 Expect(err).NotTo(HaveOccurred())
226 Expect(cmd7).To(Equal("OK"))
227
228 stop = 1
229 cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
230 Expect(err).NotTo(HaveOccurred())
231 Expect(cmd8).To(Equal([]int64{0}))
232
233 cmd9, err := client.JSONSet(ctx, "trim5", "$", `[0,1,2,3,4]`).Result()
234 Expect(err).NotTo(HaveOccurred())
235 Expect(cmd9).To(Equal("OK"))
236
237 stop = 11
238 cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
239 Expect(err).NotTo(HaveOccurred())
240 Expect(cmd10).To(Equal([]int64{0}))
241 })
242
243 It("should JSONArrPop", Label("json.arrpop", "json"), func() {
244 cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
245 Expect(cmd1.Err()).NotTo(HaveOccurred())
246 Expect(cmd1.Val()).To(Equal("OK"))
247
248 cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
249 Expect(cmd2.Err()).NotTo(HaveOccurred())
250 Expect(cmd2.Val()).To(Equal([]string{"300"}))
251
252 cmd3 := client.JSONGet(ctx, "pop4", "$")
253 Expect(cmd3.Err()).NotTo(HaveOccurred())
254 Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
255 })
256 })
257
258 Describe("get/set", Label("getset"), func() {
259 It("should JSONSet", Label("json.set", "json"), func() {
260 cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`)
261 Expect(cmd.Err()).NotTo(HaveOccurred())
262 Expect(cmd.Val()).To(Equal("OK"))
263 })
264
265 It("should JSONGet", Label("json.get", "json", "NonRedisEnterprise"), func() {
266 res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2}`).Result()
267 Expect(err).NotTo(HaveOccurred())
268 Expect(res).To(Equal("OK"))
269
270 res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-"}).Result()
271 Expect(err).NotTo(HaveOccurred())
272 Expect(res).To(Equal(`{-"a":1,-"b":2}`))
273
274 res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-", Newline: `~`, Space: `!`}).Result()
275 Expect(err).NotTo(HaveOccurred())
276 Expect(res).To(Equal(`{~-"a":!1,~-"b":!2~}`))
277 })
278
279 It("should JSONMerge", Label("json.merge", "json"), func() {
280 res, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result()
281 Expect(err).NotTo(HaveOccurred())
282 Expect(res).To(Equal("OK"))
283
284 res, err = client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`).Result()
285 Expect(err).NotTo(HaveOccurred())
286 Expect(res).To(Equal("OK"))
287
288 res, err = client.JSONGet(ctx, "merge1", "$").Result()
289 Expect(err).NotTo(HaveOccurred())
290 Expect(res).To(Equal(`[{"a":1,"b":3,"c":4}]`))
291 })
292
293 It("should JSONMSet", Label("json.mset", "json", "NonRedisEnterprise"), func() {
294 doc1 := redis.JSONSetArgs{Key: "mset1", Path: "$", Value: `{"a": 1}`}
295 doc2 := redis.JSONSetArgs{Key: "mset2", Path: "$", Value: 2}
296 docs := []redis.JSONSetArgs{doc1, doc2}
297
298 mSetResult, err := client.JSONMSetArgs(ctx, docs).Result()
299 Expect(err).NotTo(HaveOccurred())
300 Expect(mSetResult).To(Equal("OK"))
301
302 res, err := client.JSONMGet(ctx, "$", "mset1").Result()
303 Expect(err).NotTo(HaveOccurred())
304 Expect(res).To(Equal([]interface{}{`[{"a":1}]`}))
305
306 res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result()
307 Expect(err).NotTo(HaveOccurred())
308 Expect(res).To(Equal([]interface{}{`[{"a":1}]`, "[2]"}))
309
310 _, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result()
311 Expect(err).NotTo(HaveOccurred())
312 })
313
314 It("should JSONMGet", Label("json.mget", "json", "NonRedisEnterprise"), func() {
315 cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`)
316 Expect(cmd1.Err()).NotTo(HaveOccurred())
317 Expect(cmd1.Val()).To(Equal("OK"))
318 cmd2 := client.JSONSet(ctx, "mget2b", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`)
319 Expect(cmd2.Err()).NotTo(HaveOccurred())
320 Expect(cmd2.Val()).To(Equal("OK"))
321
322 cmd3 := client.JSONMGet(ctx, "$..a", "mget2a", "mget2b")
323 Expect(cmd3.Err()).NotTo(HaveOccurred())
324 Expect(cmd3.Val()).To(HaveLen(2))
325 Expect(cmd3.Val()[0]).To(Equal(`[["aa","ab","ac","ad"],["ba","bb","bc","bd"]]`))
326 Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`))
327 })
328
329 It("should JSONMget with $", Label("json.mget", "json", "NonRedisEnterprise"), func() {
330 res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result()
331 Expect(err).NotTo(HaveOccurred())
332 Expect(res).To(Equal("OK"))
333
334 res, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result()
335 Expect(err).NotTo(HaveOccurred())
336 Expect(res).To(Equal("OK"))
337
338 iRes, err := client.JSONMGet(ctx, "$..a", "doc1").Result()
339 Expect(err).NotTo(HaveOccurred())
340 Expect(iRes).To(Equal([]interface{}{`[1,3,""]`}))
341
342 iRes, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result()
343 Expect(err).NotTo(HaveOccurred())
344 Expect(iRes).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`}))
345
346 iRes, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result()
347 Expect(err).NotTo(HaveOccurred())
348 Expect(iRes).To(Equal([]interface{}{nil, nil}))
349 })
350 })
351
352 Describe("Misc", Label("misc"), func() {
353 It("should JSONClear", Label("json.clear", "json"), func() {
354 cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`)
355 Expect(cmd1.Err()).NotTo(HaveOccurred())
356 Expect(cmd1.Val()).To(Equal("OK"))
357
358 cmd2 := client.JSONClear(ctx, "clear1", "$")
359 Expect(cmd2.Err()).NotTo(HaveOccurred())
360 Expect(cmd2.Val()).To(Equal(int64(1)))
361
362 cmd3 := client.JSONGet(ctx, "clear1", "$")
363 Expect(cmd3.Err()).NotTo(HaveOccurred())
364 Expect(cmd3.Val()).To(Equal(`[[]]`))
365 })
366
367 It("should JSONClear with $", Label("json.clear", "json"), func() {
368 doc := `{
369 "nested1": {"a": {"foo": 10, "bar": 20}},
370 "a": ["foo"],
371 "nested2": {"a": "claro"},
372 "nested3": {"a": {"baz": 50}}
373 }`
374 res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
375 Expect(err).NotTo(HaveOccurred())
376 Expect(res).To(Equal("OK"))
377
378 iRes, err := client.JSONClear(ctx, "doc1", "$..a").Result()
379 Expect(err).NotTo(HaveOccurred())
380 Expect(iRes).To(Equal(int64(3)))
381
382 resGet, err := client.JSONGet(ctx, "doc1", `$`).Result()
383 Expect(err).NotTo(HaveOccurred())
384 Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`))
385
386 res, err = client.JSONSet(ctx, "doc1", "$", doc).Result()
387 Expect(err).NotTo(HaveOccurred())
388 Expect(res).To(Equal("OK"))
389
390 iRes, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result()
391 Expect(err).NotTo(HaveOccurred())
392 Expect(iRes).To(Equal(int64(1)))
393
394 resGet, err = client.JSONGet(ctx, "doc1", `$`).Result()
395 Expect(err).NotTo(HaveOccurred())
396 Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`))
397 })
398
399 It("should JSONDel", Label("json.del", "json"), func() {
400 cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`)
401 Expect(cmd1.Err()).NotTo(HaveOccurred())
402 Expect(cmd1.Val()).To(Equal("OK"))
403
404 cmd2 := client.JSONDel(ctx, "del1", "$")
405 Expect(cmd2.Err()).NotTo(HaveOccurred())
406 Expect(cmd2.Val()).To(Equal(int64(1)))
407
408 cmd3 := client.JSONGet(ctx, "del1", "$")
409 Expect(cmd3.Err()).To(Equal(redis.Nil))
410 Expect(cmd3.Val()).To(Equal(""))
411 })
412
413 It("should JSONDel with $", Label("json.del", "json"), func() {
414 res, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
415 Expect(err).NotTo(HaveOccurred())
416 Expect(res).To(Equal("OK"))
417
418 iRes, err := client.JSONDel(ctx, "del1", "$..a").Result()
419 Expect(err).NotTo(HaveOccurred())
420 Expect(iRes).To(Equal(int64(2)))
421
422 resGet, err := client.JSONGet(ctx, "del1", "$").Result()
423 Expect(err).NotTo(HaveOccurred())
424 Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))
425
426 res, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
427 Expect(err).NotTo(HaveOccurred())
428 Expect(res).To(Equal("OK"))
429
430 iRes, err = client.JSONDel(ctx, "del2", "$..a").Result()
431 Expect(err).NotTo(HaveOccurred())
432 Expect(iRes).To(Equal(int64(1)))
433
434 resGet, err = client.JSONGet(ctx, "del2", "$").Result()
435 Expect(err).NotTo(HaveOccurred())
436 Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))
437
438 doc := `[
439 {
440 "ciao": ["non ancora"],
441 "nested": [
442 {"ciao": [1, "a"]},
443 {"ciao": [2, "a"]},
444 {"ciaoc": [3, "non", "ciao"]},
445 {"ciao": [4, "a"]},
446 {"e": [5, "non", "ciao"]}
447 ]
448 }
449 ]`
450 res, err = client.JSONSet(ctx, "del3", "$", doc).Result()
451 Expect(err).NotTo(HaveOccurred())
452 Expect(res).To(Equal("OK"))
453
454 iRes, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result()
455 Expect(err).NotTo(HaveOccurred())
456 Expect(iRes).To(Equal(int64(3)))
457
458 resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
459 resGet, err = client.JSONGet(ctx, "del3", "$").Result()
460 Expect(err).NotTo(HaveOccurred())
461 Expect(resGet).To(Equal(resVal))
462 })
463
464 It("should JSONForget", Label("json.forget", "json"), func() {
465 cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`)
466 Expect(cmd1.Err()).NotTo(HaveOccurred())
467 Expect(cmd1.Val()).To(Equal("OK"))
468
469 cmd2 := client.JSONForget(ctx, "forget3", "$..a")
470 Expect(cmd2.Err()).NotTo(HaveOccurred())
471 Expect(cmd2.Val()).To(Equal(int64(2)))
472
473 cmd3 := client.JSONGet(ctx, "forget3", "$")
474 Expect(cmd3.Err()).NotTo(HaveOccurred())
475 Expect(cmd3.Val()).To(Equal(`[{"b":{"b":"annie"}}]`))
476 })
477
478 It("should JSONForget with $", Label("json.forget", "json"), func() {
479 res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
480 Expect(err).NotTo(HaveOccurred())
481 Expect(res).To(Equal("OK"))
482
483 iRes, err := client.JSONForget(ctx, "doc1", "$..a").Result()
484 Expect(err).NotTo(HaveOccurred())
485 Expect(iRes).To(Equal(int64(2)))
486
487 resGet, err := client.JSONGet(ctx, "doc1", "$").Result()
488 Expect(err).NotTo(HaveOccurred())
489 Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))
490
491 res, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
492 Expect(err).NotTo(HaveOccurred())
493 Expect(res).To(Equal("OK"))
494
495 iRes, err = client.JSONForget(ctx, "doc2", "$..a").Result()
496 Expect(err).NotTo(HaveOccurred())
497 Expect(iRes).To(Equal(int64(1)))
498
499 resGet, err = client.JSONGet(ctx, "doc2", "$").Result()
500 Expect(err).NotTo(HaveOccurred())
501 Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))
502
503 doc := `[
504 {
505 "ciao": ["non ancora"],
506 "nested": [
507 {"ciao": [1, "a"]},
508 {"ciao": [2, "a"]},
509 {"ciaoc": [3, "non", "ciao"]},
510 {"ciao": [4, "a"]},
511 {"e": [5, "non", "ciao"]}
512 ]
513 }
514 ]`
515 res, err = client.JSONSet(ctx, "doc3", "$", doc).Result()
516 Expect(err).NotTo(HaveOccurred())
517 Expect(res).To(Equal("OK"))
518
519 iRes, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result()
520 Expect(err).NotTo(HaveOccurred())
521 Expect(iRes).To(Equal(int64(3)))
522
523 resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
524 resGet, err = client.JSONGet(ctx, "doc3", "$").Result()
525 Expect(err).NotTo(HaveOccurred())
526 Expect(resGet).To(Equal(resVal))
527 })
528
529 It("should JSONNumIncrBy", Label("json.numincrby", "json"), func() {
530 cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
531 Expect(cmd1.Err()).NotTo(HaveOccurred())
532 Expect(cmd1.Val()).To(Equal("OK"))
533
534 cmd2 := client.JSONNumIncrBy(ctx, "incr3", "$..a[1]", float64(1))
535 Expect(cmd2.Err()).NotTo(HaveOccurred())
536 Expect(cmd2.Val()).To(Equal(`[3,0]`))
537 })
538
539 It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() {
540 res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
541 Expect(err).NotTo(HaveOccurred())
542 Expect(res).To(Equal("OK"))
543
544 res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result()
545 Expect(err).NotTo(HaveOccurred())
546 Expect(res).To(Equal(`[7]`))
547
548 res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result()
549 Expect(err).NotTo(HaveOccurred())
550 Expect(res).To(Equal(`[10.5]`))
551
552 res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
553 Expect(err).NotTo(HaveOccurred())
554 Expect(res).To(Equal("OK"))
555
556 res, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result()
557 Expect(err).NotTo(HaveOccurred())
558 Expect(res).To(Equal(`[5]`))
559 })
560
561 It("should JSONObjKeys", Label("json.objkeys", "json"), func() {
562 cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
563 Expect(cmd1.Err()).NotTo(HaveOccurred())
564 Expect(cmd1.Val()).To(Equal("OK"))
565
566 cmd2 := client.JSONObjKeys(ctx, "objkeys1", "$..*")
567 Expect(cmd2.Err()).NotTo(HaveOccurred())
568 Expect(cmd2.Val()).To(HaveLen(7))
569 Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil}))
570 })
571
572 It("should JSONObjKeys with $", Label("json.objkeys", "json"), func() {
573 doc := `{
574 "nested1": {"a": {"foo": 10, "bar": 20}},
575 "a": ["foo"],
576 "nested2": {"a": {"baz": 50}}
577 }`
578 cmd1, err := client.JSONSet(ctx, "objkeys1", "$", doc).Result()
579 Expect(err).NotTo(HaveOccurred())
580 Expect(cmd1).To(Equal("OK"))
581
582 cmd2, err := client.JSONObjKeys(ctx, "objkeys1", "$.nested1.a").Result()
583 Expect(err).NotTo(HaveOccurred())
584 Expect(cmd2).To(Equal([]interface{}{[]interface{}{"foo", "bar"}}))
585
586 cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".*.a").Result()
587 Expect(err).NotTo(HaveOccurred())
588 Expect(cmd2).To(Equal([]interface{}{"foo", "bar"}))
589
590 cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".nested2.a").Result()
591 Expect(err).NotTo(HaveOccurred())
592 Expect(cmd2).To(Equal([]interface{}{"baz"}))
593
594 _, err = client.JSONObjKeys(ctx, "non_existing_doc", "..a").Result()
595 Expect(err).To(HaveOccurred())
596 })
597
598 It("should JSONObjLen", Label("json.objlen", "json"), func() {
599 cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
600 Expect(cmd1.Err()).NotTo(HaveOccurred())
601 Expect(cmd1.Val()).To(Equal("OK"))
602
603 cmd2 := client.JSONObjLen(ctx, "objlen2", "$..*")
604 Expect(cmd2.Err()).NotTo(HaveOccurred())
605 Expect(cmd2.Val()).To(HaveLen(7))
606 Expect(cmd2.Val()[0]).To(BeNil())
607 Expect(*cmd2.Val()[1]).To(Equal(int64(1)))
608 })
609
610 It("should JSONStrLen", Label("json.strlen", "json"), func() {
611 cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`)
612 Expect(cmd1.Err()).NotTo(HaveOccurred())
613 Expect(cmd1.Val()).To(Equal("OK"))
614
615 cmd2 := client.JSONStrLen(ctx, "strlen2", "$..*")
616 Expect(cmd2.Err()).NotTo(HaveOccurred())
617 Expect(cmd2.Val()).To(HaveLen(5))
618 var tmp int64 = 20
619 Expect(cmd2.Val()[0]).To(BeAssignableToTypeOf(&tmp))
620 Expect(*cmd2.Val()[0]).To(Equal(int64(5)))
621 Expect(*cmd2.Val()[1]).To(Equal(int64(3)))
622 Expect(cmd2.Val()[2]).To(BeNil())
623 Expect(*cmd2.Val()[3]).To(Equal(int64(5)))
624 Expect(*cmd2.Val()[4]).To(Equal(int64(3)))
625 })
626
627 It("should JSONStrAppend", Label("json.strappend", "json"), func() {
628 cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result()
629 Expect(err).NotTo(HaveOccurred())
630 Expect(cmd1).To(Equal("OK"))
631 cmd2, err := client.JSONStrAppend(ctx, "strapp1", "$", `"bar"`).Result()
632 Expect(err).NotTo(HaveOccurred())
633 Expect(*cmd2[0]).To(Equal(int64(6)))
634 cmd3, err := client.JSONGet(ctx, "strapp1", "$").Result()
635 Expect(err).NotTo(HaveOccurred())
636 Expect(cmd3).To(Equal(`["foobar"]`))
637 })
638
639 It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() {
640 res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
641 Expect(err).NotTo(HaveOccurred())
642 Expect(res).To(Equal("OK"))
643
644 intArrayResult, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result()
645 Expect(err).NotTo(HaveOccurred())
646 Expect(*intArrayResult[0]).To(Equal(int64(8)))
647
648 res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
649 Expect(err).NotTo(HaveOccurred())
650 Expect(res).To(Equal("OK"))
651
652 intResult, err := client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result()
653 Expect(err).NotTo(HaveOccurred())
654 Expect(*intResult[0]).To(Equal(int64(5)))
655 })
656
657 It("should JSONToggle", Label("json.toggle", "json"), func() {
658 cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`)
659 Expect(cmd1.Err()).NotTo(HaveOccurred())
660 Expect(cmd1.Val()).To(Equal("OK"))
661
662 cmd2 := client.JSONToggle(ctx, "toggle1", "$[0]")
663 Expect(cmd2.Err()).NotTo(HaveOccurred())
664 Expect(cmd2.Val()).To(HaveLen(1))
665 Expect(*cmd2.Val()[0]).To(Equal(int64(0)))
666 })
667
668 It("should JSONType", Label("json.type", "json"), func() {
669 cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`)
670 Expect(cmd1.Err()).NotTo(HaveOccurred())
671 Expect(cmd1.Val()).To(Equal("OK"))
672
673 cmd2 := client.JSONType(ctx, "type1", "$[0]")
674 Expect(cmd2.Err()).NotTo(HaveOccurred())
675 Expect(cmd2.Val()).To(HaveLen(1))
676
677 Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean")))
678 })
679 })
680
681 Describe("JSON Nil Handling", func() {
682 It("should return redis.Nil for non-existent key", func() {
683 _, err := client.JSONGet(ctx, "non-existent-key", "$").Result()
684 Expect(err).To(Equal(redis.Nil))
685 })
686
687 It("should return empty array for non-existent path in existing key", func() {
688 err := client.JSONSet(ctx, "test-key", "$", `{"a": 1, "b": "hello"}`).Err()
689 Expect(err).NotTo(HaveOccurred())
690
691
692 val, err := client.JSONGet(ctx, "test-key", "$.nonexistent").Result()
693 Expect(err).NotTo(HaveOccurred())
694 Expect(val).To(Equal("[]"))
695 })
696
697 It("should distinguish empty array from non-existent path", func() {
698 err := client.JSONSet(ctx, "test-key", "$", `{"arr": [], "obj": {}}`).Err()
699 Expect(err).NotTo(HaveOccurred())
700
701
702 val, err := client.JSONGet(ctx, "test-key", "$.arr").Result()
703 Expect(err).NotTo(HaveOccurred())
704 Expect(val).To(Equal("[[]]"))
705
706
707 val, err = client.JSONGet(ctx, "test-key", "$.missing").Result()
708 Expect(err).NotTo(HaveOccurred())
709 Expect(val).To(Equal("[]"))
710 })
711
712 It("should handle multiple paths with mixed results", func() {
713 err := client.JSONSet(ctx, "test-key", "$", `{"a": 1, "b": 2}`).Err()
714 Expect(err).NotTo(HaveOccurred())
715
716
717 val, err := client.JSONGet(ctx, "test-key", "$.a").Result()
718 Expect(err).NotTo(HaveOccurred())
719 Expect(val).To(Equal("[1]"))
720
721
722 val, err = client.JSONGet(ctx, "test-key", "$.c").Result()
723 Expect(err).NotTo(HaveOccurred())
724 Expect(val).To(Equal("[]"))
725 })
726
727 AfterEach(func() {
728
729 client.Del(ctx, "test-key", "non-existent-key")
730 })
731 })
732 }
733 })
734
735 var _ = Describe("Go-Redis Advanced JSON and RediSearch Tests", func() {
736 var client *redis.Client
737 var ctx = context.Background()
738
739 setupRedisClient := func(protocolVersion int) *redis.Client {
740 return redis.NewClient(&redis.Options{
741 Addr: "localhost:6379",
742 DB: 0,
743 Protocol: protocolVersion,
744 UnstableResp3: true,
745 })
746 }
747
748 AfterEach(func() {
749 if client != nil {
750 client.FlushDB(ctx)
751 client.Close()
752 }
753 })
754
755 Context("when testing with RESP2 and RESP3", func() {
756 protocols := []int{2, 3}
757
758 for _, protocol := range protocols {
759 When("using protocol version", func() {
760 BeforeEach(func() {
761 client = setupRedisClient(protocol)
762 })
763
764 It("should perform complex JSON and RediSearch operations", func() {
765 jsonDoc := map[string]interface{}{
766 "person": map[string]interface{}{
767 "name": "Alice",
768 "age": 30,
769 "status": true,
770 "address": map[string]interface{}{
771 "city": "Wonderland",
772 "postcode": "12345",
773 },
774 "contacts": []map[string]interface{}{
775 {"type": "email", "value": "alice@example.com"},
776 {"type": "phone", "value": "+123456789"},
777 {"type": "fax", "value": "+987654321"},
778 },
779 "friends": []map[string]interface{}{
780 {"name": "Bob", "age": 35, "status": true},
781 {"name": "Charlie", "age": 28, "status": false},
782 },
783 },
784 "settings": map[string]interface{}{
785 "notifications": map[string]interface{}{
786 "email": true,
787 "sms": false,
788 "alerts": []string{"low battery", "door open"},
789 },
790 "theme": "dark",
791 },
792 }
793
794 setCmd := client.JSONSet(ctx, "person:1", ".", jsonDoc)
795 Expect(setCmd.Err()).NotTo(HaveOccurred(), "JSON.SET failed")
796
797 getCmdRaw := client.JSONGet(ctx, "person:1", ".")
798 rawJSON, err := getCmdRaw.Result()
799 Expect(err).NotTo(HaveOccurred(), "JSON.GET (raw) failed")
800 GinkgoWriter.Printf("Raw JSON: %s\n", rawJSON)
801
802 getCmdExpanded := client.JSONGet(ctx, "person:1", ".")
803 expandedJSON, err := getCmdExpanded.Expanded()
804 Expect(err).NotTo(HaveOccurred(), "JSON.GET (expanded) failed")
805 GinkgoWriter.Printf("Expanded JSON: %+v\n", expandedJSON)
806
807 Expect(rawJSON).To(MatchJSON(jsonMustMarshal(expandedJSON)))
808
809 arrAppendCmd := client.JSONArrAppend(ctx, "person:1", "$.person.contacts", `{"type": "social", "value": "@alice_wonder"}`)
810 Expect(arrAppendCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRAPPEND failed")
811 arrLenCmd := client.JSONArrLen(ctx, "person:1", "$.person.contacts")
812 arrLen, err := arrLenCmd.Result()
813 Expect(err).NotTo(HaveOccurred(), "JSON.ARRLEN failed")
814 Expect(arrLen).To(Equal([]int64{4}), "Array length mismatch after append")
815
816 arrInsertCmd := client.JSONArrInsert(ctx, "person:1", "$.person.friends", 1, `{"name": "Diana", "age": 25, "status": true}`)
817 Expect(arrInsertCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRINSERT failed")
818
819 start := 0
820 stop := 1
821 arrTrimCmd := client.JSONArrTrimWithArgs(ctx, "person:1", "$.person.friends", &redis.JSONArrTrimArgs{Start: start, Stop: &stop})
822 Expect(arrTrimCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRTRIM failed")
823
824 mergeData := map[string]interface{}{
825 "status": false,
826 "nickname": "WonderAlice",
827 "lastLogin": time.Now().Format(time.RFC3339),
828 }
829 mergeCmd := client.JSONMerge(ctx, "person:1", "$.person", jsonMustMarshal(mergeData))
830 Expect(mergeCmd.Err()).NotTo(HaveOccurred(), "JSON.MERGE failed")
831
832 typeCmd := client.JSONType(ctx, "person:1", "$.person.nickname")
833 nicknameType, err := typeCmd.Result()
834 Expect(err).NotTo(HaveOccurred(), "JSON.TYPE failed")
835 Expect(nicknameType[0]).To(Equal([]interface{}{"string"}), "JSON.TYPE mismatch for nickname")
836
837 createIndexCmd := client.Do(ctx, "FT.CREATE", "person_idx", "ON", "JSON",
838 "PREFIX", "1", "person:", "SCHEMA",
839 "$.person.name", "AS", "name", "TEXT",
840 "$.person.age", "AS", "age", "NUMERIC",
841 "$.person.address.city", "AS", "city", "TEXT",
842 "$.person.contacts[*].value", "AS", "contact_value", "TEXT",
843 )
844 Expect(createIndexCmd.Err()).NotTo(HaveOccurred(), "FT.CREATE failed")
845
846 searchCmd := client.FTSearchWithArgs(ctx, "person_idx", "@contact_value:(alice\\@example\\.com alice_wonder)", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "$.person.name"}, {FieldName: "$.person.age"}, {FieldName: "$.person.address.city"}}})
847 searchResult, err := searchCmd.Result()
848 Expect(err).NotTo(HaveOccurred(), "FT.SEARCH failed")
849 GinkgoWriter.Printf("Advanced Search result: %+v\n", searchResult)
850
851 incrCmd := client.JSONNumIncrBy(ctx, "person:1", "$.person.age", 5)
852 incrResult, err := incrCmd.Result()
853 Expect(err).NotTo(HaveOccurred(), "JSON.NUMINCRBY failed")
854 Expect(incrResult).To(Equal("[35]"), "Age increment mismatch")
855
856 delCmd := client.JSONDel(ctx, "person:1", "$.settings.notifications.email")
857 Expect(delCmd.Err()).NotTo(HaveOccurred(), "JSON.DEL failed")
858
859 typeCmd = client.JSONType(ctx, "person:1", "$.settings.notifications.email")
860 typeResult, err := typeCmd.Result()
861 Expect(err).ToNot(HaveOccurred())
862 Expect(typeResult[0]).To(BeEmpty(), "Expected JSON.TYPE to be empty for deleted field")
863 })
864 })
865 }
866 })
867 })
868
869
870 func jsonMustMarshal(v interface{}) string {
871 bytes, err := json.Marshal(v)
872 Expect(err).NotTo(HaveOccurred())
873 return string(bytes)
874 }
875
View as plain text