build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.4 to 2.5.5

Bumps [github.com/blevesearch/bleve/v2](https://github.com/blevesearch/bleve) from 2.5.4 to 2.5.5.
- [Release notes](https://github.com/blevesearch/bleve/releases)
- [Commits](https://github.com/blevesearch/bleve/compare/v2.5.4...v2.5.5)

---
updated-dependencies:
- dependency-name: github.com/blevesearch/bleve/v2
  dependency-version: 2.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-11-20 17:41:01 +01:00
committed by Ralf Haferkamp
parent e85d8effc1
commit 4ae0951f5f
16 changed files with 582 additions and 79 deletions
+2 -2
View File
@@ -82,8 +82,8 @@ func scoreSortFunc() func(i, j *search.DocumentMatch) int {
func getFusionExplAt(hit *search.DocumentMatch, i int, value float64, message string) *search.Explanation {
return &search.Explanation{
Value: value,
Message: message,
Value: value,
Message: message,
Children: []*search.Explanation{hit.Expl.Children[i]},
}
}
+8
View File
@@ -388,3 +388,11 @@ type SynonymIndex interface {
// IndexSynonym indexes a synonym definition, with the specified id and belonging to the specified collection.
IndexSynonym(id string, collection string, definition *SynonymDefinition) error
}
type InsightsIndex interface {
Index
// TermFrequencies returns the tokens ordered by frequencies for the field index.
TermFrequencies(field string, limit int, descending bool) ([]index.TermFreq, error)
// CentroidCardinalities returns the centroids (clusters) from IVF indexes ordered by data density.
CentroidCardinalities(field string, limit int, desceding bool) ([]index.CentroidCardinality, error)
}
+59
View File
@@ -23,6 +23,7 @@ import (
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
@@ -1234,3 +1235,61 @@ func (is *IndexSnapshot) MergeUpdateFieldsInfo(updatedFields map[string]*index.U
}
}
}
// TermFrequencies returns the top N terms ordered by the frequencies
// for a given field across all segments in the index snapshot.
func (is *IndexSnapshot) TermFrequencies(field string, limit int, descending bool) (
termFreqs []index.TermFreq, err error) {
if len(is.segment) == 0 {
return nil, nil
}
if limit <= 0 {
return nil, fmt.Errorf("limit must be positive")
}
// Use FieldDict which aggregates term frequencies across all segments
fieldDict, err := is.FieldDict(field)
if err != nil {
return nil, fmt.Errorf("failed to get field dictionary for field %s: %v", field, err)
}
defer fieldDict.Close()
// Preallocate slice with capacity equal to the number of unique terms
// in the field dictionary
termFreqs = make([]index.TermFreq, 0, fieldDict.Cardinality())
// Iterate through all terms using FieldDict
for {
dictEntry, err := fieldDict.Next()
if err != nil {
return nil, fmt.Errorf("error iterating field dictionary: %v", err)
}
if dictEntry == nil {
break // End of terms
}
termFreqs = append(termFreqs, index.TermFreq{
Term: dictEntry.Term,
Frequency: dictEntry.Count,
})
}
// Sort by frequency (descending or ascending)
sort.Slice(termFreqs, func(i, j int) bool {
if termFreqs[i].Frequency == termFreqs[j].Frequency {
// If frequencies are equal, sort by term lexicographically
return strings.Compare(termFreqs[i].Term, termFreqs[j].Term) < 0
}
if descending {
return termFreqs[i].Frequency > termFreqs[j].Frequency
}
return termFreqs[i].Frequency < termFreqs[j].Frequency
})
if limit >= len(termFreqs) {
return termFreqs, nil
}
return termFreqs[:limit], nil
}
@@ -23,6 +23,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"github.com/blevesearch/bleve/v2/size"
index "github.com/blevesearch/bleve_index_api"
@@ -167,3 +168,52 @@ func (i *IndexSnapshotVectorReader) Close() error {
// TODO Consider if any scope of recycling here.
return nil
}
func (i *IndexSnapshot) CentroidCardinalities(field string, limit int, descending bool) (
[]index.CentroidCardinality, error) {
if len(i.segment) == 0 {
return nil, nil
}
if limit <= 0 {
return nil, fmt.Errorf("limit must be positive")
}
centroids := make([]index.CentroidCardinality, 0, limit*len(i.segment))
for _, segment := range i.segment {
if sv, ok := segment.segment.(segment_api.VectorSegment); ok {
vecIndex, err := sv.InterpretVectorIndex(field,
false /* does not require filtering */, segment.deleted)
if err != nil {
return nil, fmt.Errorf("failed to interpret vector index for field %s in segment: %v", field, err)
}
centroidCardinalities, err := vecIndex.ObtainKCentroidCardinalitiesFromIVFIndex(limit, descending)
if err != nil {
return nil, fmt.Errorf("failed to obtain top k centroid cardinalities for field %s in segment: %v", field, err)
}
if len(centroidCardinalities) > 0 {
centroids = append(centroids, centroidCardinalities...)
}
}
}
if len(centroids) == 0 {
return nil, nil
}
sort.Slice(centroids, func(i, j int) bool {
if descending {
return centroids[i].Cardinality > centroids[j].Cardinality
}
return centroids[i].Cardinality < centroids[j].Cardinality
})
if limit >= len(centroids) {
return centroids, nil
}
return centroids[:limit], nil
}
+158
View File
@@ -17,6 +17,8 @@ package bleve
import (
"context"
"fmt"
"sort"
"strings"
"sync"
"time"
@@ -1136,3 +1138,159 @@ func (f *indexAliasImplFieldDict) Close() error {
func (f *indexAliasImplFieldDict) Cardinality() int {
return f.fieldDict.Cardinality()
}
// -----------------------------------------------------------------------------
func (i *indexAliasImpl) TermFrequencies(field string, limit int, descending bool) (
[]index.TermFreq, error) {
i.mutex.RLock()
defer i.mutex.RUnlock()
if !i.open {
return nil, ErrorIndexClosed
}
if len(i.indexes) < 1 {
return nil, ErrorAliasEmpty
}
// short circuit the simple case
if len(i.indexes) == 1 {
if idx, ok := i.indexes[0].(InsightsIndex); ok {
return idx.TermFrequencies(field, limit, descending)
}
return nil, nil
}
// run search on each index in separate go routine
var waitGroup sync.WaitGroup
asyncResults := make(chan []index.TermFreq, len(i.indexes))
searchChildIndex := func(in Index, field string, limit int, descending bool) {
var rv []index.TermFreq
if idx, ok := in.(InsightsIndex); ok {
// over sample for higher accuracy
rv, _ = idx.TermFrequencies(field, limit*5, descending)
}
asyncResults <- rv
waitGroup.Done()
}
waitGroup.Add(len(i.indexes))
for _, in := range i.indexes {
go searchChildIndex(in, field, limit, descending)
}
// on another go routine, close after finished
go func() {
waitGroup.Wait()
close(asyncResults)
}()
rvTermFreqsMap := make(map[string]uint64)
for asr := range asyncResults {
for _, entry := range asr {
rvTermFreqsMap[entry.Term] += entry.Frequency
}
}
rvTermFreqs := make([]index.TermFreq, 0, len(rvTermFreqsMap))
for term, freq := range rvTermFreqsMap {
rvTermFreqs = append(rvTermFreqs, index.TermFreq{
Term: term,
Frequency: freq,
})
}
if descending {
sort.Slice(rvTermFreqs, func(i, j int) bool {
if rvTermFreqs[i].Frequency == rvTermFreqs[j].Frequency {
// If frequencies are equal, sort by term lexicographically
return strings.Compare(rvTermFreqs[i].Term, rvTermFreqs[j].Term) < 0
}
return rvTermFreqs[i].Frequency > rvTermFreqs[j].Frequency
})
} else {
sort.Slice(rvTermFreqs, func(i, j int) bool {
if rvTermFreqs[i].Frequency == rvTermFreqs[j].Frequency {
// If frequencies are equal, sort by term lexicographically
return strings.Compare(rvTermFreqs[i].Term, rvTermFreqs[j].Term) < 0
}
return rvTermFreqs[i].Frequency < rvTermFreqs[j].Frequency
})
}
if limit > len(rvTermFreqs) {
limit = len(rvTermFreqs)
}
return rvTermFreqs[:limit], nil
}
func (i *indexAliasImpl) CentroidCardinalities(field string, limit int, descending bool) (
[]index.CentroidCardinality, error) {
i.mutex.RLock()
defer i.mutex.RUnlock()
if !i.open {
return nil, ErrorIndexClosed
}
if len(i.indexes) < 1 {
return nil, ErrorAliasEmpty
}
// short circuit the simple case
if len(i.indexes) == 1 {
if idx, ok := i.indexes[0].(InsightsIndex); ok {
return idx.CentroidCardinalities(field, limit, descending)
}
return nil, nil
}
// run search on each index in separate go routine
var waitGroup sync.WaitGroup
asyncResults := make(chan []index.CentroidCardinality, len(i.indexes))
searchChildIndex := func(in Index, field string, limit int, descending bool) {
var rv []index.CentroidCardinality
if idx, ok := in.(InsightsIndex); ok {
rv, _ = idx.CentroidCardinalities(field, limit, descending)
}
asyncResults <- rv
waitGroup.Done()
}
waitGroup.Add(len(i.indexes))
for _, in := range i.indexes {
go searchChildIndex(in, field, limit, descending)
}
// on another go routine, close after finished
go func() {
waitGroup.Wait()
close(asyncResults)
}()
rvCentroidCardinalitiesResult := make([]index.CentroidCardinality, 0, limit)
for asr := range asyncResults {
asr = append(asr, rvCentroidCardinalitiesResult...)
if descending {
sort.Slice(asr, func(i, j int) bool {
return asr[i].Cardinality > asr[j].Cardinality
})
} else {
sort.Slice(asr, func(i, j int) bool {
return asr[i].Cardinality < asr[j].Cardinality
})
}
if limit > len(asr) {
limit = len(asr)
}
rvCentroidCardinalitiesResult = asr[:limit]
}
return rvCentroidCardinalitiesResult, nil
}
+120 -45
View File
@@ -57,8 +57,6 @@ type indexImpl struct {
const storePath = "store"
var mappingInternalKey = []byte("_mapping")
const (
SearchQueryStartCallbackKey search.ContextKey = "_search_query_start_callback_key"
SearchQueryEndCallbackKey search.ContextKey = "_search_query_end_callback_key"
@@ -641,8 +639,57 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
}
}
// ------------------------------------------------------------------------------------------
// set up additional contexts for any search operation that will proceed from
// here, such as presearch, collectors etc.
// Scoring model callback to be used to get scoring model
scoringModelCallback := func() string {
if isBM25Enabled(i.m) {
return index.BM25Scoring
}
return index.DefaultScoringModel
}
ctx = context.WithValue(ctx, search.GetScoringModelCallbackKey,
search.GetScoringModelCallbackFn(scoringModelCallback))
// This callback and variable handles the tracking of bytes read
// 1. as part of creation of tfr and its Next() calls which is
// accounted by invoking this callback when the TFR is closed.
// 2. the docvalues portion (accounted in collector) and the retrieval
// of stored fields bytes (by LoadAndHighlightFields)
var totalSearchCost uint64
sendBytesRead := func(bytesRead uint64) {
totalSearchCost += bytesRead
}
// Ensure IO cost accounting and result cost assignment happen on all return paths
defer func() {
if sr != nil {
sr.Cost = totalSearchCost
}
if is, ok := indexReader.(*scorch.IndexSnapshot); ok {
is.UpdateIOStats(totalSearchCost)
}
search.RecordSearchCost(ctx, search.DoneM, 0)
}()
ctx = context.WithValue(ctx, search.SearchIOStatsCallbackKey, search.SearchIOStatsCallbackFunc(sendBytesRead))
// Geo buffer pool callback to be used for getting geo buffer pool
var bufPool *s2.GeoBufferPool
getBufferPool := func() *s2.GeoBufferPool {
if bufPool == nil {
bufPool = s2.NewGeoBufferPool(search.MaxGeoBufPoolSize, search.MinGeoBufPoolSize)
}
return bufPool
}
ctx = context.WithValue(ctx, search.GeoBufferPoolCallbackKey, search.GeoBufferPoolCallbackFunc(getBufferPool))
// ------------------------------------------------------------------------------------------
if _, ok := ctx.Value(search.PreSearchKey).(bool); ok {
preSearchResult, err := i.preSearch(ctx, req, indexReader)
sr, err = i.preSearch(ctx, req, indexReader)
if err != nil {
return nil, err
}
@@ -656,7 +703,8 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
// time stat
searchDuration := time.Since(searchStart)
atomic.AddUint64(&i.stats.searchTime, uint64(searchDuration))
return preSearchResult, nil
return sr, nil
}
var reverseQueryExecution bool
@@ -726,6 +774,9 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
// if score fusion, run collect if rescorer is defined
if rescorer != nil && requestHasKNN(req) {
knnHits, err = i.runKnnCollector(ctx, req, indexReader, false)
if err != nil {
return nil, err
}
}
}
@@ -745,7 +796,6 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
if !contextScoreFusionKeyExists {
setKnnHitsInCollector(knnHits, req, coll)
}
if fts != nil {
if is, ok := indexReader.(*scorch.IndexSnapshot); ok {
@@ -754,44 +804,12 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
ctx = context.WithValue(ctx, search.FieldTermSynonymMapKey, fts)
}
scoringModelCallback := func() string {
if isBM25Enabled(i.m) {
return index.BM25Scoring
}
return index.DefaultScoringModel
}
ctx = context.WithValue(ctx, search.GetScoringModelCallbackKey,
search.GetScoringModelCallbackFn(scoringModelCallback))
// set the bm25Stats (stats important for consistent scoring) in
// the context object
if bm25Stats != nil {
ctx = context.WithValue(ctx, search.BM25StatsKey, bm25Stats)
}
// This callback and variable handles the tracking of bytes read
// 1. as part of creation of tfr and its Next() calls which is
// accounted by invoking this callback when the TFR is closed.
// 2. the docvalues portion (accounted in collector) and the retrieval
// of stored fields bytes (by LoadAndHighlightFields)
var totalSearchCost uint64
sendBytesRead := func(bytesRead uint64) {
totalSearchCost += bytesRead
}
ctx = context.WithValue(ctx, search.SearchIOStatsCallbackKey, search.SearchIOStatsCallbackFunc(sendBytesRead))
var bufPool *s2.GeoBufferPool
getBufferPool := func() *s2.GeoBufferPool {
if bufPool == nil {
bufPool = s2.NewGeoBufferPool(search.MaxGeoBufPoolSize, search.MinGeoBufPoolSize)
}
return bufPool
}
ctx = context.WithValue(ctx, search.GeoBufferPoolCallbackKey, search.GeoBufferPoolCallbackFunc(getBufferPool))
searcher, err := req.Query.Searcher(ctx, indexReader, i.m, search.SearcherOptions{
Explain: req.Explain,
IncludeTermVectors: req.IncludeLocations || req.Highlight != nil,
@@ -804,14 +822,6 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
if serr := searcher.Close(); err == nil && serr != nil {
err = serr
}
if sr != nil {
sr.Cost = totalSearchCost
}
if sr, ok := indexReader.(*scorch.IndexSnapshot); ok {
sr.UpdateIOStats(totalSearchCost)
}
search.RecordSearchCost(ctx, search.DoneM, 0)
}()
if req.Facets != nil {
@@ -1388,3 +1398,68 @@ func (i *indexImpl) FireIndexEvent() {
internalEventIndex.FireIndexEvent()
}
}
// -----------------------------------------------------------------------------
func (i *indexImpl) TermFrequencies(field string, limit int, descending bool) (
[]index.TermFreq, error) {
i.mutex.RLock()
defer i.mutex.RUnlock()
if !i.open {
return nil, ErrorIndexClosed
}
reader, err := i.i.Reader()
if err != nil {
return nil, err
}
defer func() {
if cerr := reader.Close(); err == nil && cerr != nil {
err = cerr
}
}()
insightsReader, ok := reader.(index.IndexInsightsReader)
if !ok {
return nil, fmt.Errorf("index reader does not support TermFrequencies")
}
return insightsReader.TermFrequencies(field, limit, descending)
}
func (i *indexImpl) CentroidCardinalities(field string, limit int, descending bool) (
[]index.CentroidCardinality, error) {
i.mutex.RLock()
defer i.mutex.RUnlock()
if !i.open {
return nil, ErrorIndexClosed
}
reader, err := i.i.Reader()
if err != nil {
return nil, err
}
defer func() {
if cerr := reader.Close(); err == nil && cerr != nil {
err = cerr
}
}()
insightsReader, ok := reader.(index.IndexInsightsReader)
if !ok {
return nil, fmt.Errorf("index reader does not support CentroidCardinalities")
}
centroidCardinalities, err := insightsReader.CentroidCardinalities(field, limit, descending)
if err != nil {
return nil, err
}
for j := 0; j < len(centroidCardinalities); j++ {
centroidCardinalities[j].Index = i.name
}
return centroidCardinalities, nil
}
-1
View File
@@ -755,4 +755,3 @@ func ParseParams(r *SearchRequest, input []byte) (*RequestParams, error) {
return params, nil
}
+29 -10
View File
@@ -185,17 +185,36 @@ func (q *BooleanQuery) Searcher(ctx context.Context, i index.IndexReader, m mapp
if err != nil {
return nil, err
}
var init bool
var refDoc *search.DocumentMatch
filterFunc = func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
// Attempt to advance the filter searcher to the document identified by
// the base searcher's (unfiltered boolean) current result (d.IndexInternalID).
//
// If the filter searcher successfully finds a document with the same
// internal ID, it means the document satisfies the filter and should be kept.
//
// If the filter searcher returns an error, does not find a matching document,
// or finds a document with a different internal ID, the document should be discarded.
dm, err := filterSearcher.Advance(sctx, d.IndexInternalID)
return err == nil && dm != nil && bytes.Equal(dm.IndexInternalID, d.IndexInternalID)
// Initialize the reference document to point
// to the first document in the filterSearcher
var err error
if !init {
refDoc, err = filterSearcher.Next(sctx)
if err != nil {
return false
}
init = true
}
if refDoc == nil {
// filterSearcher is exhausted, d is not in filter
return false
}
// Compare document IDs
cmp := bytes.Compare(refDoc.IndexInternalID, d.IndexInternalID)
if cmp < 0 {
// filterSearcher is behind the current document, Advance() it
refDoc, err = filterSearcher.Advance(sctx, d.IndexInternalID)
if err != nil || refDoc == nil {
return false
}
// After advance, check if they're now equal
return bytes.Equal(refDoc.IndexInternalID, d.IndexInternalID)
}
// cmp >= 0: either equal (match) or filterSearcher is ahead (no match)
return cmp == 0
}
}
+9 -1
View File
@@ -431,6 +431,10 @@ func expandQuery(m mapping.IndexMapping, query Query) (Query, error) {
if err != nil {
return nil, err
}
q.Filter, err = expand(q.Filter)
if err != nil {
return nil, err
}
return q, nil
default:
return query, nil
@@ -481,7 +485,7 @@ func ExtractFields(q Query, m mapping.IndexMapping, fs FieldSet) (FieldSet, erro
fs, err = ExtractFields(expandedQuery, m, fs)
}
case *BooleanQuery:
for _, subq := range []Query{q.Must, q.Should, q.MustNot} {
for _, subq := range []Query{q.Must, q.Should, q.MustNot, q.Filter} {
fs, err = ExtractFields(subq, m, fs)
if err != nil {
break
@@ -553,6 +557,10 @@ func ExtractSynonyms(ctx context.Context, m mapping.SynonymMapping, r index.Thes
if err != nil {
return nil, err
}
rv, err = ExtractSynonyms(ctx, m, r, q.Filter, rv)
if err != nil {
return nil, err
}
case *ConjunctionQuery:
for _, child := range q.Conjuncts {
rv, err = ExtractSynonyms(ctx, m, r, child, rv)