2. enable Erlang
  3. Erlang lingua

  4. map on native Erlang
  5. reduce on native Erlang
  6. views on native Erlang

  7. show on native Erlang

  8. list on native Erlang

  9. validate_doc_update on native Erlang

  10. security: users, roles, ACL
  11. security: Auth
  12. security: rights

  13. update on native Erlang

  14. bulk ops
  15. grouping reduce
  16. include doc in view

start on background and redirect both standard output and standard error to a file
$> sudo couchdb &>couchlogfile &


you do everything using GET, PUT, POST, and DELETE with a URI

connecting to server:

  $> curl -X GET
creating new db "mydb":
  $> curl -X PUT
connecting to db:
  $> curl -X GET
    "purge_  seq":0,"compact_running":false,"disk_size":79,
receiving list of all dbs:
  $> curl -X GET

creating a new documet (don't use implicit _id - create your own instead)

  $> curl -X POST \
    -d '{ "name":"document 1", "_id": "1001" }' \
    -H 'Content-Type: application/json' 
revising document:
  $> curl -X PUT \
    -d '{"_rev":"1-87c171ce6037bfa65ec961110359a7be", "name":"document I"}'
receiving document:
  $> curl -X GET
   {"_id":"1001","_rev":"2-48e2c0afd8f67b4b5736a5231c1eb191","name":"document I"}
getting a list of all docs:
  $> curl -X GET
deleting document:
  $> curl -X DELETE \'1-97bfc01379bd3fc06b6c47cef46245d7' \
    -H 'Content-Type: application/json'
deleting db:
  $> curl -X DELETE

you can ask CouchDB to give you UUID:

$> curl -X GET
if you need more than one, you can pass in the ?count=10 HTTP parameter to request 10 UUIDs

you can attach file to your document:

$> curl -X PUT localhost:5984/hello-world/1/artwork.jpg?rev="1-be5d5870c9ef76734789df431d0ffe7b" \
--data-binary @/home/huh/inval.jpg \
-H 'content-type: image/jpg'
$> {"ok":true,"id":"1","rev":"2-e61cf4c3297a3f928fb022a385825e14"}
the --data-binary @ option tells curl to read a file’s contents into the HTTP request body the -H option tells CouchDB that we’re uploading a JPEG file. CouchDB will keep this information around and will send the appropriate header when requesting this attachment; in case of an image like this, a browser will render the image instead of offering you the data for download

to get attachment

$> curl -X GET > artwork.jpg

you can replicate your db locally:

$> curl -vX POST \
-d '{"source":"db1","target":"db-replica"}'  \
-H "Content-Type: application/json"
  "start_time":"Wed, 09 Sep 2015 15:23:26 GMT",
   "end_time":"Wed, 09 Sep 2015 15:23:26 GMT",
if you have a lot of documents, it’ll take a while until they are all replicated and you won’t get back the replication response until all documents are replicated

it is important to note that replication replicates the database only as it was at the point in time when replication was started

you can restart server if you are server admin:

$> curl -X POST http://adminname:secretpass@localhost:5984/_restart \
> -H 'content-type: application/json'

enable Erlang

due to security restrictions, the Erlang query server is disabled by default

unlike the JavaScript query server, the Erlang one does not runs in a sandbox mode. this means that Erlang code has full access to your OS, filesystem and network, which may lead to security issues. while Erlang functions are faster than JavaScript ones, you need to be careful about running them, especially if they were written by someone else

to enable Erlang language add a new section to your local.ini

  erlang = {couch_native_process, start_link, []}
your local.ini will most likely be at /usr/local/etc/couchdb/local.ini or /etc/couchdb/local.ini

instead, you can use Futon. select Configuration from control pannel; secect Add new section; insert
native_query_servers as 'section',
erlang as 'option' and
{couch_native_process, start_link, []} as 'value'

in old versions you need to restart the server:

$> sudo couchdb restart

Erlang lingua

the javascript object {"foo":"word", "bar":false, "baz":1} is equivalent of {[{<<"foo">>, <<"word">>}, {<<"bar">>, false}, {<<"baz">>, 1}]} Erlang term

in Erlang lingua it is a proplist wrapped in a tuple

CouchDB uses predefined map and reduce functions in a style known as MapReduce

map on native Erlang

map functions are called once with each document as the argument. the function can choose to skip the document altogether or emit one or more view rows as key/value pairs

map functions may not depend on any information outside of the document

argument of your map/1 - a single document from the database

CouchDB iterate over the items and emits key/value pairs

the built-in Emit/2 function takes two arguments:

Emit/2 creates an entry in view result

  fun ({Document}) ->
    Key = ... ,
    Val = ... ,
    Emit (Key, Val)

the Emit/2 function can be called multiple times in the map function to create multiple entries in the view results from a single document

you can pass null to the value parameter. the same is true for the key parameter. but if you don’t use the key field in your map/1 function, you are probably doing it wrong

CouchDB takes whatever you pass into the Emit/2 function and puts it into a list. each entry in that list includes the key and value. the list is sorted by key

the most important feature of a view result is that it is sorted by key. map functions give you an opportunity to sort your data using any key you choose, and that CouchDB’s design is focused on providing fast, efficient access to data within a range of keys

reduce on native Erlang

your reduce/3 function operates on the sorted items emitted by your map/1 functions
  fun (Key, Value, _Rereduce) ->
    CumulativeValue = ...
when run on leaf nodes (which contain actual map rows), the reduce function third parameter is false

the arguments in this case are the keys and values as output by the map function. the function has a single returned reduction value, which is stored on the inner node that a working set of leaf nodes have in common, and is used as a cache in future reduce calculations

when the reduce function is run on inner nodes, the rereduce flag is true

when rereduce is true, the values passed to the function are intermediate reduction values as cached from previous calculations. when the tree is more than two levels deep, the rereduce phase is repeated, consuming chunks of the previous level’s output until the final reduce value is calculated at the root node

a common mistake new CouchDB users make is attempting to construct complex aggregate values with a reduce function. full reductions should result in a scalar value

if you don’t reduce your values to a single scalar value or a small fixed-sized object or array with a fixed number of scalar values of small sizes, you are probably doing it wrong

note: use internal implemented functions _sum or _count as your reduce function - they are very efficient

views on native Erlang

the combination of a map and a reduce function is called a view in CouchDB terminology

CouchDB views are stored as a list of the rows (tuples in Erlang lingua) that are kept sorted by key. this makes retrieving data from a range of keys efficient

upload code:

  $> curl -X PUT -d @- 
  {"_id" : "_design/mytest",  "language" : "erlang",   "views" : { "myview" : {
      "map" : "fun ({D}) -> Emit (null, proplists:get_value (<<\"name\">>, D, null)) end."}}}
note that CouchDB does not like carrige_return in function' body so better write all your functions in line

now this view in action:

  $> curl -X GET
   {"id":"1001","key":null,"value":"document 1"},
   {"id":"1002","key":null,"value":"document 2"}
note that CouchDB automatically includes the document ID in the view result

let me change this view a little bit:

  $> curl -X PUT"1-31466e93643e9a446e23bc1d2b49050a" -d @- 
     "map":"fun({D}) -> Emit(null, proplists:get_value(<<\"name\">>, D, null)) end.",
     "reduce":"fun(_K, V, True) -> V end."}}}
and now:
  $> curl -X GET
   {"key":null,"value":["document 2","document 1"]}

view's parameters

key key-value - a proper URL encoded JSON value
startkey key-value - a proper URL encoded JSON value
startkey_docid doc id - document id to start with (to allow pagination for dup keys)
endkey key-value - a proper URL encoded JSON value
endkey_docid doc id - last document id to include (to allow pagination for dup keys)
limit N - limit in tde output
descending true/false f reverse tde output
skip N 0 skip N of docs
reduce true/falset/f reduce? ('t' if 'r' is defined)
group true/false f whether to reduce by each orig key
group_level N - depth of reducing, if group=true
include_docs true/false f autoinclude doc in resp
inclusive_end true/false t whetder tde endkey is included
update_seq true/false f resp includes an update_seq value
stale ok/update_after 'ok' - not refresh tde view

  $> curl localhost:5984/mydb/_design/mytest/_view/someview
  $> curl localhost:5984/mydb/_design/mytest/_view/someview?reduce=false
  $> curl localhost:5984/mydb/_design/mytest/_view/someview?'reduce=false&descending=true'

  $> curl -X GET"startkey_docid=2&endkey_docid=4"

view rules of thumb:

note that when filtering by part of the complex key, you can only filter by in-order combinations. for example, if you had [field1, field2, field3] as a key, you could only filter by [field1], [field1, field2] or [field1, field2, field3]. you could not, for example, filter by [field1, field3], as CouchDB would interpret the key you specified for field3 as the value to filter field2 by

the syntax required to use startkey=...&endkey=... when you want to filter on only part of a complex key is as follows:

say you have a key like [field1, field2, field3], and you wanted to filter on only field1 where field1 = "123". your url would look like:

  curl -X GET http://localhost:5984/mydb/_design/desname/_view/viewname?startkey=["123"]&endkey=["123",{}]

notice the {} in the endkey. this is so that you get all values returned by the view between null and {}, which for just about every case should be everything

show on native Erlang

functions 'show' convert documents into non-JSON formats

the picture:

may be several show functions per one document

they are designed to be cacheable. CouchDB handles generating Etags for show responses

show functions are pure

the show function lives inside a design document, in a field called shows

$> curl -X PUT"15-ff0c159a63d7d6d7aa835df403f0bdc" -d @-
"shows":{ "myshow":
    "fun({Doc},{Req}) ->
       Text = proplists:get_value(<<\"name\">>, Doc),
       {[{<<\"code\">>, 200},
         {<<\"headers\">>, {[]}},
         {<<\"body\">>, io_lib:format(\"~p\", [Text])}
$> {"ok":true,"id":"_design/mytest","rev":"16-c525285228872de6972d004b87f489ab"}
to invoke it append the name of the function to the design document itself, and then the _id of the document to render:
  $> curl -vX GET \  
   -H 'content-type:application/json'
  * About to connect() to port 5984 (#0)
  *   Trying
  * connected
  * Connected to ( port 5984 (#0)
  > GET /mydb/_design/mytest/_show/myshow/1003 HTTP/1.1
  > User-Agent: curl/7.25.0 (i686-pc-linux-gnu) ...
  > Host:
  > Accept: */*
  > content-type:application/json
  < HTTP/1.1 200 OK
  < Vary: Accept
  < Server: CouchDB/1.2.0 (Erlang OTP/R15B01)
  < Date: Mon, 13 Aug 2012 07:48:11 GMT
  < Content-Type: text/html; charset=utf-8
  < Content-Length: 10
  * Connection #0 to host left intact

list on native Erlang

'list' functions convert views into non-JSON formats

the picture:

they are designed to be cacheable. CouchDB handles generating Etags for list responses

like map, reduce, and show functions, list functions are pure

list functions are stored under the lists field of a design document

the function is called with two arguments, which can sometimes be ignored, as the row data itself is loaded during function execution

the first argument, Head, contains information about the view. here’s what you might see looking at a JSON representation of head: {total_rows:10, offset:0}

the second argument, Request is a much richer data structure. this is the same request object that is available to show, update, and filter functions. here’s the example Request object:

    "info": {
      "db_name"            : "testdb",
      "doc_count"          : 11,
      "doc_del_count"      : 0,
      "update_seq"         : 11,
      "purge_seq"          : 0,
      "compact_running"    : false,
      "disk_size"          : 4930,
      "instance_start_time": "1250046852578425",
      "disk_format_version": 4
    "method"  : "GET",
    "path"    : ["testdb","_design","lists","_list","listname","viewname"],
    "query"   : {"foo":"bar"},
    "headers" : {
        "Accept":          "text/html,application/xhtml+xml ,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Charset":  "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
        "Accept-Encoding": "gzip,deflate",
        "Accept-Language": "en-us,en;q=0.5",
        "Connection":      "keep-alive",
        "Cookie":          "_x=95252s.sd25; AuthSession=",
        "Host":            "",
        "Keep-Alive":      "300",
        "Referer":         "",
        "User-Agent":      "Mozilla/5.0 Gecko/20090729 Firefox/3.5.2"
     "cookie":   {"_x": "95252s.sd25","AuthSession": ""},
     "body":     "undefined",
     "form":     {},
     "userCtx":  {"db":"testdb","name":null,"roles":["_admin"]}
finally, the userCtx is the same as that sent to the validation function. it provides access to the database the user is authenticated against, the user’s name, and the roles they’ve been granted

list function API is capable of rendering lists of arbitrary length without error, when used correctly

the basic path to a list function is /db/_design/design-name/_list/list-name/view-name

just like show functions and view queries, lists are sent with proper HTTP Etags, which makes them cacheable by intermediate proxies


$> curl -X PUT"16-c525285228872de6972d004b87f489ab" -d @-
  {"_id":"_design/mytest","language":"erlang", "lists" : 
    {"mylist": "fun (_Head, _Req) ->
       Resp = {[{<<\"headers\">>, {[]}}]},
       Start (Resp),
       Fun = fun ({Row}, _) ->
         V = proplists:get_value (<<\"value\">>, Row),
         Send(io_lib:format (\"~p\",[V])),
         {ok, []}
       {ok,[]} = FoldRows (Fun, nil),
     end." }}
$> {"ok":true,"id":"_design/mytest","rev":"17-522127be5bd3ec9fc6177d5ee3eac4f5"}
  $> curl -X GET

validate_doc_update on native Erlang

one validation function per database

function stored in the validate_doc_update field of a _design document

it gets executed whenever a write requests reaches the database. it can decide to allow or deny access to the database based on the document that is being written and the authenticated user or his/her roles

if the validation function return 1 when update is accepted; when it doesn’t, the updates are denied

validate_doc_update function accepts four arguments:

  newDoc     - the document to be created or updated
  oldDoc     - the current doc
  userCtx    - user context object, which contains three properties:
                 db     - (string) name of database
                 name   - (string) user name
                 roles  - (array) roles to which user belongs
  secObj     - the security object of the database
  fun ({NewDoc}, _OldDoc, _UserCtx, _SecObj) ->
    case proplists:get_value (<<"_deleted">>, NewDoc, null) of
      null ->
        case proplists:get_value (<<"name">>, NewDoc, null) of
          null -> <<"field 'name' is absent!">>;
          _ -> 1
      _ -> 1


  $> curl -X POST \
    -d '{"_id":"1003","ammount":4.5}' \
    -H 'content-type:application/json'

   {"error":"case_clause","reason":"field 'name' is absent!"}

  $> curl -X POST \ 
     -d '{"name":"doc3","_id":"1003","ammount":4.5}'\
     -H 'content-type:application/json'


security: users, roles, ACL


a user is identified by a username and matching password that is securely stored inside CouchDB. documents in the _users database can not be read by everyone. password hashes are calculated by CouchDB, not by the client

a user can have one or more roles assigned

a user with an empty name and password is the anonymous user

CouchDB distinguishes between server admins and database admins

creating of the first server admin

in the file local.ini remove comments sign ; from string ;admin = mysecretpassword in [admins] section and type username = password:

  ;admin = mysecretpassword
  charly = 12345
save file and restart CouchDB

creating the next server admin

  > curl -X PUT http://charly:12345@ -d '"secret"'
instead of this action you could have stopped CouchDB, opened your local.ini, added anna = secret to the [admins] section, and restarted CouchDB

creating of the user

  > cat > bob.json
  {"_id":"org.couchdb.user:bob", "name":"bob",
  "password":"34567", "type":"user", "roles":["user"]}
  > curl -X PUT \
  > http://charly:12345@ \
  > -d@bob.json
  {"ok":true, "id":"org.couchdb.user:bob",
admins created through the _config API are persisted to CouchDB’s configuration file ($prefix/etc/couchdb/local.ini by default). users in the _users database are stored in that database. in addition, _config users are always automatically server admins, so use them with care


roles are associated with users, you could also call them “groups”. with roles you can group multiple users.

a role is a simple string that doesn’t start with an underscore. underscore-roles are reserved to CouchDB. the only role that CouchDB prescribes is the _admin role. it grants the user server-wide privileges to do anything

to allow more fine-grained control over who can read from your databases, CouchDB comes with Access Control Lists (ACLs)

Access Control Lists

ACL - a list of usernames or roles for the single database

CouchDB distinguishes member-ACLs and admin-ACLs

a database admin has full access to the database

the member-ACL list defines a list of users or roles that can read from the database. there is no writer-ACL

if member-ACLs are not defined, everybody can read from the database

note that there is no writer-ACL: write protection achieved by Validation functions

to create ACL you use the /db/_security object. each database in CouchDB comes with its own security object. it is a JSON structure associated with the database. on a newly created database:

  $> curl -X GET
/db/_security object is empty you can set two properties admins and members. both are another JSON objects with the two properties: roles and names and these two are lists of roles and names respectively

in case you want only specific authenticated users to be able to read from your database:

  > cat >acl
there is no need to add names or roles from the admins section, since they automatically are also members
  $> curl -X PUT http://bob:34567@localhost:5984/mydb/_security -d@acl
  $> curl localhost:5984/mydb/_security
  $> curl localhost:5984/mydb/1003
  {"error":"unauthorized","reason":"You are not authorized to access this
  $> curl http://bob:34567@localhost:5984/mydb/1003
authentication_handlers are the different ways CouchDB can do the actual authentication process for you. by default CouchDB ships with an OAuth handler, a cookie handler and the default handler (which does HTTP basic auth)

the authentication_db is the database that user documents are stored in. the default is _users, but you can change it in the CouchDB configuration settings. only do so if you have a very good reason

security: Auth

basic Auth

base64 encoding is not a form of encryption, so basic auth can only reasonably used in a trusted environment(LAN,VPN or SSL)

in local.ini file uncomment next two lines to trigger basic-auth popup on unauthorized requests:

  WWW-Authenticate = Basic realm="administrator"

  require_valid_user = true
user must be in _user database

security object for database must be propery installed

  $> curl
   {"error":"unauthorized","reason":"Authentication required."}
  $> curl -u 'tom:tomijerry'
   {"error":"unauthorized","reason":"You are not authorized to access this db."}
  $> curl -X GET -u 'foo:bar'

cookie Auth

unlike Basic Auth Secure Cookie Auth uses HMAC-encryption for cookie authentication to work, you need to enable the cookie_authentication_handler in your local.ini:

  authentication_handlers =
  {couch_httpd_oauth, oauth_authentication_handler},
  {couch_httpd_auth, cookie_authentication_handler},
  {couch_httpd_auth, default_authentication_handler}
in addition, you need to define a server secret:
  secret = yours3cr37pr4s3
once you have a user document in the _users database, you can use the _session API to get an encrypted session cookie that authenticates you for the next few requests. by default, a session cookie is valid for 10 minutes

the token lifetime can be configured with the timeout (in seconds) setting in the [couch_httpd_auth] configuration section

  $> curl -u 'foo:bar'


userCtxstructure where all the auth info is stored
nameyour login username
roleslist of roles you assigned to
infosome server-wide info about the auth system

to obtain the first token and thus authenticate a user for the first time, the username and password must be sent to the _session API. the API is smart enough to decode HTML form submissions

      <INPUT type="text" hidden=true name="name"     value="foo">
      <INPUT type="text" hidden=true name="password" value="bar">
      <INPUT type="submit" value="Send">
press "Send" and receive in your browser' window:

if you are not using HTML forms to log in, you need to send an HTTP request that looks as if an HTML form generated it:

  $> curl -vX POST \
    -H 'content-type:application/x-www-form-urlencoded' \
    -d 'name=bob&password=34567' \
    -c "cookie.dat" \
    -u 'bob:34567'
  * Connected to ( port 5984 (#0)
  * Server auth using Basic with user 'bob'
  > POST /_session HTTP/1.1
  > Authorization: Basic Ym9iOjM0NTY3
  > content-type:application/x-www-form-urlencoded
  > Content-Length: 23
  * upload completely sent off: 23 out of 23 bytes
  < HTTP/1.1 200 OK
  * Added cookie
  * AuthSession="Ym9iOjUwMkZEN0NEOujOwG7MwJ2eiB-lC47f7YfV8aWv" for domain
  *, path /, expire 0
  < Set-Cookie: AuthSession=Zm9vOjUwMkZENUY5Or9wCYzYo-V7rtV2hbJ39SWerPRe;
  Version=1; Path=/; HttpOnly
  < Server: CouchDB/1.2.0 (Erlang OTP/R15B01)
  < Date: Sat, 18 Aug 2012 17:58:37 GMT
  < Content-Type: text/plain; charset=utf-8
  < Content-Length: 42
  < Cache-Control: must-revalidate
check result:
  $> cat cookie.dat
  # Netscape HTTP Cookie File
  # This file was generated by libcurl! Edit at your own risk.

  #HttpOnly_127.0.0.1     FALSE   /       FALSE   0
  AuthSession   Zm9vOjUwMkZENUY5Or9wCYzYo-V7rtV2hbJ39SWerPRe
and now:
  $> curl $host/mydb/1001 --cookie \
or, simplier:
  $> curl $host/mydb/1001 -b 'cookie.dat'

security: rights

in the _users database:
  • - new document can be created by anybody (with 'roles' property as [])
  • - list of all documents can be read only by server admin
  • - the document can be read only by server admin or its owner
  • - only server admin may delete the document and edit its property 'roles'
  • - owner may edit anything in the document, except its property 'roles'
  • in other databases:

       read      write    |   Auth    ACL   Validate
       all       all      |    -       -       -
       all       owner    |    -       -       +
       all       users    |    -       +       +
       users     owner    |    +       -       +
       users     users    |    -       +       +

    update on native Erlang

    update functions allow you
  • - to update and create documents by POST requests from forms, or
  • - to degrade gracefully when client-side JS support is not available
  • update functions can accept plain HTML form POSTs (and other arbitary input) and turn it in to JSON for saving to the database

    the server also parses the POST body into a JSON tree called form and does the same with the query string, in JSON tree query

    update functions take two parameters

    the first parameter Doc will be populated with the document, if the update function was requested with a document id ( and if you are creating a new document, make sure to set its _id to something)

    the second parameter Req - your HTTP request to server

    update/2 function must return list with two elements. the first element - updated document. the second element - HTTP response. this can be a JSON object with headers and a body:

      [{"headers",[{"content-type","application/xml"}, ...]},
       {"body",   Somestuff                                }]
    or just a plain string:
      io:lib_format("Update function complete!")
    update functions are saved in updates on the design document example:

      {"_id":"_design/form", "language":"erlang",
       "myupd":"fun({OldDoc}, {Req}) ->
        {F} = proplists:get_value(<<"form">>, Req, null),
        V = proplists:get_value(<<"f1">>, F, null),
        X = [{<<"f1">>, V} | proplists:delete(<<"f1">>, OldDoc)],
        [ {X}, {[
           {<<"code">>, 200},
           {<<"headers">>, {[]}},
           {<<"body">>, io_lib:format("done~n",[])}]}]
    save it as update.json and:
      $> curl -X POST -d @update.json

    create html document:

        <form method="POST" action="">
          <input type="text" name="f1"> <input type="submit">
    load page and press 'Send'. magic!

    you can update doc1 from console too. look at old doc1:

      $> curl
    send request for update doc1:
      $> curl -X POST \
       -H 'content-type:application/x-www-form-urlencoded'\
       -d 'f1=boa'
    look at new content of doc1:
      $> curl

    bulk ops

    CouchDB provides a bulk insert/update feature. to use this, you make a POST request to the URI/_bulk_docs, with the request body being a JSON document containing a list of new documents to be inserted, updated or deleted
        {"_id":"1009","name":"baz","ammount":17.3,"quantity":3} ]}
      $> curl -X POST \
        -d @bulk.json -H 'content-type:application/json'
    if you omit the per-document _id specification, CouchDB will generate unique IDs for you to update existing documents, include the _rev field to the current document’s revision. if the _rev does not match the current version of the document, then that particular document will not be saved and will be reported as a conflict, but this does not prevent other documents in the batch from being saved

    to delete documents in bulk include both the revision and a boolean "_deleted":true

      {"docs" : [
    individual updates may fail (e.g. because they don’t pass validation), but you can get an all_or_nothing transaction-like feeling by adding a boolean "all_or_nothing":true field alongside the docs array
      { "all_or_nothing":true, "docs" : [ ... ] }
    if any updates fails validation, all updates will fail POSTing a single document with "all_or_nothing":true behaves completely differently from a regular PUT, since it will save conflicting versions rather than rejecting a conflict

    grouping reduce

    suppose you have next docs in db test:

      {"_id" : "1", "f0" : "a", "f1" : "a", "f2" : "a", "f3" : 1}
      {"_id" : "2", "f0" : "a", "f1" : "a", "f2" : "b", "f3" : 2}
      {"_id" : "3", "f0" : "a", "f1" : "a", "f2" : "b", "f3" : 3}
      {"_id" : "4", "f0" : "a", "f1" : "b", "f2" : "c", "f3" : 4}
      {"_id" : "5", "f0" : "a", "f1" : "b", "f2" : "c", "f3" : 5}
      {"_id" : "6", "f0" : "a", "f1" : "c", "f2" : "d", "f3" : 6}

    suppose you have the view view01 in db test:

        fun({Doc}) ->
          K0 = proplists:get_value(<<"f0">>, Doc, null),
          K1 = proplists:get_value(<<"f1">>, Doc, null),
          K2 = proplists:get_value(<<"f2">>, Doc, null),
          V  = proplists:get_value(<<"f3">>, Doc, null),
          Emit([K0, K1, K2], V)
        fun(K, V, R) -> V end.

    so you have complex key here - array of three elements

    the basic reduce operation with group=false (by default) reduces to a single value:

      $> curl

    using group=true, you get a separate reduce value for each unique key in the map - that is, all values which share the same key are grouped together and reduced to a single value

      $>  curl

    group_level=N queries run one reduce query for each interval on a set of intervals as defined by the level

      $> curl

    you'd get a reduce query run for each unique set of keys (according to their first two elements)

      $> curl

    group=true is the conceptual equivalent of group_level=exact, so CouchDB runs a reduce per unique key in the map row set

      $> curl

    note that a group=true query runs multiple reduce queries, so you may find it to be slower than you expect

    include doc in view

    each view (NOT REDUCED!) knows which document emitted it. this means that when you query the view, you can always add include_docs=true to the query parameters and get back the doc that emitted the key,value pair

    suppose you have the next docs in db test:

      {"_id" : "1", "f0" : "a", "f1" 1}
      {"_id" : "2", "f0" : "a", "f1" 2}
      {"_id" : "3", "f0" : "b", "f1" 3}
      {"_id" : "4", "f0" : "c", "f1" 4}

    suppose also that you have the view view03 in db test:

        fun({Doc}) ->
          K = proplists:get_value(<<"f0">>, Doc, null),
          V = proplists:get_value(<<"f1">>, Doc, null),
          Emit(K, V)

      $>  curl'key="a"&include_docs=true'