in eval.go [76:274]
func (d *indexData) Search(ctx context.Context, q query.Q, opts *SearchOptions) (sr *SearchResult, err error) {
copyOpts := *opts
opts = ©Opts
opts.SetDefaults()
importantMatchCount := 0
var res SearchResult
if len(d.fileNameIndex) == 0 {
return &res, nil
}
select {
case <-ctx.Done():
res.Stats.ShardsSkipped++
return &res, nil
default:
}
tr := trace.New("indexData.Search", d.file.Name())
tr.LazyPrintf("opts: %+v", opts)
defer func() {
if sr != nil {
tr.LazyPrintf("num files: %d", len(sr.Files))
tr.LazyPrintf("stats: %+v", sr.Stats)
}
if err != nil {
tr.LazyPrintf("error: %v", err)
tr.SetError()
}
tr.Finish()
}()
q = d.simplify(q)
tr.LazyLog(q, true)
if c, ok := q.(*query.Const); ok && !c.Value {
return &res, nil
}
if opts.EstimateDocCount {
res.Stats.ShardFilesConsidered = len(d.fileBranchMasks)
return &res, nil
}
q = query.Map(q, query.ExpandFileContent)
mt, err := d.newMatchTree(q)
if err != nil {
return nil, err
}
totalAtomCount := 0
visitMatchTree(mt, func(t matchTree) {
totalAtomCount++
})
cp := &contentProvider{
id: d,
stats: &res.Stats,
}
docCount := uint32(len(d.fileBranchMasks))
lastDoc := int(-1)
nextFileMatch:
for {
canceled := false
select {
case <-ctx.Done():
canceled = true
default:
}
nextDoc := mt.nextDoc()
if int(nextDoc) <= lastDoc {
nextDoc = uint32(lastDoc + 1)
}
if nextDoc >= docCount {
break
}
lastDoc = int(nextDoc)
if canceled || (res.Stats.MatchCount >= opts.ShardMaxMatchCount && opts.ShardMaxMatchCount > 0) ||
(opts.ShardMaxImportantMatch > 0 && importantMatchCount >= opts.ShardMaxImportantMatch) {
res.Stats.FilesSkipped += d.repoListEntry.Stats.Documents - lastDoc
break
}
res.Stats.FilesConsidered++
mt.prepare(nextDoc)
cp.setDocument(nextDoc)
known := make(map[matchTree]bool)
for cost := costMin; cost <= costMax; cost++ {
v, ok := mt.matches(cp, cost, known)
if ok && !v {
continue nextFileMatch
}
if cost == costMax && !ok {
log.Panicf("did not decide. Repo %s, doc %d, known %v",
d.repoMetaData.Name, nextDoc, known)
}
}
fileMatch := FileMatch{
Repository: d.repoMetaData.Name,
FileName: string(d.fileName(nextDoc)),
Checksum: d.getChecksum(nextDoc),
Language: d.languageMap[d.languages[nextDoc]],
}
if s := d.subRepos[nextDoc]; s > 0 {
if s >= uint32(len(d.subRepoPaths)) {
log.Panicf("corrupt index: subrepo %d beyond %v", s, d.subRepoPaths)
}
path := d.subRepoPaths[s]
fileMatch.SubRepositoryPath = path
sr := d.repoMetaData.SubRepoMap[path]
fileMatch.SubRepositoryName = sr.Name
if idx := d.branchIndex(nextDoc); idx >= 0 {
fileMatch.Version = sr.Branches[idx].Version
}
} else {
idx := d.branchIndex(nextDoc)
if idx >= 0 {
fileMatch.Version = d.repoMetaData.Branches[idx].Version
}
}
atomMatchCount := 0
visitMatches(mt, known, func(mt matchTree) {
atomMatchCount++
})
finalCands := gatherMatches(mt, known)
if len(finalCands) == 0 {
nm := d.fileName(nextDoc)
finalCands = append(finalCands,
&candidateMatch{
caseSensitive: false,
fileName: true,
substrBytes: nm,
substrLowered: nm,
file: nextDoc,
runeOffset: 0,
byteOffset: 0,
byteMatchSz: uint32(len(nm)),
})
}
fileMatch.LineMatches = cp.fillMatches(finalCands)
maxFileScore := 0.0
for i := range fileMatch.LineMatches {
if maxFileScore < fileMatch.LineMatches[i].Score {
maxFileScore = fileMatch.LineMatches[i].Score
}
// Order by ordering in file.
fileMatch.LineMatches[i].Score += scoreLineOrderFactor * (1.0 - (float64(i) / float64(len(fileMatch.LineMatches))))
}
// Maintain ordering of input files. This
// strictly dominates the in-file ordering of
// the matches.
fileMatch.addScore("fragment", maxFileScore)
fileMatch.addScore("atom", float64(atomMatchCount)/float64(totalAtomCount)*scoreFactorAtomMatch)
// Prefer earlier docs.
fileMatch.addScore("doc-order", scoreFileOrderFactor*(1.0-float64(nextDoc)/float64(len(d.boundaries))))
fileMatch.addScore("shard-order", scoreShardRankFactor*float64(d.repoMetaData.Rank)/maxUInt16)
if fileMatch.Score > scoreImportantThreshold {
importantMatchCount++
}
fileMatch.Branches = d.gatherBranches(nextDoc, mt, known)
sortMatchesByScore(fileMatch.LineMatches)
if opts.Whole {
fileMatch.Content = cp.data(false)
}
res.Files = append(res.Files, fileMatch)
res.Stats.MatchCount += len(fileMatch.LineMatches)
res.Stats.FileCount++
}
SortFilesByScore(res.Files)
addRepo(&res, &d.repoMetaData)
for _, v := range d.repoMetaData.SubRepoMap {
addRepo(&res, v)
}
visitMatchTree(mt, func(mt matchTree) {
if atom, ok := mt.(interface{ updateStats(*Stats) }); ok {
atom.updateStats(&res.Stats)
}
})
return &res, nil
}