9. 付録: CouchDB基礎文法最速マスター

CouchDBの操作はすべてHTTPで処理されるため。curl を使ってCouchDBを操作することができます。毎回 http://... とするのは面倒なので始める前に環境変数に設定しておくといいです。

$ URL=http://localhost:5984

9.1. インストール

インストール 参照

9.2. データベース機能

9.2.1. データベースの作成

/{db} がデータベースのパスです。PUTで作ります。

$ curl -X PUT ${URL}/hello
{"ok":true}

9.2.2. データベース情報の取得

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}

9.2.3. データベースの一覧

/_all_dbs をGETすると、アクセス先のインスタンスでホストしているデータベースの一覧を配列で取得できます。

$ curl -X GET ${URL}/_all_dbs
["relax", "mydb", "hello"]

9.2.4. データベースの削除

HTTP DELETEで削除してください。

$ curl -X DELETE ${URL}/hello
{"ok":true}

9.2.5. ドキュメントの作成

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"}

9.2.6. ドキュメントの取得

$ curl -X GET ${URL}/hello/mydoc
{"_id":"mydoc","_rev":"1-15f65339921e497348be384867bb940f","hello":"world"}

id, rev はドキュメント上は _id, _rev というフィールドに格納されています。

9.2.7. ドキュメント一覧の取得

/{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"}}
]}

クエリパラメータをつけると限定的ですがフィルタや並び替えができます。まずは、フィルタについて。

key=keyvalue
keyvalue で指定したドキュメントのみを取得します。
startkey=keyvalue
ドキュメントIDがkeyvalue以上のもののみを取得します。順序付けはErlangの文字リストの順序順に従います。
endkey=keyvalue
ドキュメントIDがkeyvalue以下のもののみを取得します。順序付けはErlangの文字リストの順序順に従います。

これらの値を指定するときには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"}
]}

さらに、並び替えや、取得内容の制御には以下のオプションを使います。

descending=true
keyの降順に並び替えます。
limit=N
先頭からN件のドキュメントのみを取得します。
skip=N
先頭N件のドキュメントをスキップします。つまり、limit=m&skip=n のときは、n+1件目からn+m+1件目までのドキュメントを取得します。
include_docs=true
ドキュメント本体も取得する場合に設定します。

9.2.8. ドキュメントの更新

ドキュメントを更新するには、衝突検出のために、最新のバージョン番号を_revフィールドにを渡してあげる必要があります。

$ curl -X PUT -d '{"_id":"mydoc","_rev":"1-15f65339921e497348be384867bb940f", "hello": "new world"}' ${URL}/hello/mydoc
{"ok":true,"id":"mydoc","rev":"2-63fea20ff4f5c3edfa86d0d5dbef3495"}

9.2.9. ドキュメントの削除

削除する場合も最新のバージョン番号を渡す必要があります。この場合はQueryStringに含めます。

$ curl -X DELETE ${URL}/hello/mydoc?rev=2-63fea20ff4f5c3edfa86d0d5dbef3495
{"ok":true,"id":"mydoc","rev":"3-7b7f5056b9c0627259a889ee284780ad"}

9.3. 2. アプリケーションサーバー機能

CouchDBはアプリケーションサーバーとしても動作します。

9.3.1. CouchApp

CouchDBで動くアプリケーションをデプロイするのは、普通にやると非常に面倒なのでここからはCouchAppというツールを使います。python 2.5 以上、setuptools が導入されたマシンで以下を実行します。

$ sudo easy_install couchapp

9.3.2. アプリケーションの作成

まずはローカルファイルシステムにアプリケーションディレクトリを作ります。

$ 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

9.3.3. デプロイ先の設定

.couchapprc にデプロイ先を記述します。

$ vi .couchapprc
{
    "env": {
        "default" : {
            "db" : "http://localhost:5984/hello"
        }
    }
}

9.3.4. デプロイ

push コマンドでデプロイします。

$ couchapp push
[INFO] Visit your CouchApp here:
http://localhost:5984/hello/_design/myapp/index.html

まだ何も作っていませんが、アプリケーションはデプロイされました。ローカルのファイルシステムで編集を加えて再びデプロイする場合も、pushコマンドを叩くだけです。

9.3.5. ドキュメントの表示

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>";
   });
}
  • doc にはアクセスしたドキュメントがマップされたObjectです。
  • req はリクエスト内容(HTTP ヘッダ−等)を格納したObjectです。
  • provides(“フォーマット”, function(){ ... }) とすることでContent-Negotiationに対応できます。フォーマットはhtml,atom,xml,json などがサポートされています。

作った関数には/{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 なにそれ?的な雰囲気が味わえます。

9.4. 3. 大量のドキュメントと向き合う

ここからは、多くのドキュメントを操作する方法を覚えます。

9.4.1. ドキュメントをまとめて更新する

複数のドキュメントを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 ...

9.4.2. データベースへの読み取りクエリ

アプリケーションにビュー(クエリ)を登録するには、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>" ...
  • 結果はrowsに含まれます。
  • emit()に渡した第1引数がkeyフィールド、第2引数がvalueフィールドに格納されています。
  • 並び順は key フィールドの昇順です。

/{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 を一緒に使うことで範囲指定ができます。

9.4.3. データベースへの集計クエリ

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}
]}

9.4.4. クエリ結果を表示する

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>";
   });
}
  • head は、view の結果の total_rows や offset などを格納したオブジェクトです(普通、使いません)。
  • list 処理をするときには、文字列をreturnするよりも、send()を使って適宜ネットワークバッファにフラッシュしてあげたほうが効率的です。
  • getRow() を呼び出すことで、Viewの結果を1行取得できます。最後まで到達するとgetRow()はnullを返します。

作った 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> ...

9.5. 管理者向け操作

9.5.1. 設定の確認、変更

設定は /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を発行して更新されたことを確認してください。

9.5.2. 再起動

設定内容によっては、CouchDBの再起動が必要です。/_restart にPOSTリクエストを送ることで再起動できます。

$ curl -X POST ${URL}/_restart
{"ok":true}

9.6. 最後に

とりあえずこれさえ分かれば基本的なアプリケーションが書ける、という範囲で紹介しました。

他にも、アプリケーションサーバーとして

  • ドキュメントの部分更新
  • ドキュメントの更新時のバリデーション
  • ドキュメントの変更監視

といった機能を提供します。そして、DBとしても

  • 添付ファイルのホスト
  • ACLに基づくアクセス制御機能
  • 任意のデータベースとの双方向レプリケーション

といったこともできますが、それらは 本編 で詳しく説明することにします。

出張ハンズオンも承っておりますので @yssk22 まで。それでは Let’s Relax.

9.6.1. おまけ ーFAQ

  • APIとかWikiに書いていなかったりするんだけどドキュメントないの?

CouchDBのAPI ドキュメントを取得するには、Firefox にFirebugをいれて Futon - http://localhost:5984/_utils/ にアクセスしてください。XHRをモニターすれば、その結果がドキュメントです。

Futon に TestSuite がついていますので、その run all で発生したXHRがすべてのテストされたAPIです。