0
0
Fork 0

support JSON marshal/unmarshal of search request sort

The syntax used is an array of strings.  The strings "_id" and
"_score" are special and reserved to mean sorting on the document
id and score repsectively.  All other strings refer to the literal
field name with that value.  If the string is prefixed with "-"
the order of that sort is descending, without it, it defaults to
ascending.

Examples:

"sort":["-abv","-_score"]

This will sort results in decreasing order of the "abv" field.
Results which have the same value of the "abv" field will then
be sorted by their score, also decreasing.

If no value for "sort" is provided in the search request the
default soring is the same as before, which is decreasing score.
This commit is contained in:
Marty Schoch 2016-08-12 19:16:24 -04:00
parent be56380833
commit 0d873916f0
3 changed files with 125 additions and 58 deletions

View File

@ -238,7 +238,7 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
Fields []string `json:"fields"`
Facets FacetsRequest `json:"facets"`
Explain bool `json:"explain"`
Sort search.SortOrder `json:"sort"`
Sort []json.RawMessage `json:"sort"`
}
err := json.Unmarshal(input, &temp)
@ -253,6 +253,11 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
}
if temp.Sort == nil {
r.Sort = search.SortOrder{&search.SortScore{Descending: true}}
} else {
r.Sort, err = search.ParseSortOrder(temp.Sort)
if err != nil {
return err
}
}
r.From = temp.From
r.Explain = temp.Explain

View File

@ -136,59 +136,3 @@ type Searcher interface {
type SearchContext struct {
DocumentMatchPool *DocumentMatchPool
}
type SearchSort interface {
Compare(a, b *DocumentMatch) int
RequiresDocID() bool
RequiresScoring() bool
RequiresStoredFields() []string
}
type SortOrder []SearchSort
func (so SortOrder) Compare(i, j *DocumentMatch) int {
// compare the documents on all search sorts until a differences is found
for _, soi := range so {
c := soi.Compare(i, j)
if c == 0 {
continue
}
return c
}
// if they are the same at this point, impose order based on index natural sort order
if i.HitNumber == j.HitNumber {
return 0
} else if i.HitNumber > j.HitNumber {
return 1
}
return -1
}
func (so SortOrder) RequiresScore() bool {
rv := false
for _, soi := range so {
if soi.RequiresScoring() {
rv = true
}
}
return rv
}
func (so SortOrder) RequiresDocID() bool {
rv := false
for _, soi := range so {
if soi.RequiresDocID() {
rv = true
}
}
return rv
}
func (so SortOrder) RequiredStoredFields() []string {
var rv []string
for _, soi := range so {
rv = append(rv, soi.RequiresStoredFields()...)
}
return rv
}

View File

@ -9,7 +9,104 @@
package search
import "strings"
import (
"encoding/json"
"strings"
)
type SearchSort interface {
Compare(a, b *DocumentMatch) int
RequiresDocID() bool
RequiresScoring() bool
RequiresStoredFields() []string
}
func ParseSearchSort(input json.RawMessage) (SearchSort, error) {
var tmp string
err := json.Unmarshal(input, &tmp)
if err != nil {
return nil, err
}
descending := false
if strings.HasPrefix(tmp, "-") {
descending = true
tmp = tmp[1:]
}
if tmp == "_id" {
return &SortDocID{
Descending: descending,
}, nil
} else if tmp == "_score" {
return &SortScore{
Descending: descending,
}, nil
}
return &SortStoredField{
Field: tmp,
Descending: descending,
}, nil
}
func ParseSortOrder(in []json.RawMessage) (SortOrder, error) {
rv := make(SortOrder, 0, len(in))
for _, i := range in {
ss, err := ParseSearchSort(i)
if err != nil {
return nil, err
}
rv = append(rv, ss)
}
return rv, nil
}
type SortOrder []SearchSort
func (so SortOrder) Compare(i, j *DocumentMatch) int {
// compare the documents on all search sorts until a differences is found
for _, soi := range so {
c := soi.Compare(i, j)
if c == 0 {
continue
}
return c
}
// if they are the same at this point, impose order based on index natural sort order
if i.HitNumber == j.HitNumber {
return 0
} else if i.HitNumber > j.HitNumber {
return 1
}
return -1
}
func (so SortOrder) RequiresScore() bool {
rv := false
for _, soi := range so {
if soi.RequiresScoring() {
rv = true
}
}
return rv
}
func (so SortOrder) RequiresDocID() bool {
rv := false
for _, soi := range so {
if soi.RequiresDocID() {
rv = true
}
}
return rv
}
func (so SortOrder) RequiredStoredFields() []string {
var rv []string
for _, soi := range so {
rv = append(rv, soi.RequiresStoredFields()...)
}
return rv
}
// SortStoredField will sort results by the value of a stored field
type SortStoredField struct {
@ -31,6 +128,13 @@ func (s *SortStoredField) RequiresScoring() bool { return false }
// RequiresStoredFields says this SearchStore requires the specified stored field
func (s *SortStoredField) RequiresStoredFields() []string { return []string{s.Field} }
func (s *SortStoredField) MarshalJSON() ([]byte, error) {
if s.Descending {
return json.Marshal("-" + s.Field)
}
return json.Marshal(s.Field)
}
// SortDocID will sort results by the document identifier
type SortDocID struct {
Descending bool
@ -53,6 +157,13 @@ func (s *SortDocID) RequiresScoring() bool { return false }
// RequiresStoredFields says this SearchStore does not require any stored fields
func (s *SortDocID) RequiresStoredFields() []string { return nil }
func (s *SortDocID) MarshalJSON() ([]byte, error) {
if s.Descending {
return json.Marshal("-_id")
}
return json.Marshal("_id")
}
// SortScore will sort results by the document match score
type SortScore struct {
Descending bool
@ -76,3 +187,10 @@ func (s *SortScore) RequiresScoring() bool { return true }
// RequiresStoredFields says this SearchStore does not require any store fields
func (s *SortScore) RequiresStoredFields() []string { return nil }
func (s *SortScore) MarshalJSON() ([]byte, error) {
if s.Descending {
return json.Marshal("-_score")
}
return json.Marshal("_score")
}