Luceneでのインデックス作成と検索

Luceneでのインデックスの作成と、検索、IntPointのインデックスの仕方による検索結果についてです。

# 環境
lucene-7.2.1

# 準備
luceneをダウンロードしてきて、

 - lucene-analyzers-common-7.2.1
 - lucene-core-7.2.1 

このあたりをAdd External JARsで追加しておきます。

# インデックス作成
```java
  public void feed() {
    // Analyzerを何か選ぶ
    Analyzer analyzer = new WhitespaceAnalyzer();
    IndexWriterConfig iwc = new IndexWriterConfig(analyzer);

    // OpenMode.CREATE_OR_APPENDならあったら追記
    // OpenMode.APPENDなら追記
    // OpenMode.CREATEは新規作成
    iwc.setOpenMode(OpenMode.CREATE);

    // 出力するディレクトリ先
    Path path = Paths.get("src/main/resources/test");
    Directory directory = null;
    IndexWriter writer = null;
    try {
      directory = FSDirectory.open(path);
      writer = new IndexWriter(directory, iwc);

      for (int i = 0; i < 10; i++) {
        // Documentクラスの作成
        Document doc = new Document();

        // textフィールドにtest textの値を入れる
        doc.add(new TextField("text", "test text", Field.Store.YES));

        // idフィールドにidの値を入れる
        doc.add(new StringField("id", "id" + i, Field.Store.YES));

        // cntフィールドに0から9の値を入れる
        doc.add(new IntPoint("cnt", i));
        doc.add(new StoredField("cnt", i));
        doc.add(new NumericDocValuesField("cnt", i));

        // 書き込み
        writer.addDocument(doc);
      }
      writer.close();

    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

  }
```

このコードを実行すると、このようなデータが入るイメージです。

|text  |id  |cnt  |
|---|---|---|
|test text  |id0  |0  |
|test text  |id1  |1  |
|test text  |id2  |2  |
|test text  |id3  |3  |
|test text  |id4  |4  |
|test text  |id5  |5  |
|test text  |id6  |6  |
|test text  |id7  |7  |
|test text  |id8  |8  |
|test text  |id9  |9  |

# 検索をする
検索をするためにこんな感じのコードを書いてみます。

```java
public void search() {
    Path path = Paths.get("src/main/resources/test");
    Directory directory = null;

    // TermQueryでテキストの検索クエリを作る textフィールドにtestを含むもの
    TermQuery tq = new TermQuery(new Term("text", "test"));

    // IntPoint.newRangeQueryでintPointの範囲検索
    Query rq = IntPoint.newRangeQuery("cnt", 1, 5);

    // BooleanQuery.Builderでクエリを作る
    BooleanQuery.Builder builder = new BooleanQuery.Builder();
    Query q = builder.add(tq, BooleanClause.Occur.MUST) // Occur.MUSTでつなげていくとAND検索
        .add(rq, BooleanClause.Occur.FILTER).build();

    // 最後の引数がtrueで降順
    Sort sort = new Sort(new SortField("cnt", SortField.Type.INT, true));

    try {
      directory = FSDirectory.open(path);
      DirectoryReader reader = DirectoryReader.open(directory);
      IndexSearcher searcher = new IndexSearcher(reader);
      ScoreDoc[] hits = searcher.search(q, 100, sort).scoreDocs;

      System.out.println("start search");
      for (int i = 0; i < hits.length; i++) {
        Document hitDoc = searcher.doc(hits[i].doc);
        System.out.println("id:" + hitDoc.get("id"));
        System.out.println("cnt:" + hitDoc.get("cnt"));
        System.out.println("-------------");
      }
      reader.close();
      directory.close();
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }
}
```
## 検索の実行結果
上のコードを実行すると、以下の出力がされます。

```:実行結果
start search
id:id5
cnt:5
-------------
id:id4
cnt:4
-------------
id:id3
cnt:3
-------------
id:id2
cnt:2
-------------
id:id1
cnt:1
-------------
```
textのフィールドにtestを含み、cntのフィールドが1から5の範囲のものが
降順でソートされて返されています。


## IntPointのインデックスの仕方による検索結果の違い
https://stackoverflow.com/questions/42482451/lucene-6-recommended-way-to-store-numeric-fields-with-term-vocabulary

ここではLongPointとStoredFieldとNumericDocValuesFieldの3つを
同じフィールド名で指定してます。
3つも指定するの？ということで、減らしたりして、
上に書いたsearch()メソッドを実行して検索をやってみました。

### IntPointとStoredFieldとNumericDocValuesFieldをすべて指定

```java
// cntフィールドに0から10の値を入れる
doc.add(new IntPoint("cnt", i));
doc.add(new StoredField("cnt", i));
doc.add(new NumericDocValuesField("cnt", i));
```

search()を実行しての検索結果

```:実行結果
start search
id:id5
cnt:5
-------------
id:id4
cnt:4
-------------
id:id3
cnt:3
-------------
id:id2
cnt:2
-------------
id:id1
cnt:1
-------------
```
ちゃんと1から5の範囲で降順で返ってきます。

### StoredFieldをなくしてインデックスしてみる
```java
// cntフィールドに0から10の値を入れる
doc.add(new IntPoint("cnt", i));
//doc.add(new StoredField("cnt", i));
doc.add(new NumericDocValuesField("cnt", i));
```

search()を実行しての検索結果

```:実行結果
start search
id:id5
cnt:null
-------------
id:id4
cnt:null
-------------
id:id3
cnt:null
-------------
id:id2
cnt:null
-------------
id:id1
cnt:null
-------------
```
1から5の範囲で降順にはなっていました。
cntのフィールドの値がnullでした。

### NumericDocValuesFieldをなくしてインデックスしてみる

```java
// cntフィールドに0から10の値を入れる
doc.add(new IntPoint("cnt", i));
doc.add(new StoredField("cnt", i));
//doc.add(new NumericDocValuesField("cnt", i));
```

search()を実行しての検索結果

```:実行結果
Exception in thread "main" java.lang.IllegalStateException: unexpected docvalues type NONE for field 'cnt' (expected=NUMERIC). Re-index with correct docvalues type.
```
検索に失敗しました。
ソートを行うコードを外して実行すると以下のように結果が返ってくるので、
NumericDocValuesFieldを指定しないと、ソートするときに失敗するようです。

```:ソートを行う部分を除いての実行結果
start search
id:id1
cnt:1
-------------
id:id2
cnt:2
-------------
id:id3
cnt:3
-------------
id:id4
cnt:4
-------------
id:id5
cnt:5
-------------
```

# 参考
 - [Apache Lucene 使い方メモ](https://qiita.com/opengl-8080/items/8762b7f328964bd1cd92)