1 package redis_test
2
3 import (
4 "bytes"
5 "context"
6 "encoding/binary"
7 "fmt"
8 "math"
9 "strings"
10 "time"
11
12 . "github.com/bsm/ginkgo/v2"
13 . "github.com/bsm/gomega"
14 "github.com/redis/go-redis/v9"
15 "github.com/redis/go-redis/v9/helper"
16 )
17
18 func WaitForIndexing(c *redis.Client, index string) {
19 for {
20 res, err := c.FTInfo(context.Background(), index).Result()
21 Expect(err).NotTo(HaveOccurred())
22 if c.Options().Protocol == 2 {
23 if res.Indexing == 0 {
24 return
25 }
26 time.Sleep(100 * time.Millisecond)
27 } else {
28 return
29 }
30 }
31 }
32
33 func encodeFloat32Vector(vec []float32) []byte {
34 buf := new(bytes.Buffer)
35 for _, v := range vec {
36 binary.Write(buf, binary.LittleEndian, v)
37 }
38 return buf.Bytes()
39 }
40
41 func encodeFloat16Vector(vec []float32) []byte {
42 buf := new(bytes.Buffer)
43 for _, v := range vec {
44
45
46 f16 := uint16(v * 1000)
47 binary.Write(buf, binary.LittleEndian, f16)
48 }
49 return buf.Bytes()
50 }
51
52 var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
53 ctx := context.TODO()
54 var client *redis.Client
55
56 BeforeEach(func() {
57 client = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 2})
58 Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
59 })
60
61 AfterEach(func() {
62 Expect(client.Close()).NotTo(HaveOccurred())
63 })
64
65 It("should FTCreate and FTSearch WithScores", Label("search", "ftcreate", "ftsearch"), func() {
66 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
67 Expect(err).NotTo(HaveOccurred())
68 Expect(val).To(BeEquivalentTo("OK"))
69 WaitForIndexing(client, "txt")
70 client.HSet(ctx, "doc1", "txt", "foo baz")
71 client.HSet(ctx, "doc2", "txt", "foo bar")
72 res, err := client.FTSearchWithArgs(ctx, "txt", "foo ~bar", &redis.FTSearchOptions{WithScores: true}).Result()
73
74 Expect(err).NotTo(HaveOccurred())
75 Expect(res.Total).To(BeEquivalentTo(int64(2)))
76 for _, doc := range res.Docs {
77 Expect(*doc.Score).To(BeNumerically(">", 0))
78 Expect(doc.ID).To(Or(Equal("doc1"), Equal("doc2")))
79 }
80 })
81
82 It("should FTCreate and FTSearch stopwords", Label("search", "ftcreate", "ftsearch"), func() {
83 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{StopWords: []interface{}{"foo", "bar", "baz"}}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
84 Expect(err).NotTo(HaveOccurred())
85 Expect(val).To(BeEquivalentTo("OK"))
86 WaitForIndexing(client, "txt")
87 client.HSet(ctx, "doc1", "txt", "foo baz")
88 client.HSet(ctx, "doc2", "txt", "hello world")
89 res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{NoContent: true}).Result()
90 Expect(err).NotTo(HaveOccurred())
91 Expect(res1.Total).To(BeEquivalentTo(int64(0)))
92 res2, err := client.FTSearchWithArgs(ctx, "txt", "foo bar hello world", &redis.FTSearchOptions{NoContent: true}).Result()
93 Expect(err).NotTo(HaveOccurred())
94 Expect(res2.Total).To(BeEquivalentTo(int64(1)))
95 })
96
97 It("should FTCreate and FTSearch filters", Label("search", "ftcreate", "ftsearch"), func() {
98 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}, &redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric}, &redis.FieldSchema{FieldName: "loc", FieldType: redis.SearchFieldTypeGeo}).Result()
99 Expect(err).NotTo(HaveOccurred())
100 Expect(val).To(BeEquivalentTo("OK"))
101 WaitForIndexing(client, "txt")
102 client.HSet(ctx, "doc1", "txt", "foo bar", "num", 3.141, "loc", "-0.441,51.458")
103 client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2, "loc", "-0.1,51.2")
104 res1, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{Filters: []redis.FTSearchFilter{{FieldName: "num", Min: 0, Max: 2}}, NoContent: true}).Result()
105 Expect(err).NotTo(HaveOccurred())
106 Expect(res1.Total).To(BeEquivalentTo(int64(1)))
107 Expect(res1.Docs[0].ID).To(BeEquivalentTo("doc2"))
108 res2, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{Filters: []redis.FTSearchFilter{{FieldName: "num", Min: 0, Max: "+inf"}}, NoContent: true}).Result()
109 Expect(err).NotTo(HaveOccurred())
110 Expect(res2.Total).To(BeEquivalentTo(int64(2)))
111 Expect(res2.Docs[0].ID).To(BeEquivalentTo("doc1"))
112
113 geoFilter1 := redis.FTSearchGeoFilter{FieldName: "loc", Longitude: -0.44, Latitude: 51.45, Radius: 10, Unit: "km"}
114 geoFilter2 := redis.FTSearchGeoFilter{FieldName: "loc", Longitude: -0.44, Latitude: 51.45, Radius: 100, Unit: "km"}
115 res3, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{GeoFilter: []redis.FTSearchGeoFilter{geoFilter1}, NoContent: true}).Result()
116 Expect(err).NotTo(HaveOccurred())
117 Expect(res3.Total).To(BeEquivalentTo(int64(1)))
118 Expect(res3.Docs[0].ID).To(BeEquivalentTo("doc1"))
119 res4, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{GeoFilter: []redis.FTSearchGeoFilter{geoFilter2}, NoContent: true}).Result()
120 Expect(err).NotTo(HaveOccurred())
121 Expect(res4.Total).To(BeEquivalentTo(int64(2)))
122 docs := []interface{}{res4.Docs[0].ID, res4.Docs[1].ID}
123 Expect(docs).To(ContainElement("doc1"))
124 Expect(docs).To(ContainElement("doc2"))
125
126 })
127
128 It("should FTCreate and FTSearch sortby", Label("search", "ftcreate", "ftsearch"), func() {
129 val, err := client.FTCreate(ctx, "num", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}, &redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).Result()
130 Expect(err).NotTo(HaveOccurred())
131 Expect(val).To(BeEquivalentTo("OK"))
132 WaitForIndexing(client, "num")
133 client.HSet(ctx, "doc1", "txt", "foo bar", "num", 1)
134 client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2)
135 client.HSet(ctx, "doc3", "txt", "foo qux", "num", 3)
136
137 sortBy1 := redis.FTSearchSortBy{FieldName: "num", Asc: true}
138 sortBy2 := redis.FTSearchSortBy{FieldName: "num", Desc: true}
139 res1, err := client.FTSearchWithArgs(ctx, "num", "foo", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy1}}).Result()
140 Expect(err).NotTo(HaveOccurred())
141 Expect(res1.Total).To(BeEquivalentTo(int64(3)))
142 Expect(res1.Docs[0].ID).To(BeEquivalentTo("doc1"))
143 Expect(res1.Docs[1].ID).To(BeEquivalentTo("doc2"))
144 Expect(res1.Docs[2].ID).To(BeEquivalentTo("doc3"))
145
146 res2, err := client.FTSearchWithArgs(ctx, "num", "foo", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy2}}).Result()
147 Expect(err).NotTo(HaveOccurred())
148 Expect(res2.Total).To(BeEquivalentTo(int64(3)))
149 Expect(res2.Docs[2].ID).To(BeEquivalentTo("doc1"))
150 Expect(res2.Docs[1].ID).To(BeEquivalentTo("doc2"))
151 Expect(res2.Docs[0].ID).To(BeEquivalentTo("doc3"))
152
153 res3, err := client.FTSearchWithArgs(ctx, "num", "foo", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy2}, SortByWithCount: true}).Result()
154 Expect(err).NotTo(HaveOccurred())
155 Expect(res3.Total).To(BeEquivalentTo(int64(3)))
156
157 res4, err := client.FTSearchWithArgs(ctx, "num", "notpresentf00", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy2}, SortByWithCount: true}).Result()
158 Expect(err).NotTo(HaveOccurred())
159 Expect(res4.Total).To(BeEquivalentTo(int64(0)))
160 })
161
162 It("should FTCreate and FTSearch example", Label("search", "ftcreate", "ftsearch"), func() {
163 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Weight: 5}, &redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).Result()
164 Expect(err).NotTo(HaveOccurred())
165 Expect(val).To(BeEquivalentTo("OK"))
166 WaitForIndexing(client, "txt")
167 client.HSet(ctx, "doc1", "title", "RediSearch", "body", "Redisearch implements a search engine on top of redis")
168 res1, err := client.FTSearchWithArgs(ctx, "txt", "search engine", &redis.FTSearchOptions{NoContent: true, Verbatim: true, LimitOffset: 0, Limit: 5}).Result()
169 Expect(err).NotTo(HaveOccurred())
170 Expect(res1.Total).To(BeEquivalentTo(int64(1)))
171
172 })
173
174 It("should FTCreate NoIndex", Label("search", "ftcreate", "ftsearch"), func() {
175 text1 := &redis.FieldSchema{FieldName: "field", FieldType: redis.SearchFieldTypeText}
176 text2 := &redis.FieldSchema{FieldName: "text", FieldType: redis.SearchFieldTypeText, NoIndex: true, Sortable: true}
177 num := &redis.FieldSchema{FieldName: "numeric", FieldType: redis.SearchFieldTypeNumeric, NoIndex: true, Sortable: true}
178 geo := &redis.FieldSchema{FieldName: "geo", FieldType: redis.SearchFieldTypeGeo, NoIndex: true, Sortable: true}
179 tag := &redis.FieldSchema{FieldName: "tag", FieldType: redis.SearchFieldTypeTag, NoIndex: true, Sortable: true}
180 val, err := client.FTCreate(ctx, "idx", &redis.FTCreateOptions{}, text1, text2, num, geo, tag).Result()
181 Expect(err).NotTo(HaveOccurred())
182 Expect(val).To(BeEquivalentTo("OK"))
183 WaitForIndexing(client, "idx")
184 client.HSet(ctx, "doc1", "field", "aaa", "text", "1", "numeric", 1, "geo", "1,1", "tag", "1")
185 client.HSet(ctx, "doc2", "field", "aab", "text", "2", "numeric", 2, "geo", "2,2", "tag", "2")
186 res1, err := client.FTSearch(ctx, "idx", "@text:aa*").Result()
187 Expect(err).NotTo(HaveOccurred())
188 Expect(res1.Total).To(BeEquivalentTo(int64(0)))
189 res2, err := client.FTSearch(ctx, "idx", "@field:aa*").Result()
190 Expect(err).NotTo(HaveOccurred())
191 Expect(res2.Total).To(BeEquivalentTo(int64(2)))
192 res3, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "text", Desc: true}}}).Result()
193 Expect(err).NotTo(HaveOccurred())
194 Expect(res3.Total).To(BeEquivalentTo(int64(2)))
195 Expect(res3.Docs[0].ID).To(BeEquivalentTo("doc2"))
196 res4, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "text", Asc: true}}}).Result()
197 Expect(err).NotTo(HaveOccurred())
198 Expect(res4.Total).To(BeEquivalentTo(int64(2)))
199 Expect(res4.Docs[0].ID).To(BeEquivalentTo("doc1"))
200 res5, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "numeric", Asc: true}}}).Result()
201 Expect(err).NotTo(HaveOccurred())
202 Expect(res5.Docs[0].ID).To(BeEquivalentTo("doc1"))
203 res6, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "geo", Asc: true}}}).Result()
204 Expect(err).NotTo(HaveOccurred())
205 Expect(res6.Docs[0].ID).To(BeEquivalentTo("doc1"))
206 res7, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "tag", Asc: true}}}).Result()
207 Expect(err).NotTo(HaveOccurred())
208 Expect(res7.Docs[0].ID).To(BeEquivalentTo("doc1"))
209
210 })
211
212 It("should FTExplain", Label("search", "ftexplain"), func() {
213 text1 := &redis.FieldSchema{FieldName: "f1", FieldType: redis.SearchFieldTypeText}
214 text2 := &redis.FieldSchema{FieldName: "f2", FieldType: redis.SearchFieldTypeText}
215 text3 := &redis.FieldSchema{FieldName: "f3", FieldType: redis.SearchFieldTypeText}
216 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, text1, text2, text3).Result()
217 Expect(err).NotTo(HaveOccurred())
218 Expect(val).To(BeEquivalentTo("OK"))
219 WaitForIndexing(client, "txt")
220 res1, err := client.FTExplain(ctx, "txt", "@f3:f3_val @f2:f2_val @f1:f1_val").Result()
221 Expect(err).NotTo(HaveOccurred())
222 Expect(res1).ToNot(BeEmpty())
223
224 })
225
226 It("should FTAlias", Label("search", "ftexplain"), func() {
227 text1 := &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}
228 text2 := &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}
229 val1, err := client.FTCreate(ctx, "testAlias", &redis.FTCreateOptions{Prefix: []interface{}{"index1:"}}, text1).Result()
230 Expect(err).NotTo(HaveOccurred())
231 Expect(val1).To(BeEquivalentTo("OK"))
232 WaitForIndexing(client, "testAlias")
233 val2, err := client.FTCreate(ctx, "testAlias2", &redis.FTCreateOptions{Prefix: []interface{}{"index2:"}}, text2).Result()
234 Expect(err).NotTo(HaveOccurred())
235 Expect(val2).To(BeEquivalentTo("OK"))
236 WaitForIndexing(client, "testAlias2")
237
238 client.HSet(ctx, "index1:lonestar", "name", "lonestar")
239 client.HSet(ctx, "index2:yogurt", "name", "yogurt")
240
241 res1, err := client.FTSearch(ctx, "testAlias", "*").Result()
242 Expect(err).NotTo(HaveOccurred())
243 Expect(res1.Docs[0].ID).To(BeEquivalentTo("index1:lonestar"))
244
245 aliasAddRes, err := client.FTAliasAdd(ctx, "testAlias", "mj23").Result()
246 Expect(err).NotTo(HaveOccurred())
247 Expect(aliasAddRes).To(BeEquivalentTo("OK"))
248
249 res1, err = client.FTSearch(ctx, "mj23", "*").Result()
250 Expect(err).NotTo(HaveOccurred())
251 Expect(res1.Docs[0].ID).To(BeEquivalentTo("index1:lonestar"))
252
253 aliasUpdateRes, err := client.FTAliasUpdate(ctx, "testAlias2", "kb24").Result()
254 Expect(err).NotTo(HaveOccurred())
255 Expect(aliasUpdateRes).To(BeEquivalentTo("OK"))
256
257 res3, err := client.FTSearch(ctx, "kb24", "*").Result()
258 Expect(err).NotTo(HaveOccurred())
259 Expect(res3.Docs[0].ID).To(BeEquivalentTo("index2:yogurt"))
260
261 aliasDelRes, err := client.FTAliasDel(ctx, "mj23").Result()
262 Expect(err).NotTo(HaveOccurred())
263 Expect(aliasDelRes).To(BeEquivalentTo("OK"))
264
265 })
266
267 It("should FTCreate and FTSearch textfield, sortable and nostem ", Label("search", "ftcreate", "ftsearch"), func() {
268 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText, Sortable: true, NoStem: true}).Result()
269 Expect(err).NotTo(HaveOccurred())
270 Expect(val).To(BeEquivalentTo("OK"))
271 WaitForIndexing(client, "idx1")
272
273 resInfo, err := client.FTInfo(ctx, "idx1").Result()
274 Expect(err).NotTo(HaveOccurred())
275 Expect(resInfo.Attributes[0].Sortable).To(BeTrue())
276 Expect(resInfo.Attributes[0].NoStem).To(BeTrue())
277
278 })
279
280 It("should FTAlter", Label("search", "ftcreate", "ftsearch", "ftalter"), func() {
281 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
282 Expect(err).NotTo(HaveOccurred())
283 Expect(val).To(BeEquivalentTo("OK"))
284 WaitForIndexing(client, "idx1")
285
286 resAlter, err := client.FTAlter(ctx, "idx1", false, []interface{}{"body", redis.SearchFieldTypeText.String()}).Result()
287 Expect(err).NotTo(HaveOccurred())
288 Expect(resAlter).To(BeEquivalentTo("OK"))
289
290 client.HSet(ctx, "doc1", "title", "MyTitle", "body", "Some content only in the body")
291 res1, err := client.FTSearch(ctx, "idx1", "only in the body").Result()
292 Expect(err).NotTo(HaveOccurred())
293 Expect(res1.Total).To(BeEquivalentTo(int64(1)))
294
295 _, err = client.FTSearch(ctx, "idx_not_exist", "only in the body").Result()
296 Expect(err).To(HaveOccurred())
297 })
298
299 It("should FTSpellCheck", Label("search", "ftcreate", "ftsearch", "ftspellcheck"), func() {
300 text1 := &redis.FieldSchema{FieldName: "f1", FieldType: redis.SearchFieldTypeText}
301 text2 := &redis.FieldSchema{FieldName: "f2", FieldType: redis.SearchFieldTypeText}
302 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
303 Expect(err).NotTo(HaveOccurred())
304 Expect(val).To(BeEquivalentTo("OK"))
305 WaitForIndexing(client, "idx1")
306
307 client.HSet(ctx, "doc1", "f1", "some valid content", "f2", "this is sample text")
308 client.HSet(ctx, "doc2", "f1", "very important", "f2", "lorem ipsum")
309
310 resSpellCheck, err := client.FTSpellCheck(ctx, "idx1", "impornant").Result()
311 Expect(err).NotTo(HaveOccurred())
312 Expect(resSpellCheck[0].Suggestions[0].Suggestion).To(BeEquivalentTo("important"))
313
314 resSpellCheck2, err := client.FTSpellCheck(ctx, "idx1", "contnt").Result()
315 Expect(err).NotTo(HaveOccurred())
316 Expect(resSpellCheck2[0].Suggestions[0].Suggestion).To(BeEquivalentTo("content"))
317
318
319 resSpellCheck3, err := client.FTSpellCheck(ctx, "idx1", "vlis").Result()
320 Expect(err).NotTo(HaveOccurred())
321 Expect(resSpellCheck3[0].Term).To(BeEquivalentTo("vlis"))
322
323 resSpellCheck4, err := client.FTSpellCheckWithArgs(ctx, "idx1", "vlis", &redis.FTSpellCheckOptions{Distance: 2}).Result()
324 Expect(err).NotTo(HaveOccurred())
325 Expect(resSpellCheck4[0].Suggestions[0].Suggestion).To(BeEquivalentTo("valid"))
326
327
328 resDictAdd, err := client.FTDictAdd(ctx, "dict", "lore", "lorem", "lorm").Result()
329 Expect(err).NotTo(HaveOccurred())
330 Expect(resDictAdd).To(BeEquivalentTo(3))
331 terms := &redis.FTSpellCheckTerms{Inclusion: "INCLUDE", Dictionary: "dict"}
332 resSpellCheck5, err := client.FTSpellCheckWithArgs(ctx, "idx1", "lorm", &redis.FTSpellCheckOptions{Terms: terms}).Result()
333 Expect(err).NotTo(HaveOccurred())
334 lorm := resSpellCheck5[0].Suggestions
335 Expect(len(lorm)).To(BeEquivalentTo(3))
336 Expect(lorm[0].Score).To(BeEquivalentTo(0.5))
337 Expect(lorm[1].Score).To(BeEquivalentTo(0))
338 Expect(lorm[2].Score).To(BeEquivalentTo(0))
339
340 terms2 := &redis.FTSpellCheckTerms{Inclusion: "EXCLUDE", Dictionary: "dict"}
341 resSpellCheck6, err := client.FTSpellCheckWithArgs(ctx, "idx1", "lorm", &redis.FTSpellCheckOptions{Terms: terms2}).Result()
342 Expect(err).NotTo(HaveOccurred())
343 Expect(resSpellCheck6).To(BeEmpty())
344 })
345
346 It("should FTDict opreations", Label("search", "ftdictdump", "ftdictdel", "ftdictadd"), func() {
347 text1 := &redis.FieldSchema{FieldName: "f1", FieldType: redis.SearchFieldTypeText}
348 text2 := &redis.FieldSchema{FieldName: "f2", FieldType: redis.SearchFieldTypeText}
349 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
350 Expect(err).NotTo(HaveOccurred())
351 Expect(val).To(BeEquivalentTo("OK"))
352 WaitForIndexing(client, "idx1")
353
354 resDictAdd, err := client.FTDictAdd(ctx, "custom_dict", "item1", "item2", "item3").Result()
355 Expect(err).NotTo(HaveOccurred())
356 Expect(resDictAdd).To(BeEquivalentTo(3))
357
358 resDictDel, err := client.FTDictDel(ctx, "custom_dict", "item2").Result()
359 Expect(err).NotTo(HaveOccurred())
360 Expect(resDictDel).To(BeEquivalentTo(1))
361
362 resDictDump, err := client.FTDictDump(ctx, "custom_dict").Result()
363 Expect(err).NotTo(HaveOccurred())
364 Expect(resDictDump).To(BeEquivalentTo([]string{"item1", "item3"}))
365
366 resDictDel2, err := client.FTDictDel(ctx, "custom_dict", "item1", "item3").Result()
367 Expect(err).NotTo(HaveOccurred())
368 Expect(resDictDel2).To(BeEquivalentTo(2))
369 })
370
371 It("should FTSearch phonetic matcher", Label("search", "ftsearch"), func() {
372 text1 := &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}
373 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1).Result()
374 Expect(err).NotTo(HaveOccurred())
375 Expect(val).To(BeEquivalentTo("OK"))
376 WaitForIndexing(client, "idx1")
377
378 client.HSet(ctx, "doc1", "name", "Jon")
379 client.HSet(ctx, "doc2", "name", "John")
380
381 res1, err := client.FTSearch(ctx, "idx1", "Jon").Result()
382 Expect(err).NotTo(HaveOccurred())
383 Expect(res1.Total).To(BeEquivalentTo(int64(1)))
384 Expect(res1.Docs[0].Fields["name"]).To(BeEquivalentTo("Jon"))
385
386 client.FlushDB(ctx)
387 text2 := &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText, PhoneticMatcher: "dm:en"}
388 val2, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text2).Result()
389 Expect(err).NotTo(HaveOccurred())
390 Expect(val2).To(BeEquivalentTo("OK"))
391 WaitForIndexing(client, "idx1")
392
393 client.HSet(ctx, "doc1", "name", "Jon")
394 client.HSet(ctx, "doc2", "name", "John")
395
396 res2, err := client.FTSearch(ctx, "idx1", "Jon").Result()
397 Expect(err).NotTo(HaveOccurred())
398 Expect(res2.Total).To(BeEquivalentTo(int64(2)))
399 names := []interface{}{res2.Docs[0].Fields["name"], res2.Docs[1].Fields["name"]}
400 Expect(names).To(ContainElement("Jon"))
401 Expect(names).To(ContainElement("John"))
402 })
403
404
405
406 It("should FTSearch WithScores", Label("search", "ftsearch"), func() {
407 SkipBeforeRedisVersion(7.9, "default scorer is not BM25STD")
408
409 text1 := &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}
410 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1).Result()
411 Expect(err).NotTo(HaveOccurred())
412 Expect(val).To(BeEquivalentTo("OK"))
413 WaitForIndexing(client, "idx1")
414
415 client.HSet(ctx, "doc1", "description", "The quick brown fox jumps over the lazy dog")
416 client.HSet(ctx, "doc2", "description", "Quick alice was beginning to get very tired of sitting by her quick sister on the bank, and of having nothing to do.")
417
418 res, err := client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true}).Result()
419 Expect(err).NotTo(HaveOccurred())
420 Expect(*res.Docs[0].Score).To(BeNumerically("<=", 0.236))
421
422 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF"}).Result()
423 Expect(err).NotTo(HaveOccurred())
424 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(1)))
425
426 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF.DOCNORM"}).Result()
427 Expect(err).NotTo(HaveOccurred())
428 Expect(*res.Docs[0].Score).To(BeEquivalentTo(0.14285714285714285))
429
430 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "BM25"}).Result()
431 Expect(err).NotTo(HaveOccurred())
432 Expect(*res.Docs[0].Score).To(BeNumerically("<=", 0.22471909420069797))
433
434 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DISMAX"}).Result()
435 Expect(err).NotTo(HaveOccurred())
436 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(2)))
437
438 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DOCSCORE"}).Result()
439 Expect(err).NotTo(HaveOccurred())
440 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(1)))
441
442 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "HAMMING"}).Result()
443 Expect(err).NotTo(HaveOccurred())
444 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(0)))
445 })
446
447
448
449 It("should FTSearch WithScores", Label("search", "ftsearch"), func() {
450 SkipAfterRedisVersion(7.9, "default scorer is not TFIDF")
451 text1 := &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}
452 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1).Result()
453 Expect(err).NotTo(HaveOccurred())
454 Expect(val).To(BeEquivalentTo("OK"))
455 WaitForIndexing(client, "idx1")
456
457 client.HSet(ctx, "doc1", "description", "The quick brown fox jumps over the lazy dog")
458 client.HSet(ctx, "doc2", "description", "Quick alice was beginning to get very tired of sitting by her quick sister on the bank, and of having nothing to do.")
459
460 res, err := client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true}).Result()
461 Expect(err).NotTo(HaveOccurred())
462 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(1)))
463
464 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF"}).Result()
465 Expect(err).NotTo(HaveOccurred())
466 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(1)))
467
468 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF.DOCNORM"}).Result()
469 Expect(err).NotTo(HaveOccurred())
470 Expect(*res.Docs[0].Score).To(BeEquivalentTo(0.14285714285714285))
471
472 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "BM25"}).Result()
473 Expect(err).NotTo(HaveOccurred())
474 Expect(*res.Docs[0].Score).To(BeNumerically("<=", 0.22471909420069797))
475
476 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DISMAX"}).Result()
477 Expect(err).NotTo(HaveOccurred())
478 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(2)))
479
480 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DOCSCORE"}).Result()
481 Expect(err).NotTo(HaveOccurred())
482 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(1)))
483
484 res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "HAMMING"}).Result()
485 Expect(err).NotTo(HaveOccurred())
486 Expect(*res.Docs[0].Score).To(BeEquivalentTo(float64(0)))
487 })
488
489 It("should FTConfigSet and FTConfigGet ", Label("search", "ftconfigget", "ftconfigset", "NonRedisEnterprise"), func() {
490 val, err := client.FTConfigSet(ctx, "MINPREFIX", "1").Result()
491 Expect(err).NotTo(HaveOccurred())
492 Expect(val).To(BeEquivalentTo("OK"))
493
494 res, err := client.FTConfigGet(ctx, "*").Result()
495 Expect(err).NotTo(HaveOccurred())
496 Expect(res["MINPREFIX"]).To(BeEquivalentTo("1"))
497
498 res, err = client.FTConfigGet(ctx, "MINPREFIX").Result()
499 Expect(err).NotTo(HaveOccurred())
500 Expect(res).To(BeEquivalentTo(map[string]interface{}{"MINPREFIX": "1"}))
501
502 })
503
504 It("should FTAggregate GroupBy ", Label("search", "ftaggregate"), func() {
505 text1 := &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}
506 text2 := &redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}
507 text3 := &redis.FieldSchema{FieldName: "parent", FieldType: redis.SearchFieldTypeText}
508 num := &redis.FieldSchema{FieldName: "random_num", FieldType: redis.SearchFieldTypeNumeric}
509 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2, text3, num).Result()
510 Expect(err).NotTo(HaveOccurred())
511 Expect(val).To(BeEquivalentTo("OK"))
512 WaitForIndexing(client, "idx1")
513
514 client.HSet(ctx, "search", "title", "RediSearch",
515 "body", "Redisearch implements a search engine on top of redis",
516 "parent", "redis",
517 "random_num", 10)
518 client.HSet(ctx, "ai", "title", "RedisAI",
519 "body", "RedisAI executes Deep Learning/Machine Learning models and managing their data.",
520 "parent", "redis",
521 "random_num", 3)
522 client.HSet(ctx, "json", "title", "RedisJson",
523 "body", "RedisJSON implements ECMA-404 The JSON Data Interchange Standard as a native data type.",
524 "parent", "redis",
525 "random_num", 8)
526
527 reducer := redis.FTAggregateReducer{Reducer: redis.SearchCount}
528 options := &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
529 res, err := client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
530 Expect(err).NotTo(HaveOccurred())
531 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
532 Expect(res.Rows[0].Fields["__generated_aliascount"]).To(BeEquivalentTo("3"))
533
534 reducer = redis.FTAggregateReducer{Reducer: redis.SearchCountDistinct, Args: []interface{}{"@title"}}
535 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
536 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
537 Expect(err).NotTo(HaveOccurred())
538 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
539 Expect(res.Rows[0].Fields["__generated_aliascount_distincttitle"]).To(BeEquivalentTo("3"))
540
541 reducer = redis.FTAggregateReducer{Reducer: redis.SearchSum, Args: []interface{}{"@random_num"}}
542 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
543 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
544 Expect(err).NotTo(HaveOccurred())
545 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
546 Expect(res.Rows[0].Fields["__generated_aliassumrandom_num"]).To(BeEquivalentTo("21"))
547
548 reducer = redis.FTAggregateReducer{Reducer: redis.SearchMin, Args: []interface{}{"@random_num"}}
549 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
550 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
551 Expect(err).NotTo(HaveOccurred())
552 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
553 Expect(res.Rows[0].Fields["__generated_aliasminrandom_num"]).To(BeEquivalentTo("3"))
554
555 reducer = redis.FTAggregateReducer{Reducer: redis.SearchMax, Args: []interface{}{"@random_num"}}
556 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
557 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
558 Expect(err).NotTo(HaveOccurred())
559 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
560 Expect(res.Rows[0].Fields["__generated_aliasmaxrandom_num"]).To(BeEquivalentTo("10"))
561
562 reducer = redis.FTAggregateReducer{Reducer: redis.SearchAvg, Args: []interface{}{"@random_num"}}
563 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
564 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
565 Expect(err).NotTo(HaveOccurred())
566 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
567 Expect(res.Rows[0].Fields["__generated_aliasavgrandom_num"]).To(BeEquivalentTo("7"))
568
569 reducer = redis.FTAggregateReducer{Reducer: redis.SearchStdDev, Args: []interface{}{"@random_num"}}
570 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
571 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
572 Expect(err).NotTo(HaveOccurred())
573 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
574 Expect(res.Rows[0].Fields["__generated_aliasstddevrandom_num"]).To(BeEquivalentTo("3.60555127546"))
575
576 reducer = redis.FTAggregateReducer{Reducer: redis.SearchQuantile, Args: []interface{}{"@random_num", 0.5}}
577 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
578 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
579 Expect(err).NotTo(HaveOccurred())
580 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
581 Expect(res.Rows[0].Fields["__generated_aliasquantilerandom_num,0.5"]).To(BeEquivalentTo("8"))
582
583 reducer = redis.FTAggregateReducer{Reducer: redis.SearchToList, Args: []interface{}{"@title"}}
584 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
585 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
586 Expect(err).NotTo(HaveOccurred())
587 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
588 Expect(res.Rows[0].Fields["__generated_aliastolisttitle"]).To(ContainElements("RediSearch", "RedisAI", "RedisJson"))
589
590 reducer = redis.FTAggregateReducer{Reducer: redis.SearchFirstValue, Args: []interface{}{"@title"}, As: "first"}
591 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
592 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
593 Expect(err).NotTo(HaveOccurred())
594 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
595 Expect(res.Rows[0].Fields["first"]).To(Or(BeEquivalentTo("RediSearch"), BeEquivalentTo("RedisAI"), BeEquivalentTo("RedisJson")))
596
597 reducer = redis.FTAggregateReducer{Reducer: redis.SearchRandomSample, Args: []interface{}{"@title", 2}, As: "random"}
598 options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
599 res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
600 Expect(err).NotTo(HaveOccurred())
601 Expect(res.Rows[0].Fields["parent"]).To(BeEquivalentTo("redis"))
602 Expect(res.Rows[0].Fields["random"]).To(Or(
603 ContainElement("RediSearch"),
604 ContainElement("RedisAI"),
605 ContainElement("RedisJson"),
606 ))
607
608 })
609
610 It("should FTAggregate sort and limit", Label("search", "ftaggregate"), func() {
611 text1 := &redis.FieldSchema{FieldName: "t1", FieldType: redis.SearchFieldTypeText}
612 text2 := &redis.FieldSchema{FieldName: "t2", FieldType: redis.SearchFieldTypeText}
613 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
614 Expect(err).NotTo(HaveOccurred())
615 Expect(val).To(BeEquivalentTo("OK"))
616 WaitForIndexing(client, "idx1")
617
618 client.HSet(ctx, "doc1", "t1", "a", "t2", "b")
619 client.HSet(ctx, "doc2", "t1", "b", "t2", "a")
620
621 options := &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t2", Asc: true}, {FieldName: "@t1", Desc: true}}}
622 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
623 Expect(err).NotTo(HaveOccurred())
624 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("b"))
625 Expect(res.Rows[1].Fields["t1"]).To(BeEquivalentTo("a"))
626 Expect(res.Rows[0].Fields["t2"]).To(BeEquivalentTo("a"))
627 Expect(res.Rows[1].Fields["t2"]).To(BeEquivalentTo("b"))
628
629 options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}}
630 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
631 Expect(err).NotTo(HaveOccurred())
632 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("a"))
633 Expect(res.Rows[1].Fields["t1"]).To(BeEquivalentTo("b"))
634
635 options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}, SortByMax: 1}
636 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
637 Expect(err).NotTo(HaveOccurred())
638 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("a"))
639
640 options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}, Limit: 1, LimitOffset: 1}
641 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
642 Expect(err).NotTo(HaveOccurred())
643 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("b"))
644
645 options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}, Limit: 1, LimitOffset: 0}
646 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
647 Expect(err).NotTo(HaveOccurred())
648 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("a"))
649 })
650
651 It("should FTAggregate load ", Label("search", "ftaggregate"), func() {
652 text1 := &redis.FieldSchema{FieldName: "t1", FieldType: redis.SearchFieldTypeText}
653 text2 := &redis.FieldSchema{FieldName: "t2", FieldType: redis.SearchFieldTypeText}
654 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
655 Expect(err).NotTo(HaveOccurred())
656 Expect(val).To(BeEquivalentTo("OK"))
657 WaitForIndexing(client, "idx1")
658
659 client.HSet(ctx, "doc1", "t1", "hello", "t2", "world")
660
661 options := &redis.FTAggregateOptions{Load: []redis.FTAggregateLoad{{Field: "t1"}}}
662 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
663 Expect(err).NotTo(HaveOccurred())
664 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("hello"))
665
666 options = &redis.FTAggregateOptions{Load: []redis.FTAggregateLoad{{Field: "t2"}}}
667 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
668 Expect(err).NotTo(HaveOccurred())
669 Expect(res.Rows[0].Fields["t2"]).To(BeEquivalentTo("world"))
670
671 options = &redis.FTAggregateOptions{Load: []redis.FTAggregateLoad{{Field: "t2", As: "t2alias"}}}
672 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
673 Expect(err).NotTo(HaveOccurred())
674 Expect(res.Rows[0].Fields["t2alias"]).To(BeEquivalentTo("world"))
675
676 options = &redis.FTAggregateOptions{Load: []redis.FTAggregateLoad{{Field: "t1"}, {Field: "t2", As: "t2alias"}}}
677 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
678 Expect(err).NotTo(HaveOccurred())
679 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("hello"))
680 Expect(res.Rows[0].Fields["t2alias"]).To(BeEquivalentTo("world"))
681
682 options = &redis.FTAggregateOptions{LoadAll: true}
683 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
684 Expect(err).NotTo(HaveOccurred())
685 Expect(res.Rows[0].Fields["t1"]).To(BeEquivalentTo("hello"))
686 Expect(res.Rows[0].Fields["t2"]).To(BeEquivalentTo("world"))
687
688 _, err = client.FTAggregateWithArgs(ctx, "idx_not_exist", "*", &redis.FTAggregateOptions{}).Result()
689 Expect(err).To(HaveOccurred())
690 })
691
692 It("should FTAggregate with scorer and addscores", Label("search", "ftaggregate", "NonRedisEnterprise"), func() {
693 SkipBeforeRedisVersion(7.4, "no addscores support")
694 title := &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Sortable: false}
695 description := &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText, Sortable: false}
696 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnHash: true, Prefix: []interface{}{"product:"}}, title, description).Result()
697 Expect(err).NotTo(HaveOccurred())
698 Expect(val).To(BeEquivalentTo("OK"))
699 WaitForIndexing(client, "idx1")
700
701 client.HSet(ctx, "product:1", "title", "New Gaming Laptop", "description", "this is not a desktop")
702 client.HSet(ctx, "product:2", "title", "Super Old Not Gaming Laptop", "description", "this laptop is not a new laptop but it is a laptop")
703 client.HSet(ctx, "product:3", "title", "Office PC", "description", "office desktop pc")
704
705 options := &redis.FTAggregateOptions{
706 AddScores: true,
707 Scorer: "BM25",
708 SortBy: []redis.FTAggregateSortBy{{
709 FieldName: "@__score",
710 Desc: true,
711 }},
712 }
713
714 res, err := client.FTAggregateWithArgs(ctx, "idx1", "laptop", options).Result()
715 Expect(err).NotTo(HaveOccurred())
716 Expect(res).ToNot(BeNil())
717 Expect(len(res.Rows)).To(BeEquivalentTo(2))
718 score1, err := helper.ParseFloat(fmt.Sprintf("%s", res.Rows[0].Fields["__score"]))
719 Expect(err).NotTo(HaveOccurred())
720 score2, err := helper.ParseFloat(fmt.Sprintf("%s", res.Rows[1].Fields["__score"]))
721 Expect(err).NotTo(HaveOccurred())
722 Expect(score1).To(BeNumerically(">", score2))
723
724 optionsDM := &redis.FTAggregateOptions{
725 AddScores: true,
726 Scorer: "DISMAX",
727 SortBy: []redis.FTAggregateSortBy{{
728 FieldName: "@__score",
729 Desc: true,
730 }},
731 }
732
733 resDM, err := client.FTAggregateWithArgs(ctx, "idx1", "laptop", optionsDM).Result()
734 Expect(err).NotTo(HaveOccurred())
735 Expect(resDM).ToNot(BeNil())
736 Expect(len(resDM.Rows)).To(BeEquivalentTo(2))
737 score1DM, err := helper.ParseFloat(fmt.Sprintf("%s", resDM.Rows[0].Fields["__score"]))
738 Expect(err).NotTo(HaveOccurred())
739 score2DM, err := helper.ParseFloat(fmt.Sprintf("%s", resDM.Rows[1].Fields["__score"]))
740 Expect(err).NotTo(HaveOccurred())
741 Expect(score1DM).To(BeNumerically(">", score2DM))
742
743 Expect(score1DM).To(BeEquivalentTo(float64(4)))
744 Expect(score2DM).To(BeEquivalentTo(float64(1)))
745 Expect(score1).NotTo(BeEquivalentTo(score1DM))
746 Expect(score2).NotTo(BeEquivalentTo(score2DM))
747 })
748
749 It("should FTAggregate apply and groupby", Label("search", "ftaggregate"), func() {
750 text1 := &redis.FieldSchema{FieldName: "PrimaryKey", FieldType: redis.SearchFieldTypeText, Sortable: true}
751 num1 := &redis.FieldSchema{FieldName: "CreatedDateTimeUTC", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}
752 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, num1).Result()
753 Expect(err).NotTo(HaveOccurred())
754 Expect(val).To(BeEquivalentTo("OK"))
755 WaitForIndexing(client, "idx1")
756
757
758 client.HSet(ctx, "doc1", "PrimaryKey", "9::362330", "CreatedDateTimeUTC", "1738823999")
759
760
761 client.HSet(ctx, "doc2", "PrimaryKey", "9::362329", "CreatedDateTimeUTC", "1739342399")
762 client.HSet(ctx, "doc3", "PrimaryKey", "9::362329", "CreatedDateTimeUTC", "1739353199")
763
764 reducer := redis.FTAggregateReducer{Reducer: redis.SearchCount, As: "perDay"}
765
766 options := &redis.FTAggregateOptions{
767 Apply: []redis.FTAggregateApply{{Field: "floor(@CreatedDateTimeUTC /(60*60*24))", As: "TimestampAsDay"}},
768 GroupBy: []redis.FTAggregateGroupBy{{
769 Fields: []interface{}{"@TimestampAsDay"},
770 Reduce: []redis.FTAggregateReducer{reducer},
771 }},
772 SortBy: []redis.FTAggregateSortBy{{
773 FieldName: "@perDay",
774 Desc: true,
775 }},
776 }
777
778 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
779 Expect(err).NotTo(HaveOccurred())
780 Expect(res).ToNot(BeNil())
781 Expect(len(res.Rows)).To(BeEquivalentTo(2))
782 Expect(res.Rows[0].Fields["perDay"]).To(BeEquivalentTo("2"))
783 Expect(res.Rows[1].Fields["perDay"]).To(BeEquivalentTo("1"))
784 })
785
786 It("should FTAggregate apply", Label("search", "ftaggregate"), func() {
787 text1 := &redis.FieldSchema{FieldName: "PrimaryKey", FieldType: redis.SearchFieldTypeText, Sortable: true}
788 num1 := &redis.FieldSchema{FieldName: "CreatedDateTimeUTC", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}
789 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, num1).Result()
790 Expect(err).NotTo(HaveOccurred())
791 Expect(val).To(BeEquivalentTo("OK"))
792 WaitForIndexing(client, "idx1")
793
794 client.HSet(ctx, "doc1", "PrimaryKey", "9::362330", "CreatedDateTimeUTC", "637387878524969984")
795 client.HSet(ctx, "doc2", "PrimaryKey", "9::362329", "CreatedDateTimeUTC", "637387875859270016")
796
797 options := &redis.FTAggregateOptions{Apply: []redis.FTAggregateApply{{Field: "@CreatedDateTimeUTC * 10", As: "CreatedDateTimeUTC"}}}
798 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
799 Expect(err).NotTo(HaveOccurred())
800 Expect(res.Rows[0].Fields["CreatedDateTimeUTC"]).To(Or(BeEquivalentTo("6373878785249699840"), BeEquivalentTo("6373878758592700416")))
801 Expect(res.Rows[1].Fields["CreatedDateTimeUTC"]).To(Or(BeEquivalentTo("6373878785249699840"), BeEquivalentTo("6373878758592700416")))
802
803 })
804
805 It("should FTAggregate filter", Label("search", "ftaggregate"), func() {
806 text1 := &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText, Sortable: true}
807 num1 := &redis.FieldSchema{FieldName: "age", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}
808 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, num1).Result()
809 Expect(err).NotTo(HaveOccurred())
810 Expect(val).To(BeEquivalentTo("OK"))
811 WaitForIndexing(client, "idx1")
812
813 client.HSet(ctx, "doc1", "name", "bar", "age", "25")
814 client.HSet(ctx, "doc2", "name", "foo", "age", "19")
815
816 for _, dlc := range []int{1, 2} {
817 options := &redis.FTAggregateOptions{Filter: "@name=='foo' && @age < 20", DialectVersion: dlc}
818 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
819 Expect(err).NotTo(HaveOccurred())
820 Expect(res.Total).To(Or(BeEquivalentTo(2), BeEquivalentTo(1)))
821 Expect(res.Rows[0].Fields["name"]).To(BeEquivalentTo("foo"))
822
823 options = &redis.FTAggregateOptions{Filter: "@age > 15", DialectVersion: dlc, SortBy: []redis.FTAggregateSortBy{{FieldName: "@age"}}}
824 res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
825 Expect(err).NotTo(HaveOccurred())
826 Expect(res.Total).To(BeEquivalentTo(2))
827 Expect(res.Rows[0].Fields["age"]).To(BeEquivalentTo("19"))
828 Expect(res.Rows[1].Fields["age"]).To(BeEquivalentTo("25"))
829 }
830 })
831
832 It("should return only the base query when options is nil", Label("search", "ftaggregate"), func() {
833 args, err := redis.FTAggregateQuery("testQuery", nil)
834 Expect(err).NotTo(HaveOccurred())
835 Expect(args).To(Equal(redis.AggregateQuery{"testQuery"}))
836 })
837
838 It("should include VERBATIM and SCORER when options are set", Label("search", "ftaggregate"), func() {
839 options := &redis.FTAggregateOptions{
840 Verbatim: true,
841 Scorer: "BM25",
842 }
843 args, err := redis.FTAggregateQuery("testQuery", options)
844 Expect(err).NotTo(HaveOccurred())
845 Expect(args[0]).To(Equal("testQuery"))
846 Expect(args).To(ContainElement("VERBATIM"))
847 Expect(args).To(ContainElement("SCORER"))
848 Expect(args).To(ContainElement("BM25"))
849 })
850
851 It("should include ADDSCORES when AddScores is true", Label("search", "ftaggregate"), func() {
852 options := &redis.FTAggregateOptions{
853 AddScores: true,
854 }
855 args, err := redis.FTAggregateQuery("q", options)
856 Expect(err).NotTo(HaveOccurred())
857 Expect(args).To(ContainElement("ADDSCORES"))
858 })
859
860 It("should include LOADALL when LoadAll is true", Label("search", "ftaggregate"), func() {
861 options := &redis.FTAggregateOptions{
862 LoadAll: true,
863 }
864 args, err := redis.FTAggregateQuery("q", options)
865 Expect(err).NotTo(HaveOccurred())
866 Expect(args).To(ContainElement("LOAD"))
867 Expect(args).To(ContainElement("*"))
868 })
869
870 It("should include LOAD when Load is provided", Label("search", "ftaggregate"), func() {
871 options := &redis.FTAggregateOptions{
872 Load: []redis.FTAggregateLoad{
873 {Field: "field1", As: "alias1"},
874 {Field: "field2"},
875 },
876 }
877 args, err := redis.FTAggregateQuery("q", options)
878 Expect(err).NotTo(HaveOccurred())
879
880 Expect(args).To(ContainElement("LOAD"))
881
882 Expect(args).To(ContainElement("field1"))
883 Expect(args).To(ContainElement("alias1"))
884 Expect(args).To(ContainElement("field2"))
885 })
886
887 It("should include TIMEOUT when Timeout > 0", Label("search", "ftaggregate"), func() {
888 options := &redis.FTAggregateOptions{
889 Timeout: 500,
890 }
891 args, err := redis.FTAggregateQuery("q", options)
892 Expect(err).NotTo(HaveOccurred())
893 Expect(args).To(ContainElement("TIMEOUT"))
894 found := false
895 for i, a := range args {
896 if fmt.Sprintf("%s", a) == "TIMEOUT" {
897 Expect(fmt.Sprintf("%d", args[i+1])).To(Equal("500"))
898 found = true
899 break
900 }
901 }
902 Expect(found).To(BeTrue())
903 })
904
905 It("should FTSearch SkipInitialScan", Label("search", "ftsearch"), func() {
906 client.HSet(ctx, "doc1", "foo", "bar")
907
908 text1 := &redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}
909 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{SkipInitialScan: true}, text1).Result()
910 Expect(err).NotTo(HaveOccurred())
911 Expect(val).To(BeEquivalentTo("OK"))
912 WaitForIndexing(client, "idx1")
913
914 res, err := client.FTSearch(ctx, "idx1", "@foo:bar").Result()
915 Expect(err).NotTo(HaveOccurred())
916 Expect(res.Total).To(BeEquivalentTo(int64(0)))
917 })
918
919 It("should FTCreate json", Label("search", "ftcreate"), func() {
920
921 text1 := &redis.FieldSchema{FieldName: "$.name", FieldType: redis.SearchFieldTypeText}
922 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true, Prefix: []interface{}{"king:"}}, text1).Result()
923 Expect(err).NotTo(HaveOccurred())
924 Expect(val).To(BeEquivalentTo("OK"))
925 WaitForIndexing(client, "idx1")
926
927 client.JSONSet(ctx, "king:1", "$", `{"name": "henry"}`)
928 client.JSONSet(ctx, "king:2", "$", `{"name": "james"}`)
929
930 res, err := client.FTSearch(ctx, "idx1", "henry").Result()
931 Expect(err).NotTo(HaveOccurred())
932 Expect(res.Total).To(BeEquivalentTo(1))
933 Expect(res.Docs[0].ID).To(BeEquivalentTo("king:1"))
934 Expect(res.Docs[0].Fields["$"]).To(BeEquivalentTo(`{"name":"henry"}`))
935 })
936
937 It("should FTCreate json fields as names", Label("search", "ftcreate"), func() {
938
939 text1 := &redis.FieldSchema{FieldName: "$.name", FieldType: redis.SearchFieldTypeText, As: "name"}
940 num1 := &redis.FieldSchema{FieldName: "$.age", FieldType: redis.SearchFieldTypeNumeric, As: "just_a_number"}
941 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true}, text1, num1).Result()
942 Expect(err).NotTo(HaveOccurred())
943 Expect(val).To(BeEquivalentTo("OK"))
944 WaitForIndexing(client, "idx1")
945
946 client.JSONSet(ctx, "doc:1", "$", `{"name": "Jon", "age": 25}`)
947
948 res, err := client.FTSearchWithArgs(ctx, "idx1", "Jon", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "name"}, {FieldName: "just_a_number"}}}).Result()
949 Expect(err).NotTo(HaveOccurred())
950 Expect(res.Total).To(BeEquivalentTo(1))
951 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc:1"))
952 Expect(res.Docs[0].Fields["name"]).To(BeEquivalentTo("Jon"))
953 Expect(res.Docs[0].Fields["just_a_number"]).To(BeEquivalentTo("25"))
954 })
955
956 It("should FTCreate CaseSensitive", Label("search", "ftcreate"), func() {
957
958 tag1 := &redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeTag, CaseSensitive: false}
959 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, tag1).Result()
960 Expect(err).NotTo(HaveOccurred())
961 Expect(val).To(BeEquivalentTo("OK"))
962 WaitForIndexing(client, "idx1")
963
964 client.HSet(ctx, "1", "t", "HELLO")
965 client.HSet(ctx, "2", "t", "hello")
966
967 res, err := client.FTSearch(ctx, "idx1", "@t:{HELLO}").Result()
968 Expect(err).NotTo(HaveOccurred())
969 Expect(res.Total).To(BeEquivalentTo(2))
970 Expect(res.Docs[0].ID).To(BeEquivalentTo("1"))
971 Expect(res.Docs[1].ID).To(BeEquivalentTo("2"))
972
973 resDrop, err := client.FTDropIndex(ctx, "idx1").Result()
974 Expect(err).NotTo(HaveOccurred())
975 Expect(resDrop).To(BeEquivalentTo("OK"))
976
977 tag2 := &redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeTag, CaseSensitive: true}
978 val, err = client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, tag2).Result()
979 Expect(err).NotTo(HaveOccurred())
980 Expect(val).To(BeEquivalentTo("OK"))
981 WaitForIndexing(client, "idx1")
982
983 res, err = client.FTSearch(ctx, "idx1", "@t:{HELLO}").Result()
984 Expect(err).NotTo(HaveOccurred())
985 Expect(res.Total).To(BeEquivalentTo(1))
986 Expect(res.Docs[0].ID).To(BeEquivalentTo("1"))
987
988 })
989
990 It("should FTSearch ReturnFields", Label("search", "ftsearch"), func() {
991 resJson, err := client.JSONSet(ctx, "doc:1", "$", `{"t": "riceratops","t2": "telmatosaurus", "n": 9072, "flt": 97.2}`).Result()
992 Expect(err).NotTo(HaveOccurred())
993 Expect(resJson).To(BeEquivalentTo("OK"))
994
995 text1 := &redis.FieldSchema{FieldName: "$.t", FieldType: redis.SearchFieldTypeText}
996 num1 := &redis.FieldSchema{FieldName: "$.flt", FieldType: redis.SearchFieldTypeNumeric}
997 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true}, text1, num1).Result()
998 Expect(err).NotTo(HaveOccurred())
999 Expect(val).To(BeEquivalentTo("OK"))
1000 WaitForIndexing(client, "idx1")
1001
1002 res, err := client.FTSearchWithArgs(ctx, "idx1", "*", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "$.t", As: "txt"}}}).Result()
1003 Expect(err).NotTo(HaveOccurred())
1004 Expect(res.Total).To(BeEquivalentTo(1))
1005 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc:1"))
1006 Expect(res.Docs[0].Fields["txt"]).To(BeEquivalentTo("riceratops"))
1007
1008 res, err = client.FTSearchWithArgs(ctx, "idx1", "*", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "$.t2", As: "txt"}}}).Result()
1009 Expect(err).NotTo(HaveOccurred())
1010 Expect(res.Total).To(BeEquivalentTo(1))
1011 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc:1"))
1012 Expect(res.Docs[0].Fields["txt"]).To(BeEquivalentTo("telmatosaurus"))
1013 })
1014
1015 It("should FTSynUpdate", Label("search", "ftsynupdate"), func() {
1016
1017 text1 := &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}
1018 text2 := &redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}
1019 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnHash: true}, text1, text2).Result()
1020 Expect(err).NotTo(HaveOccurred())
1021 Expect(val).To(BeEquivalentTo("OK"))
1022 WaitForIndexing(client, "idx1")
1023
1024 resSynUpdate, err := client.FTSynUpdateWithArgs(ctx, "idx1", "id1", &redis.FTSynUpdateOptions{SkipInitialScan: true}, []interface{}{"boy", "child", "offspring"}).Result()
1025 Expect(err).NotTo(HaveOccurred())
1026 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
1027 client.HSet(ctx, "doc1", "title", "he is a baby", "body", "this is a test")
1028
1029 resSynUpdate, err = client.FTSynUpdateWithArgs(ctx, "idx1", "id1", &redis.FTSynUpdateOptions{SkipInitialScan: true}, []interface{}{"baby"}).Result()
1030 Expect(err).NotTo(HaveOccurred())
1031 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
1032 client.HSet(ctx, "doc2", "title", "he is another baby", "body", "another test")
1033
1034 res, err := client.FTSearchWithArgs(ctx, "idx1", "child", &redis.FTSearchOptions{Expander: "SYNONYM"}).Result()
1035 Expect(err).NotTo(HaveOccurred())
1036 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc2"))
1037 Expect(res.Docs[0].Fields["title"]).To(BeEquivalentTo("he is another baby"))
1038 Expect(res.Docs[0].Fields["body"]).To(BeEquivalentTo("another test"))
1039 })
1040
1041 It("should FTSynDump", Label("search", "ftsyndump"), func() {
1042
1043 text1 := &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}
1044 text2 := &redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}
1045 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnHash: true}, text1, text2).Result()
1046 Expect(err).NotTo(HaveOccurred())
1047 Expect(val).To(BeEquivalentTo("OK"))
1048 WaitForIndexing(client, "idx1")
1049
1050 resSynUpdate, err := client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"boy", "child", "offspring"}).Result()
1051 Expect(err).NotTo(HaveOccurred())
1052 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
1053
1054 resSynUpdate, err = client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"baby", "child"}).Result()
1055 Expect(err).NotTo(HaveOccurred())
1056 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
1057
1058 resSynUpdate, err = client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"tree", "wood"}).Result()
1059 Expect(err).NotTo(HaveOccurred())
1060 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
1061
1062 resSynDump, err := client.FTSynDump(ctx, "idx1").Result()
1063 Expect(err).NotTo(HaveOccurred())
1064 Expect(resSynDump[0].Term).To(BeEquivalentTo("baby"))
1065 Expect(resSynDump[0].Synonyms).To(BeEquivalentTo([]string{"id1"}))
1066 Expect(resSynDump[1].Term).To(BeEquivalentTo("wood"))
1067 Expect(resSynDump[1].Synonyms).To(BeEquivalentTo([]string{"id1"}))
1068 Expect(resSynDump[2].Term).To(BeEquivalentTo("boy"))
1069 Expect(resSynDump[2].Synonyms).To(BeEquivalentTo([]string{"id1"}))
1070 Expect(resSynDump[3].Term).To(BeEquivalentTo("tree"))
1071 Expect(resSynDump[3].Synonyms).To(BeEquivalentTo([]string{"id1"}))
1072 Expect(resSynDump[4].Term).To(BeEquivalentTo("child"))
1073 Expect(resSynDump[4].Synonyms).To(Or(BeEquivalentTo([]string{"id1"}), BeEquivalentTo([]string{"id1", "id1"})))
1074 Expect(resSynDump[5].Term).To(BeEquivalentTo("offspring"))
1075 Expect(resSynDump[5].Synonyms).To(BeEquivalentTo([]string{"id1"}))
1076
1077 })
1078
1079 It("should FTCreate json with alias", Label("search", "ftcreate"), func() {
1080
1081 text1 := &redis.FieldSchema{FieldName: "$.name", FieldType: redis.SearchFieldTypeText, As: "name"}
1082 num1 := &redis.FieldSchema{FieldName: "$.num", FieldType: redis.SearchFieldTypeNumeric, As: "num"}
1083 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true, Prefix: []interface{}{"king:"}}, text1, num1).Result()
1084 Expect(err).NotTo(HaveOccurred())
1085 Expect(val).To(BeEquivalentTo("OK"))
1086 WaitForIndexing(client, "idx1")
1087
1088 client.JSONSet(ctx, "king:1", "$", `{"name": "henry", "num": 42}`)
1089 client.JSONSet(ctx, "king:2", "$", `{"name": "james", "num": 3.14}`)
1090
1091 res, err := client.FTSearch(ctx, "idx1", "@name:henry").Result()
1092 Expect(err).NotTo(HaveOccurred())
1093 Expect(res.Total).To(BeEquivalentTo(1))
1094 Expect(res.Docs[0].ID).To(BeEquivalentTo("king:1"))
1095 Expect(res.Docs[0].Fields["$"]).To(BeEquivalentTo(`{"name":"henry","num":42}`))
1096
1097 res, err = client.FTSearch(ctx, "idx1", "@num:[0 10]").Result()
1098 Expect(err).NotTo(HaveOccurred())
1099 Expect(res.Total).To(BeEquivalentTo(1))
1100 Expect(res.Docs[0].ID).To(BeEquivalentTo("king:2"))
1101 Expect(res.Docs[0].Fields["$"]).To(BeEquivalentTo(`{"name":"james","num":3.14}`))
1102 })
1103
1104 It("should FTCreate json with multipath", Label("search", "ftcreate"), func() {
1105
1106 tag1 := &redis.FieldSchema{FieldName: "$..name", FieldType: redis.SearchFieldTypeTag, As: "name"}
1107 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true, Prefix: []interface{}{"king:"}}, tag1).Result()
1108 Expect(err).NotTo(HaveOccurred())
1109 Expect(val).To(BeEquivalentTo("OK"))
1110 WaitForIndexing(client, "idx1")
1111
1112 client.JSONSet(ctx, "king:1", "$", `{"name": "henry", "country": {"name": "england"}}`)
1113
1114 res, err := client.FTSearch(ctx, "idx1", "@name:{england}").Result()
1115 Expect(err).NotTo(HaveOccurred())
1116 Expect(res.Total).To(BeEquivalentTo(1))
1117 Expect(res.Docs[0].ID).To(BeEquivalentTo("king:1"))
1118 Expect(res.Docs[0].Fields["$"]).To(BeEquivalentTo(`{"name":"henry","country":{"name":"england"}}`))
1119 })
1120
1121 It("should FTCreate json with jsonpath", Label("search", "ftcreate"), func() {
1122
1123 text1 := &redis.FieldSchema{FieldName: `$["prod:name"]`, FieldType: redis.SearchFieldTypeText, As: "name"}
1124 text2 := &redis.FieldSchema{FieldName: `$.prod:name`, FieldType: redis.SearchFieldTypeText, As: "name_unsupported"}
1125 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnJSON: true}, text1, text2).Result()
1126 Expect(err).NotTo(HaveOccurred())
1127 Expect(val).To(BeEquivalentTo("OK"))
1128 WaitForIndexing(client, "idx1")
1129
1130 client.JSONSet(ctx, "doc:1", "$", `{"prod:name": "RediSearch"}`)
1131
1132 res, err := client.FTSearch(ctx, "idx1", "@name:RediSearch").Result()
1133 Expect(err).NotTo(HaveOccurred())
1134 Expect(res.Total).To(BeEquivalentTo(1))
1135 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc:1"))
1136 Expect(res.Docs[0].Fields["$"]).To(BeEquivalentTo(`{"prod:name":"RediSearch"}`))
1137
1138 res, err = client.FTSearch(ctx, "idx1", "@name_unsupported:RediSearch").Result()
1139 Expect(err).NotTo(HaveOccurred())
1140 Expect(res.Total).To(BeEquivalentTo(1))
1141
1142 res, err = client.FTSearchWithArgs(ctx, "idx1", "@name:RediSearch", &redis.FTSearchOptions{Return: []redis.FTSearchReturn{{FieldName: "name"}}}).Result()
1143 Expect(err).NotTo(HaveOccurred())
1144 Expect(res.Total).To(BeEquivalentTo(1))
1145 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc:1"))
1146 Expect(res.Docs[0].Fields["name"]).To(BeEquivalentTo("RediSearch"))
1147
1148 })
1149
1150 It("should FTCreate VECTOR", Label("search", "ftcreate"), func() {
1151 hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
1152 val, err := client.FTCreate(ctx, "idx1",
1153 &redis.FTCreateOptions{},
1154 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
1155 Expect(err).NotTo(HaveOccurred())
1156 Expect(val).To(BeEquivalentTo("OK"))
1157 WaitForIndexing(client, "idx1")
1158
1159 client.HSet(ctx, "a", "v", "aaaaaaaa")
1160 client.HSet(ctx, "b", "v", "aaaabaaa")
1161 client.HSet(ctx, "c", "v", "aaaaabaa")
1162
1163 searchOptions := &redis.FTSearchOptions{
1164 Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
1165 SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
1166 DialectVersion: 2,
1167 Params: map[string]interface{}{"vec": "aaaaaaaa"},
1168 }
1169 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec]", searchOptions).Result()
1170 Expect(err).NotTo(HaveOccurred())
1171 Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
1172 Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
1173 })
1174
1175 It("should FTCreate VECTOR with dialect 1 ", Label("search", "ftcreate"), func() {
1176 hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
1177 val, err := client.FTCreate(ctx, "idx1",
1178 &redis.FTCreateOptions{},
1179 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
1180 Expect(err).NotTo(HaveOccurred())
1181 Expect(val).To(BeEquivalentTo("OK"))
1182 WaitForIndexing(client, "idx1")
1183
1184 client.HSet(ctx, "a", "v", "aaaaaaaa")
1185 client.HSet(ctx, "b", "v", "aaaabaaa")
1186 client.HSet(ctx, "c", "v", "aaaaabaa")
1187
1188 searchOptions := &redis.FTSearchOptions{
1189 Return: []redis.FTSearchReturn{{FieldName: "v"}},
1190 SortBy: []redis.FTSearchSortBy{{FieldName: "v", Asc: true}},
1191 Limit: 10,
1192 DialectVersion: 1,
1193 }
1194 res, err := client.FTSearchWithArgs(ctx, "idx1", "*", searchOptions).Result()
1195 Expect(err).NotTo(HaveOccurred())
1196 Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
1197 Expect(res.Docs[0].Fields["v"]).To(BeEquivalentTo("aaaaaaaa"))
1198 })
1199
1200 It("should FTCreate VECTOR with default dialect", Label("search", "ftcreate"), func() {
1201 hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
1202 val, err := client.FTCreate(ctx, "idx1",
1203 &redis.FTCreateOptions{},
1204 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
1205 Expect(err).NotTo(HaveOccurred())
1206 Expect(val).To(BeEquivalentTo("OK"))
1207 WaitForIndexing(client, "idx1")
1208
1209 client.HSet(ctx, "a", "v", "aaaaaaaa")
1210 client.HSet(ctx, "b", "v", "aaaabaaa")
1211 client.HSet(ctx, "c", "v", "aaaaabaa")
1212
1213 searchOptions := &redis.FTSearchOptions{
1214 Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
1215 SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
1216 Params: map[string]interface{}{"vec": "aaaaaaaa"},
1217 }
1218 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec]", searchOptions).Result()
1219 Expect(err).NotTo(HaveOccurred())
1220 Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
1221 Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
1222 })
1223
1224 It("should FTCreate and FTSearch text params", Label("search", "ftcreate", "ftsearch"), func() {
1225 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}).Result()
1226 Expect(err).NotTo(HaveOccurred())
1227 Expect(val).To(BeEquivalentTo("OK"))
1228 WaitForIndexing(client, "idx1")
1229
1230 client.HSet(ctx, "doc1", "name", "Alice")
1231 client.HSet(ctx, "doc2", "name", "Bob")
1232 client.HSet(ctx, "doc3", "name", "Carol")
1233
1234 res1, err := client.FTSearchWithArgs(ctx, "idx1", "@name:($name1 | $name2 )", &redis.FTSearchOptions{Params: map[string]interface{}{"name1": "Alice", "name2": "Bob"}, DialectVersion: 2}).Result()
1235 Expect(err).NotTo(HaveOccurred())
1236 Expect(res1.Total).To(BeEquivalentTo(int64(2)))
1237 Expect(res1.Docs[0].ID).To(BeEquivalentTo("doc1"))
1238 Expect(res1.Docs[1].ID).To(BeEquivalentTo("doc2"))
1239
1240 })
1241
1242 It("should FTCreate and FTSearch numeric params", Label("search", "ftcreate", "ftsearch"), func() {
1243 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "numval", FieldType: redis.SearchFieldTypeNumeric}).Result()
1244 Expect(err).NotTo(HaveOccurred())
1245 Expect(val).To(BeEquivalentTo("OK"))
1246 WaitForIndexing(client, "idx1")
1247
1248 client.HSet(ctx, "doc1", "numval", 101)
1249 client.HSet(ctx, "doc2", "numval", 102)
1250 client.HSet(ctx, "doc3", "numval", 103)
1251
1252 res1, err := client.FTSearchWithArgs(ctx, "idx1", "@numval:[$min $max]", &redis.FTSearchOptions{Params: map[string]interface{}{"min": 101, "max": 102}, DialectVersion: 2}).Result()
1253 Expect(err).NotTo(HaveOccurred())
1254 Expect(res1.Total).To(BeEquivalentTo(int64(2)))
1255 Expect(res1.Docs[0].ID).To(BeEquivalentTo("doc1"))
1256 Expect(res1.Docs[1].ID).To(BeEquivalentTo("doc2"))
1257
1258 })
1259
1260 It("should FTCreate and FTSearch geo params", Label("search", "ftcreate", "ftsearch"), func() {
1261 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "g", FieldType: redis.SearchFieldTypeGeo}).Result()
1262 Expect(err).NotTo(HaveOccurred())
1263 Expect(val).To(BeEquivalentTo("OK"))
1264 WaitForIndexing(client, "idx1")
1265
1266 client.HSet(ctx, "doc1", "g", "29.69465, 34.95126")
1267 client.HSet(ctx, "doc2", "g", "29.69350, 34.94737")
1268 client.HSet(ctx, "doc3", "g", "29.68746, 34.94882")
1269
1270 res1, err := client.FTSearchWithArgs(ctx, "idx1", "@g:[$lon $lat $radius $units]", &redis.FTSearchOptions{Params: map[string]interface{}{"lat": "34.95126", "lon": "29.69465", "radius": 1000, "units": "km"}, DialectVersion: 2}).Result()
1271 Expect(err).NotTo(HaveOccurred())
1272 Expect(res1.Total).To(BeEquivalentTo(int64(3)))
1273 Expect(res1.Docs[0].ID).To(BeEquivalentTo("doc1"))
1274 Expect(res1.Docs[1].ID).To(BeEquivalentTo("doc2"))
1275 Expect(res1.Docs[2].ID).To(BeEquivalentTo("doc3"))
1276
1277 })
1278
1279 It("should FTConfigGet return multiple fields", Label("search", "NonRedisEnterprise"), func() {
1280 res, err := client.FTConfigSet(ctx, "DEFAULT_DIALECT", "1").Result()
1281 Expect(err).NotTo(HaveOccurred())
1282 Expect(res).To(BeEquivalentTo("OK"))
1283
1284 defDialect, err := client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
1285 Expect(err).NotTo(HaveOccurred())
1286 Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "1"}))
1287
1288 res, err = client.FTConfigSet(ctx, "DEFAULT_DIALECT", "2").Result()
1289 Expect(err).NotTo(HaveOccurred())
1290 Expect(res).To(BeEquivalentTo("OK"))
1291
1292 defDialect, err = client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
1293 Expect(err).NotTo(HaveOccurred())
1294 Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "2"}))
1295 })
1296
1297 It("should FTConfigSet and FTConfigGet dialect", Label("search", "ftconfigget", "ftconfigset", "NonRedisEnterprise"), func() {
1298 res, err := client.FTConfigSet(ctx, "DEFAULT_DIALECT", "1").Result()
1299 Expect(err).NotTo(HaveOccurred())
1300 Expect(res).To(BeEquivalentTo("OK"))
1301
1302 defDialect, err := client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
1303 Expect(err).NotTo(HaveOccurred())
1304 Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "1"}))
1305
1306 res, err = client.FTConfigSet(ctx, "DEFAULT_DIALECT", "2").Result()
1307 Expect(err).NotTo(HaveOccurred())
1308 Expect(res).To(BeEquivalentTo("OK"))
1309
1310 defDialect, err = client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
1311 Expect(err).NotTo(HaveOccurred())
1312 Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "2"}))
1313 })
1314
1315 It("should FTCreate WithSuffixtrie", Label("search", "ftcreate", "ftinfo"), func() {
1316 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
1317 Expect(err).NotTo(HaveOccurred())
1318 Expect(val).To(BeEquivalentTo("OK"))
1319 WaitForIndexing(client, "idx1")
1320
1321 res, err := client.FTInfo(ctx, "idx1").Result()
1322 Expect(err).NotTo(HaveOccurred())
1323 Expect(res.Attributes[0].Attribute).To(BeEquivalentTo("txt"))
1324
1325 resDrop, err := client.FTDropIndex(ctx, "idx1").Result()
1326 Expect(err).NotTo(HaveOccurred())
1327 Expect(resDrop).To(BeEquivalentTo("OK"))
1328
1329
1330 val, err = client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText, WithSuffixtrie: true}).Result()
1331 Expect(err).NotTo(HaveOccurred())
1332 Expect(val).To(BeEquivalentTo("OK"))
1333 WaitForIndexing(client, "idx1")
1334
1335 res, err = client.FTInfo(ctx, "idx1").Result()
1336 Expect(err).NotTo(HaveOccurred())
1337 Expect(res.Attributes[0].WithSuffixtrie).To(BeTrue())
1338
1339 resDrop, err = client.FTDropIndex(ctx, "idx1").Result()
1340 Expect(err).NotTo(HaveOccurred())
1341 Expect(resDrop).To(BeEquivalentTo("OK"))
1342
1343
1344 val, err = client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeTag, WithSuffixtrie: true}).Result()
1345 Expect(err).NotTo(HaveOccurred())
1346 Expect(val).To(BeEquivalentTo("OK"))
1347 WaitForIndexing(client, "idx1")
1348
1349 res, err = client.FTInfo(ctx, "idx1").Result()
1350 Expect(err).NotTo(HaveOccurred())
1351 Expect(res.Attributes[0].WithSuffixtrie).To(BeTrue())
1352 })
1353
1354 It("should test dialect 4", Label("search", "ftcreate", "ftsearch", "NonRedisEnterprise"), func() {
1355 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1356 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{
1357 Prefix: []interface{}{"resource:"},
1358 }, &redis.FieldSchema{
1359 FieldName: "uuid",
1360 FieldType: redis.SearchFieldTypeTag,
1361 }, &redis.FieldSchema{
1362 FieldName: "tags",
1363 FieldType: redis.SearchFieldTypeTag,
1364 }, &redis.FieldSchema{
1365 FieldName: "description",
1366 FieldType: redis.SearchFieldTypeText,
1367 }, &redis.FieldSchema{
1368 FieldName: "rating",
1369 FieldType: redis.SearchFieldTypeNumeric,
1370 }).Result()
1371 Expect(err).NotTo(HaveOccurred())
1372 Expect(val).To(BeEquivalentTo("OK"))
1373
1374 client.HSet(ctx, "resource:1", map[string]interface{}{
1375 "uuid": "123e4567-e89b-12d3-a456-426614174000",
1376 "tags": "finance|crypto|$btc|blockchain",
1377 "description": "Analysis of blockchain technologies & Bitcoin's potential.",
1378 "rating": 5,
1379 })
1380 client.HSet(ctx, "resource:2", map[string]interface{}{
1381 "uuid": "987e6543-e21c-12d3-a456-426614174999",
1382 "tags": "health|well-being|fitness|new-year's-resolutions",
1383 "description": "Health trends for the new year, including fitness regimes.",
1384 "rating": 4,
1385 })
1386
1387 res, err := client.FTSearchWithArgs(ctx, "idx1", "@uuid:{$uuid}",
1388 &redis.FTSearchOptions{
1389 DialectVersion: 2,
1390 Params: map[string]interface{}{"uuid": "123e4567-e89b-12d3-a456-426614174000"},
1391 }).Result()
1392 Expect(err).NotTo(HaveOccurred())
1393 Expect(res.Total).To(BeEquivalentTo(int64(1)))
1394 Expect(res.Docs[0].ID).To(BeEquivalentTo("resource:1"))
1395
1396 res, err = client.FTSearchWithArgs(ctx, "idx1", "@uuid:{$uuid}",
1397 &redis.FTSearchOptions{
1398 DialectVersion: 4,
1399 Params: map[string]interface{}{"uuid": "123e4567-e89b-12d3-a456-426614174000"},
1400 }).Result()
1401 Expect(err).NotTo(HaveOccurred())
1402 Expect(res.Total).To(BeEquivalentTo(int64(1)))
1403 Expect(res.Docs[0].ID).To(BeEquivalentTo("resource:1"))
1404
1405 client.HSet(ctx, "test:1", map[string]interface{}{
1406 "uuid": "3d3586fe-0416-4572-8ce",
1407 "email": "adriano@acme.com.ie",
1408 "num": 5,
1409 })
1410
1411
1412 ftCreateOptions := &redis.FTCreateOptions{
1413 Prefix: []interface{}{"test:"},
1414 }
1415 schema := []*redis.FieldSchema{
1416 {
1417 FieldName: "uuid",
1418 FieldType: redis.SearchFieldTypeTag,
1419 },
1420 {
1421 FieldName: "email",
1422 FieldType: redis.SearchFieldTypeTag,
1423 },
1424 {
1425 FieldName: "num",
1426 FieldType: redis.SearchFieldTypeNumeric,
1427 },
1428 }
1429
1430 val, err = client.FTCreate(ctx, "idx_hash", ftCreateOptions, schema...).Result()
1431 Expect(err).NotTo(HaveOccurred())
1432 Expect(val).To(Equal("OK"))
1433 WaitForIndexing(client, "idx_hash")
1434
1435 ftSearchOptions := &redis.FTSearchOptions{
1436 DialectVersion: 4,
1437 Params: map[string]interface{}{
1438 "uuid": "3d3586fe-0416-4572-8ce",
1439 "email": "adriano@acme.com.ie",
1440 },
1441 }
1442
1443 res, err = client.FTSearchWithArgs(ctx, "idx_hash", "@uuid:{$uuid}", ftSearchOptions).Result()
1444 Expect(err).NotTo(HaveOccurred())
1445 Expect(res.Docs[0].ID).To(BeEquivalentTo("test:1"))
1446 Expect(res.Docs[0].Fields["uuid"]).To(BeEquivalentTo("3d3586fe-0416-4572-8ce"))
1447
1448 res, err = client.FTSearchWithArgs(ctx, "idx_hash", "@email:{$email}", ftSearchOptions).Result()
1449 Expect(err).NotTo(HaveOccurred())
1450 Expect(res.Docs[0].ID).To(BeEquivalentTo("test:1"))
1451 Expect(res.Docs[0].Fields["email"]).To(BeEquivalentTo("adriano@acme.com.ie"))
1452
1453 ftSearchOptions.Params = map[string]interface{}{"num": 5}
1454 res, err = client.FTSearchWithArgs(ctx, "idx_hash", "@num:[5]", ftSearchOptions).Result()
1455 Expect(err).NotTo(HaveOccurred())
1456 Expect(res.Docs[0].ID).To(BeEquivalentTo("test:1"))
1457 Expect(res.Docs[0].Fields["num"]).To(BeEquivalentTo("5"))
1458 })
1459
1460 It("should FTCreate GeoShape", Label("search", "ftcreate", "ftsearch"), func() {
1461 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "geom", FieldType: redis.SearchFieldTypeGeoShape, GeoShapeFieldType: "FLAT"}).Result()
1462 Expect(err).NotTo(HaveOccurred())
1463 Expect(val).To(BeEquivalentTo("OK"))
1464 WaitForIndexing(client, "idx1")
1465
1466 client.HSet(ctx, "small", "geom", "POLYGON((1 1, 1 100, 100 100, 100 1, 1 1))")
1467 client.HSet(ctx, "large", "geom", "POLYGON((1 1, 1 200, 200 200, 200 1, 1 1))")
1468
1469 res1, err := client.FTSearchWithArgs(ctx, "idx1", "@geom:[WITHIN $poly]",
1470 &redis.FTSearchOptions{
1471 DialectVersion: 3,
1472 Params: map[string]interface{}{"poly": "POLYGON((0 0, 0 150, 150 150, 150 0, 0 0))"},
1473 }).Result()
1474 Expect(err).NotTo(HaveOccurred())
1475 Expect(res1.Total).To(BeEquivalentTo(int64(1)))
1476 Expect(res1.Docs[0].ID).To(BeEquivalentTo("small"))
1477
1478 res2, err := client.FTSearchWithArgs(ctx, "idx1", "@geom:[CONTAINS $poly]",
1479 &redis.FTSearchOptions{
1480 DialectVersion: 3,
1481 Params: map[string]interface{}{"poly": "POLYGON((2 2, 2 50, 50 50, 50 2, 2 2))"},
1482 }).Result()
1483 Expect(err).NotTo(HaveOccurred())
1484 Expect(res2.Total).To(BeEquivalentTo(int64(2)))
1485 })
1486
1487 It("should create search index with FLOAT16 and BFLOAT16 vectors", Label("search", "ftcreate", "NonRedisEnterprise"), func() {
1488 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1489 val, err := client.FTCreate(ctx, "index", &redis.FTCreateOptions{},
1490 &redis.FieldSchema{FieldName: "float16", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{FlatOptions: &redis.FTFlatOptions{Type: "FLOAT16", Dim: 768, DistanceMetric: "COSINE"}}},
1491 &redis.FieldSchema{FieldName: "bfloat16", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{FlatOptions: &redis.FTFlatOptions{Type: "BFLOAT16", Dim: 768, DistanceMetric: "COSINE"}}},
1492 ).Result()
1493 Expect(err).NotTo(HaveOccurred())
1494 Expect(val).To(BeEquivalentTo("OK"))
1495 WaitForIndexing(client, "index")
1496 })
1497
1498 It("should test geoshapes query intersects and disjoint", Label("NonRedisEnterprise"), func() {
1499 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1500 _, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{
1501 FieldName: "g",
1502 FieldType: redis.SearchFieldTypeGeoShape,
1503 GeoShapeFieldType: "FLAT",
1504 }).Result()
1505 Expect(err).NotTo(HaveOccurred())
1506
1507 client.HSet(ctx, "doc_point1", "g", "POINT (10 10)")
1508 client.HSet(ctx, "doc_point2", "g", "POINT (50 50)")
1509 client.HSet(ctx, "doc_polygon1", "g", "POLYGON ((20 20, 25 35, 35 25, 20 20))")
1510 client.HSet(ctx, "doc_polygon2", "g", "POLYGON ((60 60, 65 75, 70 70, 65 55, 60 60))")
1511
1512 intersection, err := client.FTSearchWithArgs(ctx, "idx1", "@g:[intersects $shape]",
1513 &redis.FTSearchOptions{
1514 DialectVersion: 3,
1515 Params: map[string]interface{}{"shape": "POLYGON((15 15, 75 15, 50 70, 20 40, 15 15))"},
1516 }).Result()
1517 Expect(err).NotTo(HaveOccurred())
1518 _assert_geosearch_result(&intersection, []string{"doc_point2", "doc_polygon1"})
1519
1520 disjunction, err := client.FTSearchWithArgs(ctx, "idx1", "@g:[disjoint $shape]",
1521 &redis.FTSearchOptions{
1522 DialectVersion: 3,
1523 Params: map[string]interface{}{"shape": "POLYGON((15 15, 75 15, 50 70, 20 40, 15 15))"},
1524 }).Result()
1525 Expect(err).NotTo(HaveOccurred())
1526 _assert_geosearch_result(&disjunction, []string{"doc_point1", "doc_polygon2"})
1527 })
1528
1529 It("should test geoshapes query contains and within", func() {
1530 _, err := client.FTCreate(ctx, "idx2", &redis.FTCreateOptions{}, &redis.FieldSchema{
1531 FieldName: "g",
1532 FieldType: redis.SearchFieldTypeGeoShape,
1533 GeoShapeFieldType: "FLAT",
1534 }).Result()
1535 Expect(err).NotTo(HaveOccurred())
1536
1537 client.HSet(ctx, "doc_point1", "g", "POINT (10 10)")
1538 client.HSet(ctx, "doc_point2", "g", "POINT (50 50)")
1539 client.HSet(ctx, "doc_polygon1", "g", "POLYGON ((20 20, 25 35, 35 25, 20 20))")
1540 client.HSet(ctx, "doc_polygon2", "g", "POLYGON ((60 60, 65 75, 70 70, 65 55, 60 60))")
1541
1542 containsA, err := client.FTSearchWithArgs(ctx, "idx2", "@g:[contains $shape]",
1543 &redis.FTSearchOptions{
1544 DialectVersion: 3,
1545 Params: map[string]interface{}{"shape": "POINT(25 25)"},
1546 }).Result()
1547 Expect(err).NotTo(HaveOccurred())
1548 _assert_geosearch_result(&containsA, []string{"doc_polygon1"})
1549
1550 containsB, err := client.FTSearchWithArgs(ctx, "idx2", "@g:[contains $shape]",
1551 &redis.FTSearchOptions{
1552 DialectVersion: 3,
1553 Params: map[string]interface{}{"shape": "POLYGON((24 24, 24 26, 25 25, 24 24))"},
1554 }).Result()
1555 Expect(err).NotTo(HaveOccurred())
1556 _assert_geosearch_result(&containsB, []string{"doc_polygon1"})
1557
1558 within, err := client.FTSearchWithArgs(ctx, "idx2", "@g:[within $shape]",
1559 &redis.FTSearchOptions{
1560 DialectVersion: 3,
1561 Params: map[string]interface{}{"shape": "POLYGON((15 15, 75 15, 50 70, 20 40, 15 15))"},
1562 }).Result()
1563 Expect(err).NotTo(HaveOccurred())
1564 _assert_geosearch_result(&within, []string{"doc_point2", "doc_polygon1"})
1565 })
1566
1567 It("should search missing fields", Label("search", "ftcreate", "ftsearch", "NonRedisEnterprise"), func() {
1568 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1569 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{Prefix: []interface{}{"property:"}},
1570 &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Sortable: true},
1571 &redis.FieldSchema{FieldName: "features", FieldType: redis.SearchFieldTypeTag, IndexMissing: true},
1572 &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText, IndexMissing: true}).Result()
1573 Expect(err).NotTo(HaveOccurred())
1574 Expect(val).To(BeEquivalentTo("OK"))
1575 WaitForIndexing(client, "idx1")
1576
1577 client.HSet(ctx, "property:1", map[string]interface{}{
1578 "title": "Luxury Villa in Malibu",
1579 "features": "pool,sea view,modern",
1580 "description": "A stunning modern villa overlooking the Pacific Ocean.",
1581 })
1582
1583 client.HSet(ctx, "property:2", map[string]interface{}{
1584 "title": "Downtown Flat",
1585 "description": "Modern flat in central Paris with easy access to metro.",
1586 })
1587
1588 client.HSet(ctx, "property:3", map[string]interface{}{
1589 "title": "Beachfront Bungalow",
1590 "features": "beachfront,sun deck",
1591 })
1592
1593 res, err := client.FTSearchWithArgs(ctx, "idx1", "ismissing(@features)", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1594 Expect(err).NotTo(HaveOccurred())
1595 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:2"))
1596
1597 res, err = client.FTSearchWithArgs(ctx, "idx1", "-ismissing(@features)", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1598 Expect(err).NotTo(HaveOccurred())
1599 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:1"))
1600 Expect(res.Docs[1].ID).To(BeEquivalentTo("property:3"))
1601
1602 res, err = client.FTSearchWithArgs(ctx, "idx1", "ismissing(@description)", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1603 Expect(err).NotTo(HaveOccurred())
1604 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:3"))
1605
1606 res, err = client.FTSearchWithArgs(ctx, "idx1", "-ismissing(@description)", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1607 Expect(err).NotTo(HaveOccurred())
1608 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:1"))
1609 Expect(res.Docs[1].ID).To(BeEquivalentTo("property:2"))
1610 })
1611
1612 It("should search empty fields", Label("search", "ftcreate", "ftsearch", "NonRedisEnterprise"), func() {
1613 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1614 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{Prefix: []interface{}{"property:"}},
1615 &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Sortable: true},
1616 &redis.FieldSchema{FieldName: "features", FieldType: redis.SearchFieldTypeTag, IndexEmpty: true},
1617 &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText, IndexEmpty: true}).Result()
1618 Expect(err).NotTo(HaveOccurred())
1619 Expect(val).To(BeEquivalentTo("OK"))
1620 WaitForIndexing(client, "idx1")
1621
1622 client.HSet(ctx, "property:1", map[string]interface{}{
1623 "title": "Luxury Villa in Malibu",
1624 "features": "pool,sea view,modern",
1625 "description": "A stunning modern villa overlooking the Pacific Ocean.",
1626 })
1627
1628 client.HSet(ctx, "property:2", map[string]interface{}{
1629 "title": "Downtown Flat",
1630 "features": "",
1631 "description": "Modern flat in central Paris with easy access to metro.",
1632 })
1633
1634 client.HSet(ctx, "property:3", map[string]interface{}{
1635 "title": "Beachfront Bungalow",
1636 "features": "beachfront,sun deck",
1637 "description": "",
1638 })
1639
1640 res, err := client.FTSearchWithArgs(ctx, "idx1", "@features:{\"\"}", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1641 Expect(err).NotTo(HaveOccurred())
1642 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:2"))
1643
1644 res, err = client.FTSearchWithArgs(ctx, "idx1", "-@features:{\"\"}", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1645 Expect(err).NotTo(HaveOccurred())
1646 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:1"))
1647 Expect(res.Docs[1].ID).To(BeEquivalentTo("property:3"))
1648
1649 res, err = client.FTSearchWithArgs(ctx, "idx1", "@description:''", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1650 Expect(err).NotTo(HaveOccurred())
1651 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:3"))
1652
1653 res, err = client.FTSearchWithArgs(ctx, "idx1", "-@description:''", &redis.FTSearchOptions{DialectVersion: 4, Return: []redis.FTSearchReturn{{FieldName: "id"}}, NoContent: true}).Result()
1654 Expect(err).NotTo(HaveOccurred())
1655 Expect(res.Docs[0].ID).To(BeEquivalentTo("property:1"))
1656 Expect(res.Docs[1].ID).To(BeEquivalentTo("property:2"))
1657 })
1658
1659 It("should FTCreate VECTOR with int8 and uint8 types", Label("search", "ftcreate"), func() {
1660 SkipBeforeRedisVersion(7.9, "doesn't work with older redis")
1661
1662 hnswOptionsInt8 := &redis.FTHNSWOptions{
1663 Type: "INT8",
1664 Dim: 2,
1665 DistanceMetric: "L2",
1666 }
1667
1668
1669 hnswOptionsUint8 := &redis.FTHNSWOptions{
1670 Type: "UINT8",
1671 Dim: 2,
1672 DistanceMetric: "L2",
1673 }
1674
1675
1676 val, err := client.FTCreate(ctx, "idx1",
1677 &redis.FTCreateOptions{},
1678 &redis.FieldSchema{FieldName: "int8_vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptionsInt8}},
1679 &redis.FieldSchema{FieldName: "uint8_vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptionsUint8}},
1680 ).Result()
1681
1682 Expect(err).NotTo(HaveOccurred())
1683 Expect(val).To(BeEquivalentTo("OK"))
1684 WaitForIndexing(client, "idx1")
1685
1686
1687 client.HSet(ctx, "doc1", "int8_vector", "\x01\x02", "uint8_vector", "\x01\x02")
1688 client.HSet(ctx, "doc2", "int8_vector", "\x03\x04", "uint8_vector", "\x03\x04")
1689
1690
1691 searchOptionsInt8 := &redis.FTSearchOptions{
1692 Return: []redis.FTSearchReturn{{FieldName: "int8_vector"}},
1693 SortBy: []redis.FTSearchSortBy{{FieldName: "int8_vector", Asc: true}},
1694 DialectVersion: 2,
1695 Params: map[string]interface{}{"vec": "\x01\x02"},
1696 }
1697
1698 resInt8, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 1 @int8_vector $vec]", searchOptionsInt8).Result()
1699 Expect(err).NotTo(HaveOccurred())
1700 Expect(resInt8.Docs[0].ID).To(BeEquivalentTo("doc1"))
1701
1702
1703 searchOptionsUint8 := &redis.FTSearchOptions{
1704 Return: []redis.FTSearchReturn{{FieldName: "uint8_vector"}},
1705 SortBy: []redis.FTSearchSortBy{{FieldName: "uint8_vector", Asc: true}},
1706 DialectVersion: 2,
1707 Params: map[string]interface{}{"vec": "\x01\x02"},
1708 }
1709
1710 resUint8, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 1 @uint8_vector $vec]", searchOptionsUint8).Result()
1711 Expect(err).NotTo(HaveOccurred())
1712 Expect(resUint8.Docs[0].ID).To(BeEquivalentTo("doc1"))
1713 })
1714
1715 It("should return special float scores in FT.SEARCH vecsim", Label("search", "ftsearch", "vecsim"), func() {
1716 SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
1717
1718 vecField := &redis.FTFlatOptions{
1719 Type: "FLOAT32",
1720 Dim: 2,
1721 DistanceMetric: "IP",
1722 }
1723 _, err := client.FTCreate(ctx, "idx_vec",
1724 &redis.FTCreateOptions{OnHash: true, Prefix: []interface{}{"doc:"}},
1725 &redis.FieldSchema{FieldName: "vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{FlatOptions: vecField}}).Result()
1726 Expect(err).NotTo(HaveOccurred())
1727 WaitForIndexing(client, "idx_vec")
1728
1729 bigPos := []float32{1e38, 1e38}
1730 bigNeg := []float32{-1e38, -1e38}
1731 nanVec := []float32{float32(math.NaN()), 0}
1732 negNanVec := []float32{float32(math.Copysign(math.NaN(), -1)), 0}
1733
1734 client.HSet(ctx, "doc:1", "vector", encodeFloat32Vector(bigPos))
1735 client.HSet(ctx, "doc:2", "vector", encodeFloat32Vector(bigNeg))
1736 client.HSet(ctx, "doc:3", "vector", encodeFloat32Vector(nanVec))
1737 client.HSet(ctx, "doc:4", "vector", encodeFloat32Vector(negNanVec))
1738
1739 searchOptions := &redis.FTSearchOptions{WithScores: true, Params: map[string]interface{}{"vec": encodeFloat32Vector(bigPos)}}
1740 res, err := client.FTSearchWithArgs(ctx, "idx_vec", "*=>[KNN 4 @vector $vec]", searchOptions).Result()
1741 Expect(err).NotTo(HaveOccurred())
1742 Expect(res.Total).To(BeEquivalentTo(4))
1743
1744 var scores []float64
1745 for _, row := range res.Docs {
1746 raw := fmt.Sprintf("%v", row.Fields["__vector_score"])
1747 f, err := helper.ParseFloat(raw)
1748 Expect(err).NotTo(HaveOccurred())
1749 scores = append(scores, f)
1750 }
1751
1752 Expect(scores).To(ContainElement(BeNumerically("==", math.Inf(1))))
1753 Expect(scores).To(ContainElement(BeNumerically("==", math.Inf(-1))))
1754
1755
1756 nanCount := 0
1757 for _, score := range scores {
1758 if math.IsNaN(score) {
1759 nanCount++
1760 }
1761 }
1762 Expect(nanCount).To(Equal(2))
1763 })
1764
1765 It("should FTCreate VECTOR with VAMANA algorithm - basic", Label("search", "ftcreate"), func() {
1766 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1767 vamanaOptions := &redis.FTVamanaOptions{
1768 Type: "FLOAT32",
1769 Dim: 2,
1770 DistanceMetric: "L2",
1771 }
1772 val, err := client.FTCreate(ctx, "idx1",
1773 &redis.FTCreateOptions{},
1774 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1775 Expect(err).NotTo(HaveOccurred())
1776 Expect(val).To(BeEquivalentTo("OK"))
1777 WaitForIndexing(client, "idx1")
1778
1779 client.HSet(ctx, "a", "v", "aaaaaaaa")
1780 client.HSet(ctx, "b", "v", "aaaabaaa")
1781 client.HSet(ctx, "c", "v", "aaaaabaa")
1782
1783 searchOptions := &redis.FTSearchOptions{
1784 Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
1785 SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
1786 DialectVersion: 2,
1787 Params: map[string]interface{}{"vec": "aaaaaaaa"},
1788 }
1789 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec]", searchOptions).Result()
1790 Expect(err).NotTo(HaveOccurred())
1791 Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
1792 Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
1793 })
1794
1795 It("should FTCreate VECTOR with VAMANA algorithm - with compression", Label("search", "ftcreate"), func() {
1796 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1797 vamanaOptions := &redis.FTVamanaOptions{
1798 Type: "FLOAT16",
1799 Dim: 256,
1800 DistanceMetric: "COSINE",
1801 Compression: "LVQ8",
1802 TrainingThreshold: 10240,
1803 }
1804 val, err := client.FTCreate(ctx, "idx1",
1805 &redis.FTCreateOptions{},
1806 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1807 Expect(err).NotTo(HaveOccurred())
1808 Expect(val).To(BeEquivalentTo("OK"))
1809 WaitForIndexing(client, "idx1")
1810 })
1811
1812 It("should FTCreate VECTOR with VAMANA algorithm - advanced parameters", Label("search", "ftcreate"), func() {
1813 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1814 vamanaOptions := &redis.FTVamanaOptions{
1815 Type: "FLOAT32",
1816 Dim: 512,
1817 DistanceMetric: "IP",
1818 Compression: "LVQ8",
1819 ConstructionWindowSize: 300,
1820 GraphMaxDegree: 128,
1821 SearchWindowSize: 20,
1822 Epsilon: 0.02,
1823 TrainingThreshold: 20480,
1824 }
1825 val, err := client.FTCreate(ctx, "idx1",
1826 &redis.FTCreateOptions{},
1827 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1828 Expect(err).NotTo(HaveOccurred())
1829 Expect(val).To(BeEquivalentTo("OK"))
1830 WaitForIndexing(client, "idx1")
1831 })
1832
1833 It("should fail FTCreate VECTOR with VAMANA - missing required parameters", Label("search", "ftcreate"), func() {
1834
1835 cmd := client.FTCreate(ctx, "idx1",
1836 &redis.FTCreateOptions{},
1837 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: &redis.FTVamanaOptions{
1838 Dim: 2,
1839 DistanceMetric: "L2",
1840 }}})
1841 Expect(cmd.Err()).To(HaveOccurred())
1842 Expect(cmd.Err().Error()).To(ContainSubstring("Type, Dim and DistanceMetric are required for VECTOR VAMANA"))
1843
1844
1845 cmd = client.FTCreate(ctx, "idx1",
1846 &redis.FTCreateOptions{},
1847 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: &redis.FTVamanaOptions{
1848 Type: "FLOAT32",
1849 DistanceMetric: "L2",
1850 }}})
1851 Expect(cmd.Err()).To(HaveOccurred())
1852 Expect(cmd.Err().Error()).To(ContainSubstring("Type, Dim and DistanceMetric are required for VECTOR VAMANA"))
1853
1854
1855 cmd = client.FTCreate(ctx, "idx1",
1856 &redis.FTCreateOptions{},
1857 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: &redis.FTVamanaOptions{
1858 Type: "FLOAT32",
1859 Dim: 2,
1860 }}})
1861 Expect(cmd.Err()).To(HaveOccurred())
1862 Expect(cmd.Err().Error()).To(ContainSubstring("Type, Dim and DistanceMetric are required for VECTOR VAMANA"))
1863 })
1864
1865 It("should fail FTCreate VECTOR with multiple vector options", Label("search", "ftcreate"), func() {
1866
1867 cmd := client.FTCreate(ctx, "idx1",
1868 &redis.FTCreateOptions{},
1869 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{
1870 VamanaOptions: &redis.FTVamanaOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"},
1871 HNSWOptions: &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"},
1872 }})
1873 Expect(cmd.Err()).To(HaveOccurred())
1874 Expect(cmd.Err().Error()).To(ContainSubstring("VectorArgs must have exactly one of FlatOptions, HNSWOptions, or VamanaOptions"))
1875
1876
1877 cmd = client.FTCreate(ctx, "idx1",
1878 &redis.FTCreateOptions{},
1879 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{
1880 VamanaOptions: &redis.FTVamanaOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"},
1881 FlatOptions: &redis.FTFlatOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"},
1882 }})
1883 Expect(cmd.Err()).To(HaveOccurred())
1884 Expect(cmd.Err().Error()).To(ContainSubstring("VectorArgs must have exactly one of FlatOptions, HNSWOptions, or VamanaOptions"))
1885 })
1886
1887 It("should test VAMANA L2 distance metric", Label("search", "ftcreate", "vamana"), func() {
1888 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1889 vamanaOptions := &redis.FTVamanaOptions{
1890 Type: "FLOAT32",
1891 Dim: 3,
1892 DistanceMetric: "L2",
1893 }
1894 val, err := client.FTCreate(ctx, "idx1",
1895 &redis.FTCreateOptions{},
1896 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1897 Expect(err).NotTo(HaveOccurred())
1898 Expect(val).To(BeEquivalentTo("OK"))
1899 WaitForIndexing(client, "idx1")
1900
1901
1902 vectors := [][]float32{
1903 {1.0, 0.0, 0.0},
1904 {2.0, 0.0, 0.0},
1905 {0.0, 1.0, 0.0},
1906 {5.0, 0.0, 0.0},
1907 }
1908
1909 for i, vec := range vectors {
1910 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
1911 }
1912
1913 searchOptions := &redis.FTSearchOptions{
1914 Return: []redis.FTSearchReturn{{FieldName: "score"}},
1915 SortBy: []redis.FTSearchSortBy{{FieldName: "score", Asc: true}},
1916 DialectVersion: 2,
1917 NoContent: true,
1918 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
1919 }
1920 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
1921 Expect(err).NotTo(HaveOccurred())
1922 Expect(res.Total).To(BeEquivalentTo(3))
1923 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
1924 })
1925
1926 It("should test VAMANA COSINE distance metric", Label("search", "ftcreate", "vamana"), func() {
1927 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1928 vamanaOptions := &redis.FTVamanaOptions{
1929 Type: "FLOAT32",
1930 Dim: 3,
1931 DistanceMetric: "COSINE",
1932 }
1933 val, err := client.FTCreate(ctx, "idx1",
1934 &redis.FTCreateOptions{},
1935 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1936 Expect(err).NotTo(HaveOccurred())
1937 Expect(val).To(BeEquivalentTo("OK"))
1938 WaitForIndexing(client, "idx1")
1939
1940 vectors := [][]float32{
1941 {1.0, 0.0, 0.0},
1942 {0.707, 0.707, 0.0},
1943 {0.0, 1.0, 0.0},
1944 {-1.0, 0.0, 0.0},
1945 }
1946
1947 for i, vec := range vectors {
1948 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
1949 }
1950
1951 searchOptions := &redis.FTSearchOptions{
1952 Return: []redis.FTSearchReturn{{FieldName: "score"}},
1953 SortBy: []redis.FTSearchSortBy{{FieldName: "score", Asc: true}},
1954 DialectVersion: 2,
1955 NoContent: true,
1956 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
1957 }
1958 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
1959 Expect(err).NotTo(HaveOccurred())
1960 Expect(res.Total).To(BeEquivalentTo(3))
1961 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
1962 })
1963
1964 It("should test VAMANA IP distance metric", Label("search", "ftcreate", "vamana"), func() {
1965 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
1966 vamanaOptions := &redis.FTVamanaOptions{
1967 Type: "FLOAT32",
1968 Dim: 3,
1969 DistanceMetric: "IP",
1970 }
1971 val, err := client.FTCreate(ctx, "idx1",
1972 &redis.FTCreateOptions{},
1973 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
1974 Expect(err).NotTo(HaveOccurred())
1975 Expect(val).To(BeEquivalentTo("OK"))
1976 WaitForIndexing(client, "idx1")
1977
1978 vectors := [][]float32{
1979 {1.0, 2.0, 3.0},
1980 {2.0, 1.0, 1.0},
1981 {3.0, 3.0, 3.0},
1982 {0.1, 0.1, 0.1},
1983 }
1984
1985 for i, vec := range vectors {
1986 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
1987 }
1988
1989 searchOptions := &redis.FTSearchOptions{
1990 Return: []redis.FTSearchReturn{{FieldName: "score"}},
1991 SortBy: []redis.FTSearchSortBy{{FieldName: "score", Asc: true}},
1992 DialectVersion: 2,
1993 NoContent: true,
1994 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
1995 }
1996 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
1997 Expect(err).NotTo(HaveOccurred())
1998 Expect(res.Total).To(BeEquivalentTo(3))
1999 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc2"))
2000 })
2001
2002 It("should test VAMANA basic functionality", Label("search", "ftcreate", "vamana"), func() {
2003 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2004 vamanaOptions := &redis.FTVamanaOptions{
2005 Type: "FLOAT32",
2006 Dim: 4,
2007 DistanceMetric: "L2",
2008 }
2009 val, err := client.FTCreate(ctx, "idx1",
2010 &redis.FTCreateOptions{},
2011 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2012 Expect(err).NotTo(HaveOccurred())
2013 Expect(val).To(BeEquivalentTo("OK"))
2014 WaitForIndexing(client, "idx1")
2015
2016 vectors := [][]float32{
2017 {1.0, 2.0, 3.0, 4.0},
2018 {2.0, 3.0, 4.0, 5.0},
2019 {3.0, 4.0, 5.0, 6.0},
2020 {10.0, 11.0, 12.0, 13.0},
2021 }
2022
2023 for i, vec := range vectors {
2024 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2025 }
2026
2027 searchOptions := &redis.FTSearchOptions{
2028 Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
2029 SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
2030 DialectVersion: 2,
2031 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2032 }
2033 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 3 @v $vec]", searchOptions).Result()
2034 Expect(err).NotTo(HaveOccurred())
2035 Expect(res.Total).To(BeEquivalentTo(3))
2036 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2037 Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
2038 })
2039
2040 It("should test VAMANA FLOAT16 type", Label("search", "ftcreate", "vamana"), func() {
2041 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2042 vamanaOptions := &redis.FTVamanaOptions{
2043 Type: "FLOAT16",
2044 Dim: 4,
2045 DistanceMetric: "L2",
2046 }
2047 val, err := client.FTCreate(ctx, "idx1",
2048 &redis.FTCreateOptions{},
2049 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2050 Expect(err).NotTo(HaveOccurred())
2051 Expect(val).To(BeEquivalentTo("OK"))
2052 WaitForIndexing(client, "idx1")
2053
2054 vectors := [][]float32{
2055 {1.5, 2.5, 3.5, 4.5},
2056 {2.5, 3.5, 4.5, 5.5},
2057 {3.5, 4.5, 5.5, 6.5},
2058 }
2059
2060 for i, vec := range vectors {
2061 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat16Vector(vec))
2062 }
2063
2064 searchOptions := &redis.FTSearchOptions{
2065 DialectVersion: 2,
2066 NoContent: true,
2067 Params: map[string]interface{}{"vec": encodeFloat16Vector(vectors[0])},
2068 }
2069 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec as score]", searchOptions).Result()
2070 Expect(err).NotTo(HaveOccurred())
2071 Expect(res.Total).To(BeEquivalentTo(2))
2072 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2073 })
2074
2075 It("should test VAMANA FLOAT32 type", Label("search", "ftcreate", "vamana"), func() {
2076 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2077 vamanaOptions := &redis.FTVamanaOptions{
2078 Type: "FLOAT32",
2079 Dim: 4,
2080 DistanceMetric: "L2",
2081 }
2082 val, err := client.FTCreate(ctx, "idx1",
2083 &redis.FTCreateOptions{},
2084 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2085 Expect(err).NotTo(HaveOccurred())
2086 Expect(val).To(BeEquivalentTo("OK"))
2087 WaitForIndexing(client, "idx1")
2088
2089 vectors := [][]float32{
2090 {1.0, 2.0, 3.0, 4.0},
2091 {2.0, 3.0, 4.0, 5.0},
2092 {3.0, 4.0, 5.0, 6.0},
2093 }
2094
2095 for i, vec := range vectors {
2096 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2097 }
2098
2099 searchOptions := &redis.FTSearchOptions{
2100 DialectVersion: 2,
2101 NoContent: true,
2102 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2103 }
2104 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec as score]", searchOptions).Result()
2105 Expect(err).NotTo(HaveOccurred())
2106 Expect(res.Total).To(BeEquivalentTo(2))
2107 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2108 })
2109
2110 It("should test VAMANA with default dialect", Label("search", "ftcreate", "vamana"), func() {
2111 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2112 vamanaOptions := &redis.FTVamanaOptions{
2113 Type: "FLOAT32",
2114 Dim: 2,
2115 DistanceMetric: "L2",
2116 }
2117 val, err := client.FTCreate(ctx, "idx1",
2118 &redis.FTCreateOptions{},
2119 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2120 Expect(err).NotTo(HaveOccurred())
2121 Expect(val).To(BeEquivalentTo("OK"))
2122 WaitForIndexing(client, "idx1")
2123
2124 client.HSet(ctx, "a", "v", "aaaaaaaa")
2125 client.HSet(ctx, "b", "v", "aaaabaaa")
2126 client.HSet(ctx, "c", "v", "aaaaabaa")
2127
2128 searchOptions := &redis.FTSearchOptions{
2129 Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
2130 SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
2131 Params: map[string]interface{}{"vec": "aaaaaaaa"},
2132 }
2133 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec]", searchOptions).Result()
2134 Expect(err).NotTo(HaveOccurred())
2135 Expect(res.Total).To(BeEquivalentTo(2))
2136 })
2137
2138 It("should test VAMANA with LVQ8 compression", Label("search", "ftcreate", "vamana"), func() {
2139 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2140 vamanaOptions := &redis.FTVamanaOptions{
2141 Type: "FLOAT32",
2142 Dim: 8,
2143 DistanceMetric: "L2",
2144 Compression: "LVQ8",
2145 TrainingThreshold: 1024,
2146 }
2147 val, err := client.FTCreate(ctx, "idx1",
2148 &redis.FTCreateOptions{},
2149 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2150 Expect(err).NotTo(HaveOccurred())
2151 Expect(val).To(BeEquivalentTo("OK"))
2152 WaitForIndexing(client, "idx1")
2153
2154 vectors := make([][]float32, 20)
2155 for i := 0; i < 20; i++ {
2156 vec := make([]float32, 8)
2157 for j := 0; j < 8; j++ {
2158 vec[j] = float32(i + j)
2159 }
2160 vectors[i] = vec
2161 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2162 }
2163
2164 searchOptions := &redis.FTSearchOptions{
2165 DialectVersion: 2,
2166 NoContent: true,
2167 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2168 }
2169 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
2170 Expect(err).NotTo(HaveOccurred())
2171 Expect(res.Total).To(BeEquivalentTo(5))
2172 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2173 })
2174
2175 It("should test VAMANA compression with both vector types", Label("search", "ftcreate", "vamana"), func() {
2176 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2177
2178
2179 vamanaOptions16 := &redis.FTVamanaOptions{
2180 Type: "FLOAT16",
2181 Dim: 8,
2182 DistanceMetric: "L2",
2183 Compression: "LVQ8",
2184 TrainingThreshold: 1024,
2185 }
2186 val, err := client.FTCreate(ctx, "idx16",
2187 &redis.FTCreateOptions{},
2188 &redis.FieldSchema{FieldName: "v16", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions16}}).Result()
2189 Expect(err).NotTo(HaveOccurred())
2190 Expect(val).To(BeEquivalentTo("OK"))
2191 WaitForIndexing(client, "idx16")
2192
2193
2194 vamanaOptions32 := &redis.FTVamanaOptions{
2195 Type: "FLOAT32",
2196 Dim: 8,
2197 DistanceMetric: "L2",
2198 Compression: "LVQ8",
2199 TrainingThreshold: 1024,
2200 }
2201 val, err = client.FTCreate(ctx, "idx32",
2202 &redis.FTCreateOptions{},
2203 &redis.FieldSchema{FieldName: "v32", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions32}}).Result()
2204 Expect(err).NotTo(HaveOccurred())
2205 Expect(val).To(BeEquivalentTo("OK"))
2206 WaitForIndexing(client, "idx32")
2207
2208
2209 for i := 0; i < 15; i++ {
2210 vec := make([]float32, 8)
2211 for j := 0; j < 8; j++ {
2212 vec[j] = float32(i + j)
2213 }
2214 client.HSet(ctx, fmt.Sprintf("doc16_%d", i), "v16", encodeFloat16Vector(vec))
2215 client.HSet(ctx, fmt.Sprintf("doc32_%d", i), "v32", encodeFloat32Vector(vec))
2216 }
2217
2218 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
2219
2220
2221 searchOptions16 := &redis.FTSearchOptions{
2222 DialectVersion: 2,
2223 NoContent: true,
2224 Params: map[string]interface{}{"vec": encodeFloat16Vector(queryVec)},
2225 }
2226 res16, err := client.FTSearchWithArgs(ctx, "idx16", "*=>[KNN 3 @v16 $vec as score]", searchOptions16).Result()
2227 Expect(err).NotTo(HaveOccurred())
2228 Expect(res16.Total).To(BeEquivalentTo(3))
2229
2230
2231 searchOptions32 := &redis.FTSearchOptions{
2232 DialectVersion: 2,
2233 NoContent: true,
2234 Params: map[string]interface{}{"vec": encodeFloat32Vector(queryVec)},
2235 }
2236 res32, err := client.FTSearchWithArgs(ctx, "idx32", "*=>[KNN 3 @v32 $vec as score]", searchOptions32).Result()
2237 Expect(err).NotTo(HaveOccurred())
2238 Expect(res32.Total).To(BeEquivalentTo(3))
2239 })
2240
2241 It("should test VAMANA construction window size", Label("search", "ftcreate", "vamana"), func() {
2242 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2243 vamanaOptions := &redis.FTVamanaOptions{
2244 Type: "FLOAT32",
2245 Dim: 6,
2246 DistanceMetric: "L2",
2247 ConstructionWindowSize: 300,
2248 }
2249 val, err := client.FTCreate(ctx, "idx1",
2250 &redis.FTCreateOptions{},
2251 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2252 Expect(err).NotTo(HaveOccurred())
2253 Expect(val).To(BeEquivalentTo("OK"))
2254 WaitForIndexing(client, "idx1")
2255
2256 vectors := make([][]float32, 20)
2257 for i := 0; i < 20; i++ {
2258 vec := make([]float32, 6)
2259 for j := 0; j < 6; j++ {
2260 vec[j] = float32(i + j)
2261 }
2262 vectors[i] = vec
2263 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2264 }
2265
2266 searchOptions := &redis.FTSearchOptions{
2267 DialectVersion: 2,
2268 NoContent: true,
2269 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2270 }
2271 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
2272 Expect(err).NotTo(HaveOccurred())
2273 Expect(res.Total).To(BeEquivalentTo(5))
2274 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2275 })
2276
2277 It("should test VAMANA graph max degree", Label("search", "ftcreate", "vamana"), func() {
2278 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2279 vamanaOptions := &redis.FTVamanaOptions{
2280 Type: "FLOAT32",
2281 Dim: 6,
2282 DistanceMetric: "COSINE",
2283 GraphMaxDegree: 64,
2284 }
2285 val, err := client.FTCreate(ctx, "idx1",
2286 &redis.FTCreateOptions{},
2287 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2288 Expect(err).NotTo(HaveOccurred())
2289 Expect(val).To(BeEquivalentTo("OK"))
2290 WaitForIndexing(client, "idx1")
2291
2292 vectors := make([][]float32, 25)
2293 for i := 0; i < 25; i++ {
2294 vec := make([]float32, 6)
2295 for j := 0; j < 6; j++ {
2296 vec[j] = float32(i + j)
2297 }
2298 vectors[i] = vec
2299 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2300 }
2301
2302 searchOptions := &redis.FTSearchOptions{
2303 DialectVersion: 2,
2304 NoContent: true,
2305 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2306 }
2307 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 6 @v $vec as score]", searchOptions).Result()
2308 Expect(err).NotTo(HaveOccurred())
2309 Expect(res.Total).To(BeEquivalentTo(6))
2310 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2311 })
2312
2313 It("should test VAMANA search window size", Label("search", "ftcreate", "vamana"), func() {
2314 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2315 vamanaOptions := &redis.FTVamanaOptions{
2316 Type: "FLOAT32",
2317 Dim: 6,
2318 DistanceMetric: "L2",
2319 SearchWindowSize: 20,
2320 }
2321 val, err := client.FTCreate(ctx, "idx1",
2322 &redis.FTCreateOptions{},
2323 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2324 Expect(err).NotTo(HaveOccurred())
2325 Expect(val).To(BeEquivalentTo("OK"))
2326 WaitForIndexing(client, "idx1")
2327
2328 vectors := make([][]float32, 30)
2329 for i := 0; i < 30; i++ {
2330 vec := make([]float32, 6)
2331 for j := 0; j < 6; j++ {
2332 vec[j] = float32(i + j)
2333 }
2334 vectors[i] = vec
2335 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2336 }
2337
2338 searchOptions := &redis.FTSearchOptions{
2339 DialectVersion: 2,
2340 NoContent: true,
2341 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2342 }
2343 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 8 @v $vec as score]", searchOptions).Result()
2344 Expect(err).NotTo(HaveOccurred())
2345 Expect(res.Total).To(BeEquivalentTo(8))
2346 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2347 })
2348
2349 It("should test VAMANA all advanced parameters", Label("search", "ftcreate", "vamana"), func() {
2350 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2351 vamanaOptions := &redis.FTVamanaOptions{
2352 Type: "FLOAT32",
2353 Dim: 8,
2354 DistanceMetric: "L2",
2355 Compression: "LVQ8",
2356 ConstructionWindowSize: 200,
2357 GraphMaxDegree: 32,
2358 SearchWindowSize: 15,
2359 Epsilon: 0.01,
2360 TrainingThreshold: 1024,
2361 }
2362 val, err := client.FTCreate(ctx, "idx1",
2363 &redis.FTCreateOptions{},
2364 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2365 Expect(err).NotTo(HaveOccurred())
2366 Expect(val).To(BeEquivalentTo("OK"))
2367 WaitForIndexing(client, "idx1")
2368
2369 vectors := make([][]float32, 15)
2370 for i := 0; i < 15; i++ {
2371 vec := make([]float32, 8)
2372 for j := 0; j < 8; j++ {
2373 vec[j] = float32(i + j)
2374 }
2375 vectors[i] = vec
2376 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2377 }
2378
2379 searchOptions := &redis.FTSearchOptions{
2380 DialectVersion: 2,
2381 NoContent: true,
2382 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2383 }
2384 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
2385 Expect(err).NotTo(HaveOccurred())
2386 Expect(res.Total).To(BeEquivalentTo(5))
2387 Expect(res.Docs[0].ID).To(BeEquivalentTo("doc0"))
2388 })
2389
2390 It("should fail when using a non-zero offset with a zero limit", Label("search", "ftsearch"), func() {
2391 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2392 val, err := client.FTCreate(ctx, "testIdx", &redis.FTCreateOptions{}, &redis.FieldSchema{
2393 FieldName: "txt",
2394 FieldType: redis.SearchFieldTypeText,
2395 }).Result()
2396 Expect(err).NotTo(HaveOccurred())
2397 Expect(val).To(BeEquivalentTo("OK"))
2398 WaitForIndexing(client, "testIdx")
2399
2400 client.HSet(ctx, "doc1", "txt", "hello world")
2401
2402
2403 _, err = client.FTSearchWithArgs(ctx, "testIdx", "hello", &redis.FTSearchOptions{
2404 LimitOffset: 5,
2405 Limit: 0,
2406 }).Result()
2407 Expect(err).To(HaveOccurred())
2408 })
2409
2410 It("should evaluate exponentiation precedence in APPLY expressions correctly", Label("search", "ftaggregate"), func() {
2411 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2412 val, err := client.FTCreate(ctx, "txns", &redis.FTCreateOptions{}, &redis.FieldSchema{
2413 FieldName: "dummy",
2414 FieldType: redis.SearchFieldTypeText,
2415 }).Result()
2416 Expect(err).NotTo(HaveOccurred())
2417 Expect(val).To(BeEquivalentTo("OK"))
2418 WaitForIndexing(client, "txns")
2419
2420 client.HSet(ctx, "doc1", "dummy", "dummy")
2421
2422 correctOptions := &redis.FTAggregateOptions{
2423 Apply: []redis.FTAggregateApply{
2424 {Field: "(2*3^2)", As: "Value"},
2425 },
2426 Limit: 1,
2427 LimitOffset: 0,
2428 }
2429 correctRes, err := client.FTAggregateWithArgs(ctx, "txns", "*", correctOptions).Result()
2430 Expect(err).NotTo(HaveOccurred())
2431 Expect(correctRes.Rows[0].Fields["Value"]).To(BeEquivalentTo("18"))
2432 })
2433
2434 It("should return a syntax error when empty strings are used for numeric parameters", Label("search", "ftsearch"), func() {
2435 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2436 val, err := client.FTCreate(ctx, "idx", &redis.FTCreateOptions{}, &redis.FieldSchema{
2437 FieldName: "n",
2438 FieldType: redis.SearchFieldTypeNumeric,
2439 }).Result()
2440 Expect(err).NotTo(HaveOccurred())
2441 Expect(val).To(BeEquivalentTo("OK"))
2442 WaitForIndexing(client, "idx")
2443
2444 client.HSet(ctx, "doc1", "n", 0)
2445
2446 _, err = client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{
2447 Filters: []redis.FTSearchFilter{{
2448 FieldName: "n",
2449 Min: "",
2450 Max: "",
2451 }},
2452 DialectVersion: 2,
2453 }).Result()
2454 Expect(err).To(HaveOccurred())
2455 })
2456
2457 It("should return NaN as default for AVG reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2458 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2459 val, err := client.FTCreate(ctx, "aggTestAvg", &redis.FTCreateOptions{},
2460 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2461 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2462 ).Result()
2463 Expect(err).NotTo(HaveOccurred())
2464 Expect(val).To(BeEquivalentTo("OK"))
2465 WaitForIndexing(client, "aggTestAvg")
2466
2467 client.HSet(ctx, "doc1", "grp", "g1")
2468
2469 reducers := []redis.FTAggregateReducer{
2470 {Reducer: redis.SearchAvg, Args: []interface{}{"@n"}, As: "avg"},
2471 }
2472 groupBy := []redis.FTAggregateGroupBy{
2473 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2474 }
2475 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2476 res, err := client.FTAggregateWithArgs(ctx, "aggTestAvg", "*", options).Result()
2477 Expect(err).NotTo(HaveOccurred())
2478 Expect(res.Rows).ToNot(BeEmpty())
2479
2480 Expect(res.Rows[0].Fields["avg"]).To(SatisfyAny(Equal("nan"), Equal("NaN")))
2481 })
2482
2483 It("should return 1 as default for COUNT reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2484 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2485 val, err := client.FTCreate(ctx, "aggTestCount", &redis.FTCreateOptions{},
2486 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2487 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2488 ).Result()
2489 Expect(err).NotTo(HaveOccurred())
2490 Expect(val).To(BeEquivalentTo("OK"))
2491 WaitForIndexing(client, "aggTestCount")
2492
2493 client.HSet(ctx, "doc1", "grp", "g1")
2494
2495 reducers := []redis.FTAggregateReducer{
2496 {Reducer: redis.SearchCount, As: "cnt"},
2497 }
2498 groupBy := []redis.FTAggregateGroupBy{
2499 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2500 }
2501 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2502 res, err := client.FTAggregateWithArgs(ctx, "aggTestCount", "*", options).Result()
2503 Expect(err).NotTo(HaveOccurred())
2504 Expect(res.Rows).ToNot(BeEmpty())
2505
2506 Expect(res.Rows[0].Fields["cnt"]).To(BeEquivalentTo("1"))
2507 })
2508
2509 It("should return NaN as default for SUM reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2510 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2511 val, err := client.FTCreate(ctx, "aggTestSum", &redis.FTCreateOptions{},
2512 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2513 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2514 ).Result()
2515 Expect(err).NotTo(HaveOccurred())
2516 Expect(val).To(BeEquivalentTo("OK"))
2517 WaitForIndexing(client, "aggTestSum")
2518
2519 client.HSet(ctx, "doc1", "grp", "g1")
2520
2521 reducers := []redis.FTAggregateReducer{
2522 {Reducer: redis.SearchSum, Args: []interface{}{"@n"}, As: "sum"},
2523 }
2524 groupBy := []redis.FTAggregateGroupBy{
2525 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2526 }
2527 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2528 res, err := client.FTAggregateWithArgs(ctx, "aggTestSum", "*", options).Result()
2529 Expect(err).NotTo(HaveOccurred())
2530 Expect(res.Rows).ToNot(BeEmpty())
2531
2532 Expect(res.Rows[0].Fields["sum"]).To(SatisfyAny(Equal("nan"), Equal("NaN")))
2533 })
2534
2535 It("should return the full requested number of results by re-running the query when some results expire", Label("search", "ftsearch"), func() {
2536 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2537 val, err := client.FTCreate(ctx, "aggExpired", &redis.FTCreateOptions{},
2538 &redis.FieldSchema{FieldName: "order", FieldType: redis.SearchFieldTypeNumeric, Sortable: true},
2539 ).Result()
2540 Expect(err).NotTo(HaveOccurred())
2541 Expect(val).To(BeEquivalentTo("OK"))
2542 WaitForIndexing(client, "aggExpired")
2543
2544 for i := 1; i <= 15; i++ {
2545 key := fmt.Sprintf("doc%d", i)
2546 _, err := client.HSet(ctx, key, "order", i).Result()
2547 Expect(err).NotTo(HaveOccurred())
2548 }
2549
2550 _, err = client.Del(ctx, "doc3", "doc7").Result()
2551 Expect(err).NotTo(HaveOccurred())
2552
2553 options := &redis.FTSearchOptions{
2554 SortBy: []redis.FTSearchSortBy{{FieldName: "order", Asc: true}},
2555 LimitOffset: 0,
2556 Limit: 10,
2557 }
2558 res, err := client.FTSearchWithArgs(ctx, "aggExpired", "*", options).Result()
2559 Expect(err).NotTo(HaveOccurred())
2560
2561 Expect(len(res.Docs)).To(BeEquivalentTo(10))
2562
2563 for _, doc := range res.Docs {
2564 Expect(doc.ID).ToNot(Or(Equal("doc3"), Equal("doc7")))
2565 }
2566 })
2567
2568 It("should stop processing and return an error when a timeout occurs", Label("search", "ftaggregate"), func() {
2569 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2570 val, err := client.FTCreate(ctx, "aggTimeoutHeavy", &redis.FTCreateOptions{},
2571 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric, Sortable: true},
2572 ).Result()
2573 Expect(err).NotTo(HaveOccurred())
2574 Expect(val).To(BeEquivalentTo("OK"))
2575 WaitForIndexing(client, "aggTimeoutHeavy")
2576
2577 const totalDocs = 100000
2578 for i := 0; i < totalDocs; i++ {
2579 key := fmt.Sprintf("doc%d", i)
2580 _, err := client.HSet(ctx, key, "n", i).Result()
2581 Expect(err).NotTo(HaveOccurred())
2582 }
2583
2584 err = client.ConfigSet(ctx, "search-on-timeout", "fail").Err()
2585 Expect(err).NotTo(HaveOccurred())
2586
2587 options := &redis.FTAggregateOptions{
2588 SortBy: []redis.FTAggregateSortBy{{FieldName: "@n", Desc: true}},
2589 LimitOffset: 0,
2590 Limit: 100000,
2591 Timeout: 1,
2592 }
2593 _, err = client.FTAggregateWithArgs(ctx, "aggTimeoutHeavy", "*", options).Result()
2594 Expect(err).To(HaveOccurred())
2595 Expect(strings.ToLower(err.Error())).To(ContainSubstring("timeout"))
2596 })
2597
2598 It("should return 0 as default for COUNT_DISTINCT reducer when no values are present", Label("search", "ftaggregate"), func() {
2599 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2600 val, err := client.FTCreate(ctx, "aggTestCountDistinct", &redis.FTCreateOptions{},
2601 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2602 &redis.FieldSchema{FieldName: "x", FieldType: redis.SearchFieldTypeText},
2603 ).Result()
2604 Expect(err).NotTo(HaveOccurred())
2605 Expect(val).To(BeEquivalentTo("OK"))
2606 WaitForIndexing(client, "aggTestCountDistinct")
2607
2608 client.HSet(ctx, "doc1", "grp", "g1")
2609
2610 reducers := []redis.FTAggregateReducer{
2611 {Reducer: redis.SearchCountDistinct, Args: []interface{}{"@x"}, As: "distinct_count"},
2612 }
2613 groupBy := []redis.FTAggregateGroupBy{
2614 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2615 }
2616 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2617
2618 res, err := client.FTAggregateWithArgs(ctx, "aggTestCountDistinct", "*", options).Result()
2619 Expect(err).NotTo(HaveOccurred())
2620 Expect(res.Rows).ToNot(BeEmpty())
2621 Expect(res.Rows[0].Fields["distinct_count"]).To(BeEquivalentTo("0"))
2622 })
2623
2624 It("should return 0 as default for COUNT_DISTINCTISH reducer when no values are present", Label("search", "ftaggregate"), func() {
2625 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2626 val, err := client.FTCreate(ctx, "aggTestCountDistinctIsh", &redis.FTCreateOptions{},
2627 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2628 &redis.FieldSchema{FieldName: "y", FieldType: redis.SearchFieldTypeText},
2629 ).Result()
2630 Expect(err).NotTo(HaveOccurred())
2631 Expect(val).To(BeEquivalentTo("OK"))
2632 WaitForIndexing(client, "aggTestCountDistinctIsh")
2633
2634 _, err = client.HSet(ctx, "doc1", "grp", "g1").Result()
2635 Expect(err).NotTo(HaveOccurred())
2636
2637 reducers := []redis.FTAggregateReducer{
2638 {Reducer: redis.SearchCountDistinctish, Args: []interface{}{"@y"}, As: "distinctish_count"},
2639 }
2640 groupBy := []redis.FTAggregateGroupBy{
2641 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2642 }
2643 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2644 res, err := client.FTAggregateWithArgs(ctx, "aggTestCountDistinctIsh", "*", options).Result()
2645 Expect(err).NotTo(HaveOccurred())
2646 Expect(res.Rows).ToNot(BeEmpty())
2647 Expect(res.Rows[0].Fields["distinctish_count"]).To(BeEquivalentTo("0"))
2648 })
2649
2650 It("should use BM25 as the default scorer", Label("search", "ftsearch"), func() {
2651 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2652 val, err := client.FTCreate(ctx, "scoringTest", &redis.FTCreateOptions{},
2653 &redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText},
2654 ).Result()
2655 Expect(err).NotTo(HaveOccurred())
2656 Expect(val).To(BeEquivalentTo("OK"))
2657 WaitForIndexing(client, "scoringTest")
2658
2659 _, err = client.HSet(ctx, "doc1", "description", "red apple").Result()
2660 Expect(err).NotTo(HaveOccurred())
2661 _, err = client.HSet(ctx, "doc2", "description", "green apple").Result()
2662 Expect(err).NotTo(HaveOccurred())
2663
2664 resDefault, err := client.FTSearchWithArgs(ctx, "scoringTest", "apple", &redis.FTSearchOptions{WithScores: true}).Result()
2665 Expect(err).NotTo(HaveOccurred())
2666 Expect(resDefault.Total).To(BeNumerically(">", 0))
2667
2668 resBM25, err := client.FTSearchWithArgs(ctx, "scoringTest", "apple", &redis.FTSearchOptions{WithScores: true, Scorer: "BM25"}).Result()
2669 Expect(err).NotTo(HaveOccurred())
2670 Expect(resBM25.Total).To(BeNumerically(">", 0))
2671 Expect(resDefault.Total).To(BeEquivalentTo(resBM25.Total))
2672 Expect(resDefault.Docs[0].ID).To(BeElementOf("doc1", "doc2"))
2673 Expect(resDefault.Docs[1].ID).To(BeElementOf("doc1", "doc2"))
2674 })
2675
2676 It("should return 0 as default for STDDEV reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2677 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2678 val, err := client.FTCreate(ctx, "aggTestStddev", &redis.FTCreateOptions{},
2679 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2680 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2681 ).Result()
2682 Expect(err).NotTo(HaveOccurred())
2683 Expect(val).To(BeEquivalentTo("OK"))
2684 WaitForIndexing(client, "aggTestStddev")
2685
2686 _, err = client.HSet(ctx, "doc1", "grp", "g1").Result()
2687 Expect(err).NotTo(HaveOccurred())
2688
2689 reducers := []redis.FTAggregateReducer{
2690 {Reducer: redis.SearchStdDev, Args: []interface{}{"@n"}, As: "stddev"},
2691 }
2692 groupBy := []redis.FTAggregateGroupBy{
2693 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2694 }
2695 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2696 res, err := client.FTAggregateWithArgs(ctx, "aggTestStddev", "*", options).Result()
2697 Expect(err).NotTo(HaveOccurred())
2698 Expect(res.Rows).ToNot(BeEmpty())
2699
2700 Expect(res.Rows[0].Fields["stddev"]).To(BeEquivalentTo("0"))
2701 })
2702
2703 It("should return NaN as default for QUANTILE reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2704 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2705 val, err := client.FTCreate(ctx, "aggTestQuantile", &redis.FTCreateOptions{},
2706 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2707 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2708 ).Result()
2709 Expect(err).NotTo(HaveOccurred())
2710 Expect(val).To(BeEquivalentTo("OK"))
2711 WaitForIndexing(client, "aggTestQuantile")
2712
2713 _, err = client.HSet(ctx, "doc1", "grp", "g1").Result()
2714 Expect(err).NotTo(HaveOccurred())
2715
2716 reducers := []redis.FTAggregateReducer{
2717 {Reducer: redis.SearchQuantile, Args: []interface{}{"@n", 0.5}, As: "quantile"},
2718 }
2719 groupBy := []redis.FTAggregateGroupBy{
2720 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2721 }
2722 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2723 res, err := client.FTAggregateWithArgs(ctx, "aggTestQuantile", "*", options).Result()
2724 Expect(err).NotTo(HaveOccurred())
2725 Expect(res.Rows).ToNot(BeEmpty())
2726 Expect(res.Rows[0].Fields["quantile"]).To(SatisfyAny(Equal("nan"), Equal("NaN")))
2727 })
2728
2729 It("should return nil as default for FIRST_VALUE reducer when no values are present", Label("search", "ftaggregate"), func() {
2730 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2731 val, err := client.FTCreate(ctx, "aggTestFirstValue", &redis.FTCreateOptions{},
2732 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2733 &redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeText},
2734 ).Result()
2735 Expect(err).NotTo(HaveOccurred())
2736 Expect(val).To(BeEquivalentTo("OK"))
2737 WaitForIndexing(client, "aggTestFirstValue")
2738
2739 _, err = client.HSet(ctx, "doc1", "grp", "g1").Result()
2740 Expect(err).NotTo(HaveOccurred())
2741
2742 reducers := []redis.FTAggregateReducer{
2743 {Reducer: redis.SearchFirstValue, Args: []interface{}{"@t"}, As: "first_val"},
2744 }
2745 groupBy := []redis.FTAggregateGroupBy{
2746 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2747 }
2748 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2749 res, err := client.FTAggregateWithArgs(ctx, "aggTestFirstValue", "*", options).Result()
2750 Expect(err).NotTo(HaveOccurred())
2751 Expect(res.Rows).ToNot(BeEmpty())
2752 Expect(res.Rows[0].Fields["first_val"]).To(BeNil())
2753 })
2754
2755 It("should fail to add an alias that is an existing index name", Label("search", "ftalias"), func() {
2756 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2757 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{},
2758 &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText},
2759 ).Result()
2760 Expect(err).NotTo(HaveOccurred())
2761 Expect(val).To(BeEquivalentTo("OK"))
2762 WaitForIndexing(client, "idx1")
2763
2764 val, err = client.FTCreate(ctx, "idx2", &redis.FTCreateOptions{},
2765 &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText},
2766 ).Result()
2767 Expect(err).NotTo(HaveOccurred())
2768 Expect(val).To(BeEquivalentTo("OK"))
2769 WaitForIndexing(client, "idx2")
2770
2771 _, err = client.FTAliasAdd(ctx, "idx2", "idx1").Result()
2772 Expect(err).To(HaveOccurred())
2773 Expect(strings.ToLower(err.Error())).To(ContainSubstring("alias"))
2774 })
2775
2776 It("should test ft.search with CountOnly param", Label("search", "ftsearch"), func() {
2777 val, err := client.FTCreate(ctx, "txtIndex", &redis.FTCreateOptions{},
2778 &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText},
2779 ).Result()
2780 Expect(err).NotTo(HaveOccurred())
2781 Expect(val).To(BeEquivalentTo("OK"))
2782 WaitForIndexing(client, "txtIndex")
2783
2784 _, err = client.HSet(ctx, "doc1", "txt", "hello world").Result()
2785 Expect(err).NotTo(HaveOccurred())
2786 _, err = client.HSet(ctx, "doc2", "txt", "hello go").Result()
2787 Expect(err).NotTo(HaveOccurred())
2788 _, err = client.HSet(ctx, "doc3", "txt", "hello redis").Result()
2789 Expect(err).NotTo(HaveOccurred())
2790
2791 optsCountOnly := &redis.FTSearchOptions{
2792 CountOnly: true,
2793 LimitOffset: 0,
2794 Limit: 2,
2795 DialectVersion: 2,
2796 }
2797 resCountOnly, err := client.FTSearchWithArgs(ctx, "txtIndex", "hello", optsCountOnly).Result()
2798 Expect(err).NotTo(HaveOccurred())
2799 Expect(resCountOnly.Total).To(BeEquivalentTo(3))
2800 Expect(len(resCountOnly.Docs)).To(BeEquivalentTo(0))
2801
2802 optsLimit := &redis.FTSearchOptions{
2803 CountOnly: false,
2804 LimitOffset: 0,
2805 Limit: 2,
2806 DialectVersion: 2,
2807 }
2808 resLimit, err := client.FTSearchWithArgs(ctx, "txtIndex", "hello", optsLimit).Result()
2809 Expect(err).NotTo(HaveOccurred())
2810 Expect(resLimit.Total).To(BeEquivalentTo(3))
2811 Expect(len(resLimit.Docs)).To(BeEquivalentTo(2))
2812 })
2813
2814 It("should reject deprecated configuration keys", Label("search", "ftconfig"), func() {
2815 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2816
2817 deprecatedKeys := []string{
2818 "_FREE_RESOURCE_ON_THREAD",
2819 "_NUMERIC_COMPRESS",
2820 "_NUMERIC_RANGES_PARENTS",
2821 "_PRINT_PROFILE_CLOCK",
2822 "_PRIORITIZE_INTERSECT_UNION_CHILDREN",
2823 "BG_INDEX_SLEEP_GAP",
2824 "CONN_PER_SHARD",
2825 "CURSOR_MAX_IDLE",
2826 "CURSOR_REPLY_THRESHOLD",
2827 "DEFAULT_DIALECT",
2828 "EXTLOAD",
2829 "FORK_GC_CLEAN_THRESHOLD",
2830 "FORK_GC_RETRY_INTERVAL",
2831 "FORK_GC_RUN_INTERVAL",
2832 "FORKGC_SLEEP_BEFORE_EXIT",
2833 "FRISOINI",
2834 "GC_POLICY",
2835 "GCSCANSIZE",
2836 "INDEX_CURSOR_LIMIT",
2837 "MAXAGGREGATERESULTS",
2838 "MAXDOCTABLESIZE",
2839 "MAXPREFIXEXPANSIONS",
2840 "MAXSEARCHRESULTS",
2841 "MIN_OPERATION_WORKERS",
2842 "MIN_PHONETIC_TERM_LEN",
2843 "MINPREFIX",
2844 "MINSTEMLEN",
2845 "NO_MEM_POOLS",
2846 "NOGC",
2847 "ON_TIMEOUT",
2848 "MULTI_TEXT_SLOP",
2849 "PARTIAL_INDEXED_DOCS",
2850 "RAW_DOCID_ENCODING",
2851 "SEARCH_THREADS",
2852 "TIERED_HNSW_BUFFER_LIMIT",
2853 "TIMEOUT",
2854 "TOPOLOGY_VALIDATION_TIMEOUT",
2855 "UNION_ITERATOR_HEAP",
2856 "VSS_MAX_RESIZE",
2857 "WORKERS",
2858 "WORKERS_PRIORITY_BIAS_THRESHOLD",
2859 "MT_MODE",
2860 "WORKER_THREADS",
2861 }
2862
2863 for _, key := range deprecatedKeys {
2864 _, err := client.FTConfigSet(ctx, key, "test_value").Result()
2865 Expect(err).To(HaveOccurred())
2866 }
2867
2868 val, err := client.ConfigGet(ctx, "*").Result()
2869 Expect(err).NotTo(HaveOccurred())
2870
2871 keys := make([]string, 0, len(val))
2872 for key := range val {
2873 keys = append(keys, key)
2874 }
2875 Expect(keys).To(ContainElement(ContainSubstring("search")))
2876 })
2877
2878 It("should return INF for MIN reducer and -INF for MAX reducer when no numeric values are present", Label("search", "ftaggregate"), func() {
2879 SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
2880 val, err := client.FTCreate(ctx, "aggTestMinMax", &redis.FTCreateOptions{},
2881 &redis.FieldSchema{FieldName: "grp", FieldType: redis.SearchFieldTypeText},
2882 &redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric},
2883 ).Result()
2884 Expect(err).NotTo(HaveOccurred())
2885 Expect(val).To(BeEquivalentTo("OK"))
2886 WaitForIndexing(client, "aggTestMinMax")
2887
2888 _, err = client.HSet(ctx, "doc1", "grp", "g1").Result()
2889 Expect(err).NotTo(HaveOccurred())
2890
2891 reducers := []redis.FTAggregateReducer{
2892 {Reducer: redis.SearchMin, Args: []interface{}{"@n"}, As: "minValue"},
2893 {Reducer: redis.SearchMax, Args: []interface{}{"@n"}, As: "maxValue"},
2894 }
2895 groupBy := []redis.FTAggregateGroupBy{
2896 {Fields: []interface{}{"@grp"}, Reduce: reducers},
2897 }
2898 options := &redis.FTAggregateOptions{GroupBy: groupBy}
2899 res, err := client.FTAggregateWithArgs(ctx, "aggTestMinMax", "*", options).Result()
2900 Expect(err).NotTo(HaveOccurred())
2901 Expect(res.Rows).ToNot(BeEmpty())
2902
2903 Expect(res.Rows[0].Fields["minValue"]).To(BeEquivalentTo("inf"))
2904 Expect(res.Rows[0].Fields["maxValue"]).To(BeEquivalentTo("-inf"))
2905 })
2906
2907 It("should test VAMANA with LVQ4 compression", Label("search", "ftcreate", "vamana"), func() {
2908 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2909 vamanaOptions := &redis.FTVamanaOptions{
2910 Type: "FLOAT32",
2911 Dim: 8,
2912 DistanceMetric: "L2",
2913 Compression: "LVQ4",
2914 TrainingThreshold: 1024,
2915 }
2916 val, err := client.FTCreate(ctx, "idx1",
2917 &redis.FTCreateOptions{},
2918 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2919 Expect(err).NotTo(HaveOccurred())
2920 Expect(val).To(BeEquivalentTo("OK"))
2921 WaitForIndexing(client, "idx1")
2922
2923 vectors := make([][]float32, 20)
2924 for i := 0; i < 20; i++ {
2925 vec := make([]float32, 8)
2926 for j := 0; j < 8; j++ {
2927 vec[j] = float32(i + j)
2928 }
2929 vectors[i] = vec
2930 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2931 }
2932
2933 searchOptions := &redis.FTSearchOptions{
2934 DialectVersion: 2,
2935 NoContent: true,
2936 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2937 }
2938 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
2939 Expect(err).NotTo(HaveOccurred())
2940 Expect(res.Total).To(BeEquivalentTo(5))
2941
2942 Expect(res.Docs).To(HaveLen(5))
2943 })
2944
2945 It("should test VAMANA with LeanVec4x8 compression and reduce parameter", Label("search", "ftcreate", "vamana"), func() {
2946 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2947 vamanaOptions := &redis.FTVamanaOptions{
2948 Type: "FLOAT32",
2949 Dim: 8,
2950 DistanceMetric: "L2",
2951 Compression: "LeanVec4x8",
2952 TrainingThreshold: 1024,
2953 ReduceDim: 4,
2954 }
2955 val, err := client.FTCreate(ctx, "idx1",
2956 &redis.FTCreateOptions{},
2957 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
2958 Expect(err).NotTo(HaveOccurred())
2959 Expect(val).To(BeEquivalentTo("OK"))
2960 WaitForIndexing(client, "idx1")
2961
2962 vectors := make([][]float32, 20)
2963 for i := 0; i < 20; i++ {
2964 vec := make([]float32, 8)
2965 for j := 0; j < 8; j++ {
2966 vec[j] = float32(i + j)
2967 }
2968 vectors[i] = vec
2969 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
2970 }
2971
2972 searchOptions := &redis.FTSearchOptions{
2973 DialectVersion: 2,
2974 NoContent: true,
2975 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
2976 }
2977 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
2978 Expect(err).NotTo(HaveOccurred())
2979 Expect(res.Total).To(BeEquivalentTo(5))
2980
2981 Expect(res.Docs).To(HaveLen(5))
2982 })
2983
2984 It("should test VAMANA compression algorithms with FLOAT16 type", Label("search", "ftcreate", "vamana"), func() {
2985 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
2986
2987 compressionAlgorithms := []string{"LVQ4", "LVQ4x4", "LVQ4x8", "LeanVec4x8", "LeanVec8x8"}
2988
2989 for _, compression := range compressionAlgorithms {
2990 vamanaOptions := &redis.FTVamanaOptions{
2991 Type: "FLOAT16",
2992 Dim: 8,
2993 DistanceMetric: "L2",
2994 Compression: compression,
2995 TrainingThreshold: 1024,
2996 }
2997
2998
2999 if strings.HasPrefix(compression, "LeanVec") {
3000 vamanaOptions.ReduceDim = 4
3001 }
3002
3003 indexName := fmt.Sprintf("idx_%s", compression)
3004 val, err := client.FTCreate(ctx, indexName,
3005 &redis.FTCreateOptions{},
3006 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3007 Expect(err).NotTo(HaveOccurred())
3008 Expect(val).To(BeEquivalentTo("OK"))
3009 WaitForIndexing(client, indexName)
3010
3011 for i := 0; i < 15; i++ {
3012 vec := make([]float32, 8)
3013 for j := 0; j < 8; j++ {
3014 vec[j] = float32(i + j)
3015 }
3016 client.HSet(ctx, fmt.Sprintf("doc_%s_%d", compression, i), "v", encodeFloat16Vector(vec))
3017 }
3018
3019 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
3020 searchOptions := &redis.FTSearchOptions{
3021 DialectVersion: 2,
3022 NoContent: true,
3023 Params: map[string]interface{}{"vec": encodeFloat16Vector(queryVec)},
3024 }
3025 res, err := client.FTSearchWithArgs(ctx, indexName, "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
3026 Expect(err).NotTo(HaveOccurred())
3027 Expect(res.Total).To(BeEquivalentTo(3))
3028 }
3029 })
3030
3031 It("should test VAMANA compression algorithms with FLOAT32 type", Label("search", "ftcreate", "vamana"), func() {
3032 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3033
3034 compressionAlgorithms := []string{"LVQ4", "LVQ4x4", "LVQ4x8", "LeanVec4x8", "LeanVec8x8"}
3035
3036 for _, compression := range compressionAlgorithms {
3037 vamanaOptions := &redis.FTVamanaOptions{
3038 Type: "FLOAT32",
3039 Dim: 8,
3040 DistanceMetric: "L2",
3041 Compression: compression,
3042 TrainingThreshold: 1024,
3043 }
3044
3045
3046 if strings.HasPrefix(compression, "LeanVec") {
3047 vamanaOptions.ReduceDim = 4
3048 }
3049
3050 indexName := fmt.Sprintf("idx_%s", compression)
3051 val, err := client.FTCreate(ctx, indexName,
3052 &redis.FTCreateOptions{},
3053 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3054 Expect(err).NotTo(HaveOccurred())
3055 Expect(val).To(BeEquivalentTo("OK"))
3056 WaitForIndexing(client, indexName)
3057
3058 for i := 0; i < 15; i++ {
3059 vec := make([]float32, 8)
3060 for j := 0; j < 8; j++ {
3061 vec[j] = float32(i + j)
3062 }
3063 client.HSet(ctx, fmt.Sprintf("doc_%s_%d", compression, i), "v", encodeFloat32Vector(vec))
3064 }
3065
3066 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
3067 searchOptions := &redis.FTSearchOptions{
3068 DialectVersion: 2,
3069 NoContent: true,
3070 Params: map[string]interface{}{"vec": encodeFloat32Vector(queryVec)},
3071 }
3072 res, err := client.FTSearchWithArgs(ctx, indexName, "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
3073 Expect(err).NotTo(HaveOccurred())
3074 Expect(res.Total).To(BeEquivalentTo(3))
3075 }
3076 })
3077
3078 It("should test VAMANA compression with different distance metrics", Label("search", "ftcreate", "vamana"), func() {
3079 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3080
3081 compressionAlgorithms := []string{"LVQ4", "LVQ4x4", "LVQ4x8", "LeanVec4x8", "LeanVec8x8"}
3082 distanceMetrics := []string{"L2", "COSINE", "IP"}
3083
3084 for _, compression := range compressionAlgorithms {
3085 for _, metric := range distanceMetrics {
3086 vamanaOptions := &redis.FTVamanaOptions{
3087 Type: "FLOAT32",
3088 Dim: 8,
3089 DistanceMetric: metric,
3090 Compression: compression,
3091 TrainingThreshold: 1024,
3092 }
3093
3094
3095 if strings.HasPrefix(compression, "LeanVec") {
3096 vamanaOptions.ReduceDim = 4
3097 }
3098
3099 indexName := fmt.Sprintf("idx_%s_%s", compression, metric)
3100 val, err := client.FTCreate(ctx, indexName,
3101 &redis.FTCreateOptions{},
3102 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3103 Expect(err).NotTo(HaveOccurred())
3104 Expect(val).To(BeEquivalentTo("OK"))
3105 WaitForIndexing(client, indexName)
3106
3107 for i := 0; i < 10; i++ {
3108 vec := make([]float32, 8)
3109 for j := 0; j < 8; j++ {
3110 vec[j] = float32(i + j)
3111 }
3112 client.HSet(ctx, fmt.Sprintf("doc_%s_%s_%d", compression, metric, i), "v", encodeFloat32Vector(vec))
3113 }
3114
3115 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
3116 searchOptions := &redis.FTSearchOptions{
3117 DialectVersion: 2,
3118 NoContent: true,
3119 Params: map[string]interface{}{"vec": encodeFloat32Vector(queryVec)},
3120 }
3121 res, err := client.FTSearchWithArgs(ctx, indexName, "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
3122 Expect(err).NotTo(HaveOccurred())
3123 Expect(res.Total).To(BeEquivalentTo(3))
3124 }
3125 }
3126 })
3127
3128 It("should test VAMANA compression with all advanced parameters", Label("search", "ftcreate", "vamana"), func() {
3129 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3130
3131 compressionAlgorithms := []string{"LVQ4", "LVQ4x4", "LVQ4x8", "LeanVec4x8", "LeanVec8x8"}
3132
3133 for _, compression := range compressionAlgorithms {
3134 vamanaOptions := &redis.FTVamanaOptions{
3135 Type: "FLOAT32",
3136 Dim: 8,
3137 DistanceMetric: "L2",
3138 Compression: compression,
3139 ConstructionWindowSize: 200,
3140 GraphMaxDegree: 32,
3141 SearchWindowSize: 15,
3142 Epsilon: 0.01,
3143 TrainingThreshold: 1024,
3144 }
3145
3146
3147 if strings.HasPrefix(compression, "LeanVec") {
3148 vamanaOptions.ReduceDim = 4
3149 }
3150
3151 indexName := fmt.Sprintf("idx_%s_advanced", compression)
3152 val, err := client.FTCreate(ctx, indexName,
3153 &redis.FTCreateOptions{},
3154 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3155 Expect(err).NotTo(HaveOccurred())
3156 Expect(val).To(BeEquivalentTo("OK"))
3157 WaitForIndexing(client, indexName)
3158
3159 for i := 0; i < 15; i++ {
3160 vec := make([]float32, 8)
3161 for j := 0; j < 8; j++ {
3162 vec[j] = float32(i + j)
3163 }
3164 client.HSet(ctx, fmt.Sprintf("doc_%s_advanced_%d", compression, i), "v", encodeFloat32Vector(vec))
3165 }
3166
3167 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
3168 searchOptions := &redis.FTSearchOptions{
3169 DialectVersion: 2,
3170 NoContent: true,
3171 Params: map[string]interface{}{"vec": encodeFloat32Vector(queryVec)},
3172 }
3173 res, err := client.FTSearchWithArgs(ctx, indexName, "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
3174 Expect(err).NotTo(HaveOccurred())
3175 Expect(res.Total).To(BeEquivalentTo(5))
3176 }
3177 })
3178
3179 It("should fail when using reduce parameter with non-LeanVec compression", Label("search", "ftcreate", "vamana"), func() {
3180 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3181 vamanaOptions := &redis.FTVamanaOptions{
3182 Type: "FLOAT32",
3183 Dim: 8,
3184 DistanceMetric: "L2",
3185 Compression: "LVQ8",
3186 TrainingThreshold: 1024,
3187 ReduceDim: 4,
3188 }
3189 _, err := client.FTCreate(ctx, "idx1",
3190 &redis.FTCreateOptions{},
3191 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3192 Expect(err).To(HaveOccurred())
3193 })
3194
3195 It("should test VAMANA with LVQ4 compression in RESP3", Label("search", "ftcreate", "vamana"), func() {
3196 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3197 vamanaOptions := &redis.FTVamanaOptions{
3198 Type: "FLOAT32",
3199 Dim: 8,
3200 DistanceMetric: "L2",
3201 Compression: "LVQ4",
3202 TrainingThreshold: 1024,
3203 }
3204 val, err := client.FTCreate(ctx, "idx1",
3205 &redis.FTCreateOptions{},
3206 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3207 Expect(err).NotTo(HaveOccurred())
3208 Expect(val).To(BeEquivalentTo("OK"))
3209 WaitForIndexing(client, "idx1")
3210
3211 vectors := make([][]float32, 20)
3212 for i := 0; i < 20; i++ {
3213 vec := make([]float32, 8)
3214 for j := 0; j < 8; j++ {
3215 vec[j] = float32(i + j)
3216 }
3217 vectors[i] = vec
3218 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
3219 }
3220
3221 searchOptions := &redis.FTSearchOptions{
3222 DialectVersion: 2,
3223 NoContent: true,
3224 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
3225 }
3226 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
3227 Expect(err).NotTo(HaveOccurred())
3228 Expect(res.Total).To(BeEquivalentTo(5))
3229
3230 Expect(res.Docs).To(HaveLen(5))
3231 })
3232
3233 It("should test VAMANA with LeanVec4x8 compression and reduce parameter in RESP3", Label("search", "ftcreate", "vamana"), func() {
3234 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3235 vamanaOptions := &redis.FTVamanaOptions{
3236 Type: "FLOAT32",
3237 Dim: 8,
3238 DistanceMetric: "L2",
3239 Compression: "LeanVec4x8",
3240 TrainingThreshold: 1024,
3241 ReduceDim: 4,
3242 }
3243 val, err := client.FTCreate(ctx, "idx1",
3244 &redis.FTCreateOptions{},
3245 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3246 Expect(err).NotTo(HaveOccurred())
3247 Expect(val).To(BeEquivalentTo("OK"))
3248 WaitForIndexing(client, "idx1")
3249
3250 vectors := make([][]float32, 20)
3251 for i := 0; i < 20; i++ {
3252 vec := make([]float32, 8)
3253 for j := 0; j < 8; j++ {
3254 vec[j] = float32(i + j)
3255 }
3256 vectors[i] = vec
3257 client.HSet(ctx, fmt.Sprintf("doc%d", i), "v", encodeFloat32Vector(vec))
3258 }
3259
3260 searchOptions := &redis.FTSearchOptions{
3261 DialectVersion: 2,
3262 NoContent: true,
3263 Params: map[string]interface{}{"vec": encodeFloat32Vector(vectors[0])},
3264 }
3265 res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 5 @v $vec as score]", searchOptions).Result()
3266 Expect(err).NotTo(HaveOccurred())
3267 Expect(res.Total).To(BeEquivalentTo(5))
3268
3269 Expect(res.Docs).To(HaveLen(5))
3270 })
3271
3272 It("should test VAMANA compression algorithms with FLOAT16 type in RESP3", Label("search", "ftcreate", "vamana"), func() {
3273 SkipBeforeRedisVersion(8.2, "VAMANA requires Redis 8.2+")
3274
3275 compressionAlgorithms := []string{"LVQ4", "LVQ4x4", "LVQ4x8", "LeanVec4x8", "LeanVec8x8"}
3276
3277 for _, compression := range compressionAlgorithms {
3278 vamanaOptions := &redis.FTVamanaOptions{
3279 Type: "FLOAT16",
3280 Dim: 8,
3281 DistanceMetric: "L2",
3282 Compression: compression,
3283 TrainingThreshold: 1024,
3284 }
3285
3286
3287 if strings.HasPrefix(compression, "LeanVec") {
3288 vamanaOptions.ReduceDim = 4
3289 }
3290
3291 indexName := fmt.Sprintf("idx_resp3_%s", compression)
3292 val, err := client.FTCreate(ctx, indexName,
3293 &redis.FTCreateOptions{},
3294 &redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{VamanaOptions: vamanaOptions}}).Result()
3295 Expect(err).NotTo(HaveOccurred())
3296 Expect(val).To(BeEquivalentTo("OK"))
3297 WaitForIndexing(client, indexName)
3298
3299
3300 for i := 0; i < 15; i++ {
3301 vec := make([]float32, 8)
3302 for j := 0; j < 8; j++ {
3303 vec[j] = float32(i + j)
3304 }
3305 client.HSet(ctx, fmt.Sprintf("doc_resp3_%s_%d", compression, i), "v", encodeFloat16Vector(vec))
3306 }
3307
3308 queryVec := []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
3309 searchOptions := &redis.FTSearchOptions{
3310 DialectVersion: 2,
3311 NoContent: true,
3312 Params: map[string]interface{}{"vec": encodeFloat16Vector(queryVec)},
3313 }
3314 res, err := client.FTSearchWithArgs(ctx, indexName, "*=>[KNN 3 @v $vec as score]", searchOptions).Result()
3315 Expect(err).NotTo(HaveOccurred())
3316 Expect(res.Total).To(BeEquivalentTo(3))
3317 }
3318 })
3319 })
3320
3321 func _assert_geosearch_result(result *redis.FTSearchResult, expectedDocIDs []string) {
3322 ids := make([]string, len(result.Docs))
3323 for i, doc := range result.Docs {
3324 ids[i] = doc.ID
3325 }
3326 Expect(ids).To(ConsistOf(expectedDocIDs))
3327 Expect(result.Total).To(BeEquivalentTo(len(expectedDocIDs)))
3328 }
3329
3330 var _ = Describe("RediSearch FT.Config with Resp2 and Resp3", Label("search", "NonRedisEnterprise"), func() {
3331
3332 var clientResp2 *redis.Client
3333 var clientResp3 *redis.Client
3334 BeforeEach(func() {
3335 clientResp2 = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 2})
3336 clientResp3 = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 3, UnstableResp3: true})
3337 Expect(clientResp3.FlushDB(ctx).Err()).NotTo(HaveOccurred())
3338 })
3339
3340 AfterEach(func() {
3341 Expect(clientResp2.Close()).NotTo(HaveOccurred())
3342 Expect(clientResp3.Close()).NotTo(HaveOccurred())
3343 })
3344
3345 It("should FTConfigSet and FTConfigGet with resp2 and resp3", Label("search", "ftconfigget", "ftconfigset", "NonRedisEnterprise"), func() {
3346 val, err := clientResp3.FTConfigSet(ctx, "MINPREFIX", "1").Result()
3347 Expect(err).NotTo(HaveOccurred())
3348 Expect(val).To(BeEquivalentTo("OK"))
3349
3350 res2, err := clientResp2.FTConfigGet(ctx, "MINPREFIX").Result()
3351 Expect(err).NotTo(HaveOccurred())
3352 Expect(res2).To(BeEquivalentTo(map[string]interface{}{"MINPREFIX": "1"}))
3353
3354 res3, err := clientResp3.FTConfigGet(ctx, "MINPREFIX").Result()
3355 Expect(err).NotTo(HaveOccurred())
3356 Expect(res3).To(BeEquivalentTo(map[string]interface{}{"MINPREFIX": "1"}))
3357 })
3358
3359 It("should FTConfigGet all resp2 and resp3", Label("search", "NonRedisEnterprise"), func() {
3360 res2, err := clientResp2.FTConfigGet(ctx, "*").Result()
3361 Expect(err).NotTo(HaveOccurred())
3362
3363 res3, err := clientResp3.FTConfigGet(ctx, "*").Result()
3364 Expect(err).NotTo(HaveOccurred())
3365 Expect(len(res3)).To(BeEquivalentTo(len(res2)))
3366 Expect(res2["DEFAULT_DIALECT"]).To(BeEquivalentTo(res2["DEFAULT_DIALECT"]))
3367 })
3368 })
3369
3370 var _ = Describe("RediSearch commands Resp 3", Label("search"), func() {
3371 ctx := context.TODO()
3372 var client *redis.Client
3373 var client2 *redis.Client
3374
3375 BeforeEach(func() {
3376 client = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 3, UnstableResp3: true})
3377 client2 = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 3})
3378 Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
3379 })
3380
3381 AfterEach(func() {
3382 Expect(client.Close()).NotTo(HaveOccurred())
3383 })
3384
3385 It("should handle FTAggregate with Unstable RESP3 Search Module and without stability", Label("search", "ftcreate", "ftaggregate"), func() {
3386 text1 := &redis.FieldSchema{FieldName: "PrimaryKey", FieldType: redis.SearchFieldTypeText, Sortable: true}
3387 num1 := &redis.FieldSchema{FieldName: "CreatedDateTimeUTC", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}
3388 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, num1).Result()
3389 Expect(err).NotTo(HaveOccurred())
3390 Expect(val).To(BeEquivalentTo("OK"))
3391 WaitForIndexing(client, "idx1")
3392
3393 client.HSet(ctx, "doc1", "PrimaryKey", "9::362330", "CreatedDateTimeUTC", "637387878524969984")
3394 client.HSet(ctx, "doc2", "PrimaryKey", "9::362329", "CreatedDateTimeUTC", "637387875859270016")
3395
3396 options := &redis.FTAggregateOptions{Apply: []redis.FTAggregateApply{{Field: "@CreatedDateTimeUTC * 10", As: "CreatedDateTimeUTC"}}}
3397 res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).RawResult()
3398 results := res.(map[interface{}]interface{})["results"].([]interface{})
3399 Expect(results[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})["CreatedDateTimeUTC"]).
3400 To(Or(BeEquivalentTo("6373878785249699840"), BeEquivalentTo("6373878758592700416")))
3401 Expect(results[1].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})["CreatedDateTimeUTC"]).
3402 To(Or(BeEquivalentTo("6373878785249699840"), BeEquivalentTo("6373878758592700416")))
3403
3404 rawVal := client.FTAggregateWithArgs(ctx, "idx1", "*", options).RawVal()
3405 rawValResults := rawVal.(map[interface{}]interface{})["results"].([]interface{})
3406 Expect(err).NotTo(HaveOccurred())
3407 Expect(rawValResults[0]).To(Or(BeEquivalentTo(results[0]), BeEquivalentTo(results[1])))
3408 Expect(rawValResults[1]).To(Or(BeEquivalentTo(results[0]), BeEquivalentTo(results[1])))
3409
3410
3411 Expect(func() {
3412 options = &redis.FTAggregateOptions{Apply: []redis.FTAggregateApply{{Field: "@CreatedDateTimeUTC * 10", As: "CreatedDateTimeUTC"}}}
3413 rawRes, _ := client2.FTAggregateWithArgs(ctx, "idx1", "*", options).RawResult()
3414 rawVal = client2.FTAggregateWithArgs(ctx, "idx1", "*", options).RawVal()
3415 Expect(rawRes).To(BeNil())
3416 Expect(rawVal).To(BeNil())
3417 }).Should(Panic())
3418
3419 })
3420
3421 It("should handle FTInfo with Unstable RESP3 Search Module and without stability", Label("search", "ftcreate", "ftinfo"), func() {
3422 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText, Sortable: true, NoStem: true}).Result()
3423 Expect(err).NotTo(HaveOccurred())
3424 Expect(val).To(BeEquivalentTo("OK"))
3425 WaitForIndexing(client, "idx1")
3426
3427 resInfo, err := client.FTInfo(ctx, "idx1").RawResult()
3428 Expect(err).NotTo(HaveOccurred())
3429 attributes := resInfo.(map[interface{}]interface{})["attributes"].([]interface{})
3430 flags := attributes[0].(map[interface{}]interface{})["flags"].([]interface{})
3431 Expect(flags).To(ConsistOf("SORTABLE", "NOSTEM"))
3432
3433 valInfo := client.FTInfo(ctx, "idx1").RawVal()
3434 attributes = valInfo.(map[interface{}]interface{})["attributes"].([]interface{})
3435 flags = attributes[0].(map[interface{}]interface{})["flags"].([]interface{})
3436 Expect(flags).To(ConsistOf("SORTABLE", "NOSTEM"))
3437
3438
3439 Expect(func() {
3440 rawResInfo, _ := client2.FTInfo(ctx, "idx1").RawResult()
3441 rawValInfo := client2.FTInfo(ctx, "idx1").RawVal()
3442 Expect(rawResInfo).To(BeNil())
3443 Expect(rawValInfo).To(BeNil())
3444 }).Should(Panic())
3445 })
3446
3447 It("should handle FTSpellCheck with Unstable RESP3 Search Module and without stability", Label("search", "ftcreate", "ftspellcheck"), func() {
3448 text1 := &redis.FieldSchema{FieldName: "f1", FieldType: redis.SearchFieldTypeText}
3449 text2 := &redis.FieldSchema{FieldName: "f2", FieldType: redis.SearchFieldTypeText}
3450 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
3451 Expect(err).NotTo(HaveOccurred())
3452 Expect(val).To(BeEquivalentTo("OK"))
3453 WaitForIndexing(client, "idx1")
3454
3455 client.HSet(ctx, "doc1", "f1", "some valid content", "f2", "this is sample text")
3456 client.HSet(ctx, "doc2", "f1", "very important", "f2", "lorem ipsum")
3457
3458 resSpellCheck, err := client.FTSpellCheck(ctx, "idx1", "impornant").RawResult()
3459 valSpellCheck := client.FTSpellCheck(ctx, "idx1", "impornant").RawVal()
3460 Expect(err).NotTo(HaveOccurred())
3461 Expect(valSpellCheck).To(BeEquivalentTo(resSpellCheck))
3462 results := resSpellCheck.(map[interface{}]interface{})["results"].(map[interface{}]interface{})
3463 Expect(results["impornant"].([]interface{})[0].(map[interface{}]interface{})["important"]).To(BeEquivalentTo(0.5))
3464
3465
3466 Expect(func() {
3467 rawResSpellCheck, _ := client2.FTSpellCheck(ctx, "idx1", "impornant").RawResult()
3468 rawValSpellCheck := client2.FTSpellCheck(ctx, "idx1", "impornant").RawVal()
3469 Expect(rawResSpellCheck).To(BeNil())
3470 Expect(rawValSpellCheck).To(BeNil())
3471 }).Should(Panic())
3472 })
3473
3474 It("should handle FTSearch with Unstable RESP3 Search Module and without stability", Label("search", "ftcreate", "ftsearch"), func() {
3475 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{StopWords: []interface{}{"foo", "bar", "baz"}}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
3476 Expect(err).NotTo(HaveOccurred())
3477 Expect(val).To(BeEquivalentTo("OK"))
3478 WaitForIndexing(client, "txt")
3479 client.HSet(ctx, "doc1", "txt", "foo baz")
3480 client.HSet(ctx, "doc2", "txt", "hello world")
3481 res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{NoContent: true}).RawResult()
3482 val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{NoContent: true}).RawVal()
3483 Expect(err).NotTo(HaveOccurred())
3484 Expect(val1).To(BeEquivalentTo(res1))
3485 totalResults := res1.(map[interface{}]interface{})["total_results"]
3486 Expect(totalResults).To(BeEquivalentTo(int64(0)))
3487 res2, err := client.FTSearchWithArgs(ctx, "txt", "foo bar hello world", &redis.FTSearchOptions{NoContent: true}).RawResult()
3488 Expect(err).NotTo(HaveOccurred())
3489 totalResults2 := res2.(map[interface{}]interface{})["total_results"]
3490 Expect(totalResults2).To(BeEquivalentTo(int64(1)))
3491
3492
3493 Expect(func() {
3494 rawRes2, _ := client2.FTSearchWithArgs(ctx, "txt", "foo bar hello world", &redis.FTSearchOptions{NoContent: true}).RawResult()
3495 rawVal2 := client2.FTSearchWithArgs(ctx, "txt", "foo bar hello world", &redis.FTSearchOptions{NoContent: true}).RawVal()
3496 Expect(rawRes2).To(BeNil())
3497 Expect(rawVal2).To(BeNil())
3498 }).Should(Panic())
3499 })
3500 It("should handle FTSynDump with Unstable RESP3 Search Module and without stability", Label("search", "ftsyndump"), func() {
3501 text1 := &redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}
3502 text2 := &redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}
3503 val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{OnHash: true}, text1, text2).Result()
3504 Expect(err).NotTo(HaveOccurred())
3505 Expect(val).To(BeEquivalentTo("OK"))
3506 WaitForIndexing(client, "idx1")
3507
3508 resSynUpdate, err := client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"boy", "child", "offspring"}).Result()
3509 Expect(err).NotTo(HaveOccurred())
3510 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
3511
3512 resSynUpdate, err = client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"baby", "child"}).Result()
3513 Expect(err).NotTo(HaveOccurred())
3514 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
3515
3516 resSynUpdate, err = client.FTSynUpdate(ctx, "idx1", "id1", []interface{}{"tree", "wood"}).Result()
3517 Expect(err).NotTo(HaveOccurred())
3518 Expect(resSynUpdate).To(BeEquivalentTo("OK"))
3519
3520 resSynDump, err := client.FTSynDump(ctx, "idx1").RawResult()
3521 valSynDump := client.FTSynDump(ctx, "idx1").RawVal()
3522 Expect(err).NotTo(HaveOccurred())
3523 Expect(valSynDump).To(BeEquivalentTo(resSynDump))
3524 Expect(resSynDump.(map[interface{}]interface{})["baby"]).To(BeEquivalentTo([]interface{}{"id1"}))
3525
3526
3527 Expect(func() {
3528 rawResSynDump, _ := client2.FTSynDump(ctx, "idx1").RawResult()
3529 rawValSynDump := client2.FTSynDump(ctx, "idx1").RawVal()
3530 Expect(rawResSynDump).To(BeNil())
3531 Expect(rawValSynDump).To(BeNil())
3532 }).Should(Panic())
3533 })
3534
3535 It("should test not affected Resp 3 Search method - FTExplain", Label("search", "ftexplain"), func() {
3536 text1 := &redis.FieldSchema{FieldName: "f1", FieldType: redis.SearchFieldTypeText}
3537 text2 := &redis.FieldSchema{FieldName: "f2", FieldType: redis.SearchFieldTypeText}
3538 text3 := &redis.FieldSchema{FieldName: "f3", FieldType: redis.SearchFieldTypeText}
3539 val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, text1, text2, text3).Result()
3540 Expect(err).NotTo(HaveOccurred())
3541 Expect(val).To(BeEquivalentTo("OK"))
3542 WaitForIndexing(client, "txt")
3543 res1, err := client.FTExplain(ctx, "txt", "@f3:f3_val @f2:f2_val @f1:f1_val").Result()
3544 Expect(err).NotTo(HaveOccurred())
3545 Expect(res1).ToNot(BeEmpty())
3546
3547
3548 Expect(func() {
3549 res2, err := client2.FTExplain(ctx, "txt", "@f3:f3_val @f2:f2_val @f1:f1_val").Result()
3550 Expect(err).NotTo(HaveOccurred())
3551 Expect(res2).ToNot(BeEmpty())
3552 }).ShouldNot(Panic())
3553 })
3554 })
3555
View as plain text