@@ -3,7 +3,9 @@ package redisearch
33import (
44 "errors"
55 "fmt"
6+ "reflect"
67 "strconv"
8+ "strings"
79
810 "time"
911
@@ -40,9 +42,7 @@ var DefaultOptions = Options{
4042// Cleint is an interface to redisearch's redis commands
4143type Client struct {
4244 pool * redis.Pool
43-
44- schema * Schema
45- name string
45+ name string
4646}
4747
4848var maxConns = 500
@@ -55,8 +55,7 @@ func NewClient(addr, name string) *Client {
5555 // TODO: Add timeouts. and 2 separate pools for indexing and querying, with different timeouts
5656 return redis .Dial ("tcp" , addr )
5757 }, maxConns ),
58- schema : nil ,
59- name : name ,
58+ name : name ,
6059 }
6160
6261 ret .pool .TestOnBorrow = func (c redis.Conn , t time.Time ) (err error ) {
@@ -73,7 +72,6 @@ func NewClient(addr, name string) *Client {
7372
7473// CreateIndex configues the index and creates it on redis
7574func (i * Client ) CreateIndex (s * Schema ) error {
76- i .schema = s
7775 args := redis.Args {i .name }
7876 // Set flags based on options
7977 if s .Options .NoFieldFlags {
@@ -93,7 +91,7 @@ func (i *Client) CreateIndex(s *Schema) error {
9391 }
9492
9593 args = append (args , "SCHEMA" )
96- for _ , f := range i . schema .Fields {
94+ for _ , f := range s .Fields {
9795
9896 switch f .Type {
9997 case TextField :
@@ -110,7 +108,9 @@ func (i *Client) CreateIndex(s *Schema) error {
110108 if opts .Sortable {
111109 args = append (args , "SORTABLE" )
112110 }
113-
111+ if opts .NoStem {
112+ args = append (args , "NOSTEM" )
113+ }
114114 }
115115
116116 case NumericField :
@@ -148,6 +148,16 @@ type IndexingOptions struct {
148148 Replace bool
149149}
150150
151+ // MultiError Represents one or more errors
152+ type MultiError map [int ]error
153+
154+ func (e MultiError ) Error () string {
155+ for _ , err := range e {
156+ return err .Error ()
157+ }
158+ return ""
159+ }
160+
151161// DefaultIndexingOptions are the default options for document indexing
152162var DefaultIndexingOptions = IndexingOptions {
153163 Language : "" ,
@@ -156,15 +166,16 @@ var DefaultIndexingOptions = IndexingOptions{
156166}
157167
158168// Index indexes multiple documents on the index, with optional Options passed to options
159- func (i * Client ) IndexOptions (opts IndexingOptions , docs ... Document ) error {
169+ func (i * Client ) IndexOptions (opts IndexingOptions , docs ... Document ) ( errors MultiError ) {
160170
161171 conn := i .pool .Get ()
162172 defer conn .Close ()
163173
164174 n := 0
175+ errors = make (map [int ]error )
165176
166- for _ , doc := range docs {
167- args := make (redis.Args , 0 , len (i . schema . Fields ) * 2 + 6 )
177+ for ii , doc := range docs {
178+ args := make (redis.Args , 0 , 6 + len (doc . Properties ) )
168179 args = append (args , i .name , doc .Id , doc .Score )
169180 // apply options
170181 if opts .NoSave {
@@ -188,23 +199,29 @@ func (i *Client) IndexOptions(opts IndexingOptions, docs ...Document) error {
188199 }
189200
190201 if err := conn .Send ("FT.ADD" , args ... ); err != nil {
191- return err
202+ errors [ii ] = err
203+ return
192204 }
193205 n ++
194206 }
195207
196208 if err := conn .Flush (); err != nil {
197- return err
209+ errors [- 1 ] = err
210+ return
198211 }
199212
200213 for n > 0 {
201214 if _ , err := conn .Receive (); err != nil {
202- return err
215+ errors [ n - 1 ] = err
203216 }
204217 n --
205218 }
206219
207- return nil
220+ if len (errors ) == 0 {
221+ return nil
222+ }
223+
224+ return
208225}
209226
210227// convert the result from a redis query to a proper Document object
@@ -243,7 +260,7 @@ func loadDocument(arr []interface{}, idIdx, scoreIdx, payloadIdx, fieldsIdx int)
243260 return doc , nil
244261}
245262
246- func (i * Client ) Index (docs ... Document ) error {
263+ func (i * Client ) Index (docs ... Document ) map [ int ] error {
247264 return i .IndexOptions (DefaultIndexingOptions , docs ... )
248265}
249266
@@ -307,3 +324,153 @@ func (i *Client) Drop() error {
307324 return err
308325
309326}
327+
328+ // IndexInfo - Structure showing information about an existing index
329+ type IndexInfo struct {
330+ Schema Schema
331+ Name string `redis:"index_name"`
332+ DocCount uint64 `redis:"num_docs"`
333+ RecordCount uint64 `redis:"num_records"`
334+ TermCount uint64 `redis:"num_terms"`
335+ MaxDocID uint64 `redis:"max_doc_id"`
336+ InvertedIndexSizeMB float64 `redis:"inverted_sz_mb"`
337+ OffsetVectorSizeMB float64 `redis:"offset_vector_sz_mb"`
338+ DocTableSizeMB float64 `redis:"doc_table_size_mb"`
339+ KeyTableSizeMB float64 `redis:"key_table_size_mb"`
340+ RecordsPerDocAvg float64 `redis:"records_per_doc_avg"`
341+ BytesPerRecordAvg float64 `redis:"bytes_per_record_avg"`
342+ OffsetsPerTermAvg float64 `redis:"offsets_per_term_avg"`
343+ OffsetBitsPerTermAvg float64 `redis:"offset_bits_per_record_avg"`
344+ }
345+
346+ func (info * IndexInfo ) setTarget (key string , value interface {}) error {
347+ v := reflect .ValueOf (info ).Elem ()
348+ for i := 0 ; i < v .NumField (); i ++ {
349+ tag := v .Type ().Field (i ).Tag .Get ("redis" )
350+ if tag == key {
351+ targetInfo := v .Field (i )
352+ switch targetInfo .Kind () {
353+ case reflect .String :
354+ s , _ := redis .String (value , nil )
355+ targetInfo .SetString (s )
356+ case reflect .Uint64 :
357+ u , _ := redis .Uint64 (value , nil )
358+ targetInfo .SetUint (u )
359+ case reflect .Float64 :
360+ f , _ := redis .Float64 (value , nil )
361+ targetInfo .SetFloat (f )
362+ default :
363+ panic ("Tag set without handler" )
364+ }
365+ return nil
366+ }
367+ }
368+ return errors .New ("setTarget: No handler defined for :" + key )
369+ }
370+
371+ func sliceIndex (haystack []string , needle string ) int {
372+ for pos , elem := range haystack {
373+ if elem == needle {
374+ return pos
375+ }
376+ }
377+ return - 1
378+ }
379+
380+ func (info * IndexInfo ) loadSchema (values []interface {}, options []string ) {
381+ // Values are a list of fields
382+ scOptions := Options {}
383+ for _ , opt := range options {
384+ switch strings .ToUpper (opt ) {
385+ case "NOFIELDS" :
386+ scOptions .NoFieldFlags = true
387+ case "NOFREQS" :
388+ scOptions .NoFrequencies = true
389+ case "NOOFFSETS" :
390+ scOptions .NoOffsetVectors = true
391+ }
392+ }
393+ sc := NewSchema (scOptions )
394+ for _ , specTmp := range values {
395+ // spec, isArr := specTmp.([]string)
396+ // if !isArr {
397+ // panic("Value is not an array of strings!")
398+ // }
399+ spec , err := redis .Strings (specTmp , nil )
400+ if err != nil {
401+ panic (err )
402+ }
403+ // Name, Type,
404+ if len (spec ) < 3 {
405+ panic ("Invalid spec" )
406+ }
407+ var options []string
408+ if len (spec ) > 3 {
409+ options = spec [3 :]
410+ } else {
411+ options = []string {}
412+ }
413+
414+ f := Field {Name : spec [0 ]}
415+ switch strings .ToUpper (spec [2 ]) {
416+ case "NUMERIC" :
417+ f .Type = NumericField
418+ nfOptions := NumericFieldOptions {}
419+ f .Options = nfOptions
420+ if sliceIndex (options , "SORTABLE" ) != - 1 {
421+ nfOptions .Sortable = true
422+ }
423+ case "TEXT" :
424+ f .Type = TextField
425+ tfOptions := TextFieldOptions {}
426+ f .Options = tfOptions
427+ if sliceIndex (options , "SORTABLE" ) != - 1 {
428+ tfOptions .Sortable = true
429+ }
430+ if wIdx := sliceIndex (options , "WEIGHT" ); wIdx != - 1 && wIdx + 1 != len (spec ) {
431+ weightString := options [wIdx + 1 ]
432+ weight64 , _ := strconv .ParseFloat (weightString , 32 )
433+ tfOptions .Weight = float32 (weight64 )
434+ }
435+ }
436+ sc = sc .AddField (f )
437+ }
438+ info .Schema = * sc
439+ }
440+
441+ // Info - Get information about the index. This can also be used to check if the
442+ // index exists
443+ func (i * Client ) Info () (* IndexInfo , error ) {
444+ conn := i .pool .Get ()
445+ defer conn .Close ()
446+
447+ res , err := redis .Values (conn .Do ("FT.INFO" , i .name ))
448+ if err != nil {
449+ return nil , err
450+ }
451+
452+ ret := IndexInfo {}
453+ var schemaFields []interface {}
454+ var indexOptions []string
455+
456+ // Iterate over the values
457+ for ii := 0 ; ii < len (res ); ii += 2 {
458+ key , _ := redis .String (res [ii ], nil )
459+ if err := ret .setTarget (key , res [ii + 1 ]); err == nil {
460+ continue
461+ }
462+
463+ switch key {
464+ case "index_options" :
465+ indexOptions , _ = redis .Strings (res [ii + 1 ], nil )
466+ case "fields" :
467+ schemaFields , _ = redis .Values (res [ii + 1 ], nil )
468+ }
469+ }
470+
471+ if schemaFields != nil {
472+ ret .loadSchema (schemaFields , indexOptions )
473+ }
474+
475+ return & ret , nil
476+ }
0 commit comments