Couchbase Lite機能解説：全文検索


## はじめに

ドキュメント指向NoSQL組み込みデータベースCouchbase Liteの全文検索機能について、具体的なコードを交えて解説します。


なお、Couchbase Liteについては、[Couchbase Mobileアプリケーション開発へのロードマップ](https://qiita.com/yoshiyuki_kono/items/6a5bbad1b02196a25417)に記事をまとめている他、（これらの記事を元に構成した）以下の電子書籍を無償で頒布しています。

https://techbookfest.org/product/5074954942939136?productVariantID=5042865933647872

また、Couchbase Serverの存在意義、機能詳細、利用方法等については、拙著[NoSQLドキュメント指向データベースCouchbase Serverファーストステップガイド(インプレスR&D刊)](https://www.amazon.co.jp/-/en/%E6%B2%B3%E9%87%8E-%E6%B3%B0%E5%B9%B8/dp/429560030X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1636588872&sr=1-1)や、[NoSQL/JSONデータベースCouchbase Server理解・活用へのロードマップ](https://qiita.com/yoshiyuki_kono/items/4c9cd1115efc5807b49f)にまとめてある記事をご参考ください。


## Couchbase Lite全文検索

### 概要

全文検索（FTS）は、ステミング、関連性ベースのランキング、およびロケール固有の自然言語クエリのサポートを提供します。

FTSを実行するには、適切なプロパティにフルテキストインデックスを作成する必要があります（通常のクエリとは異なり、インデックスはオプションではありません。）。1つ以上のプロパティにインデックスを作成できます。全文検索では大文字と小文字が区別されません。

全文検索（FTS）クエリを実行するには、一致する式に全文インデックスを作成しておく必要があります。

### 利用条件

全文検索は、単語の区切りに空白を使用する言語で利用することができます。

また、ステミング（語幹処理）は、デンマーク語、オランダ語、英語、フィンランド語、フランス語、ドイツ語、ハンガリー語、イタリア語、ノルウェー語、ポルトガル語、ルーマニア語、ロシア語、スペイン語、スウェーデン語、トルコ語でサポートされています。 

ご覧の通り、日本語は利用条件を満たしていませんが、全文検索を利用する場面として、以下のケースが考えられます。

- サポートされる言語（例えば英語）による文章（例えば、システムログメッセージ）に対する検索
- サポートされる言語（例えば英語）を持つ複数のプロパティ（例えば、性別などの属性情報のように、アプリケーションに表示するデータそのものを用いる必要がない場合）に対する検索（通常のクエリでも、複数の条件を組み合わせることはできますが、シンプルな実装になる可能性があります。）



### インデックス作成

`IndexBuilder`を使用して、インデックス作成します。
次の例では、「name」プロパティにFTSインデックスを作成します。プロパティの指定に`FullTextIndexItem`を使います。


```KOTLIN:Kotlin
database.createIndex(
    "nameFTSIndex",
    IndexBuilder.fullTextIndex(FullTextIndexItem.property("name")).ignoreAccents(false)
)
```

```Java:Java
database.createIndex(
    "nameFTSIndex",
    IndexBuilder.fullTextIndex(FullTextIndexItem.property("name")).ignoreAccents(false));
```

次の定義のように、配列を使用して、インデックスを作成する際に、複数のプロパティを指定できます。プロパティの指定に`FullTextIndexItem`の配列を使います。

```KOTLIN:Kotlin
IndexBuilder.FullTextIndex(params FullTextIndexItem[] items)
```

### クエリ
インデックスを作成すると、インデックス付きのプロパティに対して全文検索（FTS）クエリを作成して実行できます。

検索条件は、`FullTextExpression`を使って指定します。

以下の例では、先に定義した"nameFTSIndex"という名前のインデックス中の「buy」という単語を含むすべてのドキュメントを検索しています。


```KOTLIN:Kotlin
val rs = QueryBuilder.select(SelectResult.expression(Meta.id))
    .from(DataSource.database(database))
    .where(FullTextExpression.index("nameFTSIndex").match("buy"))
    .execute()
for (result in rs) {
    Log.i(TAG, "document properties${result.getString(0)}")
}
```


```JAVA:Java
Expression whereClause = FullTextExpression.index("nameFTSIndex").match("buy");
Query ftsQuery = QueryBuilder.select(SelectResult.expression(Meta.id))
    .from(DataSource.database(database))
    .where(whereClause);
ResultSet ftsQueryResult = ftsQuery.execute();
for (Result result : ftsQueryResult) {
    Log.i(
        TAG,
        String.format("document properties %s", result.getString(0)));
}
```



### パターンマッチングフォーマット
以下の形式で照合するパターンを提供することができます。

### プレフィックスクエリ
文字に続けて「*」（アスタリスク）文字を付すことで、プレフィックス（接頭辞）によるクエリを行うことができます。


以下の例では、接頭辞「lin」が付いた単語を含むすべてのドキュメントをクエリします。

```
lin*
```


### プロパティ名指定
先に書いたように、FTSインデックス作成時に、複数のプロパティを指定することができます。
通常、クエリは、インデックス定義に用いられた全てのプロパティと照合されます。プロパティ名と「:」の組み合わせに続いて検索語を指定することで、インデックス中の特定のプロパティに対する照合を表現することができます。

「:」とクエリする単語の間にはスペースを入れます。プロパティ名と「:」の間にはスペースを含みません。


次の例は、「linux」という単語がドキュメントのタイトル（「title」プロパティ）に存在し、「problems」という用語がドキュメントのタイトル、またはその他のプロパティに含まれるドキュメントを照会します。


```
title: linux problems
```

### フレーズクエリ
フレーズクエリは、スペースで区切られた単語のシーケンスを二重引用符（"）で囲むことによって指定します。

次の例は、「linux applications」というフレーズを含むドキュメントをクエリします。


```
"linux applications"
```

### NEARクエリ
NEARクエリは、複数の検索語の関係を指定するために利用することができます。NEARクエリは、キーワード「NEAR」を2つの検索語（トークン、プレフィックスまたはフレーズ）の間に置くことで指定されます。近接度を指定するには、「NEAR/<近接度>」の形式を用います（デフォルトの近接度は10）。


次の例は、「database」という単語と「replication」という単語を含み、これらの単語の間に存在する単語が2つ以下である（3つ以上の単語によって隔てられていない）ドキュメントを検索します。

```
database NEAR/2 replication
```

### 拡張クエリ構文

#### AND、OR、NOT演算子

クエリ構文は、AND、OR、NOT演算子をサポートします。演算子は大文字を使用する必要があります。それ以外の場合は、演算子ではなく、検索語として解釈されます。

次の例は、「couchbase」という単語と「database」という単語の両方を含むドキュメントのセットを返します。

```
couchbase AND database
```

#### 演算子の優先順位
括弧を使用してさまざまな演算子の優先順位を指定できます。


以下の例は、「単語」という用語と、「couchbase database」または「sqlite library」というフレーズの少なくとも1つを含むドキュメントをクエリします。

```
("couchbase database" OR "sqlite library") AND linux
```

### ランキング

検索結果を関連性の高い順に並べ替えることは非常に一般的です。Couchbase Liteは、検索結果ランキング機能を提供しています。

以下に例を示します。

```swift
func queryForDocumentsContainingSpecificStringWithRankOrder(_ db:Database,limit:Int = 10) throws -> [Data]? {
    let ftsExpression = FullTextExpression.index("ContentFTSIndexNoStemming")
    let searchQuery = QueryBuilder
        .select(SelectResult.expression(Meta.id),
                SelectResult.expression(Expression.property("content")))
        .from(DataSource.database(db))
        .where(Expression.property("type").equalTo(Expression.string ("landmark"))
            .and( ftsExpression.match("attract")))
        .orderBy(Ordering.expression(FullTextFunction.rank("ContentFTSIndexNoStemming")).descending())
        .limit(Expression.int(limit))
```

上記コードは、以下から引用しています。

https://github.com/couchbaselabs/couchbase-lite-ios-api-playground/blob/master/CBLQueryPlayground.playground/Pages/FTS.xcplaygroundpage/Contents.swift

以下のように、リポジトリをcloneして利用することもできます。

```
$ git clone https://github.com/couchbaselabs/couchbase-lite-ios-api-playground
```

### ステミング
ステミングは、単語を語幹に減らすプロセスです。たとえば、「catty」、「catlike」、および「cats」は「cat」という単語に変換されます。したがって、「cat」というキーワードを検索すると、「cat」、「catlike」などに一致する結果が得られます。

ステミングはデフォルトで適用される一方、以下のようにステミングを用いないインデックスを作成することも可能です。

```swift
  let ftsIndex = IndexBuilder.fullTextIndex(items: FullTextIndexItem.property("content")).language(nil)
  try db.createIndex(ftsIndex,withName: "ContentFTSIndexNoStemming")
```



## 関連情報

https://docs.couchbase.com/couchbase-lite/3.0/android/fts.html

https://blog.couchbase.com/full-text-search-couchbase-mobile-2-0/