mirror of https://github.com/go-redis/redis.git
Add RediSearch commands and tests
This commit is contained in:
parent
b0616719b1
commit
8642634488
765
redis_search.go
765
redis_search.go
|
@ -8,20 +8,343 @@ type SearchCmdable interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FTCreateOptions struct {
|
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 {
|
type FTDropIndexOptions struct {
|
||||||
DeleteDocs bool
|
DeleteDocs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - consider remove due to temporary command
|
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 {
|
func (c cmdable) FT_List(ctx context.Context) *StringSliceCmd {
|
||||||
cmd := NewStringSliceCmd(ctx, "FT._LIST")
|
cmd := NewStringSliceCmd(ctx, "FT._LIST")
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cmdable) FTAliasAdd(ctx context.Context, alias string, index string) *StatusCmd {
|
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}
|
args := []interface{}{"FT.ALIASADD", alias, index}
|
||||||
cmd := NewStatusCmd(ctx, args...)
|
cmd := NewStatusCmd(ctx, args...)
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
|
@ -34,7 +357,7 @@ func (c cmdable) FTAliasDel(ctx context.Context, alias string) *StatusCmd {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cmdable) FTAliasUpdate(ctx context.Context, alias string, index string) *StatusCmd {
|
func (c cmdable) FTAliasUpdate(ctx context.Context, index string, alias string) *StatusCmd {
|
||||||
cmd := NewStatusCmd(ctx, "FT.ALIASUPDATE", alias, index)
|
cmd := NewStatusCmd(ctx, "FT.ALIASUPDATE", alias, index)
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
|
@ -52,18 +375,124 @@ func (c cmdable) FTAlter(ctx context.Context, index string, skipInitalScan bool,
|
||||||
return 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 {
|
func (c cmdable) FTConfigSet(ctx context.Context, option string, value interface{}) *StatusCmd {
|
||||||
cmd := NewStatusCmd(ctx, "FT.CONFIG", "SET", option, value)
|
cmd := NewStatusCmd(ctx, "FT.CONFIG", "SET", option, value)
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (c cmdable) FTCreate(ctx context.Context, index string, schema string, ) *StatusCmd {
|
// TODO Fix schema for loop
|
||||||
// args := []interface{}{"FT.CREATE", index, "SCHEMA", schema}
|
func (c cmdable) FTCreate(ctx context.Context, index string, options *FTCreateOptions, schema ...*SearchSchema) *StatusCmd {
|
||||||
// cmd := NewStatusCmd(ctx, args...)
|
args := []interface{}{"FT.CREATE", index}
|
||||||
// _ = c(ctx, cmd)
|
if options != nil {
|
||||||
// return cmd
|
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 {
|
func (c cmdable) FTCursorDel(ctx context.Context, index string, cursorId int) *StatusCmd {
|
||||||
cmd := NewStatusCmd(ctx, "FT.CURSOR", "DEL", index, cursorId)
|
cmd := NewStatusCmd(ctx, "FT.CURSOR", "DEL", index, cursorId)
|
||||||
|
@ -71,6 +500,17 @@ func (c cmdable) FTCursorDel(ctx context.Context, index string, cursorId int) *S
|
||||||
return 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 {
|
func (c cmdable) FTDictAdd(ctx context.Context, dict string, term []interface{}) *IntCmd {
|
||||||
args := []interface{}{"FT.DICTADD", dict}
|
args := []interface{}{"FT.DICTADD", dict}
|
||||||
args = append(args, term...)
|
args = append(args, term...)
|
||||||
|
@ -100,7 +540,7 @@ func (c cmdable) FTDropIndex(ctx context.Context, index string) *StatusCmd {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cmdable) FTDropIndexWithOptions(ctx context.Context, index string, options *FTDropIndexOptions) *StatusCmd {
|
func (c cmdable) FTDropIndexWithArgs(ctx context.Context, index string, options *FTDropIndexOptions) *StatusCmd {
|
||||||
args := []interface{}{"FT.DROPINDEX", index}
|
args := []interface{}{"FT.DROPINDEX", index}
|
||||||
if options != nil {
|
if options != nil {
|
||||||
if options.DeleteDocs {
|
if options.DeleteDocs {
|
||||||
|
@ -111,3 +551,308 @@ func (c cmdable) FTDropIndexWithOptions(ctx context.Context, index string, optio
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return 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
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("RediSearch commands", Label("gears"), func() {
|
var _ = Describe("RediSearch commands", Label("search"), func() {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
var client *redis.Client
|
var client *redis.Client
|
||||||
|
|
||||||
|
@ -21,7 +21,547 @@ var _ = Describe("RediSearch commands", Label("gears"), func() {
|
||||||
Expect(client.Close()).NotTo(HaveOccurred())
|
Expect(client.Close()).NotTo(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should FT_List ", Label("search", "ft_list"), func() {
|
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"}))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -82,6 +82,13 @@ const (
|
||||||
Max
|
Max
|
||||||
Range
|
Range
|
||||||
Count
|
Count
|
||||||
|
CountDistinct
|
||||||
|
CountDistinctish
|
||||||
|
StdDev
|
||||||
|
Quantile
|
||||||
|
ToList
|
||||||
|
FirstValue
|
||||||
|
RandomSample
|
||||||
First
|
First
|
||||||
Last
|
Last
|
||||||
StdP
|
StdP
|
||||||
|
@ -107,6 +114,20 @@ func (a Aggregator) String() string {
|
||||||
return "RANGE"
|
return "RANGE"
|
||||||
case Count:
|
case Count:
|
||||||
return "COUNT"
|
return "COUNT"
|
||||||
|
case CountDistinct:
|
||||||
|
return "COUNT_DISTINCT"
|
||||||
|
case CountDistinctish:
|
||||||
|
return "COUNT_DISTINCTISH"
|
||||||
|
case StdDev:
|
||||||
|
return "STDDEV"
|
||||||
|
case Quantile:
|
||||||
|
return "QUANTILE"
|
||||||
|
case ToList:
|
||||||
|
return "TOLIST"
|
||||||
|
case FirstValue:
|
||||||
|
return "FIRST_VALUE"
|
||||||
|
case RandomSample:
|
||||||
|
return "RANDOM_SAMPLE"
|
||||||
case First:
|
case First:
|
||||||
return "FIRST"
|
return "FIRST"
|
||||||
case Last:
|
case Last:
|
||||||
|
|
Loading…
Reference in New Issue