...

Source file src/github.com/redis/go-redis/v9/search_test.go

Documentation: github.com/redis/go-redis/v9

     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  		// Convert float32 to float16 (16-bit representation)
    45  		// This is a simplified conversion - in practice you'd use a proper float16 library
    46  		f16 := uint16(v * 1000) // Simple scaling for test purposes
    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  		// Test Geo filter
   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  		// test spellcheck with Levenshtein distance
   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  		// test spellcheck include
   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  	// up until redis 8 the default scorer was TFIDF, in redis 8 it is BM25
   405  	// this test expect redis major version >= 8
   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  	// up until redis 8 the default scorer was TFIDF, in redis 8 it is BM25
   448  	// this test expect redis version < 8.0
   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  		// 6 feb
   758  		client.HSet(ctx, "doc1", "PrimaryKey", "9::362330", "CreatedDateTimeUTC", "1738823999")
   759  
   760  		// 12 feb
   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  		// Verify LOAD options related arguments
   880  		Expect(args).To(ContainElement("LOAD"))
   881  		// Check that field names and aliases are present
   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  		// create withsuffixtrie index - text field
  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  		// create withsuffixtrie index - tag field
  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  		// Create the index
  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  		// Define INT8 vector field
  1662  		hnswOptionsInt8 := &redis.FTHNSWOptions{
  1663  			Type:           "INT8",
  1664  			Dim:            2,
  1665  			DistanceMetric: "L2",
  1666  		}
  1667  
  1668  		// Define UINT8 vector field
  1669  		hnswOptionsUint8 := &redis.FTHNSWOptions{
  1670  			Type:           "UINT8",
  1671  			Dim:            2,
  1672  			DistanceMetric: "L2",
  1673  		}
  1674  
  1675  		// Create index with INT8 and UINT8 vector fields
  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  		// Insert vectors in int8 and uint8 format
  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  		// Perform KNN search on INT8 vector
  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  		// Perform KNN search on UINT8 vector
  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  		// For NaN values, use a custom check since NaN != NaN in floating point math
  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  		// Test missing Type
  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  		// Test missing Dim
  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  		// Test missing DistanceMetric
  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  		// Test VAMANA + HNSW
  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  		// Test VAMANA + FLAT
  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  		// L2 distance test vectors
  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")) // Should be closest to itself
  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  		// Test FLOAT16 with LVQ8
  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  		// Test FLOAT32 with LVQ8
  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  		// Add data to both indices
  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  		// Test FLOAT16 index
  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  		// Test FLOAT32 index
  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  		// Attempt to search with a non-zero offset and zero limit.
  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  		// default behaviour was changed in 8.0.1, set to fail to validate the timeout was triggered
  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, // 1 ms timeout, expected to trigger a timeout error.
  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, // even though we limit to 2, with count-only no docs are returned
  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, // we expect to get 2 documents even though total count is 3
  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  		// List of deprecated configuration keys.
  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  		// Since FT.CONFIG is deprecated since redis 8, use CONFIG instead with new search parameters.
  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  		// Don't check specific document ID as vector search is probabilistic
  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, // Reduce dimension to 4 (half of original 8)
  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  		// Don't check specific document ID as vector search is probabilistic
  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  			// Add reduce parameter for LeanVec compressions
  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  			// Add reduce parameter for LeanVec compressions
  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  				// Add reduce parameter for LeanVec compressions
  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  			// Add reduce parameter for LeanVec compressions
  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, // This should fail for LVQ8
  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  		// Don't check specific document ID as vector search is probabilistic
  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, // Reduce dimension to 4 (half of original 8)
  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  		// Don't check specific document ID as vector search is probabilistic
  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  			// Add reduce parameter for LeanVec compressions
  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  			// Add test data
  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  		// Test with UnstableResp3 false
  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  		// Test with UnstableResp3 false
  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  		// Test with UnstableResp3 false
  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  		// Test with UnstableResp3 false
  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  		// Test with UnstableResp3 false
  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  		// Test with UnstableResp3 false
  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