CouchDB のきほんチートシート curl 編

# この解説の環境

- CouchDB-1.6.1
    - デフォルトの `127.0.0.1:5984` でサービスが稼働
- Ubuntu-16.04
- curl-7.47.0

# 諸注意

- 記事中の rev 値はすべて例であり、実行環境により異なる値が生成されるものです。（コピペで操作練習などする場合はこの点に気をつけて下さい。）
- 「基本操作」、「応用操作」、「初級」、「中級」、「変態」などは何れも「この記事の中での度合いの話」です。より多くの知識が必要になれば [Apache - CouchDB Wiki](http://wiki.apache.org/couchdb/) を読むなり [stackoverflow](http://stackoverflow.com/) しましょう。
- Qiita の特性上ドキュメントであってシートではありませんがチートシート的に基本操作をざくざくと掲載したドキュメントです。

# 基本操作

## 動作確認: GET host:port

`curl -X GET 127.0.0.1:5984`

> {"couchdb":"Welcome","uuid":"4fa79ddb179218126c1245256d74878f","version":"1.6.1","vendor":{"version":"16.04","name":"Ubuntu"}}

## 組み込みウェブGUI `Futon` の動作確認

`http://127.0.0.1:5984/_utils/`

![image](https://qiita-image-store.s3.amazonaws.com/0/33891/e29e9fd6-f8cf-8a6e-6d0f-6515d76ee788.png)

- note: 但し、この記事では futon による操作は扱わない
    - tips: もし CouchDB がリモートのサーバーで動作している場合 `ssh` に `-L 5984:remote.example.net:5984` のようにパラメーターを追加、トンネリングし、 futon にローカルのリッチなウェブブラウザーで接続すると捗るでしょう。たぶん `w3m` とかだと厳しいでしょう。

## database

### 作成: PUT host:port/database

`curl -X PUT 127.0.0.1:5984/test`

return 例（成功）:

> {"ok":true}

return 例（失敗:既に database が存在している）:

> {"error":"file_exists","reason":"The database could not be created, the file already exists."}

### 削除 DELETE host:port/database

`curl -X DELETE 127.0.0.1:5984/test`

return 例（成功）:

> {"ok":true}

return 例（失敗: database が存在しない）:

> {"error":"not_found","reason":"missing"}

## document

### 取得: GET host:port/database/document

`curl -X GET 127.0.0.1:5984/test/aaa`

> {"_id":"aaa","_rev":"1-cd869af19a904a384902a38ee92962f0","xxx":111,"yyy":222,"zzz":{"a":3,"b":4,"c":[5,6,7]}}

### 作成: PUT host:port/database/document + JSONデータ

以下の何れの方法で可能。`PUT`の場合は　URL に `_id` となる値が必須。 `POST` の場合に、データに `"_id"`が含まない場合は `"id"` は自動生成となる。何れの場合も結果に生成された "`id`" と "rev" が返る。

- `curl -X PUT 127.0.0.1:5984/test/aaa -d '{"xxx":111,"yyy":222,"zzz":{"a":3,"b":4,"c":[5,6,7]}}'`
- `curl -X POST 127.0.0.1:5984/test -H 'content-type: application/json' -d '{"xxx":111,"yyy":222,"zzz":{"a":3,"b":4,"c":[5,6,7]}}'`

return 例（成功）:

> {"ok":true,"id":"aaa","rev":"1-cd869af19a904a384902a38ee92962f0"}

return 例（失敗: database が存在しない）

> {"error":"not_found","reason":"no_db_file"}

return 例（失敗: 投げたJSONデータが不正）

> {"error":"bad_request","reason":"invalid_json"}

### 更新: PUT host:port/database/document + JSONデータ + rev値

以下の方法は何れも等価。

- `curl -X PUT 127.0.0.1:5984/test/aaa?rev="1-cd869af19a904a384902a38ee92962f0" -d { "hoge": "fuga" }`
- `curl -X PUT 127.0.0.1:5984/test/aaa -d { "_rev": "1-cd869af19a904a384902a38ee92962f0", "hoge": "fuga" }`

return 例（成功）:

> {"ok":true,"id":"aaa","rev":"2-1f448729059b06b92dfee732d83f031c"}

return 例（失敗: rev値が不正）:

> {"error":"conflict","reason":"Document update conflict."}

return 例（失敗: rev値の書式が不正）:

> {"error":"bad_request","reason":"Invalid rev format"}

### 削除: DELETE host:port/database/document + rev値

以下の方法は何れも等価。

- `curl -X DELETE 127.0.0.1:5984/test/aaa?rev="1-cd869af19a904a384902a38ee92962f0"`
- `curl -X PUT 127.0.0.1:5984/test/aaa -d { "_rev": "1-cd869af19a904a384902a38ee92962f0", "_deleted": true }`

return 例（成功）:

> {"ok":true,"id":"aaa","rev":"2-1f448729059b06b92dfee732d83f031c"}

return 例（失敗: rev値が不正）:

> {"error":"conflict","reason":"Document update conflict."}

return 例（失敗: rev値の書式が不正）:

> {"error":"bad_request","reason":"Invalid rev format"}

- note: document の削除の"実態"は「削除マークを有効にして更新」しているだけ
    - そのため、削除した document と同じ id の document を作ると rev 値だけみれば削除時点+1で"復活"したかのようになる

### 存在と rev 値のみ確認

- note: CouchDB は HTTP RESPONSE HEADER にも有用な使い方、情報の提供がある

`curl -X GET 127.0.0.1:5984/test/aaa --head`

aaa が存在する時:

```http
HTTP/1.1 200 OK
Server: CouchDB/1.6.1 (Erlang OTP/18)
ETag: "8-0517d1736e63d34d91db334b97a2a94b"
Date: Tue, 30 Aug 2016 20:21:39 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 58
Cache-Control: must-revalidate
```

- note: `ETag` に rev 値が得られている

aaa が存在しない時:

```http
HTTP/1.1 404 Object Not Found
Server: CouchDB/1.6.1 (Erlang OTP/18)
Date: Tue, 30 Aug 2016 20:21:54 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 41
Cache-Control: must-revalidate
```

### 添付追加

`curl -X PUT 127.0.0.1:5984/test/aaa/hoge.png?rev="2-1f448729059b06b92dfee732d83f031c" -H 'content-type: image/png' --data-binary @./hoge.png`

note: `curl` の `-d` 系パラメーターの値に `@` はファイルからの読み出し

return 例（成功）:

{"ok":true,"id":"aaa","rev":"3-45f180d2f1d7873ff57d0eff3f08c6ba"}

### 添付削除

`curl -X DELETE 127.0.0.1:5984/test/aaa/hoge.png?rev="3-45f180d2f1d7873ff57d0eff3f08c6ba"`

return 例（成功）:

> {"ok":true,"id":"aaa","rev":"4-8858de7dc39c5d3453df99460a10eb96"}

return 例（失敗: rev付け忘れ）:

> {"error":"conflict","reason":"Document update conflict."}

# 応用操作・初級

## 一括操作: POST + `_bulk_docs`

### 一括で個別に操作: POST + `_bulk_docs`

いわゆる SIMD 処理。以下は「documentを作成」という SI に `[ { "_id": "bbb", "value": "456" }, { "value": "789" } ]` と2件の MD を投げる例。

- note 1: `"_id"` の無いデータを投げると "`_id`" は自動生成される（host:port/database/document でいう document の部分）
- note 2: 更新操作を行いたい場合はデータへの rev 値を入れ忘れに注意
- note 3: 削除操作を行いたい場合はデータへの rev 値に加えて `"_deleted": true` を追加して投げる
- note 4: デフォルト動作の _bulk_docs の処理はデータごと個別に行われ成否はデータごとに発生する

`curl -X POST 127.0.0.1:5984/test/_bulk_docs -H 'content-type: application/json' -d '{ "docs": [ { "_id": "bbb", "value": "456" }, { "value": "789" } ] }'`

return 例（全て成功）:

> [{"ok":true,"id":"bbb","rev":"1-dc0e9d8e2fd80f086d4cc36b8eeae9e5"},{"ok":true,"id":"56b000058f5a060388a4da64e7002d11","rev":"1-0d145e34d9c50794b0a54ac3feef0d3a"}]

return 例（一部は成功、一部は失敗）:

> [{"id":"bbb","error":"conflict","reason":"Document update conflict."},{"ok":true,"id":"56b000058f5a060388a4da64e7003056","rev":"1-0d145e34d9c50794b0a54ac3feef0d3a"}]

### 一括でアトミックに操作: POST `_bulk_docs` + `all_or_nothing`

_bulk_docs のデータに `"all_or_nothing": true` を付けると投げた操作全体のアトミック操作となる。

`curl -X POST 127.0.0.1:5984/test/_bulk_docs -H 'content-type: application/json' -d '{ "all_or_nothing": true, "docs": [ { "_id": "bbb", "value": "456", "value2": 654 }, { "value": "789", "_deleted":true } ] }'`

return 例（偽性の成功≃事実上の失敗: 一部は成功、一部は失敗 ）:

既に `"_id": "bbb"` が存在する database に対して ↑`curl`コマンド例を投げた場合にも以下のように"全て"のデータに対する処理が `"ok":true` で返ってくる。しかし、 `"rev"` が更新されていない事
でも分かるように実際には処理は成功していない。

> [{"ok":true,"id":"bbb","rev":"1-956fc4802a21507c64768b30cfce1d15"},{"ok":true,"id":"56b000058f5a060388a4da64e7007c52","rev":"1-7a6605c8b61217d1d8785683b9790719"}]

## ドキュメントの検索: GET + `_all_docs`

### id と rev のみ取得: GET + `_all_docs`

`curl -X GET 127.0.0.1:5984/test/_all_docs`

return 例（成功）:

> {"total_rows":3,"offset":0,"rows":[
{"id":"aaa","key":"aaa","value":{"rev":"1-c8227456e13a740a3a950be456a85f21"}},
{"id":"bbb","key":"bbb","value":{"rev":"1-ea6f81f73a3bf9c1dc3391565b740870"}},
{"id":"ccc","key":"ccc","value":{"rev":"1-5bac6f87d40c8d92da4a0586361bb950"}}
]}

### id と rev のみ取得: GET + `_all_docs` + `include_docs`

`curl -X GET 127.0.0.1:5984/test/_all_docs?include_docs=true`

- note: `true` は暗黙の型変換の期待や省略時のデフォルト値など無いので `true` まで書く

return 例（成功）:

> {"total_rows":3,"offset":0,"rows":[
{"id":"aaa","key":"aaa","value":{"rev":"1-c8227456e13a740a3a950be456a85f21"},"doc":{"_id":"aaa","_rev":"1-c8227456e13a740a3a950be456a85f21","k1":100,"k2":101,"k3":102}},
{"id":"bbb","key":"bbb","value":{"rev":"1-ea6f81f73a3bf9c1dc3391565b740870"},"doc":{"_id":"bbb","_rev":"1-ea6f81f73a3bf9c1dc3391565b740870","k1":200,"k2":201,"k3":202}},
{"id":"ccc","key":"ccc","value":{"rev":"1-5bac6f87d40c8d92da4a0586361bb950"},"doc":{"_id":"ccc","_rev":"1-5bac6f87d40c8d92da4a0586361bb950","k1":300,"k2":301,"k3":302}}
]}

### 特定の key （≃ id ）のみ取得: GET + `_all_docs` + `key`

`curl -X GET '127.0.0.1:5984/test/_all_docs?key={"aaa","ccc"}&include_docs=true'`

- note: `?` の後はいわゆるクエリーで `key` や `include_docs` など複数のパラメーターを `&` で組み合わせて投げられる
- note: `key` には他に `keys`, `startkey` または `startkey_docid` と `endkey` または `endkey_docid` といった亜種もある --> [CouchDB Wiki - Query Options](http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options)

return 例（成功）:

> [1/2]: 127.0.0.1:5984/test/_all_docs?key="aaa"&include_docs=true --> <stdout>
--_curl_--127.0.0.1:5984/test/_all_docs?key="aaa"&include_docs=true
{"total_rows":3,"offset":0,"rows":[
{"id":"aaa","key":"aaa","value":{"rev":"1-c8227456e13a740a3a950be456a85f21"},"doc":{"_id":"aaa","_rev":"1-c8227456e13a740a3a950be456a85f21","k1":100,"k2":101,"k3":102}}
]}
> 
> [2/2]: 127.0.0.1:5984/test/_all_docs?key="ccc"&include_docs=true --> <stdout>
--_curl_--127.0.0.1:5984/test/_all_docs?key="ccc"&include_docs=true
{"total_rows":3,"offset":2,"rows":[
{"id":"ccc","key":"ccc","value":{"rev":"1-5bac6f87d40c8d92da4a0586361bb950"},"doc":{"_id":"ccc","_rev":"1-5bac6f87d40c8d92da4a0586361bb950","k1":300,"k2":301,"k3":302}}
]}

# 応用操作・中級

##  design/view (≃ Map Reduce)

- note: 検索機能はいわゆる Map Reduce 関数を定義した view を database の design に登録して使う
    - map: 射影変換（≃「入力」を「ごにょごにょ変換」して「出力」）
    - reduce: 集計（≃「入力」を「整理」して「出力」）

### 任意の条件の絞り込み検索を行う: view/map emit

#### view の作成

- note 2: デザインをワンライナーで書くのは辛いので .json ファイルを用意して `curl -d @` で投げる事にします。

d1.json:

```json
{ "language": "javascript"
, "views":
  { "v1":
    { "map":
        "function ( d )
        {
          if ( d.k1 > 200 )
            emit( d._id, { 'k1': d.k1, 'k2 + k3': d.k2 + d.k3 } );
        }"
    }
  , "v2":
    { "map":
        "function ( d )
        {
          if ( d.k2 > 200 )
            emit( d._id + '-' + d.k3, null );
        }"
    }
  }
}
```

- note 1: 組み込み関数 `emit` のシグネチャーをわかりやすく書くと、 `function emit( key, value );`
- note 2: `language` は `javascript` のほか `coffeescript` でも書ける事になっている
    - 省略する事も可能で、その場合は `javascript` として扱われる

`curl -X PUT 127.0.0.1:5984/test/_design/d1 -H 'content-type: application/json' -d @./d1.json`

return（成功）:

> {"ok":true,"id":"_design/d1","rev":"1-b0a42e17ff9d67143d6ef16174ee894c"}

- note: design も他の一般的なドキュメント同様に作成・更新・削除など行える

#### design の取得

`curl -X GET 127.0.0.1:5984/test/_design/d1`

return:

> {"_id":"_design/d1","_rev":"1-b0a42e17ff9d67143d6ef16174ee894c","language":"javascript","views":{"v1":{"map":"function ( d )        {          if ( d.k1 > 200 )            emit( d._id, { 'k1': d.k1, 'k2 + k3': d.k2 + d.k3 } );        }"},"v2":{"map":"function ( d )        {          if ( d.k2 > 200 )            emit( d._id + '-' + d.k3, null );        }"}}}

見難いので [JSON FORMATTER & VALIDATOR](https://jsonformatter.curiousconcept.com/) で format すると:

```json
{
  "_id":"_design/d1",
  "_rev":"1-b0a42e17ff9d67143d6ef16174ee894c",
  "language":"javascript",
  "views":{
    "v1":{
      "map":"function ( d )        {          if ( d.k1 > 200 )            emit( d._id, { 'k1': d.k1, 'k2 + k3': d.k2 + d.k3 } );        }"
    },
    "v2":{
      "map":"function ( d )        {          if ( d.k2 > 200 )            emit( d._id + '-' + d.k3, null );        }"
    }
  }
}
```

#### view の使用

`curl -X GET 127.0.0.1:5984/test/_design/d1/_view/v1`

return:

> {"total_rows":1,"offset":0,"rows":[
{"id":"ccc","key":"ccc","value":{"k1":300,"k2 + k3":603}}
]}

見難いので [JSON FORMATTER & VALIDATOR](https://jsonformatter.curiousconcept.com/) で format すると:

```json
{
  "total_rows":1,
  "offset":0,
  "rows":[
    {
      "id":"ccc",
      "key":"ccc",
      "value":{
        "k1":300,
        "k2 + k3":603
      }
    }
  ]
}
```

他の view の例:

`curl -X GET 127.0.0.1:5984/test/_design/d1/_view/v2`

return:

> {"total_rows":2,"offset":0,"rows":[
{"id":"bbb","key":"bbb-202","value":null},
{"id":"ccc","key":"ccc-302","value":null}
]}

[JSON FORMATTER & VALIDATOR](https://jsonformatter.curiousconcept.com/) で format すると:

```json
{
  "total_rows":2,
  "offset":0,
  "rows":[
    {
      "id":"bbb",
      "key":"bbb-202",
      "value":null
    },
    {
      "id":"ccc",
      "key":"ccc-302",
      "value":null
    }
  ]
}
```

### 任意の条件の集計を行う: view/reduce

#### reduce で集計する view を新たな design として追加

- design の追加ではなく既存の design を更新して view を追加しても構わない
- design の view の map, reduce は必要なら定義すれば良いし、それぞれ不要ならば定義しなくて構わない

map も reduce もする view を定義した小さな d2.json:

```json
{ "views":
  { "v3":
    { "map"   : "function ( d ) { emit( d._id, d.k1 + d.k2 + d.k3 ) }"
    , "reduce": "function ( k, v, r ) { return sum( v ) }"
    }
  }
}
```

d2.json を d2 として追加:

`curl -X PUT 127.0.0.1:5984/test/_design/d1 -d @./d2.json`

> {"ok":true,"id":"_design/d2","rev":"1-0a71c65253d237bbc66aa4df3b1f2d11"}

d2 を確認:

`curl -X GET 127.0.0.1:5984/test/_design/d2`

> {"_id":"_design/d2","_rev":"1-0a71c65253d237bbc66aa4df3b1f2d11","views":{"v3":{"map":"function ( d ) { emit( d._id, d.k1 + d.k2 + d.k3 ) }","reduce":"function ( k, v, r ) { return sum( v ) }"}}}

```json
{
  "_id":"_design/d2",
  "_rev":"1-0a71c65253d237bbc66aa4df3b1f2d11",
  "views":{
    "v3":{
      "map":"function ( d ) { emit( d._id, d.k1 + d.k2 + d.k3 ) }",
      "reduce":"function ( k, v, r ) { return sum( v ) }"
    }
  }
}
```

- note: `reduce` のシグニチャーをわかりやすく書くと `function ( keys, values, is_intermediate )` といったところ
    - `is_intermediate` に
        - `false` が渡されて呼ばれる時は: `reduce( [ [ key1, id1 ], ... ], [ value1, ... ], false )`
        - `true` が渡されて呼ばれる時は: `reduce( null, [ intermediate1, ... ], true )`
        - もう少し詳しい解説が欲しければ [Couch Wiki - Reduce Function](http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views?action=show&redirect=Views#Reduce_Functions) をどうぞ

#### view の使用

` curl -X GET 127.0.0.1:5984/test/_design/d2/_view/v3`

> {"rows":[
{"key":null,"value":1809}
]}

整形して見ると:

```json
{
  "rows":[
    {
      "key":null,
      "value":1809
    }
  ]
}
```

ちなみに、↑この時は↓こんなドキュメント群が詰まった test1 だったので、

```
{"id":"aaa","key":"aaa","value":{"rev":"1-c8227456e13a740a3a950be456a85f21"},"doc":{"_id":"aaa","_rev":"1-c8227456e13a740a3a950be456a85f21","k1":100,"k2":101,"k3":102}},
{"id":"bbb","key":"bbb","value":{"rev":"1-ea6f81f73a3bf9c1dc3391565b740870"},"doc":{"_id":"bbb","_rev":"1-ea6f81f73a3bf9c1dc3391565b740870","k1":200,"k2":201,"k3":202}},
{"id":"ccc","key":"ccc","value":{"rev":"1-5bac6f87d40c8d92da4a0586361bb950"},"doc":{"_id":"ccc","_rev":"1-5bac6f87d40c8d92da4a0586361bb950","k1":300,"k2":301,"k3":302}}
]}
```

ドキュメントごとに map の段階で↓のように `[ value1, ... ]` がそれぞれに処理され、

```
100 + 101 + 102 = 303 ( = value1 )
200 + 201 + 202 = 603 ( = value2 )
300 + 301 + 302 = 903 ( = value3 )
```

続いて↑の `[ value1, ... ]` が↓のように `values` に入れられ reduce が処理され、

```
303 + 603 + 903 = 1809 ( = d2/v3 の map -> reduce 後の最終的な結果としての value )
```

となったものです。

# 変態操作（まちがった事は言ってない）

ごめんなさい変態というか整形です。

## document の出力を JSON -> なにか に整形して出力: show

- note: design の shows にドキュメントを任意の関数を噛ませて射影変換して出力する機能を入れられる
    - 関数のシグニチャーは `function ( document, request )`
    - document は GET されるドキュメント
    - request は関数が呼ばれる発端となるリクエスト情報で、 request.query はじめいろいろ（本項で後述†）と詰まっている

d3.json:

```json
{ "shows":
  { "s1":
      "function ( d, r )
      {
        var hs = { };
        var b;

        if ( ! d )
          b = 'document is n/a.';
        else if ( r.query.csv )
        {
          hs[ 'content-type' ] = 'text/csv';
          b = [ d.k1, d.k2, d.k3 ].reduce( function ( a, b ) { return a + ', ' + b; } );
        }
        else
        {
          hs[ 'content-type' ] = 'application/json';
          b = JSON.stringify( d );
        }

        return { 'headers': hs, 'body': b + '\\n' };
      }"
  }
}
```

- note: この関数は eval される `"` 囲いの文字列の中にあるため改行文字のためのエスケープそれ自信もエスケープしないと eval 対象となる内部に関数定義された文字列内にエスケープ文字を入れられないため `'\\n'` となる点に注意

### request の中身†

↓こんなのを仕込むと実際の挙動として観測できる

`function ( d, r ) { return { 'body': JSON.stringify( r ) } }`

↑から中略、仕込んだ show でドキュメント `aa` を GET してみるとこうなる↓

> {"info":{"db_name":"test","doc_count":8,"doc_del_count":0,"update_seq":100,"purge_seq":0,"compact_running":false,"disk_size":446568,"data_size":8112,"instance_start_time":"1472498362522313","disk_format_version":6,"committed_update_seq":100},"id":"aa","uuid":"56b000058f5a060388a4da64e7024e5d","method":"GET","requested_path":["test","_design","d4","_show","s1","aa"],"path":["test","_design","d4","_show","s1","aa"],"raw_path":"/test/_design/d4/_show/s1/aa","query":{},"headers":{"Accept":"*/*","Host":"127.0.0.1:5984","User-Agent":"curl/7.47.0"},"body":"undefined","peer":"127.0.0.1","form":{},"cookie":{},"userCtx":{"db":"test","name":null,"roles":["_admin"]},"secObj":{}}

↑を整形すると↓:

```json
{
  "info":{
    "db_name":"test",
    "doc_count":8,
    "doc_del_count":0,
    "update_seq":100,
    "purge_seq":0,
    "compact_running":false,
    "disk_size":446568,
    "data_size":8112,
    "instance_start_time":"1472498362522313",
    "disk_format_version":6,
    "committed_update_seq":100
  },
  "id":"aa",
  "uuid":"56b000058f5a060388a4da64e7024e5d",
  "method":"GET",
  "requested_path":[
    "test",
    "_design",
    "d4",
    "_show",
    "s1",
    "aa"
  ],
  "path":[
    "test",
    "_design",
    "d4",
    "_show",
    "s1",
    "aa"
  ],
  "raw_path":"/test/_design/d4/_show/s1/aa",
  "query":{

  },
  "headers":{
    "Accept":"*/*",
    "Host":"127.0.0.1:5984",
    "User-Agent":"curl/7.47.0"
  },
  "body":"undefined",
  "peer":"127.0.0.1",
  "form":{

  },
  "cookie":{

  },
  "userCtx":{
    "db":"test",
    "name":null,
    "roles":[
      "_admin"
    ]
  },
  "secObj":{

  }
}
```

- note 1: show として定義する関数で使いたければこれらの情報を request から引っ張りだして使える
- note 2: show の return の中身はいわゆる response 構造なので body 以外にも何かと設定したり、 BASE64 エンコードでバイナリーを出力もできる: [CouchDB The Definitive Guide - Show Functions](http://guide.couchdb.org/draft/show.html)

## view の出力を JSON -> なにか に整形して出力: list

- note 1: view の結果を整形する list を lists に含んだ design を定義して使う
- note 2: couch-0.10 以降とそれ以前では仕様が list 回りの大きな仕様変更があった
    - この記事では couch-0.10 以降のスタイルでのみ紹介します
    - couch-0.9 以下の古いスタイルの情報は [Couch Wiki - Listing Views with CouchDB 0.9](http://wiki.apache.org/couchdb/Formatting_with_Show_and_List#Listing_Views_with_CouchDB_0.9) 、日本語の紹介記事では [@IT - ゆったリラックス！ CouchDBがあるところ(2) (2009-10-26)](http://www.atmarkit.co.jp/ait/articles/0910/26/news097_4.html) など
- note 3: list は view の結果を整形する機能なので design には少なくとも1つの view と list の両方が必要

d5.json:

```json
{ "views": { "v1": { "map": "function( d ) { emit( null, d.k1 + d.k2 + d.k3 ) }" } }
, "lists":
  { "l1":
    "function ( h, r )
    {
      start( { 'headers': { 'content-type': 'text/html' } } );

      send( '<ul>\\n' );

      var row;
      while ( row = getRow() )
        send( '  <li>' + row.value + '\\n' );

      send( '</ul>\\n' );
    }"
  }
}
```

- note 1: ↑は k1 + k2 + k3 を出力する map のみの view + HTML として `<ul><li>...</ul>` に整形する list で構成
- note 2: list の関数シグニチャーは `function ( header, request )`
    - header: `{"total_rows":3,"offset":0}` こんなのが入ってる
    - request: show の時と同様
- note 4: `getRow()` は組み込み関数で view の出力結果を1件読み出せる
    - 基本的に使うのは `.value`
    - 他に `.id` と `.key` が定義されて飛んで来る
- note 3: `send` は組み込み関数で、呼び付けると引数で渡されたオブジェクトの toString 結果を出力
    - `{ ... }` オブジェクトをそのまま投げても `[object Object]` と出るだけなので注意
    - 文字列や数値を投げないとあまり意味は無い

`curl -X GET 127.0.0.1:5984/test/_design/d5/_list/l1/v1`

```html
<ul>
  <li>303
  <li>603
  <li>903
</ul>
```

- note: 使う時は `host:port/database/_design/my_design_1/_list/my_list_1/my_view_1` といった文法で database に対して design の list に view の結果を出力させる

# 参考

きほんてきに CouchDB wiki を見るのが安全:

- [Apache - CouchDB - wiki]()
    - [HTTP View API](http://wiki.apache.org/couchdb/HTTP_view_API#HTTP_View_API)
    - [Introduction to CouchDB Views](http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views?action=show&redirect=Views#Introduction_to_CouchDB_Views)
    - [View Snippets](http://wiki.apache.org/couchdb/View_Snippets#View_Snippets-1)
    - [Formatting with Show and List](http://wiki.apache.org/couchdb/Formatting_with_Show_and_List#Formatting_with_Show_and_List-1)

wiki よりも個別の機能ごとに少し詳しく知りたい時にはこちらが役に立つ（この記事からの参考として直接的なところを一部抜粋してリンクを列挙するが、CouchDBを"使い倒す"つもりであればもちろん他の項も読むとより幸せになれる）:

- [CouchDB The Definitive Guide](http://guide.couchdb.org/)
    - [3. Getting Started](http://guide.couchdb.org/draft/tour.html)
    - [6. Finding Your Data with Views](http://guide.couchdb.org/draft/views.html)
    - [8. Show Functions](http://guide.couchdb.org/draft/show.html)
    - [9. Transforming Views with List Functions](http://guide.couchdb.org/draft/transforming.html)

どうしても日本語の優しい入門記事が読みたいという場合にはこちらがよいかも（但し記事が古い点に少々注意）:

- [@IT - ゆったリラックス！ CouchDBがあるところ（2） (2009-10-26)](http://www.atmarkit.co.jp/ait/articles/0910/26/news097.html)

基本はわかっていて簡潔な API リファレンスだけあればいいんじゃーとなったなら:

- [CouchDB - API Reference](http://docs.couchdb.org/en/stable/api/index.html)

# おまけ: 

著者のただのふんわり話です。

はじめてカウチを触ったのは出た頃の10年くらい前。新しいものにはとりあえず何でも触れていた頃、時代はまだウェブではHTML4じゃなくてXHTML-1.0でstrictでとか、.netが今とは違う意味でちょー元気もりもりやる気まんまんで盛り上がってくる黎明期で、Silverlightもこれからだぞーって頃だった（当時はまだWPF/Eと呼ばれてFLASHを本気で駆逐する先鋒として生まれようとしていた頃だった）。

今回、仕事でカウチを使う機会が得られたので、プロジェクトチーム向けの導入記事の側面も兼ねてこのドキュメントを書きました。ついで、仕事であれ、プライベートの技術的な興味、あるいは NoSQL の歴史的な興味であれ、触れてみようという方のとっかかりくらいには役立てば幸いです。