Solr 查询中fq参数的解析原理

    技术2026-01-13  0

     原文:http://www.30lost.com/archives/22

    首先看Lucene进行索引查询的一个核心方法:IndexSearcher.java

    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); if (fqs!=null && fqs.length!=0) { List filters = rb.getFilters(); if (filters==null) { filters = new ArrayList(); rb.setFilters( filters ); } for (String fq : fqs) { if (fq != null && fq.trim().length()!=0) { QParser fqp = QParser.getParser(fq, null, req); filters.add(fqp.getQuery()); } } }

    2、获取解析对象 由上面的代码可以看到filters这个集合中存放着所有fq参数解析得到的Query对象,哪一种QParser由fq的具体内容决定 QParserPlugin.java中可以看到所有的

    public static final Object[] standardPlugins = { LuceneQParserPlugin.NAME, LuceneQParserPlugin.class, OldLuceneQParserPlugin.NAME, OldLuceneQParserPlugin.class, FunctionQParserPlugin.NAME, FunctionQParserPlugin.class, PrefixQParserPlugin.NAME, PrefixQParserPlugin.class, BoostQParserPlugin.NAME, BoostQParserPlugin.class, DisMaxQParserPlugin.NAME, DisMaxQParserPlugin.class, ExtendedDismaxQParserPlugin.NAME, ExtendedDismaxQParserPlugin.class, FieldQParserPlugin.NAME, FieldQParserPlugin.class, RawQParserPlugin.NAME, RawQParserPlugin.class, NestedQParserPlugin.NAME, NestedQParserPlugin.class, FunctionRangeQParserPlugin.NAME, FunctionRangeQParserPlugin.class, };

    这里fq中使用frange本地参数的情况由FunctionRangeQParserPlugin来进行解析,在这个类中可以看到:

    public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { return new QParser(qstr, localParams, params, req) { ValueSource vs; String funcStr; public Query parse() throws ParseException { funcStr = localParams.get(QueryParsing.V, null); Query funcQ = subQuery(funcStr, FunctionQParserPlugin.NAME).parse(); if (funcQ instanceof FunctionQuery) { vs = ((FunctionQuery)funcQ).getValueSource(); } else { vs = new QueryValueSource(funcQ, 0.0f); }

    String l = localParams.get("l"); String u = localParams.get("u"); boolean includeLower = localParams.getBool("incl",true); boolean includeUpper = localParams.getBool("incu",true);

    // TODO: add a score=val option to allow score to be the value ValueSourceRangeFilter rf = new ValueSourceRangeFilter(vs, l, u, includeLower, includeUpper); SolrConstantScoreQuery csq = new SolrConstantScoreQuery(rf); return csq; } };

    由此可以知道使用fq进行范围查询时所得到具体Query对象是SolrConstantScoreQuery的对象。 SolrConstantScoreQuery类相关问题,创建Scorer对象: public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException { return new ConstantScorer(similarity, reader, this); } 其中ConstantScorer是内部类 ConstantScorer的迭代基础: 在其构造函数中: DocIdSet docIdSet = filter instanceof SolrFilter ? ((SolrFilter)filter).getDocIdSet(w.context, reader) : filter.getDocIdSet(reader); if (docIdSet == null) { docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator(); } else { DocIdSetIterator iter = docIdSet.iterator(); if (iter == null) { docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator(); } else { docIdSetIterator = iter; } }

    由此可以ConstantScorer的迭代器起始就是这里的docIdSet的迭代器 docIdSet的迭代器有SolrFilter进行获取,之前已经看到这个SolrFilter起始就是ValueSourceRangeFilter 它的方法:

    public DocIdSet getDocIdSet(final Map context, final IndexReader reader) throws IOException { return new DocIdSet() { public DocIdSetIterator iterator() throws IOException { return valueSource.getValues(context, reader).getRangeScorer(reader, lowerVal, upperVal, includeLower, includeUpper); } }; }

    实际的Scorer由DocValues来创建: public ValueSourceScorer getRangeScorer(IndexReader reader, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper)

    它实际返回的是重写了matchesValue方法的ValueSourceScorer的一子类:

    return new ValueSourceScorer(reader, this) { @Override public boolean matchesValue(int doc) { float docVal = floatVal(doc); System.out.println("Document id '" + doc + "' score = " + docVal); return docVal >= l && docVal <= u; } }; 回到ValueSourceScorer,我们可以发现这个迭代器是如何工作的: private int doc = -1; protected final int maxDoc; public int nextDoc() throws IOException { for (; { doc++; if (doc >= maxDoc) return doc = NO_MORE_DOCS; if (matches(doc)) return doc; } }

    也就是这个迭代器默认是匹配所有文档的,只是由重写它的部分方法来实现文档过滤。

    3、使用解析到的Query对象 具体的查询时在SolrIndexSearcher中进行的,由以下方法开始: public QueryResult search(QueryResult qr, QueryCommand cmd) 其中QueryResult和QueryCommand都是SolrIndexSearcher的内部类,分别包装了查询结果和查询条件相关内容。 fq解析得到的Query对象的List在QueryCommand中作为filterList成员变量来保存: private List filterList;

    具体到实际查询时(如果结果缓存中没有),Solr会先根据filter或filterList(filter和filterList不能同时都存在,否则报错)来先查询到一个文档集合作为过滤器: DocSet filter = 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收集到的文档: TopDocs topDocs = 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 ? new float[nDocsReturned] : null; for (int i=0; i ScoreDoc scoreDoc = topDocs.scoreDocs[i]; ids[i] = scoreDoc.doc; if (scores != null) scores[i] = scoreDoc.score; }

    最新回复(0)