Files
grafana/pkg/storage/unified/search/bleve_mappings.go
Rafael Bortolon Paulovic 987c1fc6b6 feat(unified): add index scoring model config (#116210)
* feat(unified): add bm25 index scoring model

We want try BM25 scoring model since they have global scoring which we can probably re-use for fan-in/fan-out logic

https://github.com/blevesearch/bleve/blob/32d98823c4b7482c62cc6c847508ed7659c23c37/docs/scoring.md#global-scoring

* fix(plugins): update plugin test data
2026-01-14 12:07:53 +01:00

172 lines
5.6 KiB
Go

package search
import (
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
"github.com/blevesearch/bleve/v2/analysis/analyzer/standard"
"github.com/blevesearch/bleve/v2/mapping"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func GetBleveMappings(scoringModel string, fields resource.SearchableDocumentFields) (mapping.IndexMapping, error) {
mapper := bleve.NewIndexMapping()
if scoringModel != "" {
mapper.ScoringModel = scoringModel
}
err := RegisterCustomAnalyzers(mapper)
if err != nil {
return nil, err
}
mapper.DefaultMapping = getBleveDocMappings(fields)
return mapper, nil
}
func getBleveDocMappings(fields resource.SearchableDocumentFields) *mapping.DocumentMapping {
mapper := bleve.NewDocumentStaticMapping()
nameMapping := &mapping.FieldMapping{
Analyzer: keyword.Name,
Type: "text",
Index: true,
}
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_NAME, nameMapping)
// for sorting by title full phrase
titlePhraseMapping := bleve.NewKeywordFieldMapping()
titlePhraseMapping.Store = false // already stored in title
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_PHRASE, titlePhraseMapping)
// for searching by title - uses an edge ngram token filter
titleSearchMapping := bleve.NewTextFieldMapping()
titleSearchMapping.Analyzer = TITLE_ANALYZER
titleSearchMapping.Store = false // already stored in title
// mapping for title to search on words/tokens larger than the ngram size
titleWordMapping := bleve.NewTextFieldMapping()
titleWordMapping.Analyzer = standard.Name
titleWordMapping.Store = true
// NOTE: this causes 3 title fields in the response
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE, titleWordMapping, titleSearchMapping, titlePhraseMapping)
descriptionMapping := &mapping.FieldMapping{
Name: resource.SEARCH_FIELD_DESCRIPTION,
Type: "text",
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: false,
DocValues: false,
}
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_DESCRIPTION, descriptionMapping)
tagsMapping := &mapping.FieldMapping{
Name: resource.SEARCH_FIELD_TAGS,
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
DocValues: false,
}
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TAGS, tagsMapping)
folderMapping := &mapping.FieldMapping{
Name: resource.SEARCH_FIELD_FOLDER,
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
DocValues: true, // will be needed for authz client
}
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_FOLDER, folderMapping)
// Repositories
manager := bleve.NewDocumentStaticMapping()
manager.AddFieldMappingsAt("kind", &mapping.FieldMapping{
Name: "kind",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
manager.AddFieldMappingsAt("id", &mapping.FieldMapping{
Name: "id",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
source := bleve.NewDocumentStaticMapping()
source.AddFieldMappingsAt("path", &mapping.FieldMapping{
Name: "path",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
source.AddFieldMappingsAt("checksum", &mapping.FieldMapping{
Name: "checksum",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
source.AddFieldMappingsAt("timestampMillis", mapping.NewNumericFieldMapping())
mapper.AddSubDocumentMapping("source", source)
mapper.AddSubDocumentMapping("manager", manager)
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_MANAGED_BY, &mapping.FieldMapping{
Name: "managedBy",
Type: "text",
Analyzer: keyword.Name,
Index: true, // only used for faceting
Store: false,
IncludeTermVectors: false,
IncludeInAll: false,
})
referenceMapper := bleve.NewDocumentMapping()
referenceMapper.DefaultAnalyzer = keyword.Name
mapper.AddSubDocumentMapping("reference", referenceMapper)
labelMapper := bleve.NewDocumentMapping()
mapper.AddSubDocumentMapping(resource.SEARCH_FIELD_LABELS, labelMapper)
fieldMapper := bleve.NewDocumentMapping()
if fields != nil {
for _, field := range fields.Fields() {
def := fields.Field(field)
// Filterable should use keyword analyzer for exact matches
if def.Properties != nil && def.Properties.Filterable && def.Type == resourcepb.ResourceTableColumnDefinition_STRING {
keywordMapping := bleve.NewKeywordFieldMapping()
keywordMapping.Store = true
fieldMapper.AddFieldMappingsAt(def.Name, keywordMapping)
}
// For all other fields, we do nothing.
// Bleve will see them at index time and dynamically map them as
// numeric, datetime, boolean, or standard text based on their content.
}
}
mapper.AddSubDocumentMapping("fields", fieldMapper)
return mapper
}