1 package redis
2
3 import (
4 "context"
5 "fmt"
6 "strconv"
7
8 "github.com/redis/go-redis/v9/internal"
9 "github.com/redis/go-redis/v9/internal/proto"
10 )
11
12 type SearchCmdable interface {
13 FT_List(ctx context.Context) *StringSliceCmd
14 FTAggregate(ctx context.Context, index string, query string) *MapStringInterfaceCmd
15 FTAggregateWithArgs(ctx context.Context, index string, query string, options *FTAggregateOptions) *AggregateCmd
16 FTAliasAdd(ctx context.Context, index string, alias string) *StatusCmd
17 FTAliasDel(ctx context.Context, alias string) *StatusCmd
18 FTAliasUpdate(ctx context.Context, index string, alias string) *StatusCmd
19 FTAlter(ctx context.Context, index string, skipInitialScan bool, definition []interface{}) *StatusCmd
20 FTConfigGet(ctx context.Context, option string) *MapMapStringInterfaceCmd
21 FTConfigSet(ctx context.Context, option string, value interface{}) *StatusCmd
22 FTCreate(ctx context.Context, index string, options *FTCreateOptions, schema ...*FieldSchema) *StatusCmd
23 FTCursorDel(ctx context.Context, index string, cursorId int) *StatusCmd
24 FTCursorRead(ctx context.Context, index string, cursorId int, count int) *MapStringInterfaceCmd
25 FTDictAdd(ctx context.Context, dict string, term ...interface{}) *IntCmd
26 FTDictDel(ctx context.Context, dict string, term ...interface{}) *IntCmd
27 FTDictDump(ctx context.Context, dict string) *StringSliceCmd
28 FTDropIndex(ctx context.Context, index string) *StatusCmd
29 FTDropIndexWithArgs(ctx context.Context, index string, options *FTDropIndexOptions) *StatusCmd
30 FTExplain(ctx context.Context, index string, query string) *StringCmd
31 FTExplainWithArgs(ctx context.Context, index string, query string, options *FTExplainOptions) *StringCmd
32 FTInfo(ctx context.Context, index string) *FTInfoCmd
33 FTSpellCheck(ctx context.Context, index string, query string) *FTSpellCheckCmd
34 FTSpellCheckWithArgs(ctx context.Context, index string, query string, options *FTSpellCheckOptions) *FTSpellCheckCmd
35 FTSearch(ctx context.Context, index string, query string) *FTSearchCmd
36 FTSearchWithArgs(ctx context.Context, index string, query string, options *FTSearchOptions) *FTSearchCmd
37 FTSynDump(ctx context.Context, index string) *FTSynDumpCmd
38 FTSynUpdate(ctx context.Context, index string, synGroupId interface{}, terms []interface{}) *StatusCmd
39 FTSynUpdateWithArgs(ctx context.Context, index string, synGroupId interface{}, options *FTSynUpdateOptions, terms []interface{}) *StatusCmd
40 FTTagVals(ctx context.Context, index string, field string) *StringSliceCmd
41 }
42
43 type FTCreateOptions struct {
44 OnHash bool
45 OnJSON bool
46 Prefix []interface{}
47 Filter string
48 DefaultLanguage string
49 LanguageField string
50 Score float64
51 ScoreField string
52 PayloadField string
53 MaxTextFields int
54 NoOffsets bool
55 Temporary int
56 NoHL bool
57 NoFields bool
58 NoFreqs bool
59 StopWords []interface{}
60 SkipInitialScan bool
61 }
62
63 type FieldSchema struct {
64 FieldName string
65 As string
66 FieldType SearchFieldType
67 Sortable bool
68 UNF bool
69 NoStem bool
70 NoIndex bool
71 PhoneticMatcher string
72 Weight float64
73 Separator string
74 CaseSensitive bool
75 WithSuffixtrie bool
76 VectorArgs *FTVectorArgs
77 GeoShapeFieldType string
78 IndexEmpty bool
79 IndexMissing bool
80 }
81
82 type FTVectorArgs struct {
83 FlatOptions *FTFlatOptions
84 HNSWOptions *FTHNSWOptions
85 VamanaOptions *FTVamanaOptions
86 }
87
88 type FTFlatOptions struct {
89 Type string
90 Dim int
91 DistanceMetric string
92 InitialCapacity int
93 BlockSize int
94 }
95
96 type FTHNSWOptions struct {
97 Type string
98 Dim int
99 DistanceMetric string
100 InitialCapacity int
101 MaxEdgesPerNode int
102 MaxAllowedEdgesPerNode int
103 EFRunTime int
104 Epsilon float64
105 }
106
107 type FTVamanaOptions struct {
108 Type string
109 Dim int
110 DistanceMetric string
111 Compression string
112 ConstructionWindowSize int
113 GraphMaxDegree int
114 SearchWindowSize int
115 Epsilon float64
116 TrainingThreshold int
117 ReduceDim int
118 }
119
120 type FTDropIndexOptions struct {
121 DeleteDocs bool
122 }
123
124 type SpellCheckTerms struct {
125 Include bool
126 Exclude bool
127 Dictionary string
128 }
129
130 type FTExplainOptions struct {
131
132 Dialect string
133 }
134
135 type FTSynUpdateOptions struct {
136 SkipInitialScan bool
137 }
138
139 type SearchAggregator int
140
141 const (
142 SearchInvalid = SearchAggregator(iota)
143 SearchAvg
144 SearchSum
145 SearchMin
146 SearchMax
147 SearchCount
148 SearchCountDistinct
149 SearchCountDistinctish
150 SearchStdDev
151 SearchQuantile
152 SearchToList
153 SearchFirstValue
154 SearchRandomSample
155 )
156
157 func (a SearchAggregator) String() string {
158 switch a {
159 case SearchInvalid:
160 return ""
161 case SearchAvg:
162 return "AVG"
163 case SearchSum:
164 return "SUM"
165 case SearchMin:
166 return "MIN"
167 case SearchMax:
168 return "MAX"
169 case SearchCount:
170 return "COUNT"
171 case SearchCountDistinct:
172 return "COUNT_DISTINCT"
173 case SearchCountDistinctish:
174 return "COUNT_DISTINCTISH"
175 case SearchStdDev:
176 return "STDDEV"
177 case SearchQuantile:
178 return "QUANTILE"
179 case SearchToList:
180 return "TOLIST"
181 case SearchFirstValue:
182 return "FIRST_VALUE"
183 case SearchRandomSample:
184 return "RANDOM_SAMPLE"
185 default:
186 return ""
187 }
188 }
189
190 type SearchFieldType int
191
192 const (
193 SearchFieldTypeInvalid = SearchFieldType(iota)
194 SearchFieldTypeNumeric
195 SearchFieldTypeTag
196 SearchFieldTypeText
197 SearchFieldTypeGeo
198 SearchFieldTypeVector
199 SearchFieldTypeGeoShape
200 )
201
202 func (t SearchFieldType) String() string {
203 switch t {
204 case SearchFieldTypeInvalid:
205 return ""
206 case SearchFieldTypeNumeric:
207 return "NUMERIC"
208 case SearchFieldTypeTag:
209 return "TAG"
210 case SearchFieldTypeText:
211 return "TEXT"
212 case SearchFieldTypeGeo:
213 return "GEO"
214 case SearchFieldTypeVector:
215 return "VECTOR"
216 case SearchFieldTypeGeoShape:
217 return "GEOSHAPE"
218 default:
219 return "TEXT"
220 }
221 }
222
223
224
225 type FTAggregateReducer struct {
226 Reducer SearchAggregator
227 Args []interface{}
228 As string
229 }
230
231 type FTAggregateGroupBy struct {
232 Fields []interface{}
233 Reduce []FTAggregateReducer
234 }
235
236 type FTAggregateSortBy struct {
237 FieldName string
238 Asc bool
239 Desc bool
240 }
241
242 type FTAggregateApply struct {
243 Field string
244 As string
245 }
246
247 type FTAggregateLoad struct {
248 Field string
249 As string
250 }
251
252 type FTAggregateWithCursor struct {
253 Count int
254 MaxIdle int
255 }
256
257 type FTAggregateOptions struct {
258 Verbatim bool
259 LoadAll bool
260 Load []FTAggregateLoad
261 Timeout int
262 GroupBy []FTAggregateGroupBy
263 SortBy []FTAggregateSortBy
264 SortByMax int
265
266
267
268
269 Scorer string
270
271 AddScores bool
272 Apply []FTAggregateApply
273 LimitOffset int
274 Limit int
275 Filter string
276 WithCursor bool
277 WithCursorOptions *FTAggregateWithCursor
278 Params map[string]interface{}
279
280 DialectVersion int
281 }
282
283 type FTSearchFilter struct {
284 FieldName interface{}
285 Min interface{}
286 Max interface{}
287 }
288
289 type FTSearchGeoFilter struct {
290 FieldName string
291 Longitude float64
292 Latitude float64
293 Radius float64
294 Unit string
295 }
296
297 type FTSearchReturn struct {
298 FieldName string
299 As string
300 }
301
302 type FTSearchSortBy struct {
303 FieldName string
304 Asc bool
305 Desc bool
306 }
307
308
309
310
311 type FTSearchOptions struct {
312 NoContent bool
313 Verbatim bool
314 NoStopWords bool
315 WithScores bool
316 WithPayloads bool
317 WithSortKeys bool
318 Filters []FTSearchFilter
319 GeoFilter []FTSearchGeoFilter
320 InKeys []interface{}
321 InFields []interface{}
322 Return []FTSearchReturn
323 Slop int
324 Timeout int
325 InOrder bool
326 Language string
327 Expander string
328
329
330
331
332 Scorer string
333 ExplainScore bool
334 Payload string
335 SortBy []FTSearchSortBy
336 SortByWithCount bool
337 LimitOffset int
338 Limit int
339
340
341 CountOnly bool
342 Params map[string]interface{}
343
344 DialectVersion int
345 }
346
347 type FTSynDumpResult struct {
348 Term string
349 Synonyms []string
350 }
351
352 type FTSynDumpCmd struct {
353 baseCmd
354 val []FTSynDumpResult
355 }
356
357 type FTAggregateResult struct {
358 Total int
359 Rows []AggregateRow
360 }
361
362 type AggregateRow struct {
363 Fields map[string]interface{}
364 }
365
366 type AggregateCmd struct {
367 baseCmd
368 val *FTAggregateResult
369 }
370
371 type FTInfoResult struct {
372 IndexErrors IndexErrors
373 Attributes []FTAttribute
374 BytesPerRecordAvg string
375 Cleaning int
376 CursorStats CursorStats
377 DialectStats map[string]int
378 DocTableSizeMB float64
379 FieldStatistics []FieldStatistic
380 GCStats GCStats
381 GeoshapesSzMB float64
382 HashIndexingFailures int
383 IndexDefinition IndexDefinition
384 IndexName string
385 IndexOptions []string
386 Indexing int
387 InvertedSzMB float64
388 KeyTableSizeMB float64
389 MaxDocID int
390 NumDocs int
391 NumRecords int
392 NumTerms int
393 NumberOfUses int
394 OffsetBitsPerRecordAvg string
395 OffsetVectorsSzMB float64
396 OffsetsPerTermAvg string
397 PercentIndexed float64
398 RecordsPerDocAvg string
399 SortableValuesSizeMB float64
400 TagOverheadSzMB float64
401 TextOverheadSzMB float64
402 TotalIndexMemorySzMB float64
403 TotalIndexingTime int
404 TotalInvertedIndexBlocks int
405 VectorIndexSzMB float64
406 }
407
408 type IndexErrors struct {
409 IndexingFailures int
410 LastIndexingError string
411 LastIndexingErrorKey string
412 }
413
414 type FTAttribute struct {
415 Identifier string
416 Attribute string
417 Type string
418 Weight float64
419 Sortable bool
420 NoStem bool
421 NoIndex bool
422 UNF bool
423 PhoneticMatcher string
424 CaseSensitive bool
425 WithSuffixtrie bool
426 }
427
428 type CursorStats struct {
429 GlobalIdle int
430 GlobalTotal int
431 IndexCapacity int
432 IndexTotal int
433 }
434
435 type FieldStatistic struct {
436 Identifier string
437 Attribute string
438 IndexErrors IndexErrors
439 }
440
441 type GCStats struct {
442 BytesCollected int
443 TotalMsRun int
444 TotalCycles int
445 AverageCycleTimeMs string
446 LastRunTimeMs int
447 GCNumericTreesMissed int
448 GCBlocksDenied int
449 }
450
451 type IndexDefinition struct {
452 KeyType string
453 Prefixes []string
454 DefaultScore float64
455 }
456
457 type FTSpellCheckOptions struct {
458 Distance int
459 Terms *FTSpellCheckTerms
460
461 Dialect int
462 }
463
464 type FTSpellCheckTerms struct {
465 Inclusion string
466 Dictionary string
467 Terms []interface{}
468 }
469
470 type SpellCheckResult struct {
471 Term string
472 Suggestions []SpellCheckSuggestion
473 }
474
475 type SpellCheckSuggestion struct {
476 Score float64
477 Suggestion string
478 }
479
480 type FTSearchResult struct {
481 Total int
482 Docs []Document
483 }
484
485 type Document struct {
486 ID string
487 Score *float64
488 Payload *string
489 SortKey *string
490 Fields map[string]string
491 Error error
492 }
493
494 type AggregateQuery []interface{}
495
496
497
498
499 func (c cmdable) FT_List(ctx context.Context) *StringSliceCmd {
500 cmd := NewStringSliceCmd(ctx, "FT._LIST")
501 _ = c(ctx, cmd)
502 return cmd
503 }
504
505
506
507
508
509 func (c cmdable) FTAggregate(ctx context.Context, index string, query string) *MapStringInterfaceCmd {
510 args := []interface{}{"FT.AGGREGATE", index, query}
511 cmd := NewMapStringInterfaceCmd(ctx, args...)
512 _ = c(ctx, cmd)
513 return cmd
514 }
515
516 func FTAggregateQuery(query string, options *FTAggregateOptions) (AggregateQuery, error) {
517 queryArgs := []interface{}{query}
518 if options != nil {
519 if options.Verbatim {
520 queryArgs = append(queryArgs, "VERBATIM")
521 }
522
523 if options.Scorer != "" {
524 queryArgs = append(queryArgs, "SCORER", options.Scorer)
525 }
526
527 if options.AddScores {
528 queryArgs = append(queryArgs, "ADDSCORES")
529 }
530
531 if options.LoadAll && options.Load != nil {
532 return nil, fmt.Errorf("FT.AGGREGATE: LOADALL and LOAD are mutually exclusive")
533 }
534 if options.LoadAll {
535 queryArgs = append(queryArgs, "LOAD", "*")
536 }
537 if options.Load != nil {
538 queryArgs = append(queryArgs, "LOAD", len(options.Load))
539 index, count := len(queryArgs)-1, 0
540 for _, load := range options.Load {
541 queryArgs = append(queryArgs, load.Field)
542 count++
543 if load.As != "" {
544 queryArgs = append(queryArgs, "AS", load.As)
545 count += 2
546 }
547 }
548 queryArgs[index] = count
549 }
550
551 if options.Timeout > 0 {
552 queryArgs = append(queryArgs, "TIMEOUT", options.Timeout)
553 }
554
555 for _, apply := range options.Apply {
556 queryArgs = append(queryArgs, "APPLY", apply.Field)
557 if apply.As != "" {
558 queryArgs = append(queryArgs, "AS", apply.As)
559 }
560 }
561
562 if options.GroupBy != nil {
563 for _, groupBy := range options.GroupBy {
564 queryArgs = append(queryArgs, "GROUPBY", len(groupBy.Fields))
565 queryArgs = append(queryArgs, groupBy.Fields...)
566
567 for _, reducer := range groupBy.Reduce {
568 queryArgs = append(queryArgs, "REDUCE")
569 queryArgs = append(queryArgs, reducer.Reducer.String())
570 if reducer.Args != nil {
571 queryArgs = append(queryArgs, len(reducer.Args))
572 queryArgs = append(queryArgs, reducer.Args...)
573 } else {
574 queryArgs = append(queryArgs, 0)
575 }
576 if reducer.As != "" {
577 queryArgs = append(queryArgs, "AS", reducer.As)
578 }
579 }
580 }
581 }
582 if options.SortBy != nil {
583 queryArgs = append(queryArgs, "SORTBY")
584 sortByOptions := []interface{}{}
585 for _, sortBy := range options.SortBy {
586 sortByOptions = append(sortByOptions, sortBy.FieldName)
587 if sortBy.Asc && sortBy.Desc {
588 return nil, fmt.Errorf("FT.AGGREGATE: ASC and DESC are mutually exclusive")
589 }
590 if sortBy.Asc {
591 sortByOptions = append(sortByOptions, "ASC")
592 }
593 if sortBy.Desc {
594 sortByOptions = append(sortByOptions, "DESC")
595 }
596 }
597 queryArgs = append(queryArgs, len(sortByOptions))
598 queryArgs = append(queryArgs, sortByOptions...)
599 }
600 if options.SortByMax > 0 {
601 queryArgs = append(queryArgs, "MAX", options.SortByMax)
602 }
603 if options.LimitOffset >= 0 && options.Limit > 0 {
604 queryArgs = append(queryArgs, "LIMIT", options.LimitOffset, options.Limit)
605 }
606 if options.Filter != "" {
607 queryArgs = append(queryArgs, "FILTER", options.Filter)
608 }
609 if options.WithCursor {
610 queryArgs = append(queryArgs, "WITHCURSOR")
611 if options.WithCursorOptions != nil {
612 if options.WithCursorOptions.Count > 0 {
613 queryArgs = append(queryArgs, "COUNT", options.WithCursorOptions.Count)
614 }
615 if options.WithCursorOptions.MaxIdle > 0 {
616 queryArgs = append(queryArgs, "MAXIDLE", options.WithCursorOptions.MaxIdle)
617 }
618 }
619 }
620 if options.Params != nil {
621 queryArgs = append(queryArgs, "PARAMS", len(options.Params)*2)
622 for key, value := range options.Params {
623 queryArgs = append(queryArgs, key, value)
624 }
625 }
626
627 if options.DialectVersion > 0 {
628 queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
629 } else {
630 queryArgs = append(queryArgs, "DIALECT", 2)
631 }
632 }
633 return queryArgs, nil
634 }
635
636 func ProcessAggregateResult(data []interface{}) (*FTAggregateResult, error) {
637 if len(data) == 0 {
638 return nil, fmt.Errorf("no data returned")
639 }
640
641 total, ok := data[0].(int64)
642 if !ok {
643 return nil, fmt.Errorf("invalid total format")
644 }
645
646 rows := make([]AggregateRow, 0, len(data)-1)
647 for _, row := range data[1:] {
648 fields, ok := row.([]interface{})
649 if !ok {
650 return nil, fmt.Errorf("invalid row format")
651 }
652
653 rowMap := make(map[string]interface{})
654 for i := 0; i < len(fields); i += 2 {
655 key, ok := fields[i].(string)
656 if !ok {
657 return nil, fmt.Errorf("invalid field key format")
658 }
659 value := fields[i+1]
660 rowMap[key] = value
661 }
662 rows = append(rows, AggregateRow{Fields: rowMap})
663 }
664
665 result := &FTAggregateResult{
666 Total: int(total),
667 Rows: rows,
668 }
669 return result, nil
670 }
671
672 func NewAggregateCmd(ctx context.Context, args ...interface{}) *AggregateCmd {
673 return &AggregateCmd{
674 baseCmd: baseCmd{
675 ctx: ctx,
676 args: args,
677 },
678 }
679 }
680
681 func (cmd *AggregateCmd) SetVal(val *FTAggregateResult) {
682 cmd.val = val
683 }
684
685 func (cmd *AggregateCmd) Val() *FTAggregateResult {
686 return cmd.val
687 }
688
689 func (cmd *AggregateCmd) Result() (*FTAggregateResult, error) {
690 return cmd.val, cmd.err
691 }
692
693 func (cmd *AggregateCmd) RawVal() interface{} {
694 return cmd.rawVal
695 }
696
697 func (cmd *AggregateCmd) RawResult() (interface{}, error) {
698 return cmd.rawVal, cmd.err
699 }
700
701 func (cmd *AggregateCmd) String() string {
702 return cmdString(cmd, cmd.val)
703 }
704
705 func (cmd *AggregateCmd) readReply(rd *proto.Reader) (err error) {
706 data, err := rd.ReadSlice()
707 if err != nil {
708 return err
709 }
710 cmd.val, err = ProcessAggregateResult(data)
711 if err != nil {
712 return err
713 }
714 return nil
715 }
716
717
718
719
720
721
722 func (c cmdable) FTAggregateWithArgs(ctx context.Context, index string, query string, options *FTAggregateOptions) *AggregateCmd {
723 args := []interface{}{"FT.AGGREGATE", index, query}
724 if options != nil {
725 if options.Verbatim {
726 args = append(args, "VERBATIM")
727 }
728 if options.Scorer != "" {
729 args = append(args, "SCORER", options.Scorer)
730 }
731 if options.AddScores {
732 args = append(args, "ADDSCORES")
733 }
734 if options.LoadAll && options.Load != nil {
735 cmd := NewAggregateCmd(ctx, args...)
736 cmd.SetErr(fmt.Errorf("FT.AGGREGATE: LOADALL and LOAD are mutually exclusive"))
737 return cmd
738 }
739 if options.LoadAll {
740 args = append(args, "LOAD", "*")
741 }
742 if options.Load != nil {
743 args = append(args, "LOAD", len(options.Load))
744 index, count := len(args)-1, 0
745 for _, load := range options.Load {
746 args = append(args, load.Field)
747 count++
748 if load.As != "" {
749 args = append(args, "AS", load.As)
750 count += 2
751 }
752 }
753 args[index] = count
754 }
755 if options.Timeout > 0 {
756 args = append(args, "TIMEOUT", options.Timeout)
757 }
758 for _, apply := range options.Apply {
759 args = append(args, "APPLY", apply.Field)
760 if apply.As != "" {
761 args = append(args, "AS", apply.As)
762 }
763 }
764 if options.GroupBy != nil {
765 for _, groupBy := range options.GroupBy {
766 args = append(args, "GROUPBY", len(groupBy.Fields))
767 args = append(args, groupBy.Fields...)
768
769 for _, reducer := range groupBy.Reduce {
770 args = append(args, "REDUCE")
771 args = append(args, reducer.Reducer.String())
772 if reducer.Args != nil {
773 args = append(args, len(reducer.Args))
774 args = append(args, reducer.Args...)
775 } else {
776 args = append(args, 0)
777 }
778 if reducer.As != "" {
779 args = append(args, "AS", reducer.As)
780 }
781 }
782 }
783 }
784 if options.SortBy != nil {
785 args = append(args, "SORTBY")
786 sortByOptions := []interface{}{}
787 for _, sortBy := range options.SortBy {
788 sortByOptions = append(sortByOptions, sortBy.FieldName)
789 if sortBy.Asc && sortBy.Desc {
790 cmd := NewAggregateCmd(ctx, args...)
791 cmd.SetErr(fmt.Errorf("FT.AGGREGATE: ASC and DESC are mutually exclusive"))
792 return cmd
793 }
794 if sortBy.Asc {
795 sortByOptions = append(sortByOptions, "ASC")
796 }
797 if sortBy.Desc {
798 sortByOptions = append(sortByOptions, "DESC")
799 }
800 }
801 args = append(args, len(sortByOptions))
802 args = append(args, sortByOptions...)
803 }
804 if options.SortByMax > 0 {
805 args = append(args, "MAX", options.SortByMax)
806 }
807 if options.LimitOffset >= 0 && options.Limit > 0 {
808 args = append(args, "LIMIT", options.LimitOffset, options.Limit)
809 }
810 if options.Filter != "" {
811 args = append(args, "FILTER", options.Filter)
812 }
813 if options.WithCursor {
814 args = append(args, "WITHCURSOR")
815 if options.WithCursorOptions != nil {
816 if options.WithCursorOptions.Count > 0 {
817 args = append(args, "COUNT", options.WithCursorOptions.Count)
818 }
819 if options.WithCursorOptions.MaxIdle > 0 {
820 args = append(args, "MAXIDLE", options.WithCursorOptions.MaxIdle)
821 }
822 }
823 }
824 if options.Params != nil {
825 args = append(args, "PARAMS", len(options.Params)*2)
826 for key, value := range options.Params {
827 args = append(args, key, value)
828 }
829 }
830 if options.DialectVersion > 0 {
831 args = append(args, "DIALECT", options.DialectVersion)
832 } else {
833 args = append(args, "DIALECT", 2)
834 }
835 }
836
837 cmd := NewAggregateCmd(ctx, args...)
838 _ = c(ctx, cmd)
839 return cmd
840 }
841
842
843
844
845
846 func (c cmdable) FTAliasAdd(ctx context.Context, index string, alias string) *StatusCmd {
847 args := []interface{}{"FT.ALIASADD", alias, index}
848 cmd := NewStatusCmd(ctx, args...)
849 _ = c(ctx, cmd)
850 return cmd
851 }
852
853
854
855
856
857 func (c cmdable) FTAliasDel(ctx context.Context, alias string) *StatusCmd {
858 cmd := NewStatusCmd(ctx, "FT.ALIASDEL", alias)
859 _ = c(ctx, cmd)
860 return cmd
861 }
862
863
864
865
866
867
868 func (c cmdable) FTAliasUpdate(ctx context.Context, index string, alias string) *StatusCmd {
869 cmd := NewStatusCmd(ctx, "FT.ALIASUPDATE", alias, index)
870 _ = c(ctx, cmd)
871 return cmd
872 }
873
874
875
876
877
878
879 func (c cmdable) FTAlter(ctx context.Context, index string, skipInitialScan bool, definition []interface{}) *StatusCmd {
880 args := []interface{}{"FT.ALTER", index}
881 if skipInitialScan {
882 args = append(args, "SKIPINITIALSCAN")
883 }
884 args = append(args, "SCHEMA", "ADD")
885 args = append(args, definition...)
886 cmd := NewStatusCmd(ctx, args...)
887 _ = c(ctx, cmd)
888 return cmd
889 }
890
891
892
893
894
895
896
897
898
899
900
901 func (c cmdable) FTConfigGet(ctx context.Context, option string) *MapMapStringInterfaceCmd {
902 cmd := NewMapMapStringInterfaceCmd(ctx, "FT.CONFIG", "GET", option)
903 _ = c(ctx, cmd)
904 return cmd
905 }
906
907
908
909
910
911
912
913
914
915
916
917 func (c cmdable) FTConfigSet(ctx context.Context, option string, value interface{}) *StatusCmd {
918 cmd := NewStatusCmd(ctx, "FT.CONFIG", "SET", option, value)
919 _ = c(ctx, cmd)
920 return cmd
921 }
922
923
924
925
926
927
928
929
930 func (c cmdable) FTCreate(ctx context.Context, index string, options *FTCreateOptions, schema ...*FieldSchema) *StatusCmd {
931 args := []interface{}{"FT.CREATE", index}
932 if options != nil {
933 if options.OnHash && !options.OnJSON {
934 args = append(args, "ON", "HASH")
935 }
936 if options.OnJSON && !options.OnHash {
937 args = append(args, "ON", "JSON")
938 }
939 if options.OnHash && options.OnJSON {
940 cmd := NewStatusCmd(ctx, args...)
941 cmd.SetErr(fmt.Errorf("FT.CREATE: ON HASH and ON JSON are mutually exclusive"))
942 return cmd
943 }
944 if options.Prefix != nil {
945 args = append(args, "PREFIX", len(options.Prefix))
946 args = append(args, options.Prefix...)
947 }
948 if options.Filter != "" {
949 args = append(args, "FILTER", options.Filter)
950 }
951 if options.DefaultLanguage != "" {
952 args = append(args, "LANGUAGE", options.DefaultLanguage)
953 }
954 if options.LanguageField != "" {
955 args = append(args, "LANGUAGE_FIELD", options.LanguageField)
956 }
957 if options.Score > 0 {
958 args = append(args, "SCORE", options.Score)
959 }
960 if options.ScoreField != "" {
961 args = append(args, "SCORE_FIELD", options.ScoreField)
962 }
963 if options.PayloadField != "" {
964 args = append(args, "PAYLOAD_FIELD", options.PayloadField)
965 }
966 if options.MaxTextFields > 0 {
967 args = append(args, "MAXTEXTFIELDS", options.MaxTextFields)
968 }
969 if options.NoOffsets {
970 args = append(args, "NOOFFSETS")
971 }
972 if options.Temporary > 0 {
973 args = append(args, "TEMPORARY", options.Temporary)
974 }
975 if options.NoHL {
976 args = append(args, "NOHL")
977 }
978 if options.NoFields {
979 args = append(args, "NOFIELDS")
980 }
981 if options.NoFreqs {
982 args = append(args, "NOFREQS")
983 }
984 if options.StopWords != nil {
985 args = append(args, "STOPWORDS", len(options.StopWords))
986 args = append(args, options.StopWords...)
987 }
988 if options.SkipInitialScan {
989 args = append(args, "SKIPINITIALSCAN")
990 }
991 }
992 if schema == nil {
993 cmd := NewStatusCmd(ctx, args...)
994 cmd.SetErr(fmt.Errorf("FT.CREATE: SCHEMA is required"))
995 return cmd
996 }
997 args = append(args, "SCHEMA")
998 for _, schema := range schema {
999 if schema.FieldName == "" || schema.FieldType == SearchFieldTypeInvalid {
1000 cmd := NewStatusCmd(ctx, args...)
1001 cmd.SetErr(fmt.Errorf("FT.CREATE: SCHEMA FieldName and FieldType are required"))
1002 return cmd
1003 }
1004 args = append(args, schema.FieldName)
1005 if schema.As != "" {
1006 args = append(args, "AS", schema.As)
1007 }
1008 args = append(args, schema.FieldType.String())
1009 if schema.VectorArgs != nil {
1010 if schema.FieldType != SearchFieldTypeVector {
1011 cmd := NewStatusCmd(ctx, args...)
1012 cmd.SetErr(fmt.Errorf("FT.CREATE: SCHEMA FieldType VECTOR is required for VectorArgs"))
1013 return cmd
1014 }
1015
1016 optionCount := 0
1017 if schema.VectorArgs.FlatOptions != nil {
1018 optionCount++
1019 }
1020 if schema.VectorArgs.HNSWOptions != nil {
1021 optionCount++
1022 }
1023 if schema.VectorArgs.VamanaOptions != nil {
1024 optionCount++
1025 }
1026 if optionCount != 1 {
1027 cmd := NewStatusCmd(ctx, args...)
1028 cmd.SetErr(fmt.Errorf("FT.CREATE: SCHEMA VectorArgs must have exactly one of FlatOptions, HNSWOptions, or VamanaOptions"))
1029 return cmd
1030 }
1031 if schema.VectorArgs.FlatOptions != nil {
1032 args = append(args, "FLAT")
1033 if schema.VectorArgs.FlatOptions.Type == "" || schema.VectorArgs.FlatOptions.Dim == 0 || schema.VectorArgs.FlatOptions.DistanceMetric == "" {
1034 cmd := NewStatusCmd(ctx, args...)
1035 cmd.SetErr(fmt.Errorf("FT.CREATE: Type, Dim and DistanceMetric are required for VECTOR FLAT"))
1036 return cmd
1037 }
1038 flatArgs := []interface{}{
1039 "TYPE", schema.VectorArgs.FlatOptions.Type,
1040 "DIM", schema.VectorArgs.FlatOptions.Dim,
1041 "DISTANCE_METRIC", schema.VectorArgs.FlatOptions.DistanceMetric,
1042 }
1043 if schema.VectorArgs.FlatOptions.InitialCapacity > 0 {
1044 flatArgs = append(flatArgs, "INITIAL_CAP", schema.VectorArgs.FlatOptions.InitialCapacity)
1045 }
1046 if schema.VectorArgs.FlatOptions.BlockSize > 0 {
1047 flatArgs = append(flatArgs, "BLOCK_SIZE", schema.VectorArgs.FlatOptions.BlockSize)
1048 }
1049 args = append(args, len(flatArgs))
1050 args = append(args, flatArgs...)
1051 }
1052 if schema.VectorArgs.HNSWOptions != nil {
1053 args = append(args, "HNSW")
1054 if schema.VectorArgs.HNSWOptions.Type == "" || schema.VectorArgs.HNSWOptions.Dim == 0 || schema.VectorArgs.HNSWOptions.DistanceMetric == "" {
1055 cmd := NewStatusCmd(ctx, args...)
1056 cmd.SetErr(fmt.Errorf("FT.CREATE: Type, Dim and DistanceMetric are required for VECTOR HNSW"))
1057 return cmd
1058 }
1059 hnswArgs := []interface{}{
1060 "TYPE", schema.VectorArgs.HNSWOptions.Type,
1061 "DIM", schema.VectorArgs.HNSWOptions.Dim,
1062 "DISTANCE_METRIC", schema.VectorArgs.HNSWOptions.DistanceMetric,
1063 }
1064 if schema.VectorArgs.HNSWOptions.InitialCapacity > 0 {
1065 hnswArgs = append(hnswArgs, "INITIAL_CAP", schema.VectorArgs.HNSWOptions.InitialCapacity)
1066 }
1067 if schema.VectorArgs.HNSWOptions.MaxEdgesPerNode > 0 {
1068 hnswArgs = append(hnswArgs, "M", schema.VectorArgs.HNSWOptions.MaxEdgesPerNode)
1069 }
1070 if schema.VectorArgs.HNSWOptions.MaxAllowedEdgesPerNode > 0 {
1071 hnswArgs = append(hnswArgs, "EF_CONSTRUCTION", schema.VectorArgs.HNSWOptions.MaxAllowedEdgesPerNode)
1072 }
1073 if schema.VectorArgs.HNSWOptions.EFRunTime > 0 {
1074 hnswArgs = append(hnswArgs, "EF_RUNTIME", schema.VectorArgs.HNSWOptions.EFRunTime)
1075 }
1076 if schema.VectorArgs.HNSWOptions.Epsilon > 0 {
1077 hnswArgs = append(hnswArgs, "EPSILON", schema.VectorArgs.HNSWOptions.Epsilon)
1078 }
1079 args = append(args, len(hnswArgs))
1080 args = append(args, hnswArgs...)
1081 }
1082 if schema.VectorArgs.VamanaOptions != nil {
1083 args = append(args, "SVS-VAMANA")
1084 if schema.VectorArgs.VamanaOptions.Type == "" || schema.VectorArgs.VamanaOptions.Dim == 0 || schema.VectorArgs.VamanaOptions.DistanceMetric == "" {
1085 cmd := NewStatusCmd(ctx, args...)
1086 cmd.SetErr(fmt.Errorf("FT.CREATE: Type, Dim and DistanceMetric are required for VECTOR VAMANA"))
1087 return cmd
1088 }
1089 vamanaArgs := []interface{}{
1090 "TYPE", schema.VectorArgs.VamanaOptions.Type,
1091 "DIM", schema.VectorArgs.VamanaOptions.Dim,
1092 "DISTANCE_METRIC", schema.VectorArgs.VamanaOptions.DistanceMetric,
1093 }
1094 if schema.VectorArgs.VamanaOptions.Compression != "" {
1095 vamanaArgs = append(vamanaArgs, "COMPRESSION", schema.VectorArgs.VamanaOptions.Compression)
1096 }
1097 if schema.VectorArgs.VamanaOptions.ConstructionWindowSize > 0 {
1098 vamanaArgs = append(vamanaArgs, "CONSTRUCTION_WINDOW_SIZE", schema.VectorArgs.VamanaOptions.ConstructionWindowSize)
1099 }
1100 if schema.VectorArgs.VamanaOptions.GraphMaxDegree > 0 {
1101 vamanaArgs = append(vamanaArgs, "GRAPH_MAX_DEGREE", schema.VectorArgs.VamanaOptions.GraphMaxDegree)
1102 }
1103 if schema.VectorArgs.VamanaOptions.SearchWindowSize > 0 {
1104 vamanaArgs = append(vamanaArgs, "SEARCH_WINDOW_SIZE", schema.VectorArgs.VamanaOptions.SearchWindowSize)
1105 }
1106 if schema.VectorArgs.VamanaOptions.Epsilon > 0 {
1107 vamanaArgs = append(vamanaArgs, "EPSILON", schema.VectorArgs.VamanaOptions.Epsilon)
1108 }
1109 if schema.VectorArgs.VamanaOptions.TrainingThreshold > 0 {
1110 vamanaArgs = append(vamanaArgs, "TRAINING_THRESHOLD", schema.VectorArgs.VamanaOptions.TrainingThreshold)
1111 }
1112 if schema.VectorArgs.VamanaOptions.ReduceDim > 0 {
1113 vamanaArgs = append(vamanaArgs, "REDUCE", schema.VectorArgs.VamanaOptions.ReduceDim)
1114 }
1115 args = append(args, len(vamanaArgs))
1116 args = append(args, vamanaArgs...)
1117 }
1118 }
1119 if schema.GeoShapeFieldType != "" {
1120 if schema.FieldType != SearchFieldTypeGeoShape {
1121 cmd := NewStatusCmd(ctx, args...)
1122 cmd.SetErr(fmt.Errorf("FT.CREATE: SCHEMA FieldType GEOSHAPE is required for GeoShapeFieldType"))
1123 return cmd
1124 }
1125 args = append(args, schema.GeoShapeFieldType)
1126 }
1127 if schema.NoStem {
1128 args = append(args, "NOSTEM")
1129 }
1130 if schema.Sortable {
1131 args = append(args, "SORTABLE")
1132 }
1133 if schema.UNF {
1134 args = append(args, "UNF")
1135 }
1136 if schema.NoIndex {
1137 args = append(args, "NOINDEX")
1138 }
1139 if schema.PhoneticMatcher != "" {
1140 args = append(args, "PHONETIC", schema.PhoneticMatcher)
1141 }
1142 if schema.Weight > 0 {
1143 args = append(args, "WEIGHT", schema.Weight)
1144 }
1145 if schema.Separator != "" {
1146 args = append(args, "SEPARATOR", schema.Separator)
1147 }
1148 if schema.CaseSensitive {
1149 args = append(args, "CASESENSITIVE")
1150 }
1151 if schema.WithSuffixtrie {
1152 args = append(args, "WITHSUFFIXTRIE")
1153 }
1154 if schema.IndexEmpty {
1155 args = append(args, "INDEXEMPTY")
1156 }
1157 if schema.IndexMissing {
1158 args = append(args, "INDEXMISSING")
1159
1160 }
1161 }
1162 cmd := NewStatusCmd(ctx, args...)
1163 _ = c(ctx, cmd)
1164 return cmd
1165 }
1166
1167
1168
1169
1170
1171 func (c cmdable) FTCursorDel(ctx context.Context, index string, cursorId int) *StatusCmd {
1172 cmd := NewStatusCmd(ctx, "FT.CURSOR", "DEL", index, cursorId)
1173 _ = c(ctx, cmd)
1174 return cmd
1175 }
1176
1177
1178
1179
1180
1181 func (c cmdable) FTCursorRead(ctx context.Context, index string, cursorId int, count int) *MapStringInterfaceCmd {
1182 args := []interface{}{"FT.CURSOR", "READ", index, cursorId}
1183 if count > 0 {
1184 args = append(args, "COUNT", count)
1185 }
1186 cmd := NewMapStringInterfaceCmd(ctx, args...)
1187 _ = c(ctx, cmd)
1188 return cmd
1189 }
1190
1191
1192
1193
1194
1195 func (c cmdable) FTDictAdd(ctx context.Context, dict string, term ...interface{}) *IntCmd {
1196 args := []interface{}{"FT.DICTADD", dict}
1197 args = append(args, term...)
1198 cmd := NewIntCmd(ctx, args...)
1199 _ = c(ctx, cmd)
1200 return cmd
1201 }
1202
1203
1204
1205
1206
1207 func (c cmdable) FTDictDel(ctx context.Context, dict string, term ...interface{}) *IntCmd {
1208 args := []interface{}{"FT.DICTDEL", dict}
1209 args = append(args, term...)
1210 cmd := NewIntCmd(ctx, args...)
1211 _ = c(ctx, cmd)
1212 return cmd
1213 }
1214
1215
1216
1217
1218
1219 func (c cmdable) FTDictDump(ctx context.Context, dict string) *StringSliceCmd {
1220 cmd := NewStringSliceCmd(ctx, "FT.DICTDUMP", dict)
1221 _ = c(ctx, cmd)
1222 return cmd
1223 }
1224
1225
1226
1227
1228
1229 func (c cmdable) FTDropIndex(ctx context.Context, index string) *StatusCmd {
1230 args := []interface{}{"FT.DROPINDEX", index}
1231 cmd := NewStatusCmd(ctx, args...)
1232 _ = c(ctx, cmd)
1233 return cmd
1234 }
1235
1236
1237
1238
1239
1240 func (c cmdable) FTDropIndexWithArgs(ctx context.Context, index string, options *FTDropIndexOptions) *StatusCmd {
1241 args := []interface{}{"FT.DROPINDEX", index}
1242 if options != nil {
1243 if options.DeleteDocs {
1244 args = append(args, "DD")
1245 }
1246 }
1247 cmd := NewStatusCmd(ctx, args...)
1248 _ = c(ctx, cmd)
1249 return cmd
1250 }
1251
1252
1253
1254
1255
1256 func (c cmdable) FTExplain(ctx context.Context, index string, query string) *StringCmd {
1257 cmd := NewStringCmd(ctx, "FT.EXPLAIN", index, query)
1258 _ = c(ctx, cmd)
1259 return cmd
1260 }
1261
1262
1263
1264
1265
1266 func (c cmdable) FTExplainWithArgs(ctx context.Context, index string, query string, options *FTExplainOptions) *StringCmd {
1267 args := []interface{}{"FT.EXPLAIN", index, query}
1268 if options.Dialect != "" {
1269 args = append(args, "DIALECT", options.Dialect)
1270 } else {
1271 args = append(args, "DIALECT", 2)
1272 }
1273 cmd := NewStringCmd(ctx, args...)
1274 _ = c(ctx, cmd)
1275 return cmd
1276 }
1277
1278
1279
1280 func (c cmdable) FTExplainCli(ctx context.Context, key, path string) error {
1281 return fmt.Errorf("FTExplainCli is not implemented")
1282 }
1283
1284 func parseFTInfo(data map[string]interface{}) (FTInfoResult, error) {
1285 var ftInfo FTInfoResult
1286
1287 if indexErrors, ok := data["Index Errors"].([]interface{}); ok {
1288 ftInfo.IndexErrors = IndexErrors{
1289 IndexingFailures: internal.ToInteger(indexErrors[1]),
1290 LastIndexingError: internal.ToString(indexErrors[3]),
1291 LastIndexingErrorKey: internal.ToString(indexErrors[5]),
1292 }
1293 }
1294
1295 if attributes, ok := data["attributes"].([]interface{}); ok {
1296 for _, attr := range attributes {
1297 if attrMap, ok := attr.([]interface{}); ok {
1298 att := FTAttribute{}
1299 for i := 0; i < len(attrMap); i++ {
1300 if internal.ToLower(internal.ToString(attrMap[i])) == "attribute" {
1301 att.Attribute = internal.ToString(attrMap[i+1])
1302 continue
1303 }
1304 if internal.ToLower(internal.ToString(attrMap[i])) == "identifier" {
1305 att.Identifier = internal.ToString(attrMap[i+1])
1306 continue
1307 }
1308 if internal.ToLower(internal.ToString(attrMap[i])) == "type" {
1309 att.Type = internal.ToString(attrMap[i+1])
1310 continue
1311 }
1312 if internal.ToLower(internal.ToString(attrMap[i])) == "weight" {
1313 att.Weight = internal.ToFloat(attrMap[i+1])
1314 continue
1315 }
1316 if internal.ToLower(internal.ToString(attrMap[i])) == "nostem" {
1317 att.NoStem = true
1318 continue
1319 }
1320 if internal.ToLower(internal.ToString(attrMap[i])) == "sortable" {
1321 att.Sortable = true
1322 continue
1323 }
1324 if internal.ToLower(internal.ToString(attrMap[i])) == "noindex" {
1325 att.NoIndex = true
1326 continue
1327 }
1328 if internal.ToLower(internal.ToString(attrMap[i])) == "unf" {
1329 att.UNF = true
1330 continue
1331 }
1332 if internal.ToLower(internal.ToString(attrMap[i])) == "phonetic" {
1333 att.PhoneticMatcher = internal.ToString(attrMap[i+1])
1334 continue
1335 }
1336 if internal.ToLower(internal.ToString(attrMap[i])) == "case_sensitive" {
1337 att.CaseSensitive = true
1338 continue
1339 }
1340 if internal.ToLower(internal.ToString(attrMap[i])) == "withsuffixtrie" {
1341 att.WithSuffixtrie = true
1342 continue
1343 }
1344
1345 }
1346 ftInfo.Attributes = append(ftInfo.Attributes, att)
1347 }
1348 }
1349 }
1350
1351 ftInfo.BytesPerRecordAvg = internal.ToString(data["bytes_per_record_avg"])
1352 ftInfo.Cleaning = internal.ToInteger(data["cleaning"])
1353
1354 if cursorStats, ok := data["cursor_stats"].([]interface{}); ok {
1355 ftInfo.CursorStats = CursorStats{
1356 GlobalIdle: internal.ToInteger(cursorStats[1]),
1357 GlobalTotal: internal.ToInteger(cursorStats[3]),
1358 IndexCapacity: internal.ToInteger(cursorStats[5]),
1359 IndexTotal: internal.ToInteger(cursorStats[7]),
1360 }
1361 }
1362
1363 if dialectStats, ok := data["dialect_stats"].([]interface{}); ok {
1364 ftInfo.DialectStats = make(map[string]int)
1365 for i := 0; i < len(dialectStats); i += 2 {
1366 ftInfo.DialectStats[internal.ToString(dialectStats[i])] = internal.ToInteger(dialectStats[i+1])
1367 }
1368 }
1369
1370 ftInfo.DocTableSizeMB = internal.ToFloat(data["doc_table_size_mb"])
1371
1372 if fieldStats, ok := data["field statistics"].([]interface{}); ok {
1373 for _, stat := range fieldStats {
1374 if statMap, ok := stat.([]interface{}); ok {
1375 ftInfo.FieldStatistics = append(ftInfo.FieldStatistics, FieldStatistic{
1376 Identifier: internal.ToString(statMap[1]),
1377 Attribute: internal.ToString(statMap[3]),
1378 IndexErrors: IndexErrors{
1379 IndexingFailures: internal.ToInteger(statMap[5].([]interface{})[1]),
1380 LastIndexingError: internal.ToString(statMap[5].([]interface{})[3]),
1381 LastIndexingErrorKey: internal.ToString(statMap[5].([]interface{})[5]),
1382 },
1383 })
1384 }
1385 }
1386 }
1387
1388 if gcStats, ok := data["gc_stats"].([]interface{}); ok {
1389 ftInfo.GCStats = GCStats{}
1390 for i := 0; i < len(gcStats); i += 2 {
1391 if internal.ToLower(internal.ToString(gcStats[i])) == "bytes_collected" {
1392 ftInfo.GCStats.BytesCollected = internal.ToInteger(gcStats[i+1])
1393 continue
1394 }
1395 if internal.ToLower(internal.ToString(gcStats[i])) == "total_ms_run" {
1396 ftInfo.GCStats.TotalMsRun = internal.ToInteger(gcStats[i+1])
1397 continue
1398 }
1399 if internal.ToLower(internal.ToString(gcStats[i])) == "total_cycles" {
1400 ftInfo.GCStats.TotalCycles = internal.ToInteger(gcStats[i+1])
1401 continue
1402 }
1403 if internal.ToLower(internal.ToString(gcStats[i])) == "average_cycle_time_ms" {
1404 ftInfo.GCStats.AverageCycleTimeMs = internal.ToString(gcStats[i+1])
1405 continue
1406 }
1407 if internal.ToLower(internal.ToString(gcStats[i])) == "last_run_time_ms" {
1408 ftInfo.GCStats.LastRunTimeMs = internal.ToInteger(gcStats[i+1])
1409 continue
1410 }
1411 if internal.ToLower(internal.ToString(gcStats[i])) == "gc_numeric_trees_missed" {
1412 ftInfo.GCStats.GCNumericTreesMissed = internal.ToInteger(gcStats[i+1])
1413 continue
1414 }
1415 if internal.ToLower(internal.ToString(gcStats[i])) == "gc_blocks_denied" {
1416 ftInfo.GCStats.GCBlocksDenied = internal.ToInteger(gcStats[i+1])
1417 continue
1418 }
1419 }
1420 }
1421
1422 ftInfo.GeoshapesSzMB = internal.ToFloat(data["geoshapes_sz_mb"])
1423 ftInfo.HashIndexingFailures = internal.ToInteger(data["hash_indexing_failures"])
1424
1425 if indexDef, ok := data["index_definition"].([]interface{}); ok {
1426 ftInfo.IndexDefinition = IndexDefinition{
1427 KeyType: internal.ToString(indexDef[1]),
1428 Prefixes: internal.ToStringSlice(indexDef[3]),
1429 DefaultScore: internal.ToFloat(indexDef[5]),
1430 }
1431 }
1432
1433 ftInfo.IndexName = internal.ToString(data["index_name"])
1434 ftInfo.IndexOptions = internal.ToStringSlice(data["index_options"].([]interface{}))
1435 ftInfo.Indexing = internal.ToInteger(data["indexing"])
1436 ftInfo.InvertedSzMB = internal.ToFloat(data["inverted_sz_mb"])
1437 ftInfo.KeyTableSizeMB = internal.ToFloat(data["key_table_size_mb"])
1438 ftInfo.MaxDocID = internal.ToInteger(data["max_doc_id"])
1439 ftInfo.NumDocs = internal.ToInteger(data["num_docs"])
1440 ftInfo.NumRecords = internal.ToInteger(data["num_records"])
1441 ftInfo.NumTerms = internal.ToInteger(data["num_terms"])
1442 ftInfo.NumberOfUses = internal.ToInteger(data["number_of_uses"])
1443 ftInfo.OffsetBitsPerRecordAvg = internal.ToString(data["offset_bits_per_record_avg"])
1444 ftInfo.OffsetVectorsSzMB = internal.ToFloat(data["offset_vectors_sz_mb"])
1445 ftInfo.OffsetsPerTermAvg = internal.ToString(data["offsets_per_term_avg"])
1446 ftInfo.PercentIndexed = internal.ToFloat(data["percent_indexed"])
1447 ftInfo.RecordsPerDocAvg = internal.ToString(data["records_per_doc_avg"])
1448 ftInfo.SortableValuesSizeMB = internal.ToFloat(data["sortable_values_size_mb"])
1449 ftInfo.TagOverheadSzMB = internal.ToFloat(data["tag_overhead_sz_mb"])
1450 ftInfo.TextOverheadSzMB = internal.ToFloat(data["text_overhead_sz_mb"])
1451 ftInfo.TotalIndexMemorySzMB = internal.ToFloat(data["total_index_memory_sz_mb"])
1452 ftInfo.TotalIndexingTime = internal.ToInteger(data["total_indexing_time"])
1453 ftInfo.TotalInvertedIndexBlocks = internal.ToInteger(data["total_inverted_index_blocks"])
1454 ftInfo.VectorIndexSzMB = internal.ToFloat(data["vector_index_sz_mb"])
1455
1456 return ftInfo, nil
1457 }
1458
1459 type FTInfoCmd struct {
1460 baseCmd
1461 val FTInfoResult
1462 }
1463
1464 func newFTInfoCmd(ctx context.Context, args ...interface{}) *FTInfoCmd {
1465 return &FTInfoCmd{
1466 baseCmd: baseCmd{
1467 ctx: ctx,
1468 args: args,
1469 },
1470 }
1471 }
1472
1473 func (cmd *FTInfoCmd) String() string {
1474 return cmdString(cmd, cmd.val)
1475 }
1476
1477 func (cmd *FTInfoCmd) SetVal(val FTInfoResult) {
1478 cmd.val = val
1479 }
1480
1481 func (cmd *FTInfoCmd) Result() (FTInfoResult, error) {
1482 return cmd.val, cmd.err
1483 }
1484
1485 func (cmd *FTInfoCmd) Val() FTInfoResult {
1486 return cmd.val
1487 }
1488
1489 func (cmd *FTInfoCmd) RawVal() interface{} {
1490 return cmd.rawVal
1491 }
1492
1493 func (cmd *FTInfoCmd) RawResult() (interface{}, error) {
1494 return cmd.rawVal, cmd.err
1495 }
1496 func (cmd *FTInfoCmd) readReply(rd *proto.Reader) (err error) {
1497 n, err := rd.ReadMapLen()
1498 if err != nil {
1499 return err
1500 }
1501
1502 data := make(map[string]interface{}, n)
1503 for i := 0; i < n; i++ {
1504 k, err := rd.ReadString()
1505 if err != nil {
1506 return err
1507 }
1508 v, err := rd.ReadReply()
1509 if err != nil {
1510 if err == Nil {
1511 data[k] = Nil
1512 continue
1513 }
1514 if err, ok := err.(proto.RedisError); ok {
1515 data[k] = err
1516 continue
1517 }
1518 return err
1519 }
1520 data[k] = v
1521 }
1522 cmd.val, err = parseFTInfo(data)
1523 if err != nil {
1524 return err
1525 }
1526
1527 return nil
1528 }
1529
1530
1531
1532
1533
1534 func (c cmdable) FTInfo(ctx context.Context, index string) *FTInfoCmd {
1535 cmd := newFTInfoCmd(ctx, "FT.INFO", index)
1536 _ = c(ctx, cmd)
1537 return cmd
1538 }
1539
1540
1541
1542
1543
1544
1545 func (c cmdable) FTSpellCheck(ctx context.Context, index string, query string) *FTSpellCheckCmd {
1546 args := []interface{}{"FT.SPELLCHECK", index, query}
1547 cmd := newFTSpellCheckCmd(ctx, args...)
1548 _ = c(ctx, cmd)
1549 return cmd
1550 }
1551
1552
1553
1554
1555
1556
1557 func (c cmdable) FTSpellCheckWithArgs(ctx context.Context, index string, query string, options *FTSpellCheckOptions) *FTSpellCheckCmd {
1558 args := []interface{}{"FT.SPELLCHECK", index, query}
1559 if options != nil {
1560 if options.Distance > 0 {
1561 args = append(args, "DISTANCE", options.Distance)
1562 }
1563 if options.Terms != nil {
1564 args = append(args, "TERMS", options.Terms.Inclusion, options.Terms.Dictionary)
1565 args = append(args, options.Terms.Terms...)
1566 }
1567 if options.Dialect > 0 {
1568 args = append(args, "DIALECT", options.Dialect)
1569 } else {
1570 args = append(args, "DIALECT", 2)
1571 }
1572 }
1573 cmd := newFTSpellCheckCmd(ctx, args...)
1574 _ = c(ctx, cmd)
1575 return cmd
1576 }
1577
1578 type FTSpellCheckCmd struct {
1579 baseCmd
1580 val []SpellCheckResult
1581 }
1582
1583 func newFTSpellCheckCmd(ctx context.Context, args ...interface{}) *FTSpellCheckCmd {
1584 return &FTSpellCheckCmd{
1585 baseCmd: baseCmd{
1586 ctx: ctx,
1587 args: args,
1588 },
1589 }
1590 }
1591
1592 func (cmd *FTSpellCheckCmd) String() string {
1593 return cmdString(cmd, cmd.val)
1594 }
1595
1596 func (cmd *FTSpellCheckCmd) SetVal(val []SpellCheckResult) {
1597 cmd.val = val
1598 }
1599
1600 func (cmd *FTSpellCheckCmd) Result() ([]SpellCheckResult, error) {
1601 return cmd.val, cmd.err
1602 }
1603
1604 func (cmd *FTSpellCheckCmd) Val() []SpellCheckResult {
1605 return cmd.val
1606 }
1607
1608 func (cmd *FTSpellCheckCmd) RawVal() interface{} {
1609 return cmd.rawVal
1610 }
1611
1612 func (cmd *FTSpellCheckCmd) RawResult() (interface{}, error) {
1613 return cmd.rawVal, cmd.err
1614 }
1615
1616 func (cmd *FTSpellCheckCmd) readReply(rd *proto.Reader) (err error) {
1617 data, err := rd.ReadSlice()
1618 if err != nil {
1619 return err
1620 }
1621 cmd.val, err = parseFTSpellCheck(data)
1622 if err != nil {
1623 return err
1624 }
1625 return nil
1626 }
1627
1628 func parseFTSpellCheck(data []interface{}) ([]SpellCheckResult, error) {
1629 results := make([]SpellCheckResult, 0, len(data))
1630
1631 for _, termData := range data {
1632 termInfo, ok := termData.([]interface{})
1633 if !ok || len(termInfo) != 3 {
1634 return nil, fmt.Errorf("invalid term format")
1635 }
1636
1637 term, ok := termInfo[1].(string)
1638 if !ok {
1639 return nil, fmt.Errorf("invalid term format")
1640 }
1641
1642 suggestionsData, ok := termInfo[2].([]interface{})
1643 if !ok {
1644 return nil, fmt.Errorf("invalid suggestions format")
1645 }
1646
1647 suggestions := make([]SpellCheckSuggestion, 0, len(suggestionsData))
1648 for _, suggestionData := range suggestionsData {
1649 suggestionInfo, ok := suggestionData.([]interface{})
1650 if !ok || len(suggestionInfo) != 2 {
1651 return nil, fmt.Errorf("invalid suggestion format")
1652 }
1653
1654 scoreStr, ok := suggestionInfo[0].(string)
1655 if !ok {
1656 return nil, fmt.Errorf("invalid suggestion score format")
1657 }
1658 score, err := strconv.ParseFloat(scoreStr, 64)
1659 if err != nil {
1660 return nil, fmt.Errorf("invalid suggestion score value")
1661 }
1662
1663 suggestion, ok := suggestionInfo[1].(string)
1664 if !ok {
1665 return nil, fmt.Errorf("invalid suggestion format")
1666 }
1667
1668 suggestions = append(suggestions, SpellCheckSuggestion{
1669 Score: score,
1670 Suggestion: suggestion,
1671 })
1672 }
1673
1674 results = append(results, SpellCheckResult{
1675 Term: term,
1676 Suggestions: suggestions,
1677 })
1678 }
1679
1680 return results, nil
1681 }
1682
1683 func parseFTSearch(data []interface{}, noContent, withScores, withPayloads, withSortKeys bool) (FTSearchResult, error) {
1684 if len(data) < 1 {
1685 return FTSearchResult{}, fmt.Errorf("unexpected search result format")
1686 }
1687
1688 total, ok := data[0].(int64)
1689 if !ok {
1690 return FTSearchResult{}, fmt.Errorf("invalid total results format")
1691 }
1692
1693 var results []Document
1694 for i := 1; i < len(data); {
1695 docID, ok := data[i].(string)
1696 if !ok {
1697 return FTSearchResult{}, fmt.Errorf("invalid document ID format")
1698 }
1699
1700 doc := Document{
1701 ID: docID,
1702 Fields: make(map[string]string),
1703 }
1704 i++
1705
1706 if noContent {
1707 results = append(results, doc)
1708 continue
1709 }
1710
1711 if withScores && i < len(data) {
1712 if scoreStr, ok := data[i].(string); ok {
1713 score, err := strconv.ParseFloat(scoreStr, 64)
1714 if err != nil {
1715 return FTSearchResult{}, fmt.Errorf("invalid score format")
1716 }
1717 doc.Score = &score
1718 i++
1719 }
1720 }
1721
1722 if withPayloads && i < len(data) {
1723 if payload, ok := data[i].(string); ok {
1724 doc.Payload = &payload
1725 i++
1726 }
1727 }
1728
1729 if withSortKeys && i < len(data) {
1730 if sortKey, ok := data[i].(string); ok {
1731 doc.SortKey = &sortKey
1732 i++
1733 }
1734 }
1735
1736 if i < len(data) {
1737 fields, ok := data[i].([]interface{})
1738 if !ok {
1739 if data[i] == proto.Nil || data[i] == nil {
1740 doc.Error = proto.Nil
1741 doc.Fields = map[string]string{}
1742 fields = []interface{}{}
1743 } else {
1744 return FTSearchResult{}, fmt.Errorf("invalid document fields format")
1745 }
1746 }
1747
1748 for j := 0; j < len(fields); j += 2 {
1749 key, ok := fields[j].(string)
1750 if !ok {
1751 return FTSearchResult{}, fmt.Errorf("invalid field key format")
1752 }
1753 value, ok := fields[j+1].(string)
1754 if !ok {
1755 return FTSearchResult{}, fmt.Errorf("invalid field value format")
1756 }
1757 doc.Fields[key] = value
1758 }
1759 i++
1760 }
1761
1762 results = append(results, doc)
1763 }
1764 return FTSearchResult{
1765 Total: int(total),
1766 Docs: results,
1767 }, nil
1768 }
1769
1770 type FTSearchCmd struct {
1771 baseCmd
1772 val FTSearchResult
1773 options *FTSearchOptions
1774 }
1775
1776 func newFTSearchCmd(ctx context.Context, options *FTSearchOptions, args ...interface{}) *FTSearchCmd {
1777 return &FTSearchCmd{
1778 baseCmd: baseCmd{
1779 ctx: ctx,
1780 args: args,
1781 },
1782 options: options,
1783 }
1784 }
1785
1786 func (cmd *FTSearchCmd) String() string {
1787 return cmdString(cmd, cmd.val)
1788 }
1789
1790 func (cmd *FTSearchCmd) SetVal(val FTSearchResult) {
1791 cmd.val = val
1792 }
1793
1794 func (cmd *FTSearchCmd) Result() (FTSearchResult, error) {
1795 return cmd.val, cmd.err
1796 }
1797
1798 func (cmd *FTSearchCmd) Val() FTSearchResult {
1799 return cmd.val
1800 }
1801
1802 func (cmd *FTSearchCmd) RawVal() interface{} {
1803 return cmd.rawVal
1804 }
1805
1806 func (cmd *FTSearchCmd) RawResult() (interface{}, error) {
1807 return cmd.rawVal, cmd.err
1808 }
1809
1810 func (cmd *FTSearchCmd) readReply(rd *proto.Reader) (err error) {
1811 data, err := rd.ReadSlice()
1812 if err != nil {
1813 return err
1814 }
1815 cmd.val, err = parseFTSearch(data, cmd.options.NoContent, cmd.options.WithScores, cmd.options.WithPayloads, cmd.options.WithSortKeys)
1816 if err != nil {
1817 return err
1818 }
1819 return nil
1820 }
1821
1822
1823
1824
1825
1826
1827 func (c cmdable) FTSearch(ctx context.Context, index string, query string) *FTSearchCmd {
1828 args := []interface{}{"FT.SEARCH", index, query}
1829 cmd := newFTSearchCmd(ctx, &FTSearchOptions{}, args...)
1830 _ = c(ctx, cmd)
1831 return cmd
1832 }
1833
1834 type SearchQuery []interface{}
1835
1836
1837
1838
1839
1840
1841
1842 func FTSearchQuery(query string, options *FTSearchOptions) (SearchQuery, error) {
1843 queryArgs := []interface{}{query}
1844 if options != nil {
1845 if options.NoContent {
1846 queryArgs = append(queryArgs, "NOCONTENT")
1847 }
1848 if options.Verbatim {
1849 queryArgs = append(queryArgs, "VERBATIM")
1850 }
1851 if options.NoStopWords {
1852 queryArgs = append(queryArgs, "NOSTOPWORDS")
1853 }
1854 if options.WithScores {
1855 queryArgs = append(queryArgs, "WITHSCORES")
1856 }
1857 if options.WithPayloads {
1858 queryArgs = append(queryArgs, "WITHPAYLOADS")
1859 }
1860 if options.WithSortKeys {
1861 queryArgs = append(queryArgs, "WITHSORTKEYS")
1862 }
1863 if options.Filters != nil {
1864 for _, filter := range options.Filters {
1865 queryArgs = append(queryArgs, "FILTER", filter.FieldName, filter.Min, filter.Max)
1866 }
1867 }
1868 if options.GeoFilter != nil {
1869 for _, geoFilter := range options.GeoFilter {
1870 queryArgs = append(queryArgs, "GEOFILTER", geoFilter.FieldName, geoFilter.Longitude, geoFilter.Latitude, geoFilter.Radius, geoFilter.Unit)
1871 }
1872 }
1873 if options.InKeys != nil {
1874 queryArgs = append(queryArgs, "INKEYS", len(options.InKeys))
1875 queryArgs = append(queryArgs, options.InKeys...)
1876 }
1877 if options.InFields != nil {
1878 queryArgs = append(queryArgs, "INFIELDS", len(options.InFields))
1879 queryArgs = append(queryArgs, options.InFields...)
1880 }
1881 if options.Return != nil {
1882 queryArgs = append(queryArgs, "RETURN")
1883 queryArgsReturn := []interface{}{}
1884 for _, ret := range options.Return {
1885 queryArgsReturn = append(queryArgsReturn, ret.FieldName)
1886 if ret.As != "" {
1887 queryArgsReturn = append(queryArgsReturn, "AS", ret.As)
1888 }
1889 }
1890 queryArgs = append(queryArgs, len(queryArgsReturn))
1891 queryArgs = append(queryArgs, queryArgsReturn...)
1892 }
1893 if options.Slop > 0 {
1894 queryArgs = append(queryArgs, "SLOP", options.Slop)
1895 }
1896 if options.Timeout > 0 {
1897 queryArgs = append(queryArgs, "TIMEOUT", options.Timeout)
1898 }
1899 if options.InOrder {
1900 queryArgs = append(queryArgs, "INORDER")
1901 }
1902 if options.Language != "" {
1903 queryArgs = append(queryArgs, "LANGUAGE", options.Language)
1904 }
1905 if options.Expander != "" {
1906 queryArgs = append(queryArgs, "EXPANDER", options.Expander)
1907 }
1908 if options.Scorer != "" {
1909 queryArgs = append(queryArgs, "SCORER", options.Scorer)
1910 }
1911 if options.ExplainScore {
1912 queryArgs = append(queryArgs, "EXPLAINSCORE")
1913 }
1914 if options.Payload != "" {
1915 queryArgs = append(queryArgs, "PAYLOAD", options.Payload)
1916 }
1917 if options.SortBy != nil {
1918 queryArgs = append(queryArgs, "SORTBY")
1919 for _, sortBy := range options.SortBy {
1920 queryArgs = append(queryArgs, sortBy.FieldName)
1921 if sortBy.Asc && sortBy.Desc {
1922 return nil, fmt.Errorf("FT.SEARCH: ASC and DESC are mutually exclusive")
1923 }
1924 if sortBy.Asc {
1925 queryArgs = append(queryArgs, "ASC")
1926 }
1927 if sortBy.Desc {
1928 queryArgs = append(queryArgs, "DESC")
1929 }
1930 }
1931 if options.SortByWithCount {
1932 queryArgs = append(queryArgs, "WITHCOUNT")
1933 }
1934 }
1935 if options.LimitOffset >= 0 && options.Limit > 0 {
1936 queryArgs = append(queryArgs, "LIMIT", options.LimitOffset, options.Limit)
1937 }
1938 if options.Params != nil {
1939 queryArgs = append(queryArgs, "PARAMS", len(options.Params)*2)
1940 for key, value := range options.Params {
1941 queryArgs = append(queryArgs, key, value)
1942 }
1943 }
1944 if options.DialectVersion > 0 {
1945 queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
1946 } else {
1947 queryArgs = append(queryArgs, "DIALECT", 2)
1948 }
1949 }
1950 return queryArgs, nil
1951 }
1952
1953
1954
1955
1956
1957
1958
1959 func (c cmdable) FTSearchWithArgs(ctx context.Context, index string, query string, options *FTSearchOptions) *FTSearchCmd {
1960 args := []interface{}{"FT.SEARCH", index, query}
1961 if options != nil {
1962 if options.NoContent {
1963 args = append(args, "NOCONTENT")
1964 }
1965 if options.Verbatim {
1966 args = append(args, "VERBATIM")
1967 }
1968 if options.NoStopWords {
1969 args = append(args, "NOSTOPWORDS")
1970 }
1971 if options.WithScores {
1972 args = append(args, "WITHSCORES")
1973 }
1974 if options.WithPayloads {
1975 args = append(args, "WITHPAYLOADS")
1976 }
1977 if options.WithSortKeys {
1978 args = append(args, "WITHSORTKEYS")
1979 }
1980 if options.Filters != nil {
1981 for _, filter := range options.Filters {
1982 args = append(args, "FILTER", filter.FieldName, filter.Min, filter.Max)
1983 }
1984 }
1985 if options.GeoFilter != nil {
1986 for _, geoFilter := range options.GeoFilter {
1987 args = append(args, "GEOFILTER", geoFilter.FieldName, geoFilter.Longitude, geoFilter.Latitude, geoFilter.Radius, geoFilter.Unit)
1988 }
1989 }
1990 if options.InKeys != nil {
1991 args = append(args, "INKEYS", len(options.InKeys))
1992 args = append(args, options.InKeys...)
1993 }
1994 if options.InFields != nil {
1995 args = append(args, "INFIELDS", len(options.InFields))
1996 args = append(args, options.InFields...)
1997 }
1998 if options.Return != nil {
1999 args = append(args, "RETURN")
2000 argsReturn := []interface{}{}
2001 for _, ret := range options.Return {
2002 argsReturn = append(argsReturn, ret.FieldName)
2003 if ret.As != "" {
2004 argsReturn = append(argsReturn, "AS", ret.As)
2005 }
2006 }
2007 args = append(args, len(argsReturn))
2008 args = append(args, argsReturn...)
2009 }
2010 if options.Slop > 0 {
2011 args = append(args, "SLOP", options.Slop)
2012 }
2013 if options.Timeout > 0 {
2014 args = append(args, "TIMEOUT", options.Timeout)
2015 }
2016 if options.InOrder {
2017 args = append(args, "INORDER")
2018 }
2019 if options.Language != "" {
2020 args = append(args, "LANGUAGE", options.Language)
2021 }
2022 if options.Expander != "" {
2023 args = append(args, "EXPANDER", options.Expander)
2024 }
2025 if options.Scorer != "" {
2026 args = append(args, "SCORER", options.Scorer)
2027 }
2028 if options.ExplainScore {
2029 args = append(args, "EXPLAINSCORE")
2030 }
2031 if options.Payload != "" {
2032 args = append(args, "PAYLOAD", options.Payload)
2033 }
2034 if options.SortBy != nil {
2035 args = append(args, "SORTBY")
2036 for _, sortBy := range options.SortBy {
2037 args = append(args, sortBy.FieldName)
2038 if sortBy.Asc && sortBy.Desc {
2039 cmd := newFTSearchCmd(ctx, options, args...)
2040 cmd.SetErr(fmt.Errorf("FT.SEARCH: ASC and DESC are mutually exclusive"))
2041 return cmd
2042 }
2043 if sortBy.Asc {
2044 args = append(args, "ASC")
2045 }
2046 if sortBy.Desc {
2047 args = append(args, "DESC")
2048 }
2049 }
2050 if options.SortByWithCount {
2051 args = append(args, "WITHCOUNT")
2052 }
2053 }
2054 if options.CountOnly {
2055 args = append(args, "LIMIT", 0, 0)
2056 } else {
2057 if options.LimitOffset >= 0 && options.Limit > 0 || options.LimitOffset > 0 && options.Limit == 0 {
2058 args = append(args, "LIMIT", options.LimitOffset, options.Limit)
2059 }
2060 }
2061 if options.Params != nil {
2062 args = append(args, "PARAMS", len(options.Params)*2)
2063 for key, value := range options.Params {
2064 args = append(args, key, value)
2065 }
2066 }
2067 if options.DialectVersion > 0 {
2068 args = append(args, "DIALECT", options.DialectVersion)
2069 } else {
2070 args = append(args, "DIALECT", 2)
2071 }
2072 }
2073 cmd := newFTSearchCmd(ctx, options, args...)
2074 _ = c(ctx, cmd)
2075 return cmd
2076 }
2077
2078 func NewFTSynDumpCmd(ctx context.Context, args ...interface{}) *FTSynDumpCmd {
2079 return &FTSynDumpCmd{
2080 baseCmd: baseCmd{
2081 ctx: ctx,
2082 args: args,
2083 },
2084 }
2085 }
2086
2087 func (cmd *FTSynDumpCmd) String() string {
2088 return cmdString(cmd, cmd.val)
2089 }
2090
2091 func (cmd *FTSynDumpCmd) SetVal(val []FTSynDumpResult) {
2092 cmd.val = val
2093 }
2094
2095 func (cmd *FTSynDumpCmd) Val() []FTSynDumpResult {
2096 return cmd.val
2097 }
2098
2099 func (cmd *FTSynDumpCmd) Result() ([]FTSynDumpResult, error) {
2100 return cmd.val, cmd.err
2101 }
2102
2103 func (cmd *FTSynDumpCmd) RawVal() interface{} {
2104 return cmd.rawVal
2105 }
2106
2107 func (cmd *FTSynDumpCmd) RawResult() (interface{}, error) {
2108 return cmd.rawVal, cmd.err
2109 }
2110
2111 func (cmd *FTSynDumpCmd) readReply(rd *proto.Reader) error {
2112 termSynonymPairs, err := rd.ReadSlice()
2113 if err != nil {
2114 return err
2115 }
2116
2117 var results []FTSynDumpResult
2118 for i := 0; i < len(termSynonymPairs); i += 2 {
2119 term, ok := termSynonymPairs[i].(string)
2120 if !ok {
2121 return fmt.Errorf("invalid term format")
2122 }
2123
2124 synonyms, ok := termSynonymPairs[i+1].([]interface{})
2125 if !ok {
2126 return fmt.Errorf("invalid synonyms format")
2127 }
2128
2129 synonymList := make([]string, len(synonyms))
2130 for j, syn := range synonyms {
2131 synonym, ok := syn.(string)
2132 if !ok {
2133 return fmt.Errorf("invalid synonym format")
2134 }
2135 synonymList[j] = synonym
2136 }
2137
2138 results = append(results, FTSynDumpResult{
2139 Term: term,
2140 Synonyms: synonymList,
2141 })
2142 }
2143
2144 cmd.val = results
2145 return nil
2146 }
2147
2148
2149
2150
2151
2152 func (c cmdable) FTSynDump(ctx context.Context, index string) *FTSynDumpCmd {
2153 cmd := NewFTSynDumpCmd(ctx, "FT.SYNDUMP", index)
2154 _ = c(ctx, cmd)
2155 return cmd
2156 }
2157
2158
2159
2160
2161
2162 func (c cmdable) FTSynUpdate(ctx context.Context, index string, synGroupId interface{}, terms []interface{}) *StatusCmd {
2163 args := []interface{}{"FT.SYNUPDATE", index, synGroupId}
2164 args = append(args, terms...)
2165 cmd := NewStatusCmd(ctx, args...)
2166 _ = c(ctx, cmd)
2167 return cmd
2168 }
2169
2170
2171
2172
2173
2174 func (c cmdable) FTSynUpdateWithArgs(ctx context.Context, index string, synGroupId interface{}, options *FTSynUpdateOptions, terms []interface{}) *StatusCmd {
2175 args := []interface{}{"FT.SYNUPDATE", index, synGroupId}
2176 if options.SkipInitialScan {
2177 args = append(args, "SKIPINITIALSCAN")
2178 }
2179 args = append(args, terms...)
2180 cmd := NewStatusCmd(ctx, args...)
2181 _ = c(ctx, cmd)
2182 return cmd
2183 }
2184
2185
2186
2187
2188
2189 func (c cmdable) FTTagVals(ctx context.Context, index string, field string) *StringSliceCmd {
2190 cmd := NewStringSliceCmd(ctx, "FT.TAGVALS", index, field)
2191 _ = c(ctx, cmd)
2192 return cmd
2193 }
2194
View as plain text