2016-08-12 20:23:55 +02:00
|
|
|
// Copyright (c) 2014 Couchbase, Inc.
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
|
|
// except in compliance with the License. You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
// Unless required by applicable law or agreed to in writing, software distributed under the
|
|
|
|
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
|
|
// either express or implied. See the License for the specific language governing permissions
|
|
|
|
// and limitations under the License.
|
|
|
|
|
|
|
|
package search
|
|
|
|
|
2016-08-13 01:16:24 +02:00
|
|
|
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
|
|
|
|
}
|
2016-08-12 20:23:55 +02:00
|
|
|
|
|
|
|
// SortStoredField will sort results by the value of a stored field
|
|
|
|
type SortStoredField struct {
|
|
|
|
Field string
|
|
|
|
Descending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare orders DocumentMatch instances by stored field values
|
|
|
|
func (s *SortStoredField) Compare(i, j *DocumentMatch) int {
|
|
|
|
return i.Document.CompareFieldsNamed(j.Document, s.Field, s.Descending)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequiresDocID says this SearchSort does not require the DocID be loaded
|
|
|
|
func (s *SortStoredField) RequiresDocID() bool { return false }
|
|
|
|
|
|
|
|
// RequiresScoring says this SearchStore does not require scoring
|
|
|
|
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} }
|
|
|
|
|
2016-08-13 01:16:24 +02:00
|
|
|
func (s *SortStoredField) MarshalJSON() ([]byte, error) {
|
|
|
|
if s.Descending {
|
|
|
|
return json.Marshal("-" + s.Field)
|
|
|
|
}
|
|
|
|
return json.Marshal(s.Field)
|
|
|
|
}
|
|
|
|
|
2016-08-12 20:23:55 +02:00
|
|
|
// SortDocID will sort results by the document identifier
|
|
|
|
type SortDocID struct {
|
|
|
|
Descending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare orders DocumentMatch instances by document identifiers
|
|
|
|
func (s *SortDocID) Compare(i, j *DocumentMatch) int {
|
|
|
|
if s.Descending {
|
|
|
|
return strings.Compare(j.ID, i.ID)
|
|
|
|
}
|
|
|
|
return strings.Compare(i.ID, j.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequiresDocID says this SearchSort does require the DocID be loaded
|
|
|
|
func (s *SortDocID) RequiresDocID() bool { return true }
|
|
|
|
|
|
|
|
// RequiresScoring says this SearchStore does not require scoring
|
|
|
|
func (s *SortDocID) RequiresScoring() bool { return false }
|
|
|
|
|
|
|
|
// RequiresStoredFields says this SearchStore does not require any stored fields
|
|
|
|
func (s *SortDocID) RequiresStoredFields() []string { return nil }
|
|
|
|
|
2016-08-13 01:16:24 +02:00
|
|
|
func (s *SortDocID) MarshalJSON() ([]byte, error) {
|
|
|
|
if s.Descending {
|
|
|
|
return json.Marshal("-_id")
|
|
|
|
}
|
|
|
|
return json.Marshal("_id")
|
|
|
|
}
|
|
|
|
|
2016-08-12 20:23:55 +02:00
|
|
|
// SortScore will sort results by the document match score
|
|
|
|
type SortScore struct {
|
|
|
|
Descending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare orders DocumentMatch instances by computed scores
|
|
|
|
func (s *SortScore) Compare(i, j *DocumentMatch) int {
|
|
|
|
if i.Score == j.Score {
|
|
|
|
return 0
|
|
|
|
} else if (i.Score < j.Score && !s.Descending) || (j.Score < i.Score && s.Descending) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequiresDocID says this SearchSort does not require the DocID be loaded
|
|
|
|
func (s *SortScore) RequiresDocID() bool { return false }
|
|
|
|
|
|
|
|
// RequiresScoring says this SearchStore does require scoring
|
|
|
|
func (s *SortScore) RequiresScoring() bool { return true }
|
|
|
|
|
|
|
|
// RequiresStoredFields says this SearchStore does not require any store fields
|
|
|
|
func (s *SortScore) RequiresStoredFields() []string { return nil }
|
2016-08-13 01:16:24 +02:00
|
|
|
|
|
|
|
func (s *SortScore) MarshalJSON() ([]byte, error) {
|
|
|
|
if s.Descending {
|
|
|
|
return json.Marshal("-_score")
|
|
|
|
}
|
|
|
|
return json.Marshal("_score")
|
|
|
|
}
|