1. 程式人生 > 其它 >Solr 查詢中fq引數的解析原理

Solr 查詢中fq引數的解析原理

原文地址:https://blog.csdn.net/rongdmmap/article/details/84168071 覺得分析的不錯,記錄下免得以後找不到。

首先看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來進行解析,在這個類中可以看到:

fq 的引數格式是這樣的:{!frange l=1000 u=50000}

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"); // l 表示小值的一端,API是這樣說明的:the lower bound, optional
             String u = localParams.get("u"); // u表示大的一端 API: the upper bound, optional)
             boolean includeLower = 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
             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;
}

注:最後這點程式碼好像有些問題,我還沒有去看原始碼
————————————————
版權宣告:本文為CSDN博主「rongdmmap」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/rongdmmap/article/details/84168071