mirror of
https://github.com/redis/go-redis.git
synced 2025-08-10 11:03:00 +03:00
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>
This commit is contained in:
825
search_builders.go
Normal file
825
search_builders.go
Normal file
@@ -0,0 +1,825 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// ----------------------
|
||||
// Search Module Builders
|
||||
// ----------------------
|
||||
|
||||
// SearchBuilder provides a fluent API for FT.SEARCH
|
||||
// (see original FTSearchOptions for all options).
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type SearchBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
query string
|
||||
options *FTSearchOptions
|
||||
}
|
||||
|
||||
// NewSearchBuilder creates a new SearchBuilder for FT.SEARCH commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewSearchBuilder(ctx context.Context, index, query string) *SearchBuilder {
|
||||
b := &SearchBuilder{c: c, ctx: ctx, index: index, query: query, options: &FTSearchOptions{LimitOffset: -1}}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithScores includes WITHSCORES.
|
||||
func (b *SearchBuilder) WithScores() *SearchBuilder {
|
||||
b.options.WithScores = true
|
||||
return b
|
||||
}
|
||||
|
||||
// NoContent includes NOCONTENT.
|
||||
func (b *SearchBuilder) NoContent() *SearchBuilder { b.options.NoContent = true; return b }
|
||||
|
||||
// Verbatim includes VERBATIM.
|
||||
func (b *SearchBuilder) Verbatim() *SearchBuilder { b.options.Verbatim = true; return b }
|
||||
|
||||
// NoStopWords includes NOSTOPWORDS.
|
||||
func (b *SearchBuilder) NoStopWords() *SearchBuilder { b.options.NoStopWords = true; return b }
|
||||
|
||||
// WithPayloads includes WITHPAYLOADS.
|
||||
func (b *SearchBuilder) WithPayloads() *SearchBuilder {
|
||||
b.options.WithPayloads = true
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSortKeys includes WITHSORTKEYS.
|
||||
func (b *SearchBuilder) WithSortKeys() *SearchBuilder {
|
||||
b.options.WithSortKeys = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Filter adds a FILTER clause: FILTER <field> <min> <max>.
|
||||
func (b *SearchBuilder) Filter(field string, min, max interface{}) *SearchBuilder {
|
||||
b.options.Filters = append(b.options.Filters, FTSearchFilter{
|
||||
FieldName: field,
|
||||
Min: min,
|
||||
Max: max,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// GeoFilter adds a GEOFILTER clause: GEOFILTER <field> <lon> <lat> <radius> <unit>.
|
||||
func (b *SearchBuilder) GeoFilter(field string, lon, lat, radius float64, unit string) *SearchBuilder {
|
||||
b.options.GeoFilter = append(b.options.GeoFilter, FTSearchGeoFilter{
|
||||
FieldName: field,
|
||||
Longitude: lon,
|
||||
Latitude: lat,
|
||||
Radius: radius,
|
||||
Unit: unit,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// InKeys restricts the search to the given keys.
|
||||
func (b *SearchBuilder) InKeys(keys ...interface{}) *SearchBuilder {
|
||||
b.options.InKeys = append(b.options.InKeys, keys...)
|
||||
return b
|
||||
}
|
||||
|
||||
// InFields restricts the search to the given fields.
|
||||
func (b *SearchBuilder) InFields(fields ...interface{}) *SearchBuilder {
|
||||
b.options.InFields = append(b.options.InFields, fields...)
|
||||
return b
|
||||
}
|
||||
|
||||
// ReturnFields adds simple RETURN <n> <field>...
|
||||
func (b *SearchBuilder) ReturnFields(fields ...string) *SearchBuilder {
|
||||
for _, f := range fields {
|
||||
b.options.Return = append(b.options.Return, FTSearchReturn{FieldName: f})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ReturnAs adds RETURN <field> AS <alias>.
|
||||
func (b *SearchBuilder) ReturnAs(field, alias string) *SearchBuilder {
|
||||
b.options.Return = append(b.options.Return, FTSearchReturn{FieldName: field, As: alias})
|
||||
return b
|
||||
}
|
||||
|
||||
// Slop adds SLOP <n>.
|
||||
func (b *SearchBuilder) Slop(slop int) *SearchBuilder {
|
||||
b.options.Slop = slop
|
||||
return b
|
||||
}
|
||||
|
||||
// Timeout adds TIMEOUT <ms>.
|
||||
func (b *SearchBuilder) Timeout(timeout int) *SearchBuilder {
|
||||
b.options.Timeout = timeout
|
||||
return b
|
||||
}
|
||||
|
||||
// InOrder includes INORDER.
|
||||
func (b *SearchBuilder) InOrder() *SearchBuilder {
|
||||
b.options.InOrder = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Language sets LANGUAGE <lang>.
|
||||
func (b *SearchBuilder) Language(lang string) *SearchBuilder {
|
||||
b.options.Language = lang
|
||||
return b
|
||||
}
|
||||
|
||||
// Expander sets EXPANDER <expander>.
|
||||
func (b *SearchBuilder) Expander(expander string) *SearchBuilder {
|
||||
b.options.Expander = expander
|
||||
return b
|
||||
}
|
||||
|
||||
// Scorer sets SCORER <scorer>.
|
||||
func (b *SearchBuilder) Scorer(scorer string) *SearchBuilder {
|
||||
b.options.Scorer = scorer
|
||||
return b
|
||||
}
|
||||
|
||||
// ExplainScore includes EXPLAINSCORE.
|
||||
func (b *SearchBuilder) ExplainScore() *SearchBuilder {
|
||||
b.options.ExplainScore = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Payload sets PAYLOAD <payload>.
|
||||
func (b *SearchBuilder) Payload(payload string) *SearchBuilder {
|
||||
b.options.Payload = payload
|
||||
return b
|
||||
}
|
||||
|
||||
// SortBy adds SORTBY <field> ASC|DESC.
|
||||
func (b *SearchBuilder) SortBy(field string, asc bool) *SearchBuilder {
|
||||
b.options.SortBy = append(b.options.SortBy, FTSearchSortBy{
|
||||
FieldName: field,
|
||||
Asc: asc,
|
||||
Desc: !asc,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSortByCount includes WITHCOUNT (when used with SortBy).
|
||||
func (b *SearchBuilder) WithSortByCount() *SearchBuilder {
|
||||
b.options.SortByWithCount = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Param adds a single PARAMS <k> <v>.
|
||||
func (b *SearchBuilder) Param(key string, value interface{}) *SearchBuilder {
|
||||
if b.options.Params == nil {
|
||||
b.options.Params = make(map[string]interface{}, 1)
|
||||
}
|
||||
b.options.Params[key] = value
|
||||
return b
|
||||
}
|
||||
|
||||
// ParamsMap adds multiple PARAMS at once.
|
||||
func (b *SearchBuilder) ParamsMap(p map[string]interface{}) *SearchBuilder {
|
||||
if b.options.Params == nil {
|
||||
b.options.Params = make(map[string]interface{}, len(p))
|
||||
}
|
||||
for k, v := range p {
|
||||
b.options.Params[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Dialect sets DIALECT <version>.
|
||||
func (b *SearchBuilder) Dialect(version int) *SearchBuilder {
|
||||
b.options.DialectVersion = version
|
||||
return b
|
||||
}
|
||||
|
||||
// Limit sets OFFSET and COUNT. CountOnly uses LIMIT 0 0.
|
||||
func (b *SearchBuilder) Limit(offset, count int) *SearchBuilder {
|
||||
b.options.LimitOffset = offset
|
||||
b.options.Limit = count
|
||||
return b
|
||||
}
|
||||
func (b *SearchBuilder) CountOnly() *SearchBuilder { b.options.CountOnly = true; return b }
|
||||
|
||||
// Run executes FT.SEARCH and returns a typed result.
|
||||
func (b *SearchBuilder) Run() (FTSearchResult, error) {
|
||||
cmd := b.c.FTSearchWithArgs(b.ctx, b.index, b.query, b.options)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// AggregateBuilder for FT.AGGREGATE
|
||||
// ----------------------
|
||||
|
||||
type AggregateBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
query string
|
||||
options *FTAggregateOptions
|
||||
}
|
||||
|
||||
// NewAggregateBuilder creates a new AggregateBuilder for FT.AGGREGATE commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewAggregateBuilder(ctx context.Context, index, query string) *AggregateBuilder {
|
||||
return &AggregateBuilder{c: c, ctx: ctx, index: index, query: query, options: &FTAggregateOptions{LimitOffset: -1}}
|
||||
}
|
||||
|
||||
// Verbatim includes VERBATIM.
|
||||
func (b *AggregateBuilder) Verbatim() *AggregateBuilder { b.options.Verbatim = true; return b }
|
||||
|
||||
// AddScores includes ADDSCORES.
|
||||
func (b *AggregateBuilder) AddScores() *AggregateBuilder { b.options.AddScores = true; return b }
|
||||
|
||||
// Scorer sets SCORER <scorer>.
|
||||
func (b *AggregateBuilder) Scorer(s string) *AggregateBuilder {
|
||||
b.options.Scorer = s
|
||||
return b
|
||||
}
|
||||
|
||||
// LoadAll includes LOAD * (mutually exclusive with Load).
|
||||
func (b *AggregateBuilder) LoadAll() *AggregateBuilder {
|
||||
b.options.LoadAll = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Load adds LOAD <n> <field> [AS alias]...
|
||||
// You can call it multiple times for multiple fields.
|
||||
func (b *AggregateBuilder) Load(field string, alias ...string) *AggregateBuilder {
|
||||
// each Load entry becomes one element in options.Load
|
||||
l := FTAggregateLoad{Field: field}
|
||||
if len(alias) > 0 {
|
||||
l.As = alias[0]
|
||||
}
|
||||
b.options.Load = append(b.options.Load, l)
|
||||
return b
|
||||
}
|
||||
|
||||
// Timeout sets TIMEOUT <ms>.
|
||||
func (b *AggregateBuilder) Timeout(ms int) *AggregateBuilder {
|
||||
b.options.Timeout = ms
|
||||
return b
|
||||
}
|
||||
|
||||
// Apply adds APPLY <field> [AS alias].
|
||||
func (b *AggregateBuilder) Apply(field string, alias ...string) *AggregateBuilder {
|
||||
a := FTAggregateApply{Field: field}
|
||||
if len(alias) > 0 {
|
||||
a.As = alias[0]
|
||||
}
|
||||
b.options.Apply = append(b.options.Apply, a)
|
||||
return b
|
||||
}
|
||||
|
||||
// GroupBy starts a new GROUPBY <fields...> clause.
|
||||
func (b *AggregateBuilder) GroupBy(fields ...interface{}) *AggregateBuilder {
|
||||
b.options.GroupBy = append(b.options.GroupBy, FTAggregateGroupBy{
|
||||
Fields: fields,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// Reduce adds a REDUCE <fn> [<#args> <args...>] clause to the *last* GROUPBY.
|
||||
func (b *AggregateBuilder) Reduce(fn SearchAggregator, args ...interface{}) *AggregateBuilder {
|
||||
if len(b.options.GroupBy) == 0 {
|
||||
// no GROUPBY yet — nothing to attach to
|
||||
return b
|
||||
}
|
||||
idx := len(b.options.GroupBy) - 1
|
||||
b.options.GroupBy[idx].Reduce = append(b.options.GroupBy[idx].Reduce, FTAggregateReducer{
|
||||
Reducer: fn,
|
||||
Args: args,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// ReduceAs does the same but also sets an alias: REDUCE <fn> … AS <alias>
|
||||
func (b *AggregateBuilder) ReduceAs(fn SearchAggregator, alias string, args ...interface{}) *AggregateBuilder {
|
||||
if len(b.options.GroupBy) == 0 {
|
||||
return b
|
||||
}
|
||||
idx := len(b.options.GroupBy) - 1
|
||||
b.options.GroupBy[idx].Reduce = append(b.options.GroupBy[idx].Reduce, FTAggregateReducer{
|
||||
Reducer: fn,
|
||||
Args: args,
|
||||
As: alias,
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// SortBy adds SORTBY <field> ASC|DESC.
|
||||
func (b *AggregateBuilder) SortBy(field string, asc bool) *AggregateBuilder {
|
||||
sb := FTAggregateSortBy{FieldName: field, Asc: asc, Desc: !asc}
|
||||
b.options.SortBy = append(b.options.SortBy, sb)
|
||||
return b
|
||||
}
|
||||
|
||||
// SortByMax sets MAX <n> (only if SortBy was called).
|
||||
func (b *AggregateBuilder) SortByMax(max int) *AggregateBuilder {
|
||||
b.options.SortByMax = max
|
||||
return b
|
||||
}
|
||||
|
||||
// Filter sets FILTER <expr>.
|
||||
func (b *AggregateBuilder) Filter(expr string) *AggregateBuilder {
|
||||
b.options.Filter = expr
|
||||
return b
|
||||
}
|
||||
|
||||
// WithCursor enables WITHCURSOR [COUNT <n>] [MAXIDLE <ms>].
|
||||
func (b *AggregateBuilder) WithCursor(count, maxIdle int) *AggregateBuilder {
|
||||
b.options.WithCursor = true
|
||||
if b.options.WithCursorOptions == nil {
|
||||
b.options.WithCursorOptions = &FTAggregateWithCursor{}
|
||||
}
|
||||
b.options.WithCursorOptions.Count = count
|
||||
b.options.WithCursorOptions.MaxIdle = maxIdle
|
||||
return b
|
||||
}
|
||||
|
||||
// Params adds PARAMS <k v> pairs.
|
||||
func (b *AggregateBuilder) Params(p map[string]interface{}) *AggregateBuilder {
|
||||
if b.options.Params == nil {
|
||||
b.options.Params = make(map[string]interface{}, len(p))
|
||||
}
|
||||
for k, v := range p {
|
||||
b.options.Params[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Dialect sets DIALECT <version>.
|
||||
func (b *AggregateBuilder) Dialect(version int) *AggregateBuilder {
|
||||
b.options.DialectVersion = version
|
||||
return b
|
||||
}
|
||||
|
||||
// Run executes FT.AGGREGATE and returns a typed result.
|
||||
func (b *AggregateBuilder) Run() (*FTAggregateResult, error) {
|
||||
cmd := b.c.FTAggregateWithArgs(b.ctx, b.index, b.query, b.options)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// CreateIndexBuilder for FT.CREATE
|
||||
// ----------------------
|
||||
// CreateIndexBuilder is builder for FT.CREATE
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type CreateIndexBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
options *FTCreateOptions
|
||||
schema []*FieldSchema
|
||||
}
|
||||
|
||||
// NewCreateIndexBuilder creates a new CreateIndexBuilder for FT.CREATE commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewCreateIndexBuilder(ctx context.Context, index string) *CreateIndexBuilder {
|
||||
return &CreateIndexBuilder{c: c, ctx: ctx, index: index, options: &FTCreateOptions{}}
|
||||
}
|
||||
|
||||
// OnHash sets ON HASH.
|
||||
func (b *CreateIndexBuilder) OnHash() *CreateIndexBuilder { b.options.OnHash = true; return b }
|
||||
|
||||
// OnJSON sets ON JSON.
|
||||
func (b *CreateIndexBuilder) OnJSON() *CreateIndexBuilder { b.options.OnJSON = true; return b }
|
||||
|
||||
// Prefix sets PREFIX.
|
||||
func (b *CreateIndexBuilder) Prefix(prefixes ...interface{}) *CreateIndexBuilder {
|
||||
b.options.Prefix = prefixes
|
||||
return b
|
||||
}
|
||||
|
||||
// Filter sets FILTER.
|
||||
func (b *CreateIndexBuilder) Filter(filter string) *CreateIndexBuilder {
|
||||
b.options.Filter = filter
|
||||
return b
|
||||
}
|
||||
|
||||
// DefaultLanguage sets LANGUAGE.
|
||||
func (b *CreateIndexBuilder) DefaultLanguage(lang string) *CreateIndexBuilder {
|
||||
b.options.DefaultLanguage = lang
|
||||
return b
|
||||
}
|
||||
|
||||
// LanguageField sets LANGUAGE_FIELD.
|
||||
func (b *CreateIndexBuilder) LanguageField(field string) *CreateIndexBuilder {
|
||||
b.options.LanguageField = field
|
||||
return b
|
||||
}
|
||||
|
||||
// Score sets SCORE.
|
||||
func (b *CreateIndexBuilder) Score(score float64) *CreateIndexBuilder {
|
||||
b.options.Score = score
|
||||
return b
|
||||
}
|
||||
|
||||
// ScoreField sets SCORE_FIELD.
|
||||
func (b *CreateIndexBuilder) ScoreField(field string) *CreateIndexBuilder {
|
||||
b.options.ScoreField = field
|
||||
return b
|
||||
}
|
||||
|
||||
// PayloadField sets PAYLOAD_FIELD.
|
||||
func (b *CreateIndexBuilder) PayloadField(field string) *CreateIndexBuilder {
|
||||
b.options.PayloadField = field
|
||||
return b
|
||||
}
|
||||
|
||||
// NoOffsets includes NOOFFSETS.
|
||||
func (b *CreateIndexBuilder) NoOffsets() *CreateIndexBuilder { b.options.NoOffsets = true; return b }
|
||||
|
||||
// Temporary sets TEMPORARY seconds.
|
||||
func (b *CreateIndexBuilder) Temporary(sec int) *CreateIndexBuilder {
|
||||
b.options.Temporary = sec
|
||||
return b
|
||||
}
|
||||
|
||||
// NoHL includes NOHL.
|
||||
func (b *CreateIndexBuilder) NoHL() *CreateIndexBuilder { b.options.NoHL = true; return b }
|
||||
|
||||
// NoFields includes NOFIELDS.
|
||||
func (b *CreateIndexBuilder) NoFields() *CreateIndexBuilder { b.options.NoFields = true; return b }
|
||||
|
||||
// NoFreqs includes NOFREQS.
|
||||
func (b *CreateIndexBuilder) NoFreqs() *CreateIndexBuilder { b.options.NoFreqs = true; return b }
|
||||
|
||||
// StopWords sets STOPWORDS.
|
||||
func (b *CreateIndexBuilder) StopWords(words ...interface{}) *CreateIndexBuilder {
|
||||
b.options.StopWords = words
|
||||
return b
|
||||
}
|
||||
|
||||
// SkipInitialScan includes SKIPINITIALSCAN.
|
||||
func (b *CreateIndexBuilder) SkipInitialScan() *CreateIndexBuilder {
|
||||
b.options.SkipInitialScan = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Schema adds a FieldSchema.
|
||||
func (b *CreateIndexBuilder) Schema(field *FieldSchema) *CreateIndexBuilder {
|
||||
b.schema = append(b.schema, field)
|
||||
return b
|
||||
}
|
||||
|
||||
// Run executes FT.CREATE and returns the status.
|
||||
func (b *CreateIndexBuilder) Run() (string, error) {
|
||||
cmd := b.c.FTCreate(b.ctx, b.index, b.options, b.schema...)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// DropIndexBuilder for FT.DROPINDEX
|
||||
// ----------------------
|
||||
// DropIndexBuilder is a builder for FT.DROPINDEX
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type DropIndexBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
options *FTDropIndexOptions
|
||||
}
|
||||
|
||||
// NewDropIndexBuilder creates a new DropIndexBuilder for FT.DROPINDEX commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewDropIndexBuilder(ctx context.Context, index string) *DropIndexBuilder {
|
||||
return &DropIndexBuilder{c: c, ctx: ctx, index: index}
|
||||
}
|
||||
|
||||
// DeleteRuncs includes DD.
|
||||
func (b *DropIndexBuilder) DeleteDocs() *DropIndexBuilder { b.options.DeleteDocs = true; return b }
|
||||
|
||||
// Run executes FT.DROPINDEX.
|
||||
func (b *DropIndexBuilder) Run() (string, error) {
|
||||
cmd := b.c.FTDropIndexWithArgs(b.ctx, b.index, b.options)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// AliasBuilder for FT.ALIAS* commands
|
||||
// ----------------------
|
||||
// AliasBuilder is builder for FT.ALIAS* commands
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type AliasBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
alias string
|
||||
index string
|
||||
action string // add|del|update
|
||||
}
|
||||
|
||||
// NewAliasBuilder creates a new AliasBuilder for FT.ALIAS* commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewAliasBuilder(ctx context.Context, alias string) *AliasBuilder {
|
||||
return &AliasBuilder{c: c, ctx: ctx, alias: alias}
|
||||
}
|
||||
|
||||
// Action sets the action for the alias builder.
|
||||
func (b *AliasBuilder) Action(action string) *AliasBuilder {
|
||||
b.action = action
|
||||
return b
|
||||
}
|
||||
|
||||
// Add sets the action to "add" and requires an index.
|
||||
func (b *AliasBuilder) Add(index string) *AliasBuilder {
|
||||
b.action = "add"
|
||||
b.index = index
|
||||
return b
|
||||
}
|
||||
|
||||
// Del sets the action to "del".
|
||||
func (b *AliasBuilder) Del() *AliasBuilder {
|
||||
b.action = "del"
|
||||
return b
|
||||
}
|
||||
|
||||
// Update sets the action to "update" and requires an index.
|
||||
func (b *AliasBuilder) Update(index string) *AliasBuilder {
|
||||
b.action = "update"
|
||||
b.index = index
|
||||
return b
|
||||
}
|
||||
|
||||
// Run executes the configured alias command.
|
||||
func (b *AliasBuilder) Run() (string, error) {
|
||||
switch b.action {
|
||||
case "add":
|
||||
cmd := b.c.FTAliasAdd(b.ctx, b.index, b.alias)
|
||||
return cmd.Result()
|
||||
case "del":
|
||||
cmd := b.c.FTAliasDel(b.ctx, b.alias)
|
||||
return cmd.Result()
|
||||
case "update":
|
||||
cmd := b.c.FTAliasUpdate(b.ctx, b.index, b.alias)
|
||||
return cmd.Result()
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// ExplainBuilder for FT.EXPLAIN
|
||||
// ----------------------
|
||||
// ExplainBuilder is builder for FT.EXPLAIN
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type ExplainBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
query string
|
||||
options *FTExplainOptions
|
||||
}
|
||||
|
||||
// NewExplainBuilder creates a new ExplainBuilder for FT.EXPLAIN commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewExplainBuilder(ctx context.Context, index, query string) *ExplainBuilder {
|
||||
return &ExplainBuilder{c: c, ctx: ctx, index: index, query: query, options: &FTExplainOptions{}}
|
||||
}
|
||||
|
||||
// Dialect sets dialect for EXPLAINCLI.
|
||||
func (b *ExplainBuilder) Dialect(d string) *ExplainBuilder { b.options.Dialect = d; return b }
|
||||
|
||||
// Run executes FT.EXPLAIN and returns the plan.
|
||||
func (b *ExplainBuilder) Run() (string, error) {
|
||||
cmd := b.c.FTExplainWithArgs(b.ctx, b.index, b.query, b.options)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// InfoBuilder for FT.INFO
|
||||
// ----------------------
|
||||
|
||||
type FTInfoBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
}
|
||||
|
||||
// NewSearchInfoBuilder creates a new FTInfoBuilder for FT.INFO commands.
|
||||
func (c *Client) NewSearchInfoBuilder(ctx context.Context, index string) *FTInfoBuilder {
|
||||
return &FTInfoBuilder{c: c, ctx: ctx, index: index}
|
||||
}
|
||||
|
||||
// Run executes FT.INFO and returns detailed info.
|
||||
func (b *FTInfoBuilder) Run() (FTInfoResult, error) {
|
||||
cmd := b.c.FTInfo(b.ctx, b.index)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// SpellCheckBuilder for FT.SPELLCHECK
|
||||
// ----------------------
|
||||
// SpellCheckBuilder is builder for FT.SPELLCHECK
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type SpellCheckBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
query string
|
||||
options *FTSpellCheckOptions
|
||||
}
|
||||
|
||||
// NewSpellCheckBuilder creates a new SpellCheckBuilder for FT.SPELLCHECK commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewSpellCheckBuilder(ctx context.Context, index, query string) *SpellCheckBuilder {
|
||||
return &SpellCheckBuilder{c: c, ctx: ctx, index: index, query: query, options: &FTSpellCheckOptions{}}
|
||||
}
|
||||
|
||||
// Distance sets MAXDISTANCE.
|
||||
func (b *SpellCheckBuilder) Distance(d int) *SpellCheckBuilder { b.options.Distance = d; return b }
|
||||
|
||||
// Terms sets INCLUDE or EXCLUDE terms.
|
||||
func (b *SpellCheckBuilder) Terms(include bool, dictionary string, terms ...interface{}) *SpellCheckBuilder {
|
||||
if b.options.Terms == nil {
|
||||
b.options.Terms = &FTSpellCheckTerms{}
|
||||
}
|
||||
if include {
|
||||
b.options.Terms.Inclusion = "INCLUDE"
|
||||
} else {
|
||||
b.options.Terms.Inclusion = "EXCLUDE"
|
||||
}
|
||||
b.options.Terms.Dictionary = dictionary
|
||||
b.options.Terms.Terms = terms
|
||||
return b
|
||||
}
|
||||
|
||||
// Dialect sets dialect version.
|
||||
func (b *SpellCheckBuilder) Dialect(d int) *SpellCheckBuilder { b.options.Dialect = d; return b }
|
||||
|
||||
// Run executes FT.SPELLCHECK and returns suggestions.
|
||||
func (b *SpellCheckBuilder) Run() ([]SpellCheckResult, error) {
|
||||
cmd := b.c.FTSpellCheckWithArgs(b.ctx, b.index, b.query, b.options)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// DictBuilder for FT.DICT* commands
|
||||
// ----------------------
|
||||
// DictBuilder is builder for FT.DICT* commands
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type DictBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
dict string
|
||||
terms []interface{}
|
||||
action string // add|del|dump
|
||||
}
|
||||
|
||||
// NewDictBuilder creates a new DictBuilder for FT.DICT* commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewDictBuilder(ctx context.Context, dict string) *DictBuilder {
|
||||
return &DictBuilder{c: c, ctx: ctx, dict: dict}
|
||||
}
|
||||
|
||||
// Action sets the action for the dictionary builder.
|
||||
func (b *DictBuilder) Action(action string) *DictBuilder {
|
||||
b.action = action
|
||||
return b
|
||||
}
|
||||
|
||||
// Add sets the action to "add" and requires terms.
|
||||
func (b *DictBuilder) Add(terms ...interface{}) *DictBuilder {
|
||||
b.action = "add"
|
||||
b.terms = terms
|
||||
return b
|
||||
}
|
||||
|
||||
// Del sets the action to "del" and requires terms.
|
||||
func (b *DictBuilder) Del(terms ...interface{}) *DictBuilder {
|
||||
b.action = "del"
|
||||
b.terms = terms
|
||||
return b
|
||||
}
|
||||
|
||||
// Dump sets the action to "dump".
|
||||
func (b *DictBuilder) Dump() *DictBuilder {
|
||||
b.action = "dump"
|
||||
return b
|
||||
}
|
||||
|
||||
// Run executes the configured dictionary command.
|
||||
func (b *DictBuilder) Run() (interface{}, error) {
|
||||
switch b.action {
|
||||
case "add":
|
||||
cmd := b.c.FTDictAdd(b.ctx, b.dict, b.terms...)
|
||||
return cmd.Result()
|
||||
case "del":
|
||||
cmd := b.c.FTDictDel(b.ctx, b.dict, b.terms...)
|
||||
return cmd.Result()
|
||||
case "dump":
|
||||
cmd := b.c.FTDictDump(b.ctx, b.dict)
|
||||
return cmd.Result()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// TagValsBuilder for FT.TAGVALS
|
||||
// ----------------------
|
||||
// TagValsBuilder is builder for FT.TAGVALS
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type TagValsBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
field string
|
||||
}
|
||||
|
||||
// NewTagValsBuilder creates a new TagValsBuilder for FT.TAGVALS commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewTagValsBuilder(ctx context.Context, index, field string) *TagValsBuilder {
|
||||
return &TagValsBuilder{c: c, ctx: ctx, index: index, field: field}
|
||||
}
|
||||
|
||||
// Run executes FT.TAGVALS and returns tag values.
|
||||
func (b *TagValsBuilder) Run() ([]string, error) {
|
||||
cmd := b.c.FTTagVals(b.ctx, b.index, b.field)
|
||||
return cmd.Result()
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// CursorBuilder for FT.CURSOR*
|
||||
// ----------------------
|
||||
// CursorBuilder is builder for FT.CURSOR* commands
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type CursorBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
cursorId int64
|
||||
count int
|
||||
action string // read|del
|
||||
}
|
||||
|
||||
// NewCursorBuilder creates a new CursorBuilder for FT.CURSOR* commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewCursorBuilder(ctx context.Context, index string, cursorId int64) *CursorBuilder {
|
||||
return &CursorBuilder{c: c, ctx: ctx, index: index, cursorId: cursorId}
|
||||
}
|
||||
|
||||
// Action sets the action for the cursor builder.
|
||||
func (b *CursorBuilder) Action(action string) *CursorBuilder {
|
||||
b.action = action
|
||||
return b
|
||||
}
|
||||
|
||||
// Read sets the action to "read".
|
||||
func (b *CursorBuilder) Read() *CursorBuilder {
|
||||
b.action = "read"
|
||||
return b
|
||||
}
|
||||
|
||||
// Del sets the action to "del".
|
||||
func (b *CursorBuilder) Del() *CursorBuilder {
|
||||
b.action = "del"
|
||||
return b
|
||||
}
|
||||
|
||||
// Count for READ.
|
||||
func (b *CursorBuilder) Count(count int) *CursorBuilder { b.count = count; return b }
|
||||
|
||||
// Run executes the cursor command.
|
||||
func (b *CursorBuilder) Run() (interface{}, error) {
|
||||
switch b.action {
|
||||
case "read":
|
||||
cmd := b.c.FTCursorRead(b.ctx, b.index, int(b.cursorId), b.count)
|
||||
return cmd.Result()
|
||||
case "del":
|
||||
cmd := b.c.FTCursorDel(b.ctx, b.index, int(b.cursorId))
|
||||
return cmd.Result()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// SynUpdateBuilder for FT.SYNUPDATE
|
||||
// ----------------------
|
||||
// SyncUpdateBuilder is builder for FT.SYNCUPDATE
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
type SynUpdateBuilder struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
index string
|
||||
groupId interface{}
|
||||
options *FTSynUpdateOptions
|
||||
terms []interface{}
|
||||
}
|
||||
|
||||
// NewSynUpdateBuilder creates a new SynUpdateBuilder for FT.SYNUPDATE commands.
|
||||
// EXPERIMENTAL: this API is subject to change, use with caution.
|
||||
func (c *Client) NewSynUpdateBuilder(ctx context.Context, index string, groupId interface{}) *SynUpdateBuilder {
|
||||
return &SynUpdateBuilder{c: c, ctx: ctx, index: index, groupId: groupId, options: &FTSynUpdateOptions{}}
|
||||
}
|
||||
|
||||
// SkipInitialScan includes SKIPINITIALSCAN.
|
||||
func (b *SynUpdateBuilder) SkipInitialScan() *SynUpdateBuilder {
|
||||
b.options.SkipInitialScan = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Terms adds synonyms to the group.
|
||||
func (b *SynUpdateBuilder) Terms(terms ...interface{}) *SynUpdateBuilder { b.terms = terms; return b }
|
||||
|
||||
// Run executes FT.SYNUPDATE.
|
||||
func (b *SynUpdateBuilder) Run() (string, error) {
|
||||
cmd := b.c.FTSynUpdateWithArgs(b.ctx, b.index, b.groupId, b.options, b.terms)
|
||||
return cmd.Result()
|
||||
}
|
680
search_builders_test.go
Normal file
680
search_builders_test.go
Normal file
@@ -0,0 +1,680 @@
|
||||
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))
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user