couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject [2/6] git commit: Complete article about how to write design documents.
Date Sun, 18 Nov 2012 19:19:39 GMT
Complete article about how to write design documents.

Also move filter functions description away from changes article and let it just reference
to ddocs section.


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/8cbe836e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/8cbe836e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/8cbe836e

Branch: refs/heads/docs
Commit: 8cbe836eecd1a5b70bf62cc527f8efe894621bfc
Parents: 858c1e0
Author: Alexander Shorin <kxepal@gmail.com>
Authored: Tue Nov 6 01:38:52 2012 +0400
Committer: Dave Cottlehuber <dch@apache.org>
Committed: Sun Nov 18 20:18:33 2012 +0100

----------------------------------------------------------------------
 share/doc/src/changes.rst  |  123 +-------
 share/doc/src/commonjs.rst |    2 +
 share/doc/src/ddocs.rst    |  704 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 708 insertions(+), 121 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/8cbe836e/share/doc/src/changes.rst
----------------------------------------------------------------------
diff --git a/share/doc/src/changes.rst b/share/doc/src/changes.rst
index f0d61ed..1fcec78 100644
--- a/share/doc/src/changes.rst
+++ b/share/doc/src/changes.rst
@@ -77,8 +77,9 @@ Notes:
         [httpd]
         changes_timeout=#millisecs
 
-(7) Reference a filter function from a design document to selectively get
-    updates. See the `section in the book`_ for more information.
+(7) Reference to a :ref:`filter function <filterfun>` from a design document
+    that will filter whole stream emitting only filtered events.
+    See the `section in the book`_ for more information.
 
 (8) Include the associated document with each result. If there are conflicts,
     only the winning revision is returned.
@@ -178,122 +179,4 @@ results.
 Obviously, `... tum tee tum ...` does not appear in the actual response, but
 represents a long pause before the change with seq 6 occurred.  
 
-Filters
-=======
-
-Classic filters
----------------
-
-By default changes feed emits all database documents changes. But if you're
-waiting for some special changes it's not optimal to process each record.
-
-Filters are special design document functions that allows changes feed to emit
-only specific documents that passed filter rules.
-
-Let assume that our database is mailbox and we need to listen changes to handle
-only new mails (documents with status `new`). Assuming that, our filter function
-would looks like next one:
-
-.. code-block:: javascript
-
-  function(doc, req){
-    // we need only `mail` documents
-    if (doc.type != 'mail'){
-      return false;
-    }
-    // we're interested only in `new` ones
-    if (doc.status != 'new'){
-      return false;
-    }
-    return true; // passed!
-  }
- 
-Filter function must return true in fact if document passed all defined rules.
-Now, if you apply this function to changes feed, you're changes feed will emit
-only changes about "new mail"::
-
-
-    GET /somedatabase/_changes?filter=mailbox/new_mail HTTP/1.1
-
-.. code-block:: javascript
-
-    {"results":[
-    {"seq":1,"id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
-    {"seq":7,"id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
-    ],
-    "last_seq":27}
-
-Note, that ``last_seq`` number is 27, but we'd received only two records.
-Seems like any other changes was about documents, that hadn't passed our filter.
-
-Probably, we also need to filter changes feed of our mailbox not only by single
-status value: we also interested in statuses like "spam" to update spam-filter
-heuristic rules, "outgoing" to let mail daemon actually send mails and so on.
-Creating a lot of similar functions that actually does same work isn't good
-idea - so we need dynamic filter to go.
-
-If you have noted, filter functions takes second argument as
-:ref:`request <request_object>` object - it allows to create dynamic filters
-based on query parameters, :ref:`user context <userctx_object>` and more.
-
-The dynamic version of our filter now will be next:
-
-.. code-block:: javascript
-
-  function(doc, req){
-    // we need only `mail` documents
-    if (doc.type != 'mail'){
-      return false;
-    }
-    // we're interested only in requested status
-    if (doc.status != req.query.status){
-      return false;
-    }
-    return true; // passed!
-  }
-
-and now we have pass `status` query parameter in request to let filter match
-only required documents::
-
-    GET /somedatabase/_changes?filter=mailbox/by_status&status=new HTTP/1.1
-
-.. code-block:: javascript
-
-    {"results":[
-    {"seq":1,"id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
-    {"seq":7,"id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
-    ],
-    "last_seq":27}
-
-and we can change filter behavior with easy::
-
-    GET /somedatabase/_changes?filter=mailbox/by_status&status=spam HTTP/1.1
-
-.. code-block:: javascript
-
-    {"results":[
-    {"seq":11,"id":"8960e91220798fc9f9d29d24ed612e0d","changes":[{"rev":"3-cc6ff71af716ddc2ba114967025c0ee0"}]},
-    ],
-    "last_seq":27}
-
-
-Combining filters with `continuous` feed allows to create powerful event-driven
-systems.
-
-View filters
-------------
-
-View filters are the same as classic one with one small difference: they used
-view map function instead to filter changes feed. Each time a value could be
-emitted, a change is returned. This allows to avoid creating filter functions
-that are mostly does same works as views.
-
-To use them just specify `_view` value for ``filter`` parameter and
-`designdoc/viewname` for ``view`` one::
-
-    GET /somedatabase/_changes?filter=_view&view=dname/viewname  HTTP/1.1
-
-.. note:: Since view filters are uses map function as filter they couldn't have
-   dynamic behavior since :ref:`request object<request_object>` is not available
-
 .. _section in the book: http://books.couchdb.org/relax/reference/change-notifications

http://git-wip-us.apache.org/repos/asf/couchdb/blob/8cbe836e/share/doc/src/commonjs.rst
----------------------------------------------------------------------
diff --git a/share/doc/src/commonjs.rst b/share/doc/src/commonjs.rst
index 81ef310..ff31095 100644
--- a/share/doc/src/commonjs.rst
+++ b/share/doc/src/commonjs.rst
@@ -10,6 +10,8 @@
 .. License for the specific language governing permissions and limitations under
 .. the License.
 
+.. _commonjs:
+
 CommonJS support for map functions
 ==================================
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/8cbe836e/share/doc/src/ddocs.rst
----------------------------------------------------------------------
diff --git a/share/doc/src/ddocs.rst b/share/doc/src/ddocs.rst
index 4dd9466..89fa62c 100644
--- a/share/doc/src/ddocs.rst
+++ b/share/doc/src/ddocs.rst
@@ -10,10 +10,712 @@
 .. License for the specific language governing permissions and limitations under
 .. the License.
 
+.. default-domain:: js
+
 .. _ddocs:
 
+===========
 Design Docs
 ===========
 
-To be added: information on writing design docs, including views.
+In this section we'll show how to write design document in context of the
+:ref:`JavaScript Query Server <queryserver_js>`.
+
+But before we start to write our first function, take a look at the list of
+common objects that will be used during our code journey - we'll made big deal
+with them for each function:
+
+- :ref:`Database information object <dbinfo_object>`
+- :ref:`Request object <request_object>`
+- :ref:`Response object <response_object>`
+- :ref:`UserCtx object <userctx_object>`
+- :ref:`Database Security object <security_object>`
+- :ref:`Guide to JavaScript Query Server <queryserver_js>`
+
+.. _viewfun:
+
+View functions
+==============
+
+Views are the primary tool used for querying and reporting on CouchDB databases.
+
+.. _mapfun:
+
+Map functions
+-------------
+
+.. function:: mapfun(doc)
+
+   :param doc: Processed document object.
+
+Map functions should take a single argument as document object and emits pair
+values also known as `key` and `value`. Since JavaScript doesn't support
+generators and ``yield`` statement it got emulated via :func:`emit`:
+
+.. code-block:: javascript
+
+    function(doc){
+      if (!doc.tags || !isArray(doc.tags) || !doc.type || doc.type != 'post'){
+        return;
+      }
+      for (var idx in doc.tags){
+        emit(doc.tags[idx].toLower(), 1);
+      }
+    }
+
+In this example the map function produces documents view by tag if they
+has `tags` attribute as array and `type` attribute with "post" value. Note that
+:func:`emit` function could be called for multiple times, so same document
+will be available by several `keys`.
+
+Also keep in mind that each document is *sealed* to prevent situation when one
+map function changes document state and the other one received his modified
+version.
+
+Yes, as was mentioned above, map function doesn't executes alone. Instead of
+that each document is processed by group of map functions from all views of
+related design document. This means that if you trigger index update for one
+view in ddoc, all others will get up-to-dated too.
+
+Since `1.1.0` release `map` function gains support for
+:ref:`CommonJS <commonjs>` modules and access to :func:`require` function.
+
+.. _reducefun:
+
+Reduce and rereduce functions
+-----------------------------
+
+.. function:: redfun(keys, values[, rereduce])
+
+   :param keys: Array of pairs docid-key for related map function result.
+                Always ``null`` if rereduce is running (has ``true`` value).
+   :param values: Array of map function result values.
+   :param rereduce: Boolean sign of rereduce run.
+
+   :return: Reduces `values`
+
+Reduce functions takes two required arguments of keys and values lists - the
+result of the related map function - and optional third one which signs if
+`rereduce` mode is active or not. `Rereduce` is using for additional reduce
+values list, so when it is ``true`` there is no information about related `keys`
+(first argument is ``null``).
+
+Note, that if produced result by `reduce` function is longer than initial
+values list then an Query Server error will be raised. However, this behavior
+could be disabled by setting ``reduce_limit`` config option to ``false``:
+
+.. code-block:: ini
+
+   [query_server_config]
+   reduce_limit = false
+
+While disabling ``reduce_limit`` might be useful for debug proposes, remember,
+that main task of reduce functions is to *reduce* mapped result, not to make it
+even bigger.
+
+Also CouchDB has three built-in reduce functions. These are implemented in
+Erlang and run right inside CouchDB, so they are much faster than the equivalent
+JavaScript functions: ``_sum``, ``_count`` and ``_stats``. Their equivalents in
+JavaScript below:
+
+.. code-block:: javascript
+
+    // could be replaced by _sum
+    function(keys, values){
+      sum(values);
+    }
+
+    // could be replaced by _count
+    function(keys, values, rereduce){
+      if (rereduce){
+        return sum(values);
+      } else {
+        return values.length;
+      }
+    }
+
+    // could be replaced by _stats
+    function(keys, values, rereduce){
+      return {
+        'sum': sum(values),
+        'min': Math.min.apply(null, values),
+        'max': Math.max.apply(null, values),
+        'count': values.length,
+        'sumsqr': (function(){
+          _sumsqr = 0;
+          for(var idx in values){
+            _sumsqr += values[idx] * values[idx];
+          }
+          return _sumsqr;
+        })(),
+      }
+    }
+
+.. note:: **Why reduce functions has no support of CommonJS modules?**
+
+   While `map` functions has limited access to stored modules through
+   :func:`require` function there is no such feature for `reduce` functions.
+   The reason lies deep inside in mechanism how `map` and `reduce` functions
+   are processed by Query Server. Let's take a look on `map` functions first:
+
+   #. CouchDB sends all `map` functions for processed design document to
+      Query Server.
+   #. Query Server handles them one by one, compiles and puts into inner stack.
+   #. After all `map` functions had been proceeded, CouchDB starts to send
+      reamain documents to index one by one.
+   #. Query Server handles document object and apply to him every function from
+      inner stack. Their emitted result joins into single array and pulls back
+      to CouchDB.
+
+   Now let's see how `reduce` functions are handled:
+
+   #. CouchDB sends *as single command* list of available `reduce` functions
+      with result list of key-value pairs that was previously received as
+      result of `map` functions work.
+   #. Query Server compiles reduce functions and applies them to key-value
+      lists. Reduced result sends back to CouchDB.
+
+   As you may note, `reduce` functions been applied in single shot while
+   `map` ones are applies in iterative way per each document. This means that
+   it's possible for `map` functions to precompile CommonJS library and use it
+   during whole view processing, but for `reduce` functions it will be
+   compiled again and again for each view result reducing which may leads to
+   performance degradation (`reduce` function are already does hard work to make
+   large result smaller).
+
+
+.. _showfun:
+
+Show functions
+==============
+
+.. function:: showfun(doc, req)
+
+   :param doc: Processed document, may be omitted.
+   :param req: :ref:`Request object <request_object>`.
+
+   :return: :ref:`Response object <response_object>`
+   :rtype: object or string
+
+Show functions are used to represent documents in various formats, commonly as
+HTML page with nicer formatting.
+
+Basic example of show function could be:
+
+.. code-block:: javascript
+
+    function(doc, req){
+      if (doc){
+        return "Hello from " + doc._id + "!";
+      } else {
+        return "Hello, world!";
+      }
+    }
+
+Also, there is more simple way to return json encoded data:
+
+.. code-block:: javascript
+
+    function(doc, req){
+      return {
+        'json': {
+          'id': doc['_id'],
+          'rev': doc['_rev']
+        }
+      }
+    }
+
+
+and even files (this one is CouchDB logo):
+
+.. code-block:: javascript
+
+    function(doc, req){
+      return {
+        'headers': {
+          'Content-Type' : 'image/png',
+        },
+        'base64': ''.concat(
+          'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV',
+          'BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/',
+          'AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7',
+          '/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6',
+          'wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA',
+          'AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5',
+          'zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx',
+          'vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT',
+          'LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII=')
+      }
+    }
+
+But what if you need to represent data in different formats by single function?
+Functions :func:`registerType` and :func:`provides` are your the best friends in
+that question:
+
+.. code-block:: javascript
+
+    function(doc, req){
+      provides('json', function(){
+        return {'json': doc}
+      });
+      provides('html', function(){
+        return '<pre>' + toJSON(doc) + '</pre>'
+      })
+      provides('xml', function(){
+        return {
+          'headers': {'Content-Type': 'application/xml'},
+          'body' : ''.concat(
+            '<?xml version="1.0" encoding="utf-8"?>\n',
+            '<doc>',
+            (function(){
+              escape = function(s){
+                return s.replace(/&quot;/g, '"')
+                        .replace(/&gt;/g, '>')
+                        .replace(/&lt;/g, '<')
+                        .replace(/&amp;/g, '&');
+              };
+              var content = '';
+              for(var key in doc){
+                if(!doc.hasOwnProperty(key)) continue;
+                var value = escape(toJSON(doc[key]));
+                var key = escape(key);
+                content += ''.concat(
+                  '<' + key + '>',
+                  value
+                  '</' + key + '>'
+                )
+              }
+              return content;
+            })(),
+            '</doc>'
+          )
+        }
+      })
+      registerType('text-json', 'text/json')
+      provides('text-json', function(){
+        return toJSON(doc);
+      })
+    }
+
+This function may return `html`, `json` , `xml` or our custom `text json` format
+representation of same document object with same processing rules. Probably,
+the `xml` provider in our function needs in more care to handle nested objects
+and keys with invalid characters, but you've got the idea!
+
+.. seealso::
+
+   CouchDB Wiki:
+    - `Showing Documents <http://wiki.apache.org/couchdb/Formatting_with_Show_and_List#Showing_Documents>`_
+
+   CouchDB Guide:
+     - `Show Functions <http://guide.couchdb.org/editions/1/en/show.html>`_
+
+
+.. _listfun:
+
+List functions
+==============
+
+.. function:: listfun(head, req)
+
+   :param head: :ref:`view_head_info_object`
+   :param req: :ref:`Request object <request_object>`.
+
+   :return: Last chunk.
+   :rtype: string
+
+While :ref:`showfun` are used to customize document presentation, :ref:`listfun`
+are used for same propose, but against :ref:`viewfun` result.
+
+Next list function formats view and represent in as very simple HTML page:
+
+.. code-block:: javascript
+
+    function(head, req){
+      start({
+        'headers': {
+          'Content-Type': 'text/html'
+        }
+      });
+      send('<html><body><table>');
+      send('<tr><th>ID</th><th>Key</th><th>Value</th></tr>')
+      while(row = getRow()){
+        send(''.concat(
+          '<tr>',
+          '<td>' + toJSON(row.id) + '</td>',
+          '<td>' + toJSON(row.key) + '</td>',
+          '<td>' + toJSON(row.value) + '</td>',
+          '</tr>'
+        ));
+      }
+      send('</table></body></html>');
+    }
+
+Probably, you'd better to think about templates, styles and more other things to
+show data in more nicer way, but this is good point to start from. Note, that
+you may use there :func:`registerType` and :func:`provides` functions in same
+way as for :ref:`showfun`!
+
+.. seealso::
+
+   CouchDB Wiki:
+    - `Listing Views with CouchDB 0.10 and later <http://wiki.apache.org/couchdb/Formatting_with_Show_and_List#Listing_Views_with_CouchDB_0.10_and_later>`_
+
+   CouchDB Guide:
+    - `Transforming Views with List Functions <http://guide.couchdb.org/draft/transforming.html>`_
+
+
+.. _updatefun:
+
+Update functions
+================
+
+.. function:: updatefun(doc, req)
+
+   :param doc: Update function target document.
+   :param req: :ref:`request_object`
+
+   :returns: Two-element array: the first element is the (updated or new)
+             document, which is committed to the database. If the first element
+             is ``null`` no document will be committed to the database.
+             If you are updating an existing, it should already have an ``_id``
+             set, and if you are creating a new document, make sure to set its
+             ``_id`` to something, either generated based on the input or the
+             ``req.uuid`` provided. The second element is the response that will
+             be sent back to the caller.
+
+Update handlers are functions that clients can request to invoke server-side
+logic that will create or update a document. This feature allows a range of use
+cases such as providing a server-side last modified timestamp, updating
+individual fields in a document without first getting the latest revision, etc.
+
+When the request to an update handler includes a document ID in the URL, the
+server will provide the function with the most recent version of that document.
+You can provide any other values needed by the update handler function via the
+``POST``/``PUT`` entity body or query string parameters of the request.
+
+The basic example that demonstrates all use-cases of update handlers below:
+
+.. code-block:: javascript
+
+    function(doc, req){
+        if (!doc){
+            if ('id' in req){
+                // create new document
+                return [{'_id': req['id']}, 'New World']
+            }
+            // change nothing in database
+            return [null, 'Empty World']
+        }
+        doc['world'] = 'hello';
+        doc['edited_by'] = req['userCtx']['name']
+        return [doc, 'Edited World!']
+    }
+
+.. seealso::
+
+   CouchDB Wiki:
+    - `Document Update Handlers <http://wiki.apache.org/couchdb/Document_Update_Handlers>`_
+
+
+.. _filterfun:
+
+Filter functions
+================
+
+.. function:: filterfun(doc, req)
+
+   :param doc: Processed document object.
+   :param req: :ref:`request_object`
+   :return: Boolean value: ``true`` means that `doc` passes the filter rules,
+            ``false`` that not.
+
+Filter functions are mostly acts like :ref:`showfun` and :ref:`listfun`: they
+formats, but more correctly to say, they *filters* :ref:`changes feed<changes>`.
+
+Classic filters
+---------------
+
+By default changes feed emits all database documents changes. But if you're
+waiting for some special changes this is not optimal to process each record.
+
+Filters are special design document functions that allows changes feed to emit
+only specific documents that passed filter rules.
+
+Lets assume that our database is mailbox and we need to to handle only new mails
+(documents with status `new`) events. Assuming that, our filter function
+will looks like next one:
+
+.. code-block:: javascript
+
+  function(doc, req){
+    // we need only `mail` documents
+    if (doc.type != 'mail'){
+      return false;
+    }
+    // we're interested only in `new` ones
+    if (doc.status != 'new'){
+      return false;
+    }
+    return true; // passed!
+  }
+ 
+Filter functions must return ``true`` in fact if document passed all defined
+rules. Now, if you apply this function to changes feed it will emit only changes
+about "new mails"::
+
+    GET /somedatabase/_changes?filter=mailbox/new_mail HTTP/1.1
+
+.. code-block:: javascript
+
+    {"results":[
+    {"seq":1,"id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
+    {"seq":7,"id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
+    ],
+    "last_seq":27}
+
+Note, that ``last_seq`` number is 27, but we'd received only two records.
+Seems like any other changes was about documents that hasn't passed our filter.
+
+Probably, we also need to filter changes feed of our mailbox not only by single
+status value: we're also interested in statuses like "spam" to update
+spam-filter heuristic rules, "outgoing" to let mail daemon actually send mails
+and so on. Creating a lot of similar functions that actually does similar work
+isn't good idea - so we need dynamic filter to go.
+
+If you have noted, filter functions takes second argument as
+:ref:`request <request_object>` object - it allows to create dynamic filters
+based on query parameters, :ref:`user context <userctx_object>` and more.
+
+The dynamic version of our filter now will be next:
+
+.. code-block:: javascript
+
+  function(doc, req){
+    // we need only `mail` documents
+    if (doc.type != 'mail'){
+      return false;
+    }
+    // we're interested only in requested status
+    if (doc.status != req.query.status){
+      return false;
+    }
+    return true; // passed!
+  }
+
+and now we have pass `status` query parameter in request to let filter match
+only required documents::
+
+    GET /somedatabase/_changes?filter=mailbox/by_status&status=new HTTP/1.1
+
+.. code-block:: javascript
+
+    {"results":[
+    {"seq":1,"id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
+    {"seq":7,"id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
+    ],
+    "last_seq":27}
+
+and we can change filter behavior with easy::
+
+    GET /somedatabase/_changes?filter=mailbox/by_status&status=spam HTTP/1.1
+
+.. code-block:: javascript
+
+    {"results":[
+    {"seq":11,"id":"8960e91220798fc9f9d29d24ed612e0d","changes":[{"rev":"3-cc6ff71af716ddc2ba114967025c0ee0"}]},
+    ],
+    "last_seq":27}
+
+
+Combining filters with `continuous` feed allows to create powerful event-driven
+systems.
+
+View filters
+------------
+
+View filters are the same as classic one with one small difference: they uses
+views `map` function instead to `filter` one to process the changes feed. Each
+time when a key-value pair could be emitted, a change is returned. This allows
+to avoid creating filter functions that are mostly does same works as views.
+
+To use them just specify `_view` value for ``filter`` parameter and
+`designdoc/viewname` for ``view`` one::
+
+    GET /somedatabase/_changes?filter=_view&view=dname/viewname  HTTP/1.1
+
+.. note::
+
+   Since view filters are uses `map` functions as filter they couldn't
+   have dynamic behavior since :ref:`request object<request_object>` is not
+   available.
+
+.. seealso::
+
+   CouchDB Guide:
+    - `Guide to filter change notification <http://guide.couchdb.org/draft/notifications.html#filters>`_
+
+   CouchDB Wiki:
+    - `Filtered replication <http://wiki.apache.org/couchdb/Replication#Filtered_Replication>`_
+
+
+.. _vdufun:
+
+Validate document update functions
+==================================
+
+.. function:: validatefun(newDoc, oldDoc, userCtx, secObj)
+
+   :param newDoc: New version of document that will be stored.
+   :param oldDoc: Previous version of document that is already stored.
+   :param userCtx: :ref:`userctx_object`
+   :param secObj: :ref:`security_object`
+
+   :throws: ``forbidden`` error to gracefully prevent document storing.
+
+To perform validate operations on document saving there is special design
+function type called `validate_doc_update`.
+
+Instead of thousands words take a look on next good example of validate
+function - this function is uses in ``_design/_auth`` ddoc from `_users`
+database to control users documents required field set and modification
+permissions:
+
+.. code-block:: javascript
+
+    function(newDoc, oldDoc, userCtx, secObj) {
+        if (newDoc._deleted === true) {
+            // allow deletes by admins and matching users
+            // without checking the other fields
+            if ((userCtx.roles.indexOf('_admin') !== -1) ||
+                (userCtx.name == oldDoc.name)) {
+                return;
+            } else {
+                throw({forbidden: 'Only admins may delete other user docs.'});
+            }
+        }
+
+        if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') {
+            throw({forbidden : 'doc.type must be user'});
+        } // we only allow user docs for now
+
+        if (!newDoc.name) {
+            throw({forbidden: 'doc.name is required'});
+        }
+
+        if (!newDoc.roles) {
+            throw({forbidden: 'doc.roles must exist'});
+        }
+
+        if (!isArray(newDoc.roles)) {
+            throw({forbidden: 'doc.roles must be an array'});
+        }
+
+        if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {
+            throw({
+                forbidden: 'Doc ID must be of the form org.couchdb.user:name'
+            });
+        }
+
+        if (oldDoc) { // validate all updates
+            if (oldDoc.name !== newDoc.name) {
+                throw({forbidden: 'Usernames can not be changed.'});
+            }
+        }
+
+        if (newDoc.password_sha && !newDoc.salt) {
+            throw({
+                forbidden: 'Users with password_sha must have a salt.' +
+                    'See /_utils/script/couch.js for example code.'
+            });
+        }
+
+        var is_server_or_database_admin = function(userCtx, secObj) {
+            // see if the user is a server admin
+            if(userCtx.roles.indexOf('_admin') !== -1) {
+                return true; // a server admin
+            }
+
+            // see if the user a database admin specified by name
+            if(secObj && secObj.admins && secObj.admins.names) {
+                if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
+                    return true; // database admin
+                }
+            }
+
+            // see if the user a database admin specified by role
+            if(secObj && secObj.admins && secObj.admins.roles) {
+                var db_roles = secObj.admins.roles;
+                for(var idx = 0; idx < userCtx.roles.length; idx++) {
+                    var user_role = userCtx.roles[idx];
+                    if(db_roles.indexOf(user_role) !== -1) {
+                        return true; // role matches!
+                    }
+                }
+            }
+
+            return false; // default to no admin
+        }
+
+        if (!is_server_or_database_admin(userCtx, secObj)) {
+            if (oldDoc) { // validate non-admin updates
+                if (userCtx.name !== newDoc.name) {
+                    throw({
+                        forbidden: 'You may only update your own user document.'
+                    });
+                }
+                // validate role updates
+                var oldRoles = oldDoc.roles.sort();
+                var newRoles = newDoc.roles.sort();
+
+                if (oldRoles.length !== newRoles.length) {
+                    throw({forbidden: 'Only _admin may edit roles'});
+                }
+
+                for (var i = 0; i < oldRoles.length; i++) {
+                    if (oldRoles[i] !== newRoles[i]) {
+                        throw({forbidden: 'Only _admin may edit roles'});
+                    }
+                }
+            } else if (newDoc.roles.length > 0) {
+                throw({forbidden: 'Only _admin may set roles'});
+            }
+        }
+
+        // no system roles in users db
+        for (var i = 0; i < newDoc.roles.length; i++) {
+            if (newDoc.roles[i][0] === '_') {
+                throw({
+                    forbidden:
+                    'No system roles (starting with underscore) in users db.'
+                });
+            }
+        }
+
+        // no system names as names
+        if (newDoc.name[0] === '_') {
+            throw({forbidden: 'Username may not start with underscore.'});
+        }
+
+        var badUserNameChars = [':'];
+
+        for (var i = 0; i < badUserNameChars.length; i++) {
+            if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {
+                throw({forbidden: 'Character `' + badUserNameChars[i] +
+                        '` is not allowed in usernames.'});
+            }
+        }
+    }
+
+.. note::
+
+   The ``return`` statement used only for function exiting and it doesn't
+   controls validation process.
+
+.. seealso::
+
+   CouchDB Guide:
+    - `Validation Functions <http://guide.couchdb.org/editions/1/en/validation.html>`_
 
+   CouchDB Wiki:
+    - `Document Update Validation <http://wiki.apache.org/couchdb/Document_Update_Validation>`_


Mime
View raw message