Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions redisearch/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package redisearch

// Common filter
type Filter struct {
Field string
Options interface{}
}

// Filter the results to a given radius from lon and lat. Radius is given as a number and units
type GeoFilterOptions struct {
Lon float64
Lat float64
Radius float64
Unit Unit
}

// Limit results to those having numeric values ranging between min and max. min and max follow ZRANGE syntax, and can be -inf, +inf
type NumericFilterOptions struct {
Min float64
ExclusiveMin bool
Max float64
ExclusiveMax bool
}

// units of Radius
type Unit string

const (
KILOMETERS Unit = "km"
METERS Unit = "m"
FEET Unit = "ft"
MILES Unit = "mi"
)
45 changes: 43 additions & 2 deletions redisearch/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package redisearch

import (
"github.com/gomodule/redigo/redis"
"math"
)

// Flag is a type for query flags
Expand Down Expand Up @@ -79,7 +80,7 @@ type Query struct {
Flags Flag
Slop int

Filters []Predicate
Filters []Filter
InKeys []string
ReturnFields []string
Language string
Expand Down Expand Up @@ -120,7 +121,7 @@ func (p Paging) serialize() redis.Args {
func NewQuery(raw string) *Query {
return &Query{
Raw: raw,
Filters: []Predicate{},
Filters: []Filter{},
Paging: Paging{DefaultOffset, DefaultNum},
}
}
Expand Down Expand Up @@ -197,9 +198,49 @@ func (q Query) serialize() redis.Args {
args = args.Add("SEPARATOR", q.SummarizeOpts.Separator)
}
}

if q.Filters != nil {
for _, f := range q.Filters {
if f.Options != nil {
switch f.Options.(type) {
case NumericFilterOptions:
opts, _ := f.Options.(NumericFilterOptions)
args = append(args, "FILTER", f.Field)
args = appendNumArgs(opts.Min, opts.ExclusiveMin, args)
args = appendNumArgs(opts.Max, opts.ExclusiveMax, args)
case GeoFilterOptions:
opts, _ := f.Options.(GeoFilterOptions)
args = append(args, "GEOFILTER", f.Field, opts.Lon, opts.Lat, opts.Radius, opts.Unit)
}
}
}
}
return args
}

func appendNumArgs(num float64, exclude bool, args redis.Args) redis.Args {
if math.IsInf(num, 1) {
return append(args, "+inf")
}
if math.IsInf(num, -1) {
return append(args, "-inf")
}

if exclude {
return append(args, "(", num)
}
return append(args, num)
}

// AddFilter adds a filter to the query
func (q *Query) AddFilter(f Filter) *Query {
if q.Filters == nil {
q.Filters = []Filter{}
}
q.Filters = append(q.Filters, f)
return q
}

// // AddPredicate adds a predicate to the query's filters
// func (q *Query) AddPredicate(p Predicate) *Query {
// q.Predicates = append(q.Predicates, p)
Expand Down
71 changes: 58 additions & 13 deletions redisearch/redisearch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ func getTestConnectionDetails() (string, string) {
return host, password
}


func createClient(indexName string) *Client {
host, password := getTestConnectionDetails()
if password != "" {
Expand All @@ -39,12 +38,11 @@ func createClient(indexName string) *Client {
return err
}
return NewClientFromPool(pool, indexName)
}else{
} else {
return NewClient(host, indexName)
}
}


func createAutocompleter(dictName string) *Autocompleter {
host, password := getTestConnectionDetails()
if password != "" {
Expand Down Expand Up @@ -398,9 +396,9 @@ func TestSpellCheck(t *testing.T) {
}

assert.Nil(t, c.Index(docs...))
query := NewQuery("Anla Portuga" )
opts := NewSpellCheckOptions(2 )
sugs, total, err := c.SpellCheck(query,opts )
query := NewQuery("Anla Portuga")
opts := NewSpellCheckOptions(2)
sugs, total, err := c.SpellCheck(query, opts)
assert.Nil(t, err)
assert.Equal(t, 2, len(sugs))
assert.Equal(t, 2, total)
Expand All @@ -409,21 +407,68 @@ func TestSpellCheck(t *testing.T) {
// 1) 1) "TERM"
// 2) "an"
// 3) (empty list or set)
queryEmpty := NewQuery("An" )
sugs, total, err = c.SpellCheck(queryEmpty,opts )
queryEmpty := NewQuery("An")
sugs, total, err = c.SpellCheck(queryEmpty, opts)
assert.Nil(t, err)
assert.Equal(t, 1, len(sugs))
assert.Equal(t, 0, total)

// same query but now with a distance of 4
opts.SetDistance(4)
sugs, total, err = c.SpellCheck(queryEmpty,opts )
sugs, total, err = c.SpellCheck(queryEmpty, opts)
assert.Nil(t, err)
assert.Equal(t, 1, len(sugs))
assert.Equal(t, 1, total)

}

func TestFilter(t *testing.T) {
c := createClient("testFilter")
// Create a schema
sc := NewSchema(DefaultOptions).
AddField(NewTextField("body")).
AddField(NewTextFieldOptions("title", TextFieldOptions{Weight: 5.0, Sortable: true})).
AddField(NewNumericField("age")).
AddField(NewGeoFieldOptions("location", GeoFieldOptions{}))

// Drop an existing index. If the index does not exist an error is returned
c.Drop()
assert.Nil(t, c.CreateIndex(sc))

// Create a document with an id and given score
doc := NewDocument("doc1", 1.0)
doc.Set("title", "Hello world").
Set("body", "foo bar").
Set("age", 18).
Set("location", "13.361389,38.115556")

assert.Nil(t, c.IndexOptions(DefaultIndexingOptions, doc))
// Searching with NumericFilter
docs, total, err := c.Search(NewQuery("hello world").
AddFilter(Filter{Field: "age", Options: NumericFilterOptions{Min: 1, Max: 20}}).
SetSortBy("age", true).
SetReturnFields("body"))
assert.Nil(t, err)
assert.Equal(t, 1, total)
assert.Equal(t, "foo bar", docs[0].Properties["body"])

// Searching with GeoFilter
docs, total, err = c.Search(NewQuery("hello world").
AddFilter(Filter{Field: "location", Options: GeoFilterOptions{Lon: 15, Lat: 37, Radius: 200, Unit: KILOMETERS}}).
SetSortBy("age", true).
SetReturnFields("age"))
assert.Nil(t, err)
assert.Equal(t, 1, total)
assert.Equal(t, "18", docs[0].Properties["age"])

docs, total, err = c.Search(NewQuery("hello world").
AddFilter(Filter{Field: "location", Options: GeoFilterOptions{Lon: 10, Lat: 13, Radius: 1, Unit: KILOMETERS}}).
SetSortBy("age", true).
SetReturnFields("body"))
assert.Nil(t, err)
assert.Equal(t, 0, total)
}

func ExampleClient() {

// Create a client. By default a client is schemaless
Expand All @@ -434,7 +479,8 @@ func ExampleClient() {
sc := NewSchema(DefaultOptions).
AddField(NewTextField("body")).
AddField(NewTextFieldOptions("title", TextFieldOptions{Weight: 5.0, Sortable: true})).
AddField(NewNumericField("date"))
AddField(NewNumericField("date")).
AddField(NewGeoFieldOptions("location", GeoFieldOptions{}))

// Drop an existing index. If the index does not exist an error is returned
c.Drop()
Expand All @@ -448,7 +494,8 @@ func ExampleClient() {
doc := NewDocument("doc1", 1.0)
doc.Set("title", "Hello world").
Set("body", "foo bar").
Set("date", time.Now().Unix())
Set("date", time.Now().Unix()).
Set("location", "13.361389,38.115556")

// Index the document. The API accepts multiple documents at a time
if err := c.IndexOptions(DefaultIndexingOptions, doc); err != nil {
Expand All @@ -463,5 +510,3 @@ func ExampleClient() {
fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err)
// Output: doc1 Hello world 1 <nil>
}


37 changes: 34 additions & 3 deletions redisearch/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ type NumericFieldOptions struct {
NoIndex bool
}

// GeoFieldOptions Options for geo fields
type GeoFieldOptions struct {
NoIndex bool
}

// NewTextField creates a new text field with the given weight
func NewTextField(name string) Field {
return Field{
Expand Down Expand Up @@ -158,6 +163,22 @@ func NewSortableNumericField(name string) Field {
return f
}

// NewGeoField creates a new geo field with the given name
func NewGeoField(name string) Field {
return Field{
Name: name,
Type: GeoField,
Options: nil,
}
}

// NewGeoFieldOptions creates a new geo field with the given name and additional options
func NewGeoFieldOptions(name string, options GeoFieldOptions) Field {
f := NewGeoField(name)
f.Options = options
return f
}

// Schema represents an index schema Schema, or how the index would
// treat documents sent to it.
type Schema struct {
Expand All @@ -168,7 +189,7 @@ type Schema struct {
// NewSchema creates a new Schema object
func NewSchema(opts Options) *Schema {
return &Schema{
Fields: []Field{},
Fields: []Field{},
Options: opts,
}
}
Expand All @@ -182,7 +203,6 @@ func (m *Schema) AddField(f Field) *Schema {
return m
}


func SerializeSchema(s *Schema, args redis.Args) (redis.Args, error) {
if s.Options.NoFieldFlags {
args = append(args, "NOFIELDS")
Expand Down Expand Up @@ -262,10 +282,21 @@ func SerializeSchema(s *Schema, args redis.Args) (redis.Args, error) {
args = append(args, "NOINDEX")
}
}
case GeoField:
args = append(args, f.Name, "GEO")
if f.Options != nil {
opts, ok := f.Options.(GeoFieldOptions)
if !ok {
return nil, errors.New("Invalid geo field options type")
}
if opts.NoIndex {
args = append(args, "NOINDEX")
}
}
default:
return nil, fmt.Errorf("Unsupported field type %v", f.Type)
}

}
return args, nil
}
}