forked from forgejo/forgejo
Search bar for issues/pulls (#530)
This commit is contained in:
parent
8bc431952f
commit
833f8b94c2
195 changed files with 221830 additions and 60 deletions
33
vendor/github.com/blevesearch/bleve/search/collector.go
generated
vendored
Normal file
33
vendor/github.com/blevesearch/bleve/search/collector.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Collector interface {
|
||||
Collect(ctx context.Context, searcher Searcher, reader index.IndexReader) error
|
||||
Results() DocumentMatchCollection
|
||||
Total() uint64
|
||||
MaxScore() float64
|
||||
Took() time.Duration
|
||||
SetFacetsBuilder(facetsBuilder *FacetsBuilder)
|
||||
FacetResults() FacetResults
|
||||
}
|
91
vendor/github.com/blevesearch/bleve/search/collector/heap.go
generated
vendored
Normal file
91
vendor/github.com/blevesearch/bleve/search/collector/heap.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type collectStoreHeap struct {
|
||||
heap search.DocumentMatchCollection
|
||||
compare collectorCompare
|
||||
}
|
||||
|
||||
func newStoreHeap(cap int, compare collectorCompare) *collectStoreHeap {
|
||||
rv := &collectStoreHeap{
|
||||
heap: make(search.DocumentMatchCollection, 0, cap),
|
||||
compare: compare,
|
||||
}
|
||||
heap.Init(rv)
|
||||
return rv
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Add(doc *search.DocumentMatch) {
|
||||
heap.Push(c, doc)
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) RemoveLast() *search.DocumentMatch {
|
||||
return heap.Pop(c).(*search.DocumentMatch)
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Final(skip int, fixup collectorFixup) (search.DocumentMatchCollection, error) {
|
||||
count := c.Len()
|
||||
size := count - skip
|
||||
if size <= 0 {
|
||||
return make(search.DocumentMatchCollection, 0), nil
|
||||
}
|
||||
rv := make(search.DocumentMatchCollection, size)
|
||||
for count > 0 {
|
||||
count--
|
||||
|
||||
if count >= skip {
|
||||
size--
|
||||
doc := heap.Pop(c).(*search.DocumentMatch)
|
||||
rv[size] = doc
|
||||
err := fixup(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// heap interface implementation
|
||||
|
||||
func (c *collectStoreHeap) Len() int {
|
||||
return len(c.heap)
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Less(i, j int) bool {
|
||||
so := c.compare(c.heap[i], c.heap[j])
|
||||
return -so < 0
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Swap(i, j int) {
|
||||
c.heap[i], c.heap[j] = c.heap[j], c.heap[i]
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Push(x interface{}) {
|
||||
c.heap = append(c.heap, x.(*search.DocumentMatch))
|
||||
}
|
||||
|
||||
func (c *collectStoreHeap) Pop() interface{} {
|
||||
var rv *search.DocumentMatch
|
||||
rv, c.heap = c.heap[len(c.heap)-1], c.heap[:len(c.heap)-1]
|
||||
return rv
|
||||
}
|
78
vendor/github.com/blevesearch/bleve/search/collector/list.go
generated
vendored
Normal file
78
vendor/github.com/blevesearch/bleve/search/collector/list.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type collectStoreList struct {
|
||||
results *list.List
|
||||
compare collectorCompare
|
||||
}
|
||||
|
||||
func newStoreList(cap int, compare collectorCompare) *collectStoreList {
|
||||
rv := &collectStoreList{
|
||||
results: list.New(),
|
||||
compare: compare,
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (c *collectStoreList) Add(doc *search.DocumentMatch) {
|
||||
for e := c.results.Front(); e != nil; e = e.Next() {
|
||||
curr := e.Value.(*search.DocumentMatch)
|
||||
if c.compare(doc, curr) >= 0 {
|
||||
c.results.InsertBefore(doc, e)
|
||||
return
|
||||
}
|
||||
}
|
||||
// if we got to the end, we still have to add it
|
||||
c.results.PushBack(doc)
|
||||
}
|
||||
|
||||
func (c *collectStoreList) RemoveLast() *search.DocumentMatch {
|
||||
return c.results.Remove(c.results.Front()).(*search.DocumentMatch)
|
||||
}
|
||||
|
||||
func (c *collectStoreList) Final(skip int, fixup collectorFixup) (search.DocumentMatchCollection, error) {
|
||||
if c.results.Len()-skip > 0 {
|
||||
rv := make(search.DocumentMatchCollection, c.results.Len()-skip)
|
||||
i := 0
|
||||
skipped := 0
|
||||
for e := c.results.Back(); e != nil; e = e.Prev() {
|
||||
if skipped < skip {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
rv[i] = e.Value.(*search.DocumentMatch)
|
||||
err := fixup(rv[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i++
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
return search.DocumentMatchCollection{}, nil
|
||||
}
|
||||
|
||||
func (c *collectStoreList) Len() int {
|
||||
return c.results.Len()
|
||||
}
|
68
vendor/github.com/blevesearch/bleve/search/collector/slice.go
generated
vendored
Normal file
68
vendor/github.com/blevesearch/bleve/search/collector/slice.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 collector
|
||||
|
||||
import "github.com/blevesearch/bleve/search"
|
||||
|
||||
type collectStoreSlice struct {
|
||||
slice search.DocumentMatchCollection
|
||||
compare collectorCompare
|
||||
}
|
||||
|
||||
func newStoreSlice(cap int, compare collectorCompare) *collectStoreSlice {
|
||||
rv := &collectStoreSlice{
|
||||
slice: make(search.DocumentMatchCollection, 0, cap),
|
||||
compare: compare,
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (c *collectStoreSlice) Add(doc *search.DocumentMatch) {
|
||||
// find where to insert, starting at end (lowest)
|
||||
i := len(c.slice)
|
||||
for ; i > 0; i-- {
|
||||
cmp := c.compare(doc, c.slice[i-1])
|
||||
if cmp >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
// insert at i
|
||||
c.slice = append(c.slice, nil)
|
||||
copy(c.slice[i+1:], c.slice[i:])
|
||||
c.slice[i] = doc
|
||||
}
|
||||
|
||||
func (c *collectStoreSlice) RemoveLast() *search.DocumentMatch {
|
||||
var rv *search.DocumentMatch
|
||||
rv, c.slice = c.slice[len(c.slice)-1], c.slice[:len(c.slice)-1]
|
||||
return rv
|
||||
}
|
||||
|
||||
func (c *collectStoreSlice) Final(skip int, fixup collectorFixup) (search.DocumentMatchCollection, error) {
|
||||
for i := skip; i < len(c.slice); i++ {
|
||||
err := fixup(c.slice[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if skip <= len(c.slice) {
|
||||
return c.slice[skip:], nil
|
||||
}
|
||||
return search.DocumentMatchCollection{}, nil
|
||||
}
|
||||
|
||||
func (c *collectStoreSlice) Len() int {
|
||||
return len(c.slice)
|
||||
}
|
269
vendor/github.com/blevesearch/bleve/search/collector/topn.go
generated
vendored
Normal file
269
vendor/github.com/blevesearch/bleve/search/collector/topn.go
generated
vendored
Normal file
|
@ -0,0 +1,269 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// PreAllocSizeSkipCap will cap preallocation to this amount when
|
||||
// size+skip exceeds this value
|
||||
var PreAllocSizeSkipCap = 1000
|
||||
|
||||
type collectorCompare func(i, j *search.DocumentMatch) int
|
||||
|
||||
type collectorFixup func(d *search.DocumentMatch) error
|
||||
|
||||
// TopNCollector collects the top N hits, optionally skipping some results
|
||||
type TopNCollector struct {
|
||||
size int
|
||||
skip int
|
||||
total uint64
|
||||
maxScore float64
|
||||
took time.Duration
|
||||
sort search.SortOrder
|
||||
results search.DocumentMatchCollection
|
||||
facetsBuilder *search.FacetsBuilder
|
||||
|
||||
store *collectStoreSlice
|
||||
|
||||
needDocIds bool
|
||||
neededFields []string
|
||||
cachedScoring []bool
|
||||
cachedDesc []bool
|
||||
|
||||
lowestMatchOutsideResults *search.DocumentMatch
|
||||
}
|
||||
|
||||
// CheckDoneEvery controls how frequently we check the context deadline
|
||||
const CheckDoneEvery = uint64(1024)
|
||||
|
||||
// NewTopNCollector builds a collector to find the top 'size' hits
|
||||
// skipping over the first 'skip' hits
|
||||
// ordering hits by the provided sort order
|
||||
func NewTopNCollector(size int, skip int, sort search.SortOrder) *TopNCollector {
|
||||
hc := &TopNCollector{size: size, skip: skip, sort: sort}
|
||||
|
||||
// pre-allocate space on the store to avoid reslicing
|
||||
// unless the size + skip is too large, then cap it
|
||||
// everything should still work, just reslices as necessary
|
||||
backingSize := size + skip + 1
|
||||
if size+skip > PreAllocSizeSkipCap {
|
||||
backingSize = PreAllocSizeSkipCap + 1
|
||||
}
|
||||
|
||||
hc.store = newStoreSlice(backingSize, func(i, j *search.DocumentMatch) int {
|
||||
return hc.sort.Compare(hc.cachedScoring, hc.cachedDesc, i, j)
|
||||
})
|
||||
|
||||
// these lookups traverse an interface, so do once up-front
|
||||
if sort.RequiresDocID() {
|
||||
hc.needDocIds = true
|
||||
}
|
||||
hc.neededFields = sort.RequiredFields()
|
||||
hc.cachedScoring = sort.CacheIsScore()
|
||||
hc.cachedDesc = sort.CacheDescending()
|
||||
|
||||
return hc
|
||||
}
|
||||
|
||||
// Collect goes to the index to find the matching documents
|
||||
func (hc *TopNCollector) Collect(ctx context.Context, searcher search.Searcher, reader index.IndexReader) error {
|
||||
startTime := time.Now()
|
||||
var err error
|
||||
var next *search.DocumentMatch
|
||||
|
||||
// pre-allocate enough space in the DocumentMatchPool
|
||||
// unless the size + skip is too large, then cap it
|
||||
// everything should still work, just allocates DocumentMatches on demand
|
||||
backingSize := hc.size + hc.skip + 1
|
||||
if hc.size+hc.skip > PreAllocSizeSkipCap {
|
||||
backingSize = PreAllocSizeSkipCap + 1
|
||||
}
|
||||
searchContext := &search.SearchContext{
|
||||
DocumentMatchPool: search.NewDocumentMatchPool(backingSize+searcher.DocumentMatchPoolSize(), len(hc.sort)),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
next, err = searcher.Next(searchContext)
|
||||
}
|
||||
for err == nil && next != nil {
|
||||
if hc.total%CheckDoneEvery == 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
}
|
||||
if hc.facetsBuilder != nil {
|
||||
err = hc.facetsBuilder.Update(next)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = hc.collectSingle(searchContext, reader, next)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
next, err = searcher.Next(searchContext)
|
||||
}
|
||||
// compute search duration
|
||||
hc.took = time.Since(startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// finalize actual results
|
||||
err = hc.finalizeResults(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var sortByScoreOpt = []string{"_score"}
|
||||
|
||||
func (hc *TopNCollector) collectSingle(ctx *search.SearchContext, reader index.IndexReader, d *search.DocumentMatch) error {
|
||||
// increment total hits
|
||||
hc.total++
|
||||
d.HitNumber = hc.total
|
||||
|
||||
// update max score
|
||||
if d.Score > hc.maxScore {
|
||||
hc.maxScore = d.Score
|
||||
}
|
||||
|
||||
var err error
|
||||
// see if we need to load ID (at this early stage, for example to sort on it)
|
||||
if hc.needDocIds {
|
||||
d.ID, err = reader.ExternalID(d.IndexInternalID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// see if we need to load the stored fields
|
||||
if len(hc.neededFields) > 0 {
|
||||
// find out which fields haven't been loaded yet
|
||||
fieldsToLoad := d.CachedFieldTerms.FieldsNotYetCached(hc.neededFields)
|
||||
// look them up
|
||||
fieldTerms, err := reader.DocumentFieldTerms(d.IndexInternalID, fieldsToLoad)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// cache these as well
|
||||
if d.CachedFieldTerms == nil {
|
||||
d.CachedFieldTerms = make(map[string][]string)
|
||||
}
|
||||
d.CachedFieldTerms.Merge(fieldTerms)
|
||||
}
|
||||
|
||||
// compute this hits sort value
|
||||
if len(hc.sort) == 1 && hc.cachedScoring[0] {
|
||||
d.Sort = sortByScoreOpt
|
||||
} else {
|
||||
hc.sort.Value(d)
|
||||
}
|
||||
|
||||
// optimization, we track lowest sorting hit already removed from heap
|
||||
// with this one comparison, we can avoid all heap operations if
|
||||
// this hit would have been added and then immediately removed
|
||||
if hc.lowestMatchOutsideResults != nil {
|
||||
cmp := hc.sort.Compare(hc.cachedScoring, hc.cachedDesc, d, hc.lowestMatchOutsideResults)
|
||||
if cmp >= 0 {
|
||||
// this hit can't possibly be in the result set, so avoid heap ops
|
||||
ctx.DocumentMatchPool.Put(d)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
hc.store.Add(d)
|
||||
if hc.store.Len() > hc.size+hc.skip {
|
||||
removed := hc.store.RemoveLast()
|
||||
if hc.lowestMatchOutsideResults == nil {
|
||||
hc.lowestMatchOutsideResults = removed
|
||||
} else {
|
||||
cmp := hc.sort.Compare(hc.cachedScoring, hc.cachedDesc, removed, hc.lowestMatchOutsideResults)
|
||||
if cmp < 0 {
|
||||
tmp := hc.lowestMatchOutsideResults
|
||||
hc.lowestMatchOutsideResults = removed
|
||||
ctx.DocumentMatchPool.Put(tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFacetsBuilder registers a facet builder for this collector
|
||||
func (hc *TopNCollector) SetFacetsBuilder(facetsBuilder *search.FacetsBuilder) {
|
||||
hc.facetsBuilder = facetsBuilder
|
||||
}
|
||||
|
||||
// finalizeResults starts with the heap containing the final top size+skip
|
||||
// it now throws away the results to be skipped
|
||||
// and does final doc id lookup (if necessary)
|
||||
func (hc *TopNCollector) finalizeResults(r index.IndexReader) error {
|
||||
var err error
|
||||
hc.results, err = hc.store.Final(hc.skip, func(doc *search.DocumentMatch) error {
|
||||
if doc.ID == "" {
|
||||
// look up the id since we need it for lookup
|
||||
var err error
|
||||
doc.ID, err = r.ExternalID(doc.IndexInternalID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Results returns the collected hits
|
||||
func (hc *TopNCollector) Results() search.DocumentMatchCollection {
|
||||
return hc.results
|
||||
}
|
||||
|
||||
// Total returns the total number of hits
|
||||
func (hc *TopNCollector) Total() uint64 {
|
||||
return hc.total
|
||||
}
|
||||
|
||||
// MaxScore returns the maximum score seen across all the hits
|
||||
func (hc *TopNCollector) MaxScore() float64 {
|
||||
return hc.maxScore
|
||||
}
|
||||
|
||||
// Took returns the time spent collecting hits
|
||||
func (hc *TopNCollector) Took() time.Duration {
|
||||
return hc.took
|
||||
}
|
||||
|
||||
// FacetResults returns the computed facets results
|
||||
func (hc *TopNCollector) FacetResults() search.FacetResults {
|
||||
if hc.facetsBuilder != nil {
|
||||
return hc.facetsBuilder.Results()
|
||||
}
|
||||
return search.FacetResults{}
|
||||
}
|
34
vendor/github.com/blevesearch/bleve/search/explanation.go
generated
vendored
Normal file
34
vendor/github.com/blevesearch/bleve/search/explanation.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Explanation struct {
|
||||
Value float64 `json:"value"`
|
||||
Message string `json:"message"`
|
||||
Children []*Explanation `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
func (expl *Explanation) String() string {
|
||||
js, err := json.MarshalIndent(expl, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Sprintf("error serializing explanation to json: %v", err)
|
||||
}
|
||||
return string(js)
|
||||
}
|
2909
vendor/github.com/blevesearch/bleve/search/facet/benchmark_data.txt
generated
vendored
Normal file
2909
vendor/github.com/blevesearch/bleve/search/facet/benchmark_data.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
135
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_datetime.go
generated
vendored
Normal file
135
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_datetime.go
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
// 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 facet
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/numeric"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type dateTimeRange struct {
|
||||
start time.Time
|
||||
end time.Time
|
||||
}
|
||||
|
||||
type DateTimeFacetBuilder struct {
|
||||
size int
|
||||
field string
|
||||
termsCount map[string]int
|
||||
total int
|
||||
missing int
|
||||
ranges map[string]*dateTimeRange
|
||||
}
|
||||
|
||||
func NewDateTimeFacetBuilder(field string, size int) *DateTimeFacetBuilder {
|
||||
return &DateTimeFacetBuilder{
|
||||
size: size,
|
||||
field: field,
|
||||
termsCount: make(map[string]int),
|
||||
ranges: make(map[string]*dateTimeRange, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *DateTimeFacetBuilder) AddRange(name string, start, end time.Time) {
|
||||
r := dateTimeRange{
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
fb.ranges[name] = &r
|
||||
}
|
||||
|
||||
func (fb *DateTimeFacetBuilder) Field() string {
|
||||
return fb.field
|
||||
}
|
||||
|
||||
func (fb *DateTimeFacetBuilder) Update(ft index.FieldTerms) {
|
||||
terms, ok := ft[fb.field]
|
||||
if ok {
|
||||
for _, term := range terms {
|
||||
// only consider the values which are shifted 0
|
||||
prefixCoded := numeric.PrefixCoded(term)
|
||||
shift, err := prefixCoded.Shift()
|
||||
if err == nil && shift == 0 {
|
||||
i64, err := prefixCoded.Int64()
|
||||
if err == nil {
|
||||
t := time.Unix(0, i64)
|
||||
|
||||
// look at each of the ranges for a match
|
||||
for rangeName, r := range fb.ranges {
|
||||
|
||||
if (r.start.IsZero() || t.After(r.start) || t.Equal(r.start)) && (r.end.IsZero() || t.Before(r.end)) {
|
||||
|
||||
existingCount, existed := fb.termsCount[rangeName]
|
||||
if existed {
|
||||
fb.termsCount[rangeName] = existingCount + 1
|
||||
} else {
|
||||
fb.termsCount[rangeName] = 1
|
||||
}
|
||||
fb.total++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fb.missing++
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *DateTimeFacetBuilder) Result() *search.FacetResult {
|
||||
rv := search.FacetResult{
|
||||
Field: fb.field,
|
||||
Total: fb.total,
|
||||
Missing: fb.missing,
|
||||
}
|
||||
|
||||
rv.DateRanges = make([]*search.DateRangeFacet, 0, len(fb.termsCount))
|
||||
|
||||
for term, count := range fb.termsCount {
|
||||
dateRange := fb.ranges[term]
|
||||
tf := &search.DateRangeFacet{
|
||||
Name: term,
|
||||
Count: count,
|
||||
}
|
||||
if !dateRange.start.IsZero() {
|
||||
start := dateRange.start.Format(time.RFC3339Nano)
|
||||
tf.Start = &start
|
||||
}
|
||||
if !dateRange.end.IsZero() {
|
||||
end := dateRange.end.Format(time.RFC3339Nano)
|
||||
tf.End = &end
|
||||
}
|
||||
rv.DateRanges = append(rv.DateRanges, tf)
|
||||
}
|
||||
|
||||
sort.Sort(rv.DateRanges)
|
||||
|
||||
// we now have the list of the top N facets
|
||||
if fb.size < len(rv.DateRanges) {
|
||||
rv.DateRanges = rv.DateRanges[:fb.size]
|
||||
}
|
||||
|
||||
notOther := 0
|
||||
for _, nr := range rv.DateRanges {
|
||||
notOther += nr.Count
|
||||
}
|
||||
rv.Other = fb.total - notOther
|
||||
|
||||
return &rv
|
||||
}
|
129
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_numeric.go
generated
vendored
Normal file
129
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_numeric.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
// 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 facet
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/numeric"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type numericRange struct {
|
||||
min *float64
|
||||
max *float64
|
||||
}
|
||||
|
||||
type NumericFacetBuilder struct {
|
||||
size int
|
||||
field string
|
||||
termsCount map[string]int
|
||||
total int
|
||||
missing int
|
||||
ranges map[string]*numericRange
|
||||
}
|
||||
|
||||
func NewNumericFacetBuilder(field string, size int) *NumericFacetBuilder {
|
||||
return &NumericFacetBuilder{
|
||||
size: size,
|
||||
field: field,
|
||||
termsCount: make(map[string]int),
|
||||
ranges: make(map[string]*numericRange, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *NumericFacetBuilder) AddRange(name string, min, max *float64) {
|
||||
r := numericRange{
|
||||
min: min,
|
||||
max: max,
|
||||
}
|
||||
fb.ranges[name] = &r
|
||||
}
|
||||
|
||||
func (fb *NumericFacetBuilder) Field() string {
|
||||
return fb.field
|
||||
}
|
||||
|
||||
func (fb *NumericFacetBuilder) Update(ft index.FieldTerms) {
|
||||
terms, ok := ft[fb.field]
|
||||
if ok {
|
||||
for _, term := range terms {
|
||||
// only consider the values which are shifted 0
|
||||
prefixCoded := numeric.PrefixCoded(term)
|
||||
shift, err := prefixCoded.Shift()
|
||||
if err == nil && shift == 0 {
|
||||
i64, err := prefixCoded.Int64()
|
||||
if err == nil {
|
||||
f64 := numeric.Int64ToFloat64(i64)
|
||||
|
||||
// look at each of the ranges for a match
|
||||
for rangeName, r := range fb.ranges {
|
||||
|
||||
if (r.min == nil || f64 >= *r.min) && (r.max == nil || f64 < *r.max) {
|
||||
|
||||
existingCount, existed := fb.termsCount[rangeName]
|
||||
if existed {
|
||||
fb.termsCount[rangeName] = existingCount + 1
|
||||
} else {
|
||||
fb.termsCount[rangeName] = 1
|
||||
}
|
||||
fb.total++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fb.missing++
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *NumericFacetBuilder) Result() *search.FacetResult {
|
||||
rv := search.FacetResult{
|
||||
Field: fb.field,
|
||||
Total: fb.total,
|
||||
Missing: fb.missing,
|
||||
}
|
||||
|
||||
rv.NumericRanges = make([]*search.NumericRangeFacet, 0, len(fb.termsCount))
|
||||
|
||||
for term, count := range fb.termsCount {
|
||||
numericRange := fb.ranges[term]
|
||||
tf := &search.NumericRangeFacet{
|
||||
Name: term,
|
||||
Count: count,
|
||||
Min: numericRange.min,
|
||||
Max: numericRange.max,
|
||||
}
|
||||
|
||||
rv.NumericRanges = append(rv.NumericRanges, tf)
|
||||
}
|
||||
|
||||
sort.Sort(rv.NumericRanges)
|
||||
|
||||
// we now have the list of the top N facets
|
||||
if fb.size < len(rv.NumericRanges) {
|
||||
rv.NumericRanges = rv.NumericRanges[:fb.size]
|
||||
}
|
||||
|
||||
notOther := 0
|
||||
for _, nr := range rv.NumericRanges {
|
||||
notOther += nr.Count
|
||||
}
|
||||
rv.Other = fb.total - notOther
|
||||
|
||||
return &rv
|
||||
}
|
95
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_terms.go
generated
vendored
Normal file
95
vendor/github.com/blevesearch/bleve/search/facet/facet_builder_terms.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
// 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 facet
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type TermsFacetBuilder struct {
|
||||
size int
|
||||
field string
|
||||
termsCount map[string]int
|
||||
total int
|
||||
missing int
|
||||
}
|
||||
|
||||
func NewTermsFacetBuilder(field string, size int) *TermsFacetBuilder {
|
||||
return &TermsFacetBuilder{
|
||||
size: size,
|
||||
field: field,
|
||||
termsCount: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *TermsFacetBuilder) Field() string {
|
||||
return fb.field
|
||||
}
|
||||
|
||||
func (fb *TermsFacetBuilder) Update(ft index.FieldTerms) {
|
||||
terms, ok := ft[fb.field]
|
||||
if ok {
|
||||
for _, term := range terms {
|
||||
existingCount, existed := fb.termsCount[term]
|
||||
if existed {
|
||||
fb.termsCount[term] = existingCount + 1
|
||||
} else {
|
||||
fb.termsCount[term] = 1
|
||||
}
|
||||
fb.total++
|
||||
}
|
||||
} else {
|
||||
fb.missing++
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *TermsFacetBuilder) Result() *search.FacetResult {
|
||||
rv := search.FacetResult{
|
||||
Field: fb.field,
|
||||
Total: fb.total,
|
||||
Missing: fb.missing,
|
||||
}
|
||||
|
||||
rv.Terms = make([]*search.TermFacet, 0, len(fb.termsCount))
|
||||
|
||||
for term, count := range fb.termsCount {
|
||||
tf := &search.TermFacet{
|
||||
Term: term,
|
||||
Count: count,
|
||||
}
|
||||
|
||||
rv.Terms = append(rv.Terms, tf)
|
||||
}
|
||||
|
||||
sort.Sort(rv.Terms)
|
||||
|
||||
// we now have the list of the top N facets
|
||||
trimTopN := fb.size
|
||||
if trimTopN > len(rv.Terms) {
|
||||
trimTopN = len(rv.Terms)
|
||||
}
|
||||
rv.Terms = rv.Terms[:trimTopN]
|
||||
|
||||
notOther := 0
|
||||
for _, tf := range rv.Terms {
|
||||
notOther += tf.Count
|
||||
}
|
||||
rv.Other = fb.total - notOther
|
||||
|
||||
return &rv
|
||||
}
|
296
vendor/github.com/blevesearch/bleve/search/facets_builder.go
generated
vendored
Normal file
296
vendor/github.com/blevesearch/bleve/search/facets_builder.go
generated
vendored
Normal file
|
@ -0,0 +1,296 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
)
|
||||
|
||||
type FacetBuilder interface {
|
||||
Update(index.FieldTerms)
|
||||
Result() *FacetResult
|
||||
Field() string
|
||||
}
|
||||
|
||||
type FacetsBuilder struct {
|
||||
indexReader index.IndexReader
|
||||
facets map[string]FacetBuilder
|
||||
fields []string
|
||||
}
|
||||
|
||||
func NewFacetsBuilder(indexReader index.IndexReader) *FacetsBuilder {
|
||||
return &FacetsBuilder{
|
||||
indexReader: indexReader,
|
||||
facets: make(map[string]FacetBuilder, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *FacetsBuilder) Add(name string, facetBuilder FacetBuilder) {
|
||||
fb.facets[name] = facetBuilder
|
||||
}
|
||||
|
||||
func (fb *FacetsBuilder) Update(docMatch *DocumentMatch) error {
|
||||
if fb.fields == nil {
|
||||
for _, facetBuilder := range fb.facets {
|
||||
fb.fields = append(fb.fields, facetBuilder.Field())
|
||||
}
|
||||
}
|
||||
|
||||
if len(fb.fields) > 0 {
|
||||
// find out which fields haven't been loaded yet
|
||||
fieldsToLoad := docMatch.CachedFieldTerms.FieldsNotYetCached(fb.fields)
|
||||
// look them up
|
||||
fieldTerms, err := fb.indexReader.DocumentFieldTerms(docMatch.IndexInternalID, fieldsToLoad)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// cache these as well
|
||||
if docMatch.CachedFieldTerms == nil {
|
||||
docMatch.CachedFieldTerms = make(map[string][]string)
|
||||
}
|
||||
docMatch.CachedFieldTerms.Merge(fieldTerms)
|
||||
}
|
||||
for _, facetBuilder := range fb.facets {
|
||||
facetBuilder.Update(docMatch.CachedFieldTerms)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TermFacet struct {
|
||||
Term string `json:"term"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type TermFacets []*TermFacet
|
||||
|
||||
func (tf TermFacets) Add(termFacet *TermFacet) TermFacets {
|
||||
for _, existingTerm := range tf {
|
||||
if termFacet.Term == existingTerm.Term {
|
||||
existingTerm.Count += termFacet.Count
|
||||
return tf
|
||||
}
|
||||
}
|
||||
// if we got here it wasn't already in the existing terms
|
||||
tf = append(tf, termFacet)
|
||||
return tf
|
||||
}
|
||||
|
||||
func (tf TermFacets) Len() int { return len(tf) }
|
||||
func (tf TermFacets) Swap(i, j int) { tf[i], tf[j] = tf[j], tf[i] }
|
||||
func (tf TermFacets) Less(i, j int) bool {
|
||||
if tf[i].Count == tf[j].Count {
|
||||
return tf[i].Term < tf[j].Term
|
||||
}
|
||||
return tf[i].Count > tf[j].Count
|
||||
}
|
||||
|
||||
type NumericRangeFacet struct {
|
||||
Name string `json:"name"`
|
||||
Min *float64 `json:"min,omitempty"`
|
||||
Max *float64 `json:"max,omitempty"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func (nrf *NumericRangeFacet) Same(other *NumericRangeFacet) bool {
|
||||
if nrf.Min == nil && other.Min != nil {
|
||||
return false
|
||||
}
|
||||
if nrf.Min != nil && other.Min == nil {
|
||||
return false
|
||||
}
|
||||
if nrf.Min != nil && other.Min != nil && *nrf.Min != *other.Min {
|
||||
return false
|
||||
}
|
||||
if nrf.Max == nil && other.Max != nil {
|
||||
return false
|
||||
}
|
||||
if nrf.Max != nil && other.Max == nil {
|
||||
return false
|
||||
}
|
||||
if nrf.Max != nil && other.Max != nil && *nrf.Max != *other.Max {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type NumericRangeFacets []*NumericRangeFacet
|
||||
|
||||
func (nrf NumericRangeFacets) Add(numericRangeFacet *NumericRangeFacet) NumericRangeFacets {
|
||||
for _, existingNr := range nrf {
|
||||
if numericRangeFacet.Same(existingNr) {
|
||||
existingNr.Count += numericRangeFacet.Count
|
||||
return nrf
|
||||
}
|
||||
}
|
||||
// if we got here it wasn't already in the existing terms
|
||||
nrf = append(nrf, numericRangeFacet)
|
||||
return nrf
|
||||
}
|
||||
|
||||
func (nrf NumericRangeFacets) Len() int { return len(nrf) }
|
||||
func (nrf NumericRangeFacets) Swap(i, j int) { nrf[i], nrf[j] = nrf[j], nrf[i] }
|
||||
func (nrf NumericRangeFacets) Less(i, j int) bool {
|
||||
if nrf[i].Count == nrf[j].Count {
|
||||
return nrf[i].Name < nrf[j].Name
|
||||
}
|
||||
return nrf[i].Count > nrf[j].Count
|
||||
}
|
||||
|
||||
type DateRangeFacet struct {
|
||||
Name string `json:"name"`
|
||||
Start *string `json:"start,omitempty"`
|
||||
End *string `json:"end,omitempty"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func (drf *DateRangeFacet) Same(other *DateRangeFacet) bool {
|
||||
if drf.Start == nil && other.Start != nil {
|
||||
return false
|
||||
}
|
||||
if drf.Start != nil && other.Start == nil {
|
||||
return false
|
||||
}
|
||||
if drf.Start != nil && other.Start != nil && *drf.Start != *other.Start {
|
||||
return false
|
||||
}
|
||||
if drf.End == nil && other.End != nil {
|
||||
return false
|
||||
}
|
||||
if drf.End != nil && other.End == nil {
|
||||
return false
|
||||
}
|
||||
if drf.End != nil && other.End != nil && *drf.End != *other.End {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type DateRangeFacets []*DateRangeFacet
|
||||
|
||||
func (drf DateRangeFacets) Add(dateRangeFacet *DateRangeFacet) DateRangeFacets {
|
||||
for _, existingDr := range drf {
|
||||
if dateRangeFacet.Same(existingDr) {
|
||||
existingDr.Count += dateRangeFacet.Count
|
||||
return drf
|
||||
}
|
||||
}
|
||||
// if we got here it wasn't already in the existing terms
|
||||
drf = append(drf, dateRangeFacet)
|
||||
return drf
|
||||
}
|
||||
|
||||
func (drf DateRangeFacets) Len() int { return len(drf) }
|
||||
func (drf DateRangeFacets) Swap(i, j int) { drf[i], drf[j] = drf[j], drf[i] }
|
||||
func (drf DateRangeFacets) Less(i, j int) bool {
|
||||
if drf[i].Count == drf[j].Count {
|
||||
return drf[i].Name < drf[j].Name
|
||||
}
|
||||
return drf[i].Count > drf[j].Count
|
||||
}
|
||||
|
||||
type FacetResult struct {
|
||||
Field string `json:"field"`
|
||||
Total int `json:"total"`
|
||||
Missing int `json:"missing"`
|
||||
Other int `json:"other"`
|
||||
Terms TermFacets `json:"terms,omitempty"`
|
||||
NumericRanges NumericRangeFacets `json:"numeric_ranges,omitempty"`
|
||||
DateRanges DateRangeFacets `json:"date_ranges,omitempty"`
|
||||
}
|
||||
|
||||
func (fr *FacetResult) Merge(other *FacetResult) {
|
||||
fr.Total += other.Total
|
||||
fr.Missing += other.Missing
|
||||
fr.Other += other.Other
|
||||
if fr.Terms != nil && other.Terms != nil {
|
||||
for _, term := range other.Terms {
|
||||
fr.Terms = fr.Terms.Add(term)
|
||||
}
|
||||
}
|
||||
if fr.NumericRanges != nil && other.NumericRanges != nil {
|
||||
for _, nr := range other.NumericRanges {
|
||||
fr.NumericRanges = fr.NumericRanges.Add(nr)
|
||||
}
|
||||
}
|
||||
if fr.DateRanges != nil && other.DateRanges != nil {
|
||||
for _, dr := range other.DateRanges {
|
||||
fr.DateRanges = fr.DateRanges.Add(dr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fr *FacetResult) Fixup(size int) {
|
||||
if fr.Terms != nil {
|
||||
sort.Sort(fr.Terms)
|
||||
if len(fr.Terms) > size {
|
||||
moveToOther := fr.Terms[size:]
|
||||
for _, mto := range moveToOther {
|
||||
fr.Other += mto.Count
|
||||
}
|
||||
fr.Terms = fr.Terms[0:size]
|
||||
}
|
||||
} else if fr.NumericRanges != nil {
|
||||
sort.Sort(fr.NumericRanges)
|
||||
if len(fr.NumericRanges) > size {
|
||||
moveToOther := fr.NumericRanges[size:]
|
||||
for _, mto := range moveToOther {
|
||||
fr.Other += mto.Count
|
||||
}
|
||||
fr.NumericRanges = fr.NumericRanges[0:size]
|
||||
}
|
||||
} else if fr.DateRanges != nil {
|
||||
sort.Sort(fr.DateRanges)
|
||||
if len(fr.DateRanges) > size {
|
||||
moveToOther := fr.DateRanges[size:]
|
||||
for _, mto := range moveToOther {
|
||||
fr.Other += mto.Count
|
||||
}
|
||||
fr.DateRanges = fr.DateRanges[0:size]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FacetResults map[string]*FacetResult
|
||||
|
||||
func (fr FacetResults) Merge(other FacetResults) {
|
||||
for name, oFacetResult := range other {
|
||||
facetResult, ok := fr[name]
|
||||
if ok {
|
||||
facetResult.Merge(oFacetResult)
|
||||
} else {
|
||||
fr[name] = oFacetResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fr FacetResults) Fixup(name string, size int) {
|
||||
facetResult, ok := fr[name]
|
||||
if ok {
|
||||
facetResult.Fixup(size)
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *FacetsBuilder) Results() FacetResults {
|
||||
fr := make(FacetResults)
|
||||
for facetName, facetBuilder := range fb.facets {
|
||||
facetResult := facetBuilder.Result()
|
||||
fr[facetName] = facetResult
|
||||
}
|
||||
return fr
|
||||
}
|
89
vendor/github.com/blevesearch/bleve/search/highlight/format/html/html.go
generated
vendored
Normal file
89
vendor/github.com/blevesearch/bleve/search/highlight/format/html/html.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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 html
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search/highlight"
|
||||
)
|
||||
|
||||
const Name = "html"
|
||||
|
||||
const defaultHTMLHighlightBefore = "<mark>"
|
||||
const defaultHTMLHighlightAfter = "</mark>"
|
||||
|
||||
type FragmentFormatter struct {
|
||||
before string
|
||||
after string
|
||||
}
|
||||
|
||||
func NewFragmentFormatter(before, after string) *FragmentFormatter {
|
||||
return &FragmentFormatter{
|
||||
before: before,
|
||||
after: after,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FragmentFormatter) Format(f *highlight.Fragment, orderedTermLocations highlight.TermLocations) string {
|
||||
rv := ""
|
||||
curr := f.Start
|
||||
for _, termLocation := range orderedTermLocations {
|
||||
if termLocation == nil {
|
||||
continue
|
||||
}
|
||||
// make sure the array positions match
|
||||
if !highlight.SameArrayPositions(f.ArrayPositions, termLocation.ArrayPositions) {
|
||||
continue
|
||||
}
|
||||
if termLocation.Start < curr {
|
||||
continue
|
||||
}
|
||||
if termLocation.End > f.End {
|
||||
break
|
||||
}
|
||||
// add the stuff before this location
|
||||
rv += string(f.Orig[curr:termLocation.Start])
|
||||
// add the color
|
||||
rv += a.before
|
||||
// add the term itself
|
||||
rv += string(f.Orig[termLocation.Start:termLocation.End])
|
||||
// reset the color
|
||||
rv += a.after
|
||||
// update current
|
||||
curr = termLocation.End
|
||||
}
|
||||
// add any remaining text after the last token
|
||||
rv += string(f.Orig[curr:f.End])
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.FragmentFormatter, error) {
|
||||
before := defaultHTMLHighlightBefore
|
||||
beforeVal, ok := config["before"].(string)
|
||||
if ok {
|
||||
before = beforeVal
|
||||
}
|
||||
after := defaultHTMLHighlightAfter
|
||||
afterVal, ok := config["after"].(string)
|
||||
if ok {
|
||||
after = afterVal
|
||||
}
|
||||
return NewFragmentFormatter(before, after), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterFragmentFormatter(Name, Constructor)
|
||||
}
|
142
vendor/github.com/blevesearch/bleve/search/highlight/fragmenter/simple/simple.go
generated
vendored
Normal file
142
vendor/github.com/blevesearch/bleve/search/highlight/fragmenter/simple/simple.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
|||
// 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 simple
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search/highlight"
|
||||
)
|
||||
|
||||
const Name = "simple"
|
||||
|
||||
const defaultFragmentSize = 200
|
||||
|
||||
type Fragmenter struct {
|
||||
fragmentSize int
|
||||
}
|
||||
|
||||
func NewFragmenter(fragmentSize int) *Fragmenter {
|
||||
return &Fragmenter{
|
||||
fragmentSize: fragmentSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Fragmenter) Fragment(orig []byte, ot highlight.TermLocations) []*highlight.Fragment {
|
||||
var rv []*highlight.Fragment
|
||||
maxbegin := 0
|
||||
OUTER:
|
||||
for currTermIndex, termLocation := range ot {
|
||||
// start with this
|
||||
// it should be the highest scoring fragment with this term first
|
||||
start := termLocation.Start
|
||||
end := start
|
||||
used := 0
|
||||
for end < len(orig) && used < s.fragmentSize {
|
||||
r, size := utf8.DecodeRune(orig[end:])
|
||||
if r == utf8.RuneError {
|
||||
continue OUTER // bail
|
||||
}
|
||||
end += size
|
||||
used++
|
||||
}
|
||||
|
||||
// if we still have more characters available to us
|
||||
// push back towards beginning
|
||||
// without cross maxbegin
|
||||
for start > 0 && used < s.fragmentSize {
|
||||
r, size := utf8.DecodeLastRune(orig[0:start])
|
||||
if r == utf8.RuneError {
|
||||
continue OUTER // bail
|
||||
}
|
||||
if start-size >= maxbegin {
|
||||
start -= size
|
||||
used++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// however, we'd rather have the tokens centered more in the frag
|
||||
// lets try to do that as best we can, without affecting the score
|
||||
// find the end of the last term in this fragment
|
||||
minend := end
|
||||
for _, innerTermLocation := range ot[currTermIndex:] {
|
||||
if innerTermLocation.End > end {
|
||||
break
|
||||
}
|
||||
minend = innerTermLocation.End
|
||||
}
|
||||
|
||||
// find the smaller of the two rooms to move
|
||||
roomToMove := utf8.RuneCount(orig[minend:end])
|
||||
roomToMoveStart := 0
|
||||
if start >= maxbegin {
|
||||
roomToMoveStart = utf8.RuneCount(orig[maxbegin:start])
|
||||
}
|
||||
if roomToMoveStart < roomToMove {
|
||||
roomToMove = roomToMoveStart
|
||||
}
|
||||
|
||||
offset := roomToMove / 2
|
||||
|
||||
for offset > 0 {
|
||||
r, size := utf8.DecodeLastRune(orig[0:start])
|
||||
if r == utf8.RuneError {
|
||||
continue OUTER // bail
|
||||
}
|
||||
start -= size
|
||||
|
||||
r, size = utf8.DecodeLastRune(orig[0:end])
|
||||
if r == utf8.RuneError {
|
||||
continue OUTER // bail
|
||||
}
|
||||
end -= size
|
||||
offset--
|
||||
}
|
||||
|
||||
rv = append(rv, &highlight.Fragment{Orig: orig, Start: start - offset, End: end - offset})
|
||||
// set maxbegin to the end of the current term location
|
||||
// so that next one won't back up to include it
|
||||
maxbegin = termLocation.End
|
||||
|
||||
}
|
||||
if len(ot) == 0 {
|
||||
// if there were no terms to highlight
|
||||
// produce a single fragment from the beginning
|
||||
start := 0
|
||||
end := start + s.fragmentSize
|
||||
if end > len(orig) {
|
||||
end = len(orig)
|
||||
}
|
||||
rv = append(rv, &highlight.Fragment{Orig: orig, Start: start, End: end})
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.Fragmenter, error) {
|
||||
size := defaultFragmentSize
|
||||
sizeVal, ok := config["size"].(float64)
|
||||
if ok {
|
||||
size = int(sizeVal)
|
||||
}
|
||||
return NewFragmenter(size), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterFragmenter(Name, Constructor)
|
||||
}
|
64
vendor/github.com/blevesearch/bleve/search/highlight/highlighter.go
generated
vendored
Normal file
64
vendor/github.com/blevesearch/bleve/search/highlight/highlighter.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
// 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 highlight
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type Fragment struct {
|
||||
Orig []byte
|
||||
ArrayPositions []uint64
|
||||
Start int
|
||||
End int
|
||||
Score float64
|
||||
Index int // used by heap
|
||||
}
|
||||
|
||||
func (f *Fragment) Overlaps(other *Fragment) bool {
|
||||
if other.Start >= f.Start && other.Start < f.End {
|
||||
return true
|
||||
} else if f.Start >= other.Start && f.Start < other.End {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Fragmenter interface {
|
||||
Fragment([]byte, TermLocations) []*Fragment
|
||||
}
|
||||
|
||||
type FragmentFormatter interface {
|
||||
Format(f *Fragment, orderedTermLocations TermLocations) string
|
||||
}
|
||||
|
||||
type FragmentScorer interface {
|
||||
Score(f *Fragment) float64
|
||||
}
|
||||
|
||||
type Highlighter interface {
|
||||
Fragmenter() Fragmenter
|
||||
SetFragmenter(Fragmenter)
|
||||
|
||||
FragmentFormatter() FragmentFormatter
|
||||
SetFragmentFormatter(FragmentFormatter)
|
||||
|
||||
Separator() string
|
||||
SetSeparator(string)
|
||||
|
||||
BestFragmentInField(*search.DocumentMatch, *document.Document, string) string
|
||||
BestFragmentsInField(*search.DocumentMatch, *document.Document, string, int) []string
|
||||
}
|
50
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/html/html.go
generated
vendored
Normal file
50
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/html/html.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2015 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 html
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search/highlight"
|
||||
htmlFormatter "github.com/blevesearch/bleve/search/highlight/format/html"
|
||||
simpleFragmenter "github.com/blevesearch/bleve/search/highlight/fragmenter/simple"
|
||||
simpleHighlighter "github.com/blevesearch/bleve/search/highlight/highlighter/simple"
|
||||
)
|
||||
|
||||
const Name = "html"
|
||||
|
||||
func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.Highlighter, error) {
|
||||
|
||||
fragmenter, err := cache.FragmenterNamed(simpleFragmenter.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building fragmenter: %v", err)
|
||||
}
|
||||
|
||||
formatter, err := cache.FragmentFormatterNamed(htmlFormatter.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building fragment formatter: %v", err)
|
||||
}
|
||||
|
||||
return simpleHighlighter.NewHighlighter(
|
||||
fragmenter,
|
||||
formatter,
|
||||
simpleHighlighter.DefaultSeparator),
|
||||
nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterHighlighter(Name, Constructor)
|
||||
}
|
49
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/simple/fragment_scorer_simple.go
generated
vendored
Normal file
49
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/simple/fragment_scorer_simple.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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 simple
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/highlight"
|
||||
)
|
||||
|
||||
// FragmentScorer will score fragments by how many
|
||||
// unique terms occur in the fragment with no regard for
|
||||
// any boost values used in the original query
|
||||
type FragmentScorer struct {
|
||||
tlm search.TermLocationMap
|
||||
}
|
||||
|
||||
func NewFragmentScorer(tlm search.TermLocationMap) *FragmentScorer {
|
||||
return &FragmentScorer{
|
||||
tlm: tlm,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FragmentScorer) Score(f *highlight.Fragment) {
|
||||
score := 0.0
|
||||
OUTER:
|
||||
for _, locations := range s.tlm {
|
||||
for _, location := range locations {
|
||||
if highlight.SameArrayPositions(f.ArrayPositions, location.ArrayPositions) && int(location.Start) >= f.Start && int(location.End) <= f.End {
|
||||
score += 1.0
|
||||
// once we find a term in the fragment
|
||||
// don't care about additional matches
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
}
|
||||
f.Score = score
|
||||
}
|
221
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/simple/highlighter_simple.go
generated
vendored
Normal file
221
vendor/github.com/blevesearch/bleve/search/highlight/highlighter/simple/highlighter_simple.go
generated
vendored
Normal file
|
@ -0,0 +1,221 @@
|
|||
// 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 simple
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/highlight"
|
||||
)
|
||||
|
||||
const Name = "simple"
|
||||
const DefaultSeparator = "…"
|
||||
|
||||
type Highlighter struct {
|
||||
fragmenter highlight.Fragmenter
|
||||
formatter highlight.FragmentFormatter
|
||||
sep string
|
||||
}
|
||||
|
||||
func NewHighlighter(fragmenter highlight.Fragmenter, formatter highlight.FragmentFormatter, separator string) *Highlighter {
|
||||
return &Highlighter{
|
||||
fragmenter: fragmenter,
|
||||
formatter: formatter,
|
||||
sep: separator,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Highlighter) Fragmenter() highlight.Fragmenter {
|
||||
return s.fragmenter
|
||||
}
|
||||
|
||||
func (s *Highlighter) SetFragmenter(f highlight.Fragmenter) {
|
||||
s.fragmenter = f
|
||||
}
|
||||
|
||||
func (s *Highlighter) FragmentFormatter() highlight.FragmentFormatter {
|
||||
return s.formatter
|
||||
}
|
||||
|
||||
func (s *Highlighter) SetFragmentFormatter(f highlight.FragmentFormatter) {
|
||||
s.formatter = f
|
||||
}
|
||||
|
||||
func (s *Highlighter) Separator() string {
|
||||
return s.sep
|
||||
}
|
||||
|
||||
func (s *Highlighter) SetSeparator(sep string) {
|
||||
s.sep = sep
|
||||
}
|
||||
|
||||
func (s *Highlighter) BestFragmentInField(dm *search.DocumentMatch, doc *document.Document, field string) string {
|
||||
fragments := s.BestFragmentsInField(dm, doc, field, 1)
|
||||
if len(fragments) > 0 {
|
||||
return fragments[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Highlighter) BestFragmentsInField(dm *search.DocumentMatch, doc *document.Document, field string, num int) []string {
|
||||
tlm := dm.Locations[field]
|
||||
orderedTermLocations := highlight.OrderTermLocations(tlm)
|
||||
scorer := NewFragmentScorer(tlm)
|
||||
|
||||
// score the fragments and put them into a priority queue ordered by score
|
||||
fq := make(FragmentQueue, 0)
|
||||
heap.Init(&fq)
|
||||
for _, f := range doc.Fields {
|
||||
if f.Name() == field {
|
||||
_, ok := f.(*document.TextField)
|
||||
if ok {
|
||||
termLocationsSameArrayPosition := make(highlight.TermLocations, 0)
|
||||
for _, otl := range orderedTermLocations {
|
||||
if highlight.SameArrayPositions(f.ArrayPositions(), otl.ArrayPositions) {
|
||||
termLocationsSameArrayPosition = append(termLocationsSameArrayPosition, otl)
|
||||
}
|
||||
}
|
||||
|
||||
fieldData := f.Value()
|
||||
fragments := s.fragmenter.Fragment(fieldData, termLocationsSameArrayPosition)
|
||||
for _, fragment := range fragments {
|
||||
fragment.ArrayPositions = f.ArrayPositions()
|
||||
scorer.Score(fragment)
|
||||
heap.Push(&fq, fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now find the N best non-overlapping fragments
|
||||
var bestFragments []*highlight.Fragment
|
||||
if len(fq) > 0 {
|
||||
candidate := heap.Pop(&fq)
|
||||
OUTER:
|
||||
for candidate != nil && len(bestFragments) < num {
|
||||
// see if this overlaps with any of the best already identified
|
||||
if len(bestFragments) > 0 {
|
||||
for _, frag := range bestFragments {
|
||||
if candidate.(*highlight.Fragment).Overlaps(frag) {
|
||||
if len(fq) < 1 {
|
||||
break OUTER
|
||||
}
|
||||
candidate = heap.Pop(&fq)
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
bestFragments = append(bestFragments, candidate.(*highlight.Fragment))
|
||||
} else {
|
||||
bestFragments = append(bestFragments, candidate.(*highlight.Fragment))
|
||||
}
|
||||
|
||||
if len(fq) < 1 {
|
||||
break
|
||||
}
|
||||
candidate = heap.Pop(&fq)
|
||||
}
|
||||
}
|
||||
|
||||
// now that we have the best fragments, we can format them
|
||||
orderedTermLocations.MergeOverlapping()
|
||||
formattedFragments := make([]string, len(bestFragments))
|
||||
for i, fragment := range bestFragments {
|
||||
formattedFragments[i] = ""
|
||||
if fragment.Start != 0 {
|
||||
formattedFragments[i] += s.sep
|
||||
}
|
||||
formattedFragments[i] += s.formatter.Format(fragment, orderedTermLocations)
|
||||
if fragment.End != len(fragment.Orig) {
|
||||
formattedFragments[i] += s.sep
|
||||
}
|
||||
}
|
||||
|
||||
if dm.Fragments == nil {
|
||||
dm.Fragments = make(search.FieldFragmentMap, 0)
|
||||
}
|
||||
if len(formattedFragments) > 0 {
|
||||
dm.Fragments[field] = formattedFragments
|
||||
}
|
||||
|
||||
return formattedFragments
|
||||
}
|
||||
|
||||
// FragmentQueue implements heap.Interface and holds Items.
|
||||
type FragmentQueue []*highlight.Fragment
|
||||
|
||||
func (fq FragmentQueue) Len() int { return len(fq) }
|
||||
|
||||
func (fq FragmentQueue) Less(i, j int) bool {
|
||||
// We want Pop to give us the highest, not lowest, priority so we use greater-than here.
|
||||
return fq[i].Score > fq[j].Score
|
||||
}
|
||||
|
||||
func (fq FragmentQueue) Swap(i, j int) {
|
||||
fq[i], fq[j] = fq[j], fq[i]
|
||||
fq[i].Index = i
|
||||
fq[j].Index = j
|
||||
}
|
||||
|
||||
func (fq *FragmentQueue) Push(x interface{}) {
|
||||
n := len(*fq)
|
||||
item := x.(*highlight.Fragment)
|
||||
item.Index = n
|
||||
*fq = append(*fq, item)
|
||||
}
|
||||
|
||||
func (fq *FragmentQueue) Pop() interface{} {
|
||||
old := *fq
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
item.Index = -1 // for safety
|
||||
*fq = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.Highlighter, error) {
|
||||
separator := DefaultSeparator
|
||||
separatorVal, ok := config["separator"].(string)
|
||||
if ok {
|
||||
separator = separatorVal
|
||||
}
|
||||
|
||||
fragmenterName, ok := config["fragmenter"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("must specify fragmenter")
|
||||
}
|
||||
fragmenter, err := cache.FragmenterNamed(fragmenterName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building fragmenter: %v", err)
|
||||
}
|
||||
|
||||
formatterName, ok := config["formatter"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("must specify formatter")
|
||||
}
|
||||
formatter, err := cache.FragmentFormatterNamed(formatterName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building fragment formatter: %v", err)
|
||||
}
|
||||
|
||||
return NewHighlighter(fragmenter, formatter, separator), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterHighlighter(Name, Constructor)
|
||||
}
|
117
vendor/github.com/blevesearch/bleve/search/highlight/term_locations.go
generated
vendored
Normal file
117
vendor/github.com/blevesearch/bleve/search/highlight/term_locations.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
// 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 highlight
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type TermLocation struct {
|
||||
Term string
|
||||
ArrayPositions []float64
|
||||
Pos int
|
||||
Start int
|
||||
End int
|
||||
}
|
||||
|
||||
func (tl *TermLocation) Overlaps(other *TermLocation) bool {
|
||||
if reflect.DeepEqual(tl.ArrayPositions, other.ArrayPositions) {
|
||||
if other.Start >= tl.Start && other.Start < tl.End {
|
||||
return true
|
||||
} else if tl.Start >= other.Start && tl.Start < other.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TermLocations []*TermLocation
|
||||
|
||||
func (t TermLocations) Len() int { return len(t) }
|
||||
func (t TermLocations) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t TermLocations) Less(i, j int) bool {
|
||||
|
||||
shortestArrayPositions := len(t[i].ArrayPositions)
|
||||
if len(t[j].ArrayPositions) < shortestArrayPositions {
|
||||
shortestArrayPositions = len(t[j].ArrayPositions)
|
||||
}
|
||||
|
||||
// compare all the common array positions
|
||||
for api := 0; api < shortestArrayPositions; api++ {
|
||||
if t[i].ArrayPositions[api] < t[j].ArrayPositions[api] {
|
||||
return true
|
||||
}
|
||||
if t[i].ArrayPositions[api] > t[j].ArrayPositions[api] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// all the common array positions are the same
|
||||
if len(t[i].ArrayPositions) < len(t[j].ArrayPositions) {
|
||||
return true // j array positions, longer so greater
|
||||
} else if len(t[i].ArrayPositions) > len(t[j].ArrayPositions) {
|
||||
return false // j array positions, shorter so less
|
||||
}
|
||||
|
||||
// array positions the same, compare starts
|
||||
return t[i].Start < t[j].Start
|
||||
}
|
||||
|
||||
func (t TermLocations) MergeOverlapping() {
|
||||
var lastTl *TermLocation
|
||||
for i, tl := range t {
|
||||
if lastTl == nil && tl != nil {
|
||||
lastTl = tl
|
||||
} else if lastTl != nil && tl != nil {
|
||||
if lastTl.Overlaps(tl) {
|
||||
// ok merge this with previous
|
||||
lastTl.End = tl.End
|
||||
t[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func OrderTermLocations(tlm search.TermLocationMap) TermLocations {
|
||||
rv := make(TermLocations, 0)
|
||||
for term, locations := range tlm {
|
||||
for _, location := range locations {
|
||||
tl := TermLocation{
|
||||
Term: term,
|
||||
ArrayPositions: location.ArrayPositions,
|
||||
Pos: int(location.Pos),
|
||||
Start: int(location.Start),
|
||||
End: int(location.End),
|
||||
}
|
||||
rv = append(rv, &tl)
|
||||
}
|
||||
}
|
||||
sort.Sort(rv)
|
||||
return rv
|
||||
}
|
||||
|
||||
func SameArrayPositions(fieldArrayPositions []uint64, termLocationArrayPositions []float64) bool {
|
||||
if len(fieldArrayPositions) != len(termLocationArrayPositions) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(fieldArrayPositions); i++ {
|
||||
if fieldArrayPositions[i] != uint64(termLocationArrayPositions[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
105
vendor/github.com/blevesearch/bleve/search/levenshtein.go
generated
vendored
Normal file
105
vendor/github.com/blevesearch/bleve/search/levenshtein.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func LevenshteinDistance(a, b string) int {
|
||||
la := len(a)
|
||||
lb := len(b)
|
||||
d := make([]int, la+1)
|
||||
var lastdiag, olddiag, temp int
|
||||
|
||||
for i := 1; i <= la; i++ {
|
||||
d[i] = i
|
||||
}
|
||||
for i := 1; i <= lb; i++ {
|
||||
d[0] = i
|
||||
lastdiag = i - 1
|
||||
for j := 1; j <= la; j++ {
|
||||
olddiag = d[j]
|
||||
min := d[j] + 1
|
||||
if (d[j-1] + 1) < min {
|
||||
min = d[j-1] + 1
|
||||
}
|
||||
if a[j-1] == b[i-1] {
|
||||
temp = 0
|
||||
} else {
|
||||
temp = 1
|
||||
}
|
||||
if (lastdiag + temp) < min {
|
||||
min = lastdiag + temp
|
||||
}
|
||||
d[j] = min
|
||||
lastdiag = olddiag
|
||||
}
|
||||
}
|
||||
return d[la]
|
||||
}
|
||||
|
||||
// LevenshteinDistanceMax same as LevenshteinDistance but
|
||||
// attempts to bail early once we know the distance
|
||||
// will be greater than max
|
||||
// in which case the first return val will be the max
|
||||
// and the second will be true, indicating max was exceeded
|
||||
func LevenshteinDistanceMax(a, b string, max int) (int, bool) {
|
||||
la := len(a)
|
||||
lb := len(b)
|
||||
|
||||
ld := int(math.Abs(float64(la - lb)))
|
||||
if ld > max {
|
||||
return max, true
|
||||
}
|
||||
|
||||
d := make([]int, la+1)
|
||||
var lastdiag, olddiag, temp int
|
||||
|
||||
for i := 1; i <= la; i++ {
|
||||
d[i] = i
|
||||
}
|
||||
for i := 1; i <= lb; i++ {
|
||||
d[0] = i
|
||||
lastdiag = i - 1
|
||||
rowmin := max + 1
|
||||
for j := 1; j <= la; j++ {
|
||||
olddiag = d[j]
|
||||
min := d[j] + 1
|
||||
if (d[j-1] + 1) < min {
|
||||
min = d[j-1] + 1
|
||||
}
|
||||
if a[j-1] == b[i-1] {
|
||||
temp = 0
|
||||
} else {
|
||||
temp = 1
|
||||
}
|
||||
if (lastdiag + temp) < min {
|
||||
min = lastdiag + temp
|
||||
}
|
||||
if min < rowmin {
|
||||
rowmin = min
|
||||
}
|
||||
d[j] = min
|
||||
|
||||
lastdiag = olddiag
|
||||
}
|
||||
// after each row if rowmin isn't less than max stop
|
||||
if rowmin > max {
|
||||
return max, true
|
||||
}
|
||||
}
|
||||
return d[la], false
|
||||
}
|
76
vendor/github.com/blevesearch/bleve/search/pool.go
generated
vendored
Normal file
76
vendor/github.com/blevesearch/bleve/search/pool.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
// 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
|
||||
|
||||
// DocumentMatchPoolTooSmall is a callback function that can be executed
|
||||
// when the DocumentMatchPool does not have sufficient capacity
|
||||
// By default we just perform just-in-time allocation, but you could log
|
||||
// a message, or panic, etc.
|
||||
type DocumentMatchPoolTooSmall func(p *DocumentMatchPool) *DocumentMatch
|
||||
|
||||
// DocumentMatchPool manages use/re-use of DocumentMatch instances
|
||||
// it pre-allocates space from a single large block with the expected
|
||||
// number of instances. It is not thread-safe as currently all
|
||||
// aspects of search take place in a single goroutine.
|
||||
type DocumentMatchPool struct {
|
||||
avail DocumentMatchCollection
|
||||
TooSmall DocumentMatchPoolTooSmall
|
||||
}
|
||||
|
||||
func defaultDocumentMatchPoolTooSmall(p *DocumentMatchPool) *DocumentMatch {
|
||||
return &DocumentMatch{}
|
||||
}
|
||||
|
||||
// NewDocumentMatchPool will build a DocumentMatchPool with memory
|
||||
// pre-allocated to accommodate the requested number of DocumentMatch
|
||||
// instances
|
||||
func NewDocumentMatchPool(size, sortsize int) *DocumentMatchPool {
|
||||
avail := make(DocumentMatchCollection, 0, size)
|
||||
// pre-allocate the expected number of instances
|
||||
startBlock := make([]DocumentMatch, size)
|
||||
// make these initial instances available
|
||||
for i := range startBlock {
|
||||
startBlock[i].Sort = make([]string, 0, sortsize)
|
||||
avail = append(avail, &startBlock[i])
|
||||
}
|
||||
return &DocumentMatchPool{
|
||||
avail: avail,
|
||||
TooSmall: defaultDocumentMatchPoolTooSmall,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns an available DocumentMatch from the pool
|
||||
// if the pool was not allocated with sufficient size, an allocation will
|
||||
// occur to satisfy this request. As a side-effect this will grow the size
|
||||
// of the pool.
|
||||
func (p *DocumentMatchPool) Get() *DocumentMatch {
|
||||
var rv *DocumentMatch
|
||||
if len(p.avail) > 0 {
|
||||
rv, p.avail = p.avail[len(p.avail)-1], p.avail[:len(p.avail)-1]
|
||||
} else {
|
||||
rv = p.TooSmall(p)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// Put returns a DocumentMatch to the pool
|
||||
func (p *DocumentMatchPool) Put(d *DocumentMatch) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
// reset DocumentMatch before returning it to available pool
|
||||
d.Reset()
|
||||
p.avail = append(p.avail, d)
|
||||
}
|
65
vendor/github.com/blevesearch/bleve/search/query/bool_field.go
generated
vendored
Normal file
65
vendor/github.com/blevesearch/bleve/search/query/bool_field.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type BoolFieldQuery struct {
|
||||
Bool bool `json:"bool"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewBoolFieldQuery creates a new Query for boolean fields
|
||||
func NewBoolFieldQuery(val bool) *BoolFieldQuery {
|
||||
return &BoolFieldQuery{
|
||||
Bool: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
|
||||
func (q *BoolFieldQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
term := "F"
|
||||
if q.Bool {
|
||||
term = "T"
|
||||
}
|
||||
return searcher.NewTermSearcher(i, term, field, q.BoostVal.Value(), explain)
|
||||
}
|
209
vendor/github.com/blevesearch/bleve/search/query/boolean.go
generated
vendored
Normal file
209
vendor/github.com/blevesearch/bleve/search/query/boolean.go
generated
vendored
Normal file
|
@ -0,0 +1,209 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type BooleanQuery struct {
|
||||
Must Query `json:"must,omitempty"`
|
||||
Should Query `json:"should,omitempty"`
|
||||
MustNot Query `json:"must_not,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewBooleanQuery creates a compound Query composed
|
||||
// of several other Query objects.
|
||||
// Result documents must satisfy ALL of the
|
||||
// must Queries.
|
||||
// Result documents must satisfy NONE of the must not
|
||||
// Queries.
|
||||
// Result documents that ALSO satisfy any of the should
|
||||
// Queries will score higher.
|
||||
func NewBooleanQuery(must []Query, should []Query, mustNot []Query) *BooleanQuery {
|
||||
|
||||
rv := BooleanQuery{}
|
||||
if len(must) > 0 {
|
||||
rv.Must = NewConjunctionQuery(must)
|
||||
}
|
||||
if len(should) > 0 {
|
||||
rv.Should = NewDisjunctionQuery(should)
|
||||
}
|
||||
if len(mustNot) > 0 {
|
||||
rv.MustNot = NewDisjunctionQuery(mustNot)
|
||||
}
|
||||
|
||||
return &rv
|
||||
}
|
||||
|
||||
// SetMinShould requires that at least minShould of the
|
||||
// should Queries must be satisfied.
|
||||
func (q *BooleanQuery) SetMinShould(minShould float64) {
|
||||
q.Should.(*DisjunctionQuery).SetMin(minShould)
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) AddMust(m ...Query) {
|
||||
if q.Must == nil {
|
||||
q.Must = NewConjunctionQuery([]Query{})
|
||||
}
|
||||
for _, mq := range m {
|
||||
q.Must.(*ConjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) AddShould(m ...Query) {
|
||||
if q.Should == nil {
|
||||
q.Should = NewDisjunctionQuery([]Query{})
|
||||
}
|
||||
for _, mq := range m {
|
||||
q.Should.(*DisjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) AddMustNot(m ...Query) {
|
||||
if q.MustNot == nil {
|
||||
q.MustNot = NewDisjunctionQuery([]Query{})
|
||||
}
|
||||
for _, mq := range m {
|
||||
q.MustNot.(*DisjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
var err error
|
||||
var mustNotSearcher search.Searcher
|
||||
if q.MustNot != nil {
|
||||
mustNotSearcher, err = q.MustNot.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if q.Must == nil && q.Should == nil {
|
||||
q.Must = NewMatchAllQuery()
|
||||
}
|
||||
}
|
||||
|
||||
var mustSearcher search.Searcher
|
||||
if q.Must != nil {
|
||||
mustSearcher, err = q.Must.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var shouldSearcher search.Searcher
|
||||
if q.Should != nil {
|
||||
shouldSearcher, err = q.Should.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if mustSearcher == nil && shouldSearcher != nil && mustNotSearcher == nil {
|
||||
return shouldSearcher, nil
|
||||
}
|
||||
|
||||
return searcher.NewBooleanSearcher(i, mustSearcher, shouldSearcher, mustNotSearcher, explain)
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) Validate() error {
|
||||
if qm, ok := q.Must.(ValidatableQuery); ok {
|
||||
err := qm.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if qs, ok := q.Should.(ValidatableQuery); ok {
|
||||
err := qs.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if qmn, ok := q.MustNot.(ValidatableQuery); ok {
|
||||
err := qmn.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.Must == nil && q.Should == nil && q.MustNot == nil {
|
||||
return fmt.Errorf("boolean query must contain at least one must or should or not must clause")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Must json.RawMessage `json:"must,omitempty"`
|
||||
Should json.RawMessage `json:"should,omitempty"`
|
||||
MustNot json.RawMessage `json:"must_not,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Must != nil {
|
||||
q.Must, err = ParseQuery(tmp.Must)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isConjunctionQuery := q.Must.(*ConjunctionQuery)
|
||||
if !isConjunctionQuery {
|
||||
return fmt.Errorf("must clause must be conjunction")
|
||||
}
|
||||
}
|
||||
|
||||
if tmp.Should != nil {
|
||||
q.Should, err = ParseQuery(tmp.Should)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isDisjunctionQuery := q.Should.(*DisjunctionQuery)
|
||||
if !isDisjunctionQuery {
|
||||
return fmt.Errorf("should clause must be disjunction")
|
||||
}
|
||||
}
|
||||
|
||||
if tmp.MustNot != nil {
|
||||
q.MustNot, err = ParseQuery(tmp.MustNot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isDisjunctionQuery := q.MustNot.(*DisjunctionQuery)
|
||||
if !isDisjunctionQuery {
|
||||
return fmt.Errorf("must not clause must be disjunction")
|
||||
}
|
||||
}
|
||||
|
||||
q.BoostVal = tmp.Boost
|
||||
|
||||
return nil
|
||||
}
|
33
vendor/github.com/blevesearch/bleve/search/query/boost.go
generated
vendored
Normal file
33
vendor/github.com/blevesearch/bleve/search/query/boost.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// 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 query
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Boost float64
|
||||
|
||||
func (b *Boost) Value() float64 {
|
||||
if b == nil {
|
||||
return 1.0
|
||||
}
|
||||
return float64(*b)
|
||||
}
|
||||
|
||||
func (b *Boost) GoString() string {
|
||||
if b == nil {
|
||||
return "boost unspecified"
|
||||
}
|
||||
return fmt.Sprintf("%f", *b)
|
||||
}
|
102
vendor/github.com/blevesearch/bleve/search/query/conjunction.go
generated
vendored
Normal file
102
vendor/github.com/blevesearch/bleve/search/query/conjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type ConjunctionQuery struct {
|
||||
Conjuncts []Query `json:"conjuncts"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewConjunctionQuery creates a new compound Query.
|
||||
// Result documents must satisfy all of the queries.
|
||||
func NewConjunctionQuery(conjuncts []Query) *ConjunctionQuery {
|
||||
return &ConjunctionQuery{
|
||||
Conjuncts: conjuncts,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) AddQuery(aq ...Query) {
|
||||
for _, aaq := range aq {
|
||||
q.Conjuncts = append(q.Conjuncts, aaq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
ss := make([]search.Searcher, len(q.Conjuncts))
|
||||
for in, conjunct := range q.Conjuncts {
|
||||
var err error
|
||||
ss[in], err = conjunct.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
for _, searcher := range ss {
|
||||
if searcher != nil {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return searcher.NewConjunctionSearcher(i, ss, explain)
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) Validate() error {
|
||||
for _, q := range q.Conjuncts {
|
||||
if q, ok := q.(ValidatableQuery); ok {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *ConjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Conjuncts []json.RawMessage `json:"conjuncts"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Conjuncts = make([]Query, len(tmp.Conjuncts))
|
||||
for i, term := range tmp.Conjuncts {
|
||||
query, err := ParseQuery(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Conjuncts[i] = query
|
||||
}
|
||||
q.BoostVal = tmp.Boost
|
||||
return nil
|
||||
}
|
165
vendor/github.com/blevesearch/bleve/search/query/date_range.go
generated
vendored
Normal file
165
vendor/github.com/blevesearch/bleve/search/query/date_range.go
generated
vendored
Normal file
|
@ -0,0 +1,165 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis/datetime/optional"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/numeric"
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
// QueryDateTimeParser controls the default query date time parser
|
||||
var QueryDateTimeParser = optional.Name
|
||||
|
||||
// QueryDateTimeFormat controls the format when Marshaling to JSON
|
||||
var QueryDateTimeFormat = time.RFC3339
|
||||
|
||||
var cache = registry.NewCache()
|
||||
|
||||
type BleveQueryTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func queryTimeFromString(t string) (time.Time, error) {
|
||||
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
rv, err := dateTimeParser.ParseDateTime(t)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (t *BleveQueryTime) MarshalJSON() ([]byte, error) {
|
||||
tt := time.Time(t.Time)
|
||||
return []byte("\"" + tt.Format(QueryDateTimeFormat) + "\""), nil
|
||||
}
|
||||
|
||||
func (t *BleveQueryTime) UnmarshalJSON(data []byte) error {
|
||||
var timeString string
|
||||
err := json.Unmarshal(data, &timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Time, err = dateTimeParser.ParseDateTime(timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DateRangeQuery struct {
|
||||
Start BleveQueryTime `json:"start,omitempty"`
|
||||
End BleveQueryTime `json:"end,omitempty"`
|
||||
InclusiveStart *bool `json:"inclusive_start,omitempty"`
|
||||
InclusiveEnd *bool `json:"inclusive_end,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewDateRangeQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
func NewDateRangeQuery(start, end time.Time) *DateRangeQuery {
|
||||
return NewDateRangeInclusiveQuery(start, end, nil, nil)
|
||||
}
|
||||
|
||||
// NewDateRangeInclusiveQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
// startInclusive and endInclusive control inclusion of the endpoints.
|
||||
func NewDateRangeInclusiveQuery(start, end time.Time, startInclusive, endInclusive *bool) *DateRangeQuery {
|
||||
return &DateRangeQuery{
|
||||
Start: BleveQueryTime{start},
|
||||
End: BleveQueryTime{end},
|
||||
InclusiveStart: startInclusive,
|
||||
InclusiveEnd: endInclusive,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
|
||||
func (q *DateRangeQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
min, max, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
return searcher.NewNumericRangeSearcher(i, min, max, q.InclusiveStart, q.InclusiveEnd, field, q.BoostVal.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) parseEndpoints() (*float64, *float64, error) {
|
||||
min := math.Inf(-1)
|
||||
max := math.Inf(1)
|
||||
if !q.Start.IsZero() {
|
||||
min = numeric.Int64ToFloat64(q.Start.UnixNano())
|
||||
}
|
||||
if !q.End.IsZero() {
|
||||
max = numeric.Int64ToFloat64(q.End.UnixNano())
|
||||
}
|
||||
|
||||
return &min, &max, nil
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Validate() error {
|
||||
if q.Start.IsZero() && q.End.IsZero() {
|
||||
return fmt.Errorf("must specify start or end")
|
||||
}
|
||||
_, _, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
114
vendor/github.com/blevesearch/bleve/search/query/disjunction.go
generated
vendored
Normal file
114
vendor/github.com/blevesearch/bleve/search/query/disjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type DisjunctionQuery struct {
|
||||
Disjuncts []Query `json:"disjuncts"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
Min float64 `json:"min"`
|
||||
}
|
||||
|
||||
// NewDisjunctionQuery creates a new compound Query.
|
||||
// Result documents satisfy at least one Query.
|
||||
func NewDisjunctionQuery(disjuncts []Query) *DisjunctionQuery {
|
||||
return &DisjunctionQuery{
|
||||
Disjuncts: disjuncts,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
|
||||
func (q *DisjunctionQuery) AddQuery(aq ...Query) {
|
||||
for _, aaq := range aq {
|
||||
q.Disjuncts = append(q.Disjuncts, aaq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) SetMin(m float64) {
|
||||
q.Min = m
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
ss := make([]search.Searcher, len(q.Disjuncts))
|
||||
for in, disjunct := range q.Disjuncts {
|
||||
var err error
|
||||
ss[in], err = disjunct.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
for _, searcher := range ss {
|
||||
if searcher != nil {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return searcher.NewDisjunctionSearcher(i, ss, q.Min, explain)
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) Validate() error {
|
||||
if int(q.Min) > len(q.Disjuncts) {
|
||||
return fmt.Errorf("disjunction query has fewer than the minimum number of clauses to satisfy")
|
||||
}
|
||||
for _, q := range q.Disjuncts {
|
||||
if q, ok := q.(ValidatableQuery); ok {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Disjuncts []json.RawMessage `json:"disjuncts"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
Min float64 `json:"min"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts = make([]Query, len(tmp.Disjuncts))
|
||||
for i, term := range tmp.Disjuncts {
|
||||
query, err := ParseQuery(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts[i] = query
|
||||
}
|
||||
q.BoostVal = tmp.Boost
|
||||
q.Min = tmp.Min
|
||||
return nil
|
||||
}
|
49
vendor/github.com/blevesearch/bleve/search/query/docid.go
generated
vendored
Normal file
49
vendor/github.com/blevesearch/bleve/search/query/docid.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2015 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type DocIDQuery struct {
|
||||
IDs []string `json:"ids"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewDocIDQuery creates a new Query object returning indexed documents among
|
||||
// the specified set. Combine it with ConjunctionQuery to restrict the scope of
|
||||
// other queries output.
|
||||
func NewDocIDQuery(ids []string) *DocIDQuery {
|
||||
return &DocIDQuery{
|
||||
IDs: ids,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DocIDQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *DocIDQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *DocIDQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searcher.NewDocIDSearcher(i, q.IDs, q.BoostVal.Value(), explain)
|
||||
}
|
77
vendor/github.com/blevesearch/bleve/search/query/fuzzy.go
generated
vendored
Normal file
77
vendor/github.com/blevesearch/bleve/search/query/fuzzy.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type FuzzyQuery struct {
|
||||
Term string `json:"term"`
|
||||
Prefix int `json:"prefix_length"`
|
||||
Fuzziness int `json:"fuzziness"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewFuzzyQuery creates a new Query which finds
|
||||
// documents containing terms within a specific
|
||||
// fuzziness of the specified term.
|
||||
// The default fuzziness is 1.
|
||||
//
|
||||
// The current implementation uses Levenshtein edit
|
||||
// distance as the fuzziness metric.
|
||||
func NewFuzzyQuery(term string) *FuzzyQuery {
|
||||
return &FuzzyQuery{
|
||||
Term: term,
|
||||
Fuzziness: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetFuzziness(f int) {
|
||||
q.Fuzziness = f
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetPrefix(p int) {
|
||||
q.Prefix = p
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searcher.NewFuzzySearcher(i, q.Term, q.Prefix, q.Fuzziness, field, q.BoostVal.Value(), explain)
|
||||
}
|
176
vendor/github.com/blevesearch/bleve/search/query/match.go
generated
vendored
Normal file
176
vendor/github.com/blevesearch/bleve/search/query/match.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type MatchQuery struct {
|
||||
Match string `json:"match"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
Prefix int `json:"prefix_length"`
|
||||
Fuzziness int `json:"fuzziness"`
|
||||
Operator MatchQueryOperator `json:"operator,omitempty"`
|
||||
}
|
||||
|
||||
type MatchQueryOperator int
|
||||
|
||||
const (
|
||||
// Document must satisfy AT LEAST ONE of term searches.
|
||||
MatchQueryOperatorOr = 0
|
||||
// Document must satisfy ALL of term searches.
|
||||
MatchQueryOperatorAnd = 1
|
||||
)
|
||||
|
||||
func (o MatchQueryOperator) MarshalJSON() ([]byte, error) {
|
||||
switch o {
|
||||
case MatchQueryOperatorOr:
|
||||
return json.Marshal("or")
|
||||
case MatchQueryOperatorAnd:
|
||||
return json.Marshal("and")
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot marshal match operator %d to JSON", o)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *MatchQueryOperator) UnmarshalJSON(data []byte) error {
|
||||
var operatorString string
|
||||
err := json.Unmarshal(data, &operatorString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch operatorString {
|
||||
case "or":
|
||||
*o = MatchQueryOperatorOr
|
||||
return nil
|
||||
case "and":
|
||||
*o = MatchQueryOperatorAnd
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("cannot unmarshal match operator '%v' from JSON", o)
|
||||
}
|
||||
}
|
||||
|
||||
// NewMatchQuery creates a Query for matching text.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to perform term searches. Result documents
|
||||
// must satisfy at least one of these term searches.
|
||||
func NewMatchQuery(match string) *MatchQuery {
|
||||
return &MatchQuery{
|
||||
Match: match,
|
||||
Operator: MatchQueryOperatorOr,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *MatchQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *MatchQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetFuzziness(f int) {
|
||||
q.Fuzziness = f
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetPrefix(p int) {
|
||||
q.Prefix = p
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetOperator(operator MatchQueryOperator) {
|
||||
q.Operator = operator
|
||||
}
|
||||
|
||||
func (q *MatchQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
analyzerName := ""
|
||||
if q.Analyzer != "" {
|
||||
analyzerName = q.Analyzer
|
||||
} else {
|
||||
analyzerName = m.AnalyzerNameForPath(field)
|
||||
}
|
||||
analyzer := m.AnalyzerNamed(analyzerName)
|
||||
|
||||
if analyzer == nil {
|
||||
return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
|
||||
}
|
||||
|
||||
tokens := analyzer.Analyze([]byte(q.Match))
|
||||
if len(tokens) > 0 {
|
||||
|
||||
tqs := make([]Query, len(tokens))
|
||||
if q.Fuzziness != 0 {
|
||||
for i, token := range tokens {
|
||||
query := NewFuzzyQuery(string(token.Term))
|
||||
query.SetFuzziness(q.Fuzziness)
|
||||
query.SetPrefix(q.Prefix)
|
||||
query.SetField(field)
|
||||
query.SetBoost(q.BoostVal.Value())
|
||||
tqs[i] = query
|
||||
}
|
||||
} else {
|
||||
for i, token := range tokens {
|
||||
tq := NewTermQuery(string(token.Term))
|
||||
tq.SetField(field)
|
||||
tq.SetBoost(q.BoostVal.Value())
|
||||
tqs[i] = tq
|
||||
}
|
||||
}
|
||||
|
||||
switch q.Operator {
|
||||
case MatchQueryOperatorOr:
|
||||
shouldQuery := NewDisjunctionQuery(tqs)
|
||||
shouldQuery.SetMin(1)
|
||||
shouldQuery.SetBoost(q.BoostVal.Value())
|
||||
return shouldQuery.Searcher(i, m, explain)
|
||||
|
||||
case MatchQueryOperatorAnd:
|
||||
mustQuery := NewConjunctionQuery(tqs)
|
||||
mustQuery.SetBoost(q.BoostVal.Value())
|
||||
return mustQuery.Searcher(i, m, explain)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operator %d", q.Operator)
|
||||
}
|
||||
}
|
||||
noneQuery := NewMatchNoneQuery()
|
||||
return noneQuery.Searcher(i, m, explain)
|
||||
}
|
57
vendor/github.com/blevesearch/bleve/search/query/match_all.go
generated
vendored
Normal file
57
vendor/github.com/blevesearch/bleve/search/query/match_all.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type MatchAllQuery struct {
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchAllQuery creates a Query which will
|
||||
// match all documents in the index.
|
||||
func NewMatchAllQuery() *MatchAllQuery {
|
||||
return &MatchAllQuery{}
|
||||
}
|
||||
|
||||
func (q *MatchAllQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *MatchAllQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (q *MatchAllQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searcher.NewMatchAllSearcher(i, q.BoostVal.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *MatchAllQuery) MarshalJSON() ([]byte, error) {
|
||||
tmp := map[string]interface{}{
|
||||
"boost": q.BoostVal,
|
||||
"match_all": map[string]interface{}{},
|
||||
}
|
||||
return json.Marshal(tmp)
|
||||
}
|
55
vendor/github.com/blevesearch/bleve/search/query/match_none.go
generated
vendored
Normal file
55
vendor/github.com/blevesearch/bleve/search/query/match_none.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type MatchNoneQuery struct {
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchNoneQuery creates a Query which will not
|
||||
// match any documents in the index.
|
||||
func NewMatchNoneQuery() *MatchNoneQuery {
|
||||
return &MatchNoneQuery{}
|
||||
}
|
||||
|
||||
func (q *MatchNoneQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *MatchNoneQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *MatchNoneQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searcher.NewMatchNoneSearcher(i)
|
||||
}
|
||||
|
||||
func (q *MatchNoneQuery) MarshalJSON() ([]byte, error) {
|
||||
tmp := map[string]interface{}{
|
||||
"boost": q.BoostVal,
|
||||
"match_none": map[string]interface{}{},
|
||||
}
|
||||
return json.Marshal(tmp)
|
||||
}
|
116
vendor/github.com/blevesearch/bleve/search/query/match_phrase.go
generated
vendored
Normal file
116
vendor/github.com/blevesearch/bleve/search/query/match_phrase.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type MatchPhraseQuery struct {
|
||||
MatchPhrase string `json:"match_phrase"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchPhraseQuery creates a new Query object
|
||||
// for matching phrases in the index.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to build a search phrase. Result documents
|
||||
// must match this phrase. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewMatchPhraseQuery(matchPhrase string) *MatchPhraseQuery {
|
||||
return &MatchPhraseQuery{
|
||||
MatchPhrase: matchPhrase,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *MatchPhraseQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *MatchPhraseQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *MatchPhraseQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *MatchPhraseQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *MatchPhraseQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
analyzerName := ""
|
||||
if q.Analyzer != "" {
|
||||
analyzerName = q.Analyzer
|
||||
} else {
|
||||
analyzerName = m.AnalyzerNameForPath(field)
|
||||
}
|
||||
analyzer := m.AnalyzerNamed(analyzerName)
|
||||
if analyzer == nil {
|
||||
return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
|
||||
}
|
||||
|
||||
tokens := analyzer.Analyze([]byte(q.MatchPhrase))
|
||||
if len(tokens) > 0 {
|
||||
phrase := tokenStreamToPhrase(tokens)
|
||||
phraseQuery := NewPhraseQuery(phrase, field)
|
||||
phraseQuery.SetBoost(q.BoostVal.Value())
|
||||
return phraseQuery.Searcher(i, m, explain)
|
||||
}
|
||||
noneQuery := NewMatchNoneQuery()
|
||||
return noneQuery.Searcher(i, m, explain)
|
||||
}
|
||||
|
||||
func tokenStreamToPhrase(tokens analysis.TokenStream) []string {
|
||||
firstPosition := int(^uint(0) >> 1)
|
||||
lastPosition := 0
|
||||
for _, token := range tokens {
|
||||
if token.Position < firstPosition {
|
||||
firstPosition = token.Position
|
||||
}
|
||||
if token.Position > lastPosition {
|
||||
lastPosition = token.Position
|
||||
}
|
||||
}
|
||||
phraseLen := lastPosition - firstPosition + 1
|
||||
if phraseLen > 0 {
|
||||
rv := make([]string, phraseLen)
|
||||
for i := 0; i < phraseLen; i++ {
|
||||
rv[i] = ""
|
||||
}
|
||||
for _, token := range tokens {
|
||||
pos := token.Position - firstPosition
|
||||
rv[pos] = string(token.Term)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
return nil
|
||||
}
|
87
vendor/github.com/blevesearch/bleve/search/query/numeric_range.go
generated
vendored
Normal file
87
vendor/github.com/blevesearch/bleve/search/query/numeric_range.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type NumericRangeQuery struct {
|
||||
Min *float64 `json:"min,omitempty"`
|
||||
Max *float64 `json:"max,omitempty"`
|
||||
InclusiveMin *bool `json:"inclusive_min,omitempty"`
|
||||
InclusiveMax *bool `json:"inclusive_max,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewNumericRangeQuery creates a new Query for ranges
|
||||
// of numeric values.
|
||||
// Either, but not both endpoints can be nil.
|
||||
// The minimum value is inclusive.
|
||||
// The maximum value is exclusive.
|
||||
func NewNumericRangeQuery(min, max *float64) *NumericRangeQuery {
|
||||
return NewNumericRangeInclusiveQuery(min, max, nil, nil)
|
||||
}
|
||||
|
||||
// NewNumericRangeInclusiveQuery creates a new Query for ranges
|
||||
// of numeric values.
|
||||
// Either, but not both endpoints can be nil.
|
||||
// Control endpoint inclusion with inclusiveMin, inclusiveMax.
|
||||
func NewNumericRangeInclusiveQuery(min, max *float64, minInclusive, maxInclusive *bool) *NumericRangeQuery {
|
||||
return &NumericRangeQuery{
|
||||
Min: min,
|
||||
Max: max,
|
||||
InclusiveMin: minInclusive,
|
||||
InclusiveMax: maxInclusive,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searcher.NewNumericRangeSearcher(i, q.Min, q.Max, q.InclusiveMin, q.InclusiveMax, field, q.BoostVal.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *NumericRangeQuery) Validate() error {
|
||||
if q.Min == nil && q.Min == q.Max {
|
||||
return fmt.Errorf("numeric range query must specify min or max")
|
||||
}
|
||||
return nil
|
||||
}
|
97
vendor/github.com/blevesearch/bleve/search/query/phrase.go
generated
vendored
Normal file
97
vendor/github.com/blevesearch/bleve/search/query/phrase.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type PhraseQuery struct {
|
||||
Terms []string `json:"terms"`
|
||||
Field string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
termQueries []Query
|
||||
}
|
||||
|
||||
// NewPhraseQuery creates a new Query for finding
|
||||
// exact term phrases in the index.
|
||||
// The provided terms must exist in the correct
|
||||
// order, at the correct index offsets, in the
|
||||
// specified field. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewPhraseQuery(terms []string, field string) *PhraseQuery {
|
||||
termQueries := make([]Query, 0)
|
||||
for _, term := range terms {
|
||||
if term != "" {
|
||||
tq := NewTermQuery(term)
|
||||
tq.SetField(field)
|
||||
termQueries = append(termQueries, tq)
|
||||
}
|
||||
}
|
||||
return &PhraseQuery{
|
||||
Terms: terms,
|
||||
Field: field,
|
||||
termQueries: termQueries,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *PhraseQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *PhraseQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *PhraseQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
conjunctionQuery := NewConjunctionQuery(q.termQueries)
|
||||
conjunctionSearcher, err := conjunctionQuery.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return searcher.NewPhraseSearcher(i, conjunctionSearcher.(*searcher.ConjunctionSearcher), q.Terms)
|
||||
}
|
||||
|
||||
func (q *PhraseQuery) Validate() error {
|
||||
if len(q.termQueries) < 1 {
|
||||
return fmt.Errorf("phrase query must contain at least one term")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *PhraseQuery) UnmarshalJSON(data []byte) error {
|
||||
type _phraseQuery PhraseQuery
|
||||
tmp := _phraseQuery{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Terms = tmp.Terms
|
||||
q.Field = tmp.Field
|
||||
q.BoostVal = tmp.BoostVal
|
||||
q.termQueries = make([]Query, len(q.Terms))
|
||||
for i, term := range q.Terms {
|
||||
q.termQueries[i] = &TermQuery{Term: term, FieldVal: q.Field, BoostVal: q.BoostVal}
|
||||
}
|
||||
return nil
|
||||
}
|
62
vendor/github.com/blevesearch/bleve/search/query/prefix.go
generated
vendored
Normal file
62
vendor/github.com/blevesearch/bleve/search/query/prefix.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type PrefixQuery struct {
|
||||
Prefix string `json:"prefix"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewPrefixQuery creates a new Query which finds
|
||||
// documents containing terms that start with the
|
||||
// specified prefix.
|
||||
func NewPrefixQuery(prefix string) *PrefixQuery {
|
||||
return &PrefixQuery{
|
||||
Prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *PrefixQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *PrefixQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *PrefixQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *PrefixQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *PrefixQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searcher.NewTermPrefixSearcher(i, q.Prefix, field, q.BoostVal.Value(), explain)
|
||||
}
|
328
vendor/github.com/blevesearch/bleve/search/query/query.go
generated
vendored
Normal file
328
vendor/github.com/blevesearch/bleve/search/query/query.go
generated
vendored
Normal file
|
@ -0,0 +1,328 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
var logger = log.New(ioutil.Discard, "bleve mapping ", log.LstdFlags)
|
||||
|
||||
// SetLog sets the logger used for logging
|
||||
// by default log messages are sent to ioutil.Discard
|
||||
func SetLog(l *log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
// A Query represents a description of the type
|
||||
// and parameters for a query into the index.
|
||||
type Query interface {
|
||||
Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error)
|
||||
}
|
||||
|
||||
// A BoostableQuery represents a Query which can be boosted
|
||||
// relative to other queries.
|
||||
type BoostableQuery interface {
|
||||
Query
|
||||
SetBoost(b float64)
|
||||
Boost() float64
|
||||
}
|
||||
|
||||
// A FieldableQuery represents a Query which can be restricted
|
||||
// to a single field.
|
||||
type FieldableQuery interface {
|
||||
Query
|
||||
SetField(f string)
|
||||
Field() string
|
||||
}
|
||||
|
||||
// A ValidatableQuery represents a Query which can be validated
|
||||
// prior to execution.
|
||||
type ValidatableQuery interface {
|
||||
Query
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ParseQuery deserializes a JSON representation of
|
||||
// a Query object.
|
||||
func ParseQuery(input []byte) (Query, error) {
|
||||
var tmp map[string]interface{}
|
||||
err := json.Unmarshal(input, &tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, isMatchQuery := tmp["match"]
|
||||
_, hasFuzziness := tmp["fuzziness"]
|
||||
if hasFuzziness && !isMatchQuery {
|
||||
var rv FuzzyQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isTermQuery := tmp["term"]
|
||||
if isTermQuery {
|
||||
var rv TermQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
if isMatchQuery {
|
||||
var rv MatchQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isMatchPhraseQuery := tmp["match_phrase"]
|
||||
if isMatchPhraseQuery {
|
||||
var rv MatchPhraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMust := tmp["must"]
|
||||
_, hasShould := tmp["should"]
|
||||
_, hasMustNot := tmp["must_not"]
|
||||
if hasMust || hasShould || hasMustNot {
|
||||
var rv BooleanQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasTerms := tmp["terms"]
|
||||
if hasTerms {
|
||||
var rv PhraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasConjuncts := tmp["conjuncts"]
|
||||
if hasConjuncts {
|
||||
var rv ConjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDisjuncts := tmp["disjuncts"]
|
||||
if hasDisjuncts {
|
||||
var rv DisjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
_, hasSyntaxQuery := tmp["query"]
|
||||
if hasSyntaxQuery {
|
||||
var rv QueryStringQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMin := tmp["min"]
|
||||
_, hasMax := tmp["max"]
|
||||
if hasMin || hasMax {
|
||||
var rv NumericRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasStart := tmp["start"]
|
||||
_, hasEnd := tmp["end"]
|
||||
if hasStart || hasEnd {
|
||||
var rv DateRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasPrefix := tmp["prefix"]
|
||||
if hasPrefix {
|
||||
var rv PrefixQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasRegexp := tmp["regexp"]
|
||||
if hasRegexp {
|
||||
var rv RegexpQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasWildcard := tmp["wildcard"]
|
||||
if hasWildcard {
|
||||
var rv WildcardQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchAll := tmp["match_all"]
|
||||
if hasMatchAll {
|
||||
var rv MatchAllQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchNone := tmp["match_none"]
|
||||
if hasMatchNone {
|
||||
var rv MatchNoneQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDocIds := tmp["ids"]
|
||||
if hasDocIds {
|
||||
var rv DocIDQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasBool := tmp["bool"]
|
||||
if hasBool {
|
||||
var rv BoolFieldQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown query type")
|
||||
}
|
||||
|
||||
// expandQuery traverses the input query tree and returns a new tree where
|
||||
// query string queries have been expanded into base queries. Returned tree may
|
||||
// reference queries from the input tree or new queries.
|
||||
func expandQuery(m mapping.IndexMapping, query Query) (Query, error) {
|
||||
var expand func(query Query) (Query, error)
|
||||
var expandSlice func(queries []Query) ([]Query, error)
|
||||
|
||||
expandSlice = func(queries []Query) ([]Query, error) {
|
||||
expanded := []Query{}
|
||||
for _, q := range queries {
|
||||
exp, err := expand(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expanded = append(expanded, exp)
|
||||
}
|
||||
return expanded, nil
|
||||
}
|
||||
|
||||
expand = func(query Query) (Query, error) {
|
||||
switch query.(type) {
|
||||
case *QueryStringQuery:
|
||||
q := query.(*QueryStringQuery)
|
||||
parsed, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse '%s': %s", q.Query, err)
|
||||
}
|
||||
return expand(parsed)
|
||||
case *ConjunctionQuery:
|
||||
q := *query.(*ConjunctionQuery)
|
||||
children, err := expandSlice(q.Conjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Conjuncts = children
|
||||
return &q, nil
|
||||
case *DisjunctionQuery:
|
||||
q := *query.(*DisjunctionQuery)
|
||||
children, err := expandSlice(q.Disjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Disjuncts = children
|
||||
return &q, nil
|
||||
case *BooleanQuery:
|
||||
q := *query.(*BooleanQuery)
|
||||
var err error
|
||||
q.Must, err = expand(q.Must)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Should, err = expand(q.Should)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.MustNot, err = expand(q.MustNot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &q, nil
|
||||
case *PhraseQuery:
|
||||
q := *query.(*PhraseQuery)
|
||||
children, err := expandSlice(q.termQueries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.termQueries = children
|
||||
return &q, nil
|
||||
default:
|
||||
return query, nil
|
||||
}
|
||||
}
|
||||
return expand(query)
|
||||
}
|
||||
|
||||
// DumpQuery returns a string representation of the query tree, where query
|
||||
// string queries have been expanded into base queries. The output format is
|
||||
// meant for debugging purpose and may change in the future.
|
||||
func DumpQuery(m mapping.IndexMapping, query Query) (string, error) {
|
||||
q, err := expandQuery(m, query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.MarshalIndent(q, "", " ")
|
||||
return string(data), err
|
||||
}
|
63
vendor/github.com/blevesearch/bleve/search/query/query_string.go
generated
vendored
Normal file
63
vendor/github.com/blevesearch/bleve/search/query/query_string.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type QueryStringQuery struct {
|
||||
Query string `json:"query"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewQueryStringQuery creates a new Query used for
|
||||
// finding documents that satisfy a query string. The
|
||||
// query string is a small query language for humans.
|
||||
func NewQueryStringQuery(query string) *QueryStringQuery {
|
||||
return &QueryStringQuery{
|
||||
Query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QueryStringQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *QueryStringQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *QueryStringQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
newQuery, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newQuery.Searcher(i, m, explain)
|
||||
}
|
||||
|
||||
func (q *QueryStringQuery) Validate() error {
|
||||
newQuery, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newQuery, ok := newQuery.(ValidatableQuery); ok {
|
||||
return newQuery.Validate()
|
||||
}
|
||||
return nil
|
||||
}
|
289
vendor/github.com/blevesearch/bleve/search/query/query_string.y
generated
vendored
Normal file
289
vendor/github.com/blevesearch/bleve/search/query/query_string.y
generated
vendored
Normal file
|
@ -0,0 +1,289 @@
|
|||
%{
|
||||
package query
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func logDebugGrammar(format string, v ...interface{}) {
|
||||
if debugParser {
|
||||
logger.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
%}
|
||||
|
||||
%union {
|
||||
s string
|
||||
n int
|
||||
f float64
|
||||
q Query
|
||||
pf *float64}
|
||||
|
||||
%token tSTRING tPHRASE tPLUS tMINUS tCOLON tBOOST tNUMBER tSTRING tGREATER tLESS
|
||||
tEQUAL tTILDE
|
||||
|
||||
%type <s> tSTRING
|
||||
%type <s> tPHRASE
|
||||
%type <s> tNUMBER
|
||||
%type <s> tTILDE
|
||||
%type <s> tBOOST
|
||||
%type <q> searchBase
|
||||
%type <pf> searchSuffix
|
||||
%type <n> searchPrefix
|
||||
|
||||
%%
|
||||
|
||||
input:
|
||||
searchParts {
|
||||
logDebugGrammar("INPUT")
|
||||
};
|
||||
|
||||
searchParts:
|
||||
searchPart searchParts {
|
||||
logDebugGrammar("SEARCH PARTS")
|
||||
}
|
||||
|
|
||||
searchPart {
|
||||
logDebugGrammar("SEARCH PART")
|
||||
};
|
||||
|
||||
searchPart:
|
||||
searchPrefix searchBase searchSuffix {
|
||||
query := $2
|
||||
if $3 != nil {
|
||||
if query, ok := query.(BoostableQuery); ok {
|
||||
query.SetBoost(*$3)
|
||||
}
|
||||
}
|
||||
switch($1) {
|
||||
case queryShould:
|
||||
yylex.(*lexerWrapper).query.AddShould(query)
|
||||
case queryMust:
|
||||
yylex.(*lexerWrapper).query.AddMust(query)
|
||||
case queryMustNot:
|
||||
yylex.(*lexerWrapper).query.AddMustNot(query)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
searchPrefix:
|
||||
/* empty */ {
|
||||
$$ = queryShould
|
||||
}
|
||||
|
|
||||
tPLUS {
|
||||
logDebugGrammar("PLUS")
|
||||
$$ = queryMust
|
||||
}
|
||||
|
|
||||
tMINUS {
|
||||
logDebugGrammar("MINUS")
|
||||
$$ = queryMustNot
|
||||
};
|
||||
|
||||
searchBase:
|
||||
tSTRING {
|
||||
str := $1
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1:len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?"){
|
||||
q = NewWildcardQuery(str)
|
||||
} else {
|
||||
q = NewMatchQuery(str)
|
||||
}
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tTILDE {
|
||||
str := $1
|
||||
fuzziness, err := strconv.ParseFloat($2, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid fuzziness value: %v", err))
|
||||
}
|
||||
logDebugGrammar("FUZZY STRING - %s %f", str, fuzziness)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetFuzziness(int(fuzziness))
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tSTRING tTILDE {
|
||||
field := $1
|
||||
str := $3
|
||||
fuzziness, err := strconv.ParseFloat($4, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid fuzziness value: %v", err))
|
||||
}
|
||||
logDebugGrammar("FIELD - %s FUZZY STRING - %s %f", field, str, fuzziness)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetFuzziness(int(fuzziness))
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tNUMBER {
|
||||
str := $1
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
q := NewMatchQuery(str)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tPHRASE {
|
||||
phrase := $1
|
||||
logDebugGrammar("PHRASE - %s", phrase)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tSTRING {
|
||||
field := $1
|
||||
str := $3
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1:len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?"){
|
||||
q = NewWildcardQuery(str)
|
||||
} else {
|
||||
q = NewMatchQuery(str)
|
||||
}
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tNUMBER {
|
||||
field := $1
|
||||
str := $3
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tPHRASE {
|
||||
field := $1
|
||||
phrase := $3
|
||||
logDebugGrammar("FIELD - %s PHRASE - %s", field, phrase)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tGREATER tNUMBER {
|
||||
field := $1
|
||||
min, _ := strconv.ParseFloat($4, 64)
|
||||
minInclusive := false
|
||||
logDebugGrammar("FIELD - GREATER THAN %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tGREATER tEQUAL tNUMBER {
|
||||
field := $1
|
||||
min, _ := strconv.ParseFloat($5, 64)
|
||||
minInclusive := true
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tLESS tNUMBER {
|
||||
field := $1
|
||||
max, _ := strconv.ParseFloat($4, 64)
|
||||
maxInclusive := false
|
||||
logDebugGrammar("FIELD - LESS THAN %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tLESS tEQUAL tNUMBER {
|
||||
field := $1
|
||||
max, _ := strconv.ParseFloat($5, 64)
|
||||
maxInclusive := true
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tGREATER tPHRASE {
|
||||
field := $1
|
||||
minInclusive := false
|
||||
phrase := $4
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN DATE %s", phrase)
|
||||
minTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tGREATER tEQUAL tPHRASE {
|
||||
field := $1
|
||||
minInclusive := true
|
||||
phrase := $5
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL DATE %s", phrase)
|
||||
minTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tLESS tPHRASE {
|
||||
field := $1
|
||||
maxInclusive := false
|
||||
phrase := $4
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN DATE %s", phrase)
|
||||
maxTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
tSTRING tCOLON tLESS tEQUAL tPHRASE {
|
||||
field := $1
|
||||
maxInclusive := true
|
||||
phrase := $5
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL DATE %s", phrase)
|
||||
maxTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
};
|
||||
|
||||
searchSuffix:
|
||||
/* empty */ {
|
||||
$$ = nil
|
||||
}
|
||||
|
|
||||
tBOOST {
|
||||
$$ = nil
|
||||
boost, err := strconv.ParseFloat($1, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid boost value: %v", err))
|
||||
} else {
|
||||
$$ = &boost
|
||||
}
|
||||
logDebugGrammar("BOOST %f", boost)
|
||||
};
|
773
vendor/github.com/blevesearch/bleve/search/query/query_string.y.go
generated
vendored
Normal file
773
vendor/github.com/blevesearch/bleve/search/query/query_string.y.go
generated
vendored
Normal file
|
@ -0,0 +1,773 @@
|
|||
package query
|
||||
|
||||
import __yyfmt__ "fmt"
|
||||
|
||||
//line query_string.y:2
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func logDebugGrammar(format string, v ...interface{}) {
|
||||
if debugParser {
|
||||
logger.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
//line query_string.y:17
|
||||
type yySymType struct {
|
||||
yys int
|
||||
s string
|
||||
n int
|
||||
f float64
|
||||
q Query
|
||||
pf *float64
|
||||
}
|
||||
|
||||
const tSTRING = 57346
|
||||
const tPHRASE = 57347
|
||||
const tPLUS = 57348
|
||||
const tMINUS = 57349
|
||||
const tCOLON = 57350
|
||||
const tBOOST = 57351
|
||||
const tNUMBER = 57352
|
||||
const tGREATER = 57353
|
||||
const tLESS = 57354
|
||||
const tEQUAL = 57355
|
||||
const tTILDE = 57356
|
||||
|
||||
var yyToknames = [...]string{
|
||||
"$end",
|
||||
"error",
|
||||
"$unk",
|
||||
"tSTRING",
|
||||
"tPHRASE",
|
||||
"tPLUS",
|
||||
"tMINUS",
|
||||
"tCOLON",
|
||||
"tBOOST",
|
||||
"tNUMBER",
|
||||
"tGREATER",
|
||||
"tLESS",
|
||||
"tEQUAL",
|
||||
"tTILDE",
|
||||
}
|
||||
var yyStatenames = [...]string{}
|
||||
|
||||
const yyEofCode = 1
|
||||
const yyErrCode = 2
|
||||
const yyInitialStackSize = 16
|
||||
|
||||
//line yacctab:1
|
||||
var yyExca = [...]int{
|
||||
-1, 1,
|
||||
1, -1,
|
||||
-2, 0,
|
||||
-1, 3,
|
||||
1, 3,
|
||||
-2, 5,
|
||||
}
|
||||
|
||||
const yyNprod = 26
|
||||
const yyPrivate = 57344
|
||||
|
||||
var yyTokenNames []string
|
||||
var yyStates []string
|
||||
|
||||
const yyLast = 31
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
16, 18, 21, 13, 27, 24, 17, 19, 20, 25,
|
||||
22, 15, 26, 23, 9, 11, 31, 14, 29, 3,
|
||||
10, 30, 2, 28, 5, 6, 7, 1, 4, 12,
|
||||
8,
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
18, -1000, -1000, 18, 10, -1000, -1000, -1000, -6, 3,
|
||||
-1000, -1000, -1000, -1000, -1000, -4, -12, -1000, -1000, 0,
|
||||
-1, -1000, -1000, 13, -1000, -1000, 11, -1000, -1000, -1000,
|
||||
-1000, -1000,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 30, 29, 28, 27, 22, 19,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 4, 5, 5, 6, 3, 3, 3, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 2, 2,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 2, 1, 3, 0, 1, 1, 1, 2,
|
||||
4, 1, 1, 3, 3, 3, 4, 5, 4, 5,
|
||||
4, 5, 4, 5, 0, 1,
|
||||
}
|
||||
var yyChk = [...]int{
|
||||
|
||||
-1000, -4, -5, -6, -3, 6, 7, -5, -1, 4,
|
||||
10, 5, -2, 9, 14, 8, 4, 10, 5, 11,
|
||||
12, 14, 10, 13, 5, 10, 13, 5, 10, 5,
|
||||
10, 5,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
5, -2, 1, -2, 0, 6, 7, 2, 24, 8,
|
||||
11, 12, 4, 25, 9, 0, 13, 14, 15, 0,
|
||||
0, 10, 16, 0, 20, 18, 0, 22, 17, 21,
|
||||
19, 23,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
1,
|
||||
}
|
||||
var yyTok2 = [...]int{
|
||||
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
12, 13, 14,
|
||||
}
|
||||
var yyTok3 = [...]int{
|
||||
0,
|
||||
}
|
||||
|
||||
var yyErrorMessages = [...]struct {
|
||||
state int
|
||||
token int
|
||||
msg string
|
||||
}{}
|
||||
|
||||
//line yaccpar:1
|
||||
|
||||
/* parser for yacc output */
|
||||
|
||||
var (
|
||||
yyDebug = 0
|
||||
yyErrorVerbose = false
|
||||
)
|
||||
|
||||
type yyLexer interface {
|
||||
Lex(lval *yySymType) int
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
type yyParser interface {
|
||||
Parse(yyLexer) int
|
||||
Lookahead() int
|
||||
}
|
||||
|
||||
type yyParserImpl struct {
|
||||
lval yySymType
|
||||
stack [yyInitialStackSize]yySymType
|
||||
char int
|
||||
}
|
||||
|
||||
func (p *yyParserImpl) Lookahead() int {
|
||||
return p.char
|
||||
}
|
||||
|
||||
func yyNewParser() yyParser {
|
||||
return &yyParserImpl{}
|
||||
}
|
||||
|
||||
const yyFlag = -1000
|
||||
|
||||
func yyTokname(c int) string {
|
||||
if c >= 1 && c-1 < len(yyToknames) {
|
||||
if yyToknames[c-1] != "" {
|
||||
return yyToknames[c-1]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("tok-%v", c)
|
||||
}
|
||||
|
||||
func yyStatname(s int) string {
|
||||
if s >= 0 && s < len(yyStatenames) {
|
||||
if yyStatenames[s] != "" {
|
||||
return yyStatenames[s]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("state-%v", s)
|
||||
}
|
||||
|
||||
func yyErrorMessage(state, lookAhead int) string {
|
||||
const TOKSTART = 4
|
||||
|
||||
if !yyErrorVerbose {
|
||||
return "syntax error"
|
||||
}
|
||||
|
||||
for _, e := range yyErrorMessages {
|
||||
if e.state == state && e.token == lookAhead {
|
||||
return "syntax error: " + e.msg
|
||||
}
|
||||
}
|
||||
|
||||
res := "syntax error: unexpected " + yyTokname(lookAhead)
|
||||
|
||||
// To match Bison, suggest at most four expected tokens.
|
||||
expected := make([]int, 0, 4)
|
||||
|
||||
// Look for shiftable tokens.
|
||||
base := yyPact[state]
|
||||
for tok := TOKSTART; tok-1 < len(yyToknames); tok++ {
|
||||
if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok {
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
}
|
||||
|
||||
if yyDef[state] == -2 {
|
||||
i := 0
|
||||
for yyExca[i] != -1 || yyExca[i+1] != state {
|
||||
i += 2
|
||||
}
|
||||
|
||||
// Look for tokens that we accept or reduce.
|
||||
for i += 2; yyExca[i] >= 0; i += 2 {
|
||||
tok := yyExca[i]
|
||||
if tok < TOKSTART || yyExca[i+1] == 0 {
|
||||
continue
|
||||
}
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
|
||||
// If the default action is to accept or reduce, give up.
|
||||
if yyExca[i+1] != 0 {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
for i, tok := range expected {
|
||||
if i == 0 {
|
||||
res += ", expecting "
|
||||
} else {
|
||||
res += " or "
|
||||
}
|
||||
res += yyTokname(tok)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func yylex1(lex yyLexer, lval *yySymType) (char, token int) {
|
||||
token = 0
|
||||
char = lex.Lex(lval)
|
||||
if char <= 0 {
|
||||
token = yyTok1[0]
|
||||
goto out
|
||||
}
|
||||
if char < len(yyTok1) {
|
||||
token = yyTok1[char]
|
||||
goto out
|
||||
}
|
||||
if char >= yyPrivate {
|
||||
if char < yyPrivate+len(yyTok2) {
|
||||
token = yyTok2[char-yyPrivate]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(yyTok3); i += 2 {
|
||||
token = yyTok3[i+0]
|
||||
if token == char {
|
||||
token = yyTok3[i+1]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if token == 0 {
|
||||
token = yyTok2[1] /* unknown char */
|
||||
}
|
||||
if yyDebug >= 3 {
|
||||
__yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char))
|
||||
}
|
||||
return char, token
|
||||
}
|
||||
|
||||
func yyParse(yylex yyLexer) int {
|
||||
return yyNewParser().Parse(yylex)
|
||||
}
|
||||
|
||||
func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
|
||||
var yyn int
|
||||
var yyVAL yySymType
|
||||
var yyDollar []yySymType
|
||||
_ = yyDollar // silence set and not used
|
||||
yyS := yyrcvr.stack[:]
|
||||
|
||||
Nerrs := 0 /* number of errors */
|
||||
Errflag := 0 /* error recovery flag */
|
||||
yystate := 0
|
||||
yyrcvr.char = -1
|
||||
yytoken := -1 // yyrcvr.char translated into internal numbering
|
||||
defer func() {
|
||||
// Make sure we report no lookahead when not parsing.
|
||||
yystate = -1
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
}()
|
||||
yyp := -1
|
||||
goto yystack
|
||||
|
||||
ret0:
|
||||
return 0
|
||||
|
||||
ret1:
|
||||
return 1
|
||||
|
||||
yystack:
|
||||
/* put a state and value onto the stack */
|
||||
if yyDebug >= 4 {
|
||||
__yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate))
|
||||
}
|
||||
|
||||
yyp++
|
||||
if yyp >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyS[yyp] = yyVAL
|
||||
yyS[yyp].yys = yystate
|
||||
|
||||
yynewstate:
|
||||
yyn = yyPact[yystate]
|
||||
if yyn <= yyFlag {
|
||||
goto yydefault /* simple state */
|
||||
}
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
yyn += yytoken
|
||||
if yyn < 0 || yyn >= yyLast {
|
||||
goto yydefault
|
||||
}
|
||||
yyn = yyAct[yyn]
|
||||
if yyChk[yyn] == yytoken { /* valid shift */
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
yyVAL = yyrcvr.lval
|
||||
yystate = yyn
|
||||
if Errflag > 0 {
|
||||
Errflag--
|
||||
}
|
||||
goto yystack
|
||||
}
|
||||
|
||||
yydefault:
|
||||
/* default state action */
|
||||
yyn = yyDef[yystate]
|
||||
if yyn == -2 {
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
|
||||
/* look through exception table */
|
||||
xi := 0
|
||||
for {
|
||||
if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
|
||||
break
|
||||
}
|
||||
xi += 2
|
||||
}
|
||||
for xi += 2; ; xi += 2 {
|
||||
yyn = yyExca[xi+0]
|
||||
if yyn < 0 || yyn == yytoken {
|
||||
break
|
||||
}
|
||||
}
|
||||
yyn = yyExca[xi+1]
|
||||
if yyn < 0 {
|
||||
goto ret0
|
||||
}
|
||||
}
|
||||
if yyn == 0 {
|
||||
/* error ... attempt to resume parsing */
|
||||
switch Errflag {
|
||||
case 0: /* brand new error */
|
||||
yylex.Error(yyErrorMessage(yystate, yytoken))
|
||||
Nerrs++
|
||||
if yyDebug >= 1 {
|
||||
__yyfmt__.Printf("%s", yyStatname(yystate))
|
||||
__yyfmt__.Printf(" saw %s\n", yyTokname(yytoken))
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 1, 2: /* incompletely recovered error ... try again */
|
||||
Errflag = 3
|
||||
|
||||
/* find a state where "error" is a legal shift action */
|
||||
for yyp >= 0 {
|
||||
yyn = yyPact[yyS[yyp].yys] + yyErrCode
|
||||
if yyn >= 0 && yyn < yyLast {
|
||||
yystate = yyAct[yyn] /* simulate a shift of "error" */
|
||||
if yyChk[yystate] == yyErrCode {
|
||||
goto yystack
|
||||
}
|
||||
}
|
||||
|
||||
/* the current p has no shift on "error", pop stack */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
|
||||
}
|
||||
yyp--
|
||||
}
|
||||
/* there is no state on the stack with an error shift ... abort */
|
||||
goto ret1
|
||||
|
||||
case 3: /* no shift yet; clobber input char */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken))
|
||||
}
|
||||
if yytoken == yyEofCode {
|
||||
goto ret1
|
||||
}
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
goto yynewstate /* try again in the same state */
|
||||
}
|
||||
}
|
||||
|
||||
/* reduction by production yyn */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
|
||||
}
|
||||
|
||||
yynt := yyn
|
||||
yypt := yyp
|
||||
_ = yypt // guard against "declared and not used"
|
||||
|
||||
yyp -= yyR2[yyn]
|
||||
// yyp is now the index of $0. Perform the default action. Iff the
|
||||
// reduced production is ε, $1 is possibly out of range.
|
||||
if yyp+1 >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyVAL = yyS[yyp+1]
|
||||
|
||||
/* consult goto table to find next state */
|
||||
yyn = yyR1[yyn]
|
||||
yyg := yyPgo[yyn]
|
||||
yyj := yyg + yyS[yyp].yys + 1
|
||||
|
||||
if yyj >= yyLast {
|
||||
yystate = yyAct[yyg]
|
||||
} else {
|
||||
yystate = yyAct[yyj]
|
||||
if yyChk[yystate] != -yyn {
|
||||
yystate = yyAct[yyg]
|
||||
}
|
||||
}
|
||||
// dummy call; replaced with literal code
|
||||
switch yynt {
|
||||
|
||||
case 1:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:39
|
||||
{
|
||||
logDebugGrammar("INPUT")
|
||||
}
|
||||
case 2:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line query_string.y:44
|
||||
{
|
||||
logDebugGrammar("SEARCH PARTS")
|
||||
}
|
||||
case 3:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:48
|
||||
{
|
||||
logDebugGrammar("SEARCH PART")
|
||||
}
|
||||
case 4:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:53
|
||||
{
|
||||
query := yyDollar[2].q
|
||||
if yyDollar[3].pf != nil {
|
||||
if query, ok := query.(BoostableQuery); ok {
|
||||
query.SetBoost(*yyDollar[3].pf)
|
||||
}
|
||||
}
|
||||
switch yyDollar[1].n {
|
||||
case queryShould:
|
||||
yylex.(*lexerWrapper).query.AddShould(query)
|
||||
case queryMust:
|
||||
yylex.(*lexerWrapper).query.AddMust(query)
|
||||
case queryMustNot:
|
||||
yylex.(*lexerWrapper).query.AddMustNot(query)
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line query_string.y:72
|
||||
{
|
||||
yyVAL.n = queryShould
|
||||
}
|
||||
case 6:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:76
|
||||
{
|
||||
logDebugGrammar("PLUS")
|
||||
yyVAL.n = queryMust
|
||||
}
|
||||
case 7:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:81
|
||||
{
|
||||
logDebugGrammar("MINUS")
|
||||
yyVAL.n = queryMustNot
|
||||
}
|
||||
case 8:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:87
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1 : len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?") {
|
||||
q = NewWildcardQuery(str)
|
||||
} else {
|
||||
q = NewMatchQuery(str)
|
||||
}
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 9:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line query_string.y:101
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
fuzziness, err := strconv.ParseFloat(yyDollar[2].s, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid fuzziness value: %v", err))
|
||||
}
|
||||
logDebugGrammar("FUZZY STRING - %s %f", str, fuzziness)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetFuzziness(int(fuzziness))
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 10:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:113
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
fuzziness, err := strconv.ParseFloat(yyDollar[4].s, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid fuzziness value: %v", err))
|
||||
}
|
||||
logDebugGrammar("FIELD - %s FUZZY STRING - %s %f", field, str, fuzziness)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetFuzziness(int(fuzziness))
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 11:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:127
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
q := NewMatchQuery(str)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 12:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:134
|
||||
{
|
||||
phrase := yyDollar[1].s
|
||||
logDebugGrammar("PHRASE - %s", phrase)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 13:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:141
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1 : len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?") {
|
||||
q = NewWildcardQuery(str)
|
||||
} else {
|
||||
q = NewMatchQuery(str)
|
||||
}
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 14:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:157
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 15:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:166
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
phrase := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s PHRASE - %s", field, phrase)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 16:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:175
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
min, _ := strconv.ParseFloat(yyDollar[4].s, 64)
|
||||
minInclusive := false
|
||||
logDebugGrammar("FIELD - GREATER THAN %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 17:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:185
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
min, _ := strconv.ParseFloat(yyDollar[5].s, 64)
|
||||
minInclusive := true
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 18:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:195
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
max, _ := strconv.ParseFloat(yyDollar[4].s, 64)
|
||||
maxInclusive := false
|
||||
logDebugGrammar("FIELD - LESS THAN %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 19:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:205
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
max, _ := strconv.ParseFloat(yyDollar[5].s, 64)
|
||||
maxInclusive := true
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 20:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:215
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
minInclusive := false
|
||||
phrase := yyDollar[4].s
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN DATE %s", phrase)
|
||||
minTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 21:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:230
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
minInclusive := true
|
||||
phrase := yyDollar[5].s
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL DATE %s", phrase)
|
||||
minTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 22:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:245
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
maxInclusive := false
|
||||
phrase := yyDollar[4].s
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN DATE %s", phrase)
|
||||
maxTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 23:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:260
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
maxInclusive := true
|
||||
phrase := yyDollar[5].s
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL DATE %s", phrase)
|
||||
maxTime, err := queryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 24:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line query_string.y:276
|
||||
{
|
||||
yyVAL.pf = nil
|
||||
}
|
||||
case 25:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:280
|
||||
{
|
||||
yyVAL.pf = nil
|
||||
boost, err := strconv.ParseFloat(yyDollar[1].s, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid boost value: %v", err))
|
||||
} else {
|
||||
yyVAL.pf = &boost
|
||||
}
|
||||
logDebugGrammar("BOOST %f", boost)
|
||||
}
|
||||
}
|
||||
goto yystack /* stack new state and value */
|
||||
}
|
322
vendor/github.com/blevesearch/bleve/search/query/query_string_lex.go
generated
vendored
Normal file
322
vendor/github.com/blevesearch/bleve/search/query/query_string_lex.go
generated
vendored
Normal file
|
@ -0,0 +1,322 @@
|
|||
// Copyright (c) 2016 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 query
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const reservedChars = "+-=&|><!(){}[]^\"~*?:\\/ "
|
||||
|
||||
func unescape(escaped string) string {
|
||||
// see if this character can be escaped
|
||||
if strings.ContainsAny(escaped, reservedChars) {
|
||||
return escaped
|
||||
}
|
||||
// otherwise return it with the \ intact
|
||||
return "\\" + escaped
|
||||
}
|
||||
|
||||
type queryStringLex struct {
|
||||
in *bufio.Reader
|
||||
buf string
|
||||
currState lexState
|
||||
currConsumed bool
|
||||
inEscape bool
|
||||
nextToken *yySymType
|
||||
nextTokenType int
|
||||
seenDot bool
|
||||
nextRune rune
|
||||
nextRuneSize int
|
||||
atEOF bool
|
||||
}
|
||||
|
||||
func (l *queryStringLex) reset() {
|
||||
l.buf = ""
|
||||
l.inEscape = false
|
||||
l.seenDot = false
|
||||
}
|
||||
|
||||
func (l *queryStringLex) Error(msg string) {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
func (l *queryStringLex) Lex(lval *yySymType) int {
|
||||
var err error
|
||||
|
||||
for l.nextToken == nil {
|
||||
if l.currConsumed {
|
||||
l.nextRune, l.nextRuneSize, err = l.in.ReadRune()
|
||||
if err != nil && err == io.EOF {
|
||||
l.nextRune = 0
|
||||
l.atEOF = true
|
||||
} else if err != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
l.currState, l.currConsumed = l.currState(l, l.nextRune, l.atEOF)
|
||||
if l.currState == nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
*lval = *l.nextToken
|
||||
rv := l.nextTokenType
|
||||
l.nextToken = nil
|
||||
l.nextTokenType = 0
|
||||
return rv
|
||||
}
|
||||
|
||||
func newQueryStringLex(in io.Reader) *queryStringLex {
|
||||
return &queryStringLex{
|
||||
in: bufio.NewReader(in),
|
||||
currState: startState,
|
||||
currConsumed: true,
|
||||
}
|
||||
}
|
||||
|
||||
type lexState func(l *queryStringLex, next rune, eof bool) (lexState, bool)
|
||||
|
||||
func startState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
if eof {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// handle inside escape case up front
|
||||
if l.inEscape {
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
return inStrState, true
|
||||
}
|
||||
|
||||
switch next {
|
||||
case '"':
|
||||
return inPhraseState, true
|
||||
case '+', '-', ':', '>', '<', '=':
|
||||
l.buf += string(next)
|
||||
return singleCharOpState, true
|
||||
case '^':
|
||||
return inBoostState, true
|
||||
case '~':
|
||||
return inTildeState, true
|
||||
}
|
||||
|
||||
switch {
|
||||
case !l.inEscape && next == '\\':
|
||||
l.inEscape = true
|
||||
return startState, true
|
||||
case unicode.IsDigit(next):
|
||||
l.buf += string(next)
|
||||
return inNumOrStrState, true
|
||||
case !unicode.IsSpace(next):
|
||||
l.buf += string(next)
|
||||
return inStrState, true
|
||||
}
|
||||
|
||||
// doesn't look like anything, just eat it and stay here
|
||||
l.reset()
|
||||
return startState, true
|
||||
}
|
||||
|
||||
func inPhraseState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
// unterminated phrase eats the phrase
|
||||
if eof {
|
||||
l.Error("unterminated quote")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// only a non-escaped " ends the phrase
|
||||
if !l.inEscape && next == '"' {
|
||||
// end phrase
|
||||
l.nextTokenType = tPHRASE
|
||||
l.nextToken = &yySymType{
|
||||
s: l.buf,
|
||||
}
|
||||
logDebugTokens("PHRASE - '%s'", l.nextToken.s)
|
||||
l.reset()
|
||||
return startState, true
|
||||
} else if !l.inEscape && next == '\\' {
|
||||
l.inEscape = true
|
||||
} else if l.inEscape {
|
||||
// if in escape, end it
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
} else {
|
||||
l.buf += string(next)
|
||||
}
|
||||
|
||||
return inPhraseState, true
|
||||
}
|
||||
|
||||
func singleCharOpState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
l.nextToken = &yySymType{}
|
||||
|
||||
switch l.buf {
|
||||
case "+":
|
||||
l.nextTokenType = tPLUS
|
||||
logDebugTokens("PLUS")
|
||||
case "-":
|
||||
l.nextTokenType = tMINUS
|
||||
logDebugTokens("MINUS")
|
||||
case ":":
|
||||
l.nextTokenType = tCOLON
|
||||
logDebugTokens("COLON")
|
||||
case ">":
|
||||
l.nextTokenType = tGREATER
|
||||
logDebugTokens("GREATER")
|
||||
case "<":
|
||||
l.nextTokenType = tLESS
|
||||
logDebugTokens("LESS")
|
||||
case "=":
|
||||
l.nextTokenType = tEQUAL
|
||||
logDebugTokens("EQUAL")
|
||||
}
|
||||
|
||||
l.reset()
|
||||
return startState, false
|
||||
}
|
||||
|
||||
func inBoostState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
|
||||
// only a non-escaped space ends the boost (or eof)
|
||||
if eof || (!l.inEscape && next == ' ') {
|
||||
// end boost
|
||||
l.nextTokenType = tBOOST
|
||||
if l.buf == "" {
|
||||
l.buf = "1"
|
||||
}
|
||||
l.nextToken = &yySymType{
|
||||
s: l.buf,
|
||||
}
|
||||
logDebugTokens("BOOST - '%s'", l.nextToken.s)
|
||||
l.reset()
|
||||
return startState, true
|
||||
} else if !l.inEscape && next == '\\' {
|
||||
l.inEscape = true
|
||||
} else if l.inEscape {
|
||||
// if in escape, end it
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
} else {
|
||||
l.buf += string(next)
|
||||
}
|
||||
|
||||
return inBoostState, true
|
||||
}
|
||||
|
||||
func inTildeState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
|
||||
// only a non-escaped space ends the tilde (or eof)
|
||||
if eof || (!l.inEscape && next == ' ') {
|
||||
// end tilde
|
||||
l.nextTokenType = tTILDE
|
||||
if l.buf == "" {
|
||||
l.buf = "1"
|
||||
}
|
||||
l.nextToken = &yySymType{
|
||||
s: l.buf,
|
||||
}
|
||||
logDebugTokens("TILDE - '%s'", l.nextToken.s)
|
||||
l.reset()
|
||||
return startState, true
|
||||
} else if !l.inEscape && next == '\\' {
|
||||
l.inEscape = true
|
||||
} else if l.inEscape {
|
||||
// if in escape, end it
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
} else {
|
||||
l.buf += string(next)
|
||||
}
|
||||
|
||||
return inTildeState, true
|
||||
}
|
||||
|
||||
func inNumOrStrState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
// only a non-escaped space ends the tilde (or eof)
|
||||
if eof || (!l.inEscape && next == ' ') {
|
||||
// end number
|
||||
l.nextTokenType = tNUMBER
|
||||
l.nextToken = &yySymType{
|
||||
s: l.buf,
|
||||
}
|
||||
logDebugTokens("NUMBER - '%s'", l.nextToken.s)
|
||||
l.reset()
|
||||
return startState, true
|
||||
} else if !l.inEscape && next == '\\' {
|
||||
l.inEscape = true
|
||||
return inNumOrStrState, true
|
||||
} else if l.inEscape {
|
||||
// if in escape, end it
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
// go directly to string, no successfully or unsuccessfully
|
||||
// escaped string results in a valid number
|
||||
return inStrState, true
|
||||
}
|
||||
|
||||
// see where to go
|
||||
if !l.seenDot && next == '.' {
|
||||
// stay in this state
|
||||
l.buf += string(next)
|
||||
return inNumOrStrState, true
|
||||
} else if unicode.IsDigit(next) {
|
||||
l.buf += string(next)
|
||||
return inNumOrStrState, true
|
||||
}
|
||||
|
||||
// doesn't look like an number, transition
|
||||
l.buf += string(next)
|
||||
return inStrState, true
|
||||
}
|
||||
|
||||
func inStrState(l *queryStringLex, next rune, eof bool) (lexState, bool) {
|
||||
// end on non-escped space, colon, tilde, boost (or eof)
|
||||
if eof || (!l.inEscape && (next == ' ' || next == ':' || next == '^' || next == '~')) {
|
||||
// end string
|
||||
l.nextTokenType = tSTRING
|
||||
l.nextToken = &yySymType{
|
||||
s: l.buf,
|
||||
}
|
||||
logDebugTokens("STRING - '%s'", l.nextToken.s)
|
||||
l.reset()
|
||||
|
||||
consumed := true
|
||||
if !eof && (next == ':' || next == '^' || next == '~') {
|
||||
consumed = false
|
||||
}
|
||||
|
||||
return startState, consumed
|
||||
} else if !l.inEscape && next == '\\' {
|
||||
l.inEscape = true
|
||||
} else if l.inEscape {
|
||||
// if in escape, end it
|
||||
l.inEscape = false
|
||||
l.buf += unescape(string(next))
|
||||
} else {
|
||||
l.buf += string(next)
|
||||
}
|
||||
|
||||
return inStrState, true
|
||||
}
|
||||
|
||||
func logDebugTokens(format string, v ...interface{}) {
|
||||
if debugLexer {
|
||||
logger.Printf(format, v...)
|
||||
}
|
||||
}
|
79
vendor/github.com/blevesearch/bleve/search/query/query_string_parser.go
generated
vendored
Normal file
79
vendor/github.com/blevesearch/bleve/search/query/query_string_parser.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
// 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.
|
||||
|
||||
//go:generate go tool yacc -o query_string.y.go query_string.y
|
||||
//go:generate sed -i.tmp -e 1d query_string.y.go
|
||||
//go:generate rm query_string.y.go.tmp
|
||||
|
||||
// note: OSX sed and gnu sed handle the -i (in-place) option differently.
|
||||
// using -i.tmp works on both, at the expense of having to remove
|
||||
// the unsightly .tmp files
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var debugParser bool
|
||||
var debugLexer bool
|
||||
|
||||
func parseQuerySyntax(query string) (rq Query, err error) {
|
||||
lex := newLexerWrapper(newQueryStringLex(strings.NewReader(query)))
|
||||
doParse(lex)
|
||||
|
||||
if len(lex.errs) > 0 {
|
||||
return nil, fmt.Errorf(strings.Join(lex.errs, "\n"))
|
||||
}
|
||||
return lex.query, nil
|
||||
}
|
||||
|
||||
func doParse(lex *lexerWrapper) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
lex.errs = append(lex.errs, fmt.Sprintf("parse error: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
yyParse(lex)
|
||||
}
|
||||
|
||||
const (
|
||||
queryShould = iota
|
||||
queryMust
|
||||
queryMustNot
|
||||
)
|
||||
|
||||
type lexerWrapper struct {
|
||||
lex yyLexer
|
||||
errs []string
|
||||
query *BooleanQuery
|
||||
}
|
||||
|
||||
func newLexerWrapper(lex yyLexer) *lexerWrapper {
|
||||
return &lexerWrapper{
|
||||
lex: lex,
|
||||
query: NewBooleanQuery(nil, nil, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexerWrapper) Lex(lval *yySymType) int {
|
||||
return l.lex.Lex(lval)
|
||||
}
|
||||
|
||||
func (l *lexerWrapper) Error(s string) {
|
||||
l.errs = append(l.errs, s)
|
||||
}
|
94
vendor/github.com/blevesearch/bleve/search/query/regexp.go
generated
vendored
Normal file
94
vendor/github.com/blevesearch/bleve/search/query/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type RegexpQuery struct {
|
||||
Regexp string `json:"regexp"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
compiled *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewRegexpQuery creates a new Query which finds
|
||||
// documents containing terms that match the
|
||||
// specified regular expression.
|
||||
func NewRegexpQuery(regexp string) *RegexpQuery {
|
||||
return &RegexpQuery{
|
||||
Regexp: regexp,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
err := q.compile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return searcher.NewRegexpSearcher(i, q.compiled, field, q.BoostVal.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) Validate() error {
|
||||
return q.compile()
|
||||
}
|
||||
|
||||
func (q *RegexpQuery) compile() error {
|
||||
if q.compiled == nil {
|
||||
// require that pattern be anchored to start and end of term
|
||||
actualRegexp := q.Regexp
|
||||
if !strings.HasPrefix(actualRegexp, "^") {
|
||||
actualRegexp = "^" + actualRegexp
|
||||
}
|
||||
if !strings.HasSuffix(actualRegexp, "$") {
|
||||
actualRegexp = actualRegexp + "$"
|
||||
}
|
||||
var err error
|
||||
q.compiled, err = regexp.Compile(actualRegexp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
61
vendor/github.com/blevesearch/bleve/search/query/term.go
generated
vendored
Normal file
61
vendor/github.com/blevesearch/bleve/search/query/term.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type TermQuery struct {
|
||||
Term string `json:"term"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewTermQuery creates a new Query for finding an
|
||||
// exact term match in the index.
|
||||
func NewTermQuery(term string) *TermQuery {
|
||||
return &TermQuery{
|
||||
Term: term,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *TermQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *TermQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *TermQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *TermQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *TermQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searcher.NewTermSearcher(i, q.Term, field, q.BoostVal.Value(), explain)
|
||||
}
|
106
vendor/github.com/blevesearch/bleve/search/query/wildcard.go
generated
vendored
Normal file
106
vendor/github.com/blevesearch/bleve/search/query/wildcard.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
// 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 query
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
var wildcardRegexpReplacer = strings.NewReplacer(
|
||||
// characters in the wildcard that must
|
||||
// be escaped in the regexp
|
||||
"+", `\+`,
|
||||
"(", `\(`,
|
||||
")", `\)`,
|
||||
"^", `\^`,
|
||||
"$", `\$`,
|
||||
".", `\.`,
|
||||
"{", `\{`,
|
||||
"}", `\}`,
|
||||
"[", `\[`,
|
||||
"]", `\]`,
|
||||
`|`, `\|`,
|
||||
`\`, `\\`,
|
||||
// wildcard characters
|
||||
"*", ".*",
|
||||
"?", ".")
|
||||
|
||||
type WildcardQuery struct {
|
||||
Wildcard string `json:"wildcard"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
compiled *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewWildcardQuery creates a new Query which finds
|
||||
// documents containing terms that match the
|
||||
// specified wildcard. In the wildcard pattern '*'
|
||||
// will match any sequence of 0 or more characters,
|
||||
// and '?' will match any single character.
|
||||
func NewWildcardQuery(wildcard string) *WildcardQuery {
|
||||
return &WildcardQuery{
|
||||
Wildcard: wildcard,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) Boost() float64{
|
||||
return q.BoostVal.Value()
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) SetField(f string) {
|
||||
q.FieldVal = f
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) Field() string{
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
if q.compiled == nil {
|
||||
var err error
|
||||
q.compiled, err = q.convertToRegexp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return searcher.NewRegexpSearcher(i, q.compiled, field, q.BoostVal.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) Validate() error {
|
||||
var err error
|
||||
q.compiled, err = q.convertToRegexp()
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *WildcardQuery) convertToRegexp() (*regexp.Regexp, error) {
|
||||
regexpString := "^" + wildcardRegexpReplacer.Replace(q.Wildcard) + "$"
|
||||
return regexp.Compile(regexpString)
|
||||
}
|
65
vendor/github.com/blevesearch/bleve/search/scorer/scorer_conjunction.go
generated
vendored
Normal file
65
vendor/github.com/blevesearch/bleve/search/scorer/scorer_conjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 scorer
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type ConjunctionQueryScorer struct {
|
||||
explain bool
|
||||
}
|
||||
|
||||
func NewConjunctionQueryScorer(explain bool) *ConjunctionQueryScorer {
|
||||
return &ConjunctionQueryScorer{
|
||||
explain: explain,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConjunctionQueryScorer) Score(ctx *search.SearchContext, constituents []*search.DocumentMatch) *search.DocumentMatch {
|
||||
var sum float64
|
||||
var childrenExplanations []*search.Explanation
|
||||
if s.explain {
|
||||
childrenExplanations = make([]*search.Explanation, len(constituents))
|
||||
}
|
||||
|
||||
locations := []search.FieldTermLocationMap{}
|
||||
for i, docMatch := range constituents {
|
||||
sum += docMatch.Score
|
||||
if s.explain {
|
||||
childrenExplanations[i] = docMatch.Expl
|
||||
}
|
||||
if docMatch.Locations != nil {
|
||||
locations = append(locations, docMatch.Locations)
|
||||
}
|
||||
}
|
||||
newScore := sum
|
||||
var newExpl *search.Explanation
|
||||
if s.explain {
|
||||
newExpl = &search.Explanation{Value: sum, Message: "sum of:", Children: childrenExplanations}
|
||||
}
|
||||
|
||||
// reuse constituents[0] as the return value
|
||||
rv := constituents[0]
|
||||
rv.Score = newScore
|
||||
rv.Expl = newExpl
|
||||
if len(locations) == 1 {
|
||||
rv.Locations = locations[0]
|
||||
} else if len(locations) > 1 {
|
||||
rv.Locations = search.MergeLocations(locations)
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
108
vendor/github.com/blevesearch/bleve/search/scorer/scorer_constant.go
generated
vendored
Normal file
108
vendor/github.com/blevesearch/bleve/search/scorer/scorer_constant.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// 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 scorer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type ConstantScorer struct {
|
||||
constant float64
|
||||
boost float64
|
||||
explain bool
|
||||
queryNorm float64
|
||||
queryWeight float64
|
||||
queryWeightExplanation *search.Explanation
|
||||
}
|
||||
|
||||
func NewConstantScorer(constant float64, boost float64, explain bool) *ConstantScorer {
|
||||
rv := ConstantScorer{
|
||||
explain: explain,
|
||||
queryWeight: 1.0,
|
||||
constant: constant,
|
||||
boost: boost,
|
||||
}
|
||||
|
||||
return &rv
|
||||
}
|
||||
|
||||
func (s *ConstantScorer) Weight() float64 {
|
||||
sum := s.boost
|
||||
return sum * sum
|
||||
}
|
||||
|
||||
func (s *ConstantScorer) SetQueryNorm(qnorm float64) {
|
||||
s.queryNorm = qnorm
|
||||
|
||||
// update the query weight
|
||||
s.queryWeight = s.boost * s.queryNorm
|
||||
|
||||
if s.explain {
|
||||
childrenExplanations := make([]*search.Explanation, 2)
|
||||
childrenExplanations[0] = &search.Explanation{
|
||||
Value: s.boost,
|
||||
Message: "boost",
|
||||
}
|
||||
childrenExplanations[1] = &search.Explanation{
|
||||
Value: s.queryNorm,
|
||||
Message: "queryNorm",
|
||||
}
|
||||
s.queryWeightExplanation = &search.Explanation{
|
||||
Value: s.queryWeight,
|
||||
Message: fmt.Sprintf("ConstantScore()^%f, product of:", s.boost),
|
||||
Children: childrenExplanations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConstantScorer) Score(ctx *search.SearchContext, id index.IndexInternalID) *search.DocumentMatch {
|
||||
var scoreExplanation *search.Explanation
|
||||
|
||||
score := s.constant
|
||||
|
||||
if s.explain {
|
||||
scoreExplanation = &search.Explanation{
|
||||
Value: score,
|
||||
Message: fmt.Sprintf("ConstantScore()"),
|
||||
}
|
||||
}
|
||||
|
||||
// if the query weight isn't 1, multiply
|
||||
if s.queryWeight != 1.0 {
|
||||
score = score * s.queryWeight
|
||||
if s.explain {
|
||||
childExplanations := make([]*search.Explanation, 2)
|
||||
childExplanations[0] = s.queryWeightExplanation
|
||||
childExplanations[1] = scoreExplanation
|
||||
scoreExplanation = &search.Explanation{
|
||||
Value: score,
|
||||
Message: fmt.Sprintf("weight(^%f), product of:", s.boost),
|
||||
Children: childExplanations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rv := ctx.DocumentMatchPool.Get()
|
||||
rv.IndexInternalID = id
|
||||
rv.Score = score
|
||||
if s.explain {
|
||||
rv.Expl = scoreExplanation
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
77
vendor/github.com/blevesearch/bleve/search/scorer/scorer_disjunction.go
generated
vendored
Normal file
77
vendor/github.com/blevesearch/bleve/search/scorer/scorer_disjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
// 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 scorer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type DisjunctionQueryScorer struct {
|
||||
explain bool
|
||||
}
|
||||
|
||||
func NewDisjunctionQueryScorer(explain bool) *DisjunctionQueryScorer {
|
||||
return &DisjunctionQueryScorer{
|
||||
explain: explain,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DisjunctionQueryScorer) Score(ctx *search.SearchContext, constituents []*search.DocumentMatch, countMatch, countTotal int) *search.DocumentMatch {
|
||||
var sum float64
|
||||
var childrenExplanations []*search.Explanation
|
||||
if s.explain {
|
||||
childrenExplanations = make([]*search.Explanation, len(constituents))
|
||||
}
|
||||
|
||||
var locations []search.FieldTermLocationMap
|
||||
for i, docMatch := range constituents {
|
||||
sum += docMatch.Score
|
||||
if s.explain {
|
||||
childrenExplanations[i] = docMatch.Expl
|
||||
}
|
||||
if docMatch.Locations != nil {
|
||||
locations = append(locations, docMatch.Locations)
|
||||
}
|
||||
}
|
||||
|
||||
var rawExpl *search.Explanation
|
||||
if s.explain {
|
||||
rawExpl = &search.Explanation{Value: sum, Message: "sum of:", Children: childrenExplanations}
|
||||
}
|
||||
|
||||
coord := float64(countMatch) / float64(countTotal)
|
||||
newScore := sum * coord
|
||||
var newExpl *search.Explanation
|
||||
if s.explain {
|
||||
ce := make([]*search.Explanation, 2)
|
||||
ce[0] = rawExpl
|
||||
ce[1] = &search.Explanation{Value: coord, Message: fmt.Sprintf("coord(%d/%d)", countMatch, countTotal)}
|
||||
newExpl = &search.Explanation{Value: newScore, Message: "product of:", Children: ce}
|
||||
}
|
||||
|
||||
// reuse constituents[0] as the return value
|
||||
rv := constituents[0]
|
||||
rv.Score = newScore
|
||||
rv.Expl = newExpl
|
||||
if len(locations) == 1 {
|
||||
rv.Locations = locations[0]
|
||||
} else if len(locations) > 1 {
|
||||
rv.Locations = search.MergeLocations(locations)
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
180
vendor/github.com/blevesearch/bleve/search/scorer/scorer_term.go
generated
vendored
Normal file
180
vendor/github.com/blevesearch/bleve/search/scorer/scorer_term.go
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
// 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 scorer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type TermQueryScorer struct {
|
||||
queryTerm string
|
||||
queryField string
|
||||
queryBoost float64
|
||||
docTerm uint64
|
||||
docTotal uint64
|
||||
idf float64
|
||||
explain bool
|
||||
idfExplanation *search.Explanation
|
||||
queryNorm float64
|
||||
queryWeight float64
|
||||
queryWeightExplanation *search.Explanation
|
||||
}
|
||||
|
||||
func NewTermQueryScorer(queryTerm string, queryField string, queryBoost float64, docTotal, docTerm uint64, explain bool) *TermQueryScorer {
|
||||
rv := TermQueryScorer{
|
||||
queryTerm: queryTerm,
|
||||
queryField: queryField,
|
||||
queryBoost: queryBoost,
|
||||
docTerm: docTerm,
|
||||
docTotal: docTotal,
|
||||
idf: 1.0 + math.Log(float64(docTotal)/float64(docTerm+1.0)),
|
||||
explain: explain,
|
||||
queryWeight: 1.0,
|
||||
}
|
||||
|
||||
if explain {
|
||||
rv.idfExplanation = &search.Explanation{
|
||||
Value: rv.idf,
|
||||
Message: fmt.Sprintf("idf(docFreq=%d, maxDocs=%d)", docTerm, docTotal),
|
||||
}
|
||||
}
|
||||
|
||||
return &rv
|
||||
}
|
||||
|
||||
func (s *TermQueryScorer) Weight() float64 {
|
||||
sum := s.queryBoost * s.idf
|
||||
return sum * sum
|
||||
}
|
||||
|
||||
func (s *TermQueryScorer) SetQueryNorm(qnorm float64) {
|
||||
s.queryNorm = qnorm
|
||||
|
||||
// update the query weight
|
||||
s.queryWeight = s.queryBoost * s.idf * s.queryNorm
|
||||
|
||||
if s.explain {
|
||||
childrenExplanations := make([]*search.Explanation, 3)
|
||||
childrenExplanations[0] = &search.Explanation{
|
||||
Value: s.queryBoost,
|
||||
Message: "boost",
|
||||
}
|
||||
childrenExplanations[1] = s.idfExplanation
|
||||
childrenExplanations[2] = &search.Explanation{
|
||||
Value: s.queryNorm,
|
||||
Message: "queryNorm",
|
||||
}
|
||||
s.queryWeightExplanation = &search.Explanation{
|
||||
Value: s.queryWeight,
|
||||
Message: fmt.Sprintf("queryWeight(%s:%s^%f), product of:", s.queryField, string(s.queryTerm), s.queryBoost),
|
||||
Children: childrenExplanations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TermQueryScorer) Score(ctx *search.SearchContext, termMatch *index.TermFieldDoc) *search.DocumentMatch {
|
||||
var scoreExplanation *search.Explanation
|
||||
|
||||
// need to compute score
|
||||
var tf float64
|
||||
if termMatch.Freq < MaxSqrtCache {
|
||||
tf = SqrtCache[int(termMatch.Freq)]
|
||||
} else {
|
||||
tf = math.Sqrt(float64(termMatch.Freq))
|
||||
}
|
||||
score := tf * termMatch.Norm * s.idf
|
||||
|
||||
if s.explain {
|
||||
childrenExplanations := make([]*search.Explanation, 3)
|
||||
childrenExplanations[0] = &search.Explanation{
|
||||
Value: tf,
|
||||
Message: fmt.Sprintf("tf(termFreq(%s:%s)=%d", s.queryField, string(s.queryTerm), termMatch.Freq),
|
||||
}
|
||||
childrenExplanations[1] = &search.Explanation{
|
||||
Value: termMatch.Norm,
|
||||
Message: fmt.Sprintf("fieldNorm(field=%s, doc=%s)", s.queryField, termMatch.ID),
|
||||
}
|
||||
childrenExplanations[2] = s.idfExplanation
|
||||
scoreExplanation = &search.Explanation{
|
||||
Value: score,
|
||||
Message: fmt.Sprintf("fieldWeight(%s:%s in %s), product of:", s.queryField, string(s.queryTerm), termMatch.ID),
|
||||
Children: childrenExplanations,
|
||||
}
|
||||
}
|
||||
|
||||
// if the query weight isn't 1, multiply
|
||||
if s.queryWeight != 1.0 {
|
||||
score = score * s.queryWeight
|
||||
if s.explain {
|
||||
childExplanations := make([]*search.Explanation, 2)
|
||||
childExplanations[0] = s.queryWeightExplanation
|
||||
childExplanations[1] = scoreExplanation
|
||||
scoreExplanation = &search.Explanation{
|
||||
Value: score,
|
||||
Message: fmt.Sprintf("weight(%s:%s^%f in %s), product of:", s.queryField, string(s.queryTerm), s.queryBoost, termMatch.ID),
|
||||
Children: childExplanations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rv := ctx.DocumentMatchPool.Get()
|
||||
rv.IndexInternalID = append(rv.IndexInternalID, termMatch.ID...)
|
||||
rv.Score = score
|
||||
if s.explain {
|
||||
rv.Expl = scoreExplanation
|
||||
}
|
||||
|
||||
if termMatch.Vectors != nil && len(termMatch.Vectors) > 0 {
|
||||
|
||||
rv.Locations = make(search.FieldTermLocationMap)
|
||||
for _, v := range termMatch.Vectors {
|
||||
tlm := rv.Locations[v.Field]
|
||||
if tlm == nil {
|
||||
tlm = make(search.TermLocationMap)
|
||||
}
|
||||
|
||||
loc := search.Location{
|
||||
Pos: float64(v.Pos),
|
||||
Start: float64(v.Start),
|
||||
End: float64(v.End),
|
||||
}
|
||||
|
||||
if len(v.ArrayPositions) > 0 {
|
||||
loc.ArrayPositions = make([]float64, len(v.ArrayPositions))
|
||||
for i, ap := range v.ArrayPositions {
|
||||
loc.ArrayPositions[i] = float64(ap)
|
||||
}
|
||||
}
|
||||
|
||||
locations := tlm[s.queryTerm]
|
||||
if locations == nil {
|
||||
locations = make(search.Locations, 1)
|
||||
locations[0] = &loc
|
||||
} else {
|
||||
locations = append(locations, &loc)
|
||||
}
|
||||
tlm[s.queryTerm] = locations
|
||||
|
||||
rv.Locations[v.Field] = tlm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
30
vendor/github.com/blevesearch/bleve/search/scorer/sqrt_cache.go
generated
vendored
Normal file
30
vendor/github.com/blevesearch/bleve/search/scorer/sqrt_cache.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// 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 scorer
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
var SqrtCache []float64
|
||||
|
||||
const MaxSqrtCache = 64
|
||||
|
||||
func init() {
|
||||
SqrtCache = make([]float64, MaxSqrtCache)
|
||||
for i := 0; i < MaxSqrtCache; i++ {
|
||||
SqrtCache[i] = math.Sqrt(float64(i))
|
||||
}
|
||||
}
|
144
vendor/github.com/blevesearch/bleve/search/search.go
generated
vendored
Normal file
144
vendor/github.com/blevesearch/bleve/search/search.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Pos float64 `json:"pos"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
ArrayPositions []float64 `json:"array_positions"`
|
||||
}
|
||||
|
||||
// SameArrayElement returns true if two locations are point to
|
||||
// the same array element
|
||||
func (l *Location) SameArrayElement(other *Location) bool {
|
||||
if len(l.ArrayPositions) != len(other.ArrayPositions) {
|
||||
return false
|
||||
}
|
||||
for i, elem := range l.ArrayPositions {
|
||||
if other.ArrayPositions[i] != elem {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type Locations []*Location
|
||||
|
||||
type TermLocationMap map[string]Locations
|
||||
|
||||
func (t TermLocationMap) AddLocation(term string, location *Location) {
|
||||
t[term] = append(t[term], location)
|
||||
}
|
||||
|
||||
type FieldTermLocationMap map[string]TermLocationMap
|
||||
|
||||
type FieldFragmentMap map[string][]string
|
||||
|
||||
type DocumentMatch struct {
|
||||
Index string `json:"index,omitempty"`
|
||||
ID string `json:"id"`
|
||||
IndexInternalID index.IndexInternalID `json:"-"`
|
||||
Score float64 `json:"score"`
|
||||
Expl *Explanation `json:"explanation,omitempty"`
|
||||
Locations FieldTermLocationMap `json:"locations,omitempty"`
|
||||
Fragments FieldFragmentMap `json:"fragments,omitempty"`
|
||||
Sort []string `json:"sort,omitempty"`
|
||||
|
||||
// Fields contains the values for document fields listed in
|
||||
// SearchRequest.Fields. Text fields are returned as strings, numeric
|
||||
// fields as float64s and date fields as time.RFC3339 formatted strings.
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
|
||||
// as we learn field terms, we can cache important ones for later use
|
||||
// for example, sorting and building facets need these values
|
||||
CachedFieldTerms index.FieldTerms `json:"-"`
|
||||
|
||||
// if we load the document for this hit, remember it so we dont load again
|
||||
Document *document.Document `json:"-"`
|
||||
|
||||
// used to maintain natural index order
|
||||
HitNumber uint64 `json:"-"`
|
||||
}
|
||||
|
||||
func (dm *DocumentMatch) AddFieldValue(name string, value interface{}) {
|
||||
if dm.Fields == nil {
|
||||
dm.Fields = make(map[string]interface{})
|
||||
}
|
||||
existingVal, ok := dm.Fields[name]
|
||||
if !ok {
|
||||
dm.Fields[name] = value
|
||||
return
|
||||
}
|
||||
|
||||
valSlice, ok := existingVal.([]interface{})
|
||||
if ok {
|
||||
// already a slice, append to it
|
||||
valSlice = append(valSlice, value)
|
||||
} else {
|
||||
// create a slice
|
||||
valSlice = []interface{}{existingVal, value}
|
||||
}
|
||||
dm.Fields[name] = valSlice
|
||||
}
|
||||
|
||||
// Reset allows an already allocated DocumentMatch to be reused
|
||||
func (dm *DocumentMatch) Reset() *DocumentMatch {
|
||||
// remember the []byte used for the IndexInternalID
|
||||
indexInternalID := dm.IndexInternalID
|
||||
// remember the []interface{} used for sort
|
||||
sort := dm.Sort
|
||||
// idiom to copy over from empty DocumentMatch (0 allocations)
|
||||
*dm = DocumentMatch{}
|
||||
// reuse the []byte already allocated (and reset len to 0)
|
||||
dm.IndexInternalID = indexInternalID[:0]
|
||||
// reuse the []interface{} already allocated (and reset len to 0)
|
||||
dm.Sort = sort[:0]
|
||||
return dm
|
||||
}
|
||||
|
||||
func (dm *DocumentMatch) String() string {
|
||||
return fmt.Sprintf("[%s-%f]", string(dm.IndexInternalID), dm.Score)
|
||||
}
|
||||
|
||||
type DocumentMatchCollection []*DocumentMatch
|
||||
|
||||
func (c DocumentMatchCollection) Len() int { return len(c) }
|
||||
func (c DocumentMatchCollection) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c DocumentMatchCollection) Less(i, j int) bool { return c[i].Score > c[j].Score }
|
||||
|
||||
type Searcher interface {
|
||||
Next(ctx *SearchContext) (*DocumentMatch, error)
|
||||
Advance(ctx *SearchContext, ID index.IndexInternalID) (*DocumentMatch, error)
|
||||
Close() error
|
||||
Weight() float64
|
||||
SetQueryNorm(float64)
|
||||
Count() uint64
|
||||
Min() int
|
||||
|
||||
DocumentMatchPoolSize() int
|
||||
}
|
||||
|
||||
// SearchContext represents the context around a single search
|
||||
type SearchContext struct {
|
||||
DocumentMatchPool *DocumentMatchPool
|
||||
}
|
35
vendor/github.com/blevesearch/bleve/search/searcher/ordered_searchers_list.go
generated
vendored
Normal file
35
vendor/github.com/blevesearch/bleve/search/searcher/ordered_searchers_list.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type OrderedSearcherList []search.Searcher
|
||||
|
||||
// sort.Interface
|
||||
|
||||
func (otrl OrderedSearcherList) Len() int {
|
||||
return len(otrl)
|
||||
}
|
||||
|
||||
func (otrl OrderedSearcherList) Less(i, j int) bool {
|
||||
return otrl[i].Count() < otrl[j].Count()
|
||||
}
|
||||
|
||||
func (otrl OrderedSearcherList) Swap(i, j int) {
|
||||
otrl[i], otrl[j] = otrl[j], otrl[i]
|
||||
}
|
391
vendor/github.com/blevesearch/bleve/search/searcher/search_boolean.go
generated
vendored
Normal file
391
vendor/github.com/blevesearch/bleve/search/searcher/search_boolean.go
generated
vendored
Normal file
|
@ -0,0 +1,391 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
type BooleanSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
mustSearcher search.Searcher
|
||||
shouldSearcher search.Searcher
|
||||
mustNotSearcher search.Searcher
|
||||
queryNorm float64
|
||||
currMust *search.DocumentMatch
|
||||
currShould *search.DocumentMatch
|
||||
currMustNot *search.DocumentMatch
|
||||
currentID index.IndexInternalID
|
||||
min uint64
|
||||
scorer *scorer.ConjunctionQueryScorer
|
||||
matches []*search.DocumentMatch
|
||||
initialized bool
|
||||
}
|
||||
|
||||
func NewBooleanSearcher(indexReader index.IndexReader, mustSearcher search.Searcher, shouldSearcher search.Searcher, mustNotSearcher search.Searcher, explain bool) (*BooleanSearcher, error) {
|
||||
// build our searcher
|
||||
rv := BooleanSearcher{
|
||||
indexReader: indexReader,
|
||||
mustSearcher: mustSearcher,
|
||||
shouldSearcher: shouldSearcher,
|
||||
mustNotSearcher: mustNotSearcher,
|
||||
scorer: scorer.NewConjunctionQueryScorer(explain),
|
||||
matches: make([]*search.DocumentMatch, 2),
|
||||
}
|
||||
rv.computeQueryNorm()
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) computeQueryNorm() {
|
||||
// first calculate sum of squared weights
|
||||
sumOfSquaredWeights := 0.0
|
||||
if s.mustSearcher != nil {
|
||||
sumOfSquaredWeights += s.mustSearcher.Weight()
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
sumOfSquaredWeights += s.shouldSearcher.Weight()
|
||||
}
|
||||
|
||||
// now compute query norm from this
|
||||
s.queryNorm = 1.0 / math.Sqrt(sumOfSquaredWeights)
|
||||
// finally tell all the downstream searchers the norm
|
||||
if s.mustSearcher != nil {
|
||||
s.mustSearcher.SetQueryNorm(s.queryNorm)
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
s.shouldSearcher.SetQueryNorm(s.queryNorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) initSearchers(ctx *search.SearchContext) error {
|
||||
var err error
|
||||
// get all searchers pointing at their first match
|
||||
if s.mustSearcher != nil {
|
||||
if s.currMust != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currMust)
|
||||
}
|
||||
s.currMust, err = s.mustSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.shouldSearcher != nil {
|
||||
if s.currShould != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currShould)
|
||||
}
|
||||
s.currShould, err = s.shouldSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.mustNotSearcher != nil {
|
||||
if s.currMustNot != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currMustNot)
|
||||
}
|
||||
s.currMustNot, err = s.mustNotSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.mustSearcher != nil && s.currMust != nil {
|
||||
s.currentID = s.currMust.IndexInternalID
|
||||
} else if s.mustSearcher == nil && s.currShould != nil {
|
||||
s.currentID = s.currShould.IndexInternalID
|
||||
} else {
|
||||
s.currentID = nil
|
||||
}
|
||||
|
||||
s.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) advanceNextMust(ctx *search.SearchContext, skipReturn *search.DocumentMatch) error {
|
||||
var err error
|
||||
|
||||
if s.mustSearcher != nil {
|
||||
if s.currMust != skipReturn {
|
||||
ctx.DocumentMatchPool.Put(s.currMust)
|
||||
}
|
||||
s.currMust, err = s.mustSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if s.currShould != skipReturn {
|
||||
ctx.DocumentMatchPool.Put(s.currShould)
|
||||
}
|
||||
s.currShould, err = s.shouldSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.mustSearcher != nil && s.currMust != nil {
|
||||
s.currentID = s.currMust.IndexInternalID
|
||||
} else if s.mustSearcher == nil && s.currShould != nil {
|
||||
s.currentID = s.currShould.IndexInternalID
|
||||
} else {
|
||||
s.currentID = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Weight() float64 {
|
||||
var rv float64
|
||||
if s.mustSearcher != nil {
|
||||
rv += s.mustSearcher.Weight()
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
rv += s.shouldSearcher.Weight()
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) SetQueryNorm(qnorm float64) {
|
||||
if s.mustSearcher != nil {
|
||||
s.mustSearcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
s.shouldSearcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
var rv *search.DocumentMatch
|
||||
|
||||
for s.currentID != nil {
|
||||
if s.currMustNot != nil {
|
||||
cmp := s.currMustNot.IndexInternalID.Compare(s.currentID)
|
||||
if cmp < 0 {
|
||||
ctx.DocumentMatchPool.Put(s.currMustNot)
|
||||
// advance must not searcher to our candidate entry
|
||||
s.currMustNot, err = s.mustNotSearcher.Advance(ctx, s.currentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.currMustNot != nil && s.currMustNot.IndexInternalID.Equals(s.currentID) {
|
||||
// the candidate is excluded
|
||||
err = s.advanceNextMust(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if cmp == 0 {
|
||||
// the candidate is excluded
|
||||
err = s.advanceNextMust(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
shouldCmpOrNil := 1 // NOTE: shouldCmp will also be 1 when currShould == nil.
|
||||
if s.currShould != nil {
|
||||
shouldCmpOrNil = s.currShould.IndexInternalID.Compare(s.currentID)
|
||||
}
|
||||
|
||||
if shouldCmpOrNil < 0 {
|
||||
ctx.DocumentMatchPool.Put(s.currShould)
|
||||
// advance should searcher to our candidate entry
|
||||
s.currShould, err = s.shouldSearcher.Advance(ctx, s.currentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.currShould != nil && s.currShould.IndexInternalID.Equals(s.currentID) {
|
||||
// score bonus matches should
|
||||
var cons []*search.DocumentMatch
|
||||
if s.currMust != nil {
|
||||
cons = s.matches
|
||||
cons[0] = s.currMust
|
||||
cons[1] = s.currShould
|
||||
} else {
|
||||
cons = s.matches[0:1]
|
||||
cons[0] = s.currShould
|
||||
}
|
||||
rv = s.scorer.Score(ctx, cons)
|
||||
err = s.advanceNextMust(ctx, rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
} else if s.shouldSearcher.Min() == 0 {
|
||||
// match is OK anyway
|
||||
cons := s.matches[0:1]
|
||||
cons[0] = s.currMust
|
||||
rv = s.scorer.Score(ctx, cons)
|
||||
err = s.advanceNextMust(ctx, rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if shouldCmpOrNil == 0 {
|
||||
// score bonus matches should
|
||||
var cons []*search.DocumentMatch
|
||||
if s.currMust != nil {
|
||||
cons = s.matches
|
||||
cons[0] = s.currMust
|
||||
cons[1] = s.currShould
|
||||
} else {
|
||||
cons = s.matches[0:1]
|
||||
cons[0] = s.currShould
|
||||
}
|
||||
rv = s.scorer.Score(ctx, cons)
|
||||
err = s.advanceNextMust(ctx, rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
} else if s.shouldSearcher == nil || s.shouldSearcher.Min() == 0 {
|
||||
// match is OK anyway
|
||||
cons := s.matches[0:1]
|
||||
cons[0] = s.currMust
|
||||
rv = s.scorer.Score(ctx, cons)
|
||||
err = s.advanceNextMust(ctx, rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
err = s.advanceNextMust(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if s.mustSearcher != nil {
|
||||
if s.currMust != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currMust)
|
||||
}
|
||||
s.currMust, err = s.mustSearcher.Advance(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
if s.currShould != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currShould)
|
||||
}
|
||||
s.currShould, err = s.shouldSearcher.Advance(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if s.mustNotSearcher != nil {
|
||||
if s.currMustNot != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currMustNot)
|
||||
}
|
||||
s.currMustNot, err = s.mustNotSearcher.Advance(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if s.mustSearcher != nil && s.currMust != nil {
|
||||
s.currentID = s.currMust.IndexInternalID
|
||||
} else if s.mustSearcher == nil && s.currShould != nil {
|
||||
s.currentID = s.currShould.IndexInternalID
|
||||
} else {
|
||||
s.currentID = nil
|
||||
}
|
||||
|
||||
return s.Next(ctx)
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Count() uint64 {
|
||||
|
||||
// for now return a worst case
|
||||
var sum uint64
|
||||
if s.mustSearcher != nil {
|
||||
sum += s.mustSearcher.Count()
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
sum += s.shouldSearcher.Count()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Close() error {
|
||||
var err0, err1, err2 error
|
||||
if s.mustSearcher != nil {
|
||||
err0 = s.mustSearcher.Close()
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
err1 = s.shouldSearcher.Close()
|
||||
}
|
||||
if s.mustNotSearcher != nil {
|
||||
err2 = s.mustNotSearcher.Close()
|
||||
}
|
||||
if err0 != nil {
|
||||
return err0
|
||||
}
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *BooleanSearcher) DocumentMatchPoolSize() int {
|
||||
rv := 3
|
||||
if s.mustSearcher != nil {
|
||||
rv += s.mustSearcher.DocumentMatchPoolSize()
|
||||
}
|
||||
if s.shouldSearcher != nil {
|
||||
rv += s.shouldSearcher.DocumentMatchPoolSize()
|
||||
}
|
||||
if s.mustNotSearcher != nil {
|
||||
rv += s.mustNotSearcher.DocumentMatchPoolSize()
|
||||
}
|
||||
return rv
|
||||
}
|
232
vendor/github.com/blevesearch/bleve/search/searcher/search_conjunction.go
generated
vendored
Normal file
232
vendor/github.com/blevesearch/bleve/search/searcher/search_conjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,232 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
type ConjunctionSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
searchers OrderedSearcherList
|
||||
queryNorm float64
|
||||
currs []*search.DocumentMatch
|
||||
maxIDIdx int
|
||||
scorer *scorer.ConjunctionQueryScorer
|
||||
initialized bool
|
||||
explain bool
|
||||
}
|
||||
|
||||
func NewConjunctionSearcher(indexReader index.IndexReader, qsearchers []search.Searcher, explain bool) (*ConjunctionSearcher, error) {
|
||||
// build the downstream searchers
|
||||
searchers := make(OrderedSearcherList, len(qsearchers))
|
||||
for i, searcher := range qsearchers {
|
||||
searchers[i] = searcher
|
||||
}
|
||||
// sort the searchers
|
||||
sort.Sort(searchers)
|
||||
// build our searcher
|
||||
rv := ConjunctionSearcher{
|
||||
indexReader: indexReader,
|
||||
explain: explain,
|
||||
searchers: searchers,
|
||||
currs: make([]*search.DocumentMatch, len(searchers)),
|
||||
scorer: scorer.NewConjunctionQueryScorer(explain),
|
||||
}
|
||||
rv.computeQueryNorm()
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) computeQueryNorm() {
|
||||
// first calculate sum of squared weights
|
||||
sumOfSquaredWeights := 0.0
|
||||
for _, termSearcher := range s.searchers {
|
||||
sumOfSquaredWeights += termSearcher.Weight()
|
||||
}
|
||||
// now compute query norm from this
|
||||
s.queryNorm = 1.0 / math.Sqrt(sumOfSquaredWeights)
|
||||
// finally tell all the downstream searchers the norm
|
||||
for _, termSearcher := range s.searchers {
|
||||
termSearcher.SetQueryNorm(s.queryNorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) initSearchers(ctx *search.SearchContext) error {
|
||||
var err error
|
||||
// get all searchers pointing at their first match
|
||||
for i, termSearcher := range s.searchers {
|
||||
if s.currs[i] != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = termSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Weight() float64 {
|
||||
var rv float64
|
||||
for _, searcher := range s.searchers {
|
||||
rv += searcher.Weight()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) SetQueryNorm(qnorm float64) {
|
||||
for _, searcher := range s.searchers {
|
||||
searcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var rv *search.DocumentMatch
|
||||
var err error
|
||||
OUTER:
|
||||
for s.currs[s.maxIDIdx] != nil {
|
||||
maxID := s.currs[s.maxIDIdx].IndexInternalID
|
||||
|
||||
i := 0
|
||||
for i < len(s.currs) {
|
||||
if s.currs[i] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if i == s.maxIDIdx {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
cmp := maxID.Compare(s.currs[i].IndexInternalID)
|
||||
if cmp == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if cmp < 0 {
|
||||
// maxID < currs[i], so we found a new maxIDIdx
|
||||
s.maxIDIdx = i
|
||||
|
||||
// advance the positions where [0 <= x < i], since we
|
||||
// know they were equal to the former max entry
|
||||
maxID = s.currs[s.maxIDIdx].IndexInternalID
|
||||
for x := 0; x < i; x++ {
|
||||
err = s.advanceChild(ctx, x, maxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// maxID > currs[i], so need to advance searchers[i]
|
||||
err = s.advanceChild(ctx, i, maxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// don't bump i, so that we'll examine the just-advanced
|
||||
// currs[i] again
|
||||
}
|
||||
|
||||
// if we get here, a doc matched all readers, so score and add it
|
||||
rv = s.scorer.Score(ctx, s.currs)
|
||||
|
||||
// we know all the searchers are pointing at the same thing
|
||||
// so they all need to be bumped
|
||||
for i, termSearcher := range s.searchers {
|
||||
if s.currs[i] != rv {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = termSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// don't continue now, wait for the next call to Next()
|
||||
break
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i := range s.searchers {
|
||||
err := s.advanceChild(ctx, i, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.Next(ctx)
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) advanceChild(ctx *search.SearchContext, i int, ID index.IndexInternalID) (err error) {
|
||||
if s.currs[i] != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = s.searchers[i].Advance(ctx, ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Count() uint64 {
|
||||
// for now return a worst case
|
||||
var sum uint64
|
||||
for _, searcher := range s.searchers {
|
||||
sum += searcher.Count()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Close() (rv error) {
|
||||
for _, searcher := range s.searchers {
|
||||
err := searcher.Close()
|
||||
if err != nil && rv == nil {
|
||||
rv = err
|
||||
}
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *ConjunctionSearcher) DocumentMatchPoolSize() int {
|
||||
rv := len(s.currs)
|
||||
for _, s := range s.searchers {
|
||||
rv += s.DocumentMatchPoolSize()
|
||||
}
|
||||
return rv
|
||||
}
|
258
vendor/github.com/blevesearch/bleve/search/searcher/search_disjunction.go
generated
vendored
Normal file
258
vendor/github.com/blevesearch/bleve/search/searcher/search_disjunction.go
generated
vendored
Normal file
|
@ -0,0 +1,258 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
// DisjunctionMaxClauseCount is a compile time setting that applications can
|
||||
// adjust to non-zero value to cause the DisjunctionSearcher to return an
|
||||
// error instead of exeucting searches when the size exceeds this value.
|
||||
var DisjunctionMaxClauseCount = 0
|
||||
|
||||
type DisjunctionSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
searchers OrderedSearcherList
|
||||
numSearchers int
|
||||
queryNorm float64
|
||||
currs []*search.DocumentMatch
|
||||
scorer *scorer.DisjunctionQueryScorer
|
||||
min int
|
||||
matching []*search.DocumentMatch
|
||||
matchingIdxs []int
|
||||
initialized bool
|
||||
}
|
||||
|
||||
func tooManyClauses(count int) bool {
|
||||
if DisjunctionMaxClauseCount != 0 && count > DisjunctionMaxClauseCount {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func tooManyClausesErr() error {
|
||||
return fmt.Errorf("TooManyClauses[maxClauseCount is set to %d]", DisjunctionMaxClauseCount)
|
||||
}
|
||||
|
||||
func NewDisjunctionSearcher(indexReader index.IndexReader, qsearchers []search.Searcher, min float64, explain bool) (*DisjunctionSearcher, error) {
|
||||
if tooManyClauses(len(qsearchers)) {
|
||||
return nil, tooManyClausesErr()
|
||||
}
|
||||
// build the downstream searchers
|
||||
searchers := make(OrderedSearcherList, len(qsearchers))
|
||||
for i, searcher := range qsearchers {
|
||||
searchers[i] = searcher
|
||||
}
|
||||
// sort the searchers
|
||||
sort.Sort(sort.Reverse(searchers))
|
||||
// build our searcher
|
||||
rv := DisjunctionSearcher{
|
||||
indexReader: indexReader,
|
||||
searchers: searchers,
|
||||
numSearchers: len(searchers),
|
||||
currs: make([]*search.DocumentMatch, len(searchers)),
|
||||
scorer: scorer.NewDisjunctionQueryScorer(explain),
|
||||
min: int(min),
|
||||
matching: make([]*search.DocumentMatch, len(searchers)),
|
||||
matchingIdxs: make([]int, len(searchers)),
|
||||
}
|
||||
rv.computeQueryNorm()
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) computeQueryNorm() {
|
||||
// first calculate sum of squared weights
|
||||
sumOfSquaredWeights := 0.0
|
||||
for _, termSearcher := range s.searchers {
|
||||
sumOfSquaredWeights += termSearcher.Weight()
|
||||
}
|
||||
// now compute query norm from this
|
||||
s.queryNorm = 1.0 / math.Sqrt(sumOfSquaredWeights)
|
||||
// finally tell all the downstream searchers the norm
|
||||
for _, termSearcher := range s.searchers {
|
||||
termSearcher.SetQueryNorm(s.queryNorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) initSearchers(ctx *search.SearchContext) error {
|
||||
var err error
|
||||
// get all searchers pointing at their first match
|
||||
for i, termSearcher := range s.searchers {
|
||||
if s.currs[i] != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = termSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.updateMatches()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) updateMatches() error {
|
||||
matching := s.matching[:0]
|
||||
matchingIdxs := s.matchingIdxs[:0]
|
||||
|
||||
for i := 0; i < len(s.currs); i++ {
|
||||
curr := s.currs[i]
|
||||
if curr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(matching) > 0 {
|
||||
cmp := curr.IndexInternalID.Compare(matching[0].IndexInternalID)
|
||||
if cmp > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if cmp < 0 {
|
||||
matching = matching[:0]
|
||||
matchingIdxs = matchingIdxs[:0]
|
||||
}
|
||||
}
|
||||
|
||||
matching = append(matching, curr)
|
||||
matchingIdxs = append(matchingIdxs, i)
|
||||
}
|
||||
|
||||
s.matching = matching
|
||||
s.matchingIdxs = matchingIdxs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Weight() float64 {
|
||||
var rv float64
|
||||
for _, searcher := range s.searchers {
|
||||
rv += searcher.Weight()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) SetQueryNorm(qnorm float64) {
|
||||
for _, searcher := range s.searchers {
|
||||
searcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
var rv *search.DocumentMatch
|
||||
|
||||
found := false
|
||||
for !found && len(s.matching) > 0 {
|
||||
if len(s.matching) >= s.min {
|
||||
found = true
|
||||
// score this match
|
||||
rv = s.scorer.Score(ctx, s.matching, len(s.matching), s.numSearchers)
|
||||
}
|
||||
|
||||
// invoke next on all the matching searchers
|
||||
for _, i := range s.matchingIdxs {
|
||||
searcher := s.searchers[i]
|
||||
if s.currs[i] != rv {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = searcher.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.updateMatches()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// get all searchers pointing at their first match
|
||||
var err error
|
||||
for i, termSearcher := range s.searchers {
|
||||
if s.currs[i] != nil {
|
||||
ctx.DocumentMatchPool.Put(s.currs[i])
|
||||
}
|
||||
s.currs[i], err = termSearcher.Advance(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.updateMatches()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Next(ctx)
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Count() uint64 {
|
||||
// for now return a worst case
|
||||
var sum uint64
|
||||
for _, searcher := range s.searchers {
|
||||
sum += searcher.Count()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Close() (rv error) {
|
||||
for _, searcher := range s.searchers {
|
||||
err := searcher.Close()
|
||||
if err != nil && rv == nil {
|
||||
rv = err
|
||||
}
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) Min() int {
|
||||
return s.min
|
||||
}
|
||||
|
||||
func (s *DisjunctionSearcher) DocumentMatchPoolSize() int {
|
||||
rv := len(s.currs)
|
||||
for _, s := range s.searchers {
|
||||
rv += s.DocumentMatchPoolSize()
|
||||
}
|
||||
return rv
|
||||
}
|
93
vendor/github.com/blevesearch/bleve/search/searcher/search_docid.go
generated
vendored
Normal file
93
vendor/github.com/blevesearch/bleve/search/searcher/search_docid.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) 2015 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
// DocIDSearcher returns documents matching a predefined set of identifiers.
|
||||
type DocIDSearcher struct {
|
||||
reader index.DocIDReader
|
||||
scorer *scorer.ConstantScorer
|
||||
count int
|
||||
}
|
||||
|
||||
func NewDocIDSearcher(indexReader index.IndexReader, ids []string, boost float64,
|
||||
explain bool) (searcher *DocIDSearcher, err error) {
|
||||
|
||||
reader, err := indexReader.DocIDReaderOnly(ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scorer := scorer.NewConstantScorer(1.0, boost, explain)
|
||||
return &DocIDSearcher{
|
||||
scorer: scorer,
|
||||
reader: reader,
|
||||
count: len(ids),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Count() uint64 {
|
||||
return uint64(s.count)
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Weight() float64 {
|
||||
return s.scorer.Weight()
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.scorer.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
docidMatch, err := s.reader.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if docidMatch == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
docMatch := s.scorer.Score(ctx, docidMatch)
|
||||
return docMatch, nil
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
docidMatch, err := s.reader.Advance(ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if docidMatch == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
docMatch := s.scorer.Score(ctx, docidMatch)
|
||||
return docMatch, nil
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Close() error {
|
||||
return s.reader.Close()
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *DocIDSearcher) DocumentMatchPoolSize() int {
|
||||
return 1
|
||||
}
|
143
vendor/github.com/blevesearch/bleve/search/searcher/search_fuzzy.go
generated
vendored
Normal file
143
vendor/github.com/blevesearch/bleve/search/searcher/search_fuzzy.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type FuzzySearcher struct {
|
||||
indexReader index.IndexReader
|
||||
term string
|
||||
prefix int
|
||||
fuzziness int
|
||||
field string
|
||||
explain bool
|
||||
searcher *DisjunctionSearcher
|
||||
}
|
||||
|
||||
func NewFuzzySearcher(indexReader index.IndexReader, term string, prefix, fuzziness int, field string, boost float64, explain bool) (*FuzzySearcher, error) {
|
||||
// Note: we don't byte slice the term for a prefix because of runes.
|
||||
prefixTerm := ""
|
||||
for i, r := range term {
|
||||
if i < prefix {
|
||||
prefixTerm += string(r)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
candidateTerms, err := findFuzzyCandidateTerms(indexReader, term, fuzziness, field, prefixTerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// enumerate all the terms in the range
|
||||
qsearchers := make([]search.Searcher, 0, len(candidateTerms))
|
||||
qsearchersClose := func() {
|
||||
for _, searcher := range qsearchers {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
for _, cterm := range candidateTerms {
|
||||
qsearcher, err := NewTermSearcher(indexReader, cterm, field, boost, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
qsearchers = append(qsearchers, qsearcher)
|
||||
}
|
||||
|
||||
// build disjunction searcher of these ranges
|
||||
searcher, err := NewDisjunctionSearcher(indexReader, qsearchers, 0, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FuzzySearcher{
|
||||
indexReader: indexReader,
|
||||
term: term,
|
||||
prefix: prefix,
|
||||
fuzziness: fuzziness,
|
||||
field: field,
|
||||
explain: explain,
|
||||
searcher: searcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func findFuzzyCandidateTerms(indexReader index.IndexReader, term string, fuzziness int, field, prefixTerm string) (rv []string, err error) {
|
||||
rv = make([]string, 0)
|
||||
var fieldDict index.FieldDict
|
||||
if len(prefixTerm) > 0 {
|
||||
fieldDict, err = indexReader.FieldDictPrefix(field, []byte(prefixTerm))
|
||||
} else {
|
||||
fieldDict, err = indexReader.FieldDict(field)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := fieldDict.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
// enumerate terms and check levenshtein distance
|
||||
tfd, err := fieldDict.Next()
|
||||
for err == nil && tfd != nil {
|
||||
ld, exceeded := search.LevenshteinDistanceMax(term, tfd.Term, fuzziness)
|
||||
if !exceeded && ld <= fuzziness {
|
||||
rv = append(rv, tfd.Term)
|
||||
if tooManyClauses(len(rv)) {
|
||||
return rv, tooManyClausesErr()
|
||||
}
|
||||
}
|
||||
tfd, err = fieldDict.Next()
|
||||
}
|
||||
|
||||
return rv, err
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Count() uint64 {
|
||||
return s.searcher.Count()
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Weight() float64 {
|
||||
return s.searcher.Weight()
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) SetQueryNorm(qnorm float64) {
|
||||
s.searcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Next(ctx)
|
||||
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Advance(ctx, ID)
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Close() error {
|
||||
return s.searcher.Close()
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *FuzzySearcher) DocumentMatchPoolSize() int {
|
||||
return s.searcher.DocumentMatchPoolSize()
|
||||
}
|
105
vendor/github.com/blevesearch/bleve/search/searcher/search_match_all.go
generated
vendored
Normal file
105
vendor/github.com/blevesearch/bleve/search/searcher/search_match_all.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
type MatchAllSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
reader index.DocIDReader
|
||||
scorer *scorer.ConstantScorer
|
||||
count uint64
|
||||
}
|
||||
|
||||
func NewMatchAllSearcher(indexReader index.IndexReader, boost float64, explain bool) (*MatchAllSearcher, error) {
|
||||
reader, err := indexReader.DocIDReaderAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, err := indexReader.DocCount()
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
return nil, err
|
||||
}
|
||||
scorer := scorer.NewConstantScorer(1.0, boost, explain)
|
||||
return &MatchAllSearcher{
|
||||
indexReader: indexReader,
|
||||
reader: reader,
|
||||
scorer: scorer,
|
||||
count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Count() uint64 {
|
||||
return s.count
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Weight() float64 {
|
||||
return s.scorer.Weight()
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.scorer.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
id, err := s.reader.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if id == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// score match
|
||||
docMatch := s.scorer.Score(ctx, id)
|
||||
// return doc match
|
||||
return docMatch, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
id, err := s.reader.Advance(ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if id == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// score match
|
||||
docMatch := s.scorer.Score(ctx, id)
|
||||
|
||||
// return doc match
|
||||
return docMatch, nil
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Close() error {
|
||||
return s.reader.Close()
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *MatchAllSearcher) DocumentMatchPoolSize() int {
|
||||
return 1
|
||||
}
|
62
vendor/github.com/blevesearch/bleve/search/searcher/search_match_none.go
generated
vendored
Normal file
62
vendor/github.com/blevesearch/bleve/search/searcher/search_match_none.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type MatchNoneSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
}
|
||||
|
||||
func NewMatchNoneSearcher(indexReader index.IndexReader) (*MatchNoneSearcher, error) {
|
||||
return &MatchNoneSearcher{
|
||||
indexReader: indexReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Count() uint64 {
|
||||
return uint64(0)
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Weight() float64 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) SetQueryNorm(qnorm float64) {
|
||||
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *MatchNoneSearcher) DocumentMatchPoolSize() int {
|
||||
return 0
|
||||
}
|
235
vendor/github.com/blevesearch/bleve/search/searcher/search_numeric_range.go
generated
vendored
Normal file
235
vendor/github.com/blevesearch/bleve/search/searcher/search_numeric_range.go
generated
vendored
Normal file
|
@ -0,0 +1,235 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/numeric"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type NumericRangeSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
min *float64
|
||||
max *float64
|
||||
field string
|
||||
explain bool
|
||||
searcher *DisjunctionSearcher
|
||||
}
|
||||
|
||||
func NewNumericRangeSearcher(indexReader index.IndexReader, min *float64, max *float64, inclusiveMin, inclusiveMax *bool, field string, boost float64, explain bool) (*NumericRangeSearcher, error) {
|
||||
// account for unbounded edges
|
||||
if min == nil {
|
||||
negInf := math.Inf(-1)
|
||||
min = &negInf
|
||||
}
|
||||
if max == nil {
|
||||
Inf := math.Inf(1)
|
||||
max = &Inf
|
||||
}
|
||||
if inclusiveMin == nil {
|
||||
defaultInclusiveMin := true
|
||||
inclusiveMin = &defaultInclusiveMin
|
||||
}
|
||||
if inclusiveMax == nil {
|
||||
defaultInclusiveMax := false
|
||||
inclusiveMax = &defaultInclusiveMax
|
||||
}
|
||||
// find all the ranges
|
||||
minInt64 := numeric.Float64ToInt64(*min)
|
||||
if !*inclusiveMin && minInt64 != math.MaxInt64 {
|
||||
minInt64++
|
||||
}
|
||||
maxInt64 := numeric.Float64ToInt64(*max)
|
||||
if !*inclusiveMax && maxInt64 != math.MinInt64 {
|
||||
maxInt64--
|
||||
}
|
||||
// FIXME hard-coded precision, should match field declaration
|
||||
termRanges := splitInt64Range(minInt64, maxInt64, 4)
|
||||
terms := termRanges.Enumerate()
|
||||
if tooManyClauses(len(terms)) {
|
||||
return nil, tooManyClausesErr()
|
||||
}
|
||||
// enumerate all the terms in the range
|
||||
qsearchers := make([]search.Searcher, len(terms))
|
||||
qsearchersClose := func() {
|
||||
for _, searcher := range qsearchers {
|
||||
if searcher != nil {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, term := range terms {
|
||||
var err error
|
||||
qsearchers[i], err = NewTermSearcher(indexReader, string(term), field, boost, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// build disjunction searcher of these ranges
|
||||
searcher, err := NewDisjunctionSearcher(indexReader, qsearchers, 0, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
return &NumericRangeSearcher{
|
||||
indexReader: indexReader,
|
||||
min: min,
|
||||
max: max,
|
||||
field: field,
|
||||
explain: explain,
|
||||
searcher: searcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Count() uint64 {
|
||||
return s.searcher.Count()
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Weight() float64 {
|
||||
return s.searcher.Weight()
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.searcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Next(ctx)
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Advance(ctx, ID)
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Close() error {
|
||||
return s.searcher.Close()
|
||||
}
|
||||
|
||||
type termRange struct {
|
||||
startTerm []byte
|
||||
endTerm []byte
|
||||
}
|
||||
|
||||
func (t *termRange) Enumerate() [][]byte {
|
||||
var rv [][]byte
|
||||
next := t.startTerm
|
||||
for bytes.Compare(next, t.endTerm) <= 0 {
|
||||
rv = append(rv, next)
|
||||
next = incrementBytes(next)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func incrementBytes(in []byte) []byte {
|
||||
rv := make([]byte, len(in))
|
||||
copy(rv, in)
|
||||
for i := len(rv) - 1; i >= 0; i-- {
|
||||
rv[i] = rv[i] + 1
|
||||
if rv[i] != 0 {
|
||||
// didn't overflow, so stop
|
||||
break
|
||||
}
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
type termRanges []*termRange
|
||||
|
||||
func (tr termRanges) Enumerate() [][]byte {
|
||||
var rv [][]byte
|
||||
for _, tri := range tr {
|
||||
trie := tri.Enumerate()
|
||||
rv = append(rv, trie...)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func splitInt64Range(minBound, maxBound int64, precisionStep uint) termRanges {
|
||||
rv := make(termRanges, 0)
|
||||
if minBound > maxBound {
|
||||
return rv
|
||||
}
|
||||
|
||||
for shift := uint(0); ; shift += precisionStep {
|
||||
|
||||
diff := int64(1) << (shift + precisionStep)
|
||||
mask := ((int64(1) << precisionStep) - int64(1)) << shift
|
||||
hasLower := (minBound & mask) != int64(0)
|
||||
hasUpper := (maxBound & mask) != mask
|
||||
|
||||
var nextMinBound int64
|
||||
if hasLower {
|
||||
nextMinBound = (minBound + diff) &^ mask
|
||||
} else {
|
||||
nextMinBound = minBound &^ mask
|
||||
}
|
||||
var nextMaxBound int64
|
||||
if hasUpper {
|
||||
nextMaxBound = (maxBound - diff) &^ mask
|
||||
} else {
|
||||
nextMaxBound = maxBound &^ mask
|
||||
}
|
||||
|
||||
lowerWrapped := nextMinBound < minBound
|
||||
upperWrapped := nextMaxBound > maxBound
|
||||
|
||||
if shift+precisionStep >= 64 || nextMinBound > nextMaxBound || lowerWrapped || upperWrapped {
|
||||
// We are in the lowest precision or the next precision is not available.
|
||||
rv = append(rv, newRange(minBound, maxBound, shift))
|
||||
// exit the split recursion loop
|
||||
break
|
||||
}
|
||||
|
||||
if hasLower {
|
||||
rv = append(rv, newRange(minBound, minBound|mask, shift))
|
||||
}
|
||||
if hasUpper {
|
||||
rv = append(rv, newRange(maxBound&^mask, maxBound, shift))
|
||||
}
|
||||
|
||||
// recurse to next precision
|
||||
minBound = nextMinBound
|
||||
maxBound = nextMaxBound
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func newRange(minBound, maxBound int64, shift uint) *termRange {
|
||||
maxBound |= (int64(1) << shift) - int64(1)
|
||||
minBytes := numeric.MustNewPrefixCodedInt64(minBound, shift)
|
||||
maxBytes := numeric.MustNewPrefixCodedInt64(maxBound, shift)
|
||||
return newRangeBytes(minBytes, maxBytes)
|
||||
}
|
||||
|
||||
func newRangeBytes(minBytes, maxBytes []byte) *termRange {
|
||||
return &termRange{
|
||||
startTerm: minBytes,
|
||||
endTerm: maxBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *NumericRangeSearcher) DocumentMatchPoolSize() int {
|
||||
return s.searcher.DocumentMatchPoolSize()
|
||||
}
|
197
vendor/github.com/blevesearch/bleve/search/searcher/search_phrase.go
generated
vendored
Normal file
197
vendor/github.com/blevesearch/bleve/search/searcher/search_phrase.go
generated
vendored
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type PhraseSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
mustSearcher *ConjunctionSearcher
|
||||
queryNorm float64
|
||||
currMust *search.DocumentMatch
|
||||
slop int
|
||||
terms []string
|
||||
initialized bool
|
||||
}
|
||||
|
||||
func NewPhraseSearcher(indexReader index.IndexReader, mustSearcher *ConjunctionSearcher, terms []string) (*PhraseSearcher, error) {
|
||||
// build our searcher
|
||||
rv := PhraseSearcher{
|
||||
indexReader: indexReader,
|
||||
mustSearcher: mustSearcher,
|
||||
terms: terms,
|
||||
}
|
||||
rv.computeQueryNorm()
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) computeQueryNorm() {
|
||||
// first calculate sum of squared weights
|
||||
sumOfSquaredWeights := 0.0
|
||||
if s.mustSearcher != nil {
|
||||
sumOfSquaredWeights += s.mustSearcher.Weight()
|
||||
}
|
||||
|
||||
// now compute query norm from this
|
||||
s.queryNorm = 1.0 / math.Sqrt(sumOfSquaredWeights)
|
||||
// finally tell all the downstream searchers the norm
|
||||
if s.mustSearcher != nil {
|
||||
s.mustSearcher.SetQueryNorm(s.queryNorm)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) initSearchers(ctx *search.SearchContext) error {
|
||||
err := s.advanceNextMust(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) advanceNextMust(ctx *search.SearchContext) error {
|
||||
var err error
|
||||
|
||||
if s.mustSearcher != nil {
|
||||
s.currMust, err = s.mustSearcher.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Weight() float64 {
|
||||
return s.mustSearcher.Weight()
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.mustSearcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var rv *search.DocumentMatch
|
||||
for s.currMust != nil {
|
||||
rvftlm := make(search.FieldTermLocationMap, 0)
|
||||
freq := 0
|
||||
firstTerm := s.terms[0]
|
||||
for field, termLocMap := range s.currMust.Locations {
|
||||
rvtlm := make(search.TermLocationMap, 0)
|
||||
locations, ok := termLocMap[firstTerm]
|
||||
if ok {
|
||||
OUTER:
|
||||
for _, location := range locations {
|
||||
crvtlm := make(search.TermLocationMap, 0)
|
||||
INNER:
|
||||
for i := 0; i < len(s.terms); i++ {
|
||||
nextTerm := s.terms[i]
|
||||
if nextTerm != "" {
|
||||
// look through all these term locations
|
||||
// to try and find the correct offsets
|
||||
nextLocations, ok := termLocMap[nextTerm]
|
||||
if ok {
|
||||
for _, nextLocation := range nextLocations {
|
||||
if nextLocation.Pos == location.Pos+float64(i) && nextLocation.SameArrayElement(location) {
|
||||
// found a location match for this term
|
||||
crvtlm.AddLocation(nextTerm, nextLocation)
|
||||
continue INNER
|
||||
}
|
||||
}
|
||||
// if we got here we didn't find a location match for this term
|
||||
continue OUTER
|
||||
} else {
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we got here all the terms matched
|
||||
freq++
|
||||
search.MergeTermLocationMaps(rvtlm, crvtlm)
|
||||
rvftlm[field] = rvtlm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if freq > 0 {
|
||||
// return match
|
||||
rv = s.currMust
|
||||
rv.Locations = rvftlm
|
||||
err := s.advanceNextMust(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
err := s.advanceNextMust(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
if !s.initialized {
|
||||
err := s.initSearchers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
s.currMust, err = s.mustSearcher.Advance(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Next(ctx)
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Count() uint64 {
|
||||
// for now return a worst case
|
||||
return s.mustSearcher.Count()
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Close() error {
|
||||
if s.mustSearcher != nil {
|
||||
err := s.mustSearcher.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *PhraseSearcher) DocumentMatchPoolSize() int {
|
||||
return s.mustSearcher.DocumentMatchPoolSize() + 1
|
||||
}
|
92
vendor/github.com/blevesearch/bleve/search/searcher/search_regexp.go
generated
vendored
Normal file
92
vendor/github.com/blevesearch/bleve/search/searcher/search_regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2015 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 searcher
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
func NewRegexpSearcher(indexReader index.IndexReader, pattern *regexp.Regexp, field string, boost float64, explain bool) (search.Searcher, error) {
|
||||
|
||||
prefixTerm, complete := pattern.LiteralPrefix()
|
||||
var candidateTerms []string
|
||||
if complete {
|
||||
// there is no pattern
|
||||
candidateTerms = []string{prefixTerm}
|
||||
} else {
|
||||
var err error
|
||||
candidateTerms, err = findRegexpCandidateTerms(indexReader, pattern, field, prefixTerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// enumerate all the terms in the range
|
||||
qsearchers := make([]search.Searcher, 0, len(candidateTerms))
|
||||
qsearchersClose := func() {
|
||||
for _, searcher := range qsearchers {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
for _, cterm := range candidateTerms {
|
||||
qsearcher, err := NewTermSearcher(indexReader, cterm, field, boost, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
qsearchers = append(qsearchers, qsearcher)
|
||||
}
|
||||
|
||||
// build disjunction searcher of these ranges
|
||||
searcher, err := NewDisjunctionSearcher(indexReader, qsearchers, 0, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return searcher, err
|
||||
}
|
||||
|
||||
func findRegexpCandidateTerms(indexReader index.IndexReader, pattern *regexp.Regexp, field, prefixTerm string) (rv []string, err error) {
|
||||
rv = make([]string, 0)
|
||||
var fieldDict index.FieldDict
|
||||
if len(prefixTerm) > 0 {
|
||||
fieldDict, err = indexReader.FieldDictPrefix(field, []byte(prefixTerm))
|
||||
} else {
|
||||
fieldDict, err = indexReader.FieldDict(field)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := fieldDict.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
// enumerate the terms and check against regexp
|
||||
tfd, err := fieldDict.Next()
|
||||
for err == nil && tfd != nil {
|
||||
if pattern.MatchString(tfd.Term) {
|
||||
rv = append(rv, tfd.Term)
|
||||
if tooManyClauses(len(rv)) {
|
||||
return rv, tooManyClausesErr()
|
||||
}
|
||||
}
|
||||
tfd, err = fieldDict.Next()
|
||||
}
|
||||
|
||||
return rv, err
|
||||
}
|
110
vendor/github.com/blevesearch/bleve/search/searcher/search_term.go
generated
vendored
Normal file
110
vendor/github.com/blevesearch/bleve/search/searcher/search_term.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/scorer"
|
||||
)
|
||||
|
||||
type TermSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
term string
|
||||
field string
|
||||
reader index.TermFieldReader
|
||||
scorer *scorer.TermQueryScorer
|
||||
tfd index.TermFieldDoc
|
||||
explain bool
|
||||
}
|
||||
|
||||
func NewTermSearcher(indexReader index.IndexReader, term string, field string, boost float64, explain bool) (*TermSearcher, error) {
|
||||
reader, err := indexReader.TermFieldReader([]byte(term), field, true, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, err := indexReader.DocCount()
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
return nil, err
|
||||
}
|
||||
scorer := scorer.NewTermQueryScorer(term, field, boost, count, reader.Count(), explain)
|
||||
return &TermSearcher{
|
||||
indexReader: indexReader,
|
||||
term: term,
|
||||
field: field,
|
||||
explain: explain,
|
||||
reader: reader,
|
||||
scorer: scorer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Count() uint64 {
|
||||
return s.reader.Count()
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Weight() float64 {
|
||||
return s.scorer.Weight()
|
||||
}
|
||||
|
||||
func (s *TermSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.scorer.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
termMatch, err := s.reader.Next(s.tfd.Reset())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if termMatch == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// score match
|
||||
docMatch := s.scorer.Score(ctx, termMatch)
|
||||
// return doc match
|
||||
return docMatch, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
termMatch, err := s.reader.Advance(ID, s.tfd.Reset())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if termMatch == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// score match
|
||||
docMatch := s.scorer.Score(ctx, termMatch)
|
||||
|
||||
// return doc match
|
||||
return docMatch, nil
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Close() error {
|
||||
return s.reader.Close()
|
||||
}
|
||||
|
||||
func (s *TermSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *TermSearcher) DocumentMatchPoolSize() int {
|
||||
return 1
|
||||
}
|
111
vendor/github.com/blevesearch/bleve/search/searcher/search_term_prefix.go
generated
vendored
Normal file
111
vendor/github.com/blevesearch/bleve/search/searcher/search_term_prefix.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
// 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 searcher
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type TermPrefixSearcher struct {
|
||||
indexReader index.IndexReader
|
||||
prefix string
|
||||
field string
|
||||
explain bool
|
||||
searcher *DisjunctionSearcher
|
||||
}
|
||||
|
||||
func NewTermPrefixSearcher(indexReader index.IndexReader, prefix string, field string, boost float64, explain bool) (*TermPrefixSearcher, error) {
|
||||
// find the terms with this prefix
|
||||
fieldDict, err := indexReader.FieldDictPrefix(field, []byte(prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// enumerate all the terms in the range
|
||||
qsearchers := make([]search.Searcher, 0, 25)
|
||||
qsearchersClose := func() {
|
||||
for _, searcher := range qsearchers {
|
||||
_ = searcher.Close()
|
||||
}
|
||||
}
|
||||
|
||||
tfd, err := fieldDict.Next()
|
||||
for err == nil && tfd != nil {
|
||||
var qsearcher *TermSearcher
|
||||
qsearcher, err = NewTermSearcher(indexReader, string(tfd.Term), field, 1.0, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
_ = fieldDict.Close()
|
||||
return nil, err
|
||||
}
|
||||
qsearchers = append(qsearchers, qsearcher)
|
||||
tfd, err = fieldDict.Next()
|
||||
}
|
||||
|
||||
err = fieldDict.Close()
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// build disjunction searcher of these ranges
|
||||
searcher, err := NewDisjunctionSearcher(indexReader, qsearchers, 0, explain)
|
||||
if err != nil {
|
||||
qsearchersClose()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TermPrefixSearcher{
|
||||
indexReader: indexReader,
|
||||
prefix: prefix,
|
||||
field: field,
|
||||
explain: explain,
|
||||
searcher: searcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Count() uint64 {
|
||||
return s.searcher.Count()
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Weight() float64 {
|
||||
return s.searcher.Weight()
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) SetQueryNorm(qnorm float64) {
|
||||
s.searcher.SetQueryNorm(qnorm)
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Next(ctx)
|
||||
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||
return s.searcher.Advance(ctx, ID)
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Close() error {
|
||||
return s.searcher.Close()
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) Min() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *TermPrefixSearcher) DocumentMatchPoolSize() int {
|
||||
return s.searcher.DocumentMatchPoolSize()
|
||||
}
|
493
vendor/github.com/blevesearch/bleve/search/sort.go
generated
vendored
Normal file
493
vendor/github.com/blevesearch/bleve/search/sort.go
generated
vendored
Normal file
|
@ -0,0 +1,493 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/numeric"
|
||||
)
|
||||
|
||||
var HighTerm = strings.Repeat(string([]byte{0xff}), 10)
|
||||
var LowTerm = string([]byte{0x00})
|
||||
|
||||
type SearchSort interface {
|
||||
Value(a *DocumentMatch) string
|
||||
Descending() bool
|
||||
|
||||
RequiresDocID() bool
|
||||
RequiresScoring() bool
|
||||
RequiresFields() []string
|
||||
}
|
||||
|
||||
func ParseSearchSortObj(input map[string]interface{}) (SearchSort, error) {
|
||||
descending, ok := input["desc"].(bool)
|
||||
by, ok := input["by"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("search sort must specify by")
|
||||
}
|
||||
switch by {
|
||||
case "id":
|
||||
return &SortDocID{
|
||||
Desc: descending,
|
||||
}, nil
|
||||
case "score":
|
||||
return &SortScore{
|
||||
Desc: descending,
|
||||
}, nil
|
||||
case "field":
|
||||
field, ok := input["field"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("search sort mode field must specify field")
|
||||
}
|
||||
rv := &SortField{
|
||||
Field: field,
|
||||
Desc: descending,
|
||||
}
|
||||
typ, ok := input["type"].(string)
|
||||
if ok {
|
||||
switch typ {
|
||||
case "auto":
|
||||
rv.Type = SortFieldAuto
|
||||
case "string":
|
||||
rv.Type = SortFieldAsString
|
||||
case "number":
|
||||
rv.Type = SortFieldAsNumber
|
||||
case "date":
|
||||
rv.Type = SortFieldAsDate
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown sort field type: %s", typ)
|
||||
}
|
||||
}
|
||||
mode, ok := input["mode"].(string)
|
||||
if ok {
|
||||
switch mode {
|
||||
case "default":
|
||||
rv.Mode = SortFieldDefault
|
||||
case "min":
|
||||
rv.Mode = SortFieldMin
|
||||
case "max":
|
||||
rv.Mode = SortFieldMax
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown sort field mode: %s", mode)
|
||||
}
|
||||
}
|
||||
missing, ok := input["missing"].(string)
|
||||
if ok {
|
||||
switch missing {
|
||||
case "first":
|
||||
rv.Missing = SortFieldMissingFirst
|
||||
case "last":
|
||||
rv.Missing = SortFieldMissingLast
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown sort field missing: %s", missing)
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown search sort by: %s", by)
|
||||
}
|
||||
|
||||
func ParseSearchSortString(input string) SearchSort {
|
||||
descending := false
|
||||
if strings.HasPrefix(input, "-") {
|
||||
descending = true
|
||||
input = input[1:]
|
||||
} else if strings.HasPrefix(input, "+") {
|
||||
input = input[1:]
|
||||
}
|
||||
if input == "_id" {
|
||||
return &SortDocID{
|
||||
Desc: descending,
|
||||
}
|
||||
} else if input == "_score" {
|
||||
return &SortScore{
|
||||
Desc: descending,
|
||||
}
|
||||
}
|
||||
return &SortField{
|
||||
Field: input,
|
||||
Desc: descending,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseSearchSortJSON(input json.RawMessage) (SearchSort, error) {
|
||||
// first try to parse it as string
|
||||
var sortString string
|
||||
err := json.Unmarshal(input, &sortString)
|
||||
if err != nil {
|
||||
var sortObj map[string]interface{}
|
||||
err = json.Unmarshal(input, &sortObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseSearchSortObj(sortObj)
|
||||
}
|
||||
return ParseSearchSortString(sortString), nil
|
||||
}
|
||||
|
||||
func ParseSortOrderStrings(in []string) SortOrder {
|
||||
rv := make(SortOrder, 0, len(in))
|
||||
for _, i := range in {
|
||||
ss := ParseSearchSortString(i)
|
||||
rv = append(rv, ss)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func ParseSortOrderJSON(in []json.RawMessage) (SortOrder, error) {
|
||||
rv := make(SortOrder, 0, len(in))
|
||||
for _, i := range in {
|
||||
ss, err := ParseSearchSortJSON(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv = append(rv, ss)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type SortOrder []SearchSort
|
||||
|
||||
func (so SortOrder) Value(doc *DocumentMatch) {
|
||||
for _, soi := range so {
|
||||
doc.Sort = append(doc.Sort, soi.Value(doc))
|
||||
}
|
||||
}
|
||||
|
||||
// Compare will compare two document matches using the specified sort order
|
||||
// if both are numbers, we avoid converting back to term
|
||||
func (so SortOrder) Compare(cachedScoring, cachedDesc []bool, i, j *DocumentMatch) int {
|
||||
// compare the documents on all search sorts until a differences is found
|
||||
for x := range so {
|
||||
c := 0
|
||||
if cachedScoring[x] {
|
||||
if i.Score < j.Score {
|
||||
c = -1
|
||||
} else if i.Score > j.Score {
|
||||
c = 1
|
||||
}
|
||||
} else {
|
||||
iVal := i.Sort[x]
|
||||
jVal := j.Sort[x]
|
||||
c = strings.Compare(iVal, jVal)
|
||||
}
|
||||
|
||||
if c == 0 {
|
||||
continue
|
||||
}
|
||||
if cachedDesc[x] {
|
||||
c = -c
|
||||
}
|
||||
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) RequiredFields() []string {
|
||||
var rv []string
|
||||
for _, soi := range so {
|
||||
rv = append(rv, soi.RequiresFields()...)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (so SortOrder) CacheIsScore() []bool {
|
||||
var rv []bool
|
||||
for _, soi := range so {
|
||||
rv = append(rv, soi.RequiresScoring())
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (so SortOrder) CacheDescending() []bool {
|
||||
var rv []bool
|
||||
for _, soi := range so {
|
||||
rv = append(rv, soi.Descending())
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// SortFieldType lets you control some internal sort behavior
|
||||
// normally leaving this to the zero-value of SortFieldAuto is fine
|
||||
type SortFieldType int
|
||||
|
||||
const (
|
||||
// SortFieldAuto applies heuristics attempt to automatically sort correctly
|
||||
SortFieldAuto SortFieldType = iota
|
||||
// SortFieldAsString forces sort as string (no prefix coded terms removed)
|
||||
SortFieldAsString
|
||||
// SortFieldAsNumber forces sort as string (prefix coded terms with shift > 0 removed)
|
||||
SortFieldAsNumber
|
||||
// SortFieldAsDate forces sort as string (prefix coded terms with shift > 0 removed)
|
||||
SortFieldAsDate
|
||||
)
|
||||
|
||||
// SortFieldMode describes the behavior if the field has multiple values
|
||||
type SortFieldMode int
|
||||
|
||||
const (
|
||||
// SortFieldDefault uses the first (or only) value, this is the default zero-value
|
||||
SortFieldDefault SortFieldMode = iota // FIXME name is confusing
|
||||
// SortFieldMin uses the minimum value
|
||||
SortFieldMin
|
||||
// SortFieldMax uses the maximum value
|
||||
SortFieldMax
|
||||
)
|
||||
|
||||
// SortFieldMissing controls where documents missing a field value should be sorted
|
||||
type SortFieldMissing int
|
||||
|
||||
const (
|
||||
// SortFieldMissingLast sorts documents missing a field at the end
|
||||
SortFieldMissingLast SortFieldMissing = iota
|
||||
|
||||
// SortFieldMissingFirst sorts documents missing a field at the beginning
|
||||
SortFieldMissingFirst
|
||||
)
|
||||
|
||||
// SortField will sort results by the value of a stored field
|
||||
// Field is the name of the field
|
||||
// Descending reverse the sort order (default false)
|
||||
// Type allows forcing of string/number/date behavior (default auto)
|
||||
// Mode controls behavior for multi-values fields (default first)
|
||||
// Missing controls behavior of missing values (default last)
|
||||
type SortField struct {
|
||||
Field string
|
||||
Desc bool
|
||||
Type SortFieldType
|
||||
Mode SortFieldMode
|
||||
Missing SortFieldMissing
|
||||
}
|
||||
|
||||
// Value returns the sort value of the DocumentMatch
|
||||
func (s *SortField) Value(i *DocumentMatch) string {
|
||||
iTerms := i.CachedFieldTerms[s.Field]
|
||||
iTerms = s.filterTermsByType(iTerms)
|
||||
iTerm := s.filterTermsByMode(iTerms)
|
||||
return iTerm
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortField) Descending() bool {
|
||||
return s.Desc
|
||||
}
|
||||
|
||||
func (s *SortField) filterTermsByMode(terms []string) string {
|
||||
if len(terms) == 1 || (len(terms) > 1 && s.Mode == SortFieldDefault) {
|
||||
return terms[0]
|
||||
} else if len(terms) > 1 {
|
||||
switch s.Mode {
|
||||
case SortFieldMin:
|
||||
sort.Strings(terms)
|
||||
return terms[0]
|
||||
case SortFieldMax:
|
||||
sort.Strings(terms)
|
||||
return terms[len(terms)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// handle missing terms
|
||||
if s.Missing == SortFieldMissingLast {
|
||||
if s.Desc {
|
||||
return LowTerm
|
||||
}
|
||||
return HighTerm
|
||||
}
|
||||
if s.Desc {
|
||||
return HighTerm
|
||||
}
|
||||
return LowTerm
|
||||
}
|
||||
|
||||
// filterTermsByType attempts to make one pass on the terms
|
||||
// if we are in auto-mode AND all the terms look like prefix-coded numbers
|
||||
// return only the terms which had shift of 0
|
||||
// if we are in explicit number or date mode, return only valid
|
||||
// prefix coded numbers with shift of 0
|
||||
func (s *SortField) filterTermsByType(terms []string) []string {
|
||||
stype := s.Type
|
||||
if stype == SortFieldAuto {
|
||||
allTermsPrefixCoded := true
|
||||
var termsWithShiftZero []string
|
||||
for _, term := range terms {
|
||||
valid, shift := numeric.ValidPrefixCodedTerm(term)
|
||||
if valid && shift == 0 {
|
||||
termsWithShiftZero = append(termsWithShiftZero, term)
|
||||
} else if !valid {
|
||||
allTermsPrefixCoded = false
|
||||
}
|
||||
}
|
||||
if allTermsPrefixCoded {
|
||||
terms = termsWithShiftZero
|
||||
}
|
||||
} else if stype == SortFieldAsNumber || stype == SortFieldAsDate {
|
||||
var termsWithShiftZero []string
|
||||
for _, term := range terms {
|
||||
valid, shift := numeric.ValidPrefixCodedTerm(term)
|
||||
if valid && shift == 0 {
|
||||
termsWithShiftZero = append(termsWithShiftZero)
|
||||
}
|
||||
}
|
||||
terms = termsWithShiftZero
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
// RequiresDocID says this SearchSort does not require the DocID be loaded
|
||||
func (s *SortField) RequiresDocID() bool { return false }
|
||||
|
||||
// RequiresScoring says this SearchStore does not require scoring
|
||||
func (s *SortField) RequiresScoring() bool { return false }
|
||||
|
||||
// RequiresFields says this SearchStore requires the specified stored field
|
||||
func (s *SortField) RequiresFields() []string { return []string{s.Field} }
|
||||
|
||||
func (s *SortField) MarshalJSON() ([]byte, error) {
|
||||
// see if simple format can be used
|
||||
if s.Missing == SortFieldMissingLast &&
|
||||
s.Mode == SortFieldDefault &&
|
||||
s.Type == SortFieldAuto {
|
||||
if s.Desc {
|
||||
return json.Marshal("-" + s.Field)
|
||||
}
|
||||
return json.Marshal(s.Field)
|
||||
}
|
||||
sfm := map[string]interface{}{
|
||||
"by": "field",
|
||||
"field": s.Field,
|
||||
}
|
||||
if s.Desc {
|
||||
sfm["desc"] = true
|
||||
}
|
||||
if s.Missing > SortFieldMissingLast {
|
||||
switch s.Missing {
|
||||
case SortFieldMissingFirst:
|
||||
sfm["missing"] = "first"
|
||||
}
|
||||
}
|
||||
if s.Mode > SortFieldDefault {
|
||||
switch s.Mode {
|
||||
case SortFieldMin:
|
||||
sfm["mode"] = "min"
|
||||
case SortFieldMax:
|
||||
sfm["mode"] = "max"
|
||||
}
|
||||
}
|
||||
if s.Type > SortFieldAuto {
|
||||
switch s.Type {
|
||||
case SortFieldAsString:
|
||||
sfm["type"] = "string"
|
||||
case SortFieldAsNumber:
|
||||
sfm["type"] = "number"
|
||||
case SortFieldAsDate:
|
||||
sfm["type"] = "date"
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(sfm)
|
||||
}
|
||||
|
||||
// SortDocID will sort results by the document identifier
|
||||
type SortDocID struct {
|
||||
Desc bool
|
||||
}
|
||||
|
||||
// Value returns the sort value of the DocumentMatch
|
||||
func (s *SortDocID) Value(i *DocumentMatch) string {
|
||||
return i.ID
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortDocID) Descending() bool {
|
||||
return s.Desc
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// RequiresFields says this SearchStore does not require any stored fields
|
||||
func (s *SortDocID) RequiresFields() []string { return nil }
|
||||
|
||||
func (s *SortDocID) MarshalJSON() ([]byte, error) {
|
||||
if s.Desc {
|
||||
return json.Marshal("-_id")
|
||||
}
|
||||
return json.Marshal("_id")
|
||||
}
|
||||
|
||||
// SortScore will sort results by the document match score
|
||||
type SortScore struct {
|
||||
Desc bool
|
||||
}
|
||||
|
||||
// Value returns the sort value of the DocumentMatch
|
||||
func (s *SortScore) Value(i *DocumentMatch) string {
|
||||
return "_score"
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortScore) Descending() bool {
|
||||
return s.Desc
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// RequiresFields says this SearchStore does not require any store fields
|
||||
func (s *SortScore) RequiresFields() []string { return nil }
|
||||
|
||||
func (s *SortScore) MarshalJSON() ([]byte, error) {
|
||||
if s.Desc {
|
||||
return json.Marshal("-_score")
|
||||
}
|
||||
return json.Marshal("_score")
|
||||
}
|
42
vendor/github.com/blevesearch/bleve/search/util.go
generated
vendored
Normal file
42
vendor/github.com/blevesearch/bleve/search/util.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// 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
|
||||
|
||||
func MergeLocations(locations []FieldTermLocationMap) FieldTermLocationMap {
|
||||
rv := locations[0]
|
||||
|
||||
for i := 1; i < len(locations); i++ {
|
||||
nextLocations := locations[i]
|
||||
for field, termLocationMap := range nextLocations {
|
||||
rvTermLocationMap, rvHasField := rv[field]
|
||||
if rvHasField {
|
||||
rv[field] = MergeTermLocationMaps(rvTermLocationMap, termLocationMap)
|
||||
} else {
|
||||
rv[field] = termLocationMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func MergeTermLocationMaps(rv, other TermLocationMap) TermLocationMap {
|
||||
for term, locationMap := range other {
|
||||
// for a given term/document there cannot be different locations
|
||||
// if they came back from different clauses, overwrite is ok
|
||||
rv[term] = locationMap
|
||||
}
|
||||
return rv
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue