Adding more tests and fixing commands

This commit is contained in:
ofekshenawa 2023-11-17 00:06:45 +02:00
parent 8642634488
commit aa69e5ebec
5 changed files with 2363 additions and 1425 deletions

View File

@ -221,6 +221,7 @@ type Cmdable interface {
ProbabilisticCmdable
PubSubCmdable
ScriptingFunctionsCmdable
SearchCmdable
SetCmdable
SortedSetCmdable
StringCmdable

View File

@ -1,858 +0,0 @@
package redis
import (
"context"
)
type SearchCmdable interface {
}
type FTCreateOptions struct {
OnHash bool
OnJSON bool
Prefix []interface{}
Filter string
DefaultLanguage string
LanguageField string
Score float64
ScoreField string
PayloadField string
MaxTextFields int
NoOffsets bool
Temporary int
NoHL bool
NoFields bool
NoFreqs bool
StopWords []interface{}
SkipInitalScan bool
}
type SearchSchema struct {
Identifier string
Attribute string
AttributeType string
Sortable bool
UNF bool
NoStem bool
NoIndex bool
PhoneticMatcher string
Weight float64
Seperator string
CaseSensitive bool
WithSuffix bool
}
type FTDropIndexOptions struct {
DeleteDocs bool
}
type SpellCheckTerms struct {
Include bool
Exclude bool
Dictionary string
}
type FTSpellCheckOptions struct {
Distance int
Terms SpellCheckTerms
Dialect int
}
type FTExplainOptions struct {
Dialect string
}
type FTSynUpdateOptions struct {
SkipInitialScan bool
}
type SearchAggregator int
const (
SearchInvalid = SearchAggregator(iota)
SearchAvg
SearchSum
SearchMin
SearchMax
SearchCount
SearchCountDistinct
SearchCountDistinctish
SearchStdDev
SearchQuantile
SearchToList
SearchFirstValue
SearchRandomSample
)
func (a SearchAggregator) String() string {
switch a {
case SearchInvalid:
return ""
case SearchAvg:
return "AVG"
case SearchSum:
return "SUM"
case SearchMin:
return "MIN"
case SearchMax:
return "MAX"
case SearchCount:
return "COUNT"
case SearchCountDistinct:
return "COUNT_DISTINCT"
case SearchCountDistinctish:
return "COUNT_DISTINCTISH"
case SearchStdDev:
return "STDDEV"
case SearchQuantile:
return "QUANTILE"
case SearchToList:
return "TOLIST"
case SearchFirstValue:
return "FIRST_VALUE"
case SearchRandomSample:
return "RANDOM_SAMPLE"
default:
return ""
}
}
// Each AggregateReducer have different args.
// Please follow https://redis.io/docs/interact/search-and-query/search/aggregations/#supported-groupby-reducers for more information.
type FTAggregateReducer struct {
Reducer SearchAggregator
Args []interface{}
As string
}
type FTAggregateGroupBy struct {
Fields []interface{}
Reduce []FTAggregateReducer
}
type FTAggregateSortBy struct {
FieldName string
Asc bool
Desc bool
}
type FTAggregateApply struct {
Field string
As string
}
type FTAggregateLoad struct {
Field string
As string
}
type FTAggregateWithCursor struct {
Count int
MaxIdle int
}
type FTAggregateOptions struct {
Verbatim bool
LoadAll bool
Load []FTAggregateLoad
Timeout int
GroupBy []FTAggregateGroupBy
SortBy []FTAggregateSortBy
SortByMax int
Apply []FTAggregateApply
LimitOffset int
Limit int
Filter string
WithCursor bool
WithCursorOptions *FTAggregateWithCursor
Params map[string]interface{}
DialectVersion int
}
type FTSearchFilter struct {
FieldName interface{}
Min interface{}
Max interface{}
}
type FTSearchGeoFilter struct {
FieldName string
Longitude float64
Latitude float64
Radius float64
Unit string
}
type FTSearchReturn struct {
FieldName string
As string
}
type FTSearchSortBy struct {
FieldName string
Asc bool
Desc bool
}
type FTSearchOptions struct {
NoContent bool
Verbatim bool
NoStopWrods bool
WithScores bool
WithPayloads bool
WithSortKeys bool
Filters []FTSearchFilter
GeoFilter []FTSearchGeoFilter
InKeys []interface{}
InFields []interface{}
Return []FTSearchReturn
Slop int
Timeout int
InOrder bool
Language string
Expander string
Scorer string
ExplainScore bool
Payload string
SortBy []FTSearchSortBy
SortByWithCount bool
LimitOffset int
Limit int
Params map[string]interface{}
DialectVersion int
}
func (c cmdable) FT_List(ctx context.Context) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, "FT._LIST")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAggregate(ctx context.Context, index string, query string) *MapStringInterfaceCmd {
args := []interface{}{"FT.AGGREGATE", index, query}
cmd := NewMapStringInterfaceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAggregateWithArgs(ctx context.Context, index string, query string, options *FTAggregateOptions) *MapStringInterfaceCmd {
args := []interface{}{"FT.AGGREGATE", index, query}
if options != nil {
if options.Verbatim {
args = append(args, "VERBATIM")
}
if options.LoadAll && options.Load != nil {
panic("FT.AGGREGATE: LOADALL and LOAD are mutually exclusive")
}
if options.LoadAll {
args = append(args, "LOAD", "*")
}
if options.Load != nil {
args = append(args, "LOAD", len(options.Load))
for _, load := range options.Load {
args = append(args, load.Field)
if load.As != "" {
args = append(args, "AS", load.As)
}
}
}
if options.Timeout > 0 {
args = append(args, "TIMEOUT", options.Timeout)
}
if options.GroupBy != nil {
for _, groupBy := range options.GroupBy {
args = append(args, "GROUPBY", len(groupBy.Fields))
args = append(args, groupBy.Fields...)
for _, reducer := range groupBy.Reduce {
args = append(args, "REDUCE")
args = append(args, reducer.Reducer.String())
if reducer.Args != nil {
args = append(args, len(reducer.Args))
args = append(args, reducer.Args...)
} else {
args = append(args, 0)
}
if reducer.As != "" {
args = append(args, "AS", reducer.As)
}
}
}
}
if options.SortBy != nil {
args = append(args, "SORTBY")
sortByOptions := []interface{}{}
for _, sortBy := range options.SortBy {
sortByOptions = append(sortByOptions, sortBy.FieldName)
if sortBy.Asc && sortBy.Desc {
panic("FT.AGGREGATE: ASC and DESC are mutually exclusive")
}
if sortBy.Asc {
sortByOptions = append(sortByOptions, "ASC")
}
if sortBy.Desc {
sortByOptions = append(sortByOptions, "DESC")
}
}
args = append(args, len(sortByOptions))
args = append(args, sortByOptions...)
}
if options.SortByMax > 0 {
args = append(args, "MAX", options.SortByMax)
}
if options.Apply != nil {
args = append(args, "APPLY", len(options.Apply))
for _, apply := range options.Apply {
args = append(args, apply.Field)
if apply.As != "" {
args = append(args, "AS", apply.As)
}
}
}
if options.LimitOffset > 0 {
args = append(args, "LIMIT", options.LimitOffset)
}
if options.Limit > 0 {
args = append(args, options.Limit)
}
if options.Filter != "" {
args = append(args, "FILTER", options.Filter)
}
if options.WithCursor {
args = append(args, "WITHCURSOR")
if options.WithCursorOptions != nil {
if options.WithCursorOptions.Count > 0 {
args = append(args, "COUNT", options.WithCursorOptions.Count)
}
if options.WithCursorOptions.MaxIdle > 0 {
args = append(args, "MAXIDLE", options.WithCursorOptions.MaxIdle)
}
}
}
if options.Params != nil {
args = append(args, "PARAMS", len(options.Params)*2)
for key, value := range options.Params {
args = append(args, key, value)
}
}
if options.DialectVersion > 0 {
args = append(args, "DIALECT", options.DialectVersion)
}
}
cmd := NewMapStringInterfaceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAliasAdd(ctx context.Context, index string, alias string) *StatusCmd {
args := []interface{}{"FT.ALIASADD", alias, index}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAliasDel(ctx context.Context, alias string) *StatusCmd {
cmd := NewStatusCmd(ctx, "FT.ALIASDEL", alias)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAliasUpdate(ctx context.Context, index string, alias string) *StatusCmd {
cmd := NewStatusCmd(ctx, "FT.ALIASUPDATE", alias, index)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTAlter(ctx context.Context, index string, skipInitalScan bool, definition []interface{}) *StatusCmd {
args := []interface{}{"FT.ALTER", index}
if skipInitalScan {
args = append(args, "SKIPINITIALSCAN")
}
args = append(args, "SCHEMA", "ADD")
args = append(args, definition...)
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTConfigGet(ctx context.Context, option string) *MapStringInterfaceCmd {
cmd := NewMapStringInterfaceCmd(ctx, "FT.CONFIG", "GET", option)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTConfigSet(ctx context.Context, option string, value interface{}) *StatusCmd {
cmd := NewStatusCmd(ctx, "FT.CONFIG", "SET", option, value)
_ = c(ctx, cmd)
return cmd
}
// TODO Fix schema for loop
func (c cmdable) FTCreate(ctx context.Context, index string, options *FTCreateOptions, schema ...*SearchSchema) *StatusCmd {
args := []interface{}{"FT.CREATE", index}
if options != nil {
if options.OnHash && !options.OnJSON {
args = append(args, "ON", "HASH")
}
if options.OnJSON && !options.OnHash {
args = append(args, "ON", "JSON")
}
if options.OnHash && options.OnJSON {
panic("FT.CREATE: ON HASH and ON JSON are mutually exclusive")
}
if options.Prefix != nil {
args = append(args, "PREFIX", len(options.Prefix))
args = append(args, options.Prefix...)
}
if options.Filter != "" {
args = append(args, "FILTER", options.Filter)
}
if options.DefaultLanguage != "" {
args = append(args, "LANGUAGE", options.DefaultLanguage)
}
if options.LanguageField != "" {
args = append(args, "LANGUAGE_FIELD", options.LanguageField)
}
if options.Score > 0 {
args = append(args, "SCORE", options.Score)
}
if options.ScoreField != "" {
args = append(args, "SCORE_FIELD", options.ScoreField)
}
if options.PayloadField != "" {
args = append(args, "PAYLOAD_FIELD", options.PayloadField)
}
if options.MaxTextFields > 0 {
args = append(args, "MAXTEXTFIELDS", options.MaxTextFields)
}
if options.NoOffsets {
args = append(args, "NOOFFSETS")
}
if options.Temporary > 0 {
args = append(args, "TEMPORARY", options.Temporary)
}
if options.NoHL {
args = append(args, "NOHL")
}
if options.NoFields {
args = append(args, "NOFIELDS")
}
if options.NoFreqs {
args = append(args, "NOFREQS")
}
if options.StopWords != nil {
args = append(args, "STOPWORDS", len(options.StopWords))
args = append(args, options.StopWords...)
}
if options.SkipInitalScan {
args = append(args, "SKIPINITIALSCAN")
}
}
if schema == nil {
panic("FT.CREATE: SCHEMA is required")
}
args = append(args, "SCHEMA")
for _, schema := range schema {
if schema.Identifier == "" || schema.AttributeType == "" {
panic("FT.CREATE: SCHEMA IDENTIFIER and ATTRIBUTE_TYPE are required")
}
args = append(args, schema.Identifier)
if schema.Attribute != "" {
args = append(args, "AS", schema.Attribute)
}
args = append(args, schema.AttributeType)
if schema.NoStem {
args = append(args, "NOSTEM")
}
if schema.Sortable {
args = append(args, "SORTABLE")
}
if schema.UNF {
args = append(args, "UNF")
}
if schema.NoIndex {
args = append(args, "NOINDEX")
}
if schema.PhoneticMatcher != "" {
args = append(args, "PHONETIC", schema.PhoneticMatcher)
}
if schema.Weight > 0 {
args = append(args, "WEIGHT", schema.Weight)
}
if schema.Seperator != "" {
args = append(args, "SEPERATOR", schema.Seperator)
}
if schema.CaseSensitive {
args = append(args, "CASESENSITIVE")
}
if schema.WithSuffix {
args = append(args, "WITHSUFFIX")
}
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTCursorDel(ctx context.Context, index string, cursorId int) *StatusCmd {
cmd := NewStatusCmd(ctx, "FT.CURSOR", "DEL", index, cursorId)
_ = c(ctx, cmd)
return cmd
}
// TODO TEST IT
func (c cmdable) FTCursorRead(ctx context.Context, index string, cursorId int, count int) *MapStringInterfaceCmd {
args := []interface{}{"FT.CURSOR", "READ", index, cursorId}
if count > 0 {
args = append(args, "COUNT", count)
}
cmd := NewMapStringInterfaceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTDictAdd(ctx context.Context, dict string, term []interface{}) *IntCmd {
args := []interface{}{"FT.DICTADD", dict}
args = append(args, term...)
cmd := NewIntCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTDictDel(ctx context.Context, dict string, term []interface{}) *IntCmd {
args := []interface{}{"FT.DICTDEL", dict}
args = append(args, term...)
cmd := NewIntCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTDictDump(ctx context.Context, dict string) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, "FT.DICTDUMP", dict)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTDropIndex(ctx context.Context, index string) *StatusCmd {
args := []interface{}{"FT.DROPINDEX", index}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTDropIndexWithArgs(ctx context.Context, index string, options *FTDropIndexOptions) *StatusCmd {
args := []interface{}{"FT.DROPINDEX", index}
if options != nil {
if options.DeleteDocs {
args = append(args, "DD")
}
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTExplain(ctx context.Context, index string, query string) *StringCmd {
cmd := NewStringCmd(ctx, "FT.EXPLAIN", index, query)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTExplainWithArgs(ctx context.Context, index string, query string, options *FTExplainOptions) *StringCmd {
args := []interface{}{"FT.EXPLAIN", index, query}
if options.Dialect != "" {
args = append(args, "DIALECT", options.Dialect)
}
cmd := NewStringCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTInfo(ctx context.Context, index string) *MapStringInterfaceCmd {
cmd := NewMapStringInterfaceCmd(ctx, "FT.INFO", index)
_ = c(ctx, cmd)
return cmd
}
// TODO - When ft search is ready
// func (c cmdable) FTProfileSearch(ctx context.Context, index string, limited bool, query string) *StringCmd {
// args := []interface{}{"FT.PROFILE", index, "SEARCH"}
// if limited {
// args = append(args, "LIMITED")
// }
// args = append(args, "QUERY", query)
// cmd := NewStringCmd(ctx, args...)
// _ = c(ctx, cmd)
// return cmd
// }
// func (c cmdable) FTProfileAggregate(ctx context.Context, index string, limited bool, query string) *FTProfileAggregateCmd {
// args := []interface{}{"FT.PROFILE", index, "AGGREGATE"}
// if limited {
// args = append(args, "LIMITED")
// }
// args = append(args, "QUERY", query)
// cmd := newFTProfileAggregateCmd(ctx, args...)
// _ = c(ctx, cmd)
// return cmd
// }
// type FTProfileAggregateResult struct {
// aggregateResult MapStringInterfaceCmd
// profileResult KeyValueSliceCmd
// }
// type FTProfileAggregateCmd struct {
// baseCmd
// val FTProfileAggregateResult
// }
// func newFTProfileAggregateCmd(ctx context.Context, args ...interface{}) *FTProfileAggregateCmd {
// return &FTProfileAggregateCmd{
// baseCmd: baseCmd{
// ctx: ctx,
// args: args,
// },
// }
// }
// func (cmd *FTProfileAggregateCmd) String() string {
// return cmdString(cmd, cmd.val)
// }
// func (cmd *FTProfileAggregateCmd) SetVal(val FTProfileAggregateResult) {
// cmd.val = val
// }
// func (cmd *FTProfileAggregateCmd) Result() (FTProfileAggregateResult, error) {
// return cmd.val, cmd.err
// }
// func (cmd *FTProfileAggregateCmd) Val() FTProfileAggregateResult {
// return cmd.val
// }
// func (cmd *FTProfileAggregateCmd) readReply(rd *proto.Reader) (err error) {
// _, err = rd.ReadArrayLen()
// if err != nil {
// return err
// }
// _, err = rd.ReadArrayLen()
// if err != nil {
// return err
// }
// cmd.val = FTProfileAggregateResult{}
// status, err := rd.ReadInt()
// if err != nil {
// return err
// }
// cmd.val.aggregateResult.Status = status
// nn, err := rd.ReadArrayLen()
// if err != nil {
// return err
// }
// cmd.val.aggregateResult.Fields = make(map[string]string, nn/2)
// for i := 0; i < nn; i++ {
// key, err := rd.ReadString()
// if err != nil {
// return err
// }
// value, err := rd.ReadString()
// if err != nil {
// return err
// }
// cmd.val.aggregateResult.Fields[key] = value
// }
// cmd.val.profileResult = *NewKeyValueSliceCmd(cmd.ctx, cmd.args...)
// return nil
// }
// For more details about spellcheck query please follow:
// https://redis.io/docs/interact/search-and-query/advanced-concepts/spellcheck/
func (c cmdable) FTSpellCheck(ctx context.Context, index string, query string) *MapStringInterfaceCmd {
cmd := NewMapStringInterfaceCmd(ctx, "FT.SPELLCHECK", index, query)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSpellCheckWithArgs(ctx context.Context, index string, query string, options *FTSpellCheckOptions) *MapStringInterfaceCmd {
args := []interface{}{"FT.SPELLCHECK", index, query}
if options != nil {
if options.Distance > 4 {
panic("FT.SPELLCHECK: DISTANCE must be between 0 and 4")
}
if options.Distance > 0 {
args = append(args, "DISTANCE", options.Distance)
}
if options.Terms.Include && options.Terms.Exclude {
panic("FT.SPELLCHECK: INCLUDE and EXCLUDE are mutually exclusive")
}
if options.Terms.Include {
args = append(args, "TERMS", "INCLUDE")
}
if options.Terms.Exclude {
args = append(args, "TERMS", "EXCLUDE")
}
if options.Terms.Dictionary != "" {
args = append(args, options.Terms.Dictionary)
}
if options.Dialect > 0 {
args = append(args, "DIALECT", options.Dialect)
}
}
cmd := NewMapStringInterfaceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSearch(ctx context.Context, index string, query string) *Cmd {
args := []interface{}{"FT.SEARCH", index, query}
cmd := NewCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSearchWithArgs(ctx context.Context, index string, query string, options *FTSearchOptions) *Cmd {
args := []interface{}{"FT.SEARCH", index, query}
if options != nil {
if options.NoContent {
args = append(args, "NOCONTENT")
}
if options.Verbatim {
args = append(args, "VERBATIM")
}
if options.NoStopWrods {
args = append(args, "NOSTOPWORDS")
}
if options.WithScores {
args = append(args, "WITHSCORES")
}
if options.WithPayloads {
args = append(args, "WITHPAYLOADS")
}
if options.WithSortKeys {
args = append(args, "WITHSORTKEYS")
}
if options.Filters != nil {
for _, filter := range options.Filters {
args = append(args, "FILTER", filter.FieldName, filter.Min, filter.Max)
}
}
if options.GeoFilter != nil {
for _, geoFilter := range options.GeoFilter {
args = append(args, "GEOFILTER", geoFilter.FieldName, geoFilter.Longitude, geoFilter.Latitude, geoFilter.Radius, geoFilter.Unit)
}
}
if options.InKeys != nil {
args = append(args, "INKEYS", len(options.InKeys))
args = append(args, options.InKeys...)
}
if options.InFields != nil {
args = append(args, "INFIELDS", len(options.InFields))
args = append(args, options.InFields...)
}
if options.Return != nil {
args = append(args, "RETURN", len(options.Return))
for _, ret := range options.Return {
args = append(args, ret.FieldName)
if ret.As != "" {
args = append(args, "AS", ret.As)
}
}
}
if options.Slop > 0 {
args = append(args, "SLOP", options.Slop)
}
if options.Timeout > 0 {
args = append(args, "TIMEOUT", options.Timeout)
}
if options.InOrder {
args = append(args, "INORDER")
}
if options.Language != "" {
args = append(args, "LANGUAGE", options.Language)
}
if options.Expander != "" {
args = append(args, "EXPANDER", options.Expander)
}
if options.Scorer != "" {
args = append(args, "SCORER", options.Scorer)
}
if options.ExplainScore {
args = append(args, "EXPLAINSCORE")
}
if options.Payload != "" {
args = append(args, "PAYLOAD", options.Payload)
}
if options.SortBy != nil {
args = append(args, "SORTBY")
for _, sortBy := range options.SortBy {
args = append(args, sortBy.FieldName)
if sortBy.Asc && sortBy.Desc {
panic("FT.SEARCH: ASC and DESC are mutually exclusive")
}
if sortBy.Asc {
args = append(args, "ASC")
}
if sortBy.Desc {
args = append(args, "DESC")
}
}
if options.SortByWithCount {
args = append(args, "WITHCOUT")
}
}
if options.LimitOffset >= 0 && options.Limit > 0 {
args = append(args, "LIMIT", options.LimitOffset, options.Limit)
}
if options.Params != nil {
args = append(args, "PARAMS", len(options.Params)*2)
for key, value := range options.Params {
args = append(args, key, value)
}
}
if options.DialectVersion > 0 {
args = append(args, "DIALECT", options.DialectVersion)
}
}
cmd := NewCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSynDump(ctx context.Context, index string) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, "FT.SYNDUMP", index)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSynUpdate(ctx context.Context, index string, synGroupId interface{}, terms []interface{}) *StatusCmd {
args := []interface{}{"FT.SYNUPDATE", index, synGroupId}
args = append(args, terms...)
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTSynUpdateWithArgs(ctx context.Context, index string, synGroupId interface{}, options *FTSynUpdateOptions, terms []interface{}) *StatusCmd {
args := []interface{}{"FT.SYNUPDATE", index, synGroupId}
if options.SkipInitialScan {
args = append(args, "SKIPINITIALSCAN")
}
args = append(args, terms...)
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FTTagVals(ctx context.Context, index string, field string) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, "FT.TAGVALS", index, field)
_ = c(ctx, cmd)
return cmd
}

View File

@ -1,567 +0,0 @@
package redis_test
import (
"context"
. "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega"
"github.com/redis/go-redis/v9"
)
var _ = Describe("RediSearch commands", Label("search"), func() {
ctx := context.TODO()
var client *redis.Client
BeforeEach(func() {
client = redis.NewClient(&redis.Options{Addr: ":6379"})
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(client.Close()).NotTo(HaveOccurred())
})
It("should FTCreate and FTSearch WithScores ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "txt", "foo baz")
client.HSet(ctx, "doc2", "txt", "foo bar")
res, err := client.FTSearchWithArgs(ctx, "txt", "foo ~bar", &redis.FTSearchOptions{WithScores: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult := res.(map[interface{}]interface{})
Expect(searchResult["total_results"]).To(BeEquivalentTo(int64(2)))
Expect(searchResult["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc2"))
Expect(searchResult["results"].([]interface{})[0].(map[interface{}]interface{})["score"]).To(BeEquivalentTo(float64(3.0)))
Expect(searchResult["results"].([]interface{})[1].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
})
It("should FTCreate and FTSearch stopwords ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{StopWords: []interface{}{"foo", "bar", "baz"}}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "txt", "foo baz")
client.HSet(ctx, "doc2", "txt", "hello world")
res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult1 := res1.(map[interface{}]interface{})
Expect(searchResult1["total_results"]).To(BeEquivalentTo(int64(0)))
res2, err := client.FTSearchWithArgs(ctx, "txt", "foo bar hello world", &redis.FTSearchOptions{NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult2 := res2.(map[interface{}]interface{})
Expect(searchResult2["total_results"]).To(BeEquivalentTo(int64(1)))
})
It("should FTCreate and FTSearch filters ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT"}, &redis.SearchSchema{Identifier: "num", AttributeType: "NUMERIC"}, &redis.SearchSchema{Identifier: "loc", AttributeType: "GEO"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
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")
res1, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{Filters: []redis.FTSearchFilter{{FieldName: "num", Min: 0, Max: 2}}, NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult1 := res1.(map[interface{}]interface{})
Expect(searchResult1["total_results"]).To(BeEquivalentTo(int64(1)))
Expect(searchResult1["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc2"))
res2, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{Filters: []redis.FTSearchFilter{{FieldName: "num", Min: 0, Max: "+inf"}}, NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult2 := res2.(map[interface{}]interface{})
Expect(searchResult2["total_results"]).To(BeEquivalentTo(int64(2)))
Expect(searchResult2["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
// Test Geo filter
geoFilter1 := redis.FTSearchGeoFilter{FieldName: "loc", Longitude: -0.44, Latitude: 51.45, Radius: 10, Unit: "km"}
geoFilter2 := redis.FTSearchGeoFilter{FieldName: "loc", Longitude: -0.44, Latitude: 51.45, Radius: 100, Unit: "km"}
res3, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{GeoFilter: []redis.FTSearchGeoFilter{geoFilter1}, NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult3 := res3.(map[interface{}]interface{})
Expect(searchResult3["total_results"]).To(BeEquivalentTo(int64(1)))
Expect(searchResult3["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
res4, err := client.FTSearchWithArgs(ctx, "txt", "foo", &redis.FTSearchOptions{GeoFilter: []redis.FTSearchGeoFilter{geoFilter2}, NoContent: true}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult4 := res4.(map[interface{}]interface{})
Expect(searchResult4["total_results"]).To(BeEquivalentTo(int64(2)))
docs := []interface{}{searchResult4["results"].([]interface{})[0].(map[interface{}]interface{})["id"], searchResult4["results"].([]interface{})[1].(map[interface{}]interface{})["id"]}
Expect(docs).To(ContainElement("doc1"))
Expect(docs).To(ContainElement("doc2"))
})
It("should FTCreate and FTSearch sortby ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "num", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT"}, &redis.SearchSchema{Identifier: "num", AttributeType: "NUMERIC", Sortable: true}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
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)
sortBy1 := redis.FTSearchSortBy{FieldName: "num", Asc: true}
sortBy2 := redis.FTSearchSortBy{FieldName: "num", Desc: true}
res1, err := client.FTSearchWithArgs(ctx, "num", "foo", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy1}}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult1 := res1.(map[interface{}]interface{})
Expect(searchResult1["total_results"]).To(BeEquivalentTo(int64(3)))
Expect(searchResult1["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
Expect(searchResult1["results"].([]interface{})[1].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc2"))
Expect(searchResult1["results"].([]interface{})[2].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc3"))
res2, err := client.FTSearchWithArgs(ctx, "num", "foo", &redis.FTSearchOptions{NoContent: true, SortBy: []redis.FTSearchSortBy{sortBy2}}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult2 := res2.(map[interface{}]interface{})
Expect(searchResult2["total_results"]).To(BeEquivalentTo(int64(3)))
Expect(searchResult2["results"].([]interface{})[2].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
Expect(searchResult2["results"].([]interface{})[1].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc2"))
Expect(searchResult2["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc3"))
})
It("should FTCreate and FTSearch example ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "title", AttributeType: "TEXT", Weight: 5}, &redis.SearchSchema{Identifier: "body", AttributeType: "TEXT"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "title", "RediSearch", "body", "Redisearch impements a search engine on top of redis")
res1, err := client.FTSearchWithArgs(ctx, "txt", "search engine", &redis.FTSearchOptions{NoContent: true, Verbatim: true, LimitOffset: 0, Limit: 5}).Result()
Expect(err).NotTo(HaveOccurred())
searchResult1 := res1.(map[interface{}]interface{})
Expect(searchResult1).ToNot(BeEmpty())
})
It("should FTCreate NoIndex ", Label("search", "ftcreate", "ftsearch"), func() {
text1 := &redis.SearchSchema{Identifier: "field", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "text", AttributeType: "TEXT", NoIndex: true, Sortable: true}
num := &redis.SearchSchema{Identifier: "numeric", AttributeType: "NUMERIC", NoIndex: true, Sortable: true}
geo := &redis.SearchSchema{Identifier: "geo", AttributeType: "GEO", NoIndex: true, Sortable: true}
tag := &redis.SearchSchema{Identifier: "tag", AttributeType: "TAG", NoIndex: true, Sortable: true}
val, err := client.FTCreate(ctx, "idx", &redis.FTCreateOptions{}, text1, text2, num, geo, tag).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "field", "aaa", "text", "1", "numeric", 1, "geo", "1,1", "tag", "1")
client.HSet(ctx, "doc2", "field", "aab", "text", "2", "numeric", 2, "geo", "2,2", "tag", "2")
res1, err := client.FTSearch(ctx, "idx", "@text:aa*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(0)))
res2, err := client.FTSearch(ctx, "idx", "@field:aa*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res2.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(2)))
res3, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "text", Desc: true}}}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res3.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(2)))
Expect(res3.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc2"))
res4, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "text", Asc: true}}}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res4.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(2)))
Expect(res4.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
res5, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "numeric", Asc: true}}}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res5.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
res6, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "geo", Asc: true}}}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res6.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
res7, err := client.FTSearchWithArgs(ctx, "idx", "*", &redis.FTSearchOptions{SortBy: []redis.FTSearchSortBy{{FieldName: "tag", Asc: true}}}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res7.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("doc1"))
})
It("should FTExplain ", Label("search", "ftexplain"), func() {
text1 := &redis.SearchSchema{Identifier: "f1", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "f2", AttributeType: "TEXT"}
text3 := &redis.SearchSchema{Identifier: "f3", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, text1, text2, text3).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
res1, err := client.FTExplain(ctx, "txt", "@f3:f3_val @f2:f2_val @f1:f1_val").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1).ToNot(BeEmpty())
})
It("should FTAlias ", Label("search", "ftexplain"), func() {
text1 := &redis.SearchSchema{Identifier: "name", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "name", AttributeType: "TEXT"}
val1, err := client.FTCreate(ctx, "testAlias", &redis.FTCreateOptions{Prefix: []interface{}{"index1:"}}, text1).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val1).To(BeEquivalentTo("OK"))
val2, err := client.FTCreate(ctx, "testAlias2", &redis.FTCreateOptions{Prefix: []interface{}{"index2:"}}, text2).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val2).To(BeEquivalentTo("OK"))
client.HSet(ctx, "index1:lonestar", "name", "lonestar")
client.HSet(ctx, "index2:yogurt", "name", "yogurt")
res1, err := client.FTSearch(ctx, "testAlias", "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("index1:lonestar"))
aliasAddRes, err := client.FTAliasAdd(ctx, "testAlias", "mj23").Result()
Expect(err).NotTo(HaveOccurred())
Expect(aliasAddRes).To(BeEquivalentTo("OK"))
res1, err = client.FTSearch(ctx, "mj23", "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("index1:lonestar"))
aliasUpdateRes, err := client.FTAliasUpdate(ctx, "testAlias2", "kb24").Result()
Expect(err).NotTo(HaveOccurred())
Expect(aliasUpdateRes).To(BeEquivalentTo("OK"))
res3, err := client.FTSearch(ctx, "kb24", "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res3.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["id"]).To(BeEquivalentTo("index2:yogurt"))
aliasDelRes, err := client.FTAliasDel(ctx, "mj23").Result()
Expect(err).NotTo(HaveOccurred())
Expect(aliasDelRes).To(BeEquivalentTo("OK"))
})
It("should FTCreate and FTSearch textfield, sortable and nostem ", Label("search", "ftcreate", "ftsearch"), func() {
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT", Sortable: true, NoStem: true}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
resInfo, err := client.FTInfo(ctx, "idx1").Result()
Expect(err).NotTo(HaveOccurred())
Expect(resInfo["attributes"].([]interface{})[0].(map[interface{}]interface{})["flags"]).To(ContainElements("SORTABLE", "NOSTEM"))
})
It("should FTAlter ", Label("search", "ftcreate", "ftsearch", "ftalter"), func() {
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.SearchSchema{Identifier: "txt", AttributeType: "TEXT"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
resAlter, err := client.FTAlter(ctx, "idx1", false, []interface{}{"body", "TEXT"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resAlter).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "title", "MyTitle", "body", "Some content only in the body")
res1, err := client.FTSearch(ctx, "idx1", "only in the body").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(1)))
})
It("should FTSpellCheck", Label("search", "ftcreate", "ftsearch", "ftspellcheck"), func() {
text1 := &redis.SearchSchema{Identifier: "f1", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "f2", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "f1", "some valid content", "f2", "this is sample text")
client.HSet(ctx, "doc2", "f1", "very important", "f2", "lorem ipsum")
resSpellCheck, err := client.FTSpellCheck(ctx, "idx1", "impornant").Result()
Expect(err).NotTo(HaveOccurred())
res := resSpellCheck["results"].(map[interface{}]interface{})["impornant"].([]interface{})[0].(map[interface{}]interface{})
Expect("important").To(BeKeyOf(res))
resSpellCheck2, err := client.FTSpellCheck(ctx, "idx1", "contnt").Result()
Expect(err).NotTo(HaveOccurred())
res2 := resSpellCheck2["results"].(map[interface{}]interface{})["contnt"].([]interface{})[0].(map[interface{}]interface{})
Expect("content").To(BeKeyOf(res2))
// test spellcheck with Levenshtein distance
resSpellCheck3, err := client.FTSpellCheck(ctx, "idx1", "vlis").Result()
Expect(err).NotTo(HaveOccurred())
Expect(resSpellCheck3["results"]).To(BeEquivalentTo(map[interface{}]interface{}{"vlis": []interface{}{}}))
resSpellCheck4, err := client.FTSpellCheckWithArgs(ctx, "idx1", "vlis", &redis.FTSpellCheckOptions{Distance: 2}).Result()
Expect(err).NotTo(HaveOccurred())
Expect("valid").To(BeKeyOf(resSpellCheck4["results"].(map[interface{}]interface{})["vlis"].([]interface{})[0].(map[interface{}]interface{})))
// test spellcheck include
dict := []interface{}{"lore", "lorem", "lorm"}
resDictAdd, err := client.FTDictAdd(ctx, "dict", dict).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resDictAdd).To(BeEquivalentTo(3))
terms := redis.SpellCheckTerms{Include: true, Dictionary: "dict"}
resSpellCheck5, err := client.FTSpellCheckWithArgs(ctx, "idx1", "lorm", &redis.FTSpellCheckOptions{Terms: terms}).Result()
Expect(err).NotTo(HaveOccurred())
lorm := resSpellCheck5["results"].(map[interface{}]interface{})["lorm"].([]interface{})
Expect(len(lorm)).To(BeEquivalentTo(3))
Expect(lorm[0].(map[interface{}]interface{})["lorem"]).To(BeEquivalentTo(0.5))
Expect(lorm[1].(map[interface{}]interface{})["lore"]).To(BeEquivalentTo(0))
Expect(lorm[2].(map[interface{}]interface{})["lorm"]).To(BeEquivalentTo(0))
terms2 := redis.SpellCheckTerms{Exclude: true, Dictionary: "dict"}
resSpellCheck6, err := client.FTSpellCheckWithArgs(ctx, "idx1", "lorm", &redis.FTSpellCheckOptions{Terms: terms2}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resSpellCheck6["results"]).To(BeEmpty())
})
It("should FTDict opreations ", Label("search", "ftdictdump", "ftdictdel", "ftdictadd"), func() {
text1 := &redis.SearchSchema{Identifier: "f1", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "f2", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
dict := []interface{}{"item1", "item2", "item3"}
resDictAdd, err := client.FTDictAdd(ctx, "custom_dict", dict).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resDictAdd).To(BeEquivalentTo(3))
resDictDel, err := client.FTDictDel(ctx, "custom_dict", []interface{}{"item2"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resDictDel).To(BeEquivalentTo(1))
resDictDump, err := client.FTDictDump(ctx, "custom_dict").Result()
Expect(err).NotTo(HaveOccurred())
Expect(resDictDump).To(BeEquivalentTo([]string{"item1", "item3"}))
resDictDel2, err := client.FTDictDel(ctx, "custom_dict", []interface{}{"item1", "item3"}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(resDictDel2).To(BeEquivalentTo(2))
})
It("should FTSearch phonetic matcher ", Label("search", "ftsearch"), func() {
text1 := &redis.SearchSchema{Identifier: "name", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "name", "Jon")
client.HSet(ctx, "doc2", "name", "John")
res1, err := client.FTSearch(ctx, "idx1", "Jon").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res1.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(1)))
name := res1.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})["name"]
Expect(name).To(BeEquivalentTo("Jon"))
client.FlushDB(ctx)
text2 := &redis.SearchSchema{Identifier: "name", AttributeType: "TEXT", PhoneticMatcher: "dm:en"}
val2, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text2).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val2).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "name", "Jon")
client.HSet(ctx, "doc2", "name", "John")
res2, err := client.FTSearch(ctx, "idx1", "Jon").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res2.(map[interface{}]interface{})["total_results"]).To(BeEquivalentTo(int64(2)))
results2 := res2.(map[interface{}]interface{})["results"].([]interface{})
n1 := results2[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})["name"]
n2 := results2[1].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})["name"]
names := []interface{}{n1, n2}
Expect(names).To(ContainElement("Jon"))
Expect(names).To(ContainElement("John"))
})
It("should FTSearch WithScores", Label("search", "ftsearch"), func() {
text1 := &redis.SearchSchema{Identifier: "description", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
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, and of having nothing to do.")
res, err := client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true}).Result()
Expect(err).NotTo(HaveOccurred())
result := res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(1))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(1))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "TFIDF.DOCNORM"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(0.1111111111111111))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "BM25"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(0.17699114465425977))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DISMAX"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(2))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "DOCSCORE"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(1))
res, err = client.FTSearchWithArgs(ctx, "idx1", "quick", &redis.FTSearchOptions{WithScores: true, Scorer: "HAMMING"}).Result()
Expect(err).NotTo(HaveOccurred())
result = res.(map[interface{}]interface{})["results"].([]interface{})[0].(map[interface{}]interface{})["score"]
Expect(result).To(BeEquivalentTo(0))
})
It("should FTConfigSet and FTConfigGet ", Label("search", "ftconfigget", "ftconfigset"), func() {
val, err := client.FTConfigSet(ctx, "TIMEOUT", "100").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
res, err := client.FTConfigGet(ctx, "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res["TIMEOUT"]).To(BeEquivalentTo("100"))
res, err = client.FTConfigGet(ctx, "TIMEOUT").Result()
Expect(err).NotTo(HaveOccurred())
Expect(res).To(BeEquivalentTo(map[string]interface{}{"TIMEOUT": "100"}))
})
It("should FTAggregate GroupBy ", Label("search", "ftaggregate"), func() {
text1 := &redis.SearchSchema{Identifier: "title", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "body", AttributeType: "TEXT"}
text3 := &redis.SearchSchema{Identifier: "parent", AttributeType: "TEXT"}
num := &redis.SearchSchema{Identifier: "random_num", AttributeType: "NUMERIC"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2, text3, num).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "search", "title", "RediSearch",
"body", "Redisearch impements a search engine on top of redis",
"parent", "redis",
"random_num", 10)
client.HSet(ctx, "ai", "title", "RedisAI",
"body", "RedisAI executes Deep Learning/Machine Learning models and managing their data.",
"parent", "redis",
"random_num", 3)
client.HSet(ctx, "json", "title", "RedisJson",
"body", "RedisJSON implements ECMA-404 The JSON Data Interchange Standard as a native data type.",
"parent", "redis",
"random_num", 8)
reducer := redis.FTAggregateReducer{Reducer: redis.SearchCount}
options := &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err := client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr := res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliascount"]).To(BeEquivalentTo("3"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchCountDistinct, Args: []interface{}{"@title"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliascount_distincttitle"]).To(BeEquivalentTo("3"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchSum, Args: []interface{}{"@random_num"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliassumrandom_num"]).To(BeEquivalentTo("21"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchMin, Args: []interface{}{"@random_num"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliasminrandom_num"]).To(BeEquivalentTo("3"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchMax, Args: []interface{}{"@random_num"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliasmaxrandom_num"]).To(BeEquivalentTo("10"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchAvg, Args: []interface{}{"@random_num"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliasavgrandom_num"]).To(BeEquivalentTo("7"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchStdDev, Args: []interface{}{"@random_num"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliasstddevrandom_num"]).To(BeEquivalentTo("3.60555127546"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchQuantile, Args: []interface{}{"@random_num", 0.5}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliasquantilerandom_num,0.5"]).To(BeEquivalentTo("8"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchToList, Args: []interface{}{"@title"}}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["__generated_aliastolisttitle"].([]interface{})).To(ContainElements("RediSearch", "RedisAI", "RedisJson"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchFirstValue, Args: []interface{}{"@title"}, As: "first"}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(extraAttr["first"]).To(BeEquivalentTo("RediSearch"))
reducer = redis.FTAggregateReducer{Reducer: redis.SearchRandomSample, Args: []interface{}{"@title", 2}, As: "random"}
options = &redis.FTAggregateOptions{GroupBy: []redis.FTAggregateGroupBy{{Fields: []interface{}{"@parent"}, Reduce: []redis.FTAggregateReducer{reducer}}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "redis", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr["parent"]).To(BeEquivalentTo("redis"))
Expect(len(extraAttr["random"].([]interface{}))).To(BeEquivalentTo(2))
Expect(extraAttr["random"].([]interface{})[0]).To(BeElementOf([]string{"RediSearch", "RedisAI", "RedisJson"}))
})
It("should FTAggregate sort and limit ", Label("search", "ftaggregate"), func() {
text1 := &redis.SearchSchema{Identifier: "t1", AttributeType: "TEXT"}
text2 := &redis.SearchSchema{Identifier: "t2", AttributeType: "TEXT"}
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, text1, text2).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(BeEquivalentTo("OK"))
client.HSet(ctx, "doc1", "t1", "a", "t2", "b")
client.HSet(ctx, "doc2", "t1", "b", "t2", "a")
options := &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t2", Asc: true}, {FieldName: "@t1", Desc: true}}}
res, err := client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr0 := res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
extraAttr1 := res["results"].([]interface{})[1].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr0).To(BeEquivalentTo(map[interface{}]interface{}{"t1": "b", "t2": "a"}))
Expect(extraAttr1).To(BeEquivalentTo(map[interface{}]interface{}{"t1": "a", "t2": "b"}))
options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
Expect(err).NotTo(HaveOccurred())
extraAttr0 = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
extraAttr1 = res["results"].([]interface{})[1].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(extraAttr0).To(BeEquivalentTo(map[interface{}]interface{}{"t1": "a"}))
Expect(extraAttr1).To(BeEquivalentTo(map[interface{}]interface{}{"t1": "b"}))
options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}, SortByMax: 1}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
Expect(err).NotTo(HaveOccurred())
results := res["results"].([]interface{})
Expect(len(results)).To(BeEquivalentTo(1))
options = &redis.FTAggregateOptions{SortBy: []redis.FTAggregateSortBy{{FieldName: "@t1"}}, Limit: 1, LimitOffset: 1}
res, err = client.FTAggregateWithArgs(ctx, "idx1", "*", options).Result()
Expect(err).NotTo(HaveOccurred())
results = res["results"].([]interface{})
extraAttr0 = res["results"].([]interface{})[0].(map[interface{}]interface{})["extra_attributes"].(map[interface{}]interface{})
Expect(len(results)).To(BeEquivalentTo(1))
Expect(extraAttr0).To(BeEquivalentTo(map[interface{}]interface{}{"t1": "b"}))
})
})

1215
search_commands.go Normal file

File diff suppressed because it is too large Load Diff

1147
search_test.go Normal file

File diff suppressed because it is too large Load Diff