0
0
Fork 0

Adding sort to SearchRequest.

This commit is contained in:
Danny Tylman 2016-08-09 16:18:53 +03:00
parent aa3ae3d39c
commit 5164e70f6e
5 changed files with 289 additions and 2 deletions

View File

@ -384,8 +384,6 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
return nil, ErrorIndexClosed
}
collector := collectors.NewTopScorerSkipCollector(req.Size, req.From)
// open a reader for this search
indexReader, err := i.i.Reader()
if err != nil {
@ -407,6 +405,9 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
}
}()
collector := collectors.NewHeapCollector(req.Size, req.From, indexReader, req.Sort)
//collector := collectors.NewTopScorerSkipCollector(req.Size, req.From)
if req.Facets != nil {
facetsBuilder := search.NewFacetsBuilder(indexReader)
for facetName, facetRequest := range req.Facets {

View File

@ -29,6 +29,9 @@ import (
"github.com/blevesearch/bleve/index"
"github.com/blevesearch/bleve/index/store/null"
"github.com/blevesearch/bleve/search"
"github.com/Pallinder/go-randomdata"
"github.com/divan/num2words"
)
func TestCrud(t *testing.T) {
@ -715,6 +718,52 @@ func TestIndexMetadataRaceBug198(t *testing.T) {
close(done)
}
func TestSortMatchSearch(t *testing.T) {
defer func() {
err := os.RemoveAll("testidx")
if err != nil {
t.Fatal(err)
}
}()
index, err := New("testidx", NewIndexMapping())
if err != nil {
t.Fatal(err)
}
for i := 0; i < 200; i++ {
doc := make(map[string]interface{})
doc["Name"] = randomdata.SillyName()
doc["Day"] = randomdata.Day()
doc["Number"] = num2words.Convert(i)
err = index.Index(fmt.Sprintf("%d", i), doc)
if err != nil {
t.Fatal(err)
}
}
req := NewSearchRequest(NewMatchQuery("one"))
req.SortBy("Day", true)
req.SortBy("Name", true)
req.Fields = []string{"*"}
sr, err := index.Search(req)
if err != nil {
t.Fatal(err)
}
prev := ""
for _, hit := range sr.Hits {
val := hit.Fields["Day"].(string)
if prev > val {
t.Errorf("Hits must be sorted by 'Day'. Found '%s' before '%s'", prev, val)
}
prev = val
}
err = index.Close()
if err != nil {
t.Fatal(err)
}
}
func TestIndexCountMatchSearch(t *testing.T) {
defer func() {
err := os.RemoveAll("testidx")

View File

@ -201,6 +201,7 @@ type SearchRequest struct {
Fields []string `json:"fields"`
Facets FacetsRequest `json:"facets"`
Explain bool `json:"explain"`
Sort search.SortOrder `json:"sort"`
}
func (sr *SearchRequest) Validate() error {
@ -220,6 +221,10 @@ func (r *SearchRequest) AddFacet(facetName string, f *FacetRequest) {
r.Facets[facetName] = f
}
func (r *SearchRequest) SortBy(fieldName string, ascends bool) {
r.Sort = append(r.Sort, search.SearchSort{Field: fieldName, Ascends: ascends})
}
// UnmarshalJSON deserializes a JSON representation of
// a SearchRequest
func (r *SearchRequest) UnmarshalJSON(input []byte) error {

View File

@ -0,0 +1,225 @@
package collectors
import (
"container/heap"
"github.com/blevesearch/bleve/document"
"github.com/blevesearch/bleve/index"
"github.com/blevesearch/bleve/search"
"golang.org/x/net/context"
"time"
)
type collectedDoc struct {
match search.DocumentMatch
doc *document.Document
}
type HeapCollector struct {
size int
skip int
total uint64
took time.Duration
sort search.SortOrder
results []*collectedDoc
facetsBuilder *search.FacetsBuilder
reader index.IndexReader
}
func NewHeapCollector(size int, skip int, reader index.IndexReader, sort search.SortOrder) *HeapCollector {
hc := &HeapCollector{size: size, skip: skip, reader: reader, sort: sort}
heap.Init(hc)
return hc
}
func (hc *HeapCollector) Collect(ctx context.Context, searcher search.Searcher) error {
startTime := time.Now()
var err error
var pre search.DocumentMatch // A single pre-alloc'ed, reused instance.
var next *search.DocumentMatch
select {
case <-ctx.Done():
return ctx.Err()
default:
next, err = searcher.Next(&pre)
}
for err == nil && next != nil {
if hc.total%COLLECT_CHECK_DONE_EVERY == 0 {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
}
hc.collectSingle(next)
if hc.facetsBuilder != nil {
err = hc.facetsBuilder.Update(next)
if err != nil {
break
}
}
next, err = searcher.Next(pre.Reset())
}
// compute search duration
hc.took = time.Since(startTime)
if err != nil {
return err
}
return nil
}
func (hc *HeapCollector) collectSingle(dmIn *search.DocumentMatch) error {
// increment total hits
hc.total++
single := new(collectedDoc)
single.match = *dmIn
var err error
if len(hc.sort) > 0 {
single.doc, err = hc.reader.Document(dmIn.ID)
if err != nil {
return err
}
}
if hc.Len() >= hc.size {
hc.Pop()
}
heap.Push(hc, single)
return nil
}
func (hc *HeapCollector) SetFacetsBuilder(facetsBuilder *search.FacetsBuilder) {
hc.facetsBuilder = facetsBuilder
}
func (hc *HeapCollector) Results() search.DocumentMatchCollection {
rv := make(search.DocumentMatchCollection, hc.Len())
for i := 0; hc.Len() > 0; i++ {
doc := heap.Pop(hc).(*collectedDoc)
rv[i] = &doc.match
}
return rv
}
func (hc *HeapCollector) Total() uint64 {
return hc.total
}
func (hc *HeapCollector) MaxScore() float64 {
return 0
}
func (hc *HeapCollector) FacetResults() search.FacetResults {
if hc.facetsBuilder != nil {
return hc.facetsBuilder.Results()
}
return search.FacetResults{}
}
func (hc *HeapCollector) Len() int {
return len(hc.results)
}
func field(doc *document.Document, field string) document.Field {
if doc == nil {
return nil
}
for _, f := range doc.Fields {
if f.Name() == field {
return f
}
}
return nil
}
func textFieldCompare(i, j *document.TextField, ascends bool) (bool, bool) {
ivalue := string(i.Value())
jvalue := string(j.Value())
if ivalue == jvalue {
return true, false
}
if ascends {
return false, ivalue < jvalue
}
return false, ivalue > jvalue
}
func numericFieldCompare(i, j *document.NumericField, ascends bool) (bool, bool) {
ivalue, _ := i.Number()
jvalue, _ := i.Number()
if ivalue == jvalue {
return true, false
}
if ascends {
return false, ivalue < jvalue
}
return false, ivalue > jvalue
}
func dateTimeFieldCompare(i, j *document.DateTimeField, ascends bool) (bool, bool) {
ivalue, _ := i.DateTime()
jvalue, _ := i.DateTime()
if ivalue.Equal(jvalue) {
return true, false
}
if ascends {
return false, ivalue.Before(jvalue)
}
return false, ivalue.After(jvalue)
}
func boolFieldCompare(i, j *document.BooleanField, ascends bool) (bool, bool) {
ivalue, _ := i.Boolean()
jvalue, _ := i.Boolean()
if ivalue == jvalue {
return true, false
}
return false, ascends && jvalue
}
//returns: equals, less
func fieldCompare(i, j document.Field, ascends bool) (bool, bool) {
switch ft := i.(type) {
case *document.TextField:
return textFieldCompare(ft, j.(*document.TextField), ascends)
case *document.NumericField:
return numericFieldCompare(ft, j.(*document.NumericField), ascends)
case *document.DateTimeField:
return dateTimeFieldCompare(ft, j.(*document.DateTimeField), ascends)
case *document.BooleanField:
return boolFieldCompare(ft, j.(*document.BooleanField), ascends)
}
return false, false
}
func (hc *HeapCollector) Less(i, j int) bool {
if len(hc.sort) > 0 {
doci := hc.results[i].doc
docj := hc.results[j].doc
if doci != nil && docj != nil {
for _, so := range hc.sort {
fieldi := field(doci, so.Field)
fieldj := field(docj, so.Field)
equals, lt := fieldCompare(fieldi, fieldj, so.Ascends)
if !equals {
return lt
}
}
}
}
return hc.results[i].match.Score > hc.results[j].match.Score
}
func (hc *HeapCollector) Swap(i, j int) {
hc.results[i], hc.results[j] = hc.results[j], hc.results[i]
}
func (hc *HeapCollector) Push(x interface{}) {
hc.results = append(hc.results, x.(*collectedDoc))
}
func (hc *HeapCollector) Pop() interface{} {
n := len(hc.results)
doc := hc.results[n-1]
hc.results = hc.results[0 : n-1]
return doc
}

View File

@ -105,3 +105,10 @@ type Searcher interface {
Count() uint64
Min() int
}
type SearchSort struct {
Field string `json:"field"`
Ascends bool `json:"ascends"`
}
type SortOrder []SearchSort