Skip to content

Commit f7bb851

Browse files
authored
Merge branch 'master' into vv-9.7-master-sync
2 parents 9c0b623 + caa2592 commit f7bb851

16 files changed

+3464
-1808
lines changed

.github/wordlist.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ stunnel
5454
SynDump
5555
TCP
5656
TLS
57+
UnstableResp
5758
uri
5859
URI
5960
url
@@ -62,3 +63,5 @@ RedisStack
6263
RedisGears
6364
RedisTimeseries
6465
RediSearch
66+
RawResult
67+
RawVal

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: make test
4040

4141
- name: Upload to Codecov
42-
uses: codecov/codecov-action@v4
42+
uses: codecov/codecov-action@v5
4343
with:
4444
files: coverage.txt
4545
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/spellcheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
- name: Checkout
99
uses: actions/checkout@v4
1010
- name: Check Spelling
11-
uses: rojopolis/spellcheck-github-actions@0.40.0
11+
uses: rojopolis/spellcheck-github-actions@0.45.0
1212
with:
1313
config_path: .github/spellcheck-settings.yml
1414
task_name: Markdown

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,21 @@ rdb := redis.NewClient(&redis.Options{
186186
#### Unstable RESP3 Structures for RediSearch Commands
187187
When integrating Redis with application functionalities using RESP3, it's important to note that some response structures aren't final yet. This is especially true for more complex structures like search and query results. We recommend using RESP2 when using the search and query capabilities, but we plan to stabilize the RESP3-based API-s in the coming versions. You can find more guidance in the upcoming release notes.
188188

189+
To enable unstable RESP3, set the option in your client configuration:
190+
191+
```go
192+
redis.NewClient(&redis.Options{
193+
UnstableResp3: true,
194+
})
195+
```
196+
**Note:** When UnstableResp3 mode is enabled, it's necessary to use RawResult() and RawVal() to retrieve a raw data.
197+
Since, raw response is the only option for unstable search commands Val() and Result() calls wouldn't have any affect on them:
198+
199+
```go
200+
res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawResult()
201+
val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawVal()
202+
```
203+
189204
## Contributing
190205

191206
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!

command.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ func (cmd *baseCmd) stringArg(pos int) string {
167167
switch v := arg.(type) {
168168
case string:
169169
return v
170+
case []byte:
171+
return string(v)
170172
default:
171173
// TODO: consider using appendArg
172174
return fmt.Sprint(v)
@@ -1403,27 +1405,63 @@ func (cmd *MapStringSliceInterfaceCmd) Val() map[string][]interface{} {
14031405
}
14041406

14051407
func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) {
1406-
n, err := rd.ReadMapLen()
1408+
readType, err := rd.PeekReplyType()
14071409
if err != nil {
14081410
return err
14091411
}
1410-
cmd.val = make(map[string][]interface{}, n)
1411-
for i := 0; i < n; i++ {
1412-
k, err := rd.ReadString()
1412+
1413+
cmd.val = make(map[string][]interface{})
1414+
1415+
if readType == proto.RespMap {
1416+
n, err := rd.ReadMapLen()
14131417
if err != nil {
14141418
return err
14151419
}
1416-
nn, err := rd.ReadArrayLen()
1420+
for i := 0; i < n; i++ {
1421+
k, err := rd.ReadString()
1422+
if err != nil {
1423+
return err
1424+
}
1425+
nn, err := rd.ReadArrayLen()
1426+
if err != nil {
1427+
return err
1428+
}
1429+
cmd.val[k] = make([]interface{}, nn)
1430+
for j := 0; j < nn; j++ {
1431+
value, err := rd.ReadReply()
1432+
if err != nil {
1433+
return err
1434+
}
1435+
cmd.val[k][j] = value
1436+
}
1437+
}
1438+
} else if readType == proto.RespArray {
1439+
// RESP2 response
1440+
n, err := rd.ReadArrayLen()
14171441
if err != nil {
14181442
return err
14191443
}
1420-
cmd.val[k] = make([]interface{}, nn)
1421-
for j := 0; j < nn; j++ {
1422-
value, err := rd.ReadReply()
1444+
1445+
for i := 0; i < n; i++ {
1446+
// Each entry in this array is itself an array with key details
1447+
itemLen, err := rd.ReadArrayLen()
14231448
if err != nil {
14241449
return err
14251450
}
1426-
cmd.val[k][j] = value
1451+
1452+
key, err := rd.ReadString()
1453+
if err != nil {
1454+
return err
1455+
}
1456+
cmd.val[key] = make([]interface{}, 0, itemLen-1)
1457+
for j := 1; j < itemLen; j++ {
1458+
// Read the inner array for timestamp-value pairs
1459+
data, err := rd.ReadReply()
1460+
if err != nil {
1461+
return err
1462+
}
1463+
cmd.val[key] = append(cmd.val[key], data)
1464+
}
14271465
}
14281466
}
14291467

doctests/home_json_example_test.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// EXAMPLE: go_home_json
2+
// HIDE_START
3+
package example_commands_test
4+
5+
// HIDE_END
6+
// STEP_START import
7+
import (
8+
"context"
9+
"fmt"
10+
"sort"
11+
12+
"github.com/redis/go-redis/v9"
13+
)
14+
15+
// STEP_END
16+
17+
func ExampleClient_search_json() {
18+
// STEP_START connect
19+
ctx := context.Background()
20+
21+
rdb := redis.NewClient(&redis.Options{
22+
Addr: "localhost:6379",
23+
Password: "", // no password docs
24+
DB: 0, // use default DB
25+
Protocol: 2,
26+
})
27+
// STEP_END
28+
// REMOVE_START
29+
rdb.Del(ctx, "user:1", "user:2", "user:3")
30+
rdb.FTDropIndex(ctx, "idx:users")
31+
// REMOVE_END
32+
33+
// STEP_START create_data
34+
user1 := map[string]interface{}{
35+
"name": "Paul John",
36+
"email": "[email protected]",
37+
"age": 42,
38+
"city": "London",
39+
}
40+
41+
user2 := map[string]interface{}{
42+
"name": "Eden Zamir",
43+
"email": "[email protected]",
44+
"age": 29,
45+
"city": "Tel Aviv",
46+
}
47+
48+
user3 := map[string]interface{}{
49+
"name": "Paul Zamir",
50+
"email": "[email protected]",
51+
"age": 35,
52+
"city": "Tel Aviv",
53+
}
54+
// STEP_END
55+
56+
// STEP_START make_index
57+
_, err := rdb.FTCreate(
58+
ctx,
59+
"idx:users",
60+
// Options:
61+
&redis.FTCreateOptions{
62+
OnJSON: true,
63+
Prefix: []interface{}{"user:"},
64+
},
65+
// Index schema fields:
66+
&redis.FieldSchema{
67+
FieldName: "$.name",
68+
As: "name",
69+
FieldType: redis.SearchFieldTypeText,
70+
},
71+
&redis.FieldSchema{
72+
FieldName: "$.city",
73+
As: "city",
74+
FieldType: redis.SearchFieldTypeTag,
75+
},
76+
&redis.FieldSchema{
77+
FieldName: "$.age",
78+
As: "age",
79+
FieldType: redis.SearchFieldTypeNumeric,
80+
},
81+
).Result()
82+
83+
if err != nil {
84+
panic(err)
85+
}
86+
// STEP_END
87+
88+
// STEP_START add_data
89+
_, err = rdb.JSONSet(ctx, "user:1", "$", user1).Result()
90+
91+
if err != nil {
92+
panic(err)
93+
}
94+
95+
_, err = rdb.JSONSet(ctx, "user:2", "$", user2).Result()
96+
97+
if err != nil {
98+
panic(err)
99+
}
100+
101+
_, err = rdb.JSONSet(ctx, "user:3", "$", user3).Result()
102+
103+
if err != nil {
104+
panic(err)
105+
}
106+
// STEP_END
107+
108+
// STEP_START query1
109+
findPaulResult, err := rdb.FTSearch(
110+
ctx,
111+
"idx:users",
112+
"Paul @age:[30 40]",
113+
).Result()
114+
115+
if err != nil {
116+
panic(err)
117+
}
118+
119+
fmt.Println(findPaulResult)
120+
// >>> {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv"...
121+
// STEP_END
122+
123+
// STEP_START query2
124+
citiesResult, err := rdb.FTSearchWithArgs(
125+
ctx,
126+
"idx:users",
127+
"Paul",
128+
&redis.FTSearchOptions{
129+
Return: []redis.FTSearchReturn{
130+
{
131+
FieldName: "$.city",
132+
As: "city",
133+
},
134+
},
135+
},
136+
).Result()
137+
138+
if err != nil {
139+
panic(err)
140+
}
141+
142+
sort.Slice(citiesResult.Docs, func(i, j int) bool {
143+
return citiesResult.Docs[i].Fields["city"] < citiesResult.Docs[j].Fields["city"]
144+
})
145+
146+
for _, result := range citiesResult.Docs {
147+
fmt.Println(result.Fields["city"])
148+
}
149+
// >>> London
150+
// >>> Tel Aviv
151+
// STEP_END
152+
153+
// STEP_START query3
154+
aggOptions := redis.FTAggregateOptions{
155+
GroupBy: []redis.FTAggregateGroupBy{
156+
{
157+
Fields: []interface{}{"@city"},
158+
Reduce: []redis.FTAggregateReducer{
159+
{
160+
Reducer: redis.SearchCount,
161+
As: "count",
162+
},
163+
},
164+
},
165+
},
166+
}
167+
168+
aggResult, err := rdb.FTAggregateWithArgs(
169+
ctx,
170+
"idx:users",
171+
"*",
172+
&aggOptions,
173+
).Result()
174+
175+
if err != nil {
176+
panic(err)
177+
}
178+
179+
sort.Slice(aggResult.Rows, func(i, j int) bool {
180+
return aggResult.Rows[i].Fields["city"].(string) <
181+
aggResult.Rows[j].Fields["city"].(string)
182+
})
183+
184+
for _, row := range aggResult.Rows {
185+
fmt.Printf("%v - %v\n",
186+
row.Fields["city"], row.Fields["count"],
187+
)
188+
}
189+
// >>> City: London - 1
190+
// >>> City: Tel Aviv - 2
191+
// STEP_END
192+
193+
// Output:
194+
// {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv","email":"[email protected]","name":"Paul Zamir"}]}]}
195+
// London
196+
// Tel Aviv
197+
// London - 1
198+
// Tel Aviv - 2
199+
}

0 commit comments

Comments
 (0)