Couchbase Lite機能 解説：配列データへのクエリ

## はじめに

ここでは、Couchbase Liteデータベースで配列を含むJSONドキュメントへクエリする方法について解説します。

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

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

また、Couchbase Mobileは、Couchbase LiteとCouchbase Serverとのデータ同期機能を提供します。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)にまとめてある記事をご参考ください。

## 配列データへのクエリ

配列は、JSONデータモデリングの不可欠な要素です。ここでは、配列データへのクエリについて説明します。

ここで説明する例ではiOS用のSwiftを使用していますが、シンタックス上の違いはあれど、同じクエリインターフェイスがAndroid等他のプラットフォームでも利用可能です。

### サンプルプロジェクト

サンプルのSwiftプロジェクトに興味がある場合は、以下の手順に従ってください

GitHubからiOS Swift Playgroundのクローンを作成します

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

対応するREADMEファイルのインストール手順に従って、構築して実行します。

### データモデル

ここでは、ホテルに関する情報を表す以下のJSONドキュメントモデルを用いて説明します。
（簡潔化のために、サンプルデータのモデルからいくつかのプロパティを省略しています）。

このデータモデルには、文字列の配列（`public_likes`）や、ネストされたオブジェクトを要素として持つ配列(`reviews`)が含まれています。

```json
 {
   "type": "hotel",
   "name": "Medway Youth Hostel",
   "address": "Capstone Road, ME7 3JE",
   "city": "Medway",
   "country": "United Kingdom",
   "description": "blah blah",
   "public_likes": [
     "Julius Tromp I",
     "Corrine Hilll"
   ],
   "reviews": [
   {
     "author": "Ozella Sipes",
     "content": "blah blah.",
     "date": "2013–06–22 18:33:50 +0300",
     "ratings": {
       "Cleanliness": 5,
       "Location": 4,
       "Overall": 4,
       "Rooms": 3,
       "Service": 5,
       "Value": 4
     }
   },
 {
   "author": "Jeremy Snapes",
   "content": "blah blah.",
   "date": "2013–05–05 18:33:50 +0300",
   "ratings": {
       "Cleanliness": 2,
       "Location": 2,
       "Overall": 4,
       "Rooms": 3,
       "Service": 5,
       "Value": 4
     }
   }
 ],
  "url":"http://www.yha.org.uk",
  "vacancy": true
}
```


## 配列の要素による検索

以下の例では、`public_like`配列プロパティに「Corrine Hillll」の値が含まれているドキュメントを検索しています。

```swift
let searchQuery = QueryBuilde.select(SelectResult.expression(Meta.id),
                  SelectResult.expression(Expression.property("name")),
                  SelectResult.expression(Expression.property("public_likes")))
 .from(DataSource.database(db))
 .where(Expression.property("type").equalTo(Expression.string("hotel"))
 .and(ArrayFunction.contains(Expression.property("public_likes"), value: Expression.string("Corrine Hilll"))))
```

`ArrayFunction.contains`のように、`ArrayFunction`クラスの`contains`(スタティック)メソッドを利用します。

## 配列サイズ取得

以下の例では、 配列のサイズを取得します。 

```swift
 let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id),
           SelectResult.expression(Expression.property("name")),
           SelectResult.expression(ArrayFunction.length(Expression.property("public_likes"))).as("NumLikes"))
 .from(DataSource.database(db))
 .where(Expression.property("type").equalTo(Expression.string("hotel")))
 .limit(Expression.int(limit))
```

`ArrayFunction.length(Expression.property("public_likes"))`のように、`ArrayFunction`クラスの`length`(スタティック)メソッドを利用します。

また、`as("NumLikes"))`のように、サイズを取得した結果に「NumLikes」という名前をつけます。
式の結果をエイリアスしない場合、プロパティキーは自動的に生成されるため、明示的に名前をつけることによって、プログラムの可読性を増すことができます。

## 配列メンバーの評価

`ArrayFunction.contains`メソッドを使用して、指定された配列に特定の値が含まれているかどうかを確認できることを紹介しましたが、Couchbase Liteには、より、強力なフィルタリング機能が用意されています。

以下の例は、`public_like`配列プロパティに「Cor」から始まる値が含まれているドキュメントを検索しています。

```swift
 let VAR_LIKEDBY = ArrayExpression.variable("likedby")
 
 let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id),
                  SelectResult.expression((Expression.property("public_likes"))))
.from(DataSource.database(db))
.where(Expression.property("type").equalTo(Expression.string("hotel"))
.and(ArrayExpression.any(VAR_LIKEDBY).in(Expression.property("public_likes"))
.satisfies(VAR_LIKEDBY.like(Expression.string("Cor%")))))
.limit(Expression.int(limit))<code>
```

まず、`public_likes`配列内のすべての要素(要素のイテレーション)を表すために、`VAR_LIKEDBY`という変数を宣言しています（ここで用いる名称は任意のものです。いわばFORループの内部で用いられる変数と考えると理解しやすいでしょう）。

この変数と評価対象の配列との組み合わせで、`ArrayExpression.any(VAR_LIKEDBY).in(Expression.property("public_likes"))`のように、`an <変数>　in <配列>`という構文を用いて、クエリを構築します。


それに続く、`.satisfies(VAR_LIKEDBY.like(Expression.string("Cor%")))`では、満たすべき条件〜項目の値が「Cor」で始まるかどうか〜チェックしています。

### インデックスによる配列アクセス

以下のように、インデックス指定により要素をクエリすることができます。

```swift
 let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id),
           SelectResult.expression(Expression.property("name")),
           SelectResult.expression(Expression.property("public_likes[0]")))
 .from(DataSource.database(db))
 .where(Expression.property("type").equalTo(Expression.string("hotel")))
 .limit(Expression.int(limit))
```

ここで、`Expression.property("public_likes[0]")`は、`public_likes`配列のはじめの要素を指します。


## ネストされた配列の評価

ネストされた配列のメンバーを評価できます。

上記のデータモデルからわかるように、`reviews`プロパティはオブジェクトの配列を保持します。各オブジェクトには`ratings`という（ディクショナリ）プロパティが含まれています。

次のクエリ、複数種類存在するレーティングの内、`Overall`での評価が4以上のドキュメントを返します。

```swift

 let VAR_OVERALL = ArrayExpression.variable("review.ratings.Overall")

 let VAR_REVIEWS = ArrayExpression.variable("review")

 let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id),
          SelectResult.expression(Expression.property("name")))
 .from(DataSource.database(db))
 .where(Expression.property("type").equalTo(Expression.string("hotel"))
 .and(ArrayExpression.any(VAR_REVIEWS).in(Expression.property("reviews"))
 .satisfies(VAR_OVERALL.greaterThanOrEqualTo(Expression.int(4)))))
 .limit(Expression.int(limit))
```


まず、`review.ratings.Overall`要素を表す変数を宣言しています。

 そして、`reviews`配列内のすべての要素を表す変数を宣言しています

`ArrayExpression.any(VAR_REVIEWS).in(Expression.property("reviews"))
 .satisfies(VAR_OVERALL.greaterThanOrEqualTo(Expression.int(4))`が条件指定箇所ですが、`any ~ in ~ satisfies`という全体の構造は、上述の単純なケースと同じであることが同じです。

`satisfies`メソッドの引数として、ネストされたオブジェクトのプロパティをドット表記で指定した変数を定義することによって`.greaterThanOrEqualTo(Expression.int(4))`のように、そのプロパティの値を条件として、フィルタリングを行うことができます。



#### 関連情報

https://blog.couchbase.com/querying-array-collections-couchbase-mobile/