Skip to content

Commit dbf0869

Browse files
authored
Merge pull request #1 from RedisLabs/mark_fixes
"a large number of fixes and cleanups"
2 parents ed2e723 + b4d3b1a commit dbf0869

File tree

3 files changed

+205
-21
lines changed

3 files changed

+205
-21
lines changed

redisearch/client.go

Lines changed: 183 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package redisearch
33
import (
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
4143
type Client struct {
4244
pool *redis.Pool
43-
44-
schema *Schema
45-
name string
45+
name string
4646
}
4747

4848
var 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
7574
func (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
152162
var 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+
}

redisearch/redisearch_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,25 @@ package redisearch_test
33
import (
44
"fmt"
55
"log"
6+
"os"
67
"testing"
78
"time"
89

910
"github.com/RedisLabs/redisearch-go/redisearch"
1011
)
1112

13+
func createClient(indexName string) *redisearch.Client {
14+
value, exists := os.LookupEnv("REDISEARCH_TEST_HOST")
15+
host := "localhost:6379"
16+
if exists && value != "" {
17+
host = value
18+
}
19+
return redisearch.NewClient(host, indexName)
20+
}
21+
1222
func TestClient(t *testing.T) {
1323

14-
c := redisearch.NewClient("localhost:6379", "testung")
24+
c := createClient("testung")
1525

1626
sc := redisearch.NewSchema(redisearch.DefaultOptions).
1727
AddField(redisearch.NewTextField("foo"))
@@ -25,10 +35,17 @@ func TestClient(t *testing.T) {
2535
docs[i] = redisearch.NewDocument(fmt.Sprintf("doc%d", i), float32(i)/float32(100)).Set("foo", "hello world")
2636
}
2737

28-
if err := c.Index(docs, redisearch.DefaultIndexingOptions); err != nil {
38+
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, docs...); err != nil {
2939
t.Fatal(err)
3040
}
3141

42+
// Test it again
43+
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, docs...); err == nil {
44+
t.Fatal("Expected error for duplicate document")
45+
} else if len(err) != 100 {
46+
t.Fatal("Not enough errors received")
47+
}
48+
3249
docs, total, err := c.Search(redisearch.NewQuery("hello world"))
3350
fmt.Println(docs, total, err)
3451
}
@@ -37,7 +54,7 @@ func ExampleClient() {
3754

3855
// Create a client. By default a client is schemaless
3956
// unless a schema is provided when creating the index
40-
c := redisearch.NewClient("localhost:6379", "myIndex")
57+
c := createClient("myIndex")
4158

4259
// Create a schema
4360
sc := redisearch.NewSchema(redisearch.DefaultOptions).
@@ -60,8 +77,7 @@ func ExampleClient() {
6077
Set("date", time.Now().Unix())
6178

6279
// Index the document. The API accepts multiple documents at a time
63-
if err := c.Index([]redisearch.Document{doc},
64-
redisearch.DefaultIndexingOptions); err != nil {
80+
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, doc); err != nil {
6581
log.Fatal(err)
6682
}
6783

redisearch/schema.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Field struct {
3232
type TextFieldOptions struct {
3333
Weight float32
3434
Sortable bool
35+
NoStem bool
3536
}
3637

3738
// NumericFieldOptions Options for numeric fields

0 commit comments

Comments
 (0)