No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

2194 líneas
66 KiB

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