2014-07-30 18:30:38 +02:00
|
|
|
// Copyright (c) 2014 Couchbase, Inc.
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
|
|
// except in compliance with the License. You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
// Unless required by applicable law or agreed to in writing, software distributed under the
|
|
|
|
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
|
|
// either express or implied. See the License for the specific language governing permissions
|
|
|
|
// and limitations under the License.
|
2014-08-29 20:18:36 +02:00
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
package bleve
|
|
|
|
|
|
|
|
import (
|
2014-08-20 22:58:20 +02:00
|
|
|
"encoding/json"
|
2014-07-30 18:30:38 +02:00
|
|
|
"fmt"
|
2014-08-20 22:58:20 +02:00
|
|
|
"os"
|
2014-08-25 15:06:53 +02:00
|
|
|
"sync"
|
2014-10-02 20:12:22 +02:00
|
|
|
"sync/atomic"
|
2014-08-06 19:52:20 +02:00
|
|
|
"time"
|
2014-07-30 18:30:38 +02:00
|
|
|
|
2014-08-28 21:38:57 +02:00
|
|
|
"github.com/blevesearch/bleve/document"
|
|
|
|
"github.com/blevesearch/bleve/index"
|
|
|
|
"github.com/blevesearch/bleve/index/store"
|
|
|
|
"github.com/blevesearch/bleve/index/upside_down"
|
|
|
|
"github.com/blevesearch/bleve/registry"
|
|
|
|
"github.com/blevesearch/bleve/search"
|
2014-09-01 17:15:38 +02:00
|
|
|
"github.com/blevesearch/bleve/search/collectors"
|
|
|
|
"github.com/blevesearch/bleve/search/facets"
|
2014-07-30 18:30:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type indexImpl struct {
|
2014-08-25 15:06:53 +02:00
|
|
|
path string
|
|
|
|
meta *indexMeta
|
|
|
|
s store.KVStore
|
|
|
|
i index.Index
|
|
|
|
m *IndexMapping
|
|
|
|
mutex sync.RWMutex
|
|
|
|
open bool
|
2014-10-02 20:12:22 +02:00
|
|
|
stats *IndexStat
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const storePath = "store"
|
|
|
|
|
|
|
|
var mappingInternalKey = []byte("_mapping")
|
|
|
|
|
|
|
|
func indexStorePath(path string) string {
|
|
|
|
return path + string(os.PathSeparator) + storePath
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMemIndex(mapping *IndexMapping) (*indexImpl, error) {
|
|
|
|
rv := indexImpl{
|
2014-10-02 20:12:22 +02:00
|
|
|
path: "",
|
|
|
|
m: mapping,
|
|
|
|
meta: newIndexMeta("mem"),
|
|
|
|
stats: &IndexStat{},
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
storeConstructor := registry.KVStoreConstructorByName(rv.meta.Storage)
|
|
|
|
if storeConstructor == nil {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorUnknownStorageType
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
// now open the store
|
|
|
|
var err error
|
|
|
|
rv.s, err = storeConstructor(nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-18 18:43:12 +01:00
|
|
|
// open the index
|
2014-09-24 14:13:14 +02:00
|
|
|
rv.i = upside_down.NewUpsideDownCouch(rv.s, Config.analysisQueue)
|
2014-08-20 22:58:20 +02:00
|
|
|
err = rv.i.Open()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-02 20:12:22 +02:00
|
|
|
rv.stats.indexStat = rv.i.Stats()
|
2014-08-20 22:58:20 +02:00
|
|
|
|
|
|
|
// now persist the mapping
|
|
|
|
mappingBytes, err := json.Marshal(mapping)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = rv.i.SetInternal(mappingInternalKey, mappingBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-25 15:06:53 +02:00
|
|
|
|
|
|
|
// mark the index as open
|
|
|
|
rv.mutex.Lock()
|
|
|
|
defer rv.mutex.Unlock()
|
|
|
|
rv.open = true
|
2014-08-20 22:58:20 +02:00
|
|
|
return &rv, nil
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func newIndex(path string, mapping *IndexMapping) (*indexImpl, error) {
|
2014-08-20 22:58:20 +02:00
|
|
|
// first validate the mapping
|
2014-08-30 06:06:16 +02:00
|
|
|
err := mapping.validate()
|
2014-08-14 03:14:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-08-20 22:58:20 +02:00
|
|
|
if path == "" {
|
|
|
|
return newMemIndex(mapping)
|
|
|
|
}
|
|
|
|
|
|
|
|
rv := indexImpl{
|
2014-10-02 20:12:22 +02:00
|
|
|
path: path,
|
|
|
|
m: mapping,
|
|
|
|
meta: newIndexMeta(Config.DefaultKVStore),
|
|
|
|
stats: &IndexStat{},
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
storeConstructor := registry.KVStoreConstructorByName(rv.meta.Storage)
|
|
|
|
if storeConstructor == nil {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorUnknownStorageType
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
2014-12-18 18:43:12 +01:00
|
|
|
// at this point there is hope that we can be successful, so save index meta
|
2014-08-20 22:58:20 +02:00
|
|
|
err = rv.meta.Save(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
storeConfig := map[string]interface{}{
|
|
|
|
"path": indexStorePath(path),
|
|
|
|
"create_if_missing": true,
|
|
|
|
"error_if_exists": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
// now open the store
|
|
|
|
rv.s, err = storeConstructor(storeConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-18 18:43:12 +01:00
|
|
|
// open the index
|
2014-09-24 14:13:14 +02:00
|
|
|
rv.i = upside_down.NewUpsideDownCouch(rv.s, Config.analysisQueue)
|
2014-08-20 22:58:20 +02:00
|
|
|
err = rv.i.Open()
|
2014-07-30 18:30:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-02 20:12:22 +02:00
|
|
|
rv.stats.indexStat = rv.i.Stats()
|
2014-08-20 22:58:20 +02:00
|
|
|
|
|
|
|
// now persist the mapping
|
|
|
|
mappingBytes, err := json.Marshal(mapping)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = rv.i.SetInternal(mappingInternalKey, mappingBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-25 15:06:53 +02:00
|
|
|
|
|
|
|
// mark the index as open
|
|
|
|
rv.mutex.Lock()
|
|
|
|
defer rv.mutex.Unlock()
|
|
|
|
rv.open = true
|
2014-08-20 22:58:20 +02:00
|
|
|
return &rv, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func openIndex(path string) (*indexImpl, error) {
|
|
|
|
|
|
|
|
rv := indexImpl{
|
2014-10-02 20:12:22 +02:00
|
|
|
path: path,
|
|
|
|
stats: &IndexStat{},
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
var err error
|
2014-08-29 21:19:02 +02:00
|
|
|
rv.meta, err = openIndexMeta(path)
|
2014-08-20 22:58:20 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
storeConstructor := registry.KVStoreConstructorByName(rv.meta.Storage)
|
|
|
|
if storeConstructor == nil {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorUnknownStorageType
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
storeConfig := map[string]interface{}{
|
|
|
|
"path": indexStorePath(path),
|
|
|
|
"create_if_missing": false,
|
|
|
|
"error_if_exists": false,
|
|
|
|
}
|
|
|
|
|
|
|
|
// now open the store
|
|
|
|
rv.s, err = storeConstructor(storeConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-18 18:43:12 +01:00
|
|
|
// open the index
|
2014-09-24 14:13:14 +02:00
|
|
|
rv.i = upside_down.NewUpsideDownCouch(rv.s, Config.analysisQueue)
|
2014-08-20 22:58:20 +02:00
|
|
|
err = rv.i.Open()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-02 20:12:22 +02:00
|
|
|
rv.stats.indexStat = rv.i.Stats()
|
2014-08-20 22:58:20 +02:00
|
|
|
|
|
|
|
// now load the mapping
|
2014-10-31 14:40:23 +01:00
|
|
|
indexReader, err := rv.i.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-12 23:21:35 +02:00
|
|
|
defer indexReader.Close()
|
|
|
|
mappingBytes, err := indexReader.GetInternal(mappingInternalKey)
|
2014-07-30 18:30:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-20 22:58:20 +02:00
|
|
|
|
|
|
|
var im IndexMapping
|
|
|
|
err = json.Unmarshal(mappingBytes, &im)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-08-25 15:06:53 +02:00
|
|
|
// mark the index as open
|
|
|
|
rv.mutex.Lock()
|
|
|
|
defer rv.mutex.Unlock()
|
|
|
|
rv.open = true
|
|
|
|
|
2014-08-20 22:58:20 +02:00
|
|
|
// validate the mapping
|
2014-08-30 06:06:16 +02:00
|
|
|
err = im.validate()
|
2014-08-20 22:58:20 +02:00
|
|
|
if err != nil {
|
2014-08-25 15:06:53 +02:00
|
|
|
// note even if the mapping is invalid
|
|
|
|
// we still return an open usable index
|
|
|
|
return &rv, err
|
2014-08-20 22:58:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rv.m = &im
|
|
|
|
return &rv, nil
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Mapping returns the IndexMapping in use by this
|
|
|
|
// Index.
|
2014-08-25 15:06:53 +02:00
|
|
|
func (i *indexImpl) Mapping() *IndexMapping {
|
|
|
|
return i.m
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Index the object with the specified identifier.
|
|
|
|
// The IndexMapping for this index will determine
|
|
|
|
// how the object is indexed.
|
2014-08-11 18:47:29 +02:00
|
|
|
func (i *indexImpl) Index(id string, data interface{}) error {
|
2014-09-24 14:13:14 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
2014-08-25 15:06:53 +02:00
|
|
|
|
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
doc := document.NewDocument(id)
|
2014-08-30 06:06:16 +02:00
|
|
|
err := i.m.mapDocument(doc, data)
|
2014-07-30 18:30:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = i.i.Update(doc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Delete entries for the specified identifier from
|
|
|
|
// the index.
|
2014-08-11 18:47:29 +02:00
|
|
|
func (i *indexImpl) Delete(id string) error {
|
2014-09-24 14:13:14 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
2014-08-25 15:06:53 +02:00
|
|
|
|
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
err := i.i.Delete(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Batch executes multiple Index and Delete
|
|
|
|
// operations at the same time. There are often
|
|
|
|
// significant performance benefits when performing
|
|
|
|
// operations in a batch.
|
2014-10-31 14:40:23 +01:00
|
|
|
func (i *indexImpl) Batch(b *Batch) error {
|
2014-09-24 14:13:14 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
2014-08-25 15:06:53 +02:00
|
|
|
|
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-10-31 14:40:23 +01:00
|
|
|
ib := index.NewBatch()
|
2014-11-25 17:11:28 +01:00
|
|
|
for bk, bd := range b.indexOps {
|
2014-08-11 22:27:18 +02:00
|
|
|
if bd == nil {
|
|
|
|
ib.Delete(bk)
|
|
|
|
} else {
|
|
|
|
doc := document.NewDocument(bk)
|
2014-08-30 06:06:16 +02:00
|
|
|
err := i.m.mapDocument(doc, bd)
|
2014-08-11 22:27:18 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-10-31 14:40:23 +01:00
|
|
|
ib.Update(doc)
|
|
|
|
}
|
|
|
|
}
|
2014-11-25 17:11:28 +01:00
|
|
|
for ik, iv := range b.internalOps {
|
2014-10-31 14:40:23 +01:00
|
|
|
if iv == nil {
|
|
|
|
ib.DeleteInternal([]byte(ik))
|
|
|
|
} else {
|
|
|
|
ib.SetInternal([]byte(ik), iv)
|
2014-08-11 22:27:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return i.i.Batch(ib)
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Document is used to find the values of all the
|
|
|
|
// stored fields for a document in the index. These
|
|
|
|
// stored fields are put back into a Document object
|
|
|
|
// and returned.
|
2014-07-30 18:30:38 +02:00
|
|
|
func (i *indexImpl) Document(id string) (*document.Document, error) {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
2014-10-31 14:40:23 +01:00
|
|
|
indexReader, err := i.i.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-12 23:21:35 +02:00
|
|
|
defer indexReader.Close()
|
|
|
|
return indexReader.Document(id)
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// DocCount returns the number of documents in the
|
|
|
|
// index.
|
2014-10-31 14:40:23 +01:00
|
|
|
func (i *indexImpl) DocCount() (uint64, error) {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
2014-10-31 14:40:23 +01:00
|
|
|
return 0, ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
return i.i.DocCount()
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Search executes a search request operation.
|
|
|
|
// Returns a SearchResult object or an error.
|
2014-07-30 18:30:38 +02:00
|
|
|
func (i *indexImpl) Search(req *SearchRequest) (*SearchResult, error) {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
2014-10-02 20:12:22 +02:00
|
|
|
searchStart := time.Now()
|
|
|
|
|
2014-08-25 15:06:53 +02:00
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-09-01 17:15:38 +02:00
|
|
|
collector := collectors.NewTopScorerSkipCollector(req.Size, req.From)
|
2014-09-12 23:21:35 +02:00
|
|
|
|
|
|
|
// open a reader for this search
|
2014-10-31 14:40:23 +01:00
|
|
|
indexReader, err := i.i.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-12 23:21:35 +02:00
|
|
|
defer indexReader.Close()
|
|
|
|
|
|
|
|
searcher, err := req.Query.Searcher(indexReader, i.m, req.Explain)
|
2014-07-30 18:30:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-25 21:13:15 +02:00
|
|
|
defer searcher.Close()
|
2014-08-11 17:03:29 +02:00
|
|
|
|
|
|
|
if req.Facets != nil {
|
2014-09-12 23:21:35 +02:00
|
|
|
facetsBuilder := search.NewFacetsBuilder(indexReader)
|
2014-08-11 17:03:29 +02:00
|
|
|
for facetName, facetRequest := range req.Facets {
|
|
|
|
if facetRequest.NumericRanges != nil {
|
|
|
|
// build numeric range facet
|
2014-09-01 17:15:38 +02:00
|
|
|
facetBuilder := facets.NewNumericFacetBuilder(facetRequest.Field, facetRequest.Size)
|
2014-08-11 17:03:29 +02:00
|
|
|
for _, nr := range facetRequest.NumericRanges {
|
|
|
|
facetBuilder.AddRange(nr.Name, nr.Min, nr.Max)
|
|
|
|
}
|
|
|
|
facetsBuilder.Add(facetName, facetBuilder)
|
|
|
|
} else if facetRequest.DateTimeRanges != nil {
|
|
|
|
// build date range facet
|
2014-09-01 17:15:38 +02:00
|
|
|
facetBuilder := facets.NewDateTimeFacetBuilder(facetRequest.Field, facetRequest.Size)
|
2014-08-30 05:50:47 +02:00
|
|
|
dateTimeParser := i.m.dateTimeParserNamed(i.m.DefaultDateTimeParser)
|
2014-08-11 17:03:29 +02:00
|
|
|
for _, dr := range facetRequest.DateTimeRanges {
|
2014-08-14 03:14:47 +02:00
|
|
|
dr.ParseDates(dateTimeParser)
|
2014-08-11 17:03:29 +02:00
|
|
|
facetBuilder.AddRange(dr.Name, dr.Start, dr.End)
|
|
|
|
}
|
|
|
|
facetsBuilder.Add(facetName, facetBuilder)
|
|
|
|
} else {
|
|
|
|
// build terms facet
|
2014-09-01 17:15:38 +02:00
|
|
|
facetBuilder := facets.NewTermsFacetBuilder(facetRequest.Field, facetRequest.Size)
|
2014-08-11 17:03:29 +02:00
|
|
|
facetsBuilder.Add(facetName, facetBuilder)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
collector.SetFacetsBuilder(facetsBuilder)
|
|
|
|
}
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
err = collector.Collect(searcher)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hits := collector.Results()
|
|
|
|
|
|
|
|
if req.Highlight != nil {
|
|
|
|
// get the right highlighter
|
2014-09-01 17:15:38 +02:00
|
|
|
highlighter, err := Config.Cache.HighlighterNamed(Config.DefaultHighlighter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-30 18:30:38 +02:00
|
|
|
if req.Highlight.Style != nil {
|
2014-09-01 17:15:38 +02:00
|
|
|
highlighter, err = Config.Cache.HighlighterNamed(*req.Highlight.Style)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
}
|
2014-09-01 17:15:38 +02:00
|
|
|
if highlighter == nil {
|
|
|
|
return nil, fmt.Errorf("no highlighter named `%s` registered", *req.Highlight.Style)
|
|
|
|
}
|
2014-07-30 18:30:38 +02:00
|
|
|
|
|
|
|
for _, hit := range hits {
|
2014-09-12 23:21:35 +02:00
|
|
|
doc, err := indexReader.Document(hit.ID)
|
2014-07-30 18:30:38 +02:00
|
|
|
if err == nil {
|
|
|
|
highlightFields := req.Highlight.Fields
|
|
|
|
if highlightFields == nil {
|
|
|
|
// add all fields with matches
|
|
|
|
highlightFields = make([]string, 0, len(hit.Locations))
|
2014-09-02 23:40:46 +02:00
|
|
|
for k := range hit.Locations {
|
2014-07-30 18:30:38 +02:00
|
|
|
highlightFields = append(highlightFields, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, hf := range highlightFields {
|
2014-08-28 20:45:51 +02:00
|
|
|
highlighter.BestFragmentsInField(hit, doc, hf, 1)
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-06 19:52:20 +02:00
|
|
|
if len(req.Fields) > 0 {
|
|
|
|
for _, hit := range hits {
|
|
|
|
// FIXME avoid loading doc second time
|
|
|
|
// if we already loaded it for highlighting
|
2014-09-12 23:21:35 +02:00
|
|
|
doc, err := indexReader.Document(hit.ID)
|
2014-08-06 19:52:20 +02:00
|
|
|
if err == nil {
|
|
|
|
for _, f := range req.Fields {
|
|
|
|
for _, docF := range doc.Fields {
|
2014-10-16 01:16:16 +02:00
|
|
|
if f == "*" || docF.Name() == f {
|
2014-08-06 19:52:20 +02:00
|
|
|
var value interface{}
|
|
|
|
switch docF := docF.(type) {
|
|
|
|
case *document.TextField:
|
|
|
|
value = string(docF.Value())
|
|
|
|
case *document.NumericField:
|
|
|
|
num, err := docF.Number()
|
|
|
|
if err == nil {
|
|
|
|
value = num
|
|
|
|
}
|
|
|
|
case *document.DateTimeField:
|
|
|
|
datetime, err := docF.DateTime()
|
|
|
|
if err == nil {
|
|
|
|
value = datetime.Format(time.RFC3339)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if value != nil {
|
2014-10-16 01:16:16 +02:00
|
|
|
hit.AddFieldValue(docF.Name(), value)
|
2014-08-06 19:52:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-02 20:12:22 +02:00
|
|
|
atomic.AddUint64(&i.stats.searches, 1)
|
|
|
|
atomic.AddUint64(&i.stats.searchTime, uint64(time.Since(searchStart)))
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
return &SearchResult{
|
|
|
|
Request: req,
|
|
|
|
Hits: hits,
|
|
|
|
Total: collector.Total(),
|
|
|
|
MaxScore: collector.MaxScore(),
|
|
|
|
Took: collector.Took(),
|
2014-08-11 17:03:29 +02:00
|
|
|
Facets: collector.FacetResults(),
|
2014-07-30 18:30:38 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Fields returns the name of all the fields this
|
|
|
|
// Index has operated on.
|
|
|
|
func (i *indexImpl) Fields() ([]string, error) {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
2014-09-02 20:14:05 +02:00
|
|
|
return nil, ErrorIndexClosed
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
2014-09-12 23:21:35 +02:00
|
|
|
|
2014-10-31 14:40:23 +01:00
|
|
|
indexReader, err := i.i.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-09-12 23:21:35 +02:00
|
|
|
defer indexReader.Close()
|
|
|
|
return indexReader.Fields()
|
2014-07-30 20:29:26 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// DumpAll writes all index rows to a channel.
|
|
|
|
// INTERNAL: do not rely on this function, it is
|
2014-12-18 18:43:12 +01:00
|
|
|
// only intended to be used by the debug utilities
|
2014-08-31 16:55:22 +02:00
|
|
|
func (i *indexImpl) DumpAll() chan interface{} {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
2014-08-31 16:55:22 +02:00
|
|
|
return nil
|
2014-08-25 15:06:53 +02:00
|
|
|
}
|
2014-08-31 16:55:22 +02:00
|
|
|
|
|
|
|
return i.i.DumpAll()
|
2014-07-31 17:47:36 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// DumpFields writes all field rows in the index
|
|
|
|
// to a channel.
|
|
|
|
// INTERNAL: do not rely on this function, it is
|
2014-12-18 18:43:12 +01:00
|
|
|
// only intended to be used by the debug utilities
|
2014-08-15 19:12:55 +02:00
|
|
|
func (i *indexImpl) DumpFields() chan interface{} {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
|
|
|
return nil
|
|
|
|
}
|
2014-08-15 19:12:55 +02:00
|
|
|
return i.i.DumpFields()
|
2014-07-30 20:29:26 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// DumpDoc writes all rows in the index associated
|
|
|
|
// with the specified identifier to a channel.
|
|
|
|
// INTERNAL: do not rely on this function, it is
|
2014-12-18 18:43:12 +01:00
|
|
|
// only intended to be used by the debug utilities
|
2014-08-15 19:12:55 +02:00
|
|
|
func (i *indexImpl) DumpDoc(id string) chan interface{} {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
if !i.open {
|
|
|
|
return nil
|
|
|
|
}
|
2014-07-30 18:30:38 +02:00
|
|
|
return i.i.DumpDoc(id)
|
|
|
|
}
|
|
|
|
|
2014-10-31 14:40:23 +01:00
|
|
|
func (i *indexImpl) Close() error {
|
2014-08-25 15:06:53 +02:00
|
|
|
i.mutex.Lock()
|
|
|
|
defer i.mutex.Unlock()
|
|
|
|
|
|
|
|
i.open = false
|
2014-10-31 14:40:23 +01:00
|
|
|
return i.i.Close()
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
2014-10-02 20:12:22 +02:00
|
|
|
|
|
|
|
func (i *indexImpl) Stats() *IndexStat {
|
|
|
|
return i.stats
|
|
|
|
}
|
2014-10-22 22:03:55 +02:00
|
|
|
|
|
|
|
func (i *indexImpl) GetInternal(key []byte) ([]byte, error) {
|
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
2014-10-31 14:40:23 +01:00
|
|
|
reader, err := i.i.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-22 22:03:55 +02:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
return reader.GetInternal(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *indexImpl) SetInternal(key, val []byte) error {
|
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
return i.i.SetInternal(key, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *indexImpl) DeleteInternal(key []byte) error {
|
|
|
|
i.mutex.RLock()
|
|
|
|
defer i.mutex.RUnlock()
|
|
|
|
|
|
|
|
return i.i.DeleteInternal(key)
|
|
|
|
}
|