...

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

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

     1  package redis_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	. "github.com/bsm/ginkgo/v2"
     8  	. "github.com/bsm/gomega"
     9  	"github.com/redis/go-redis/v9"
    10  )
    11  
    12  var _ = Describe("RediSearch Builders", Label("search", "builders"), func() {
    13  	ctx := context.Background()
    14  	var client *redis.Client
    15  
    16  	BeforeEach(func() {
    17  		client = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 2})
    18  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
    19  	})
    20  
    21  	AfterEach(func() {
    22  		expectCloseErr := client.Close()
    23  		Expect(expectCloseErr).NotTo(HaveOccurred())
    24  	})
    25  
    26  	It("should create index and search with scores using builders", Label("search", "ftcreate", "ftsearch"), func() {
    27  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx1").
    28  			OnHash().
    29  			Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
    30  			Run()
    31  		Expect(err).NotTo(HaveOccurred())
    32  		Expect(createVal).To(Equal("OK"))
    33  
    34  		WaitForIndexing(client, "idx1")
    35  
    36  		client.HSet(ctx, "doc1", "foo", "hello world")
    37  		client.HSet(ctx, "doc2", "foo", "hello redis")
    38  
    39  		res, err := client.NewSearchBuilder(ctx, "idx1", "hello").WithScores().Run()
    40  		Expect(err).NotTo(HaveOccurred())
    41  		Expect(res.Total).To(Equal(2))
    42  		for _, doc := range res.Docs {
    43  			Expect(*doc.Score).To(BeNumerically(">", 0))
    44  		}
    45  	})
    46  
    47  	It("should aggregate using builders", Label("search", "ftaggregate"), func() {
    48  		_, err := client.NewCreateIndexBuilder(ctx, "idx2").
    49  			OnHash().
    50  			Schema(&redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric}).
    51  			Run()
    52  		Expect(err).NotTo(HaveOccurred())
    53  		WaitForIndexing(client, "idx2")
    54  
    55  		client.HSet(ctx, "d1", "n", 1)
    56  		client.HSet(ctx, "d2", "n", 2)
    57  
    58  		agg, err := client.NewAggregateBuilder(ctx, "idx2", "*").
    59  			GroupBy("@n").
    60  			ReduceAs(redis.SearchCount, "count").
    61  			Run()
    62  		Expect(err).NotTo(HaveOccurred())
    63  		Expect(len(agg.Rows)).To(Equal(2))
    64  	})
    65  
    66  	It("should drop index using builder", Label("search", "ftdropindex"), func() {
    67  		Expect(client.NewCreateIndexBuilder(ctx, "idx3").
    68  			OnHash().
    69  			Schema(&redis.FieldSchema{FieldName: "x", FieldType: redis.SearchFieldTypeText}).
    70  			Run()).To(Equal("OK"))
    71  		WaitForIndexing(client, "idx3")
    72  
    73  		dropVal, err := client.NewDropIndexBuilder(ctx, "idx3").Run()
    74  		Expect(err).NotTo(HaveOccurred())
    75  		Expect(dropVal).To(Equal("OK"))
    76  	})
    77  
    78  	It("should manage aliases using builder", Label("search", "ftalias"), func() {
    79  		Expect(client.NewCreateIndexBuilder(ctx, "idx4").
    80  			OnHash().
    81  			Schema(&redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeText}).
    82  			Run()).To(Equal("OK"))
    83  		WaitForIndexing(client, "idx4")
    84  
    85  		addVal, err := client.NewAliasBuilder(ctx, "alias1").Add("idx4").Run()
    86  		Expect(err).NotTo(HaveOccurred())
    87  		Expect(addVal).To(Equal("OK"))
    88  
    89  		_, err = client.NewSearchBuilder(ctx, "alias1", "*").Run()
    90  		Expect(err).NotTo(HaveOccurred())
    91  
    92  		delVal, err := client.NewAliasBuilder(ctx, "alias1").Del().Run()
    93  		Expect(err).NotTo(HaveOccurred())
    94  		Expect(delVal).To(Equal("OK"))
    95  	})
    96  
    97  	It("should explain query using ExplainBuilder", Label("search", "builders", "ftexplain"), func() {
    98  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_explain").
    99  			OnHash().
   100  			Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
   101  			Run()
   102  		Expect(err).NotTo(HaveOccurred())
   103  		Expect(createVal).To(Equal("OK"))
   104  		WaitForIndexing(client, "idx_explain")
   105  
   106  		expl, err := client.NewExplainBuilder(ctx, "idx_explain", "foo").Run()
   107  		Expect(err).NotTo(HaveOccurred())
   108  		Expect(expl).To(ContainSubstring("UNION"))
   109  	})
   110  
   111  	It("should retrieve info using SearchInfo builder", Label("search", "builders", "ftinfo"), func() {
   112  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_info").
   113  			OnHash().
   114  			Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
   115  			Run()
   116  		Expect(err).NotTo(HaveOccurred())
   117  		Expect(createVal).To(Equal("OK"))
   118  		WaitForIndexing(client, "idx_info")
   119  
   120  		i, err := client.NewSearchInfoBuilder(ctx, "idx_info").Run()
   121  		Expect(err).NotTo(HaveOccurred())
   122  		Expect(i.IndexName).To(Equal("idx_info"))
   123  	})
   124  
   125  	It("should spellcheck using builder", Label("search", "builders", "ftspellcheck"), func() {
   126  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_spell").
   127  			OnHash().
   128  			Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
   129  			Run()
   130  		Expect(err).NotTo(HaveOccurred())
   131  		Expect(createVal).To(Equal("OK"))
   132  		WaitForIndexing(client, "idx_spell")
   133  
   134  		client.HSet(ctx, "doc1", "foo", "bar")
   135  
   136  		_, err = client.NewSpellCheckBuilder(ctx, "idx_spell", "ba").Distance(1).Run()
   137  		Expect(err).NotTo(HaveOccurred())
   138  	})
   139  
   140  	It("should manage dictionary using DictBuilder", Label("search", "ftdict"), func() {
   141  		addCount, err := client.NewDictBuilder(ctx, "dict1").Add("a", "b").Run()
   142  		Expect(err).NotTo(HaveOccurred())
   143  		Expect(addCount).To(Equal(int64(2)))
   144  
   145  		dump, err := client.NewDictBuilder(ctx, "dict1").Dump().Run()
   146  		Expect(err).NotTo(HaveOccurred())
   147  		Expect(dump).To(ContainElements("a", "b"))
   148  
   149  		delCount, err := client.NewDictBuilder(ctx, "dict1").Del("a").Run()
   150  		Expect(err).NotTo(HaveOccurred())
   151  		Expect(delCount).To(Equal(int64(1)))
   152  	})
   153  
   154  	It("should tag values using TagValsBuilder", Label("search", "builders", "fttagvals"), func() {
   155  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_tag").
   156  			OnHash().
   157  			Schema(&redis.FieldSchema{FieldName: "tags", FieldType: redis.SearchFieldTypeTag}).
   158  			Run()
   159  		Expect(err).NotTo(HaveOccurred())
   160  		Expect(createVal).To(Equal("OK"))
   161  		WaitForIndexing(client, "idx_tag")
   162  
   163  		client.HSet(ctx, "doc1", "tags", "red,blue")
   164  		client.HSet(ctx, "doc2", "tags", "green,blue")
   165  
   166  		vals, err := client.NewTagValsBuilder(ctx, "idx_tag", "tags").Run()
   167  		Expect(err).NotTo(HaveOccurred())
   168  		Expect(vals).To(BeAssignableToTypeOf([]string{}))
   169  	})
   170  
   171  	It("should cursor read and delete using CursorBuilder", Label("search", "builders", "ftcursor"), func() {
   172  		Expect(client.NewCreateIndexBuilder(ctx, "idx5").
   173  			OnHash().
   174  			Schema(&redis.FieldSchema{FieldName: "f", FieldType: redis.SearchFieldTypeText}).
   175  			Run()).To(Equal("OK"))
   176  		WaitForIndexing(client, "idx5")
   177  		client.HSet(ctx, "doc1", "f", "hello")
   178  		client.HSet(ctx, "doc2", "f", "world")
   179  
   180  		cursorBuilder := client.NewCursorBuilder(ctx, "idx5", 1)
   181  		Expect(cursorBuilder).NotTo(BeNil())
   182  
   183  		cursorBuilder = cursorBuilder.Count(10)
   184  		Expect(cursorBuilder).NotTo(BeNil())
   185  
   186  		delBuilder := client.NewCursorBuilder(ctx, "idx5", 1)
   187  		Expect(delBuilder).NotTo(BeNil())
   188  	})
   189  
   190  	It("should update synonyms using SynUpdateBuilder", Label("search", "builders", "ftsynupdate"), func() {
   191  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_syn").
   192  			OnHash().
   193  			Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
   194  			Run()
   195  		Expect(err).NotTo(HaveOccurred())
   196  		Expect(createVal).To(Equal("OK"))
   197  		WaitForIndexing(client, "idx_syn")
   198  
   199  		syn, err := client.NewSynUpdateBuilder(ctx, "idx_syn", "grp1").Terms("a", "b").Run()
   200  		Expect(err).NotTo(HaveOccurred())
   201  		Expect(syn).To(Equal("OK"))
   202  	})
   203  
   204  	It("should test SearchBuilder with NoContent and Verbatim", Label("search", "ftsearch", "builders"), func() {
   205  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_nocontent").
   206  			OnHash().
   207  			Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Weight: 5}).
   208  			Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
   209  			Run()
   210  		Expect(err).NotTo(HaveOccurred())
   211  		Expect(createVal).To(Equal("OK"))
   212  		WaitForIndexing(client, "idx_nocontent")
   213  
   214  		client.HSet(ctx, "doc1", "title", "RediSearch", "body", "Redisearch implements a search engine on top of redis")
   215  
   216  		res, err := client.NewSearchBuilder(ctx, "idx_nocontent", "search engine").
   217  			NoContent().
   218  			Verbatim().
   219  			Limit(0, 5).
   220  			Run()
   221  		Expect(err).NotTo(HaveOccurred())
   222  		Expect(res.Total).To(Equal(1))
   223  		Expect(res.Docs[0].ID).To(Equal("doc1"))
   224  		// NoContent means no fields should be returned
   225  		Expect(res.Docs[0].Fields).To(BeEmpty())
   226  	})
   227  
   228  	It("should test SearchBuilder with NoStopWords", Label("search", "ftsearch", "builders"), func() {
   229  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_nostop").
   230  			OnHash().
   231  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   232  			Run()
   233  		Expect(err).NotTo(HaveOccurred())
   234  		Expect(createVal).To(Equal("OK"))
   235  		WaitForIndexing(client, "idx_nostop")
   236  
   237  		client.HSet(ctx, "doc1", "txt", "hello world")
   238  		client.HSet(ctx, "doc2", "txt", "test document")
   239  
   240  		// Test that NoStopWords method can be called and search works
   241  		res, err := client.NewSearchBuilder(ctx, "idx_nostop", "hello").NoContent().NoStopWords().Run()
   242  		Expect(err).NotTo(HaveOccurred())
   243  		Expect(res.Total).To(Equal(1))
   244  	})
   245  
   246  	It("should test SearchBuilder with filters", Label("search", "ftsearch", "builders"), func() {
   247  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_filters").
   248  			OnHash().
   249  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   250  			Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric}).
   251  			Schema(&redis.FieldSchema{FieldName: "loc", FieldType: redis.SearchFieldTypeGeo}).
   252  			Run()
   253  		Expect(err).NotTo(HaveOccurred())
   254  		Expect(createVal).To(Equal("OK"))
   255  		WaitForIndexing(client, "idx_filters")
   256  
   257  		client.HSet(ctx, "doc1", "txt", "foo bar", "num", 3.141, "loc", "-0.441,51.458")
   258  		client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2, "loc", "-0.1,51.2")
   259  
   260  		// Test numeric filter
   261  		res1, err := client.NewSearchBuilder(ctx, "idx_filters", "foo").
   262  			Filter("num", 2, 4).
   263  			NoContent().
   264  			Run()
   265  		Expect(err).NotTo(HaveOccurred())
   266  		Expect(res1.Total).To(Equal(2))
   267  
   268  		// Test geo filter
   269  		res2, err := client.NewSearchBuilder(ctx, "idx_filters", "foo").
   270  			GeoFilter("loc", -0.44, 51.45, 10, "km").
   271  			NoContent().
   272  			Run()
   273  		Expect(err).NotTo(HaveOccurred())
   274  		Expect(res2.Total).To(Equal(1))
   275  	})
   276  
   277  	It("should test SearchBuilder with sorting", Label("search", "ftsearch", "builders"), func() {
   278  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_sort").
   279  			OnHash().
   280  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   281  			Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
   282  			Run()
   283  		Expect(err).NotTo(HaveOccurred())
   284  		Expect(createVal).To(Equal("OK"))
   285  		WaitForIndexing(client, "idx_sort")
   286  
   287  		client.HSet(ctx, "doc1", "txt", "foo bar", "num", 1)
   288  		client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2)
   289  		client.HSet(ctx, "doc3", "txt", "foo qux", "num", 3)
   290  
   291  		// Test ascending sort
   292  		res1, err := client.NewSearchBuilder(ctx, "idx_sort", "foo").
   293  			SortBy("num", true).
   294  			NoContent().
   295  			Run()
   296  		Expect(err).NotTo(HaveOccurred())
   297  		Expect(res1.Total).To(Equal(3))
   298  		Expect(res1.Docs[0].ID).To(Equal("doc1"))
   299  		Expect(res1.Docs[1].ID).To(Equal("doc2"))
   300  		Expect(res1.Docs[2].ID).To(Equal("doc3"))
   301  
   302  		// Test descending sort
   303  		res2, err := client.NewSearchBuilder(ctx, "idx_sort", "foo").
   304  			SortBy("num", false).
   305  			NoContent().
   306  			Run()
   307  		Expect(err).NotTo(HaveOccurred())
   308  		Expect(res2.Total).To(Equal(3))
   309  		Expect(res2.Docs[0].ID).To(Equal("doc3"))
   310  		Expect(res2.Docs[1].ID).To(Equal("doc2"))
   311  		Expect(res2.Docs[2].ID).To(Equal("doc1"))
   312  	})
   313  
   314  	It("should test SearchBuilder with InKeys and InFields", Label("search", "ftsearch", "builders"), func() {
   315  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_in").
   316  			OnHash().
   317  			Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
   318  			Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
   319  			Run()
   320  		Expect(err).NotTo(HaveOccurred())
   321  		Expect(createVal).To(Equal("OK"))
   322  		WaitForIndexing(client, "idx_in")
   323  
   324  		client.HSet(ctx, "doc1", "title", "hello world", "body", "lorem ipsum")
   325  		client.HSet(ctx, "doc2", "title", "foo bar", "body", "hello world")
   326  		client.HSet(ctx, "doc3", "title", "baz qux", "body", "dolor sit")
   327  
   328  		// Test InKeys
   329  		res1, err := client.NewSearchBuilder(ctx, "idx_in", "hello").
   330  			InKeys("doc1", "doc2").
   331  			NoContent().
   332  			Run()
   333  		Expect(err).NotTo(HaveOccurred())
   334  		Expect(res1.Total).To(Equal(2))
   335  
   336  		// Test InFields
   337  		res2, err := client.NewSearchBuilder(ctx, "idx_in", "hello").
   338  			InFields("title").
   339  			NoContent().
   340  			Run()
   341  		Expect(err).NotTo(HaveOccurred())
   342  		Expect(res2.Total).To(Equal(1))
   343  		Expect(res2.Docs[0].ID).To(Equal("doc1"))
   344  	})
   345  
   346  	It("should test SearchBuilder with Return fields", Label("search", "ftsearch", "builders"), func() {
   347  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_return").
   348  			OnHash().
   349  			Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
   350  			Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
   351  			Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric}).
   352  			Run()
   353  		Expect(err).NotTo(HaveOccurred())
   354  		Expect(createVal).To(Equal("OK"))
   355  		WaitForIndexing(client, "idx_return")
   356  
   357  		client.HSet(ctx, "doc1", "title", "hello", "body", "world", "num", 42)
   358  
   359  		// Test ReturnFields
   360  		res1, err := client.NewSearchBuilder(ctx, "idx_return", "hello").
   361  			ReturnFields("title", "num").
   362  			Run()
   363  		Expect(err).NotTo(HaveOccurred())
   364  		Expect(res1.Total).To(Equal(1))
   365  		Expect(res1.Docs[0].Fields).To(HaveKey("title"))
   366  		Expect(res1.Docs[0].Fields).To(HaveKey("num"))
   367  		Expect(res1.Docs[0].Fields).NotTo(HaveKey("body"))
   368  
   369  		// Test ReturnAs
   370  		res2, err := client.NewSearchBuilder(ctx, "idx_return", "hello").
   371  			ReturnAs("title", "doc_title").
   372  			Run()
   373  		Expect(err).NotTo(HaveOccurred())
   374  		Expect(res2.Total).To(Equal(1))
   375  		Expect(res2.Docs[0].Fields).To(HaveKey("doc_title"))
   376  		Expect(res2.Docs[0].Fields).NotTo(HaveKey("title"))
   377  	})
   378  
   379  	It("should test SearchBuilder with advanced options", Label("search", "ftsearch", "builders"), func() {
   380  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_advanced").
   381  			OnHash().
   382  			Schema(&redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}).
   383  			Run()
   384  		Expect(err).NotTo(HaveOccurred())
   385  		Expect(createVal).To(Equal("OK"))
   386  		WaitForIndexing(client, "idx_advanced")
   387  
   388  		client.HSet(ctx, "doc1", "description", "The quick brown fox jumps over the lazy dog")
   389  		client.HSet(ctx, "doc2", "description", "Quick alice was beginning to get very tired of sitting by her quick sister on the bank")
   390  
   391  		// Test with scores and different scorers
   392  		res1, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
   393  			WithScores().
   394  			Scorer("TFIDF").
   395  			Run()
   396  		Expect(err).NotTo(HaveOccurred())
   397  		Expect(res1.Total).To(Equal(2))
   398  		for _, doc := range res1.Docs {
   399  			Expect(*doc.Score).To(BeNumerically(">", 0))
   400  		}
   401  
   402  		res2, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
   403  			WithScores().
   404  			Payload("test_payload").
   405  			NoContent().
   406  			Run()
   407  		Expect(err).NotTo(HaveOccurred())
   408  		Expect(res2.Total).To(Equal(2))
   409  
   410  		// Test with Slop and InOrder
   411  		res3, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick brown").
   412  			Slop(1).
   413  			InOrder().
   414  			NoContent().
   415  			Run()
   416  		Expect(err).NotTo(HaveOccurred())
   417  		Expect(res3.Total).To(Equal(1))
   418  
   419  		// Test with Language and Expander
   420  		res4, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
   421  			Language("english").
   422  			Expander("SYNONYM").
   423  			NoContent().
   424  			Run()
   425  		Expect(err).NotTo(HaveOccurred())
   426  		Expect(res4.Total).To(BeNumerically(">=", 0))
   427  
   428  		// Test with Timeout
   429  		res5, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
   430  			Timeout(1000).
   431  			NoContent().
   432  			Run()
   433  		Expect(err).NotTo(HaveOccurred())
   434  		Expect(res5.Total).To(Equal(2))
   435  	})
   436  
   437  	It("should test SearchBuilder with Params and Dialect", Label("search", "ftsearch", "builders"), func() {
   438  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_params").
   439  			OnHash().
   440  			Schema(&redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}).
   441  			Run()
   442  		Expect(err).NotTo(HaveOccurred())
   443  		Expect(createVal).To(Equal("OK"))
   444  		WaitForIndexing(client, "idx_params")
   445  
   446  		client.HSet(ctx, "doc1", "name", "Alice")
   447  		client.HSet(ctx, "doc2", "name", "Bob")
   448  		client.HSet(ctx, "doc3", "name", "Carol")
   449  
   450  		// Test with single param
   451  		res1, err := client.NewSearchBuilder(ctx, "idx_params", "@name:$name").
   452  			Param("name", "Alice").
   453  			NoContent().
   454  			Run()
   455  		Expect(err).NotTo(HaveOccurred())
   456  		Expect(res1.Total).To(Equal(1))
   457  		Expect(res1.Docs[0].ID).To(Equal("doc1"))
   458  
   459  		// Test with multiple params using ParamsMap
   460  		params := map[string]interface{}{
   461  			"name1": "Bob",
   462  			"name2": "Carol",
   463  		}
   464  		res2, err := client.NewSearchBuilder(ctx, "idx_params", "@name:($name1|$name2)").
   465  			ParamsMap(params).
   466  			Dialect(2).
   467  			NoContent().
   468  			Run()
   469  		Expect(err).NotTo(HaveOccurred())
   470  		Expect(res2.Total).To(Equal(2))
   471  	})
   472  
   473  	It("should test SearchBuilder with Limit and CountOnly", Label("search", "ftsearch", "builders"), func() {
   474  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_limit").
   475  			OnHash().
   476  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   477  			Run()
   478  		Expect(err).NotTo(HaveOccurred())
   479  		Expect(createVal).To(Equal("OK"))
   480  		WaitForIndexing(client, "idx_limit")
   481  
   482  		for i := 1; i <= 10; i++ {
   483  			client.HSet(ctx, fmt.Sprintf("doc%d", i), "txt", "test document")
   484  		}
   485  
   486  		// Test with Limit
   487  		res1, err := client.NewSearchBuilder(ctx, "idx_limit", "test").
   488  			Limit(2, 3).
   489  			NoContent().
   490  			Run()
   491  		Expect(err).NotTo(HaveOccurred())
   492  		Expect(res1.Total).To(Equal(10))
   493  		Expect(len(res1.Docs)).To(Equal(3))
   494  
   495  		// Test with CountOnly
   496  		res2, err := client.NewSearchBuilder(ctx, "idx_limit", "test").
   497  			CountOnly().
   498  			Run()
   499  		Expect(err).NotTo(HaveOccurred())
   500  		Expect(res2.Total).To(Equal(10))
   501  		Expect(len(res2.Docs)).To(Equal(0))
   502  	})
   503  
   504  	It("should test SearchBuilder with WithSortByCount and SortBy", Label("search", "ftsearch", "builders"), func() {
   505  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_payloads").
   506  			OnHash().
   507  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   508  			Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
   509  			Run()
   510  		Expect(err).NotTo(HaveOccurred())
   511  		Expect(createVal).To(Equal("OK"))
   512  		WaitForIndexing(client, "idx_payloads")
   513  
   514  		client.HSet(ctx, "doc1", "txt", "hello", "num", 1)
   515  		client.HSet(ctx, "doc2", "txt", "world", "num", 2)
   516  
   517  		// Test WithSortByCount and SortBy
   518  		res, err := client.NewSearchBuilder(ctx, "idx_payloads", "*").
   519  			SortBy("num", true).
   520  			WithSortByCount().
   521  			NoContent().
   522  			Run()
   523  		Expect(err).NotTo(HaveOccurred())
   524  		Expect(res.Total).To(Equal(2))
   525  	})
   526  
   527  	It("should test SearchBuilder with JSON", Label("search", "ftsearch", "builders", "json"), func() {
   528  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_json").
   529  			OnJSON().
   530  			Prefix("king:").
   531  			Schema(&redis.FieldSchema{FieldName: "$.name", FieldType: redis.SearchFieldTypeText}).
   532  			Run()
   533  		Expect(err).NotTo(HaveOccurred())
   534  		Expect(createVal).To(Equal("OK"))
   535  		WaitForIndexing(client, "idx_json")
   536  
   537  		client.JSONSet(ctx, "king:1", "$", `{"name": "henry"}`)
   538  		client.JSONSet(ctx, "king:2", "$", `{"name": "james"}`)
   539  
   540  		res, err := client.NewSearchBuilder(ctx, "idx_json", "henry").Run()
   541  		Expect(err).NotTo(HaveOccurred())
   542  		Expect(res.Total).To(Equal(1))
   543  		Expect(res.Docs[0].ID).To(Equal("king:1"))
   544  		Expect(res.Docs[0].Fields["$"]).To(Equal(`{"name":"henry"}`))
   545  	})
   546  
   547  	It("should test SearchBuilder with vector search", Label("search", "ftsearch", "builders", "vector"), func() {
   548  		hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
   549  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_vector").
   550  			OnHash().
   551  			Schema(&redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).
   552  			Run()
   553  		Expect(err).NotTo(HaveOccurred())
   554  		Expect(createVal).To(Equal("OK"))
   555  		WaitForIndexing(client, "idx_vector")
   556  
   557  		client.HSet(ctx, "a", "v", "aaaaaaaa")
   558  		client.HSet(ctx, "b", "v", "aaaabaaa")
   559  		client.HSet(ctx, "c", "v", "aaaaabaa")
   560  
   561  		res, err := client.NewSearchBuilder(ctx, "idx_vector", "*=>[KNN 2 @v $vec]").
   562  			ReturnFields("__v_score").
   563  			SortBy("__v_score", true).
   564  			Dialect(2).
   565  			Param("vec", "aaaaaaaa").
   566  			Run()
   567  		Expect(err).NotTo(HaveOccurred())
   568  		Expect(res.Docs[0].ID).To(Equal("a"))
   569  		Expect(res.Docs[0].Fields["__v_score"]).To(Equal("0"))
   570  	})
   571  
   572  	It("should test SearchBuilder with complex filtering and aggregation", Label("search", "ftsearch", "builders"), func() {
   573  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_complex").
   574  			OnHash().
   575  			Schema(&redis.FieldSchema{FieldName: "category", FieldType: redis.SearchFieldTypeTag}).
   576  			Schema(&redis.FieldSchema{FieldName: "price", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
   577  			Schema(&redis.FieldSchema{FieldName: "location", FieldType: redis.SearchFieldTypeGeo}).
   578  			Schema(&redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}).
   579  			Run()
   580  		Expect(err).NotTo(HaveOccurred())
   581  		Expect(createVal).To(Equal("OK"))
   582  		WaitForIndexing(client, "idx_complex")
   583  
   584  		client.HSet(ctx, "product1", "category", "electronics", "price", 100, "location", "-0.1,51.5", "description", "smartphone device")
   585  		client.HSet(ctx, "product2", "category", "electronics", "price", 200, "location", "-0.2,51.6", "description", "laptop computer")
   586  		client.HSet(ctx, "product3", "category", "books", "price", 20, "location", "-0.3,51.7", "description", "programming guide")
   587  
   588  		res, err := client.NewSearchBuilder(ctx, "idx_complex", "@category:{electronics} @description:(device|computer)").
   589  			Filter("price", 50, 250).
   590  			GeoFilter("location", -0.15, 51.55, 50, "km").
   591  			SortBy("price", true).
   592  			ReturnFields("category", "price", "description").
   593  			Limit(0, 10).
   594  			Run()
   595  		Expect(err).NotTo(HaveOccurred())
   596  		Expect(res.Total).To(BeNumerically(">=", 1))
   597  
   598  		res2, err := client.NewSearchBuilder(ctx, "idx_complex", "@category:{$cat} @price:[$min $max]").
   599  			ParamsMap(map[string]interface{}{
   600  				"cat": "electronics",
   601  				"min": 150,
   602  				"max": 300,
   603  			}).
   604  			Dialect(2).
   605  			WithScores().
   606  			Run()
   607  		Expect(err).NotTo(HaveOccurred())
   608  		Expect(res2.Total).To(Equal(1))
   609  		Expect(res2.Docs[0].ID).To(Equal("product2"))
   610  	})
   611  
   612  	It("should test SearchBuilder error handling and edge cases", Label("search", "ftsearch", "builders", "edge-cases"), func() {
   613  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_edge").
   614  			OnHash().
   615  			Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
   616  			Run()
   617  		Expect(err).NotTo(HaveOccurred())
   618  		Expect(createVal).To(Equal("OK"))
   619  		WaitForIndexing(client, "idx_edge")
   620  
   621  		client.HSet(ctx, "doc1", "txt", "hello world")
   622  
   623  		// Test empty query
   624  		res1, err := client.NewSearchBuilder(ctx, "idx_edge", "*").NoContent().Run()
   625  		Expect(err).NotTo(HaveOccurred())
   626  		Expect(res1.Total).To(Equal(1))
   627  
   628  		// Test query with no results
   629  		res2, err := client.NewSearchBuilder(ctx, "idx_edge", "nonexistent").NoContent().Run()
   630  		Expect(err).NotTo(HaveOccurred())
   631  		Expect(res2.Total).To(Equal(0))
   632  
   633  		// Test with multiple chained methods
   634  		res3, err := client.NewSearchBuilder(ctx, "idx_edge", "hello").
   635  			WithScores().
   636  			NoContent().
   637  			Verbatim().
   638  			InOrder().
   639  			Slop(0).
   640  			Timeout(5000).
   641  			Language("english").
   642  			Dialect(2).
   643  			Run()
   644  		Expect(err).NotTo(HaveOccurred())
   645  		Expect(res3.Total).To(Equal(1))
   646  	})
   647  
   648  	It("should test SearchBuilder method chaining", Label("search", "ftsearch", "builders", "fluent"), func() {
   649  		createVal, err := client.NewCreateIndexBuilder(ctx, "idx_fluent").
   650  			OnHash().
   651  			Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
   652  			Schema(&redis.FieldSchema{FieldName: "tags", FieldType: redis.SearchFieldTypeTag}).
   653  			Schema(&redis.FieldSchema{FieldName: "score", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
   654  			Run()
   655  		Expect(err).NotTo(HaveOccurred())
   656  		Expect(createVal).To(Equal("OK"))
   657  		WaitForIndexing(client, "idx_fluent")
   658  
   659  		client.HSet(ctx, "doc1", "title", "Redis Search Tutorial", "tags", "redis,search,tutorial", "score", 95)
   660  		client.HSet(ctx, "doc2", "title", "Advanced Redis", "tags", "redis,advanced", "score", 88)
   661  
   662  		builder := client.NewSearchBuilder(ctx, "idx_fluent", "@title:(redis) @tags:{search}")
   663  		result := builder.
   664  			WithScores().
   665  			Filter("score", 90, 100).
   666  			SortBy("score", false).
   667  			ReturnFields("title", "score").
   668  			Limit(0, 5).
   669  			Dialect(2).
   670  			Timeout(1000).
   671  			Language("english")
   672  
   673  		res, err := result.Run()
   674  		Expect(err).NotTo(HaveOccurred())
   675  		Expect(res.Total).To(Equal(1))
   676  		Expect(res.Docs[0].ID).To(Equal("doc1"))
   677  		Expect(res.Docs[0].Fields["title"]).To(Equal("Redis Search Tutorial"))
   678  		Expect(*res.Docs[0].Score).To(BeNumerically(">", 0))
   679  	})
   680  })
   681  

View as plain text