// Search executes a search request operation. // Returns a SearchResult object or an error. func (i *indexImpl) Search(req *SearchRequest) (sr *SearchResult, err error) { i.mutex.RLock() defer i.mutex.RUnlock() searchStart := time.Now() if !i.open { return nil, ErrorIndexClosed } collector := collectors.NewTopScorerSkipCollector(req.Size, req.From) // open a reader for this search indexReader, err := i.i.Reader() if err != nil { return nil, fmt.Errorf("error opening index reader %v", err) } defer func() { if cerr := indexReader.Close(); err == nil && cerr != nil { err = cerr } }() searcher, err := req.Query.Searcher(indexReader, i.m, req.Explain) if err != nil { return nil, err } defer func() { if serr := searcher.Close(); err == nil && serr != nil { err = serr } }() if req.Facets != nil { facetsBuilder := search.NewFacetsBuilder(indexReader) for facetName, facetRequest := range req.Facets { if facetRequest.NumericRanges != nil { // build numeric range facet facetBuilder := facets.NewNumericFacetBuilder(facetRequest.Field, facetRequest.Size) 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 facetBuilder := facets.NewDateTimeFacetBuilder(facetRequest.Field, facetRequest.Size) dateTimeParser := i.m.dateTimeParserNamed(i.m.DefaultDateTimeParser) for _, dr := range facetRequest.DateTimeRanges { dr.ParseDates(dateTimeParser) facetBuilder.AddRange(dr.Name, dr.Start, dr.End) } facetsBuilder.Add(facetName, facetBuilder) } else { // build terms facet facetBuilder := facets.NewTermsFacetBuilder(facetRequest.Field, facetRequest.Size) facetsBuilder.Add(facetName, facetBuilder) } } collector.SetFacetsBuilder(facetsBuilder) } err = collector.Collect(searcher) if err != nil { return nil, err } hits := collector.Results() if req.Highlight != nil { // get the right highlighter highlighter, err := Config.Cache.HighlighterNamed(Config.DefaultHighlighter) if err != nil { return nil, err } if req.Highlight.Style != nil { highlighter, err = Config.Cache.HighlighterNamed(*req.Highlight.Style) if err != nil { return nil, err } } if highlighter == nil { return nil, fmt.Errorf("no highlighter named `%s` registered", *req.Highlight.Style) } for _, hit := range hits { doc, err := indexReader.Document(hit.ID) if err == nil { highlightFields := req.Highlight.Fields if highlightFields == nil { // add all fields with matches highlightFields = make([]string, 0, len(hit.Locations)) for k := range hit.Locations { highlightFields = append(highlightFields, k) } } for _, hf := range highlightFields { highlighter.BestFragmentsInField(hit, doc, hf, 1) } } } } if len(req.Fields) > 0 { for _, hit := range hits { // FIXME avoid loading doc second time // if we already loaded it for highlighting doc, err := indexReader.Document(hit.ID) if err == nil { for _, f := range req.Fields { for _, docF := range doc.Fields { if f == "*" || docF.Name() == f { 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 { hit.AddFieldValue(docF.Name(), value) } } } } } } } atomic.AddUint64(&i.stats.searches, 1) searchDuration := time.Since(searchStart) atomic.AddUint64(&i.stats.searchTime, uint64(searchDuration)) if searchDuration > Config.SlowSearchLogThreshold { logger.Printf("slow search took %s - %v", searchDuration, req) } return &SearchResult{ Request: req, Hits: hits, Total: collector.Total(), MaxScore: collector.MaxScore(), Took: searchDuration, Facets: collector.FacetResults(), }, nil }
// SearchInContext executes a search request operation within the provided // Context. Returns a SearchResult object or an error. func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr *SearchResult, err error) { i.mutex.RLock() defer i.mutex.RUnlock() searchStart := time.Now() if !i.open { return nil, ErrorIndexClosed } collector := collector.NewTopNCollector(req.Size, req.From, req.Sort) // open a reader for this search indexReader, err := i.i.Reader() if err != nil { return nil, fmt.Errorf("error opening index reader %v", err) } defer func() { if cerr := indexReader.Close(); err == nil && cerr != nil { err = cerr } }() searcher, err := req.Query.Searcher(indexReader, i.m, req.Explain) if err != nil { return nil, err } defer func() { if serr := searcher.Close(); err == nil && serr != nil { err = serr } }() if req.Facets != nil { facetsBuilder := search.NewFacetsBuilder(indexReader) for facetName, facetRequest := range req.Facets { if facetRequest.NumericRanges != nil { // build numeric range facet facetBuilder := facet.NewNumericFacetBuilder(facetRequest.Field, facetRequest.Size) 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 facetBuilder := facet.NewDateTimeFacetBuilder(facetRequest.Field, facetRequest.Size) dateTimeParser := i.m.DateTimeParserNamed("") for _, dr := range facetRequest.DateTimeRanges { dr.ParseDates(dateTimeParser) facetBuilder.AddRange(dr.Name, dr.Start, dr.End) } facetsBuilder.Add(facetName, facetBuilder) } else { // build terms facet facetBuilder := facet.NewTermsFacetBuilder(facetRequest.Field, facetRequest.Size) facetsBuilder.Add(facetName, facetBuilder) } } collector.SetFacetsBuilder(facetsBuilder) } err = collector.Collect(ctx, searcher, indexReader) if err != nil { return nil, err } hits := collector.Results() var highlighter highlight.Highlighter if req.Highlight != nil { // get the right highlighter highlighter, err = Config.Cache.HighlighterNamed(Config.DefaultHighlighter) if err != nil { return nil, err } if req.Highlight.Style != nil { highlighter, err = Config.Cache.HighlighterNamed(*req.Highlight.Style) if err != nil { return nil, err } } if highlighter == nil { return nil, fmt.Errorf("no highlighter named `%s` registered", *req.Highlight.Style) } } for _, hit := range hits { if len(req.Fields) > 0 || highlighter != nil { doc, err := indexReader.Document(hit.ID) if err == nil && doc != nil { if len(req.Fields) > 0 { for _, f := range req.Fields { for _, docF := range doc.Fields { if f == "*" || docF.Name() == f { 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) } case *document.BooleanField: boolean, err := docF.Boolean() if err == nil { value = boolean } } if value != nil { hit.AddFieldValue(docF.Name(), value) } } } } } if highlighter != nil { highlightFields := req.Highlight.Fields if highlightFields == nil { // add all fields with matches highlightFields = make([]string, 0, len(hit.Locations)) for k := range hit.Locations { highlightFields = append(highlightFields, k) } } for _, hf := range highlightFields { highlighter.BestFragmentsInField(hit, doc, hf, 1) } } } else if doc == nil { // unexpected case, a doc ID that was found as a search hit // was unable to be found during document lookup return nil, ErrorIndexReadInconsistency } } if i.name != "" { hit.Index = i.name } } atomic.AddUint64(&i.stats.searches, 1) searchDuration := time.Since(searchStart) atomic.AddUint64(&i.stats.searchTime, uint64(searchDuration)) if searchDuration > Config.SlowSearchLogThreshold { logger.Printf("slow search took %s - %v", searchDuration, req) } return &SearchResult{ Status: &SearchStatus{ Total: 1, Failed: 0, Successful: 1, Errors: make(map[string]error), }, Request: req, Hits: hits, Total: collector.Total(), MaxScore: collector.MaxScore(), Took: searchDuration, Facets: collector.FacetResults(), }, nil }