注:本文基于Lucene 8.2.0 版本。

本文介绍一个比较“特殊”的查询API——QueryParser,它的特殊之处在于定义了一些查询语法,通过这些语法几乎可以实现前文介绍的所有Query API提供的功能,但它的存在并不是为了替换那些API,而是用在一些交互式场景中。本文不会再细述Lucene各个查询的含义及用法(比如什么是edit distance),所以如果你还不熟悉,请务必先阅读《Lucene系列(8)——常用Query介绍》一文。

QueryParser概述

其实在《Lucene系列(2)——代码实践》一文中我们已经使用过QueryParser进行查询了,这里再贴一下部分代码:

/**
 * Minimal Search Files code
 **/
public class SearchFilesMinimal {

    public static void main(String[] args) throws Exception {
        // 索引保存目录
        final String indexPath = "/Users/allan/Git/allan/github/CodeSnippet/Java/lucene-learning/indices/poems-index";
        // 搜索的字段
        final String searchField = "contents";

        // 从索引目录读取索引信息
        IndexReader indexReader = DirectoryReader.open(FSDirectory.open(Paths.get(indexPath)));
        // 创建索引查询对象
        IndexSearcher searcher = new IndexSearcher(indexReader);
        // 使用标准分词器
        Analyzer analyzer = new StandardAnalyzer();

        // 从终端获取查询语句
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        // 创建查询语句解析对象
        QueryParser queryParser = new QueryParser(searchField, analyzer);
        while (true) {
            System.out.println("Enter query: ");

            String input = in.readLine();
            if (input == null) {
                break;
            }

            input = input.trim();
            if (input.length() == 0) {
                break;
            }

            // 解析用户输入的查询语句:build query
            Query query = queryParser.parse(input);
            System.out.println("searching for: " + query.toString(searchField));
            // 查询
            TopDocs results = searcher.search(query, 10);
            // 省略后面查询结果打印的代码
            }
        }
    }
}

在这段代码中,先读取了已经创建好的索引文件,然后创建了一个QueryParser实例(queryParser)。接着不断读取用户输入(input),并传给QueryParser的parse方法,该方法通过用户的输入构建一个Query对象用于查询。

QueryParser的构造函数为QueryParser(String f, Analyzer a),第1个参数指定一个默认的查询字段,如果后面输入的input里面没有指定查询字段,则默认查询该该字段,比如输入hello表示在默认字段中查询"hello",而content: hello则表示在content字段中查询"hello"。第2个参数指定一个分析器,一般该分析器应该选择和索引阶段同样的Analyzer。

另外有两个点需要特别注意:

  1. QueryParser默认使用TermQuery进行多个Term的OR关系查询(后文布尔查询那里会再介绍)。比如输入hello world,表示先将hello world分词(一般会分为hello和world两个词),然后使用TermQuery查询。如果需要全词匹配(即使用PhraseQuery),则需要将搜索词用双引号引起来,比如"hello world"
  2. 指定搜索字段时,该字段仅对紧随其后的第一个词或第一个用双引号引起来的串有效。比如title:hello world这个输入,title仅对hello有效,即搜索时只会在title字段中搜索hello,然后在默认搜索字段中搜索world。如果想要在一个字段中搜索多个词或多个用双引号引起来的词组时,将这些词用小括号括起来即可,比如title:(hello world)

下面通过一些例子看一下如何通过QueryParser提供的语法在一个输入串中实现前文介绍的各种搜索。

QueryParser语法

Wildcard搜索

通配符搜索和WildcardQuery API一样,仅支持?*两个通配符,前者用于匹配1个字符,后者匹配0到多个字符。输入title:te?t,则可以匹配到title中的"test"、"text"等词。

注意:使用QueryParser中的wildcard搜索时,不允许以?*开头,否则会抛异常,但直接使用WildcardQuery API时,允许以通配符开头,只是因为性能原因,不推荐使用。这样设计的原因我猜是因为QueryParser的输入是面向用户的,用户对于通配符开头造成的后果并不清楚,所以直接禁掉;而WildcardQuery是给开发者使用的,开发者在开发阶段很清楚如果允许这样做造成的后果是否可以接受,如果不能接受,也是可以通过接口禁掉开头就是用通配符的情况。

Regexp搜索

正则搜索和RegexpQuery一样,不同之处在于QueryParser中输入的正则表达式需要使用两个斜线("/")包围起来,比如匹配"moat"或"boat"的正则为/[mb]oat/

Fuzzy搜索

在QueryParser中,通过在搜索词后面加波浪字符来实现FuzzyQuery,比如love~,默认edit distance是2,可以在波浪符后面加具体的整数值来修改默认值,合法的值为0、1、2.

Phrase slop搜索

PhraseQuery中可以指定slop(默认值为0,精确匹配)来实现相似性搜索,QueryParser中同样可以,使用方法与Fuzzy类似——将搜索字符串用双引号引起来,然后在末尾加上波浪符,比如"jakarta apache"~10。这里对数edit distance没有限制,合法值为非负数,默认值为0.

Range搜索

QueryParser的范围搜索同时支持TermRangeQuery和数值型的范围搜索,排序使用的是字典序开区间使用大括号,闭区间使用方括号。比如搜索修改日期介于2019年9月份和10月份的文档:mod_date:[20190901 TO 20191031],再比如搜索标题字段中包含hatelove的词(但不包含这两个词)的文档:title:{hate TO love}.

提升权重(boost)

查询时可以通过给搜索的关键字或双引号引起来的搜索串后面添加脱字符(^)及一个正数来提升其计算相关性时的权重(默认为1),比如love^5 China"love China"^0.3

Boolean操作符

QueryParser中提供了5种布尔操作符:AND+ORNOT-,所有的操作符必须大写

  • OR是默认的操作符,表示满足任意一个term即可。比如搜索love ChinaloveChina之间就是OR的关系,检索时文档匹配任意一个词即视为匹配。OR也可以使用可用||代替。
  • AND表示必须满足所有term才可以,可以使用&&代替。
  • +用在term之前,表示该term必须存在。比如+love China表示匹配文档中必须包含loveChina则可包含也可不含。
  • -用在term之前,表示该term必须不存在。比如-"hate China" "love China"表示匹配文档中包含"love China",但不包含"hate China"的词。

分组

前面已经介绍过,可以使用小括号进行分组,通过分组可以表达一些复杂的逻辑。举两个例子:

  • (jakarta OR apache) AND website表示匹配文档中必须包含webiste,同时需要至少包含jakartaapache二者之一。
  • title:(+return +"pink panther")表示匹配文档中的title字段中必须同时存在return"pink panther"串。

特殊字符

从前面的介绍可知,有很多符号在QueryParser中具有特殊含义,目前所有的特殊符号包括:+- && || ! ( ) { } [ ] ^ " ~ * ? : \ /。如果搜索关键字中存在这些特殊符号,则需要使用反斜线(\)转义。比如搜索(1+1)*2则必须写为\(1\+1\)\*2

相比于Lucene的其它搜索API,QueryParser提供了一种方式,让普通用户可以不需要写代码,只是掌握一些语法就可以进行复杂的搜索,在一些交互式检索场景中,还是非常方便的。

References