in styles/lunr.js [1752:1910]
lunr.Index.prototype.query = function (fn) {
// for each query clause
// * process terms
// * expand terms from token set
// * find matching documents and metadata
// * get document vectors
// * score documents
var query = new lunr.Query(this.fields),
matchingFields = Object.create(null),
queryVectors = Object.create(null)
fn.call(query, query)
for (var i = 0; i < query.clauses.length; i++) {
/*
* Unless the pipeline has been disabled for this term, which is
* the case for terms with wildcards, we need to pass the clause
* term through the search pipeline. A pipeline returns an array
* of processed terms. Pipeline functions may expand the passed
* term, which means we may end up performing multiple index lookups
* for a single query term.
*/
var clause = query.clauses[i],
terms = null
if (clause.usePipeline) {
terms = this.pipeline.runString(clause.term)
} else {
terms = [clause.term]
}
for (var m = 0; m < terms.length; m++) {
var term = terms[m]
/*
* Each term returned from the pipeline needs to use the same query
* clause object, e.g. the same boost and or edit distance. The
* simplest way to do this is to re-use the clause object but mutate
* its term property.
*/
clause.term = term
/*
* From the term in the clause we create a token set which will then
* be used to intersect the indexes token set to get a list of terms
* to lookup in the inverted index
*/
var termTokenSet = lunr.TokenSet.fromClause(clause),
expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
for (var j = 0; j < expandedTerms.length; j++) {
/*
* For each term get the posting and termIndex, this is required for
* building the query vector.
*/
var expandedTerm = expandedTerms[j],
posting = this.invertedIndex[expandedTerm],
termIndex = posting._index
for (var k = 0; k < clause.fields.length; k++) {
/*
* For each field that this query term is scoped by (by default
* all fields are in scope) we need to get all the document refs
* that have this term in that field.
*
* The posting is the entry in the invertedIndex for the matching
* term from above.
*/
var field = clause.fields[k],
fieldPosting = posting[field],
matchingDocumentRefs = Object.keys(fieldPosting)
/*
* To support field level boosts a query vector is created per
* field. This vector is populated using the termIndex found for
* the term and a unit value with the appropriate boost applied.
*
* If the query vector for this field does not exist yet it needs
* to be created.
*/
if (!(field in queryVectors)) {
queryVectors[field] = new lunr.Vector
}
/*
* Using upsert because there could already be an entry in the vector
* for the term we are working with. In that case we just add the scores
* together.
*/
queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b })
for (var l = 0; l < matchingDocumentRefs.length; l++) {
/*
* All metadata for this term/field/document triple
* are then extracted and collected into an instance
* of lunr.MatchData ready to be returned in the query
* results
*/
var matchingDocumentRef = matchingDocumentRefs[l],
matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),
documentMetadata, matchData
documentMetadata = fieldPosting[matchingDocumentRef]
matchData = new lunr.MatchData (expandedTerm, field, documentMetadata)
if (matchingFieldRef in matchingFields) {
matchingFields[matchingFieldRef].combine(matchData)
} else {
matchingFields[matchingFieldRef] = matchData
}
}
}
}
}
}
var matchingFieldRefs = Object.keys(matchingFields),
results = {}
for (var i = 0; i < matchingFieldRefs.length; i++) {
/*
* Currently we have document fields that match the query, but we
* need to return documents. The matchData and scores are combined
* from multiple fields belonging to the same document.
*
* Scores are calculated by field, using the query vectors created
* above, and combined into a final document score using addition.
*/
var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
docRef = fieldRef.docRef,
fieldVector = this.fieldVectors[fieldRef],
score = queryVectors[fieldRef.fieldName].similarity(fieldVector)
if (docRef in results) {
results[docRef].score += score
results[docRef].matchData.combine(matchingFields[fieldRef])
} else {
results[docRef] = {
ref: docRef,
score: score,
matchData: matchingFields[fieldRef]
}
}
}
/*
* The results object needs to be converted into a list
* of results, sorted by score before being returned.
*/
return Object.keys(results)
.map(function (key) {
return results[key]
})
.sort(function (a, b) {
return b.score - a.score
})
}