1
0
mirror of https://github.com/redis/go-redis.git synced 2025-09-02 22:01:16 +03:00
Files
go-redis/search_builders_test.go
ofekshenawa c6868653d3 feat(search): Add Query Builder for RediSearch commands (#3436)
* Add search module builders and tests (#1)

* Add search module builders and tests

* Add tests

* Use builders and Actions in more clean way

* Update search_builders.go

Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>

* Update search_builders.go

Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>

---------

Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
2025-08-11 01:42:56 +03:00

681 lines
24 KiB
Go

package redis_test
import (
"context"
"fmt"
. "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega"
"github.com/redis/go-redis/v9"
)
var _ = Describe("RediSearch Builders", Label("search", "builders"), func() {
ctx := context.Background()
var client *redis.Client
BeforeEach(func() {
client = redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 2})
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
})
AfterEach(func() {
expectCloseErr := client.Close()
Expect(expectCloseErr).NotTo(HaveOccurred())
})
It("should create index and search with scores using builders", Label("search", "ftcreate", "ftsearch"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx1").
OnHash().
Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx1")
client.HSet(ctx, "doc1", "foo", "hello world")
client.HSet(ctx, "doc2", "foo", "hello redis")
res, err := client.NewSearchBuilder(ctx, "idx1", "hello").WithScores().Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(2))
for _, doc := range res.Docs {
Expect(*doc.Score).To(BeNumerically(">", 0))
}
})
It("should aggregate using builders", Label("search", "ftaggregate"), func() {
_, err := client.NewCreateIndexBuilder(ctx, "idx2").
OnHash().
Schema(&redis.FieldSchema{FieldName: "n", FieldType: redis.SearchFieldTypeNumeric}).
Run()
Expect(err).NotTo(HaveOccurred())
WaitForIndexing(client, "idx2")
client.HSet(ctx, "d1", "n", 1)
client.HSet(ctx, "d2", "n", 2)
agg, err := client.NewAggregateBuilder(ctx, "idx2", "*").
GroupBy("@n").
ReduceAs(redis.SearchCount, "count").
Run()
Expect(err).NotTo(HaveOccurred())
Expect(len(agg.Rows)).To(Equal(2))
})
It("should drop index using builder", Label("search", "ftdropindex"), func() {
Expect(client.NewCreateIndexBuilder(ctx, "idx3").
OnHash().
Schema(&redis.FieldSchema{FieldName: "x", FieldType: redis.SearchFieldTypeText}).
Run()).To(Equal("OK"))
WaitForIndexing(client, "idx3")
dropVal, err := client.NewDropIndexBuilder(ctx, "idx3").Run()
Expect(err).NotTo(HaveOccurred())
Expect(dropVal).To(Equal("OK"))
})
It("should manage aliases using builder", Label("search", "ftalias"), func() {
Expect(client.NewCreateIndexBuilder(ctx, "idx4").
OnHash().
Schema(&redis.FieldSchema{FieldName: "t", FieldType: redis.SearchFieldTypeText}).
Run()).To(Equal("OK"))
WaitForIndexing(client, "idx4")
addVal, err := client.NewAliasBuilder(ctx, "alias1").Add("idx4").Run()
Expect(err).NotTo(HaveOccurred())
Expect(addVal).To(Equal("OK"))
_, err = client.NewSearchBuilder(ctx, "alias1", "*").Run()
Expect(err).NotTo(HaveOccurred())
delVal, err := client.NewAliasBuilder(ctx, "alias1").Del().Run()
Expect(err).NotTo(HaveOccurred())
Expect(delVal).To(Equal("OK"))
})
It("should explain query using ExplainBuilder", Label("search", "builders", "ftexplain"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_explain").
OnHash().
Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_explain")
expl, err := client.NewExplainBuilder(ctx, "idx_explain", "foo").Run()
Expect(err).NotTo(HaveOccurred())
Expect(expl).To(ContainSubstring("UNION"))
})
It("should retrieve info using SearchInfo builder", Label("search", "builders", "ftinfo"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_info").
OnHash().
Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_info")
i, err := client.NewSearchInfoBuilder(ctx, "idx_info").Run()
Expect(err).NotTo(HaveOccurred())
Expect(i.IndexName).To(Equal("idx_info"))
})
It("should spellcheck using builder", Label("search", "builders", "ftspellcheck"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_spell").
OnHash().
Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_spell")
client.HSet(ctx, "doc1", "foo", "bar")
_, err = client.NewSpellCheckBuilder(ctx, "idx_spell", "ba").Distance(1).Run()
Expect(err).NotTo(HaveOccurred())
})
It("should manage dictionary using DictBuilder", Label("search", "ftdict"), func() {
addCount, err := client.NewDictBuilder(ctx, "dict1").Add("a", "b").Run()
Expect(err).NotTo(HaveOccurred())
Expect(addCount).To(Equal(int64(2)))
dump, err := client.NewDictBuilder(ctx, "dict1").Dump().Run()
Expect(err).NotTo(HaveOccurred())
Expect(dump).To(ContainElements("a", "b"))
delCount, err := client.NewDictBuilder(ctx, "dict1").Del("a").Run()
Expect(err).NotTo(HaveOccurred())
Expect(delCount).To(Equal(int64(1)))
})
It("should tag values using TagValsBuilder", Label("search", "builders", "fttagvals"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_tag").
OnHash().
Schema(&redis.FieldSchema{FieldName: "tags", FieldType: redis.SearchFieldTypeTag}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_tag")
client.HSet(ctx, "doc1", "tags", "red,blue")
client.HSet(ctx, "doc2", "tags", "green,blue")
vals, err := client.NewTagValsBuilder(ctx, "idx_tag", "tags").Run()
Expect(err).NotTo(HaveOccurred())
Expect(vals).To(BeAssignableToTypeOf([]string{}))
})
It("should cursor read and delete using CursorBuilder", Label("search", "builders", "ftcursor"), func() {
Expect(client.NewCreateIndexBuilder(ctx, "idx5").
OnHash().
Schema(&redis.FieldSchema{FieldName: "f", FieldType: redis.SearchFieldTypeText}).
Run()).To(Equal("OK"))
WaitForIndexing(client, "idx5")
client.HSet(ctx, "doc1", "f", "hello")
client.HSet(ctx, "doc2", "f", "world")
cursorBuilder := client.NewCursorBuilder(ctx, "idx5", 1)
Expect(cursorBuilder).NotTo(BeNil())
cursorBuilder = cursorBuilder.Count(10)
Expect(cursorBuilder).NotTo(BeNil())
delBuilder := client.NewCursorBuilder(ctx, "idx5", 1)
Expect(delBuilder).NotTo(BeNil())
})
It("should update synonyms using SynUpdateBuilder", Label("search", "builders", "ftsynupdate"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_syn").
OnHash().
Schema(&redis.FieldSchema{FieldName: "foo", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_syn")
syn, err := client.NewSynUpdateBuilder(ctx, "idx_syn", "grp1").Terms("a", "b").Run()
Expect(err).NotTo(HaveOccurred())
Expect(syn).To(Equal("OK"))
})
It("should test SearchBuilder with NoContent and Verbatim", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_nocontent").
OnHash().
Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText, Weight: 5}).
Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_nocontent")
client.HSet(ctx, "doc1", "title", "RediSearch", "body", "Redisearch implements a search engine on top of redis")
res, err := client.NewSearchBuilder(ctx, "idx_nocontent", "search engine").
NoContent().
Verbatim().
Limit(0, 5).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(1))
Expect(res.Docs[0].ID).To(Equal("doc1"))
// NoContent means no fields should be returned
Expect(res.Docs[0].Fields).To(BeEmpty())
})
It("should test SearchBuilder with NoStopWords", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_nostop").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_nostop")
client.HSet(ctx, "doc1", "txt", "hello world")
client.HSet(ctx, "doc2", "txt", "test document")
// Test that NoStopWords method can be called and search works
res, err := client.NewSearchBuilder(ctx, "idx_nostop", "hello").NoContent().NoStopWords().Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(1))
})
It("should test SearchBuilder with filters", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_filters").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric}).
Schema(&redis.FieldSchema{FieldName: "loc", FieldType: redis.SearchFieldTypeGeo}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_filters")
client.HSet(ctx, "doc1", "txt", "foo bar", "num", 3.141, "loc", "-0.441,51.458")
client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2, "loc", "-0.1,51.2")
// Test numeric filter
res1, err := client.NewSearchBuilder(ctx, "idx_filters", "foo").
Filter("num", 2, 4).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(2))
// Test geo filter
res2, err := client.NewSearchBuilder(ctx, "idx_filters", "foo").
GeoFilter("loc", -0.44, 51.45, 10, "km").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(1))
})
It("should test SearchBuilder with sorting", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_sort").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_sort")
client.HSet(ctx, "doc1", "txt", "foo bar", "num", 1)
client.HSet(ctx, "doc2", "txt", "foo baz", "num", 2)
client.HSet(ctx, "doc3", "txt", "foo qux", "num", 3)
// Test ascending sort
res1, err := client.NewSearchBuilder(ctx, "idx_sort", "foo").
SortBy("num", true).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(3))
Expect(res1.Docs[0].ID).To(Equal("doc1"))
Expect(res1.Docs[1].ID).To(Equal("doc2"))
Expect(res1.Docs[2].ID).To(Equal("doc3"))
// Test descending sort
res2, err := client.NewSearchBuilder(ctx, "idx_sort", "foo").
SortBy("num", false).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(3))
Expect(res2.Docs[0].ID).To(Equal("doc3"))
Expect(res2.Docs[1].ID).To(Equal("doc2"))
Expect(res2.Docs[2].ID).To(Equal("doc1"))
})
It("should test SearchBuilder with InKeys and InFields", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_in").
OnHash().
Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_in")
client.HSet(ctx, "doc1", "title", "hello world", "body", "lorem ipsum")
client.HSet(ctx, "doc2", "title", "foo bar", "body", "hello world")
client.HSet(ctx, "doc3", "title", "baz qux", "body", "dolor sit")
// Test InKeys
res1, err := client.NewSearchBuilder(ctx, "idx_in", "hello").
InKeys("doc1", "doc2").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(2))
// Test InFields
res2, err := client.NewSearchBuilder(ctx, "idx_in", "hello").
InFields("title").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(1))
Expect(res2.Docs[0].ID).To(Equal("doc1"))
})
It("should test SearchBuilder with Return fields", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_return").
OnHash().
Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "body", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_return")
client.HSet(ctx, "doc1", "title", "hello", "body", "world", "num", 42)
// Test ReturnFields
res1, err := client.NewSearchBuilder(ctx, "idx_return", "hello").
ReturnFields("title", "num").
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(1))
Expect(res1.Docs[0].Fields).To(HaveKey("title"))
Expect(res1.Docs[0].Fields).To(HaveKey("num"))
Expect(res1.Docs[0].Fields).NotTo(HaveKey("body"))
// Test ReturnAs
res2, err := client.NewSearchBuilder(ctx, "idx_return", "hello").
ReturnAs("title", "doc_title").
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(1))
Expect(res2.Docs[0].Fields).To(HaveKey("doc_title"))
Expect(res2.Docs[0].Fields).NotTo(HaveKey("title"))
})
It("should test SearchBuilder with advanced options", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_advanced").
OnHash().
Schema(&redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_advanced")
client.HSet(ctx, "doc1", "description", "The quick brown fox jumps over the lazy dog")
client.HSet(ctx, "doc2", "description", "Quick alice was beginning to get very tired of sitting by her quick sister on the bank")
// Test with scores and different scorers
res1, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
WithScores().
Scorer("TFIDF").
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(2))
for _, doc := range res1.Docs {
Expect(*doc.Score).To(BeNumerically(">", 0))
}
res2, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
WithScores().
Payload("test_payload").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(2))
// Test with Slop and InOrder
res3, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick brown").
Slop(1).
InOrder().
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res3.Total).To(Equal(1))
// Test with Language and Expander
res4, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
Language("english").
Expander("SYNONYM").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res4.Total).To(BeNumerically(">=", 0))
// Test with Timeout
res5, err := client.NewSearchBuilder(ctx, "idx_advanced", "quick").
Timeout(1000).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res5.Total).To(Equal(2))
})
It("should test SearchBuilder with Params and Dialect", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_params").
OnHash().
Schema(&redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_params")
client.HSet(ctx, "doc1", "name", "Alice")
client.HSet(ctx, "doc2", "name", "Bob")
client.HSet(ctx, "doc3", "name", "Carol")
// Test with single param
res1, err := client.NewSearchBuilder(ctx, "idx_params", "@name:$name").
Param("name", "Alice").
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(1))
Expect(res1.Docs[0].ID).To(Equal("doc1"))
// Test with multiple params using ParamsMap
params := map[string]interface{}{
"name1": "Bob",
"name2": "Carol",
}
res2, err := client.NewSearchBuilder(ctx, "idx_params", "@name:($name1|$name2)").
ParamsMap(params).
Dialect(2).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(2))
})
It("should test SearchBuilder with Limit and CountOnly", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_limit").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_limit")
for i := 1; i <= 10; i++ {
client.HSet(ctx, fmt.Sprintf("doc%d", i), "txt", "test document")
}
// Test with Limit
res1, err := client.NewSearchBuilder(ctx, "idx_limit", "test").
Limit(2, 3).
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(10))
Expect(len(res1.Docs)).To(Equal(3))
// Test with CountOnly
res2, err := client.NewSearchBuilder(ctx, "idx_limit", "test").
CountOnly().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(10))
Expect(len(res2.Docs)).To(Equal(0))
})
It("should test SearchBuilder with WithSortByCount and SortBy", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_payloads").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "num", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_payloads")
client.HSet(ctx, "doc1", "txt", "hello", "num", 1)
client.HSet(ctx, "doc2", "txt", "world", "num", 2)
// Test WithSortByCount and SortBy
res, err := client.NewSearchBuilder(ctx, "idx_payloads", "*").
SortBy("num", true).
WithSortByCount().
NoContent().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(2))
})
It("should test SearchBuilder with JSON", Label("search", "ftsearch", "builders", "json"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_json").
OnJSON().
Prefix("king:").
Schema(&redis.FieldSchema{FieldName: "$.name", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_json")
client.JSONSet(ctx, "king:1", "$", `{"name": "henry"}`)
client.JSONSet(ctx, "king:2", "$", `{"name": "james"}`)
res, err := client.NewSearchBuilder(ctx, "idx_json", "henry").Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(1))
Expect(res.Docs[0].ID).To(Equal("king:1"))
Expect(res.Docs[0].Fields["$"]).To(Equal(`{"name":"henry"}`))
})
It("should test SearchBuilder with vector search", Label("search", "ftsearch", "builders", "vector"), func() {
hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_vector").
OnHash().
Schema(&redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_vector")
client.HSet(ctx, "a", "v", "aaaaaaaa")
client.HSet(ctx, "b", "v", "aaaabaaa")
client.HSet(ctx, "c", "v", "aaaaabaa")
res, err := client.NewSearchBuilder(ctx, "idx_vector", "*=>[KNN 2 @v $vec]").
ReturnFields("__v_score").
SortBy("__v_score", true).
Dialect(2).
Param("vec", "aaaaaaaa").
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Docs[0].ID).To(Equal("a"))
Expect(res.Docs[0].Fields["__v_score"]).To(Equal("0"))
})
It("should test SearchBuilder with complex filtering and aggregation", Label("search", "ftsearch", "builders"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_complex").
OnHash().
Schema(&redis.FieldSchema{FieldName: "category", FieldType: redis.SearchFieldTypeTag}).
Schema(&redis.FieldSchema{FieldName: "price", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
Schema(&redis.FieldSchema{FieldName: "location", FieldType: redis.SearchFieldTypeGeo}).
Schema(&redis.FieldSchema{FieldName: "description", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_complex")
client.HSet(ctx, "product1", "category", "electronics", "price", 100, "location", "-0.1,51.5", "description", "smartphone device")
client.HSet(ctx, "product2", "category", "electronics", "price", 200, "location", "-0.2,51.6", "description", "laptop computer")
client.HSet(ctx, "product3", "category", "books", "price", 20, "location", "-0.3,51.7", "description", "programming guide")
res, err := client.NewSearchBuilder(ctx, "idx_complex", "@category:{electronics} @description:(device|computer)").
Filter("price", 50, 250).
GeoFilter("location", -0.15, 51.55, 50, "km").
SortBy("price", true).
ReturnFields("category", "price", "description").
Limit(0, 10).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(BeNumerically(">=", 1))
res2, err := client.NewSearchBuilder(ctx, "idx_complex", "@category:{$cat} @price:[$min $max]").
ParamsMap(map[string]interface{}{
"cat": "electronics",
"min": 150,
"max": 300,
}).
Dialect(2).
WithScores().
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(1))
Expect(res2.Docs[0].ID).To(Equal("product2"))
})
It("should test SearchBuilder error handling and edge cases", Label("search", "ftsearch", "builders", "edge-cases"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_edge").
OnHash().
Schema(&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_edge")
client.HSet(ctx, "doc1", "txt", "hello world")
// Test empty query
res1, err := client.NewSearchBuilder(ctx, "idx_edge", "*").NoContent().Run()
Expect(err).NotTo(HaveOccurred())
Expect(res1.Total).To(Equal(1))
// Test query with no results
res2, err := client.NewSearchBuilder(ctx, "idx_edge", "nonexistent").NoContent().Run()
Expect(err).NotTo(HaveOccurred())
Expect(res2.Total).To(Equal(0))
// Test with multiple chained methods
res3, err := client.NewSearchBuilder(ctx, "idx_edge", "hello").
WithScores().
NoContent().
Verbatim().
InOrder().
Slop(0).
Timeout(5000).
Language("english").
Dialect(2).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(res3.Total).To(Equal(1))
})
It("should test SearchBuilder method chaining", Label("search", "ftsearch", "builders", "fluent"), func() {
createVal, err := client.NewCreateIndexBuilder(ctx, "idx_fluent").
OnHash().
Schema(&redis.FieldSchema{FieldName: "title", FieldType: redis.SearchFieldTypeText}).
Schema(&redis.FieldSchema{FieldName: "tags", FieldType: redis.SearchFieldTypeTag}).
Schema(&redis.FieldSchema{FieldName: "score", FieldType: redis.SearchFieldTypeNumeric, Sortable: true}).
Run()
Expect(err).NotTo(HaveOccurred())
Expect(createVal).To(Equal("OK"))
WaitForIndexing(client, "idx_fluent")
client.HSet(ctx, "doc1", "title", "Redis Search Tutorial", "tags", "redis,search,tutorial", "score", 95)
client.HSet(ctx, "doc2", "title", "Advanced Redis", "tags", "redis,advanced", "score", 88)
builder := client.NewSearchBuilder(ctx, "idx_fluent", "@title:(redis) @tags:{search}")
result := builder.
WithScores().
Filter("score", 90, 100).
SortBy("score", false).
ReturnFields("title", "score").
Limit(0, 5).
Dialect(2).
Timeout(1000).
Language("english")
res, err := result.Run()
Expect(err).NotTo(HaveOccurred())
Expect(res.Total).To(Equal(1))
Expect(res.Docs[0].ID).To(Equal("doc1"))
Expect(res.Docs[0].Fields["title"]).To(Equal("Redis Search Tutorial"))
Expect(*res.Docs[0].Score).To(BeNumerically(">", 0))
})
})