Support Json with Resp 2 (#3146) (#3155)

* Support ReJSON resp 2 && Test ReJSON against RESP 2 and 3 && Add complex search and json test

* Remove comments

* Remove unnecessary changes

Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com>
This commit is contained in:
Vladyslav Vildanov 2024-10-14 14:59:45 +03:00 committed by GitHub
parent ec680aec14
commit ac2e91d9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 772 additions and 614 deletions

View File

@ -60,7 +60,7 @@ type JSONArrTrimArgs struct {
type JSONCmd struct { type JSONCmd struct {
baseCmd baseCmd
val string val string
expanded []interface{} expanded interface{}
} }
var _ Cmder = (*JSONCmd)(nil) var _ Cmder = (*JSONCmd)(nil)
@ -100,11 +100,11 @@ func (cmd *JSONCmd) Result() (string, error) {
return cmd.Val(), cmd.Err() return cmd.Val(), cmd.Err()
} }
func (cmd JSONCmd) Expanded() (interface{}, error) { func (cmd *JSONCmd) Expanded() (interface{}, error) {
if len(cmd.val) != 0 && cmd.expanded == nil { if len(cmd.val) != 0 && cmd.expanded == nil {
err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
if err != nil { if err != nil {
return "", err return nil, err
} }
} }
@ -494,7 +494,7 @@ func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
} }
// JSONNumIncrBy increments the number value stored at the specified path by the provided 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 // For more information, see https://redis.io/docs/latest/commands/json.numincrby/
func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { 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...) cmd := newJSONCmd(ctx, args...)

View File

@ -2,6 +2,8 @@ package redis_test
import ( import (
"context" "context"
"encoding/json"
"time"
. "github.com/bsm/ginkgo/v2" . "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega" . "github.com/bsm/gomega"
@ -17,13 +19,27 @@ var _ = Describe("JSON Commands", Label("json"), func() {
ctx := context.TODO() ctx := context.TODO()
var client *redis.Client var client *redis.Client
BeforeEach(func() { setupRedisClient := func(protocolVersion int) *redis.Client {
client = redis.NewClient(&redis.Options{Addr: ":6379"}) return redis.NewClient(&redis.Options{
Expect(client.FlushAll(ctx).Err()).NotTo(HaveOccurred()) Addr: "localhost:6379",
DB: 0,
Protocol: protocolVersion,
UnstableResp3: true,
}) })
}
AfterEach(func() { AfterEach(func() {
Expect(client.Close()).NotTo(HaveOccurred()) if client != nil {
client.FlushDB(ctx)
client.Close()
}
})
protocols := []int{2, 3}
for _, protocol := range protocols {
BeforeEach(func() {
client = setupRedisClient(protocol)
Expect(client.FlushAll(ctx).Err()).NotTo(HaveOccurred())
}) })
Describe("arrays", Label("arrays"), func() { Describe("arrays", Label("arrays"), func() {
@ -657,4 +673,146 @@ var _ = Describe("JSON Commands", Label("json"), func() {
Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean"))) Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean")))
}) })
}) })
}
}) })
var _ = Describe("Go-Redis Advanced JSON and RediSearch Tests", func() {
var client *redis.Client
var ctx = context.Background()
setupRedisClient := func(protocolVersion int) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
Protocol: protocolVersion, // Setting RESP2 or RESP3 protocol
UnstableResp3: true, // Enable RESP3 features
})
}
AfterEach(func() {
if client != nil {
client.FlushDB(ctx)
client.Close()
}
})
Context("when testing with RESP2 and RESP3", func() {
protocols := []int{2, 3}
for _, protocol := range protocols {
When("using protocol version", func() {
BeforeEach(func() {
client = setupRedisClient(protocol)
})
It("should perform complex JSON and RediSearch operations", func() {
jsonDoc := map[string]interface{}{
"person": map[string]interface{}{
"name": "Alice",
"age": 30,
"status": true,
"address": map[string]interface{}{
"city": "Wonderland",
"postcode": "12345",
},
"contacts": []map[string]interface{}{
{"type": "email", "value": "alice@example.com"},
{"type": "phone", "value": "+123456789"},
{"type": "fax", "value": "+987654321"},
},
"friends": []map[string]interface{}{
{"name": "Bob", "age": 35, "status": true},
{"name": "Charlie", "age": 28, "status": false},
},
},
"settings": map[string]interface{}{
"notifications": map[string]interface{}{
"email": true,
"sms": false,
"alerts": []string{"low battery", "door open"},
},
"theme": "dark",
},
}
setCmd := client.JSONSet(ctx, "person:1", ".", jsonDoc)
Expect(setCmd.Err()).NotTo(HaveOccurred(), "JSON.SET failed")
getCmdRaw := client.JSONGet(ctx, "person:1", ".")
rawJSON, err := getCmdRaw.Result()
Expect(err).NotTo(HaveOccurred(), "JSON.GET (raw) failed")
GinkgoWriter.Printf("Raw JSON: %s\n", rawJSON)
getCmdExpanded := client.JSONGet(ctx, "person:1", ".")
expandedJSON, err := getCmdExpanded.Expanded()
Expect(err).NotTo(HaveOccurred(), "JSON.GET (expanded) failed")
GinkgoWriter.Printf("Expanded JSON: %+v\n", expandedJSON)
Expect(rawJSON).To(MatchJSON(jsonMustMarshal(expandedJSON)))
arrAppendCmd := client.JSONArrAppend(ctx, "person:1", "$.person.contacts", `{"type": "social", "value": "@alice_wonder"}`)
Expect(arrAppendCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRAPPEND failed")
arrLenCmd := client.JSONArrLen(ctx, "person:1", "$.person.contacts")
arrLen, err := arrLenCmd.Result()
Expect(err).NotTo(HaveOccurred(), "JSON.ARRLEN failed")
Expect(arrLen).To(Equal([]int64{4}), "Array length mismatch after append")
arrInsertCmd := client.JSONArrInsert(ctx, "person:1", "$.person.friends", 1, `{"name": "Diana", "age": 25, "status": true}`)
Expect(arrInsertCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRINSERT failed")
start := 0
stop := 1
arrTrimCmd := client.JSONArrTrimWithArgs(ctx, "person:1", "$.person.friends", &redis.JSONArrTrimArgs{Start: start, Stop: &stop})
Expect(arrTrimCmd.Err()).NotTo(HaveOccurred(), "JSON.ARRTRIM failed")
mergeData := map[string]interface{}{
"status": false,
"nickname": "WonderAlice",
"lastLogin": time.Now().Format(time.RFC3339),
}
mergeCmd := client.JSONMerge(ctx, "person:1", "$.person", jsonMustMarshal(mergeData))
Expect(mergeCmd.Err()).NotTo(HaveOccurred(), "JSON.MERGE failed")
typeCmd := client.JSONType(ctx, "person:1", "$.person.nickname")
nicknameType, err := typeCmd.Result()
Expect(err).NotTo(HaveOccurred(), "JSON.TYPE failed")
Expect(nicknameType[0]).To(Equal([]interface{}{"string"}), "JSON.TYPE mismatch for nickname")
createIndexCmd := client.Do(ctx, "FT.CREATE", "person_idx", "ON", "JSON",
"PREFIX", "1", "person:", "SCHEMA",
"$.person.name", "AS", "name", "TEXT",
"$.person.age", "AS", "age", "NUMERIC",
"$.person.address.city", "AS", "city", "TEXT",
"$.person.contacts[*].value", "AS", "contact_value", "TEXT",
)
Expect(createIndexCmd.Err()).NotTo(HaveOccurred(), "FT.CREATE failed")
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"}}})
searchResult, err := searchCmd.Result()
Expect(err).NotTo(HaveOccurred(), "FT.SEARCH failed")
GinkgoWriter.Printf("Advanced Search result: %+v\n", searchResult)
incrCmd := client.JSONNumIncrBy(ctx, "person:1", "$.person.age", 5)
incrResult, err := incrCmd.Result()
Expect(err).NotTo(HaveOccurred(), "JSON.NUMINCRBY failed")
Expect(incrResult).To(Equal("[35]"), "Age increment mismatch")
delCmd := client.JSONDel(ctx, "person:1", "$.settings.notifications.email")
Expect(delCmd.Err()).NotTo(HaveOccurred(), "JSON.DEL failed")
typeCmd = client.JSONType(ctx, "person:1", "$.settings.notifications.email")
typeResult, err := typeCmd.Result()
Expect(err).ToNot(HaveOccurred())
Expect(typeResult[0]).To(BeEmpty(), "Expected JSON.TYPE to be empty for deleted field")
})
})
}
})
})
// Helper function to marshal data into JSON for comparisons
func jsonMustMarshal(v interface{}) string {
bytes, err := json.Marshal(v)
Expect(err).NotTo(HaveOccurred())
return string(bytes)
}