CouchDBの操作はすべてHTTPで処理されるため。curl を使ってCouchDBを操作することができます。毎回 http://... とするのは面倒なので始める前に環境変数に設定しておくといいです。
$ URL=http://localhost:5984
GETすれば取得できます。
$ curl -X GET ${URL}/hello
{"db_name":"hello","doc_count":0,"doc_del_count":0,"update_seq":0,"purge_seq":0,"compact_running":false,"disk_size":79,"instance_start_time":"1265440722047285","disk_format_version":4}
/_all_dbs をGETすると、アクセス先のインスタンスでホストしているデータベースの一覧を配列で取得できます。
$ curl -X GET ${URL}/_all_dbs
["relax", "mydb", "hello"]
CouchDBはデータ構造の事前定義スキーマがありません。任意のJSONを格納できます。
$ curl -X POST -d '{"hello": "world"}' ${URL}/hello
{"ok":true,"id":"4221ec3941a5fd00995d90bfc08eabdf","rev":"1-15f65339921e497348be384867bb940f"}
id がドキュメントに割り当てられたデータベース内で一意になるキー。revがバージョン番号となります。アンダースコアがついていないので注意してください。
id は自分で指定することもできます。その場合はPUTを使います。/{db}/{doc_id} がドキュメントのパスです。
$ curl -X PUT -d '{"hello": "world"}' ${URL}/hello/mydoc
{"ok":true,"id":"foo","rev":"1-15f65339921e497348be384867bb940f"}
$ curl -X GET ${URL}/hello/mydoc
{"_id":"mydoc","_rev":"1-15f65339921e497348be384867bb940f","hello":"world"}
id, rev はドキュメント上は _id, _rev というフィールドに格納されています。
/{db}/_all_docs ですべてのドキュメントの id, rev を取得できます。
$ curl -X GET ${URL}/hello/_all_docs
{"total_rows":2,"offset":0,"rows":[
{"id":"4221ec3941a5fd00995d90bfc08eabdf","key":"4221ec3941a5fd00995d90bfc08eabdf","value":{"rev":"1-15f65339921e497348be384867bb940f"}},
{"id":"mydoc","key":"mydoc","value":{"rev":"4-5e59652bcdd21e0e644b081bc19cb676"}}
]}
クエリパラメータをつけると限定的ですがフィルタや並び替えができます。まずは、フィルタについて。
これらの値を指定するときにはkeyvalueをJSONで渡す必要があります。つまり、ダブルクオートで囲ってください。
$ curl -X GET ${URL}/hello/_all_docs?'startkey="mydoc"'
{"total_rows":2,"offset":2,"rows":[
{"id":"mydoc","key":"mydoc","value":{"rev":"4-5e59652bcdd21e0e644b081bc19cb676"}
]}
さらに、並び替えや、取得内容の制御には以下のオプションを使います。
ドキュメントを更新するには、衝突検出のために、最新のバージョン番号を_revフィールドにを渡してあげる必要があります。
$ curl -X PUT -d '{"_id":"mydoc","_rev":"1-15f65339921e497348be384867bb940f", "hello": "new world"}' ${URL}/hello/mydoc
{"ok":true,"id":"mydoc","rev":"2-63fea20ff4f5c3edfa86d0d5dbef3495"}
削除する場合も最新のバージョン番号を渡す必要があります。この場合はQueryStringに含めます。
$ curl -X DELETE ${URL}/hello/mydoc?rev=2-63fea20ff4f5c3edfa86d0d5dbef3495
{"ok":true,"id":"mydoc","rev":"3-7b7f5056b9c0627259a889ee284780ad"}
CouchDBはアプリケーションサーバーとしても動作します。
CouchDBで動くアプリケーションをデプロイするのは、普通にやると非常に面倒なのでここからはCouchAppというツールを使います。python 2.5 以上、setuptools が導入されたマシンで以下を実行します。
$ sudo easy_install couchapp
まずはローカルファイルシステムにアプリケーションディレクトリを作ります。
$ couchapp generate myapp
$ cd myapp
$ ls -al
total 24
drwxr-xr-x 11 yssk22 staff 374 2 6 15:04 .
drwxr-xr-x 24 yssk22 staff 816 2 6 15:04 ..
-rw-r--r-- 1 yssk22 staff 2 2 6 15:04 .couchapprc
drwxr-xr-x 4 yssk22 staff 136 2 6 15:04 _attachments
-rw-r--r-- 1 yssk22 staff 13 2 6 15:04 _id
-rw-r--r-- 1 yssk22 staff 70 2 2 23:38 couchapp.json
drwxr-xr-x 2 yssk22 staff 68 2 6 15:04 lists
drwxr-xr-x 2 yssk22 staff 68 2 6 15:04 shows
drwxr-xr-x 2 yssk22 staff 68 2 6 15:04 updates
drwxr-xr-x 3 yssk22 staff 102 2 6 15:04 vendor
drwxr-xr-x 2 yssk22 staff 68 2 6 15:04 views
.couchapprc にデプロイ先を記述します。
$ vi .couchapprc
{
"env": {
"default" : {
"db" : "http://localhost:5984/hello"
}
}
}
push コマンドでデプロイします。
$ couchapp push
[INFO] Visit your CouchApp here:
http://localhost:5984/hello/_design/myapp/index.html
まだ何も作っていませんが、アプリケーションはデプロイされました。ローカルのファイルシステムで編集を加えて再びデプロイする場合も、pushコマンドを叩くだけです。
shows ディレクトリにJavaScriptを配置すると、1つのドキュメントを表示するページを作ることができます。Railsのshowみたいなもので、JavaScriptのFunctionオブジェクトとしてファイルに定義します。
$ vi shows/hello.js
function(doc, req){
provides("html", function(){
return "<html>" +
"<body>" +
"<p>Hello " + doc.hello + "</p>" +
"</body>" +
"</html>";
});
}
作った関数には/{db}/_design/{app}/_show/{show}/{doc_id} でアクセスできます。{app} は couchapp generate 時に渡したディレクトリ名、{show} は shows ディレクトリに作ったファイルの名前です。
$ curl -X PUT -d '{"hello": "world"}' ${URL}/hello/mydoc # データ追加
{"ok":true,"id":"foo","rev":"1-15f65339921e497348be384867bb940f"}
$ couchapp push # 再デプロイ
$ curl -X GET ${URL}/hello/_design/myapp/_show/hello/mydoc
<html><body><p>Hello world</p></body></html>
mydoc 以外にもいろいろドキュメントを追加して試してみてください。O/R Mapper なにそれ?的な雰囲気が味わえます。
ここからは、多くのドキュメントを操作する方法を覚えます。
複数のドキュメントを1トランザクションでまとめて更新するには /{db}/_bulc_docs に対してPOSTを送信します。リクエストボディに所定フォーマットでドキュメントの配列を埋め込んでおきます。
所定のフォーマットは以下のような形になります。尚、便宜上 // でコメントをいれていますが、JSONにはコメントというスペックはありませんので、実際に試す場合はコメント部分は削除してください。
$ vi bulk.json
{
"docs" : [
{ "hello" : "world" }, // 新しいドキュメントを追加(id指定なし)
{ "_id" : "hoge", "hello" : "world" }, // 新しいドキュメントを追加(idにhogeを指定)
{ "_id" : "foo", "_rev" : "3-xxxxxxx", "Hello" : "Relax"}, // 既存のドキュメント foo を更新
{ "_id" : "bar", "_rev" : "3-xxxxxxx", "_deleted" : true } // 既存のドキュメント bar を削除
]
}
$ curl -X POST --data @bulk.json ${URL}/hello/_bulk_docs
[ {"id" : "d0c97c1c72b12271a399ae3638ad0e54", "rev" : "1-xxxx" },
{"id" : "d0c97c1c72b12271a399ae3638ad0e54", "rev" : "1-yyyy" },
{"id" : "d0c97c1c72b12271a399ae3638ad0e54", "rev" : "4-mmmm" },
{"id" : "d0c97c1c72b12271a399ae3638ad0e54", "rev" : "4-nnnn" },
]
sample_data/bulk.json にサンプルデータが含まれているので、追加してみてください。このデータは、twitter.com/yssk22 のtweetデータを抽出したものです。
$ curl -X POST --data @bulk.json ${URL}/hello/_bulk_docs
[{"id":"5a6bf152e1c80eeb92f18568b40f4597","rev":"1-52086164178dfba4b3a3222ca515bb6c"},{"id":"aca8f5a639f0a0ae8ce850c8fd770a18","rev":"1-acafd745e6980f2f56d3da4e19576743"},{"id":"860e65bfca74912a744cab529556816f","rev ...
アプリケーションにビュー(クエリ)を登録するには、views/{view}/map.js と views/{view}/reduce.js を用意します。reduce.js はオプションです。それぞれのファイルに、Functionオブジェクトを記述します。
まずは map only view です。ドキュメントのcreated_atフィールドをキーにmapします。
$ mkdir views/sample
$ vi views/sample/map.js
function(doc){ // doc は1つのドキュメント
if(doc.created_at){
emit(new Date(doc.created_at), doc);
}
}
map.js で定義したMap Functionはそれぞれのドキュメント毎に呼び出されます。結果を出力するにはemit(key, value)関数を用います。
このアプリケーションをデプロイし、結果を確認します。結果のURLは /{db}/_design/{app}/_view/{view} です。
$ couchapp push # 再デプロイ
$ curl -X GET ${URL}/hello/_design/myapp/_view/sample
{"total_rows":102,"offset":0,"rows":[
{"id":"d0c97c1c72b12271a399ae3638ad0e54","key":"2010-02-03T14:43:53Z","value":{"_id":"d0c97c1c72b12271a399ae3638ad0e54","_rev":"1-38239d5d9da31eabc6a2a465f5c6dcd3","favorited":false,"contributors":null,"truncated":false,"text":"\u3069\u3046\u8003\u3048\u3066\u3082SpiderMonkey\u304c\u308f\u308b\u3044\u306a","id":8589909168,"geo":null,"source":"<a href=\"http://sites.google.com/site/yorufukurou/\" rel=\"nofollow\">YoruFukurou</a>" ...
/{db}/_all_docs で使用したクエリパラメータは、ビューの結果に対しても使うことができます。例えば、2010-02-05T14:30以降のtweetを拾うには次のようにします。
$ curl -X GET ${URL}/hello/_design/myapp/_view/sample?'endkey="2010-02-05T14:30:00"&descending=true'
startkey, endkey を一緒に使うことで範囲指定ができます。
reduce.js を使うことで、集計操作が行えます。曜日と時間毎の発言回数をカウントします。
まずはmap関数で、曜日と時間を抽出します。value には回数を数えるために1を与えます。
$ mkdir views/sample2
$ vi views/sample2/map.js
function(doc){
if(doc.created_at){
var dt = new Date(doc.created_at);
emit([dt.getDay(), dt.getHours()], 1);
}
}
さらにreducek関数でmapの結果を数えます。
$ vi views/sample2/reduce.js
function(keys, values, rereduce){
return sum(values);
}
これを登録し、結果を確認します。
$ couchapp push
$ curl -X GET ${URL}/hello/_design/myapp/_view/sample2
{"rows":[
{"key":null,"value":100}
]}
デフォルトではすべてのキーにreduce関数を実行した結果を返します。group=true を指定することで同じキー毎にグループ化してreduce関数を発行します。。
$ curl -X GET ${URL}/hello/_design/myapp/_view/sample2?group=true
{"rows":[
{"key":[3,23],"value":8},
{"key":[4,0],"value":9},
{"key":[4,7],"value":1},
{"key":[4,8],"value":3},
{"key":[4,9],"value":1},
{"key":[4,19],"value":1},
{"key":[4,20],"value":7},
{"key":[4,21],"value":2},
{"key":[4,22],"value":22},
{"key":[4,23],"value":24},
{"key":[5,0],"value":2},
{"key":[5,21],"value":2},
{"key":[5,22],"value":6},
{"key":[5,23],"value":10},
{"key":[6,10],"value":2}
]}
平日の昼間にtweetしている暇がないことがわかります。
ところで、キーに[曜日,時間]の配列を渡していますが、キーが配列の場合は、group_levelパラメータを使ってグループ化する深さを指定することができます。group_levelパラメーターにはグループ化する配列のインデックス+1を渡します。
曜日だけでグループ化するため、group_level=1をクエリに指定します。
$ curl -X GET ${URL}/hello/_design/myapp/_view/sample2?'group=true&group_level=1'
{"rows":[
{"key":[3],"value":8},
{"key":[4],"value":70},
{"key":[5],"value":20},
{"key":[6],"value":2}
]}
lists ディレクトリにJavaScriptを配置すると、クエリ結果を表示するページを作ることができます。
$ vi lists/timeline.js
function(head, req){
provides("html",function(){
var row;
send("<html><body>");
send("<ul>");
while(row = getRow()){
send("<li>" + row.key + ": " + row.value.text + "</li>");
}
send("</ul>");
return "</body></html>";
});
}
作った list 処理にアクセスするには /{db}/_design/{app}/_list/{list}/{view} を使ってアクセスできます。ビュー(クエリ)を指定する必要があることに注意してください。list処理はビューの結果のドキュメントフォーマットを知っておく必要があります。
$ curl -X GET ${URL}/hello/_design/myapp/_list/timeline/sample
<html><body><ul><li>2010-02-03T14:43:53Z: どう考えてもSpiderMonkeyがわるいな</li><li>2010-02-03T14:47:27Z: couchjs からやる限りはOKだし。。</li><li>2010-02-03T14:48:25Z: toString がこけるのかー</li><li>2...
list に対してはViewと同じクエリパラメータが使えます。
$ curl -X GET ${URL}/hello/_design/myapp/_list/timeline/sample?'endkey="2010-02-03T14:30"&descending=true' | more
<html><body><ul><li>2010-02-06T01:17:30Z: いまDellのノートPCについているグラボがPCI Ex x16 だ。。</li><li>2010-02-06T01:10:22Z: さむい</li><li>2010-02-05T14:28:45Z: とりあえず今日のところは svn merge 疲れなので寝る。</li><li>2010-02-05T14:27:02Z: 明日、この流れに乗ってCouchDB基礎文法最速マスターを書くんだ。と宣言しておけばきっとやる。</li><li>2010-02-05T14:26:12Z: Erlang http://bit.ly/bpFUO8 おお。</li> ...
設定は /usr/local/etc/couchdb/local.ini などにあるのですが、/_config/{section}/{key} で操作可能できるので、編集ミスで起動しなくなる、などを防ぐためにそちらを使いましょう。
取得する場合はGET、更新する場合はPUTです。PUTする場合は、例によってJSONを送るので、文字列をダブルクオートで囲んで送ってください。
$ curl -X GET ${URL}/_config/httpd/bind_address
"127.0.0.1"
$ curl -X PUT --data '"0.0.0.0"' ${URL}/_config/httpd/bind_address
"127.0.0.1"
$ curl -X GET ${URL}/_config/httpd/bind_address
"0.0.0.0"
PUTでは現在の設定内容が返ってきてしまうので、直後にGETを発行して更新されたことを確認してください。
設定内容によっては、CouchDBの再起動が必要です。/_restart にPOSTリクエストを送ることで再起動できます。
$ curl -X POST ${URL}/_restart
{"ok":true}
とりあえずこれさえ分かれば基本的なアプリケーションが書ける、という範囲で紹介しました。
他にも、アプリケーションサーバーとして
といった機能を提供します。そして、DBとしても
といったこともできますが、それらは 本編 で詳しく説明することにします。
出張ハンズオンも承っておりますので @yssk22 まで。それでは Let’s Relax.
CouchDBのAPI ドキュメントを取得するには、Firefox にFirebugをいれて Futon - http://localhost:5984/_utils/ にアクセスしてください。XHRをモニターすれば、その結果がドキュメントです。
Futon に TestSuite がついていますので、その run all で発生したXHRがすべてのテストされたAPIです。