Solr 查询中fq参数的解析原理
Solr 查询中fq参数的解析原理
(2011-11-02 17:43:03)public void search(Weight weight, Filter filter, Collector collector)
其中Weight是用来计算查询的权重并生成Scorer(这是一个集合迭代器),它一般由顶层的Query对象使用一个Seacher对象来创建(Query.createWeight(Searcher)),
Filter的作用是得到一个文档集,只有在这个集合内的文档才会返回,
Collector是原始查询结果的收集器。
Solr的查询就是基于Lucene的查询方式的,因此进行一次查询时就需要的对象与上面列出的相同。
核心的查询对象由Solr扩展为SolrIndexSearcher,但最终查询依然是调用IndexSearcher的search方法。1、fq参数解析QueryComponent.java的prepare方法中对参数进行解析
String[] fqs = req.getParams().getParams(CommonParams.FQ);
<p>if(fqs!=null&&fqs.length!=0){</p>
<p>Listfilters=rb.getFilters();</p>
<p>if(filters==null){</p>
<p>filters=newArrayList();</p>
<p>rb.setFilters(filters);</p>
<p>}</p>
<p>for(Stringfq:fqs){</p>
<p>if(fq!=null&&fq.trim().length()!=0){</p>
<p>QParserfqp=QParser.getParser(fq,null,req);</p>
<p>filters.add(fqp.getQuery());</p>
<p>}</p>
<p>}</p>
}
2、获取解析对象
由上面的代码可以看到filters这个集合中存放着所有fq参数解析得到的Query对象,哪一种QParser由fq的具体内容决定
QParserPlugin.java中可以看到所有的public static final Object[] standardPlugins = {
<p>LuceneQParserPlugin.NAME,LuceneQParserPlugin.class,</p>
<p>OldLuceneQParserPlugin.NAME,OldLuceneQParserPlugin.class,</p>
<p>FunctionQParserPlugin.NAME,FunctionQParserPlugin.class,</p>
<p>PrefixQParserPlugin.NAME,PrefixQParserPlugin.class,</p>
<p>BoostQParserPlugin.NAME,BoostQParserPlugin.class,</p>
<p>DisMaxQParserPlugin.NAME,DisMaxQParserPlugin.class,</p>
<p>ExtendedDismaxQParserPlugin.NAME,ExtendedDismaxQParserPlugin.class,</p>
<p>FieldQParserPlugin.NAME,FieldQParserPlugin.class,</p>
<p>RawQParserPlugin.NAME,RawQParserPlugin.class,</p>
<p>NestedQParserPlugin.NAME,NestedQParserPlugin.class,</p>
<p>FunctionRangeQParserPlugin.NAME,FunctionRangeQParserPlugin.class,</p>
};
这里fq中使用frange本地参数的情况由FunctionRangeQParserPlugin来进行解析,在这个类中可以看到:
fq 的参数格式是这样的:{!frange l=1000 u=50000}
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
<p>returnnewQParser(qstr,localParams,params,req){</p>
<p>ValueSourcevs;</p>
<p>StringfuncStr;</p>
<p>publicQueryparse()throwsParseException{</p>
<p>funcStr=localParams.get(QueryParsing.V,null);</p>
<p>QueryfuncQ=subQuery(funcStr,FunctionQParserPlugin.NAME).parse();</p>
<p>if(funcQinstanceofFunctionQuery){</p>
<p>vs=((FunctionQuery)funcQ).getValueSource();</p>
<p>}else{</p>
<p>vs=newQueryValueSource(funcQ,0.0f);</p>
}
String l = localParams.get("l"); // l 表示小值的一端,API是这样说明的:the lower bound, optional
Stringu=localParams.get("u");//u表示大的一端API:theupperbound,optional)
booleanincludeLower=localParams.getBool("incl",true);//如果incl为TRUE,则包含值I
boolean includeUpper = localParams.getBool("incu",true); //如果为TRUE,则包含值u// TODO: add a score=val option to allow score to be the value
ValueSourceRangeFilterrf=newValueSourceRangeFilter(vs,l,u,includeLower,includeUpper);
SolrConstantScoreQuerycsq=newSolrConstantScoreQuery(rf);
returncsq;
}
};由此可以知道使用fq进行范围查询时所得到具体Query对象是SolrConstantScoreQuery的对象。
SolrConstantScoreQuery类相关问题,创建Scorer对象:
publicScorerscorer(IndexReaderreader,booleanscoreDocsInOrder,booleantopScorer)throwsIOException{</p>
<p>returnnewConstantScorer(similarity,reader,this);</p>
<p>}
其中ConstantScorer是内部类
ConstantScorer的迭代基础:
在其构造函数中:
DocIdSetdocIdSet=filterinstanceofSolrFilter?((SolrFilter)filter).getDocIdSet(w.context,reader):filter.getDocIdSet(reader);</p>
<p>if(docIdSet==null){</p>
<p>docIdSetIterator=DocIdSet.EMPTY_DOCIDSET.iterator();</p>
<p>}else{</p>
<p>DocIdSetIteratoriter=docIdSet.iterator();</p>
<p>if(iter==null){</p>
<p>docIdSetIterator=DocIdSet.EMPTY_DOCIDSET.iterator();</p>
<p>}else{</p>
<p>docIdSetIterator=iter;</p>
<p>}</p>
}
由此可以ConstantScorer的迭代器起始就是这里的docIdSet的迭代器
docIdSet的迭代器有SolrFilter进行获取,之前已经看到这个SolrFilter起始就是ValueSourceRangeFilter
它的方法:public DocIdSet getDocIdSet(final Map context, final IndexReader reader) throws IOException {
<p>returnnewDocIdSet(){</p>
<p>publicDocIdSetIteratoriterator()throwsIOException{</p>
<p>returnvalueSource.getValues(context,reader).getRangeScorer(reader,lowerVal,upperVal,includeLower,includeUpper);</p>
<p>}</p>
<p>};</p>
}
实际的Scorer由DocValues来创建:public ValueSourceScorer getRangeScorer(IndexReader reader, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper)
它实际返回的是重写了matchesValue方法的ValueSourceScorer的一子类:
return new ValueSourceScorer(reader, this) {
<p>@Override</p>
<p>publicbooleanmatchesValue(intdoc){</p>
<p>floatdocVal=floatVal(doc);</p>
<p>System.out.println("Documentid'"+doc+"'score="+docVal);</p>
<p>returndocVal>=l&&docVal<=u;</p>
<p>}</p>
<p>};
回到ValueSourceScorer,我们可以发现这个迭代器是如何工作的:
privateintdoc=-1;</p>
<p>protectedfinalintmaxDoc;</p>
<p>publicintnextDoc()throwsIOException{</p>
<p>for(;;){</p>
<p>doc++;</p>
<p>if(doc>=maxDoc)returndoc=NO_MORE_DOCS;</p>
<p>if(matches(doc))returndoc;</p>
<p>}</p>
}
也就是这个迭代器默认是匹配所有文档的,只是由重写它的部分方法来实现文档过滤。
3、使用解析到的Query对象
具体的查询时在SolrIndexSearcher中进行的,由以下方法开始:
publicQueryResultsearch(QueryResultqr,QueryCommandcmd)
其中QueryResult和QueryCommand都是SolrIndexSearcher的内部类,分别包装了查询结果和查询条件相关内容。
fq解析得到的Query对象的List在QueryCommand中作为filterList成员变量来保存:
private List filterList;具体到实际查询时(如果结果缓存中没有),Solr会先根据filter或filterList(filter和filterList不能同时都存在,否则报错)来先查询到一个文档集合作为过滤器:
DocSetfilter=cmd.getFilter()!=null?cmd.getFilter():getDocSet(cmd.getFilterList());
其中getDocSet()方法负责根据fq的查询条件来查询到一个文档集,查询方式与普通的查询类似该过滤器如果存在,那么就能到一个Lucene可用的Filter对象:final Filter luceneFilter = filter==null ? null : filter.getTopFilter();
最后使用这个对象来进行查询:
super.search(query,luceneFilter,collector);
这个里面的query是查询参数中q以及其他相关参数(不包括fq)解析得到的Query对象处理collector收集到的文档:
TopDocstopDocs=topCollector.topDocs(0,len);
maxScore=totalHits>0?topDocs.getMaxScore():0.0f;
nDocsReturned = topDocs.scoreDocs.length;ids = new int[nDocsReturned];
scores=(cmd.getFlags()&GET_SCORES)!=0?newfloat[nDocsReturned]:null;
for(inti=0;i
ScoreDocscoreDoc=topDocs.scoreDocs[i];
ids[i]=scoreDoc.doc;
if(scores!=null)scores[i]=scoreDoc.score;
}