BluemixのCloudantDBでの挿入・削除・更新

Bluemixでnode.jsを使ってアプリを開発している。アプリ作成にDBが必要となり、お手軽に使えそうだなという理由だけで、CloudantDBを使ってみたらハマった。
僕がハマるところは、他人もハマる可能性がある。なのでポイントを書いておこうと思う。

---

#DBへの接続準備

CloudantDBへのコマンドはRestAPIを使う方法が簡単そうなのだが、RestAPIが個人的に（喰わず）嫌いなので「Cloudant」というライブラリを使った。

Bluemixでは、package.jsonに以下のように、cloudantを指定。

```package.json
"dependencies": {
	"cloudant": "1.7.x"
},
```

また、app.jsの冒頭で、データベースへの接続を行う必要がある。
~~親切にも[CloudantライブラリのREADME.md](https://github.com/cloudant/nodejs-cloudant)に親切にBluemixで接続する場合が書かれている。
「Running on Bluemix」で検索するとすぐ探し出せるぞ。~~
コードがたった３行とは、すごくシンプル！
CloudantがIAM対応になった後も使えている。

- 以下コードの　DB_Service_nameには、自分のデータベースサービス名を指定。 例）  Cloudant NoSQL DB-xx
- Database_nameは、データベースサービスの管理画面等で用意したデータベース名を指定。　例）hege_db

```app.js
var Cloudant = require('cloudant');
var cloudant = Cloudant({instanceName: "DB_Service_name", vcapServices: JSON.parse(process.env.VCAP_SERVICES)});
var db = cloudant.db.use('database_name');
```

#ＤＢへのドキュメント（レコード）の挿入
CloudantDBでいうドキュメントとは、RDBでいうレコードみたいなものらしい。
初めは「ドキュメントって何者？」と思った。なので章タイトルに「レコード」と書いてみた。これで読者にも分かりやすくなったと思う。
さて、ＤＢに、ドキュメントをインサートするには、insertメソッドを使用する。
insertメソッドには、ドキュメントのデータを指定する。

以下は、

- 先ほど宣言した、ＤＢ「database_name」にドキュメントを一行追加する。
- ドキュメントのデータは、キー「data1」の値が「abcde」、キー「item1」の値が「fghij」というものとする。
- 追加後の処理を無名のコールバック関数に仕込んである。
- 追加後の結果がエラーだったら「DB Access Error!!!」とログに出力。

という例だ。
システムで必須となるキー「_id」の値は指定しなければ自動的に割り当てられる。

```app.js
db.insert({ data1: 'abcde' , item1: 'fghij'}, function (er, result) {
	if (er) {
		console.log("DB Access Error!!!");
	} else {
		console.log("Write Success Result: %s", JSON.stringify(result));
	}
}); 	
```

#ドキュメントの検索

ＤＢを検索するには、findメソッド、または、searchメソッドを利用する。
私はドキュメントの内容所得まで同時にできてしまうfindメソッドをお勧めする。
##findメソッドによるドキュメントの検索
findメソッドは、ＪＳＯＮ形式のセレクターを指定する。
セレクターの文法は[IBM Cloud資料 -Query](https://console.bluemix.net/docs/services/Cloudant/api/cloudant_query.html)の「Selector Syntax」の章を参照してほしい。

以下の例では、項目dataが「hoge」という条件を満たす全ドキュメントの項目「_id、_rev、data、item」の値を表示するものである。
コードの最初の方でＪＳＯＮ形式のセレクターを生成している。

```app.js
//セレクターを作成
//ここでは、dataという項目が「hoge」であるドキュメントの、_id、_rev、data、itamの項目の値を取ってくる。
var selector_text = {};
selector_text["selector"] = {};
selector_text.selector["data"] = {};
selector_text.selector.data["$eq"] = 'hoge';
selector_text["fields"] = ["_id","_rev","data","item"];
db.find(selector_text, function(er, result) {
	if (er) {
		console.log("DB Access Error!!!");
	} else {
  		console.log('Number of Docs : %d', result.docs.length);
		for (var i = 0; i < result.docs.length; i++) {
			console.log("_id",result.docs[i]._id);
			console.log("_rev",result.docs[i]._rev);
			console.log("data",result.docs[i].data);
			console.log("item",result.docs[i].item);
		}
	}
});

```

##searchメソッドによるドキュメントの検索
searchメソッドは以下の理由であまりお勧め出来ない。

- 検索してヒットしたドキュメントの_idを返してくれるので、それをgetメソッドで利用してレコードの各項目の情報を取るた２度の手間がかかる。
- 該当するドキュメントが２５件以上ある場合、ディフォルトでは初めにヒットした２５件の_idしかわからない[^1]　
- BluemixでCloudantDBサービスを使う場合、プランによって１秒間に実行できるLookups、Queriesに制限があるが、searchメソッドでQuery１つ、getメソッドでLookupsをヒットした_id分消費する。

[^1]: searchメソッドで２６件目以降の_idを知るには、クエリー文字列の第二パラメータに、`{q:'[検索キー値]:[検索文字列]',limit:[件数]}`のようにlimitを指定する。件数は２００まで指定可能。コメントをくれたkazzwachiさんありがとう！

それでも参考までにsearchメソッドについて書く。searchメソッドは、デザインドキュメント名、インディックス名、クエリー文字列の順で指定する。
デザインドキュメント名、インディックス名は、ＤＢサービスの管理画面でインデックス作成時に好きな名前を付けることが出来る。

クエリー文字列は、
`{q:'[検索キー値]:[検索文字列]'}`

という形式で指定する。

以下は、

- キー「data1」の値がabcdから始まるドキュメントが検索される。
- 検索結果は、resultという変数内にrowsという配列が作られ、その配列のキー「id」にドキュメントの_idが格納される。
- 条件を満たすレコードの最初の_idを一個だけログ出力する。

という例だ。

```app.js
db.search('design_document_name', 'index_name', {q:'data1:abcd*'}, function(er, result) {
	if (er) {
		console.log("DB Access Error!!!");
	} else {
		console.log('Showing %d out of a total %d 1st._id :%s', result.rows.length, result.total_rows, result.rows[0].id);
	}
});
```


#ドキュメントの内容取得

serachメソッドで検索した時のように、ドキュメントの\_idのみが分かっている状態で他の値を参照するにはgetメソッドを使う。
getメソッドは、_idを指定する。

以下は、

- searchメソッドで求めた条件に合致する全てのドキュメントの_idを求め、
- 次にgetメソッドでその_idのドキュメントの中のitem1というキーの値を取得してログ出力する。（複数ドキュメントがあれば複数出力する。）

という例だ。

```app.js
db.search('design_document_name', 'index_name', {q:'data1:abcd*'}, function(er, result) {
	if (er) {
		console.log("DB Access Error!!!");
  	} else {
		console.log('Showing %d out of a total %d books by Dickens', result.rows.length, result.total_rows);
		if (result.rows.length === 0) {
//				ドキュメントが一件もないときの処理
		} else {
			for (var i = 0; i < result.rows.length; i++) {
				db.get(result.rows[i].id, function(er2, data) {
					if (er2) {
						console.log("DB Access Error!!!");	
					} else {
					    console.log(data.item1);
					}
				});
	  		}
  		}	
  	}
});
```

#ドキュメントの更新

ＤＢを更新する場合も、insertメソッドを使う。
insertメソッドは、書込みたいデータを指定するのだが、
更新したいときは更新したいドキュメントの**\_idと、_rev**を書込みたい**データに含める**。当初、これに気づかずドキュメントの更新が出来なかった。とても苦労した覚えがある。

以下は、

- 条件に合致したドキュメントのキーitem1の値を12345に書き換える。
- 他の例と違ってデータにＪＳＯＮフォーマットの文字列でなくオブジェクト変数を指定している。この方がデータを組み立てる時に楽なのだ。

という例だ。

```app.js
db.search('design_document_name', 'index_name', {q:'data1:abcd*'}, function(er, result) {
	if (er) {
		console.log("DB Access Error!!!");
  	} else {
		console.log('Showing %d out of a total %d books by Dickens', result.rows.length, result.total_rows);
		if (result.rows.length === 0) {
//				ドキュメントが一件もないときの処理
		} else {
			for (var i = 0; i < result.rows.length; i++) {
				db.get(result.rows[i].id, function(er2, data) {
					if (er2) {
						console.log("DB Access Error!!!");	
					} else {
						//データ組み立て
						var write_doc = {};
						//ここがミソ
						write_doc._rev = data._rev;
						write_doc._id = data._id;
						//ここまでがミソ
						//data1は、書き換えないという例。（読みだした値をそのまま書き込む。）
						write_doc.data1 = data.data1;
						write_doc.item1 = "12345";
						//書き込み
						db.insert(write_doc, function (er3, result2) {
							if (er3) {
								console.log("DB Access Error!!!");
							} else {
								console.log("Write Result: %s", JSON.stringify(result2));
							}
						}); 	
					}
				});
	  		}
  		}	
  	}
});
```

#ドキュメントの削除

ドキュメントの削除は、destroyメソッドを使う。
たった１ドキュメント削除なのにdestroyとは、ちょっとイメージしにくい。
destroyメソッドには、_id と _rev を指定する。
以下は、

- ＤＢから条件に合致するドキュメントをすべて削除する。

という例である。

```app.js
db.search('design_document_name', 'index_name', {q:'data1:abcd*'}, function(er, result) {
	if (er) {
		console.log("DB Access Error!!!");
  	} else {
		console.log('Showing %d out of a total %d books by Dickens', result.rows.length, result.total_rows);
		if (result.rows.length === 0) {
//				ドキュメントが一件もないときの処理
		} else {
			for (var i = 0; i < result.rows.length; i++) {
				db.get(result.rows[i].id, function(er2, data) {
					if (er2) {
						console.log("DB Access Error!!!");	
					} else {
						db.destroy( data._id, data._rev, function(er3, body) {
							if (er3) {
								console.log("DB Access Error!!!");
							} else {
								console.log("Deleate Record");
							}
						});	
					}
				});
	  		}
  		}	
  	}
});
```
#ドキュメント検索時のエラーについて
BluemixのCloudantDBサービスのフリープランは、getメソッドの実行を毎秒２０回までと制限している。
![cloudant_limit.png](https://qiita-image-store.s3.amazonaws.com/0/89689/886c873c-a9bd-97ef-8eee-d8c01ef681f0.png)

制限を超えた場合はメソッドがエラーコードを吐く。適切なエラー処理をしていないとプログラムが強制終了したりもする。
また、searchメソッドはヒットするドキュメントが２０以上あると、２１個目以降の_idを取得する術が不明だ。
よってBluemixのCloudantDBサービスを使う場合は、下図のようにヒットするドキュメントが１つにになるように設計し、１ドキュメントに複数のデータを独自の配列なりで格納するのが良いのかもしれない。

![mameo_data_image2.png](https://qiita-image-store.s3.amazonaws.com/0/89689/5d779769-5e01-61e8-5c4a-fc997ad3c702.png)

#参考にしたサイト
https://github.com/cloudant/nodejs-cloudant
http://nikami.org/cloudant-bluemix-node-js-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%80%E3%83%B3%E3%83%88-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB/