CLOVER🍀

That was when it all began.

Apache Solrで、実は後方一致(WildcardQuery)ができるという話

今までずっとSolr(というかLucene)で、ワイルドカードによる後方一致はできないものだと思っていたのですが、どうやらそうではないようです。

最近、Solrで後方一致のクエリを投げている人が近くにいて、「使えるの?」と聞いてみたら「動いてますよー」とあっけらかんと返ってきたことで知りました…。

で、なんで後方一致ができないと思っていたかというのは、LuceneのQueryParserが元です。

Wildcard Searches

Note: You cannot use a * or ? symbol as the first character of a search.

http://lucene.apache.org/core/5_4_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Wildcard_Searches

後方一致できないって書いてるやないかー。

現時点、Lucene 5.4.0でも。

で、これに対してWildcardQueryを見ると

Note this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow WildcardQueries, a Wildcard term should not start with the wildcard *

http://lucene.apache.org/core/5_4_0/core/org/apache/lucene/search/WildcardQuery.html

となっていて、遅いからやめた方がいいよくらいな感じになっています。

どういうことでしょう?

というわけで、ちょっと確認。Luceneで。

WildcardQuery。

    it("prefix search, using WildcardQuery") {
      val analyzer = new StandardAnalyzer
      val directory = new RAMDirectory

      val indexWriter = new IndexWriter(directory, new IndexWriterConfig(analyzer))
      val document1 = {
        val d = new Document()
        d.add(new TextField("name", "foo_bar", Store.YES))
        d
      }
      val document2 = {
        val d = new Document()
        d.add(new TextField("name", "bar_foo", Store.YES))
        d
      }

      indexWriter.addDocument(document1)
      indexWriter.addDocument(document2)
      indexWriter.commit()
      indexWriter.close()

      val query = new WildcardQuery(new Term("name", "*foo"))

      val reader = DirectoryReader.open(directory)
      val searcher = new IndexSearcher(reader)

      val topDocs = searcher.search(query, 100, Sort.RELEVANCE)
      val resultDoc = searcher.doc(topDocs.scoreDocs(0).doc)
      resultDoc.get("name") should be("bar_foo")

      directory.close()
    }

こちらはOKですね。

QueryParser。

    it("prefix search, using QueryParser") {
      val analyzer = new StandardAnalyzer
      val directory = new RAMDirectory

      val indexWriter = new IndexWriter(directory, new IndexWriterConfig(analyzer))
      val document1 = {
        val d = new Document()
        d.add(new TextField("name", "foo_bar", Store.YES))
        d
      }
      val document2 = {
        val d = new Document()
        d.add(new TextField("name", "bar_foo", Store.YES))
        d
      }

      indexWriter.addDocument(document1)
      indexWriter.addDocument(document2)
      indexWriter.commit()
      indexWriter.close()

      val queryParser = new QueryParser("name", analyzer)
      a[ParseException] should be thrownBy queryParser.parse("*_foo")

      directory.close()
    }

こっちは失敗するし…。

これに対して、Solrで

q=name:*A

みたいなクエリを投げても、確かにエラーになりません。普通のLuceneのQueryParserではないってことかな…。

気になって書いた検証コードは、こちらに置いています。
https://github.com/kazuhira-r/lucene-examples/tree/master/lucene-wildcard-query-suffix-match