Solr bug 小记

Solr Bug 小记

做为Apache的顶级开源项目之一,一般来说大家遇到Solr中的源码bug的概率还是非常底的,但是笔者还真就遇到了一个,突然感觉幸福来的太突然了。(因为笔者并不是这个bug的第一发现这和解决者,所以这里笔者更多的是记录这个bug的发现和解决方式) [这里的Solr版本是7.7.2]

Bug复现

这个bug复现的条件有三个:

  • 使用Solr的edismax Query Parser

    1
    2
    3
    4
    5
     <requestHandler name="/select" class="solr.SearchHandler">
    <lst name="defaults">
    <str name="echoParams">explicit</str>
    <str name="defType">edismax</str>
    ...
  • edismax的lowercaseOperators参数设置成true

    1
    <bool name="lowercaseOperators">true</bool>
  • Phrase Query的多了对括号

    1
    2
    3
    id:("car and bus")
    //这里的id字段是我们随意设置的某个字段,字段的定义如下
    //<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />

(完整的SearchHandler的定义如下)

1
2
3
4
5
6
7
8
9
<requestHandler name="/select" class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="defType">edismax</str>
<bool name="lowercaseOperators">true</bool>
<int name="rows">10</int>
<bool name="preferLocalShards">false</bool>
</lst>
</requestHandler>

Bug的点为: id:(“car and bus”)中的and会因为edismax的解析会变成大写的AND,但是如果去掉括号[id:”car and bus”],那么解析的结果就是正常的。具体的可以参考下图:
lkEPJJ.md.png

为啥括号惹了祸

如果引号的两侧没有括号,那么这个query的解析就是正常的,这个还是好理解的,如果加上了引号,在Lucene中我们称这种query为Phrase Query(短语查询),而针对Phrase Query如果字段类型是string类型的话,Solr是不会处理phrase的任何内容,但是如果加上括号为啥即使字段类型为string类型,我们输入的小写的and会变成大写的AND呢?我们来看下org.apache.solr.search.ExtendedDismaxQParser.splitIntoClauses#686

1
2
3
4
5
6
ch = s.charAt(pos);
if (!ignoreQuote && ch=='"') {
clause.isPhrase = true;
inString = '"'; // 如果冒号(:)后面的第一个字符是引号("),那么这个clause会被标记成phrase,但是如果我们加上了括号,那么冒号(:)后面的第一个字符就是左括号了,那么这个phrase标记就不会被打上
pos++;
}

也就是说因为我们加上了括号使得原来的代码对Phrase query的处理失效了。这样会导致query,id:(“car and bus”)会被拆成3个clause:

  • clause 1 : id:(“car
  • clause 2 : and
  • clause 3 : bus”)
    关注到源码中splitIntoClauses方法的下一个方法org.apache.solr.search.ExtendedDismaxQParser.rebuildUserQuery#414
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ...
    if (lowercaseOperators && i>0 && i+1<clauses.size()) {
    if ("AND".equalsIgnoreCase(s)) {
    s="AND";// 就是这里将小写的and专成了大写的AND
    } else if ("OR".equalsIgnoreCase(s)) {
    s="OR";
    }
    }
    ...

好吧,确实是一个括号导致的这个bug的出现。笔者遇到这个bug也是因为部分数据中出现了小写的and,然后因为使用了edismax和lowercaseOperators=true才被客户发现了。(对的,这个bug是我们的客户发现的:p )

如何修正Bug

既然我们已经知道了为什么会产生,那么修正应该是显而易见的事情吧?有人看到这里可能会说不就是多了对括号嘛:我在代码里面多加上一个判断就好了,确实,这可以解决我们现在的特殊case,但是你怎么知道引号两侧只有一对括号,可能会有2对[id:((“car and bus”))],可能有n对,貌似这样代码不好写啊!!!这里我们不卖关子,直接给出最终答案:org.apache.solr.search.ExtendedDismaxQParser#715:
lkZaxs.md.png,为了方便大家Ctrl+c,Ctrl+v,这里也给出代码部:

1
2
3
4
5
6
else if(!ignoreQuote && ch=='"') {
clause.isPhrase = true;
inString = '"';
continue;
}
`

看起来非常简单,只有4行,其实就是把我们上面的想法嵌入到已有的流程中,当你仔细阅读源码的时候你会发现org.apache.solr.search.ExtendedDismaxQParser的693到715,实际上在处理冒号(:)之后的内容,而我们要加上的就是如果冒号后面(非第一个)的字符也是引号的情况下,继续保证原有的phrase不会因为括号的出现而被拆成好几份。修改源码之后重新使用测试query你会发现问题解决了,如下图:
lkeMYF.md.png